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 | includes/Editors/BlockEditor.php +249 -135 3.4.2 → 4.9.4 View file →
@@ -2,177 +2,291 @@
2 2
3 3 namespace WPDeveloper\BetterDocs\Editors;
4 4
5 5 if ( ! defined( 'ABSPATH' ) ) {
6 - exit; // Exit if accessed directly
6 + exit; // Exit if accessed directly
7 7 }
8 8
9 -use WPDeveloper\BetterDocs\Editors\BlockEditor\Patterns\BasePattern;
9 +use WPDeveloper\BetterDocs\Core\AIActions;
10 +use WPDeveloper\BetterDocs\Utils\Helper;
11 +use WPDeveloper\BetterDocs\Editors\BlockEditor\FontLoader;
10 12 use WPDeveloper\BetterDocs\Editors\BlockEditor\StyleHandler;
11 -use WPDeveloper\BetterDocs\Editors\BlockEditor\FontLoader;
12 13 use WPDeveloper\BetterDocs\Editors\BlockEditor\TemplatesController;
14 +use WPDeveloper\BetterDocs\Editors\BlockEditor\Patterns\BasePattern;
13 15
14 16 class BlockEditor extends BaseEditor {
15 17
16 - public function init() {
17 - betterdocs()->container->get( TemplatesController::class );
18 + public function init() {
19 + betterdocs()->container->get( TemplatesController::class );
18 20
19 - add_action( 'admin_init', [$this, 'enqueue'] );
20 - add_action( 'admin_enqueue_scripts', [$this, 'enqueue'] );
21 + add_action( 'admin_init', [ $this, 'enqueue' ] );
22 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
21 23
22 - $_blocks_category_hook = version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) ? 'block_categories_all' : 'block_categories';
23 - add_filter( $_blocks_category_hook, [$this, 'register_category'], 9, 1 );
24 + $_blocks_category_hook = version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) ? 'block_categories_all' : 'block_categories';
25 + add_filter( $_blocks_category_hook, [ $this, 'register_category' ], 9, 1 );
24 26
25 - StyleHandler::get_instance();
26 - FontLoader::get_instance();
27 + StyleHandler::get_instance();
28 + FontLoader::get_instance();
27 29
28 - $this->register_blocks();
30 + $this->register_blocks();
29 31
30 - add_action( 'init', [$this, 'pattern_category'] );
32 + add_action( 'init', [ $this, 'pattern_category' ] );
31 33
32 - $this->pattern_initialization();
33 - }
34 + $this->pattern_initialization();
35 + }
34 36
35 - /**
36 - * Pattren category registration.
37 - * @return void
38 - */
39 - public function pattern_category() {
40 - register_block_pattern_category(
41 - 'batterdocs',
42 - array( 'label' => __( 'BetterDocs', 'betterdocs' ) )
43 - );
44 - }
37 + /**
38 + * Pattren category registration.
39 + * @return void
40 + */
41 + public function pattern_category() {
42 + register_block_pattern_category(
43 + 'batterdocs',
44 + [ 'label' => __( 'BetterDocs', 'betterdocs' ) ]
45 + );
46 + }
45 47
46 - /**
47 - * Get all the Pattrens initialized.
48 - * @return void
49 - */
50 - public function pattern_initialization() {
51 - $_api_classes = scandir( __DIR__ . DIRECTORY_SEPARATOR . 'BlockEditor' . DIRECTORY_SEPARATOR . 'Patterns' );
48 + /**
49 + * Get all the Pattrens initialized.
50 + * @return void
51 + */
52 + public function pattern_initialization() {
53 + $_api_classes = scandir( __DIR__ . DIRECTORY_SEPARATOR . 'BlockEditor' . DIRECTORY_SEPARATOR . 'Patterns' );
52 54
53 - if ( ! empty( $_api_classes ) && is_array( $_api_classes ) ) {
54 - foreach ( $_api_classes as $class ) {
55 - if ( $class == '.' || $class == '..' || $class == 'BasePattern.php' || strpos( $class, '.' ) === 0) {
56 - continue;
57 - }
55 + if ( ! empty( $_api_classes ) && is_array( $_api_classes ) ) {
56 + foreach ( $_api_classes as $class ) {
57 + if ( $class == '.' || $class == '..' || $class == 'BasePattern.php' || strpos( $class, '.' ) === 0 ) {
58 + continue;
59 + }
58 60
59 - $classname = basename( $class, '.php' );
60 - $classname = '\\' . __NAMESPACE__ . "\\BlockEditor\\Patterns\\$classname";
61 - $patterns_class = betterdocs()->container->get( $classname );
61 + $classname = basename( $class, '.php' );
62 + $classname = '\\' . __NAMESPACE__ . "\\BlockEditor\\Patterns\\$classname";
63 + $patterns_class = betterdocs()->container->get( $classname );
62 64
63 - if ( $patterns_class instanceof BasePattern ) {
64 - $patterns_class->pattern_category();
65 - $patterns_class->register();
66 - }
67 - }
68 - }
69 - }
65 + if ( $patterns_class instanceof BasePattern ) {
66 + $patterns_class->pattern_category();
67 + $patterns_class->register();
68 + }
69 + }
70 + }
71 + }
70 72
71 - public function admin_init() {
72 - $blocks = $this->get_blocks();
73 + public function admin_init() {
74 + $blocks = $this->get_blocks();
73 75
74 - if ( empty( $blocks ) || ! is_array( $blocks ) ) {
75 - return;
76 - }
76 + if ( empty( $blocks ) || ! is_array( $blocks ) ) {
77 + return;
78 + }
77 79
78 - foreach ( $blocks as $block_name => $block ) {
79 - if ( isset( $block['object'] ) ) {
80 - $block_object = betterdocs()->container->get( $block['object'] );
80 + foreach ( $blocks as $block_name => $block ) {
81 + if ( isset( $block['object'] ) ) {
82 + $block_object = betterdocs()->container->get( $block['object'] );
81 83
82 - if ( ! $block_object->can_enable() ) {
83 - continue;
84 - }
84 + if ( ! $block_object->can_enable() ) {
85 + continue;
86 + }
85 87
86 - $block_object->register_scripts();
87 - }
88 - }
89 - }
88 + $block_object->register_scripts();
89 + }
90 + }
91 + }
90 92
91 - /**
92 - * Only for Admin Add/Edit Pages
93 - */
94 - public function enqueue( $hook ) {
95 - $editor = 'core/edit-post';
96 - if ( $hook == 'site-editor.php' || ( $hook == 'themes.php' && isset( $_GET['page'] ) && $_GET['page'] == 'gutenberg-edit-site' ) ) {
97 - $editor = 'core/edit-site';
98 - }
93 + /**
94 + * Only for Admin Add/Edit Pages
95 + */
96 + public function enqueue( $hook ) {
97 + $editor = 'core/edit-post';
98 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection.
99 + $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
100 + if ( $hook === 'site-editor.php' || ( $hook === 'themes.php' && $page === 'gutenberg-edit-site' ) ) {
101 + $editor = 'core/edit-site';
102 + }
99 103
100 - $this->assets->register( 'betterdocs-blocks-editor-controls', 'blocks/controls.css' );
101 - $this->assets->register( 'betterdocs-blocks-editor', 'blocks/style-editor.css', ['betterdocs-blocks-editor-controls'] );
102 - $this->assets->register( 'betterdocs-blocks-editor', 'blocks/editor.js', ['betterdocs-blocks-style-handler'] );
103 - $this->assets->enqueue( 'betterdocs-blocks-actions', 'blocks/actions.js', [] );
104 - $this->assets->localize( 'betterdocs-blocks-editor', 'betterDocsBlocksHelper', [
105 - 'is_pro_active' => betterdocs()->is_pro_active(),
106 - 'resturl' => get_rest_url(),
107 - 'editorType' => $editor
108 - ] );
104 + $this->assets->register( 'betterdocs-blocks-editor-controls', 'blocks/controls.css' );
105 + $this->assets->register( 'betterdocs-blocks-editor', 'blocks/style-editor.css', [ 'betterdocs-blocks-editor-controls' ] );
106 + $this->assets->register( 'betterdocs-blocks-editor', 'blocks/editor.js', [ 'betterdocs-blocks-style-handler' ] );
107 + $this->assets->enqueue( 'betterdocs-blocks-actions', 'blocks/actions.js', [] );
108 + $this->assets->localize(
109 + 'betterdocs-blocks-editor',
110 + 'betterDocsBlocksHelper',
111 + array_merge([
112 + 'is_pro_active' => betterdocs()->is_pro_active(),
113 + 'is_woocommerce_active' => class_exists( 'WooCommerce' ),
114 + 'resturl' => get_rest_url(),
115 + 'editorType' => $editor,
116 + 'betterdocs_glossaries' => Helper::get_glossaries(),
117 + 'docsIcon' => isset( betterdocs()->settings->get( 'docs_list_icon' )['url'] ) ? betterdocs()->settings->get( 'docs_list_icon' )['url'] : '',
118 + 'settings' => $this->block_setting_defaults(),
119 + 'aiActions' => $this->ai_actions_catalog()
120 + ], betterdocs()->settings->chatbot_active_localize())
121 + );
109 122
110 - if ( $hook == 'post-new.php' || $hook == 'post.php' || $hook == 'site-editor.php' ) {
111 - $this->assets->enqueue( 'fontpicker-default-theme', 'vendor/css/fonticonpicker.base-theme.react.css' );
112 - $this->assets->enqueue( 'fontpicker-material-theme', 'vendor/css/fonticonpicker.material-theme.react.css' );
113 - }
114 - }
123 + if ( $hook == 'post-new.php' || $hook == 'post.php' || $hook == 'site-editor.php' ) {
124 + $this->assets->enqueue( 'fontpicker-default-theme', 'vendor/css/fonticonpicker.base-theme.react.css' );
125 + $this->assets->enqueue( 'fontpicker-material-theme', 'vendor/css/fonticonpicker.material-theme.react.css' );
126 + }
127 + }
115 128
116 - /**
117 - * Add a block category
118 - *
119 - * @param $block_categories array
120 - *
121 - * @return array
122 - */
123 - public function register_category( array $block_categories ): array {
124 - $categories_slugs = wp_list_pluck( $block_categories, 'slug' );
129 + /**
130 + * Settings-panel values a block uses as the default for its own controls.
131 + *
132 + * A block control that has never been touched should show — and render — what
133 + * the Settings panel says, so switching a feature on there is enough and the
134 + * per-block control is only for overriding one instance. The editor cannot read
135 + * `betterdocs_settings` (the REST route behind it needs `edit_docs_settings`,
136 + * which an author does not have), so the values are handed over at enqueue time.
137 + *
138 + * Nested deliberately: wp_localize_script casts every *top-level* scalar to a
139 + * string, so a top-level `false` would arrive in JS as `""` and a `true` as
140 + * `"1"`. Inside a sub-array the values are JSON-encoded and keep their types,
141 + * which is what a boolean block attribute needs.
142 + *
143 + * Keys mirror the Settings keys exactly, so the JS side needs no translation
144 + * table and a mismatch is obvious on sight.
145 + *
146 + * @return array
147 + */
148 + protected function block_setting_defaults() {
149 + $settings = betterdocs()->settings;
125 150
126 - return in_array( 'betterdocs', $categories_slugs, true ) ? $block_categories : array_merge( [[
127 - 'slug' => 'betterdocs',
128 - 'title' => __( 'Betterdocs', 'betterdocs' )
129 - ]], $block_categories );
130 - }
151 + $booleans = [
152 + 'enable_estimated_reading_time',
153 + 'enable_ai_actions',
154 + 'enable_listen',
155 + 'listen_show_speed',
156 + 'ai_actions_copy_page',
157 + 'ai_actions_view_markdown',
158 + 'ai_actions_chatgpt',
159 + 'ai_actions_claude',
160 + 'ai_actions_gemini',
161 + 'ai_actions_perplexity',
162 + 'ai_actions_grok'
163 + ];
131 164
132 - /**
133 - * Get Blocks
134 - *
135 - * @since 2.5.0
136 - * @return array<array>
137 - */
138 - public function get_blocks() {
139 - $config_array = require_once BETTERDOCS_ABSPATH . 'includes/blocks.php';
140 - return apply_filters( 'betterdocs_blocks_config', $config_array );
141 - }
165 + $strings = [
166 + 'estimated_reading_time_title',
167 + 'estimated_reading_time_text',
168 + 'singular_estimated_reading_time_text',
169 + 'ai_actions_button_label',
170 + 'ai_actions_prompt_template',
171 + 'listen_button_label'
172 + ];
142 173
143 - public function register_blocks( $enqueue = false ) {
144 - $blocks = $this->get_blocks();
174 + // Kept apart from the strings above so it arrives as a number: the JS side
175 + // divides by it, and "180" would work by coercion while an empty string
176 + // would quietly become 0.
177 + $numbers = [
178 + 'listen_words_per_minute'
179 + ];
145 180
146 - if ( empty( $blocks ) || ! is_array( $blocks ) ) {
147 - return;
148 - }
181 + $defaults = [];
149 182
150 - foreach ( $blocks as $block_name => $block ) {
151 - if ( isset( $block['object'] ) ) {
152 - $block_object = betterdocs()->container->get( $block['object'] );
183 + foreach ( $booleans as $key ) {
184 + $defaults[ $key ] = (bool) $settings->get( $key );
185 + }
153 186
154 - if ( ! $block_object->can_enable() ) {
155 - continue;
156 - }
187 + foreach ( $strings as $key ) {
188 + $defaults[ $key ] = (string) $settings->get( $key );
189 + }
157 190
158 - if ( method_exists( $block_object, 'load_dependencies' ) ) {
159 - $block_object->load_dependencies();
160 - }
191 + foreach ( $numbers as $key ) {
192 + $defaults[ $key ] = (int) $settings->get( $key );
193 + }
161 194
162 - if ( $enqueue && method_exists( $block_object, 'enqueue' ) ) {
163 - $block_object->enqueue( $this->assets );
164 - continue;
165 - }
195 + return $defaults;
196 + }
166 197
167 - if ( method_exists( $block_object, 'inner_blocks' ) ) {
168 - $_inner_blocks = $block_object->inner_blocks();
169 - foreach ( $_inner_blocks as $block_name => $block ) {
170 - $block->register( $this->assets );
171 - }
172 - }
198 + /**
199 + * Everything the editor needs to draw the AI Actions dropdown.
200 + *
201 + * The reading-time block's preview shows the real menu — same order, labels,
202 + * descriptions and icons as the frontend — so an author can see what the action
203 + * toggles do without saving and previewing. There is no post to resolve against
204 + * in the editor, so no hrefs are sent: the preview is deliberately inert.
205 + *
206 + * `caret` rides along because the disclosure arrow is not a registry entry but
207 + * still has to match the button the frontend renders.
208 + *
209 + * @return array
210 + */
211 + protected function ai_actions_catalog() {
212 + if ( ! isset( betterdocs()->ai_actions ) ) {
213 + return [
214 + 'items' => [],
215 + 'caret' => ''
216 + ];
217 + }
173 218
174 - $block_object->register( $this->assets );
175 - }
176 - }
177 - }
219 + return [
220 + 'items' => betterdocs()->ai_actions->catalog(),
221 + 'caret' => AIActions::icon( 'caret' )
222 + ];
223 + }
224 +
225 + /**
226 + * Add a block category
227 + *
228 + * @param $block_categories array
229 + *
230 + * @return array
231 + */
232 + public function register_category( array $block_categories ): array {
233 + $categories_slugs = wp_list_pluck( $block_categories, 'slug' );
234 +
235 + return in_array( 'betterdocs', $categories_slugs, true ) ? $block_categories : array_merge(
236 + [
237 + [
238 + 'slug' => 'betterdocs',
239 + 'title' => __( 'Betterdocs', 'betterdocs' )
240 + ]
241 + ],
242 + $block_categories
243 + );
244 + }
245 +
246 + /**
247 + * Get Blocks
248 + *
249 + * @since 2.5.0
250 + * @return array<array>
251 + */
252 + public function get_blocks() {
253 + $config_array = require_once BETTERDOCS_ABSPATH . 'includes/blocks.php';
254 + return apply_filters( 'betterdocs_blocks_config', $config_array );
255 + }
256 +
257 + public function register_blocks( $enqueue = false ) {
258 + $blocks = $this->get_blocks();
259 +
260 + if ( empty( $blocks ) || ! is_array( $blocks ) ) {
261 + return;
262 + }
263 +
264 + foreach ( $blocks as $block_name => $block ) {
265 + if ( isset( $block['object'] ) ) {
266 + $block_object = betterdocs()->container->get( $block['object'] );
267 +
268 + if ( ! $block_object->can_enable() ) {
269 + continue;
270 + }
271 +
272 + if ( method_exists( $block_object, 'load_dependencies' ) ) {
273 + $block_object->load_dependencies();
274 + }
275 +
276 + if ( $enqueue && method_exists( $block_object, 'enqueue' ) ) {
277 + $block_object->enqueue( $this->assets );
278 + continue;
279 + }
280 +
281 + if ( method_exists( $block_object, 'inner_blocks' ) ) {
282 + $_inner_blocks = $block_object->inner_blocks();
283 + foreach ( $_inner_blocks as $block_name => $block ) {
284 + $block->register( $this->assets );
285 + }
286 + }
287 +
288 + $block_object->register( $this->assets );
289 + }
290 + }
291 + }
178 292 }