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

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