PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.3
4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 All 201 releases
← All changes | includes/Core/Admin.php +111 -27 4.9.0 → 4.9.3 View file →
@@ -19,19 +19,26 @@
19 19 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
20 20
21 21 class Admin extends Base {
22 22 /**
23 - * Per-user flag recording that this administrator has opened the MCP screen.
23 + * Per-user flag recording that this administrator has opened the Content IQ
24 + * screen — the discovery badge (ADR-063) now flags Content Intelligence, the
25 + * headline feature, rather than MCP.
24 26 *
25 27 * Stores the timestamp of the first visit, but only its **presence** is read:
26 - * absent means "this user has not seen MCP yet", which is what puts the
27 - * one-time discovery badge on the menu (ADR-063). Per user on purpose — two
28 - * administrators each get their own first look, and neither clears the other's.
28 + * absent means "this user has not seen Content IQ yet", which is what puts the
29 + * one-time discovery badge on the menu. Per user on purpose — two administrators
30 + * each get their own first look, and neither clears the other's. A deliberately
31 + * fresh meta key (not the old `betterdocs_mcp_seen`) so a user who already
32 + * dismissed the MCP badge still gets this one for the new feature.
29 33 *
34 + * The private `*_mcp_*` helper names below are kept as-is to hold the diff to
35 + * the target slug + this key; they now paint the Content IQ item.
36 + *
30 37 * @var string
31 38 * @since 4.9.0
32 39 */
33 - const MCP_SEEN_META = 'betterdocs_mcp_seen';
40 + const MCP_SEEN_META = 'betterdocs_content_iq_seen';
34 41
35 42 /**
36 43 * Whether this request painted the MCP discovery badge onto the menu.
37 44 *
@@ -99,9 +106,8 @@
99 106 *
100 107 * @var FAQBuilder
101 108 */
102 109 private $faq_builder;
103 - private $glossaries;
104 110
105 111 public function __construct( Container $container, PostType $type, Enqueue $assets, Settings $settings, KBMigration $kbmigration ) {
106 112 $this->container = $container;
107 113 $this->assets = $assets;
@@ -115,9 +121,8 @@
115 121 $type->init();
116 122 $type->admin_init();
117 123
118 124 $this->faq_builder = $this->container->get( FAQBuilder::class );
119 - $this->glossaries = $this->container->get( Glossaries::class );
120 125
121 126 /**
122 127 * Register usage tracking (including the daily `put_do_weekly_action` cron
123 128 * handler) on every request — WP-Cron runs with is_admin() === false, so
@@ -170,8 +175,10 @@
170 175 add_filter( 'admin_body_class', array( $this, 'body_classes' ) );
171 176 add_filter( 'parent_file', array( $type, 'highlight_admin_menu' ) );
172 177 add_filter( 'submenu_file', array( $type, 'highlight_admin_submenu' ), 10, 2 );
173 178 add_filter( 'betterdocs_admin_menu', array( $this, 'quick_setup_menu' ), 10, 1 );
179 + // Runs last so it also orders items Pro/add-ons append through this filter.
180 + add_filter( 'betterdocs_admin_menu', array( $this, 'order_admin_menu' ), 999, 1 );
174 181
175 182 /**
176 183 * Remove Comments Column from List Table.
177 184 */
@@ -597,8 +604,9 @@
597 604 'betterdocs-settings',
598 605 'betterdocs-admin',
599 606 'betterdocs-dashboard',
600 607 'betterdocs-analytics',
608 + 'betterdocs-content-iq',
601 609 'betterdocs-glossaries',
602 610 'betterdocs-faq',
603 611 'betterdocs-doc-categories',
604 612 'betterdocs-doc-tags',
@@ -875,8 +883,13 @@
875 883 'betterdocs_pro_version' => betterdocs()->pro_version(),
876 884 'analytics_older' => version_compare( betterdocs()->pro_version(), '3.3.4', '<=' ),
877 885 'betterdocs_ChatBot_plugin' => is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' ),
878 886 'api_docs_teaser' => betterdocs()->show_api_docs_teaser(),
887 + 'glossaries_teaser' => betterdocs()->show_glossary_teaser(),
888 + 'glossaries_needs_pro_update' => betterdocs()->glossaries_needs_pro_update(),
889 + 'glossaries_min_pro_version' => betterdocs()->glossaries_min_pro_version(),
890 + 'glossaries_pro_update_url' => self_admin_url( 'plugins.php' ),
891 + 'content_intelligence_teaser' => betterdocs()->show_content_intelligence_teaser(),
879 892 'is_woocommerce_active' => class_exists( 'WooCommerce' ),
880 893 'total_doc_category_terms' => wp_count_terms( 'doc_category' ),
881 894 'current_admin_language' => Helper::get_current_admin_language(),
882 895 'is_multilingual' => Helper::is_multilingual_active(),
@@ -943,24 +956,10 @@
943 956 'betterdocs_settings' => $betterdocs_settings,
944 957 )
945 958 );
946 959
947 - // Glossaries Related Localization
948 - betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/css/faq.css' );
949 -
950 - betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/js/glossaries.js' );
951 -
952 - betterdocs()->assets->localize(
953 - 'betterdocs-admin-glossaries',
954 - 'betterdocsGlossary',
955 - array(
956 - 'dir_url' => BETTERDOCS_ABSURL,
957 - 'rest_url' => esc_url_raw( rest_url() ),
958 - 'free_version' => betterdocs()->version,
959 - 'nonce' => wp_create_nonce( 'wp_rest' ),
960 - 'betterdocs_settings' => $betterdocs_settings,
961 - )
962 - );
960 + // Glossaries is Pro — Pro's Core\Glossaries::enqueue() owns that bundle
961 + // and both of its localized objects (`betterdocs`, `betterdocsGlossary`).
963 962 }
964 963
965 964 /**
966 965 * All admin pages header
@@ -1095,9 +1094,9 @@
1095 1094 }
1096 1095
1097 1096 if ( isset( $submenu[ $this->slug ] ) && is_array( $submenu[ $this->slug ] ) ) {
1098 1097 foreach ( $submenu[ $this->slug ] as &$sub_item ) {
1099 - if ( isset( $sub_item[2] ) && 'betterdocs-mcp' === $sub_item[2] ) {
1098 + if ( isset( $sub_item[2] ) && 'betterdocs-content-iq' === $sub_item[2] ) {
1100 1099 $sub_item[0] .= self::mcp_submenu_pill();
1101 1100 break;
1102 1101 }
1103 1102 }
@@ -1182,9 +1181,9 @@
1182 1181 public static function is_mcp_screen_request() {
1183 1182 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection; see mark_mcp_seen().
1184 1183 $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
1185 1184
1186 - if ( 'betterdocs-mcp' !== $page ) {
1185 + if ( 'betterdocs-content-iq' !== $page ) {
1187 1186 return false;
1188 1187 }
1189 1188
1190 1189 return current_user_can( 'manage_options' );
@@ -1389,8 +1388,18 @@
1389 1388 'output',
1390 1389 ),
1391 1390 $parent_slug
1392 1391 ),
1392 + 'content_intelligence' => $this->normalize_menu(
1393 + __( 'Content IQ', 'betterdocs' ),
1394 + 'betterdocs-content-iq',
1395 + 'read_docs_analytics',
1396 + array(
1397 + $this,
1398 + 'output',
1399 + ),
1400 + $parent_slug
1401 + ),
1393 1402 'faq' => $this->normalize_menu(
1394 1403 __( 'FAQ Builder', 'betterdocs' ),
1395 1404 'betterdocs-faq',
1396 1405 'read_faq_builder',
@@ -1401,13 +1410,25 @@
1401 1410 $parent_slug
1402 1411 ),
1403 1412 );
1404 1413
1405 - if ( betterdocs()->is_pro_active() && betterdocs()->settings->get( 'enable_glossaries' ) == true ) {
1414 + // Content Intelligence ships in Pro, which overwrites that same key in place.
1415 + // Unlike API Docs it has to sit directly after Analytics, and `menus()` walks
1416 + // this array in insertion order — so the slot is declared inside the literal
1417 + // above and only withdrawn here. Appending it after the fact, api_docs-style,
1418 + // would park it at the bottom of the menu.
1419 + if ( ! ( betterdocs()->show_content_intelligence_teaser() || betterdocs()->has_content_intelligence() ) ) {
1420 + unset( $betterdocs_admin_pages['content_intelligence'] );
1421 + }
1422 +
1423 + // Glossaries is Pro. Reserve this same 'glossaries' slot for Free's locked
1424 + // teaser so the item keeps this position; once Pro is active the real
1425 + // screen overwrites the key in place (same pattern as API Docs below).
1426 + if ( betterdocs()->show_glossary_teaser() || ( betterdocs()->is_pro_active() && betterdocs()->settings->get( 'enable_glossaries' ) == true ) ) {
1406 1427 $betterdocs_admin_pages['glossaries'] = $this->normalize_menu(
1407 1428 __( 'Glossaries', 'betterdocs' ),
1408 1429 'betterdocs-glossaries',
1409 - 'read_docs_analytics',
1430 + betterdocs()->show_glossary_teaser() ? 'manage_options' : 'read_docs_analytics',
1410 1431 array(
1411 1432 $this,
1412 1433 'output',
1413 1434 ),
@@ -1446,8 +1467,70 @@
1446 1467
1447 1468 return apply_filters( 'betterdocs_admin_menu', $betterdocs_admin_pages, array( $this, 'output' ), $parent_slug );
1448 1469 }
1449 1470
1471 + /**
1472 + * Put the BetterDocs submenu in a deliberate order.
1473 + *
1474 + * Order used to be an accident of *when* each item was added: Free declares
1475 + * most of them inline, and reserves in-place slots for `glossaries` /
1476 + * `api_docs` so Pro can overwrite the key without moving it. Anything added
1477 + * purely through this filter, though, could only land at the end — which is
1478 + * why Multiple KB (Pro, priority 100) and AI Chatbot Logs sat after
1479 + * everything else regardless of where they belong.
1480 + *
1481 + * Sorting here, at priority 999, fixes that for every source at once: Free's
1482 + * own entries, Pro's, and any add-on's. Knowledge Base now follows Tags (it
1483 + * is the third taxonomy-ish thing, so it belongs with Categories and Tags
1484 + * rather than past Analytics), and API Docs follows Knowledge Base.
1485 + *
1486 + * Keys not listed keep their relative order and are appended, so an add-on
1487 + * that registers something unknown to this list is never dropped.
1488 + *
1489 + * @param array $pages Menu pages keyed by slug id.
1490 + * @return array
1491 + */
1492 + public function order_admin_menu( $pages ) {
1493 + if ( ! is_array( $pages ) ) {
1494 + return $pages;
1495 + }
1496 +
1497 + $order = array(
1498 + 'betterdocs',
1499 + 'dashboard',
1500 + 'all_docs',
1501 + 'add_new',
1502 + 'categories',
1503 + 'tags',
1504 + 'multiple_kb',
1505 + 'api_docs',
1506 + 'settings',
1507 + 'mcp',
1508 + 'analytics',
1509 + // Content IQ reads as a second Analytics screen, so it has to stay
1510 + // pinned directly behind it. Without this entry it would fall into
1511 + // the unknown-key bucket below and be appended to the bottom of the
1512 + // menu — the exact placement the menu literal avoids by declaring
1513 + // the slot inline rather than filtering it in api_docs-style.
1514 + 'content_intelligence',
1515 + 'faq',
1516 + 'glossaries',
1517 + 'ai_chatbot',
1518 + 'ai_chatbot_logs',
1519 + );
1520 +
1521 + $ordered = array();
1522 + foreach ( $order as $key ) {
1523 + if ( array_key_exists( $key, $pages ) ) {
1524 + $ordered[ $key ] = $pages[ $key ];
1525 + unset( $pages[ $key ] );
1526 + }
1527 + }
1528 +
1529 + // `$pages` now holds only unknown keys, still in their original order.
1530 + return array_merge( $ordered, $pages );
1531 + }
1532 +
1450 1533 public function add_custom_classes_to_menu_items() {
1451 1534 global $menu, $submenu;
1452 1535
1453 1536 $menu_items = array(
@@ -1458,8 +1541,9 @@
1458 1541 'betterdocs-doc-tags' => 'betterdocs-tags',
1459 1542 'betterdocs-settings' => 'betterdocs-settings',
1460 1543 'betterdocs-mcp' => 'betterdocs-mcp',
1461 1544 'betterdocs-analytics' => 'betterdocs-analytics',
1545 + 'betterdocs-content-iq' => 'betterdocs-content-iq',
1462 1546 'betterdocs-faq' => 'betterdocs-faq',
1463 1547 'betterdocs-glossaries' => 'betterdocs-glossaries',
1464 1548 'betterdocs-ai-chatbot' => 'betterdocs-ai-chatbot',
1465 1549 'betterdocs-api-docs' => 'betterdocs-api-docs',