PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.0
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 3.5.2 All 199 releases
betterdocs / includes / Plugin.php

Plugin.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.0, at includes/Plugin.php

582 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit; // Exit if accessed directly
7 }
8
9 use WPDeveloper\BetterDocs\Admin\Analytics;
10 use WPDeveloper\BetterDocs\Admin\Customizer\Customizer;
11 use WPDeveloper\BetterDocs\Admin\HelpScoutMigration;
12 use WPDeveloper\BetterDocs\Admin\ReportEmail;
13 use WPDeveloper\BetterDocs\Core\Admin;
14 use WPDeveloper\BetterDocs\Core\AnalyticsTracker;
15 use WPDeveloper\BetterDocs\Core\AnalyticsRetention;
16 use WPDeveloper\BetterDocs\Core\BaseAPI;
17 use WPDeveloper\BetterDocs\Core\Install;
18 use WPDeveloper\BetterDocs\Core\KBMigration;
19 use WPDeveloper\BetterDocs\Core\Query;
20 use WPDeveloper\BetterDocs\Core\Request;
21 use WPDeveloper\BetterDocs\Core\Rewrite;
22 use WPDeveloper\BetterDocs\Core\Roles;
23 use WPDeveloper\BetterDocs\Core\Scripts;
24 use WPDeveloper\BetterDocs\Core\Settings;
25 use WPDeveloper\BetterDocs\Core\ShortcodeFactory;
26 use WPDeveloper\BetterDocs\Core\WriteWithAI;
27 use WPDeveloper\BetterDocs\Core\ArticleSummary;
28 use WPDeveloper\BetterDocs\Core\ArticleQualityScore;
29 use WPDeveloper\BetterDocs\Core\UnifiedMetabox;
30 use WPDeveloper\BetterDocs\Core\DocsAISuite;
31 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
32 use WPDeveloper\BetterDocs\Dependencies\DI\ContainerBuilder;
33 use WPDeveloper\BetterDocs\Editors\Editor;
34 use WPDeveloper\BetterDocs\FrontEnd\FrontEnd;
35 use WPDeveloper\BetterDocs\FrontEnd\SearchExtender;
36 use WPDeveloper\BetterDocs\FrontEnd\TemplateTags;
37 use WPDeveloper\BetterDocs\FrontEnd\WooProductFAQ;
38 use WPDeveloper\BetterDocs\Abilities\AbilitiesRegistrar;
39 use WPDeveloper\BetterDocs\Mcp\MCPManager;
40 use WPDeveloper\BetterDocs\Modules\StyleHandler as ModulesStyleHandler;
41 use WPDeveloper\BetterDocs\Utils\Database;
42 use WPDeveloper\BetterDocs\Utils\Enqueue;
43 use WPDeveloper\BetterDocs\Utils\Helper;
44 use WPDeveloper\BetterDocs\Utils\Views;
45
46 final class Plugin {
47 private static $_instance = null;
48 /**
49 * Assets manager
50 *
51 * @var Enqueue
52 */
53 public $assets;
54 /**
55 * View manager
56 *
57 * @var Views
58 */
59 public $views;
60 /**
61 * Container Manager
62 *
63 * @var Container
64 */
65 public $container;
66 /**
67 * Editor Manager
68 * @var Editor
69 */
70 public $editor;
71 /**
72 * Helper class
73 * @var Helper
74 */
75 public $helper;
76 /**
77 * KBMigration class
78 * @var KBMigration
79 */
80 public $kbmigration;
81 /**
82 * KBMigration class
83 * @var Admin
84 */
85 public $admin;
86 /**
87 * Helper class
88 * @var Database
89 */
90 public $database;
91 /**
92 * Helper class
93 * @var Settings
94 */
95 public $settings;
96 /**
97 * Helper class
98 * @var TemplateTags
99 */
100 public $template_helper;
101 /**
102 * Article Summary class
103 * @var ArticleSummary
104 */
105 public $article_summary;
106 /**
107 * Customizer class
108 * @var Customizer
109 */
110 public $customizer;
111 /**
112 * Query class
113 * @var Query
114 */
115 public $query;
116 /**
117 * Rewrite Class
118 * @var Rewrite
119 */
120 public $rewrite;
121 /**
122 * Request Class
123 * @var Request
124 */
125 public $request;
126 /**
127 * Analytics Class
128 * @var Analytics
129 */
130 public $analytics;
131 /**
132 * Plugin Version
133 * @var string
134 */
135 public $version = '4.9.0';
136
137 /**
138 * WriteWithAI Class
139 * @var string
140 */
141 public $ai_autowrtie;
142 public $backgroundProccessor;
143
144 /**
145 * ArticleQualityScore Class
146 * @var ArticleQualityScore
147 */
148 public $article_quality_score;
149
150 /**
151 * Plugin DB Version
152 * @var string
153 */
154 public $db_version = '1.0.2';
155
156 public function __construct() {
157 $this->define_constants();
158
159 do_action( 'betterdocs_init_before' );
160
161 $this->setup_container();
162 /**
163 * Register activation and deactivation hooks
164 * and version updates check
165 */
166 $this->container->get( Install::class );
167
168 /**
169 * Abilities API registrar.
170 *
171 * Resolved here, at plugin load, and not from initialize(): both the
172 * bundled Abilities API and WordPress core's build their registry as a
173 * lazy singleton and fire `wp_abilities_api_init` from it, at or after
174 * `init`, the first time anything reads the registry. Our listeners
175 * therefore have to be attached before that first read, whenever it
176 * happens — and a container resolve on `init` would already be too late
177 * for a plugin that reads the registry earlier in the same hook.
178 */
179 $this->container->get( AbilitiesRegistrar::class );
180
181 add_action( 'init', array( $this, 'initialize' ), 0 );
182
183 /**
184 * Initialize API
185 */
186 add_action( 'rest_api_init', array( $this, 'api_initialization' ) );
187
188 /**
189 * For admin only
190 */
191 add_action( 'admin_init', array( $this, 'admin_init' ), 0 );
192
193 /**
194 * For AJAX only
195 */
196 $this->ajax();
197
198 /**
199 * Style Handler For Parsing and Saving Styles as file.
200 */
201 ModulesStyleHandler::init();
202 }
203
204 private function define_constants() {
205 $this->define( 'BETTERDOCS_VERSION', $this->version );
206 $this->define( 'BETTERDOCS_DB_VERSION', $this->db_version );
207 $this->define( 'BETTERDOCS_ABSPATH', dirname( BETTERDOCS_PLUGIN_FILE ) . '/' );
208 $this->define( 'BETTERDOCS_ABSURL', plugin_dir_url( BETTERDOCS_PLUGIN_FILE ) );
209 $this->define( 'BETTERDOCS_PLUGIN_BASENAME', plugin_basename( BETTERDOCS_PLUGIN_FILE ) );
210 // Compiled block metadata lives under assets/build/ since the Node 24 asset
211 // restructure; register_block_type() fails silently if this path is wrong.
212 $this->define( 'BETTERDOCS_BLOCKS_DIRECTORY', BETTERDOCS_ABSPATH . 'assets/build/blocks/' );
213 $this->define( 'BETTERDOCS_ROOT_DIR_PATH', plugin_dir_path( BETTERDOCS_PLUGIN_FILE ) );
214 $this->define( 'BETTERDOCS_FSE_TEMPLATES_PATH', BETTERDOCS_ROOT_DIR_PATH . 'views/templates/fse' );
215
216 /**
217 * Third Party Constants
218 * @since 2.5.0
219 *
220 * WPML compatibility with Polylang
221 */
222 if ( Helper::is_plugin_active( 'polylang/polylang.php' ) ) {
223 // Polylang's documented integration constant — must use the upstream-defined name.
224 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
225 define( 'PLL_WPML_COMPAT', false );
226 }
227 }
228
229 /**
230 * Define constant if not already set.
231 *
232 * @param string $name Constant name.
233 * @param string|bool $value Constant value.
234 */
235 private function define( $name, $value ) {
236 if ( ! defined( $name ) ) {
237 // Caller passes plugin-prefixed names (BETTERDOCS_*); $name comes from a controlled internal allowlist.
238 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.VariableConstantNameFound
239 define( $name, $value );
240 }
241 }
242
243 public function setup_container() {
244 $config_array = require_once BETTERDOCS_ABSPATH . 'includes/config.php';
245 $config = apply_filters( 'betterdocs_container_config', $config_array );
246 $builder = new ContainerBuilder();
247
248 $builder->addDefinitions( $config );
249 $this->container = $builder->build();
250 }
251
252 public function initialize() {
253
254 /**
255 * Setup localization.
256 */
257 $this->load_plugin_textdomain();
258
259 $this->container->get( Scripts::class );
260 $this->rewrite = $this->container->get( Rewrite::class );
261 $this->request = $this->container->get( Request::class );
262 $this->query = $this->container->get( Query::class );
263
264 // Initialize background process
265 $this->backgroundProccessor = $this->container->get( HelpScoutMigration::class );
266
267 $this->rewrite->init();
268 $this->request->init();
269
270 $this->assets = $this->container->get( Enqueue::class );
271 $this->views = $this->container->get( Views::class );
272 $this->helper = $this->container->get( Helper::class );
273 $this->kbmigration = $this->container->get( KBMigration::class );
274 $this->admin = $this->container->get( Admin::class );
275 $this->database = $this->container->get( Database::class );
276 $this->settings = $this->container->get( Settings::class );
277 $this->analytics = $this->container->get( Analytics::class );
278 // Free analytics collectors: the frontend view/scroll tracker and the
279 // daily retention purge. Each self-registers its hooks on construct.
280 $this->container->get( AnalyticsTracker::class );
281 $this->container->get( AnalyticsRetention::class );
282
283 $this->template_helper = $this->container->get( TemplateTags::class );
284 $this->customizer = $this->container->get( Customizer::class );
285 $this->editor = $this->container->get( Editor::class );
286 $this->ai_autowrtie = $this->container->get( WriteWithAI::class );
287 $this->article_summary = $this->container->get( ArticleSummary::class );
288
289 // Initialize unified metabox before individual features
290 $this->container->get( UnifiedMetabox::class );
291 $this->article_quality_score = $this->container->get( ArticleQualityScore::class );
292
293 // Editor "Suggest Categories / Tags / Glossaries" AI actions on the docs
294 // post type (gated by enable_docs_ai_suite + an OpenAI key inside the class).
295 $this->container->get( DocsAISuite::class );
296
297 $this->container->get( Admin::class );
298 $this->container->get( Roles::class );
299
300 /**
301 * MCP transport: rewrite rules, the pretty endpoint, OAuth discovery
302 * and every REST route. Resolved here rather than at plugin load
303 * (where the abilities registrar has to be) because its earliest hook
304 * is `init`, which is what this method already runs on.
305 */
306 $this->container->get( MCPManager::class );
307
308 $this->container->get( ReportEmail::class );
309 // Usage-analytics collector. Registered here (runs on every request, incl.
310 // WP-Cron) rather than in Admin so its `betterdocs_insights_data` filter
311 // callback is present whenever the tracking payload is built.
312 $this->container->get( \WPDeveloper\BetterDocs\Insights\Collector::class );
313 /**
314 * Initialize Shortcode
315 * Make sure you have listed out all shortcode in shortcode factory.
316 */
317 $this->container->get( ShortcodeFactory::class )->init();
318
319 $this->container->get( FrontEnd::class );
320 $this->container->get( SearchExtender::class );
321
322 /**
323 * Single-product FAQ rendering (WooCommerce). The class itself bails when
324 * WooCommerce is inactive or the feature is disabled.
325 */
326 $this->container->get( WooProductFAQ::class );
327
328 do_action( 'betterdocs_init' );
329
330 $this->editor->init();
331 }
332
333 /**
334 * Load plugins textdomain `betterdocs` into actions.
335 * @return void
336 */
337 public function load_plugin_textdomain( $textdomain = 'betterdocs', $plugin_file = BETTERDOCS_PLUGIN_FILE ) {
338 $locale = determine_locale();
339
340 /**
341 * Filter to adjust the BetterDocs locale to use for translations.
342 */
343 // 'plugin_locale' is a WP-core filter; using its documented name is required.
344 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
345 $locale = apply_filters( 'plugin_locale', $locale, $textdomain );
346
347 if ( file_exists( WP_LANG_DIR . "/$textdomain-" . $locale . '.mo' ) ) {
348 unload_textdomain( $textdomain );
349 load_textdomain( $textdomain, WP_LANG_DIR . "/$textdomain-" . $locale . '.mo' );
350 }
351 }
352
353 /**
354 * For AJAX Only
355 * @return void
356 */
357 public function ajax() {
358 }
359
360 /**
361 * Create a plugin instance.
362 *
363 * @param mixed ...$args
364 *
365 * @return static
366 *
367 * @suppress PHP0441
368 * @since 2.5.0
369 */
370 public static function get_instance() {
371 if ( null == self::$_instance ) {
372 self::$_instance = new self();
373
374 do_action( 'betterdocs_loaded' );
375 }
376
377 return self::$_instance;
378 }
379
380 /**
381 * Hooked with `admin_init` action.
382 * @return void
383 */
384 public function admin_init() {
385 /**
386 * Maybe Redirect
387 * for setup related settings.
388 */
389 $this->maybe_redirect();
390 }
391
392 /**
393 * Summary of maybe_redirect
394 * @return void
395 */
396 public function maybe_redirect() {
397 // Bail if no activation transient is set.
398 if ( ! $this->database->get_transient( 'betterdocs_maybe_redirect' ) ) {
399 return;
400 }
401
402 // Delete the activation transient.
403 $this->database->delete_transient( 'betterdocs_maybe_redirect' );
404
405 if ( ! is_multisite() ) {
406 $betterdocs_settings = get_option( 'betterdocs_settings' );
407 if ( $betterdocs_settings ) {
408 wp_safe_redirect( add_query_arg( array( 'page' => 'betterdocs-settings' ), admin_url( 'admin.php' ) ) );
409 } else {
410 wp_safe_redirect( add_query_arg( array( 'page' => 'betterdocs-setup' ), admin_url( 'admin.php' ) ) );
411 }
412 // This runs at `admin_init` priority 0. Without exiting, the rest of
413 // admin_init still executes and can mutate the very state the redirect
414 // target depends on before the browser ever follows the Location header.
415 exit;
416 }
417 }
418
419 /**
420 * Is Pro Plugin Is Installed?
421 * @return bool
422 */
423 public function is_pro_installed() {
424 return $this->helper->get_plugins( 'betterdocs-pro/betterdocs-pro.php' );
425 }
426
427 /**
428 * Is Pro Plugin Is Active?
429 * @return bool
430 */
431 public function pro_file() {
432 return WP_PLUGIN_DIR . '/betterdocs-pro/betterdocs-pro.php';
433 }
434
435 public function is_pro_active() {
436 if ( file_exists( $this->pro_file() ) ) {
437 return $this->helper->is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' );
438 }
439
440 return false;
441 }
442
443 public function chatbot_file() {
444 return WP_PLUGIN_DIR . '/betterdocs-ai-chatbot/betterdocs-ai-chatbot.php';
445 }
446
447 public function is_chatbot_active() {
448 if ( file_exists( $this->chatbot_file() ) ) {
449 return $this->helper->is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' );
450 }
451
452 return false;
453 }
454
455 /**
456 * Whether Pro provides the real API Documentation screen.
457 *
458 * Pro flips this via `betterdocs_pro_has_api_docs`; the class_exists() default
459 * keeps the gate correct against a Pro build that predates that filter.
460 *
461 * @return bool
462 */
463 public function has_api_docs() {
464 return (bool) apply_filters(
465 'betterdocs_pro_has_api_docs',
466 class_exists( '\\WPDeveloper\\BetterDocsPro\\Core\\ApiReferences' )
467 );
468 }
469
470 /**
471 * Whether Free should show its locked API Docs teaser.
472 *
473 * Only without Pro. An older Pro has already paid, so they get nothing here —
474 * they need a plugin update, not an upsell.
475 *
476 * @return bool
477 */
478 public function show_api_docs_teaser() {
479 return ! $this->is_pro_active() && ! $this->has_api_docs();
480 }
481
482 public function pro_version() {
483 if ( ! $this->is_pro_active() ) {
484 return false;
485 }
486
487 if ( ! function_exists( 'get_plugin_data' ) ) {
488 require_once ABSPATH . 'wp-admin/includes/plugin.php';
489 }
490
491 $plugin_data = get_plugin_data( $this->pro_file() );
492
493 return $plugin_data[ 'Version' ];
494 }
495
496 /**
497 * Get all the API initialized.
498 * @return void
499 */
500 public function api_initialization() {
501 $_api_classes = scandir( __DIR__ . DIRECTORY_SEPARATOR . 'REST' );
502
503 if ( ! empty( $_api_classes ) && is_array( $_api_classes ) ) {
504 foreach ( $_api_classes as $class ) {
505 if ( '.' == $class || '..' == $class || strpos( $class, '.' ) === 0 ) {
506 continue;
507 }
508
509 $classname = basename( $class, '.php' );
510 $classname = '\\' . __NAMESPACE__ . "\\REST\\$classname";
511 $_api_class = $this->container->get( $classname );
512
513 if ( $_api_class instanceof BaseAPI ) {
514 $_api_class->register();
515 }
516 }
517 }
518 }
519
520 public function is_betterdocs_screen( $hook, $admin_check = true ): bool {
521 /**
522 * Filter the list of admin screen hook suffixes treated as BetterDocs
523 * screens (controls whether the React admin bundle + styles load).
524 * Pro/add-ons can register their own React pages here, e.g. the
525 * Knowledge Base admin page.
526 *
527 * @param string[] $screens Full hook suffixes (e.g. betterdocs_page_betterdocs-foo).
528 */
529 $screens = apply_filters( 'betterdocs_admin_screens', array(
530 'toplevel_page_betterdocs-dashboard',
531 'toplevel_page_betterdocs-admin',
532 'admin_page_betterdocs-admin',
533 'betterdocs_page_betterdocs-admin',
534 'betterdocs_page_betterdocs-analytics',
535 'betterdocs_page_betterdocs-settings',
536 'betterdocs_page_betterdocs-mcp',
537 'betterdocs_page_betterdocs-faq',
538 'betterdocs_page_betterdocs-glossaries',
539 'betterdocs_page_betterdocs-ai-chatbot',
540 'betterdocs_page_betterdocs-api-docs',
541 'betterdocs_page_betterdocs-doc-categories',
542 'betterdocs_page_betterdocs-doc-tags',
543 ) );
544
545 if ( $admin_check ) {
546 if ( in_array( $hook, $screens ) ) {
547 return true;
548 }
549
550 return false;
551 }
552
553 return false;
554 }
555
556 public function get_betterdocs_screen() {
557 $registered_screens = array(
558 'toplevel_page_betterdocs-dashboard',
559 'admin_page_betterdocs-admin',
560 'betterdocs_page_betterdocs-admin',
561 'betterdocs_page_betterdocs-settings',
562 'betterdocs_page_betterdocs-mcp',
563 'betterdocs_page_betterdocs-analytics',
564 'betterdocs_page_betterdocs-faq',
565 'betterdocs_page_betterdocs-glossaries',
566 'betterdocs_page_betterdocs-ai-chatbot',
567 'betterdocs_page_betterdocs-api-docs',
568 'betterdocs_page_betterdocs-doc-categories',
569 'betterdocs_page_betterdocs-doc-tags',
570 'edit-docs'
571 );
572
573 $current_screen_id = get_current_screen() != null ? get_current_screen()->id : '';
574
575 if ( in_array( $current_screen_id, $registered_screens ) ) {
576 return $current_screen_id;
577 }
578
579 return false;
580 }
581 }
582