PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | class/class-mainwp-system.php +662 -13 6.1.3 → 6.2.1 View file →
@@ -34,9 +34,9 @@
34 34 * Public static variable to hold the current plugin version.
35 35 *
36 36 * @var string Current plugin version.
37 37 */
38 - public static $version = '6.1.3'; // NOSONAR.
38 + public static $version = '6.2.1'; // NOSONAR.
39 39
40 40 /**
41 41 * Private static variable to hold the single instance of the class.
42 42 *
@@ -63,9 +63,9 @@
63 63 * Private variable to hold the check update version number.
64 64 *
65 65 * @var string The plugin current update version.
66 66 */
67 - private $check_ver_update = '0.0.5';
67 + private $check_ver_update = '0.0.8';
68 68
69 69 /**
70 70 * Private variable to hold the plugin slug (mainwp/mainwp.php)
71 71 *
@@ -239,8 +239,9 @@
239 239 MainWP_Bulk_Post::get_instance();
240 240
241 241 add_action( 'admin_init', array( &$this, 'admin_init' ), 20 );
242 242 add_action( 'admin_init', array( $this, 'hook_admin_update_check' ) );
243 + add_action( 'admin_init', array( $this, 'admin_init_update' ) );
243 244 add_action( 'after_setup_theme', array( &$this, 'after_setup_theme' ) );
244 245
245 246 add_action( 'init', array( &$this, 'parse_init' ) );
246 247 add_action( 'init', array( &$this, 'init_jobs' ) );
@@ -301,13 +302,16 @@
301 302
302 303 if ( defined( 'WP_CLI' ) && WP_CLI ) {
303 304 MainWP_WP_CLI_Command::init();
304 305 }
305 - if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['mainwp_run'] ) && 'test' === $_GET['mainwp_run'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
306 - add_action( 'init', array( MainWP_System_Cron_Jobs::instance(), 'cron_active' ), PHP_INT_MAX );
307 - }
308 306 MainWP_Unhooks_Helper::instance();
309 307 MainWP_Cache_Warm_Helper::instance();
308 + MainWP_System_Utility::instance();
309 +
310 + add_action(
311 + 'wp_ajax_nopriv_mainwp_self_connect',
312 + array( MainWP_Post_Handler::instance(), 'ajax_self_connect' )
313 + );
310 314 }
311 315
312 316 /**
313 317 * Method get_dashboard_version()
@@ -398,8 +402,9 @@
398 402 'mainwp_show_language_updates',
399 403 'mainwp_logger_check_daily',
400 404 'mainwp_site_actions_notification_enable',
401 405 'mainwp_update_check_version',
406 + 'mainwp_admin_init_update_data',
402 407 'mainwp_setting_demo_mode_enabled',
403 408 'mainwp_log_wait_lasttime',
404 409 'mainwp_cron_license_deactivated_alert_lasttime',
405 410 'mainwp_updatescheck_is_running',
@@ -612,9 +617,9 @@
612 617 */
613 618 public static function is_mainwp_site_page() {
614 619 $is_page = false;
615 620 //phpcs:disable WordPress.Security.NonceVerification.Recommended
616 - if ( isset( $_GET['page'] ) && 'CostTrackerAdd' !== $_GET['page'] && ( ( ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ) || ( isset( $_GET['dashboard'] ) && ! empty( $_GET['dashboard'] ) ) || ( isset( $_GET['updateid'] ) && ! empty( $_GET['updateid'] ) ) || ( isset( $_GET['monitor_wpid'] ) && ! empty( $_GET['monitor_wpid'] ) ) || ( isset( $_GET['emailsettingsid'] ) && ! empty( $_GET['emailsettingsid'] ) ) || ( isset( $_GET['scanid'] ) && ! empty( $_GET['scanid'] ) ) ) || ( 'ServerInformation' === $_GET['page'] || 'ServerInformationCron' === $_GET['page'] || 'ErrorLog' === $_GET['page'] || 'ActionLogs' === $_GET['page'] || 'PluginPrivacy' === $_GET['page'] || 'Settings' === $_GET['page'] || 'SettingsAdvanced' === $_GET['page'] || 'SettingsEmail' === $_GET['page'] || 'MainWPTools' === $_GET['page'] || 'SettingsInsights' === $_GET['page'] || 'SettingsApiBackups' === $_GET['page'] || 'MonitoringSettings' === $_GET['page'] ) ) ) {
621 + if ( isset( $_GET['page'] ) && 'CostTrackerAdd' !== $_GET['page'] && ( ( ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ) || ( isset( $_GET['dashboard'] ) && ! empty( $_GET['dashboard'] ) ) || ( isset( $_GET['updateid'] ) && ! empty( $_GET['updateid'] ) ) || ( isset( $_GET['monitor_wpid'] ) && ! empty( $_GET['monitor_wpid'] ) ) || ( isset( $_GET['emailsettingsid'] ) && ! empty( $_GET['emailsettingsid'] ) ) || ( isset( $_GET['scanid'] ) && ! empty( $_GET['scanid'] ) ) ) || ( 'ServerInformation' === $_GET['page'] || 'ServerInformationCron' === $_GET['page'] || 'ErrorLog' === $_GET['page'] || 'ActionLogs' === $_GET['page'] || 'PluginPrivacy' === $_GET['page'] || 'Settings' === $_GET['page'] || 'SettingsAdvanced' === $_GET['page'] || 'SettingsEmail' === $_GET['page'] || 'PremiumUpdates' === $_GET['page'] || 'MainWPTools' === $_GET['page'] || 'SettingsInsights' === $_GET['page'] || 'SettingsApiBackups' === $_GET['page'] || 'MonitoringSettings' === $_GET['page'] ) ) ) {
617 622 $is_page = true;
618 623 }
619 624 //phpcs:enable
620 625
@@ -788,19 +793,15 @@
788 793 * Method hook_admin_update_check()
789 794 */
790 795 public function hook_admin_update_check() {
791 796 $current_ver = $this->check_ver_update;
792 - $saved_ver = get_option( 'mainwp_update_check_version', false );
797 + $saved_ver = get_option( 'mainwp_update_check_version', '' );
793 798
794 - if ( false === $saved_ver ) {
795 - return;
796 - }
797 -
798 799 if ( version_compare( $saved_ver, $current_ver, '=' ) ) {
799 800 return;
800 801 }
801 802
802 - if ( version_compare( $saved_ver, '0.0.4', '<' ) ) {
803 + if ( ! empty( $saved_ver ) && version_compare( $saved_ver, '0.0.4', '<' ) ) {
803 804 $sched = wp_next_scheduled( 'mainwp_cronstats_action' );
804 805 if ( ! empty( $sched ) ) {
805 806 wp_unschedule_event( $sched, 'mainwp_cronstats_action' );
806 807 }
@@ -807,9 +808,9 @@
807 808 global $wpdb;
808 809 $wpdb->query( 'DELETE FROM ' . $wpdb->usermeta . ' WHERE meta_key = "mainwp_widgets_sorted_toplevel_page_mainwp_tab" OR meta_key="mainwp_settings_show_widgets"' );//phpcs:ignore -- safe.
809 810 }
810 811
811 - if ( version_compare( $saved_ver, '0.0.5', '<' ) ) {
812 + if ( ! empty( $saved_ver ) && version_compare( $saved_ver, '0.0.5', '<' ) ) {
812 813 $all_ext = MainWP_Extensions_View::get_available_extensions();
813 814 foreach ( $all_ext as $slug => $info ) {
814 815 $data = MainWP_Api_Manager::instance()->get_activation_info( $slug );
815 816 if ( is_array( $data ) && isset( $data['api_key'] ) ) {
@@ -820,12 +821,73 @@
820 821 }
821 822 update_option( 'mainwp_extensions_all_activation_cached', '' );
822 823 }
823 824
825 + if ( version_compare( $saved_ver, '0.0.8', '<' ) ) {
826 + static::handle_admin_perform_update( 'attempted_repair_primary_monitors' ); // init attempted repair.
827 + }
828 +
824 829 MainWP_Utility::update_option( 'mainwp_update_check_version', $current_ver );
825 830 }
826 831
832 +
827 833 /**
834 + * Method admin_init_update()
835 + */
836 + public function admin_init_update() {
837 + static::handle_admin_perform_update();
838 + }
839 +
840 +
841 + /**
842 + * Method handle_admin_perform_update()
843 + *
844 + * @param string $action Init action.
845 + */
846 + public function handle_admin_perform_update( $action = '' ) {
847 +
848 + $update_actions = get_option( 'mainwp_admin_init_update_data' );
849 +
850 + /**
851 + * Filters the update actions.
852 + *
853 + * @param array $update_actions The available update actions.
854 + * @param string $action The current update action.
855 + *
856 + * @since 6.2.1
857 + *
858 + * @return array The filtered update actions.
859 + */
860 + $update_actions = apply_filters( 'mainwp_admin_init_update_data', $update_actions, $action );
861 +
862 + if ( ! empty( $action ) ) {
863 + if ( ! is_array( $update_actions ) ) {
864 + $update_actions = array();
865 + }
866 + if ( 'attempted_repair_primary_monitors' === $action ) {
867 + $update_actions['attempted_repair_primary_monitors'] = 0;
868 + }
869 + }
870 +
871 + if ( is_array( $update_actions ) ) {
872 + $update = false;
873 + if ( isset( $update_actions['attempted_repair_primary_monitors'] ) ) {
874 + $result = MainWP_DB_Uptime_Monitoring::instance()->repair_missing_primary_monitors( $update_actions['attempted_repair_primary_monitors'] );
875 + if ( is_array( $result ) && ! empty( $result['attempted'] ) ) {
876 + $update_actions['attempted_repair_primary_monitors'] = $result['attempted'];
877 + } else {
878 + unset( $update_actions['attempted_repair_primary_monitors'] );
879 + }
880 + $update = true;
881 + }
882 +
883 + if ( $update ) {
884 + MainWP_Utility::update_option( 'mainwp_admin_init_update_data', $update_actions );
885 + }
886 + }
887 + }
888 +
889 + /**
828 890 * Method admin_init()
829 891 *
830 892 * Do nothing if current user is not an Admin else display the page.
831 893 *
@@ -969,8 +1031,9 @@
969 1031 remove_action( 'admin_notices', 'update_nag', 3 );
970 1032 }
971 1033 }
972 1034
1035 +
973 1036 /**
974 1037 * Method hook_wp_shutdown()
975 1038 */
976 1039 public function hook_wp_shutdown() {
@@ -1225,8 +1288,594 @@
1225 1288 wp_script_add_data( $h, 'strategy', 'defer' ); // adds defer to <script> tag.
1226 1289 }
1227 1290 }
1228 1291 }
1292 +
1293 + $this->enqueue_command_palette_script();
1294 + }
1295 +
1296 + /**
1297 + * Enqueue the MainWP command palette integration.
1298 + */
1299 + private function enqueue_command_palette_script() {
1300 +
1301 + if ( ! wp_script_is( 'wp-core-commands', 'enqueued' ) ) {
1302 + return;
1303 + }
1304 +
1305 + $palette_data = $this->get_command_palette_data();
1306 +
1307 + if ( empty( $palette_data['commands'] ) && empty( $palette_data['unregister'] ) ) {
1308 + return;
1309 + }
1310 +
1311 + wp_enqueue_script(
1312 + 'mainwp-command-palette',
1313 + MAINWP_PLUGIN_URL . 'assets/js/mainwp-command-palette.js',
1314 + array( 'wp-core-commands', 'wp-data' ),
1315 + $this->current_version,
1316 + true
1317 + );
1318 +
1319 + wp_add_inline_script(
1320 + 'mainwp-command-palette',
1321 + 'window.mainwpCommandPalette = ' . wp_json_encode( $palette_data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';',
1322 + 'before'
1323 + );
1324 + }
1325 +
1326 + /**
1327 + * Build the MainWP command palette configuration.
1328 + *
1329 + * @return array<string, array<int, mixed>> Command palette configuration.
1330 + */
1331 + private function get_command_palette_data() {
1332 + $registered_pages = $this->get_command_palette_registered_pages();
1333 + $registered_page_urls = array();
1334 +
1335 + foreach ( $registered_pages as $slug => $page_data ) {
1336 + $registered_page_urls[ $slug ] = $page_data['url'];
1337 + }
1338 +
1339 + $commands = $this->get_command_palette_menu_commands( $registered_pages );
1340 +
1341 + if ( empty( $commands ) ) {
1342 + return array(
1343 + 'commands' => array(),
1344 + 'unregister' => array(),
1345 + );
1346 + }
1347 +
1348 + $commands = apply_filters( 'mainwp_command_palette_commands', $commands, $registered_page_urls );
1349 +
1350 + $unregister = array( 'mainwp_tab' );
1351 + foreach ( array_keys( $registered_pages ) as $slug ) {
1352 + $unregister[] = 'mainwp_tab-' . $slug;
1353 + }
1354 +
1355 + $unregister = apply_filters( 'mainwp_command_palette_unregister', array_values( array_unique( $unregister ) ), $registered_page_urls );
1356 +
1357 + return array(
1358 + 'commands' => array_values( array_filter( $commands ) ),
1359 + 'unregister' => array_values( array_filter( $unregister ) ),
1360 + );
1361 + }
1362 +
1363 + /**
1364 + * Get registered MainWP submenu pages that can be opened from the palette.
1365 + *
1366 + * @return array<string, array<string, mixed>> Registered page map.
1367 + */
1368 + private function get_command_palette_registered_pages() {
1369 + global $submenu;
1370 +
1371 + if ( empty( $submenu['mainwp_tab'] ) || ! is_array( $submenu['mainwp_tab'] ) ) {
1372 + return array();
1373 + }
1374 +
1375 + $registered_pages = array();
1376 +
1377 + foreach ( $submenu['mainwp_tab'] as $submenu_item ) {
1378 + if ( empty( $submenu_item[2] ) || ( ! empty( $submenu_item[1] ) && ! current_user_can( $submenu_item[1] ) ) ) {
1379 + continue;
1380 + }
1381 +
1382 + $menu_slug = (string) $submenu_item[2];
1383 + $menu_url = '';
1384 +
1385 + if ( preg_match( '/\.php($|\?)/', $menu_slug ) || $this->is_command_palette_absolute_url( $menu_slug ) ) {
1386 + $menu_url = $menu_slug;
1387 + } elseif ( ! empty( menu_page_url( $menu_slug, false ) ) ) {
1388 + $menu_url = wp_specialchars_decode( menu_page_url( $menu_slug, false ), ENT_QUOTES );
1389 + }
1390 +
1391 + if ( empty( $menu_url ) ) {
1392 + continue;
1393 + }
1394 +
1395 + $raw_label = isset( $submenu_item[0] ) ? (string) $submenu_item[0] : '';
1396 +
1397 + $registered_pages[ $menu_slug ] = array(
1398 + 'label' => $this->clean_command_palette_label( $raw_label ),
1399 + 'url' => $menu_url,
1400 + 'hidden' => false !== strpos( $raw_label, 'mainwp-hidden' ),
1401 + );
1402 + }
1403 +
1404 + return $registered_pages;
1405 + }
1406 +
1407 + /**
1408 + * Build MainWP command-palette commands from runtime menu data.
1409 + *
1410 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1411 + *
1412 + * @return array<int, array<string, mixed>> Command list.
1413 + */
1414 + private function get_command_palette_menu_commands( $registered_pages ) {
1415 + $menu_nodes = $this->get_command_palette_menu_nodes();
1416 +
1417 + if ( empty( $menu_nodes ) ) {
1418 + $menu_nodes = array();
1419 + }
1420 +
1421 + $nodes_by_id = array();
1422 + $nodes_by_slug = array();
1423 +
1424 + foreach ( $menu_nodes as $node ) {
1425 + $nodes_by_id[ $node['id'] ] = $node;
1426 +
1427 + if ( ! empty( $node['slug_key'] ) && ! isset( $nodes_by_slug[ $node['slug_key'] ] ) ) {
1428 + $nodes_by_slug[ $node['slug_key'] ] = $node['id'];
1429 + }
1430 + }
1431 +
1432 + $commands_by_url = array();
1433 + $command_page_slugs = array();
1434 +
1435 + foreach ( $menu_nodes as $node ) {
1436 + $path_titles = $this->get_command_palette_menu_path_titles( $node, $nodes_by_id, $nodes_by_slug );
1437 +
1438 + if ( empty( $path_titles ) ) {
1439 + continue;
1440 + }
1441 +
1442 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), implode( ' > ', $path_titles ) );
1443 +
1444 + $command_name = ! empty( $node['page_slug'] ) && isset( $registered_pages[ $node['page_slug'] ] )
1445 + ? 'mainwp_tab-' . $node['page_slug']
1446 + : 'mainwp-command-' . sanitize_key( ! empty( $node['page_slug'] ) ? $node['page_slug'] : md5( $node['url'] ) );
1447 +
1448 + $command = array(
1449 + 'name' => $command_name,
1450 + 'label' => $label,
1451 + 'url' => $node['url'],
1452 + 'keywords' => $this->get_command_palette_keywords( $path_titles, $node['page_slug'] ),
1453 + 'searchLabel' => $label,
1454 + '_depth' => count( $path_titles ),
1455 + );
1456 +
1457 + if ( ! isset( $commands_by_url[ $node['url'] ] ) || $command['_depth'] > $commands_by_url[ $node['url'] ]['_depth'] ) {
1458 + $commands_by_url[ $node['url'] ] = $command;
1459 + }
1460 +
1461 + if ( ! empty( $node['page_slug'] ) ) {
1462 + $command_page_slugs[ $node['page_slug'] ] = true;
1463 + }
1464 + }
1465 +
1466 + foreach ( $this->get_command_palette_utility_commands( $registered_pages ) as $command ) {
1467 + if ( empty( $command['url'] ) || empty( $command['label'] ) ) {
1468 + continue;
1469 + }
1470 +
1471 + $command['_depth'] = isset( $command['_depth'] ) ? (int) $command['_depth'] : 1;
1472 +
1473 + if (
1474 + ! isset( $commands_by_url[ $command['url'] ] ) ||
1475 + $command['_depth'] >= $commands_by_url[ $command['url'] ]['_depth']
1476 + ) {
1477 + $commands_by_url[ $command['url'] ] = $command;
1478 + }
1479 +
1480 + if ( ! empty( $command['_page_slug'] ) ) {
1481 + $command_page_slugs[ $command['_page_slug'] ] = true;
1482 + }
1483 + }
1484 +
1485 + foreach ( $registered_pages as $page_slug => $page_data ) {
1486 + if (
1487 + isset( $command_page_slugs[ $page_slug ] ) ||
1488 + empty( $page_data['label'] ) ||
1489 + empty( $page_data['url'] ) ||
1490 + ! empty( $page_data['hidden'] )
1491 + ) {
1492 + continue;
1493 + }
1494 +
1495 + if ( isset( $commands_by_url[ $page_data['url'] ] ) ) {
1496 + continue;
1497 + }
1498 +
1499 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), $page_data['label'] );
1500 +
1501 + $commands_by_url[ $page_data['url'] ] = array(
1502 + 'name' => 'mainwp_tab-' . $page_slug,
1503 + 'label' => $label,
1504 + 'url' => $page_data['url'],
1505 + 'keywords' => $this->get_command_palette_keywords( array( $page_data['label'] ), $page_slug ),
1506 + 'searchLabel' => $label,
1507 + '_depth' => 1,
1508 + );
1509 + }
1510 +
1511 + foreach ( $commands_by_url as &$command ) {
1512 + unset( $command['_depth'], $command['_page_slug'] );
1513 + }
1514 + unset( $command );
1515 +
1516 + return array_values( $commands_by_url );
1517 + }
1518 +
1519 + /**
1520 + * Build command-palette commands for MainWP pages that do not reliably exist in the global sidebar tree.
1521 + *
1522 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1523 + *
1524 + * @return array<int, array<string, mixed>> Command list.
1525 + */
1526 + private function get_command_palette_utility_commands( $registered_pages ) {
1527 + return array_merge(
1528 + $this->get_command_palette_settings_commands( $registered_pages ),
1529 + $this->get_command_palette_info_commands( $registered_pages )
1530 + );
1531 + }
1532 +
1533 + /**
1534 + * Build Settings-section command palette commands.
1535 + *
1536 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1537 + *
1538 + * @return array<int, array<string, mixed>> Command list.
1539 + */
1540 + private function get_command_palette_settings_commands( $registered_pages ) {
1541 + $commands = array();
1542 +
1543 + foreach ( MainWP_Settings::get_command_palette_items() as $item ) {
1544 + if ( empty( $item['slug'] ) || empty( $item['path_titles'] ) ) {
1545 + continue;
1546 + }
1547 +
1548 + $commands[] = $this->build_command_palette_registered_command(
1549 + $registered_pages,
1550 + $item['slug'],
1551 + $item['path_titles'],
1552 + array( 'mainwp settings' ),
1553 + isset( $item['href'] ) ? $item['href'] : ''
1554 + );
1555 + }
1556 +
1557 + return array_values( array_filter( $commands ) );
1558 + }
1559 +
1560 + /**
1561 + * Build System Info and profile utility command palette commands.
1562 + *
1563 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1564 + *
1565 + * @return array<int, array<string, mixed>> Command list.
1566 + */
1567 + private function get_command_palette_info_commands( $registered_pages ) {
1568 + $commands = array();
1569 +
1570 + foreach ( MainWP_Server_Information::get_command_palette_items() as $item ) {
1571 + if ( empty( $item['slug'] ) || empty( $item['path_titles'] ) ) {
1572 + continue;
1573 + }
1574 +
1575 + $commands[] = $this->build_command_palette_registered_command(
1576 + $registered_pages,
1577 + $item['slug'],
1578 + $item['path_titles'],
1579 + array( 'system info', 'server info' ),
1580 + isset( $item['href'] ) ? $item['href'] : ''
1581 + );
1582 + }
1583 +
1584 + return array_values( array_filter( $commands ) );
1585 + }
1586 +
1587 + /**
1588 + * Build a command palette command for a registered page.
1589 + *
1590 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1591 + * @param string $page_slug Registered page slug.
1592 + * @param array<int, string> $path_titles Breadcrumb path labels.
1593 + * @param array<int, string> $extra_keywords Additional search keywords.
1594 + * @param string $href Optional command href override.
1595 + *
1596 + * @return array<string, mixed>|null Command configuration.
1597 + */
1598 + private function build_command_palette_registered_command( $registered_pages, $page_slug, $path_titles, $extra_keywords = array(), $href = '' ) {
1599 + if ( empty( $registered_pages[ $page_slug ]['url'] ) ) {
1600 + return null;
1601 + }
1602 +
1603 + $path_titles = array_values(
1604 + array_filter(
1605 + array_map(
1606 + array( $this, 'clean_command_palette_label' ),
1607 + is_array( $path_titles ) ? $path_titles : array()
1608 + )
1609 + )
1610 + );
1611 +
1612 + if ( empty( $path_titles ) ) {
1613 + return null;
1614 + }
1615 +
1616 + $keywords = $this->get_command_palette_keywords( $path_titles, $page_slug );
1617 +
1618 + foreach ( $extra_keywords as $keyword ) {
1619 + $keyword = strtolower( $this->clean_command_palette_label( $keyword ) );
1620 +
1621 + if ( ! empty( $keyword ) ) {
1622 + $keywords[] = $keyword;
1623 + }
1624 + }
1625 +
1626 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), implode( ' > ', $path_titles ) );
1627 + $url = ! empty( $href ) ? $this->normalize_command_palette_url( $href, $page_slug ) : $registered_pages[ $page_slug ]['url'];
1628 +
1629 + if ( empty( $url ) ) {
1630 + return null;
1631 + }
1632 +
1633 + return array(
1634 + 'name' => 'mainwp_tab-' . $page_slug,
1635 + 'label' => $label,
1636 + 'url' => $url,
1637 + 'keywords' => array_values( array_unique( array_filter( $keywords ) ) ),
1638 + 'searchLabel' => $label,
1639 + '_depth' => count( $path_titles ),
1640 + '_page_slug' => $page_slug,
1641 + );
1642 + }
1643 +
1644 + /**
1645 + * Collect MainWP menu nodes from the runtime left-menu structures.
1646 + *
1647 + * @return array<int, array<string, string>> Menu nodes.
1648 + */
1649 + private function get_command_palette_menu_nodes() {
1650 + global $mainwp_leftmenu, $mainwp_sub_leftmenu;
1651 +
1652 + $left_menu = apply_filters( 'mainwp_main_menu', is_array( $mainwp_leftmenu ) ? $mainwp_leftmenu : array() );
1653 + $sub_left_menu = apply_filters( 'mainwp_main_menu_submenu', is_array( $mainwp_sub_leftmenu ) ? $mainwp_sub_leftmenu : array() );
1654 + $menu_nodes = array();
1655 +
1656 + if ( ! empty( $left_menu['leftbar'] ) && is_array( $left_menu['leftbar'] ) ) {
1657 + foreach ( $left_menu['leftbar'] as $item ) {
1658 + $this->add_command_palette_menu_node(
1659 + $menu_nodes,
1660 + isset( $item[0] ) ? $item[0] : '',
1661 + isset( $item[2] ) ? $item[2] : '',
1662 + isset( $item[1] ) ? $item[1] : '',
1663 + 'mainwp_tab'
1664 + );
1665 + }
1666 + }
1667 +
1668 + foreach ( $left_menu as $parent_key => $items ) {
1669 + if ( 'leftbar' === $parent_key || ! is_array( $items ) ) {
1670 + continue;
1671 + }
1672 +
1673 + foreach ( $items as $item ) {
1674 + $this->add_command_palette_menu_node(
1675 + $menu_nodes,
1676 + isset( $item[0] ) ? $item[0] : '',
1677 + isset( $item[2] ) ? $item[2] : '',
1678 + isset( $item[1] ) ? $item[1] : '',
1679 + $parent_key
1680 + );
1681 + }
1682 + }
1683 +
1684 + if ( ! empty( $sub_left_menu['leftbar'] ) && is_array( $sub_left_menu['leftbar'] ) ) {
1685 + foreach ( $sub_left_menu['leftbar'] as $parent_key => $items ) {
1686 + if ( ! is_array( $items ) ) {
1687 + continue;
1688 + }
1689 +
1690 + foreach ( $items as $item ) {
1691 + $this->add_command_palette_menu_node(
1692 + $menu_nodes,
1693 + isset( $item[0] ) ? $item[0] : '',
1694 + isset( $item[2] ) ? $item[2] : '',
1695 + isset( $item[1] ) ? $item[1] : '',
1696 + $parent_key
1697 + );
1698 + }
1699 + }
1700 + }
1701 +
1702 + foreach ( $sub_left_menu as $parent_key => $items ) {
1703 + if ( 'leftbar' === $parent_key || ! is_array( $items ) ) {
1704 + continue;
1705 + }
1706 +
1707 + foreach ( $items as $item ) {
1708 + $this->add_command_palette_menu_node(
1709 + $menu_nodes,
1710 + isset( $item[0] ) ? $item[0] : '',
1711 + isset( $item[1] ) ? $item[1] : '',
1712 + isset( $item[4] ) ? $item[4] : '',
1713 + $parent_key
1714 + );
1715 + }
1716 + }
1717 +
1718 + return $menu_nodes;
1719 + }
1720 +
1721 + /**
1722 + * Add a single node to the command palette source list.
1723 + *
1724 + * @param array<int, array<string, string>> $menu_nodes Menu nodes.
1725 + * @param string $title Menu title.
1726 + * @param string $href Menu href.
1727 + * @param string $slug Menu slug.
1728 + * @param string $parent_key Parent menu slug.
1729 + */
1730 + private function add_command_palette_menu_node( &$menu_nodes, $title, $href, $slug, $parent_key ) {
1731 + $title = $this->clean_command_palette_label( $title );
1732 + $url = $this->normalize_command_palette_url( $href, $slug );
1733 +
1734 + if ( empty( $title ) || empty( $url ) ) {
1735 + return;
1736 + }
1737 +
1738 + $page_slug = $this->get_command_palette_page_slug_from_url( $url );
1739 + $slug_key = ! empty( $slug ) ? (string) $slug : $page_slug;
1740 +
1741 + $menu_nodes[] = array(
1742 + 'id' => md5( $parent_key . '|' . $slug_key . '|' . $url . '|' . $title ),
1743 + 'parent' => (string) $parent_key,
1744 + 'page_slug' => $page_slug,
1745 + 'slug_key' => $slug_key,
1746 + 'title' => $title,
1747 + 'url' => $url,
1748 + );
1749 + }
1750 +
1751 + /**
1752 + * Build a node breadcrumb from the MainWP menu tree.
1753 + *
1754 + * @param array<string, string> $node Menu node.
1755 + * @param array<string, array<string, string>> $nodes_by_id Menu nodes indexed by id.
1756 + * @param array<string, string> $nodes_by_slug Menu nodes indexed by slug.
1757 + *
1758 + * @return array<int, string> Path titles.
1759 + */
1760 + private function get_command_palette_menu_path_titles( $node, $nodes_by_id, $nodes_by_slug ) {
1761 + $titles = array();
1762 + $current = $node;
1763 + $seen_nodes = array();
1764 +
1765 + while ( is_array( $current ) && ! empty( $current['id'] ) && ! isset( $seen_nodes[ $current['id'] ] ) ) {
1766 + $seen_nodes[ $current['id'] ] = true;
1767 +
1768 + if ( ! empty( $current['title'] ) ) {
1769 + array_unshift( $titles, $current['title'] );
1770 + }
1771 +
1772 + if ( empty( $current['parent'] ) || 'mainwp_tab' === $current['parent'] || ! isset( $nodes_by_slug[ $current['parent'] ] ) ) {
1773 + break;
1774 + }
1775 +
1776 + $parent_id = $nodes_by_slug[ $current['parent'] ];
1777 + $current = isset( $nodes_by_id[ $parent_id ] ) ? $nodes_by_id[ $parent_id ] : array();
1778 + }
1779 +
1780 + return $titles;
1781 + }
1782 +
1783 + /**
1784 + * Build search keywords for a MainWP command.
1785 + *
1786 + * @param array<int, string> $path_titles Path titles.
1787 + * @param string $page_slug Page slug.
1788 + *
1789 + * @return array<int, string> Search keywords.
1790 + */
1791 + private function get_command_palette_keywords( $path_titles, $page_slug = '' ) {
1792 + $keywords = array( 'mainwp' );
1793 +
1794 + foreach ( $path_titles as $title ) {
1795 + $keywords[] = strtolower( $title );
1796 + }
1797 +
1798 + if ( ! empty( $page_slug ) ) {
1799 + $keywords[] = strtolower( str_replace( array( '-', '_' ), ' ', $page_slug ) );
1800 + }
1801 +
1802 + return array_values( array_unique( array_filter( $keywords ) ) );
1803 + }
1804 +
1805 + /**
1806 + * Normalize menu text into readable plain text.
1807 + *
1808 + * @param string $label Raw menu label.
1809 + *
1810 + * @return string Plain-text label.
1811 + */
1812 + private function clean_command_palette_label( $label ) {
1813 + $label = wp_specialchars_decode( wp_strip_all_tags( (string) $label ), ENT_QUOTES );
1814 + $label = preg_replace( '/\s+/', ' ', $label );
1815 +
1816 + return trim( (string) $label );
1817 + }
1818 +
1819 + /**
1820 + * Check whether a command-palette URL is already absolute.
1821 + *
1822 + * @param string $url URL to check.
1823 + *
1824 + * @return bool True when the URL is an absolute HTTP(S) URL.
1825 + */
1826 + private function is_command_palette_absolute_url( $url ) {
1827 + return 1 === preg_match( '/^https?:\/\//i', trim( (string) $url ) );
1828 + }
1829 +
1830 + /**
1831 + * Normalize a MainWP menu href into a usable URL.
1832 + *
1833 + * @param string $href Menu href.
1834 + * @param string $slug Menu slug.
1835 + *
1836 + * @return string Normalized URL.
1837 + */
1838 + private function normalize_command_palette_url( $href, $slug = '' ) {
1839 + $href = trim( wp_specialchars_decode( (string) $href, ENT_QUOTES ) );
1840 + $slug = trim( (string) $slug );
1841 +
1842 + if ( empty( $href ) || '#' === $href || 0 === strpos( $href, 'javascript:' ) ) {
1843 + if ( empty( $slug ) || preg_match( '/\.php($|\?)/', $slug ) || $this->is_command_palette_absolute_url( $slug ) ) {
1844 + return '';
1845 + }
1846 +
1847 + return admin_url( 'admin.php?page=' . $slug );
1848 + }
1849 +
1850 + if ( $this->is_command_palette_absolute_url( $href ) ) {
1851 + return $href;
1852 + }
1853 +
1854 + return admin_url( ltrim( $href, '/' ) );
1855 + }
1856 +
1857 + /**
1858 + * Extract a page slug from a menu URL when one exists.
1859 + *
1860 + * @param string $url Menu URL.
1861 + *
1862 + * @return string Page slug.
1863 + */
1864 + private function get_command_palette_page_slug_from_url( $url ) {
1865 + $query = wp_parse_url( $url, PHP_URL_QUERY );
1866 +
1867 + if ( empty( $query ) ) {
1868 + return '';
1869 + }
1870 +
1871 + parse_str( $query, $query_args );
1872 +
1873 + if ( ! isset( $query_args['page'] ) ) {
1874 + return '';
1875 + }
1876 +
1877 + return preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $query_args['page'] );
1229 1878 }
1230 1879
1231 1880 /**
1232 1881 * Method admin_enqueue_scripts_fix_conflict()