PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.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 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.8.2, at includes/Plugin.php

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