PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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 +668 -13 6.0.12 → 6.2.2 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.0.12'; // NOSONAR.
38 + public static $version = '6.2.2'; // 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 *
@@ -138,8 +138,9 @@
138 138 $includer = new MainWP_Includes();
139 139 $includer->includes();
140 140
141 141 MainWP_Keys_Manager::auto_load_files();
142 + MainWP_Keys_Manager::register_migration_hooks();
142 143
143 144 $this->load_all_options();
144 145
145 146 $this->update_install();
@@ -166,8 +167,13 @@
166 167 if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '5.0.2', '<' ) && version_compare( $this->current_version, '5.0.2', '>=' ) ) {
167 168 add_action( 'mainwp_after_header', array( MainWP_System_View::get_class_name(), 'mainwp_ver502_update_notice' ) );
168 169 }
169 170
171 + // Use 6.0.12 as the lower bound so 6.1-er.x builds trigger this notice.
172 + if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '6.1', '<' ) && version_compare( $currentVersion, '6.0.12', '>' ) ) {
173 + add_action( 'mainwp_after_header', array( MainWP_System_View::get_class_name(), 'mainwp_ver61_update_notice' ) );
174 + }
175 +
170 176 MainWP_Utility::update_option( 'mainwp_plugin_version', $this->current_version );
171 177 }
172 178
173 179 if ( ! defined( 'MAINWP_VERSION' ) ) {
@@ -233,8 +239,9 @@
233 239 MainWP_Bulk_Post::get_instance();
234 240
235 241 add_action( 'admin_init', array( &$this, 'admin_init' ), 20 );
236 242 add_action( 'admin_init', array( $this, 'hook_admin_update_check' ) );
243 + add_action( 'admin_init', array( $this, 'admin_init_update' ) );
237 244 add_action( 'after_setup_theme', array( &$this, 'after_setup_theme' ) );
238 245
239 246 add_action( 'init', array( &$this, 'parse_init' ) );
240 247 add_action( 'init', array( &$this, 'init_jobs' ) );
@@ -295,13 +302,16 @@
295 302
296 303 if ( defined( 'WP_CLI' ) && WP_CLI ) {
297 304 MainWP_WP_CLI_Command::init();
298 305 }
299 - if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['mainwp_run'] ) && 'test' === $_GET['mainwp_run'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
300 - add_action( 'init', array( MainWP_System_Cron_Jobs::instance(), 'cron_active' ), PHP_INT_MAX );
301 - }
302 306 MainWP_Unhooks_Helper::instance();
303 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 + );
304 314 }
305 315
306 316 /**
307 317 * Method get_dashboard_version()
@@ -392,8 +402,9 @@
392 402 'mainwp_show_language_updates',
393 403 'mainwp_logger_check_daily',
394 404 'mainwp_site_actions_notification_enable',
395 405 'mainwp_update_check_version',
406 + 'mainwp_admin_init_update_data',
396 407 'mainwp_setting_demo_mode_enabled',
397 408 'mainwp_log_wait_lasttime',
398 409 'mainwp_cron_license_deactivated_alert_lasttime',
399 410 'mainwp_updatescheck_is_running',
@@ -606,9 +617,9 @@
606 617 */
607 618 public static function is_mainwp_site_page() {
608 619 $is_page = false;
609 620 //phpcs:disable WordPress.Security.NonceVerification.Recommended
610 - 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'] ) ) ) {
611 622 $is_page = true;
612 623 }
613 624 //phpcs:enable
614 625
@@ -782,19 +793,15 @@
782 793 * Method hook_admin_update_check()
783 794 */
784 795 public function hook_admin_update_check() {
785 796 $current_ver = $this->check_ver_update;
786 - $saved_ver = get_option( 'mainwp_update_check_version', false );
797 + $saved_ver = get_option( 'mainwp_update_check_version', '' );
787 798
788 - if ( false === $saved_ver ) {
789 - return;
790 - }
791 -
792 799 if ( version_compare( $saved_ver, $current_ver, '=' ) ) {
793 800 return;
794 801 }
795 802
796 - if ( version_compare( $saved_ver, '0.0.4', '<' ) ) {
803 + if ( ! empty( $saved_ver ) && version_compare( $saved_ver, '0.0.4', '<' ) ) {
797 804 $sched = wp_next_scheduled( 'mainwp_cronstats_action' );
798 805 if ( ! empty( $sched ) ) {
799 806 wp_unschedule_event( $sched, 'mainwp_cronstats_action' );
800 807 }
@@ -801,9 +808,9 @@
801 808 global $wpdb;
802 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.
803 810 }
804 811
805 - if ( version_compare( $saved_ver, '0.0.5', '<' ) ) {
812 + if ( ! empty( $saved_ver ) && version_compare( $saved_ver, '0.0.5', '<' ) ) {
806 813 $all_ext = MainWP_Extensions_View::get_available_extensions();
807 814 foreach ( $all_ext as $slug => $info ) {
808 815 $data = MainWP_Api_Manager::instance()->get_activation_info( $slug );
809 816 if ( is_array( $data ) && isset( $data['api_key'] ) ) {
@@ -814,12 +821,73 @@
814 821 }
815 822 update_option( 'mainwp_extensions_all_activation_cached', '' );
816 823 }
817 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 +
818 829 MainWP_Utility::update_option( 'mainwp_update_check_version', $current_ver );
819 830 }
820 831
832 +
821 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 + /**
822 890 * Method admin_init()
823 891 *
824 892 * Do nothing if current user is not an Admin else display the page.
825 893 *
@@ -963,8 +1031,9 @@
963 1031 remove_action( 'admin_notices', 'update_nag', 3 );
964 1032 }
965 1033 }
966 1034
1035 +
967 1036 /**
968 1037 * Method hook_wp_shutdown()
969 1038 */
970 1039 public function hook_wp_shutdown() {
@@ -1219,8 +1288,594 @@
1219 1288 wp_script_add_data( $h, 'strategy', 'defer' ); // adds defer to <script> tag.
1220 1289 }
1221 1290 }
1222 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'] );
1223 1878 }
1224 1879
1225 1880 /**
1226 1881 * Method admin_enqueue_scripts_fix_conflict()