PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.4
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.4
4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 All 202 releases
← All changes | views/widgets/reading-time.php +147 -4 4.9.2 → 4.9.4 View file →
@@ -1,10 +1,153 @@
1 1 <?php
2 2 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- view template receives variables via extract(); prefixing is impractical.
3 3
4 +/**
5 + * The single-doc meta row, as an Elementor widget.
6 + *
7 + * Emits the same `.betterdocs-doc-meta` wrapper as
8 + * views/templates/parts/doc-meta.php, so the leading and trailing groups land on
9 + * opposite edges and all three controls sit on one centre line without this widget
10 + * shipping any layout CSS.
11 + *
12 + * @var array $attributes
13 + * @var bool $enable_reading_time
14 + * @var bool $enable_listen
15 + * @var array $listen_args Per-instance overrides for Listen::render().
16 + * @var bool $enable_ai_actions
17 + * @var array $ai_actions_args Per-instance overrides for AIActions::render().
18 + * @var bool $is_editing Elementor's canvas or preview iframe.
19 + */
20 +
21 +use WPDeveloper\BetterDocs\Core\AIActions;
22 +use WPDeveloper\BetterDocs\Core\Listen;
23 +use WPDeveloper\BetterDocs\Shortcodes\AIActions as AIActionsShortcode;
24 +use WPDeveloper\BetterDocs\Shortcodes\Listen as ListenShortcode;
25 +
4 26 if ( ! defined( 'ABSPATH' ) ) {
5 27 exit;
6 28 }
7 -$reading_text = $attributes['ert_reading_text'];
8 -$singular_reading_text = $attributes['singular_ert_reading_text'];
9 -$reading_title = $attributes['ert_reading_title'];
10 -echo do_shortcode( '[betterdocs_reading_time singular_reading_text="' . $singular_reading_text . '" reading_text="' . $reading_text . '" reading_title="' . $reading_title . '"]' );
29 +
30 +$reading_time = '';
31 +if ( ! empty( $enable_reading_time ) ) {
32 + // Defaulted rather than dereferenced: Elementor fills in every registered
33 + // control, but a widget saved before one existed can still arrive short of a
34 + // key if the control was renamed or a third party filtered it away.
35 + $att = function ( $key ) use ( $attributes ) {
36 + return isset( $attributes[ $key ] ) ? (string) $attributes[ $key ] : '';
37 + };
38 +
39 + // esc_attr, not raw: these land inside double-quoted shortcode attributes,
40 + // where an unescaped quote would break out of the attribute.
41 + $reading_time = (string) do_shortcode(
42 + '[betterdocs_reading_time'
43 + . ' singular_reading_text="' . esc_attr( $att( 'singular_ert_reading_text' ) ) . '"'
44 + . ' reading_text="' . esc_attr( $att( 'ert_reading_text' ) ) . '"'
45 + . ' reading_title="' . esc_attr( $att( 'ert_reading_title' ) ) . '"]'
46 + );
47 +}
48 +
49 +$listen = '';
50 +if ( ! empty( $enable_listen ) && isset( betterdocs()->listen ) ) {
51 + ob_start();
52 +
53 + if ( ! empty( $is_editing ) && ! betterdocs()->listen->has_listen( get_the_ID(), $listen_args ) ) {
54 + // A template edited against a preview that is not a readable doc resolves to
55 + // nothing — and a widget that renders nothing cannot be seen, selected or
56 + // moved. It reads as broken rather than as empty, so the canvas gets static
57 + // chrome instead. Same reasoning as the AI Actions placeholder below.
58 + $listen_label = ! empty( $attributes['listen_button_label'] )
59 + ? $attributes['listen_button_label']
60 + : betterdocs()->settings->get( 'listen_button_label' );
61 + ?>
62 + <?php
63 + // Spans, not buttons, and that is the whole safety mechanism: the delegated
64 + // handlers in public/betterdocs.js bind to `button.betterdocs-listen-…`, so
65 + // nothing here can reach BetterDocsListen — which is important, because this
66 + // placeholder has no player element for it to measure and open. Same
67 + // arrangement as the AI Actions placeholder below.
68 + ?>
69 + <div class="betterdocs-listen betterdocs-listen-placeholder">
70 + <span class="betterdocs-listen-trigger">
71 + <span class="betterdocs-listen-icon" aria-hidden="true">
72 + <?php echo wp_kses( Listen::icon( 'headphones' ), Listen::svg_kses() ); ?>
73 + </span>
74 + <span class="betterdocs-listen-label"><?php echo esc_html( $listen_label ); ?></span>
75 + </span>
76 + </div>
77 + <?php
78 + } else {
79 + // Handed off by token rather than as attributes: `show_speed` is tri-state
80 + // and a shortcode attribute string cannot express "inherit the setting".
81 + // See ListenShortcode::push_context().
82 + $listen_context = ListenShortcode::push_context( $listen_args );
83 +
84 + echo do_shortcode( '[betterdocs_listen context="' . esc_attr( $listen_context ) . '"]' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rendered template parts, escaped at source.
85 + }
86 +
87 + $listen = trim( (string) ob_get_clean() );
88 +}
89 +
90 +$ai_actions = '';
91 +if ( ! empty( $enable_ai_actions ) && isset( betterdocs()->ai_actions ) ) {
92 + ob_start();
93 +
94 + if ( ! empty( $is_editing ) && ! betterdocs()->ai_actions->has_actions( get_the_ID(), $ai_actions_args ) ) {
95 + // The action list is resolved per doc, so a template edited against a
96 + // preview that is not a readable doc resolves to nothing — and a widget
97 + // that renders nothing cannot be seen, selected or moved. It reads as
98 + // broken rather than as empty, so the canvas gets static chrome instead.
99 + $svg_kses = AIActions::svg_kses();
100 + $button_label = ! empty( $attributes['ai_actions_button_label'] )
101 + ? $attributes['ai_actions_button_label']
102 + : betterdocs()->settings->get( 'ai_actions_button_label' );
103 + ?>
104 + <div class="betterdocs-ai-actions betterdocs-ai-actions-placeholder">
105 + <span class="betterdocs-ai-actions-primary">
106 + <span class="betterdocs-ai-actions-icon" aria-hidden="true">
107 + <?php echo wp_kses( AIActions::icon( 'copy' ), $svg_kses ); ?>
108 + </span>
109 + <span class="betterdocs-ai-actions-label"><?php echo esc_html( $button_label ); ?></span>
110 + </span>
111 + <span class="betterdocs-ai-actions-toggle">
112 + <span class="betterdocs-ai-actions-caret" aria-hidden="true">
113 + <?php echo wp_kses( AIActions::icon( 'caret' ), $svg_kses ); ?>
114 + </span>
115 + </span>
116 + </div>
117 + <?php
118 + } else {
119 + // Handed off by token rather than as attributes: the args carry a
120 + // tri-state `enabled_actions` map and free text that a shortcode
121 + // attribute string cannot round-trip. See AIActionsShortcode::push_context().
122 + $ai_actions_context = AIActionsShortcode::push_context( $ai_actions_args );
123 +
124 + echo do_shortcode( '[betterdocs_ai_actions context="' . esc_attr( $ai_actions_context ) . '"]' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rendered template parts, escaped at source.
125 + }
126 +
127 + $ai_actions = trim( (string) ob_get_clean() );
128 +}
129 +
130 +// Everything switched off, or a doc where nothing resolves: emit no wrapper
131 +// rather than an empty flex row consuming vertical space.
132 +if ( '' === trim( $reading_time ) && '' === $listen && '' === $ai_actions ) {
133 + return;
134 +}
135 +
136 +// One leading box holding both pills, rather than one each: the row's `> *` rule
137 +// zeroes child block margins, and the 8px gap between the pills is the leading
138 +// box's own, so two boxes would space them by the row's wider 16px.
139 +$leading = trim( $reading_time ) . $listen;
140 +?>
141 +<div class="betterdocs-doc-meta">
142 + <?php if ( '' !== $leading ) : ?>
143 + <div class="betterdocs-doc-meta-start">
144 + <?php echo $leading; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rendered template parts, escaped at source. ?>
145 + </div>
146 + <?php endif; ?>
147 +
148 + <?php if ( '' !== $ai_actions ) : ?>
149 + <div class="betterdocs-doc-meta-end">
150 + <?php echo $ai_actions; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rendered template parts, escaped at source. ?>
151 + </div>
152 + <?php endif; ?>
153 +</div>