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

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

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