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