| @@ -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 | * |
| @@ -170,8 +177,10 @@ | ||
| 170 | 177 | add_filter( 'admin_body_class', array( $this, 'body_classes' ) ); |
| 171 | 178 | add_filter( 'parent_file', array( $type, 'highlight_admin_menu' ) ); |
| 172 | 179 | add_filter( 'submenu_file', array( $type, 'highlight_admin_submenu' ), 10, 2 ); |
| 173 | 180 | add_filter( 'betterdocs_admin_menu', array( $this, 'quick_setup_menu' ), 10, 1 ); |
| 181 | + // Runs last so it also orders items Pro/add-ons append through this filter. | |
| 182 | + add_filter( 'betterdocs_admin_menu', array( $this, 'order_admin_menu' ), 999, 1 ); | |
| 174 | 183 | |
| 175 | 184 | /** |
| 176 | 185 | * Remove Comments Column from List Table. |
| 177 | 186 | */ |
| @@ -597,8 +606,9 @@ | ||
| 597 | 606 | 'betterdocs-settings', |
| 598 | 607 | 'betterdocs-admin', |
| 599 | 608 | 'betterdocs-dashboard', |
| 600 | 609 | 'betterdocs-analytics', |
| 610 | + 'betterdocs-content-iq', | |
| 601 | 611 | 'betterdocs-glossaries', |
| 602 | 612 | 'betterdocs-faq', |
| 603 | 613 | 'betterdocs-doc-categories', |
| 604 | 614 | 'betterdocs-doc-tags', |
| @@ -876,8 +886,9 @@ | ||
| 876 | 886 | 'analytics_older' => version_compare( betterdocs()->pro_version(), '3.3.4', '<=' ), |
| 877 | 887 | 'betterdocs_ChatBot_plugin' => is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' ), |
| 878 | 888 | 'api_docs_teaser' => betterdocs()->show_api_docs_teaser(), |
| 879 | 889 | 'glossaries_teaser' => betterdocs()->show_glossary_teaser(), |
| 890 | + 'content_intelligence_teaser' => betterdocs()->show_content_intelligence_teaser(), | |
| 880 | 891 | 'is_woocommerce_active' => class_exists( 'WooCommerce' ), |
| 881 | 892 | 'total_doc_category_terms' => wp_count_terms( 'doc_category' ), |
| 882 | 893 | 'current_admin_language' => Helper::get_current_admin_language(), |
| 883 | 894 | 'is_multilingual' => Helper::is_multilingual_active(), |
| @@ -1096,9 +1107,9 @@ | ||
| 1096 | 1107 | } |
| 1097 | 1108 | |
| 1098 | 1109 | if ( isset( $submenu[ $this->slug ] ) && is_array( $submenu[ $this->slug ] ) ) { |
| 1099 | 1110 | foreach ( $submenu[ $this->slug ] as &$sub_item ) { |
| 1100 | - if ( isset( $sub_item[2] ) && 'betterdocs-mcp' === $sub_item[2] ) { | |
| 1111 | + if ( isset( $sub_item[2] ) && 'betterdocs-content-iq' === $sub_item[2] ) { | |
| 1101 | 1112 | $sub_item[0] .= self::mcp_submenu_pill(); |
| 1102 | 1113 | break; |
| 1103 | 1114 | } |
| 1104 | 1115 | } |
| @@ -1183,9 +1194,9 @@ | ||
| 1183 | 1194 | public static function is_mcp_screen_request() { |
| 1184 | 1195 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection; see mark_mcp_seen(). |
| 1185 | 1196 | $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; |
| 1186 | 1197 | |
| 1187 | - if ( 'betterdocs-mcp' !== $page ) { | |
| 1198 | + if ( 'betterdocs-content-iq' !== $page ) { | |
| 1188 | 1199 | return false; |
| 1189 | 1200 | } |
| 1190 | 1201 | |
| 1191 | 1202 | return current_user_can( 'manage_options' ); |
| @@ -1390,8 +1401,18 @@ | ||
| 1390 | 1401 | 'output', |
| 1391 | 1402 | ), |
| 1392 | 1403 | $parent_slug |
| 1393 | 1404 | ), |
| 1405 | + 'content_intelligence' => $this->normalize_menu( | |
| 1406 | + __( 'Content IQ', 'betterdocs' ), | |
| 1407 | + 'betterdocs-content-iq', | |
| 1408 | + 'read_docs_analytics', | |
| 1409 | + array( | |
| 1410 | + $this, | |
| 1411 | + 'output', | |
| 1412 | + ), | |
| 1413 | + $parent_slug | |
| 1414 | + ), | |
| 1394 | 1415 | 'faq' => $this->normalize_menu( |
| 1395 | 1416 | __( 'FAQ Builder', 'betterdocs' ), |
| 1396 | 1417 | 'betterdocs-faq', |
| 1397 | 1418 | 'read_faq_builder', |
| @@ -1402,8 +1423,17 @@ | ||
| 1402 | 1423 | $parent_slug |
| 1403 | 1424 | ), |
| 1404 | 1425 | ); |
| 1405 | 1426 | |
| 1427 | + // Content Intelligence ships in Pro, which overwrites that same key in place. | |
| 1428 | + // Unlike API Docs it has to sit directly after Analytics, and `menus()` walks | |
| 1429 | + // this array in insertion order — so the slot is declared inside the literal | |
| 1430 | + // above and only withdrawn here. Appending it after the fact, api_docs-style, | |
| 1431 | + // would park it at the bottom of the menu. | |
| 1432 | + if ( ! ( betterdocs()->show_content_intelligence_teaser() || betterdocs()->has_content_intelligence() ) ) { | |
| 1433 | + unset( $betterdocs_admin_pages['content_intelligence'] ); | |
| 1434 | + } | |
| 1435 | + | |
| 1406 | 1436 | // Glossaries is Pro. Reserve this same 'glossaries' slot for Free's locked |
| 1407 | 1437 | // teaser so the item keeps this position; once Pro is active the real |
| 1408 | 1438 | // screen overwrites the key in place (same pattern as API Docs below). |
| 1409 | 1439 | if ( betterdocs()->show_glossary_teaser() || ( betterdocs()->is_pro_active() && betterdocs()->settings->get( 'enable_glossaries' ) == true ) ) { |
| @@ -1450,8 +1480,70 @@ | ||
| 1450 | 1480 | |
| 1451 | 1481 | return apply_filters( 'betterdocs_admin_menu', $betterdocs_admin_pages, array( $this, 'output' ), $parent_slug ); |
| 1452 | 1482 | } |
| 1453 | 1483 | |
| 1484 | + /** | |
| 1485 | + * Put the BetterDocs submenu in a deliberate order. | |
| 1486 | + * | |
| 1487 | + * Order used to be an accident of *when* each item was added: Free declares | |
| 1488 | + * most of them inline, and reserves in-place slots for `glossaries` / | |
| 1489 | + * `api_docs` so Pro can overwrite the key without moving it. Anything added | |
| 1490 | + * purely through this filter, though, could only land at the end — which is | |
| 1491 | + * why Multiple KB (Pro, priority 100) and AI Chatbot Logs sat after | |
| 1492 | + * everything else regardless of where they belong. | |
| 1493 | + * | |
| 1494 | + * Sorting here, at priority 999, fixes that for every source at once: Free's | |
| 1495 | + * own entries, Pro's, and any add-on's. Knowledge Base now follows Tags (it | |
| 1496 | + * is the third taxonomy-ish thing, so it belongs with Categories and Tags | |
| 1497 | + * rather than past Analytics), and API Docs follows Knowledge Base. | |
| 1498 | + * | |
| 1499 | + * Keys not listed keep their relative order and are appended, so an add-on | |
| 1500 | + * that registers something unknown to this list is never dropped. | |
| 1501 | + * | |
| 1502 | + * @param array $pages Menu pages keyed by slug id. | |
| 1503 | + * @return array | |
| 1504 | + */ | |
| 1505 | + public function order_admin_menu( $pages ) { | |
| 1506 | + if ( ! is_array( $pages ) ) { | |
| 1507 | + return $pages; | |
| 1508 | + } | |
| 1509 | + | |
| 1510 | + $order = array( | |
| 1511 | + 'betterdocs', | |
| 1512 | + 'dashboard', | |
| 1513 | + 'all_docs', | |
| 1514 | + 'add_new', | |
| 1515 | + 'categories', | |
| 1516 | + 'tags', | |
| 1517 | + 'multiple_kb', | |
| 1518 | + 'api_docs', | |
| 1519 | + 'settings', | |
| 1520 | + 'mcp', | |
| 1521 | + 'analytics', | |
| 1522 | + // Content IQ reads as a second Analytics screen, so it has to stay | |
| 1523 | + // pinned directly behind it. Without this entry it would fall into | |
| 1524 | + // the unknown-key bucket below and be appended to the bottom of the | |
| 1525 | + // menu — the exact placement the menu literal avoids by declaring | |
| 1526 | + // the slot inline rather than filtering it in api_docs-style. | |
| 1527 | + 'content_intelligence', | |
| 1528 | + 'faq', | |
| 1529 | + 'glossaries', | |
| 1530 | + 'ai_chatbot', | |
| 1531 | + 'ai_chatbot_logs', | |
| 1532 | + ); | |
| 1533 | + | |
| 1534 | + $ordered = array(); | |
| 1535 | + foreach ( $order as $key ) { | |
| 1536 | + if ( array_key_exists( $key, $pages ) ) { | |
| 1537 | + $ordered[ $key ] = $pages[ $key ]; | |
| 1538 | + unset( $pages[ $key ] ); | |
| 1539 | + } | |
| 1540 | + } | |
| 1541 | + | |
| 1542 | + // `$pages` now holds only unknown keys, still in their original order. | |
| 1543 | + return array_merge( $ordered, $pages ); | |
| 1544 | + } | |
| 1545 | + | |
| 1454 | 1546 | public function add_custom_classes_to_menu_items() { |
| 1455 | 1547 | global $menu, $submenu; |
| 1456 | 1548 | |
| 1457 | 1549 | $menu_items = array( |
| @@ -1462,8 +1554,9 @@ | ||
| 1462 | 1554 | 'betterdocs-doc-tags' => 'betterdocs-tags', |
| 1463 | 1555 | 'betterdocs-settings' => 'betterdocs-settings', |
| 1464 | 1556 | 'betterdocs-mcp' => 'betterdocs-mcp', |
| 1465 | 1557 | 'betterdocs-analytics' => 'betterdocs-analytics', |
| 1558 | + 'betterdocs-content-iq' => 'betterdocs-content-iq', | |
| 1466 | 1559 | 'betterdocs-faq' => 'betterdocs-faq', |
| 1467 | 1560 | 'betterdocs-glossaries' => 'betterdocs-glossaries', |
| 1468 | 1561 | 'betterdocs-ai-chatbot' => 'betterdocs-ai-chatbot', |
| 1469 | 1562 | 'betterdocs-api-docs' => 'betterdocs-api-docs', |