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

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