PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
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 3.5.0 3.5.1 All 200 releases
← All changes | includes/Plugin.php +169 -8 4.5.44.9.2 View file →
@@ -10,8 +10,10 @@
10 10 use WPDeveloper\BetterDocs\Admin\Customizer\Customizer;
11 11 use WPDeveloper\BetterDocs\Admin\HelpScoutMigration;
12 12 use WPDeveloper\BetterDocs\Admin\ReportEmail;
13 13 use WPDeveloper\BetterDocs\Core\Admin;
14 +use WPDeveloper\BetterDocs\Core\AnalyticsTracker;
15 +use WPDeveloper\BetterDocs\Core\AnalyticsRetention;
14 16 use WPDeveloper\BetterDocs\Core\BaseAPI;
15 17 use WPDeveloper\BetterDocs\Core\Install;
16 18 use WPDeveloper\BetterDocs\Core\KBMigration;
17 19 use WPDeveloper\BetterDocs\Core\Query;
@@ -24,14 +26,19 @@
24 26 use WPDeveloper\BetterDocs\Core\WriteWithAI;
25 27 use WPDeveloper\BetterDocs\Core\ArticleSummary;
26 28 use WPDeveloper\BetterDocs\Core\ArticleQualityScore;
27 29 use WPDeveloper\BetterDocs\Core\UnifiedMetabox;
30 +use WPDeveloper\BetterDocs\Core\DocsAISuite;
28 31 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
29 32 use WPDeveloper\BetterDocs\Dependencies\DI\ContainerBuilder;
30 33 use WPDeveloper\BetterDocs\Editors\Editor;
31 34 use WPDeveloper\BetterDocs\FrontEnd\FrontEnd;
35 +use WPDeveloper\BetterDocs\FrontEnd\PrintTemplate;
32 36 use WPDeveloper\BetterDocs\FrontEnd\SearchExtender;
33 37 use WPDeveloper\BetterDocs\FrontEnd\TemplateTags;
38 +use WPDeveloper\BetterDocs\FrontEnd\WooProductFAQ;
39 +use WPDeveloper\BetterDocs\Abilities\AbilitiesRegistrar;
40 +use WPDeveloper\BetterDocs\Mcp\MCPManager;
34 41 use WPDeveloper\BetterDocs\Modules\StyleHandler as ModulesStyleHandler;
35 42 use WPDeveloper\BetterDocs\Utils\Database;
36 43 use WPDeveloper\BetterDocs\Utils\Enqueue;
37 44 use WPDeveloper\BetterDocs\Utils\Helper;
@@ -125,9 +132,9 @@
125 132 /**
126 133 * Plugin Version
127 134 * @var string
128 135 */
129 - public $version = '4.5.4';
136 + public $version = '4.9.2';
130 137
131 138 /**
132 139 * WriteWithAI Class
133 140 * @var string
@@ -144,9 +151,9 @@
144 151 /**
145 152 * Plugin DB Version
146 153 * @var string
147 154 */
148 - public $db_version = '1.0.1';
155 + public $db_version = '1.0.3';
149 156
150 157 public function __construct() {
151 158 $this->define_constants();
152 159
@@ -158,8 +165,21 @@
158 165 * and version updates check
159 166 */
160 167 $this->container->get( Install::class );
161 168
169 + /**
170 + * Abilities API registrar.
171 + *
172 + * Resolved here, at plugin load, and not from initialize(): both the
173 + * bundled Abilities API and WordPress core's build their registry as a
174 + * lazy singleton and fire `wp_abilities_api_init` from it, at or after
175 + * `init`, the first time anything reads the registry. Our listeners
176 + * therefore have to be attached before that first read, whenever it
177 + * happens — and a container resolve on `init` would already be too late
178 + * for a plugin that reads the registry earlier in the same hook.
179 + */
180 + $this->container->get( AbilitiesRegistrar::class );
181 +
162 182 add_action( 'init', array( $this, 'initialize' ), 0 );
163 183
164 184 /**
165 185 * Initialize API
@@ -187,9 +207,11 @@
187 207 $this->define( 'BETTERDOCS_DB_VERSION', $this->db_version );
188 208 $this->define( 'BETTERDOCS_ABSPATH', dirname( BETTERDOCS_PLUGIN_FILE ) . '/' );
189 209 $this->define( 'BETTERDOCS_ABSURL', plugin_dir_url( BETTERDOCS_PLUGIN_FILE ) );
190 210 $this->define( 'BETTERDOCS_PLUGIN_BASENAME', plugin_basename( BETTERDOCS_PLUGIN_FILE ) );
191 - $this->define( 'BETTERDOCS_BLOCKS_DIRECTORY', BETTERDOCS_ABSPATH . 'assets/blocks/' );
211 + // Compiled block metadata lives under assets/build/ since the Node 24 asset
212 + // restructure; register_block_type() fails silently if this path is wrong.
213 + $this->define( 'BETTERDOCS_BLOCKS_DIRECTORY', BETTERDOCS_ABSPATH . 'assets/build/blocks/' );
192 214 $this->define( 'BETTERDOCS_ROOT_DIR_PATH', plugin_dir_path( BETTERDOCS_PLUGIN_FILE ) );
193 215 $this->define( 'BETTERDOCS_FSE_TEMPLATES_PATH', BETTERDOCS_ROOT_DIR_PATH . 'views/templates/fse' );
194 216
195 217 /**
@@ -198,8 +220,10 @@
198 220 *
199 221 * WPML compatibility with Polylang
200 222 */
201 223 if ( Helper::is_plugin_active( 'polylang/polylang.php' ) ) {
224 + // Polylang's documented integration constant — must use the upstream-defined name.
225 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
202 226 define( 'PLL_WPML_COMPAT', false );
203 227 }
204 228 }
205 229
@@ -210,8 +234,10 @@
210 234 * @param string|bool $value Constant value.
211 235 */
212 236 private function define( $name, $value ) {
213 237 if ( ! defined( $name ) ) {
238 + // Caller passes plugin-prefixed names (BETTERDOCS_*); $name comes from a controlled internal allowlist.
239 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.VariableConstantNameFound
214 240 define( $name, $value );
215 241 }
216 242 }
217 243
@@ -249,8 +275,12 @@
249 275 $this->admin = $this->container->get( Admin::class );
250 276 $this->database = $this->container->get( Database::class );
251 277 $this->settings = $this->container->get( Settings::class );
252 278 $this->analytics = $this->container->get( Analytics::class );
279 + // Free analytics collectors: the frontend view/scroll tracker and the
280 + // daily retention purge. Each self-registers its hooks on construct.
281 + $this->container->get( AnalyticsTracker::class );
282 + $this->container->get( AnalyticsRetention::class );
253 283
254 284 $this->template_helper = $this->container->get( TemplateTags::class );
255 285 $this->customizer = $this->container->get( Customizer::class );
256 286 $this->editor = $this->container->get( Editor::class );
@@ -260,11 +290,28 @@
260 290 // Initialize unified metabox before individual features
261 291 $this->container->get( UnifiedMetabox::class );
262 292 $this->article_quality_score = $this->container->get( ArticleQualityScore::class );
263 293
294 + // Editor "Suggest Categories / Tags / Glossaries" AI actions on the docs
295 + // post type (gated by enable_docs_ai_suite + an OpenAI key inside the class).
296 + $this->container->get( DocsAISuite::class );
297 +
264 298 $this->container->get( Admin::class );
265 299 $this->container->get( Roles::class );
300 +
301 + /**
302 + * MCP transport: rewrite rules, the pretty endpoint, OAuth discovery
303 + * and every REST route. Resolved here rather than at plugin load
304 + * (where the abilities registrar has to be) because its earliest hook
305 + * is `init`, which is what this method already runs on.
306 + */
307 + $this->container->get( MCPManager::class );
308 +
266 309 $this->container->get( ReportEmail::class );
310 + // Usage-analytics collector. Registered here (runs on every request, incl.
311 + // WP-Cron) rather than in Admin so its `betterdocs_insights_data` filter
312 + // callback is present whenever the tracking payload is built.
313 + $this->container->get( \WPDeveloper\BetterDocs\Insights\Collector::class );
267 314 /**
268 315 * Initialize Shortcode
269 316 * Make sure you have listed out all shortcode in shortcode factory.
270 317 */
@@ -272,8 +319,19 @@
272 319
273 320 $this->container->get( FrontEnd::class );
274 321 $this->container->get( SearchExtender::class );
275 322
323 + // Running logo header / page footer for the browser's own Print command.
324 + // Registered unconditionally: it covers every front-end page, including
325 + // the ones that carry no BetterDocs print button.
326 + $this->container->get( PrintTemplate::class );
327 +
328 + /**
329 + * Single-product FAQ rendering (WooCommerce). The class itself bails when
330 + * WooCommerce is inactive or the feature is disabled.
331 + */
332 + $this->container->get( WooProductFAQ::class );
333 +
276 334 do_action( 'betterdocs_init' );
277 335
278 336 $this->editor->init();
279 337 }
@@ -287,8 +345,10 @@
287 345
288 346 /**
289 347 * Filter to adjust the BetterDocs locale to use for translations.
290 348 */
349 + // 'plugin_locale' is a WP-core filter; using its documented name is required.
350 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
291 351 $locale = apply_filters( 'plugin_locale', $locale, $textdomain );
292 352
293 353 if ( file_exists( WP_LANG_DIR . "/$textdomain-" . $locale . '.mo' ) ) {
294 354 unload_textdomain( $textdomain );
@@ -293,10 +353,8 @@
293 353 if ( file_exists( WP_LANG_DIR . "/$textdomain-" . $locale . '.mo' ) ) {
294 354 unload_textdomain( $textdomain );
295 355 load_textdomain( $textdomain, WP_LANG_DIR . "/$textdomain-" . $locale . '.mo' );
296 356 }
297 -
298 - load_plugin_textdomain( $textdomain, false, plugin_basename( dirname( $plugin_file ) ) . '/languages' );
299 357 }
300 358
301 359 /**
302 360 * For AJAX Only
@@ -356,8 +414,12 @@
356 414 wp_safe_redirect( add_query_arg( array( 'page' => 'betterdocs-settings' ), admin_url( 'admin.php' ) ) );
357 415 } else {
358 416 wp_safe_redirect( add_query_arg( array( 'page' => 'betterdocs-setup' ), admin_url( 'admin.php' ) ) );
359 417 }
418 + // This runs at `admin_init` priority 0. Without exiting, the rest of
419 + // admin_init still executes and can mutate the very state the redirect
420 + // target depends on before the browser ever follows the Location header.
421 + exit;
360 422 }
361 423 }
362 424
363 425 /**
@@ -395,8 +457,90 @@
395 457
396 458 return false;
397 459 }
398 460
461 + /**
462 + * Whether Pro provides the real API Documentation screen.
463 + *
464 + * Pro flips this via `betterdocs_pro_has_api_docs`; the class_exists() default
465 + * keeps the gate correct against a Pro build that predates that filter.
466 + *
467 + * @return bool
468 + */
469 + public function has_api_docs() {
470 + return (bool) apply_filters(
471 + 'betterdocs_pro_has_api_docs',
472 + class_exists( '\\WPDeveloper\\BetterDocsPro\\Core\\ApiReferences' )
473 + );
474 + }
475 +
476 + /**
477 + * Whether Free should show its locked API Docs teaser.
478 + *
479 + * Only without Pro. An older Pro has already paid, so they get nothing here —
480 + * they need a plugin update, not an upsell.
481 + *
482 + * @return bool
483 + */
484 + public function show_api_docs_teaser() {
485 + return ! $this->is_pro_active() && ! $this->has_api_docs();
486 + }
487 +
488 + /**
489 + * Whether the real Glossaries screen is available (i.e. Pro is licensing it).
490 + *
491 + * Glossaries ships in Free's code but is Pro-licensed, so unlike API Docs there
492 + * is no separate Pro class to detect — the gate is simply Pro being active. Kept
493 + * behind a filter so Pro/add-ons can flip it explicitly, mirroring
494 + * `betterdocs_pro_has_api_docs`.
495 + *
496 + * @return bool
497 + */
498 + public function has_glossaries() {
499 + return (bool) apply_filters( 'betterdocs_pro_has_glossaries', $this->is_pro_active() );
500 + }
501 +
502 + /**
503 + * Whether Free should show its locked Glossaries teaser.
504 + *
505 + * Only without Pro — an active Pro either has the feature or has simply left it
506 + * disabled in settings; neither case wants an upsell. Mirrors
507 + * show_api_docs_teaser().
508 + *
509 + * @return bool
510 + */
511 + public function show_glossary_teaser() {
512 + return ! $this->is_pro_active() && ! $this->has_glossaries();
513 + }
514 +
515 + /**
516 + * Whether Pro provides the real Content Intelligence screen.
517 + *
518 + * Pro flips this via `betterdocs_pro_has_content_intelligence`; the
519 + * class_exists() default keeps the gate correct against a Pro build that
520 + * predates that filter.
521 + *
522 + * @return bool
523 + */
524 + public function has_content_intelligence() {
525 + return (bool) apply_filters(
526 + 'betterdocs_pro_has_content_intelligence',
527 + class_exists( '\\WPDeveloper\\BetterDocsPro\\Core\\ContentIntelligenceService' )
528 + );
529 + }
530 +
531 + /**
532 + * Whether Free should show its locked Content Intelligence teaser.
533 + *
534 + * Only without Pro. An older Pro has already paid, so they get nothing here —
535 + * they need a plugin update, not an upsell.
536 + *
537 + * @return bool
538 + */
539 + public function show_content_intelligence_teaser() {
540 + return ! $this->is_pro_active() && ! $this->has_content_intelligence();
541 + }
542 +
399 543 public function pro_version() {
400 544 if ( ! $this->is_pro_active() ) {
401 545 return false;
402 546 }
@@ -434,19 +578,32 @@
434 578 }
435 579 }
436 580
437 581 public function is_betterdocs_screen( $hook, $admin_check = true ): bool {
438 - $screens = array(
582 + /**
583 + * Filter the list of admin screen hook suffixes treated as BetterDocs
584 + * screens (controls whether the React admin bundle + styles load).
585 + * Pro/add-ons can register their own React pages here, e.g. the
586 + * Knowledge Base admin page.
587 + *
588 + * @param string[] $screens Full hook suffixes (e.g. betterdocs_page_betterdocs-foo).
589 + */
590 + $screens = apply_filters( 'betterdocs_admin_screens', array(
439 591 'toplevel_page_betterdocs-dashboard',
440 592 'toplevel_page_betterdocs-admin',
441 593 'admin_page_betterdocs-admin',
442 594 'betterdocs_page_betterdocs-admin',
443 595 'betterdocs_page_betterdocs-analytics',
596 + 'betterdocs_page_betterdocs-content-iq',
444 597 'betterdocs_page_betterdocs-settings',
598 + 'betterdocs_page_betterdocs-mcp',
445 599 'betterdocs_page_betterdocs-faq',
446 600 'betterdocs_page_betterdocs-glossaries',
447 - 'betterdocs_page_betterdocs-ai-chatbot'
448 - );
601 + 'betterdocs_page_betterdocs-ai-chatbot',
602 + 'betterdocs_page_betterdocs-api-docs',
603 + 'betterdocs_page_betterdocs-doc-categories',
604 + 'betterdocs_page_betterdocs-doc-tags',
605 + ) );
449 606
450 607 if ( $admin_check ) {
451 608 if ( in_array( $hook, $screens ) ) {
452 609 return true;
@@ -463,12 +620,16 @@
463 620 'toplevel_page_betterdocs-dashboard',
464 621 'admin_page_betterdocs-admin',
465 622 'betterdocs_page_betterdocs-admin',
466 623 'betterdocs_page_betterdocs-settings',
624 + 'betterdocs_page_betterdocs-mcp',
467 625 'betterdocs_page_betterdocs-analytics',
468 626 'betterdocs_page_betterdocs-faq',
469 627 'betterdocs_page_betterdocs-glossaries',
470 628 'betterdocs_page_betterdocs-ai-chatbot',
629 + 'betterdocs_page_betterdocs-api-docs',
630 + 'betterdocs_page_betterdocs-doc-categories',
631 + 'betterdocs_page_betterdocs-doc-tags',
471 632 'edit-docs'
472 633 );
473 634
474 635 $current_screen_id = get_current_screen() != null ? get_current_screen()->id : '';