PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.4.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.4.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 / Core / Admin.php

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

1,179 lines 38.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\Core;
4
5 use Exception;
6 use PriyoMukul\WPNotice\Notices;
7 use WPDeveloper\BetterDocs\Utils\Base;
8 use PriyoMukul\WPNotice\Utils\CacheBank;
9 use WPDeveloper\BetterDocs\Utils\Helper;
10 use WPDeveloper\BetterDocs\Utils\Enqueue;
11 use WPDeveloper\BetterDocs\Utils\Insights;
12 use PriyoMukul\WPNotice\Utils\NoticeRemover;
13 use WPDeveloper\BetterDocs\Core\PluginInstaller;
14 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
15
16 class Admin extends Base {
17 /**
18 * @var CacheBank
19 */
20 private static $cache_bank;
21 /**
22 * Admin Root Menu Slug
23 * @var string
24 */
25 private $slug = 'betterdocs-dashboard';
26 /**
27 * Insights
28 *
29 * @var Insights
30 */
31 private $insights = null;
32
33 /**
34 * DI\Container
35 *
36 * @var Container
37 */
38 private $container;
39
40 /**
41 * Database Wrapper
42 *
43 * @var Settings
44 */
45 private $settings;
46
47 /**
48 * KBMigration
49 *
50 * @var KBMigration
51 */
52 private $kbmigration;
53
54 /**
55 * Enqueue
56 * @var Enqueue
57 */
58 private $assets;
59
60 // modules
61 protected $installer;
62
63 /**
64 * FAQBuilder
65 * @var FAQBuilder
66 */
67 private $faq_builder;
68 private $glossaries;
69
70 public function __construct( Container $container, PostType $type, Enqueue $assets, Settings $settings, KBMigration $kbmigration ) {
71 $this->container = $container;
72 $this->assets = $assets;
73 $this->settings = $settings;
74 $this->kbmigration = $kbmigration;
75 $this->slug = 'betterdocs-dashboard';
76
77 add_action( 'init', [ $type, 'register' ], 9 );
78 add_action('rest_api_init', [$this, 'order_terms_in_wp_terms_admin_table']);
79
80 $type->init();
81 $type->admin_init();
82
83 $this->faq_builder = $this->container->get( FAQBuilder::class );
84 $this->glossaries = $this->container->get( Glossaries::class );
85
86 if ( ! is_admin() ) {
87 return;
88 }
89
90 $this->installer = new PluginInstaller();
91
92 $this->plugin_insights();
93 add_action( 'admin_notices', [ $this, 'compatibility_notices' ] );
94 // add_action( 'admin_init', [$this, 'notices'], 9 );
95 add_filter( 'admin_init', [ $this, 'save_admin_page' ], 99 );
96
97 add_action( 'admin_menu', [ $this, 'menus' ] );
98 add_action( 'admin_menu', [ $this, 'reset_submenu' ] );
99 add_action( 'admin_head', [ $this, 'add_custom_classes_to_menu_items' ] );
100 add_filter( 'plugin_action_links_' . BETTERDOCS_PLUGIN_BASENAME, [ $this, 'insert_plugin_links' ] );
101
102 // $this->container->get( SetupWizard::class )->init();
103
104 add_action( 'admin_enqueue_scripts', [ $this, 'styles' ] );
105 add_action( 'admin_enqueue_scripts', [ $this, 'scripts' ] );
106 //add_action( 'betterdocs_listing_header', [ $this, 'header' ], 10, 1 );
107 add_action( 'admin_bar_menu', [ $this, 'toolbar_menu' ], 32 );
108
109 add_filter( 'admin_body_class', [ $this, 'body_classes' ] );
110 add_filter( 'parent_file', [ $type, 'highlight_admin_menu' ] );
111 add_filter( 'submenu_file', [ $type, 'highlight_admin_submenu' ], 10, 2 );
112 add_filter( 'betterdocs_admin_menu', [ $this, 'quick_setup_menu' ], 10, 1 );
113
114 /**
115 * Remove Comments Column from List Table.
116 */
117 add_filter( 'manage_docs_posts_columns', [ $this, 'set_custom_edit_action_columns' ] );
118 add_filter( 'manage_docs_posts_custom_column', [ $this, 'manage_custom_columns' ], 10, 2 );
119
120 /**
121 * Add New Column
122 */
123 add_filter( 'manage_users_columns', [ $this, 'add_users_total_docs_column' ], 10, 1 );
124 add_filter( 'manage_users_custom_column', [ $this, 'popular_users_docs_data' ], 10, 3 );
125 if ( is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' ) ) {
126 add_action( 'admin_footer-plugins.php', array( $this, 'disable_deactivation' ) );
127 }
128
129 if ( $this->settings->get( 'enable_estimated_reading_time' ) ) {
130 // Hook into unified metabox instead of creating separate metabox
131 add_action( 'betterdocs_reading_time_tab_content', [ $this, 'render_estimated_time_markup' ] );
132 }
133
134 self::$cache_bank = CacheBank::get_instance();
135
136 // Remove OLD notice from 1.0.0 (if other WPDeveloper plugin has notice)
137 NoticeRemover::get_instance( '1.0.0' );
138
139 try {
140 $this->notices();
141 } catch ( Exception $e ) {
142 unset( $e );
143 }
144 }
145
146 public function order_terms_in_wp_terms_admin_table() {
147 //order the terms correctly to be shown on the admin panel categories menu with betterdocs order
148 add_action('rest_insert_doc_category', function( $term, $request, $bool ) {
149 $max_order = Helper::get_max_doc_category_order_from_term_meta() ?? 0;
150 $next_order = $max_order + 1;
151 update_term_meta( $term->term_id, 'doc_category_order', $next_order );
152 }, 10, 3);
153 }
154
155 public function disable_deactivation() {
156 $tooltip_text = esc_html__( 'Deactivate BetterDocs Pro First', 'betterdocs' );
157 ?>
158 <style type="text/css">
159 #deactivate-betterdocs {
160 color: #cccccc;
161 position: relative;
162 }
163
164 /* Tooltip styling */
165 #deactivate-betterdocs[title]:hover::after {
166 content: attr(title);
167 position: absolute;
168 bottom: 100%;
169 left: 50%;
170 transform: translateX(-50%);
171 background-color: #333;
172 color: #fff;
173 padding: 5px 10px;
174 border-radius: 4px;
175 font-size: 12px;
176 white-space: nowrap;
177 box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
178 z-index: 10;
179 }
180 #deactivate-betterdocs:focus {
181 box-shadow: none;
182 outline: none;
183 }
184 </style>
185 <script type="text/javascript">
186 jQuery(document).ready(function($) {
187 // Disable the default action and add class with tooltip by default
188 const tooltipText = "<?php echo esc_attr( $tooltip_text ); ?>";
189 $("#deactivate-betterdocs")
190 .addClass("disabled-tooltip")
191 .attr("title", tooltipText)
192 .on("click", function(e) {
193 e.preventDefault(); // Prevent any action on click
194 });
195 });
196 </script>
197 <?php
198 }
199
200 public function add_users_total_docs_column( $columns ) {
201 $new_column = [
202 'docs' => __( 'Docs', 'betterdocs' )
203 ];
204 $columns = array_merge( $columns, $new_column );
205 return $columns;
206 }
207
208 public function popular_users_docs_data( $output, $column_name, $user_id ) {
209 if ( $column_name == 'docs' ) {
210 $total_count = count_user_posts( $user_id, 'docs', true );
211 return '<a href="edit.php?post_type=docs&author=' . $user_id . '" class="edit"><span aria-hidden="true">' . $total_count . '</span></a>';
212 }
213 return $output;
214 }
215
216 public function render_estimated_time_markup() {
217 betterdocs()->views->get( 'admin/metabox/estimated-reading-box' );
218 }
219
220 public function compatibility_notices() {
221 if ( betterdocs()->is_pro_active() ) {
222 $plugins = Helper::get_plugins();
223 $plugin_data = $plugins['betterdocs-pro/betterdocs-pro.php'];
224
225 if ( isset( $plugin_data['Version'] ) && version_compare( $plugin_data['Version'], '2.5.0', '>=' ) ) {
226 return;
227 }
228
229 betterdocs()->views->get( 'admin/notices/compatibility', [ 'version' => $plugin_data['Version'] ] );
230 }
231 }
232
233 public function plugin_insights( $prevent_init = false ) {
234 $this->insights = Insights::get_instance(
235 BETTERDOCS_PLUGIN_FILE,
236 [
237 'opt_in' => true,
238 'goodbye_form' => true,
239 'item_id' => 'c7b16777b4f1b83f6083'
240 ]
241 );
242
243 $this->insights->set_notice_options(
244 [
245 'notice' => __( 'Want to help make <strong>BetterDocs</strong> even more awesome? You can get a <strong>10% discount coupon</strong> for Premium extensions if you allow us to track the usage.', 'betterdocs' ),
246 'extra_notice' => __( 'We collect non-sensitive diagnostic data and plugin usage information. Your site URL, WordPress & PHP version, plugins & themes and email address to send you the discount coupon. This data lets us make sure this plugin always stays compatible with the most popular plugins and themes. No spam, I promise.', 'betterdocs' )
247 ]
248 );
249
250 if ( ! $prevent_init ) {
251 $this->insights->init();
252 }
253
254 return $this->insights;
255 }
256
257 /**
258 * Admin notices for Review and others.
259 *
260 * @return void
261 * @throws Exception
262 */
263 public function notices() {
264 $notices = new Notices(
265 [
266 'id' => 'betterdocs',
267 'storage_key' => 'notices',
268 'lifetime' => 3,
269 'stylesheet_url' => $this->assets->asset_url( 'admin/css/notices.css' ),
270 'styles' => $this->assets->asset_url( 'admin/css/notices.css' ),
271 'priority' => 4
272 ]
273 );
274
275 /**
276 * Review Notice
277 * @var mixed $message
278 */
279
280 $message = __( 'We hope you\'re enjoying BetterDocs! Could you please do us a BIG favor and give it a 5-star rating on WordPress to help us spread the word and boost our motivation?', 'betterdocs' );
281
282 $_review_notice = [
283 'thumbnail' => $this->assets->icon( 'betterdocs-logo.svg', true ),
284 'html' => '<p>' . $message . '</p>',
285 'links' => [
286 'later' => [
287 'link' => 'https://wordpress.org/plugins/betterdocs/#reviews',
288 'target' => '_blank',
289 'label' => __( 'Sure, you deserve it!', 'betterdocs' ),
290 'icon_class' => 'dashicons dashicons-external'
291 ],
292 'allready' => [
293 'label' => __( 'I already did', 'betterdocs' ),
294 'icon_class' => 'dashicons dashicons-smiley',
295 'attributes' => [
296 'data-dismiss' => true
297 ]
298 ],
299 'maybe_later' => [
300 'label' => __( 'Maybe Later', 'betterdocs' ),
301 'icon_class' => 'dashicons dashicons-calendar-alt',
302 'attributes' => [
303 'data-later' => true,
304 'class' => 'dismiss-btn'
305 ]
306 ],
307 'support' => [
308 'link' => 'https://wpdeveloper.com/support',
309 'attributes' => [
310 'target' => '_blank'
311 ],
312 'label' => __( 'I need help', 'betterdocs' ),
313 'icon_class' => 'dashicons dashicons-sos'
314 ],
315 'never_show_again' => [
316 'label' => __( 'Never show again', 'betterdocs' ),
317 'icon_class' => 'dashicons dashicons-dismiss',
318 'attributes' => [
319 'data-dismiss' => true
320 ]
321 ]
322 ]
323 ];
324
325 $notices->add(
326 'review',
327 $_review_notice,
328 [
329 'start' => $notices->strtotime( '+10 days' ),
330 'recurrence' => 30,
331 'dismissible' => true
332 ]
333 );
334
335 if ( $this->kbmigration->existing_plugins && ! in_array( $this->kbmigration->existing_plugins[0][0], $this->kbmigration->migrated_plugins ) ) {
336 $plugin_name = '<strong>' . esc_html( $this->kbmigration->existing_plugins[0][1] ) . '</strong>';
337
338 $message = sprintf(
339 /* translators: %s is the name of the existing knowledge base plugin. */
340 __( 'Already using %s? Power up your Knowledge Base by migrating all your docs and settings to BetterDocs with just 1 click.', 'betterdocs' ),
341 esc_html( $plugin_name )
342 );
343
344 $migration_message = sprintf(
345 '<p class="migration-message">%s</p><a class="button button-primary betterdocs-migration-notice" href="%s">%s</a>',
346 $message,
347 esc_url( admin_url( 'admin.php?page=betterdocs-settings&tab=tab-migration' ) ),
348 esc_html__( 'Start Migration', 'betterdocs' )
349 );
350
351 $_migration_notice = [
352 'thumbnail' => '',
353 'html' => $migration_message,
354 'links' => [
355 'maybe_later' => [
356 'label' => __( 'Maybe Later', 'betterdocs' ),
357 'icon_class' => 'dashicons dashicons-calendar-alt',
358 'attributes' => [
359 'data-later' => true,
360 'class' => 'dismiss-btn'
361 ]
362 ],
363 'never_show_again' => [
364 'label' => __( 'Never show again', 'betterdocs' ),
365 'icon_class' => 'dashicons dashicons-dismiss',
366 'attributes' => [
367 'data-dismiss' => true
368 ]
369 ]
370 ]
371 ];
372
373 $notices->add(
374 'migration',
375 $_migration_notice,
376 [
377 'start' => $notices->time(),
378 'recurrence' => false,
379 'dismissible' => true
380 ]
381 );
382 }
383
384 /**
385 * Opt-In Notice
386 */
387 $allow_tracking = get_option( 'wpins_allow_tracking' );
388 if ( $this->insights != null && ! isset( $allow_tracking['betterdocs'] ) ) {
389 $notices->add(
390 'opt_in',
391 [ $this->insights, 'notice' ],
392 [
393 'classes' => 'updated put-dismiss-notice',
394 'start' => $notices->time(),
395 'refresh' => BETTERDOCS_VERSION,
396 'dismissible' => true,
397 'do_action' => 'wpdeveloper_notice_clicked_for_betterdocs',
398 'display_if' => ! function_exists( 'betterdocs_pro' ),
399 'screens' => [ 'dashboard' ]
400 ]
401 );
402 }
403
404 $spring_campaign_message = '<div class="betterdocs-spring-notice-body"><p style="margin-top: 0; margin-bottom: 0;">🌸 <strong>Spring Savings:</strong> Build AI-powered Knowledge Bases & FAQs to empower support and improve user experience – now <strong>Flat 25% OFF!</strong> ⚡️</p></div>';
405 $_spring_campaign_notice = [
406 'thumbnail' => $this->assets->icon( 'betterdocs-logo.svg', true ),
407 'html' => $spring_campaign_message,
408 'links' => [
409 'support' => [
410 'link' => 'https://betterdocs.co/spring2026-admin-notice',
411 'attributes' => [
412 'target' => '_blank',
413 'class' => 'offer-button',
414 ],
415 'label' => __( 'Upgrade To PRO Now', 'betterdocs' ),
416 ],
417 'maybe_later' => [
418 'label' => __( 'I\'ll Grab It Later', 'betterdocs' ),
419 'attributes' => [
420 'target' => '_blank',
421 'data-later' => true,
422 'class' => 'dismiss-btn',
423 ],
424 ],
425 ],
426 ];
427
428 $notices->add(
429 'spring-campaign-26',
430 $_spring_campaign_notice,
431 [
432 'start' => $notices->time(),
433 'recurrence' => false,
434 'dismissible' => true,
435 'refresh' => BETTERDOCS_VERSION,
436 'expire' => strtotime( '11:59:59pm May 31, 2026' ),
437 'display_if' => ! is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' ),
438 ]
439 );
440
441 self::$cache_bank->create_account( $notices );
442 self::$cache_bank->calculate_deposits( $notices );
443 if ( method_exists( self::$cache_bank, 'clear_notices_in_' ) ) {
444 self::$cache_bank->clear_notices_in_(
445 [
446 'toplevel_page_betterdocs-dashboard',
447 'admin_page_betterdocs-admin',
448 'betterdocs_page_betterdocs-admin',
449 'betterdocs_page_betterdocs-settings',
450 'betterdocs_page_betterdocs-faq',
451 'betterdocs_page_betterdocs-analytics',
452 'betterdocs_page_betterdocs-glossaries',
453 'betterdocs_page_betterdocs-ai-chatbot',
454 'edit-doc_category',
455 'edit-doc_tag'
456 ],
457 $notices,
458 true
459 );
460 }
461 }
462
463 public function body_classes( $classes ) {
464 $saved_settings = get_option( 'betterdocs_settings', false );
465 $dark_mode = isset( $saved_settings['dark_mode'] ) ? $saved_settings['dark_mode'] : false;
466 $dark_mode = ! empty( $dark_mode ) ? boolval( $dark_mode ) : false;
467 $current_screen_id = get_current_screen() != null ? str_replace( 'betterdocs_page_', '', str_replace( 'toplevel_page_', '', str_replace( 'admin_page_', '', get_current_screen()->id ) ) ) : '';
468 $registered_screens = [
469 'betterdocs-settings',
470 'betterdocs-admin',
471 'betterdocs-dashboard',
472 'betterdocs-analytics',
473 'betterdocs-glossaries',
474 'betterdocs-faq',
475 'edit-doc_category',
476 'edit-doc_tag',
477 'edit-knowledge_base',
478 'betterdocs-ai-chatbot'
479 ];
480
481 if( in_array( $current_screen_id, $registered_screens ) ) {
482 $classes .= ' betterdocs-admin ';
483 }
484
485 if ( $dark_mode === true && in_array( $current_screen_id, $registered_screens ) ) {
486 $classes .= ' betterdocs-dark-mode ';
487 }
488
489 return $classes;
490 }
491
492 /**
493 * Remove Comments Column From List Table
494 *
495 * @param array $columns
496 *
497 * @return array
498 * @since 1.0.0
499 */
500 public function set_custom_edit_action_columns( $columns ) {
501 unset( $columns['comments'] );
502 $new_columns = [];
503 foreach ( $columns as $key => $value ) {
504 if ( $key == 'date' ) {
505 $new_columns['betterdocs_word_count'] = __( 'Word Count', 'betterdocs' ); // put the tags column before it
506 $new_columns['betterdocs_reaction'] = __( 'Reactions', 'betterdocs' );
507 }
508 $new_columns[ $key ] = $value;
509 }
510
511 return $new_columns;
512 }
513
514 public function manage_custom_columns( $column, $post_id ) {
515 global $wpdb;
516 switch ( $column ) {
517 case 'betterdocs_word_count':
518 $content_without_html_tags = trim( strip_tags( get_post_field( 'post_content', $post_id ) ) );
519 preg_match_all( '/<[^>]*>|[\p{L}\p{M}]+/u', $content_without_html_tags, $matches );
520 $total_words = ! empty( $matches[0] ) ? count( $matches[0] ) : count( [] );
521 $word_count = $total_words;
522 echo '<span>' . esc_html( intval( $word_count ) ) . '</span>';
523 break;
524 case 'betterdocs_reaction':
525 $where = "WHERE post_id='" . esc_sql( $post_id ) . "'";
526 $analytics = $wpdb->get_results(
527 "SELECT
528 sum(impressions) as totalViews,
529 sum(unique_visit) as totalUniqueViews,
530 sum(happy + sad + normal) as totalReactions,
531 sum(happy) as totalHappy,
532 sum(normal) as totalNormal,
533 sum(sad) as totalSad
534 FROM {$wpdb->prefix}betterdocs_analytics
535 $where"
536 );
537
538 echo '<ul class="reactions-count">
539 <li>
540 <a title="happy" class="betterdocs-feelings happy" data-feelings="happy" href="#">
541 <svg width="15" height="15" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
542 <path class="st0" d="M10,0.1c-5.4,0-9.9,4.4-9.9,9.8c0,5.4,4.4,9.9,9.8,9.9c5.4,0,9.9-4.4,9.9-9.8C19.9,4.5,15.4,0.1,10,0.1z
543 M13.3,6.4c0.8,0,1.5,0.7,1.5,1.5c0,0.8-0.7,1.5-1.5,1.5c-0.8,0-1.5-0.7-1.5-1.5C11.8,7.1,12.5,6.4,13.3,6.4z M6.7,6.4
544 c0.8,0,1.5,0.7,1.5,1.5c0,0.8-0.7,1.5-1.5,1.5c-0.8,0-1.5-0.7-1.5-1.5C5.2,7.1,5.9,6.4,6.7,6.4z M10,16.1c-2.6,0-4.9-1.6-5.8-4
545 l1.2-0.4c0.7,1.9,2.5,3.2,4.6,3.2s3.9-1.3,4.6-3.2l1.2,0.4C14.9,14.5,12.6,16.1,10,16.1z" />
546 <path class="st1" d="M-6.6-119.7c-7.1,0-12.9,5.8-12.9,12.9s5.8,12.9,12.9,12.9s12.9-5.8,12.9-12.9S0.6-119.7-6.6-119.7z
547 M-2.3-111.4c1.1,0,2,0.9,2,2c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2C-4.3-110.5-3.4-111.4-2.3-111.4z M-10.9-111.4c1.1,0,2,0.9,2,2
548 c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2C-12.9-110.5-12-111.4-10.9-111.4z M-6.6-98.7c-3.4,0-6.4-2.1-7.6-5.3l1.6-0.6
549 c0.9,2.5,3.3,4.2,6,4.2s5.1-1.7,6-4.2L1-104C-0.1-100.8-3.2-98.7-6.6-98.7z" />
550 </svg>
551 <span>' . esc_html( ( intval( $analytics[0]->totalHappy ) !== null ? intval( $analytics[0]->totalHappy ) : 0 ) ) . '</span>
552 </a>
553 </li>
554 <li>
555 <a title="normal" class="betterdocs-feelings normal" data-feelings="normal" href="#">
556 <svg width="15" height="15" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
557 <path class="st0" d="M10,0.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S15.4,0.2,10,0.2z M6.7,6.5
558 c0.8,0,1.5,0.7,1.5,1.5c0,0.8-0.7,1.5-1.5,1.5C5.9,9.5,5.2,8.9,5.2,8C5.2,7.2,5.9,6.5,6.7,6.5z M14.2,14.3H5.9
559 c-0.3,0-0.6-0.3-0.6-0.6c0-0.3,0.3-0.6,0.6-0.6h8.3c0.3,0,0.6,0.3,0.6,0.6C14.8,14,14.5,14.3,14.2,14.3z M13.3,9.5
560 c-0.8,0-1.5-0.7-1.5-1.5c0-0.8,0.7-1.5,1.5-1.5c0.8,0,1.5,0.7,1.5,1.5C14.8,8.9,14.1,9.5,13.3,9.5z" />
561 </svg>
562 <span>' . esc_html( ( intval( $analytics[0]->totalNormal ) !== null ? intval( $analytics[0]->totalNormal ) : 0 ) ) . '</span>
563 </a>
564 </li>
565 <li>
566 <a title="sad" class="betterdocs-feelings sad" data-feelings="sad" href="#">
567 <svg width="15" height="15" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
568 <circle class="st0" cx="27.5" cy="0.6" r="1.9" />
569 <circle class="st0" cx="36" cy="0.6" r="1.9" />
570 <path class="st1" d="M10,0.3c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S15.4,0.3,10,0.3z M13.3,6.6
571 c0.8,0,1.5,0.7,1.5,1.5c0,0.8-0.7,1.5-1.5,1.5c-0.8,0-1.5-0.7-1.5-1.5C11.8,7.3,12.4,6.6,13.3,6.6z M6.7,6.6c0.8,0,1.5,0.7,1.5,1.5
572 c0,0.8-0.7,1.5-1.5,1.5C5.9,9.6,5.2,9,5.2,8.1C5.2,7.3,5.9,6.6,6.7,6.6z M14.1,15L14.1,15c-0.2,0-0.4-0.1-0.5-0.2
573 c-0.9-1-2.2-1.7-3.7-1.7s-2.8,0.6-3.7,1.7C6.2,14.9,6,15,5.9,15h0c-0.6,0-0.8-0.6-0.5-1.1c1.1-1.3,2.8-2.1,4.6-2.1
574 c1.8,0,3.5,0.8,4.6,2.1C15,14.3,14.7,15,14.1,15z" />
575 </svg>
576 <span>' . esc_html( ( intval( $analytics[0]->totalNormal ) !== null ? intval( $analytics[0]->totalNormal ) : 0 ) ) . '</span>
577 </a>
578 </li>
579 </ul>';
580 break;
581 }
582 }
583
584 /**
585 * Enqueue Assets for Admin ( Styles )
586 *
587 * @param string $hook
588 *
589 * @return void
590 * @since 1.0.0
591 */
592 public function styles( $hook ) {
593 $this->assets->enqueue( 'betterdocs-global', 'admin/css/global.css', [], 'all' );
594
595 if ( ! betterdocs()->is_betterdocs_screen( $hook ) ) {
596 return;
597 }
598
599 $this->assets->enqueue( 'betterdocs-select2', 'vendor/css/select2.min.css', [], 'all' );
600 $this->assets->enqueue( 'betterdocs-daterangepicker', 'vendor/css/daterangepicker.css', [], 'all' );
601 $this->assets->enqueue( 'betterdocs-old', 'admin/css/betterdocs.css', [], 'all' );
602
603 /**
604 * This scripts enqueued for Dashboard App.
605 */
606 $this->assets->enqueue( 'betterdocs', 'admin/css/dashboard.css', [ 'betterdocs-old' ], '', BETTERDOCS_VERSION );
607 $this->assets->enqueue( 'betterdocs-icons', 'admin/btd-icon/style.css' );
608 }
609
610 /**
611 * Enqueue Assets for Admin ( Scripts )
612 *
613 * @param string $hook
614 *
615 * @return void
616 * @since 1.0.0
617 */
618 public function scripts( $hook ) {
619 if ( ( $hook === 'edit.php' ) && get_post_type() == 'docs' ) {
620 $this->assets->enqueue(
621 'betterdocs-switcher',
622 'admin/js/switcher.js',
623 [
624 'jquery'
625 ]
626 );
627
628 $this->assets->localize(
629 'betterdocs-switcher',
630 'betterdocsSwitcher',
631 [
632 'menu_title' => __( 'Switch to BetterDocs UI', 'betterdocs' ),
633 'site_address' => get_bloginfo( 'url' ),
634 'betterdocs_pro_plugin' => betterdocs()->is_pro_active(),
635 'betterdocs_pro_version' => betterdocs()->pro_version()
636 ]
637 );
638
639 return;
640 }
641
642 wp_enqueue_script( 'wp-editor' ); // enqueue this for yoast related issue
643
644 if ( ! betterdocs()->is_betterdocs_screen( $hook ) ) {
645 return;
646 }
647
648 wp_enqueue_media(); // load early to fix problems with media upload issues on settings for wordpress 6.0.9
649 $this->assets->register( 'betterdocs-admin', 'admin/js/dashboard.js' );
650
651 $saved_settings = get_option( 'betterdocs_settings', false );
652 $dark_mode = $saved_settings['dark_mode'] ?? false;
653 $dark_mode = ! empty( $dark_mode ) && boolval( $dark_mode );
654 $this->assets->localize(
655 'betterdocs-admin',
656 'betterdocs_admin',
657 [
658 'ajaxurl' => admin_url( 'admin-ajax.php' ),
659 'doc_cat_order_nonce' => wp_create_nonce( 'doc_cat_order_nonce' ),
660 'knowledge_base_order_nonce' => wp_create_nonce( 'knowledge_base_order_nonce' ),
661 'paged' => isset( $_GET['paged'] ) ? absint( wp_unslash( $_GET['paged'] ) ) : 0, // phpcs:ignore WordPress.Security.NonceVerification.Missing
662 'per_page_id' => 'edit_doc_category_per_page',
663 'menu_title' => __( 'Switch to BetterDocs UI', 'betterdocs' ),
664 'dark_mode' => $dark_mode,
665 'text' => __( 'Copied!', 'betterdocs' ),
666 'test_report' => __( 'Test Report!', 'betterdocs' ),
667 'sending' => __( 'Sending...', 'betterdocs' ),
668 'dir_url' => BETTERDOCS_ABSURL,
669 'rest_url' => esc_url_raw( rest_url() ),
670 'free_version' => betterdocs()->version,
671 'generate_data_url' => get_rest_url( null, '/betterdocs/v1/create-sample-docs' ),
672 'nonce' => wp_create_nonce( 'wp_rest' ),
673 'sync_nonce' => wp_create_nonce( 'ai_chatbot_embed' ),
674 'count_all_docs' => array_sum((array) wp_count_posts('docs')),
675 'count_all_faq' => array_sum((array) wp_count_posts('betterdocs_faq')),
676 'count_new_docs' => count(get_option('saved_docs_post_ids', [])) + count(get_option('betterdocs_ai_chatbot_error_posts', [])),
677 'admin_url' => admin_url(),
678 'ia_preview' => betterdocs()->settings->get( 'ia_enable_preview', false ),
679 'multiple_kb' => betterdocs()->settings->get( 'multiple_kb' ),
680 'previewMode' => betterdocs()->settings->get( 'ia_enable_preview', false ),
681 'dashboard_mode' => get_option( 'dashboard_mode' ),
682 'betterdocs_pro_plugin' => betterdocs()->is_pro_active(),
683 'betterdocs_pro_version' => betterdocs()->pro_version(),
684 'analytics_older' => version_compare( betterdocs()->pro_version(), '3.3.4', '<=' ),
685 'disabled_embed_model_option' => get_option('disabled_embed_model_option'),
686 'betterdocs_ChatBot_plugin' => is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' ),
687 'total_doc_category_terms' => wp_count_terms( 'doc_category'),
688 'github_oauth_nonce' => wp_create_nonce( 'betterdocs_github_oauth_nonce' ),
689 'git_settings' => [
690 'git_repository_url' => betterdocs()->settings->get_raw_field( 'git_repository_url', '' ),
691 'git_branch' => betterdocs()->settings->get_raw_field( 'git_branch', '' ),
692 'git_docs_directory' => betterdocs()->settings->get_raw_field( 'git_docs_directory', '' ),
693 'git_provider' => betterdocs()->settings->get_raw_field( 'git_provider', 'github' ),
694 ],
695 ]
696 );
697
698 // If wp-date (which includes moment.js) is not registered, enqueue your custom moment.js
699 if ( ! wp_script_is( 'wp-date', 'registered' ) ) {
700 $this->assets->enqueue( 'moment', 'vendor/js/moment.min.js', [] );
701 }
702 wp_enqueue_script( 'betterdocs-admin' );
703
704 /**
705 * Duplicate Codes Need to Be Removed From Here Onwards
706 */
707
708 //FAQ Builder Related Localization
709 betterdocs()->assets->enqueue( 'betterdocs-admin-faq', 'admin/css/faq.css' );
710 betterdocs()->assets->enqueue( 'betterdocs-admin-faq', 'admin/js/faq.js' );
711
712 // removing emoji support
713 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
714 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
715
716 betterdocs()->assets->localize(
717 'betterdocs-admin-faq',
718 'betterdocsFaq',
719 [
720 'dir_url' => BETTERDOCS_ABSURL,
721 'rest_url' => esc_url_raw( rest_url() ),
722 'free_version' => betterdocs()->version,
723 'nonce' => wp_create_nonce( 'wp_rest' ),
724 'betterdocs_settings' => get_option( 'betterdocs_settings', false )
725 ]
726 );
727
728 //Glossaries Related Localization
729 betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/css/faq.css' );
730
731 betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/js/glossaries.js' );
732
733 betterdocs()->assets->localize(
734 'betterdocs-admin-glossaries',
735 'betterdocsGlossary',
736 [
737 'dir_url' => BETTERDOCS_ABSURL,
738 'rest_url' => esc_url_raw( rest_url() ),
739 'free_version' => betterdocs()->version,
740 'nonce' => wp_create_nonce( 'wp_rest' ),
741 'betterdocs_settings' => get_option( 'betterdocs_settings', false )
742 ]
743 );
744 }
745
746 /**
747 * All admin pages header
748 *
749 * @return void
750 * @since 1.0.0
751 */
752 public function header( $admin_tab_name ) {
753 $quick_links = [
754 'switch_view' => sprintf(
755 '<a href="%s" class="betterdocs-button betterdocs-button-secondary">%s</a>',
756 add_query_arg(
757 [
758 'post_type' => 'docs',
759 'bdocs_view' => 'classic'
760 ],
761 'edit.php'
762 ),
763 __( 'Switch to Classic UI', 'betterdocs' )
764 ),
765 'add_new_doc' => sprintf( '<a href="%s" class="betterdocs-button betterdocs-button-primary">%s</a>', add_query_arg( [ 'post_type' => 'docs' ], 'post-new.php' ), __( 'Add New Doc', 'betterdocs' ) )
766 ];
767
768 $quick_links = apply_filters( 'betterdocs_quick_links', $quick_links );
769
770 betterdocs()->views->get(
771 'admin/header',
772 [
773 'quick_links' => $quick_links,
774 'active_tab' => $admin_tab_name
775 ]
776 );
777 }
778
779 /**
780 * Register all the menus for BetterDocs
781 *
782 * @return void
783 * @since 1.0.0
784 */
785 public function menus() {
786 $default_args = [
787 'page_title' => 'BetterDocs',
788 'menu_title' => 'BetterDocs',
789 'capability' => 'edit_docs', // Unified capability
790 'menu_slug' => $this->slug,
791 'callback' => [ $this, 'output' ],
792 'icon_url' => betterdocs()->assets->icon( 'betterdocs-icon-white.svg', true ),
793 'position' => 5
794 ];
795
796 $_menu_position = 5;
797 global $submenu;
798
799 // Always register both UI endpoints
800 $this->register_modern_ui_fallback();
801
802 foreach ( $this->menu_list() as $key => $value ) {
803 if ( $key === 'betterdocs' ) {
804 $callable = 'add_menu_page';
805 $value = wp_parse_args( $value, $default_args );
806 call_user_func_array( $callable, $value );
807 } else {
808 $is_core_page = strpos($value['menu_slug'], '?') !== false;
809
810 if ($is_core_page) {
811 // Add classic UI directly
812 $submenu[$this->slug][] = [
813 $value['menu_title'],
814 $value['capability'],
815 $value['menu_slug'],
816 $value['page_title']
817 ];
818 } else {
819 // Add modern UI through WordPress API
820 add_submenu_page(
821 $this->slug,
822 $value['page_title'],
823 $value['menu_title'],
824 $value['capability'],
825 $value['menu_slug'],
826 $value['callback']
827 );
828 }
829 $_menu_position++;
830 }
831 }
832 }
833
834 private function register_modern_ui_fallback() {
835 // Add the submenu with valid parent slug
836 add_submenu_page(
837 'betterdocs', // Valid parent slug
838 __('All Docs', 'betterdocs'),
839 '', // Empty menu title hides it
840 'edit_docs',
841 'betterdocs-admin',
842 [ $this, 'output' ]
843 );
844
845 // Hide the menu item from appearing in the admin sidebar
846 global $submenu;
847 if (isset($submenu['betterdocs'])) {
848 foreach ($submenu['betterdocs'] as $key => $item) {
849 if ($item[2] === 'betterdocs-admin') {
850 unset($submenu['betterdocs'][$key]);
851 break;
852 }
853 }
854 }
855 }
856
857 /**
858 * BetterDocs Admin Page Output
859 *
860 * @return void
861 * @since 1.0.0
862 */
863 public function output() {
864 if ( betterdocs()->is_pro_active()
865 && version_compare( betterdocs()->pro_version(), '3.3.4', '<=' )
866 && get_current_screen()->id == 'betterdocs_page_betterdocs-analytics' ) {
867 betterdocs_pro()->views->get( 'admin/analytics-pro' );
868 } else {
869 betterdocs()->views->get(
870 'admin/main',
871 [
872 'admin_ui' => 'dnd'
873 ]
874 );
875 }
876 }
877
878 /**
879 * Menu creator helper
880 *
881 * @param string $title
882 * @param string $slug
883 * @param string $cap
884 * @param array $callback
885 *
886 * @return array
887 * @since 2.5.0
888 *
889 */
890 private function normalize_menu( $title, $slug, $cap = 'edit_docs', $callback = null, $optional = [] ) {
891 return Helper::normalize_menu( $title, $slug, $cap, $callback, $optional );
892 }
893
894 /**
895 * BetterDocs Menu List
896 *
897 * @return array
898 * @since 1.0.0
899 */
900 private function menu_list() {
901 $parent_slug = [];
902
903 $betterdocs_admin_pages = [
904 'betterdocs' => [
905 'menu_slug' => $this->slug,
906 'page_title' => 'BetterDocs',
907 'menu_title' => 'BetterDocs',
908 'capability' => 'edit_docs',
909 'callback' => [ $this, 'output' ],
910 'icon_url' => betterdocs()->assets->icon( 'betterdocs-icon-white.svg', true ),
911 'position' => 5
912 ],
913 'dashboard' => $this->normalize_menu(
914 __( 'Dashboard', 'betterdocs' ),
915 'betterdocs-dashboard',
916 'edit_docs',
917 [
918 $this,
919 'output'
920 ]
921 ),
922 'all_docs' => $this->normalize_menu(
923 __( 'All Docs', 'betterdocs' ),
924 $this->ui_slug(),
925 'edit_docs',
926 [ $this, 'output' ],
927 $parent_slug
928 ),
929 'add_new' => $this->normalize_menu(
930 __( 'Add New', 'betterdocs' ),
931 'post-new.php?post_type=docs'
932 ),
933 'categories' => $this->normalize_menu(
934 __( 'Categories', 'betterdocs' ),
935 'edit-tags.php?taxonomy=doc_category&post_type=docs',
936 'manage_doc_terms'
937 ),
938 'tags' => $this->normalize_menu(
939 __( 'Tags', 'betterdocs' ),
940 'edit-tags.php?taxonomy=doc_tag&post_type=docs',
941 'manage_doc_terms'
942 ),
943 'settings' => $this->normalize_menu(
944 __( 'Settings', 'betterdocs' ),
945 'betterdocs-settings',
946 'edit_docs_settings',
947 [
948 $this,
949 'output'
950 ],
951 $parent_slug
952 ),
953 'analytics' => $this->normalize_menu(
954 __( 'Analytics', 'betterdocs' ),
955 'betterdocs-analytics',
956 'read_docs_analytics',
957 [
958 $this,
959 'output'
960 ],
961 $parent_slug
962 ),
963 'faq' => $this->normalize_menu(
964 __( 'FAQ Builder', 'betterdocs' ),
965 'betterdocs-faq',
966 'read_faq_builder',
967 [
968 $this,
969 'output'
970 ],
971 $parent_slug
972 )
973 ];
974
975 if ( betterdocs()->is_pro_active() && betterdocs()->settings->get( 'enable_glossaries' ) == true ) {
976 $betterdocs_admin_pages['glossaries'] = $this->normalize_menu(
977 __( 'Glossaries', 'betterdocs' ),
978 'betterdocs-glossaries',
979 'read_docs_analytics',
980 [
981 $this,
982 'output'
983 ],
984 $parent_slug
985 );
986 }
987
988
989 if (!betterdocs()->is_chatbot_active()) {
990 $betterdocs_admin_pages['ai_chatbot'] = $this->normalize_menu(
991 __('AI Chatbot', 'betterdocs'),
992 'betterdocs-ai-chatbot',
993 'edit_docs_settings',
994 [
995 $this,
996 'output'
997 ],
998 $parent_slug
999 );
1000 }
1001
1002 return apply_filters( 'betterdocs_admin_menu', $betterdocs_admin_pages, [$this, 'output'], $parent_slug);
1003
1004 }
1005
1006 public function add_custom_classes_to_menu_items() {
1007 global $menu, $submenu;
1008
1009 $menu_items = [
1010 'betterdocs' => 'betterdocs',
1011 'betterdocs_page_all_docs' => 'betterdocs-all-docs',
1012 'betterdocs_page_add_new' => 'betterdocs-add-new',
1013 'edit-tags.php?taxonomy=doc_category&post_type=docs' => 'betterdocs-categories',
1014 'edit-tags.php?taxonomy=doc_tag&post_type=docs' => 'betterdocs-tags',
1015 'betterdocs-settings' => 'betterdocs-settings',
1016 'betterdocs-analytics' => 'betterdocs-analytics',
1017 'betterdocs-faq' => 'betterdocs-faq',
1018 'betterdocs-glossaries' => 'betterdocs-glossaries',
1019 'betterdocs-ai-chatbot' => 'betterdocs-ai-chatbot',
1020 'edit-tags.php?taxonomy=knowledge_base&post_type=docs' => 'betterdocs-multiplekb'
1021 ];
1022
1023 foreach ( $menu as &$item ) {
1024 if ( isset( $menu_items[ $item[2] ] ) ) {
1025 if ( ! isset( $item[4] ) ) {
1026 $item[4] = '';
1027 }
1028 $item[4] .= '' . $menu_items[ $item[2] ];
1029 }
1030 }
1031
1032 foreach ( $submenu as &$submenu_items ) {
1033 foreach ( $submenu_items as &$sub_item ) {
1034 if ( isset( $menu_items[ $sub_item[2] ] ) ) {
1035 if ( ! isset( $sub_item[4] ) ) {
1036 $sub_item[4] = '';
1037 }
1038 $sub_item[4] .= '' . $menu_items[ $sub_item[2] ];
1039 }
1040 }
1041 }
1042 }
1043
1044 public function quick_setup_menu( $menus ) {
1045 $betterdocs_settings = get_option( 'betterdocs_settings' );
1046 if ( $betterdocs_settings ) {
1047 return $menus;
1048 } else {
1049 $menus['quick_setup'] = $this->normalize_menu(
1050 __( 'Quick Setup', 'betterdocs' ),
1051 'betterdocs-setup',
1052 'delete_users',
1053 [
1054 $this->container->get( SetupWizard::class ),
1055 'views'
1056 ]
1057 );
1058 }
1059
1060 return $menus;
1061 }
1062
1063 public function insert_plugin_links( $links ) {
1064 $links[] = '<a href="admin.php?page=betterdocs-settings">' . __( 'Settings', 'betterdocs' ) . '</a>';
1065
1066 return $links;
1067 }
1068
1069 public function toolbar_menu( $admin_bar ) {
1070 if ( ! is_admin() || ! is_admin_bar_showing() ) {
1071 return;
1072 }
1073
1074 // Show only when the user is a member of this site, or they're a super admin.
1075 if ( ! is_user_member_of_blog() && ! is_super_admin() ) {
1076 return;
1077 }
1078
1079 $docs_url = '';
1080 $encyclopedia_url = '';
1081
1082 if ( $this->settings->get( 'builtin_doc_page' ) ) {
1083 $docs_url = get_post_type_archive_link( 'docs' );
1084 } elseif ( intval( $docs_page = $this->settings->get( 'docs_page' ) ) ) {
1085 $docs_url = ! empty( $docs_page ) ? get_page_link( $docs_page ) : false;
1086 }
1087
1088 if ( ! $docs_url ) {
1089 return;
1090 }
1091
1092 $slug = $this->settings->get( 'encyclopedia_root_slug' );
1093
1094 global $wp_rewrite;
1095 if ( $wp_rewrite->using_index_permalinks() ) {
1096 $slug = $wp_rewrite->index . '/' . $slug;
1097 }
1098
1099 $encyclopedia_url = home_url( $slug );
1100
1101 $admin_bar->add_node(
1102 [
1103 'parent' => 'site-name',
1104 'id' => 'view-docs',
1105 'title' => __( 'Visit Documentation', 'betterdocs' ),
1106 'href' => $docs_url
1107 ]
1108 );
1109
1110 $is_enable_encyclopedia = betterdocs()->settings->get( 'enable_encyclopedia' );
1111
1112 if ( $is_enable_encyclopedia && betterdocs()->is_pro_active() ) {
1113 $admin_bar->add_node(
1114 [
1115 'parent' => 'site-name',
1116 'id' => 'view-encyclopedia',
1117 'title' => __( 'Visit Encyclopedia', 'betterdocs' ),
1118 'href' => $encyclopedia_url
1119 ]
1120 );
1121 }
1122 }
1123
1124 /**
1125 * Save last visited admin ui
1126 *
1127 * @since 3.0.1
1128 *
1129 */
1130 public function save_admin_page() {
1131 if ( isset( $_GET['post_type'] ) && $_GET['post_type'] === 'docs' && isset( $_GET['bdocs_view'] ) && $_GET['bdocs_view'] === 'classic' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1132 update_user_meta( get_current_user_id(), 'last_visited_docs_admin_page', 'classic_ui' );
1133 } elseif ( isset( $_GET['page'] ) && $_GET['page'] === 'betterdocs-admin' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1134 update_user_meta( get_current_user_id(), 'last_visited_docs_admin_page', 'modern_ui' );
1135 }
1136 }
1137
1138 /**
1139 * Return last visited admin ui slug
1140 *
1141 * @return string
1142 * @since 3.0.1
1143 */
1144 public function ui_slug() {
1145 $last_visited = get_user_meta(get_current_user_id(), 'last_visited_docs_admin_page', true);
1146 $docs_exist = get_posts([
1147 'post_type' => 'docs',
1148 'post_status' => 'any',
1149 'numberposts' => 1
1150 ]);
1151
1152 return ($last_visited === 'modern_ui' || empty($docs_exist))
1153 ? 'betterdocs-admin'
1154 : 'edit.php?post_type=docs&bdocs_view=classic';
1155 }
1156
1157 /**
1158 * Resets a duplicate submenu in WordPress if the parent main menu and the first submenu permalink are not the same.
1159 *
1160 * @return string
1161 * @since 3.0.1
1162 */
1163 public function reset_submenu() {
1164 global $submenu;
1165
1166 $docs = get_posts( [ 'post_type' => 'docs' ] );
1167 if ( count( $docs ) == 0 ) {
1168 return;
1169 }
1170
1171 $last_visited = get_user_meta( get_current_user_id(), 'last_visited_docs_admin_page', true );
1172
1173 if ( $last_visited === 'classic_ui' && isset( $submenu['betterdocs-admin'] ) && in_array( 'betterdocs-admin', $submenu['betterdocs-admin'][0] ) ) {
1174 unset( $submenu['betterdocs-admin'][0] );
1175 $submenu['betterdocs-admin'] = array_values( $submenu['betterdocs-admin'] );
1176 }
1177 }
1178 }
1179