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
betterdocs / views / widgets / reading-time.php

reading-time.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.4, at views/widgets/reading-time.php

154 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- view template receives variables via extract(); prefixing is impractical.
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
26 if ( ! defined( 'ABSPATH' ) ) {
27 exit;
28 }
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>
154