PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / trunk
MainWP Dashboard: Self-hosted WordPress Management for Agencies vtrunk
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 5.2.2 5.3 All 153 releases
← All changes | class/class-mainwp-system.php +871 -73 5.2trunk View file →
@@ -6,9 +6,17 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +use MainWP\Dashboard\Module\Log\Log_Manage_Insights_Events_Page;
11 +
12 +// Exit if accessed directly.
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
16 +
10 17 // phpcs:disable Generic.Metrics.CyclomaticComplexity -- complexity.
18 +// phpcs:disable plugin_updater_detected -- not a self-updater; injects update data exclusively for licensed MainWP premium extensions hosted on mainwp.com.
11 19
12 20 const MAINWP_VIEW_PER_SITE = 1;
13 21 const MAINWP_VIEW_PER_PLUGIN_THEME = 0;
14 22 const MAINWP_VIEW_PER_GROUP = 2;
@@ -26,9 +34,9 @@
26 34 * Public static variable to hold the current plugin version.
27 35 *
28 36 * @var string Current plugin version.
29 37 */
30 - public static $version = '5.2'; // NOSONAR.
38 + public static $version = '6.2'; // NOSONAR.
31 39
32 40 /**
33 41 * Private static variable to hold the single instance of the class.
34 42 *
@@ -64,9 +72,18 @@
64 72 * @var string Plugin slug.
65 73 */
66 74 private $plugin_slug;
67 75
76 +
68 77 /**
78 + * Private variable to hold the defer js handle.
79 + *
80 + * @var array Defer js handle.
81 + */
82 + private static $defer_js_handle = array();
83 +
84 +
85 + /**
69 86 * Method instance()
70 87 *
71 88 * Create a public static instance.
72 89 *
@@ -121,8 +138,9 @@
121 138 $includer = new MainWP_Includes();
122 139 $includer->includes();
123 140
124 141 MainWP_Keys_Manager::auto_load_files();
142 + MainWP_Keys_Manager::register_migration_hooks();
125 143
126 144 $this->load_all_options();
127 145
128 146 $this->update_install();
@@ -127,8 +145,10 @@
127 145
128 146 $this->update_install();
129 147 $this->plugin_slug = plugin_basename( $mainwp_plugin_file );
130 148
149 + add_action( 'shutdown', array( $this, 'hook_wp_shutdown' ) );
150 +
131 151 do_action( 'mainwp_system_init' );
132 152
133 153 if ( is_admin() ) {
134 154 include_once ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'plugin.php'; // NOSONAR - WP compatible.
@@ -147,8 +167,13 @@
147 167 if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '5.0.2', '<' ) && version_compare( $this->current_version, '5.0.2', '>=' ) ) {
148 168 add_action( 'mainwp_after_header', array( MainWP_System_View::get_class_name(), 'mainwp_ver502_update_notice' ) );
149 169 }
150 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 +
151 176 MainWP_Utility::update_option( 'mainwp_plugin_version', $this->current_version );
152 177 }
153 178
154 179 if ( ! defined( 'MAINWP_VERSION' ) ) {
@@ -183,13 +208,14 @@
183 208 MainWP_Extensions_Groups::init();
184 209
185 210 $systemHandler = MainWP_System_Handler::instance();
186 211
187 - add_action( 'plugins_loaded', array( &$this, 'localization' ) );
188 - add_filter( 'site_transient_update_plugins', array( $systemHandler, 'check_update_custom' ) );
189 - add_filter( 'pre_set_site_transient_update_plugins', array( $systemHandler, 'pre_check_update_custom' ) );
212 + add_action( 'init', array( &$this, 'localization' ) );
213 + add_filter( 'site_transient_update_plugins', array( $systemHandler, 'check_update_custom' ) ); // phpcs:ignore PluginCheck.CodeAnalysis.Sniffs.UpdatingPluginTransientFound -- not a self-updater; injects update data exclusively for licensed MainWP premium extensions (hosted on mainwp.com), not for the Dashboard plugin itself.
214 + add_filter( 'pre_set_site_transient_update_plugins', array( $systemHandler, 'pre_check_update_custom' ) ); // phpcs:ignore PluginCheck.CodeAnalysis.Sniffs.UpdatingPluginTransientFound -- same as above; pre-populates update info for MainWP premium extensions before WordPress saves the transient.
190 215 add_filter( 'plugins_api', array( $systemHandler, 'plugins_api_extension_info' ), 10, 3 );
191 216 add_filter( 'plugins_api_result', array( $systemHandler, 'plugins_api_wp_plugins_api_result' ), 10, 3 );
217 + add_filter( 'upgrader_pre_download', array( $systemHandler, 'hook_refresh_trusted_extension_package_url' ), 10, 4 );
192 218
193 219 $this->metaboxes = new MainWP_Meta_Boxes();
194 220
195 221 MainWP_Overview::get();
@@ -195,12 +221,14 @@
195 221 MainWP_Overview::get();
196 222 MainWP_Client_Overview::instance();
197 223
198 224 MainWP_Manage_Sites::init();
225 + MainWP_Uptime_Monitoring_Handle::instance();
199 226
200 227 MainWP_Hooks::get_instance();
201 228 MainWP_Menu::get_instance();
202 229
230 + add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 9 );
203 231 add_action( 'admin_menu', array( MainWP_Menu::get_class_name(), 'init_mainwp_menus' ) );
204 232 add_filter( 'admin_footer', array( &$this, 'admin_footer' ), 15 );
205 233 add_action( 'admin_head', array( MainWP_System_View::get_class_name(), 'admin_head' ) );
206 234 add_action( 'admin_enqueue_scripts', array( &$this, 'admin_enqueue_styles' ) );
@@ -214,9 +242,11 @@
214 242 add_action( 'admin_init', array( $this, 'hook_admin_update_check' ) );
215 243 add_action( 'after_setup_theme', array( &$this, 'after_setup_theme' ) );
216 244
217 245 add_action( 'init', array( &$this, 'parse_init' ) );
246 + add_action( 'init', array( &$this, 'init_jobs' ) );
218 247 add_action( 'init', array( &$this, 'init' ), 9999 );
248 +
219 249 add_action( 'admin_init', array( $this, 'admin_redirects' ) );
220 250 add_action( 'current_screen', array( &$this, 'current_screen_redirects' ), 15 );
221 251 add_filter( 'plugin_action_links', array( $this, 'hook_plugin_action_links' ), 10, 4 );
222 252 add_action( 'admin_menu', array( &$this, 'admin_menu' ) );
@@ -225,10 +255,8 @@
225 255 add_action( 'wp_logout', array( &$this, 'clear_sessions' ) );
226 256
227 257 MainWP_Install_Bulk::init();
228 258
229 - MainWP_System_Cron_Jobs::instance()->init_cron_jobs();
230 -
231 259 add_action( 'mainwp_after_header', array( MainWP_System_View::get_class_name(), 'admin_notices' ) );
232 260 add_action( 'admin_notices', array( MainWP_System_View::get_class_name(), 'wp_admin_notices' ) );
233 261 add_action( 'wp_mail_failed', array( &$this, 'wp_mail_failed' ) );
234 262
@@ -255,8 +283,9 @@
255 283 MainWP_Manage_Backups::init();
256 284 }
257 285 MainWP_Manage_Groups::init();
258 286 MainWP_User::init();
287 + MainWP_Password_Policy_Settings::init();
259 288 MainWP_Page::init();
260 289 MainWP_Themes::init();
261 290 MainWP_Plugins::init();
262 291 MainWP_Updates_Overview::init();
@@ -261,16 +290,27 @@
261 290 MainWP_Plugins::init();
262 291 MainWP_Updates_Overview::init();
263 292 MainWP_Client::init();
264 293 MainWP_Rest_Api_Page::init();
265 - MainWP_Non_MainWP_Actions::instance();
294 + MainWP_Logger::instance();
266 295
296 + if ( class_exists( '\MainWP\Dashboard\Module\Log\Log_Manage_Insights_Events_Page' ) ) {
297 + Log_Manage_Insights_Events_Page::instance();
298 + }
299 +
300 + MainWP_Uptime_Monitoring_Schedule::instance();
301 +
267 302 if ( defined( 'WP_CLI' ) && WP_CLI ) {
268 303 MainWP_WP_CLI_Command::init();
269 304 }
270 - if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['mainwp_run'] ) && 'test' === $_GET['mainwp_run'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
271 - add_action( 'init', array( MainWP_System_Cron_Jobs::instance(), 'cron_active' ), PHP_INT_MAX );
272 - }
305 + MainWP_Unhooks_Helper::instance();
306 + MainWP_Cache_Warm_Helper::instance();
307 + MainWP_System_Utility::instance();
308 +
309 + add_action(
310 + 'wp_ajax_nopriv_mainwp_self_connect',
311 + array( MainWP_Post_Handler::instance(), 'ajax_self_connect' )
312 + );
273 313 }
274 314
275 315 /**
276 316 * Method get_dashboard_version()
@@ -283,8 +323,19 @@
283 323 return static::$version;
284 324 }
285 325
286 326 /**
327 + * Method get_mainwp_version()
328 + *
329 + * Get dashboard version.
330 + *
331 + * @return string $version Version.
332 + */
333 + public static function get_mainwp_version() { //phpcs:ignore -- NOSONAR same function body.
334 + return static::$version;
335 + }
336 +
337 + /**
287 338 * Method load_all_options()
288 339 *
289 340 * Load all wp_options data.
290 341 *
@@ -327,9 +378,8 @@
327 378 'mainwp_enableLegacyBackupFeature',
328 379 'mainwp_maximumInstallUpdateRequests',
329 380 'mainwp_maximumSyncRequests',
330 381 'mainwp_primaryBackup',
331 - 'mainwp_security',
332 382 'mainwp_use_favicon',
333 383 'mainwp_wp_cron',
334 384 'mainwp_timeDailyUpdate',
335 385 'mainwp_frequencyDailyUpdate',
@@ -340,14 +390,11 @@
340 390 'mainwp_disable_update_confirmations',
341 391 'mainwp_settings_show_widgets',
342 392 'mainwp_clients_show_widgets',
343 393 'mainwp_settings_show_manage_sites_columns',
344 - 'mainwp_disableSitesChecking',
394 + 'mainwp_individual_uptime_monitoring_schedule_enabled',
345 395 'mainwp_disableSitesHealthMonitoring',
346 - 'mainwp_frequencySitesChecking',
347 396 'mainwp_sitehealthThreshold',
348 - 'mainwp_settings_notification_emails',
349 - 'mainwp_ignore_HTTP_response_status',
350 397 'mainwp_check_http_response',
351 398 'mainwp_extmenu',
352 399 'mainwp_opensslLibLocation',
353 400 'mainwp_notice_wp_mail_failed',
@@ -356,9 +403,8 @@
356 403 'mainwp_site_actions_notification_enable',
357 404 'mainwp_update_check_version',
358 405 'mainwp_setting_demo_mode_enabled',
359 406 'mainwp_log_wait_lasttime',
360 - 'mainwp_updatescheck_start_last_schedule_timestamp',
361 407 'mainwp_cron_license_deactivated_alert_lasttime',
362 408 'mainwp_updatescheck_is_running',
363 409 'mainwp_automatic_updates_is_running',
364 410 'mainwp_frequency_AutoUpdate',
@@ -363,9 +409,13 @@
363 409 'mainwp_automatic_updates_is_running',
364 410 'mainwp_frequency_AutoUpdate',
365 411 'mainwp_batch_updates_is_running',
366 412 'mainwp_batch_individual_updates_is_running',
367 -
413 + 'mainwp_maximum_uptime_monitoring_requests',
414 + 'mainwp_actionlogs',
415 + 'mainwp_process_uptime_notification_run_status',
416 + 'mainwp_warm_cache_pages_ttl',
417 + 'mainwp_module_log_settings_logs_selection_data',
368 418 );
369 419
370 420 $options = apply_filters( 'mainwp_init_load_all_options', $options );
371 421
@@ -408,12 +458,13 @@
408 458 */
409 459 public function localization() {
410 460 $load = apply_filters( 'mainwp_load_text_domain', true );
411 461 if ( $load ) {
412 - load_plugin_textdomain( 'mainwp', false, dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' );
462 + load_plugin_textdomain( 'mainwp', false, dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' ); // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- kept for older WP versions (<4.6) and to support local/custom translation files loaded from the plugin's languages directory.
413 463 }
414 464 }
415 465
466 +
416 467 /**
417 468 * Method wp_mail_failed()
418 469 *
419 470 * Check if there has been a wp mail failer.
@@ -500,25 +551,33 @@
500 551 MainWP_System_Cron_Jobs::instance()->cron_updates_check();
501 552 }
502 553
503 554 /**
504 - * Method mainwp_crondeactivatedlicensesalert_action()
555 + * Method mainwp_cron_uptime_monitoring_check_action()
505 556 *
506 - * Run cron activated licenses alert action.
557 + * Run cron uptime monitoring check action.
507 558 */
508 - public function mainwp_crondeactivatedlicensesalert_action() {
509 - MainWP_System_Cron_Jobs::instance()->cron_deactivated_licenses_alert();
559 + public function mainwp_cron_uptime_monitoring_check_action() {
560 + MainWP_Uptime_Monitoring_Schedule::instance()->cron_uptime_check();
510 561 }
511 562
563 + /**
564 + * Method mainwp_cron_perform_general_schedules_action()
565 + *
566 + * Run cron uptime monitoring check action.
567 + */
568 + public function mainwp_cron_perform_general_schedules_action() {
569 + MainWP_System_Cron_Jobs::instance()->cron_perform_general_process();
570 + }
571 +
572 +
512 573 /**
513 - * Method mainwp_croncheckstatus_action()
574 + * Method mainwp_crondeactivatedlicensesalert_action()
514 575 *
515 - * Run cron check sites status action.
516 - *
517 - * @uses \MainWP\Dashboard\MainWP_System_Cron_Jobs::cron_check_websites_status()
576 + * Run cron activated licenses alert action.
518 577 */
519 - public function mainwp_croncheckstatus_action() {
520 - MainWP_System_Cron_Jobs::instance()->cron_check_websites_status();
578 + public function mainwp_crondeactivatedlicensesalert_action() {
579 + MainWP_System_Cron_Jobs::instance()->cron_deactivated_licenses_alert();
521 580 }
522 581
523 582 /**
524 583 * Method mainwp_cronchecksitehealth_action()
@@ -554,45 +613,43 @@
554 613 *
555 614 * @return boolean ture|false.
556 615 */
557 616 public static function is_mainwp_site_page() {
617 + $is_page = false;
558 618 //phpcs:disable WordPress.Security.NonceVerification.Recommended
559 - 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['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'] ) ) ) {
560 - return true;
619 + 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'] ) ) ) {
620 + $is_page = true;
561 621 }
562 622 //phpcs:enable
563 - return false;
564 - }
565 623
566 - /**
567 - * Method init()
568 - *
569 - * Instantiate Plugin.
570 - */
571 - public function init() { //phpcs:ignore -- NOSONAR - complex.
572 -
573 624 /**
574 - * MainWP disabled menu items array.
625 + * Hook mainwp_is_mainwp_site_page.
575 626 *
576 - * @global object $_mainwp_disable_menus_items
627 + * @since 5.4.1
577 628 */
578 - global $_mainwp_disable_menus_items;
629 + return apply_filters( 'mainwp_is_mainwp_site_page', $is_page );
630 + }
579 631
580 - $_mainwp_disable_menus_items = apply_filters( 'mainwp_disablemenuitems', $_mainwp_disable_menus_items );
581 632
633 + /**
634 + * Method plugins_loaded()
635 + *
636 + * Load plugin text domain.
637 + */
638 + public function plugins_loaded() { // phpcs:ignore -- NOSONAR - complex.
582 639 /**
583 - * Filter: mainwp_main_menu_disable_menu_items
640 + * Action: mainwp_system_plugins_loaded
584 641 *
585 - * Filters disabled MainWP navigation items.
642 + * Fires after all plugins are loaded.
586 643 *
587 - * @since Unknown
644 + * @since 6.1.0
588 645 */
589 - $_mainwp_disable_menus_items = apply_filters( 'mainwp_main_menu_disable_menu_items', $_mainwp_disable_menus_items );
646 + do_action( 'mainwp_system_plugins_loaded' );
590 647
591 648 if ( ! function_exists( 'MainWP\Dashboard\mainwp_current_user_have_right' ) ) {
592 649
593 650 /**
594 - * Method mainwp_current_user_have_right()
651 + * Method \mainwp_current_user_can()
595 652 *
596 653 * Check permission level by hook mainwp_currentusercan of Team Control extension.
597 654 *
598 655 * @param string $cap_type group or type of capabilities.
@@ -629,9 +686,35 @@
629 686
630 687 return apply_filters( 'mainwp_currentusercan', true, $cap_type, $cap );
631 688 }
632 689 }
690 + }
633 691
692 + /**
693 + * Method init()
694 + *
695 + * Instantiate Plugin.
696 + */
697 + public function init() { //phpcs:ignore -- NOSONAR - complex.
698 +
699 + /**
700 + * MainWP disabled menu items array.
701 + *
702 + * @global object $_mainwp_disable_menus_items
703 + */
704 + global $_mainwp_disable_menus_items;
705 +
706 + $_mainwp_disable_menus_items = apply_filters( 'mainwp_disablemenuitems', $_mainwp_disable_menus_items );
707 +
708 + /**
709 + * Filter: mainwp_main_menu_disable_menu_items
710 + *
711 + * Filters disabled MainWP navigation items.
712 + *
713 + * @since Unknown
714 + */
715 + $_mainwp_disable_menus_items = apply_filters( 'mainwp_main_menu_disable_menu_items', $_mainwp_disable_menus_items );
716 +
634 717 MainWP_System_Handler::instance()->handle_settings_post();
635 718 }
636 719
637 720 /**
@@ -684,8 +767,18 @@
684 767 // phpcs:enable
685 768 }
686 769
687 770 /**
771 + * Method init_jobs()
772 + *
773 + * Initiate mainwp schedule jobs.
774 + */
775 + public function init_jobs() {
776 + MainWP_System_Cron_Jobs::instance()->init_cron_jobs();
777 + }
778 +
779 +
780 + /**
688 781 * Method after_setup_theme()
689 782 *
690 783 * After setup theme hook, to support post thumbnails.
691 784 */
@@ -786,8 +879,9 @@
786 879 MainWP_Post_Plugin_Theme_Handler::instance()->init();
787 880 MainWP_Post_Extension_Handler::instance()->init();
788 881 MainWP_Post_Backup_Handler::instance()->init();
789 882 MainWP_Manage_Sites_Filter_Segment::get_instance()->admin_init();
883 + MainWP_Ui_Manage_Widgets_Layout::get_instance()->admin_init();
790 884
791 885 /**
792 886 * Filter: mainwp_ui_use_wp_calendar
793 887 *
@@ -803,9 +897,14 @@
803 897 $en_params = array( 'jquery-ui-dialog' );
804 898 if ( $use_wp_datepicker ) {
805 899 $en_params[] = 'jquery-ui-datepicker';
806 900 }
901 +
902 + static::$defer_js_handle = array_merge( static::$defer_js_handle, array( 'mainwp' ) );
903 +
807 904 wp_enqueue_script( 'mainwp', MAINWP_PLUGIN_URL . 'assets/js/mainwp.js', $en_params, $this->current_version, true );
905 + wp_enqueue_script( 'mainwp-cache-warm', MAINWP_PLUGIN_URL . 'assets/js/mainwp-cache-warm.js', array(), $this->current_version, true );
906 +
808 907 $disable_backup_checking = true; // removed option.
809 908 $mainwpParams = array(
810 909 'image_url' => MAINWP_PLUGIN_URL . 'assets/images/',
811 910 'backup_before_upgrade' => 1 === (int) get_option( 'mainwp_backup_before_upgrade' ),
@@ -816,11 +915,15 @@
816 915 'time_format' => get_option( 'time_format' ),
817 916 'installedBulkSettingsManager' => is_plugin_active( 'mainwp-bulk-settings-manager/mainwp-bulk-settings-manager.php' ) ? 1 : 0,
818 917 'maximumSyncRequests' => ( get_option( 'mainwp_maximumSyncRequests' ) === false ) ? 8 : get_option( 'mainwp_maximumSyncRequests' ),
819 918 'maximumInstallUpdateRequests' => ( get_option( 'mainwp_maximumInstallUpdateRequests' ) === false ) ? 3 : get_option( 'mainwp_maximumInstallUpdateRequests' ),
919 + 'maximumUptimeMonitoringRequests' => (int) get_option( 'mainwp_maximum_uptime_monitoring_requests', 10 ),
820 920 '_wpnonce' => wp_create_nonce( 'mainwp-admin-nonce' ),
921 + 'quickThemeChangeNonce' => wp_create_nonce( 'mainwp_quick_theme_change' ),
821 922 'demoMode' => MainWP_Demo_Handle::is_demo_mode() ? 1 : 0,
822 923 'roll_ui_icon' => MainWP_Updates_Helper::get_roll_icon( '', true ),
924 + 'admin_url_base' => admin_url(),
925 + 'mainwpVersion' => static::$version,
823 926 );
824 927 wp_localize_script( 'mainwp', 'mainwpParams', $mainwpParams );
825 928
826 929 $mainwpTranslations = MainWP_System_View::get_mainwp_translations();
@@ -837,20 +940,22 @@
837 940 wp_enqueue_script( 'thickbox' );
838 941 wp_enqueue_script( 'user-profile' );
839 942 wp_enqueue_style( 'thickbox' );
840 943
841 - $load_gridster = false;
944 + $load_gridstack = false;
842 945
843 946 // phpcs:disable WordPress.Security.NonceVerification
844 947 if ( ( isset( $_GET['page'] ) && ( 'mainwp_tab' === $_GET['page'] || ( 'managesites' === $_GET['page'] && isset( $_GET['dashboard'] ) ) ) ) || ( isset( $_GET['page'] ) && 'ManageClients' === $_GET['page'] && isset( $_GET['client_id'] ) ) || ( isset( $_GET['page'] ) && 0 === strpos( wp_unslash( $_GET['page'] ), 'ManageSites' ) ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- individual page.
845 - $load_gridster = true;
948 + $load_gridstack = true;
846 949 }
847 950
848 - $load_gridster = apply_filters( 'mainwp_enqueue_script_gridster', $load_gridster );
951 + // compatible filter.
952 + $load_gridstack = apply_filters( 'mainwp_enqueue_script_gridster', $load_gridstack );
849 953
850 - if ( $load_gridster ) {
851 - wp_enqueue_script( 'gridster', MAINWP_PLUGIN_URL . 'assets/js/gridster/jquery.dsmorse-gridster.js', array(), $this->current_version, true );
852 - wp_enqueue_style( 'gridster', MAINWP_PLUGIN_URL . 'assets/js/gridster/jquery.gridster.min.css', array(), $this->current_version );
954 + if ( $load_gridstack ) {
955 + static::$defer_js_handle[] = 'mainwp_gridstack';
956 + wp_enqueue_script( 'mainwp_gridstack', MAINWP_PLUGIN_URL . 'assets/js/gridstack/gridstack-all.js', array( 'jquery' ), $this->current_version, true );
957 + wp_enqueue_style( 'mainwp_gridstack', MAINWP_PLUGIN_URL . 'assets/js/gridstack/gridstack.min.css', array(), $this->current_version );
853 958 }
854 959
855 960 if ( isset( $_GET['page'] ) && ( 'managesites' === $_GET['page'] || 'MonitoringSites' === $_GET['page'] || 'ManageGroups' === $_GET['page'] ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
856 961 wp_enqueue_script( 'preview', MAINWP_PLUGIN_URL . 'assets/js/preview.js', array(), $this->current_version, true );
@@ -868,8 +973,32 @@
868 973 }
869 974 }
870 975
871 976 /**
977 + * Method hook_wp_shutdown()
978 + */
979 + public function hook_wp_shutdown() {
980 + if ( MainWP_Logger::DISABLED !== (int) get_option( 'mainwp_actionlogs' ) ) {
981 + $error = error_get_last();
982 + if ( is_array( $error ) && isset( $error['type'] ) && ( E_ERROR === $error['type'] || E_CORE_ERROR === $error['type'] || E_COMPILE_ERROR === $error['type'] || E_PARSE === $error['type'] ) ) {
983 + MainWP_Logger::instance()->log_action( '[Fatal ERROR detected=' . print_r( $error, true ) . ']', false, MainWP_Logger::WARNING_COLOR, true ); //phpcs:ignore -- NOSONAR.
984 + }
985 + }
986 + MainWP_Cache_Helper::log_metrics();
987 +
988 + /**
989 + * MainWP shutdown.
990 + *
991 + * @since 5.5.
992 + */
993 + do_action( 'mainwp_shutdown' );
994 +
995 + $exctime = MainWP_Execution_Helper::get_run_time();
996 + MainWP_Logger::instance()->log_events( 'execution-time', 'Shutdown :: ' . $exctime );
997 + MainWP_Logger::instance()->log_execution_sync( 'end' );
998 + }
999 +
1000 + /**
872 1001 * Method hook_plugin_action_links()
873 1002 *
874 1003 * @param string[] $actions An array of plugin action links. By default this can include
875 1004 * 'activate', 'deactivate', and 'delete'. With Multisite active
@@ -951,9 +1080,9 @@
951 1080 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
952 1081 $_pos = strlen( $request_uri ) - strlen( '/wp-admin/' );
953 1082 if ( ! empty( $request_uri ) && strpos( $request_uri, '/wp-admin/' ) !== false && strpos( $request_uri, '/wp-admin/' ) === $_pos ) {
954 1083 $referer = wp_get_referer();
955 - if ( ! empty( $referer ) && strpos( $referer, 'wp-login.php?redirect_to' ) !== false && strpos( $referer, '&reauth=1' ) !== false && mainwp_current_user_have_right( 'dashboard', 'access_global_dashboard' ) ) {
1084 + if ( ! empty( $referer ) && strpos( $referer, 'wp-login.php?redirect_to' ) !== false && strpos( $referer, '&reauth=1' ) !== false && \mainwp_current_user_can( 'dashboard', 'access_global_dashboard' ) ) {
956 1085 wp_safe_redirect( admin_url( 'admin.php?page=mainwp_tab' ) );
957 1086 die();
958 1087 }
959 1088 }
@@ -1002,14 +1131,15 @@
1002 1131 MainWP_Cache::init_cache( $ca ); // to re-set cache.
1003 1132 }
1004 1133 }
1005 1134
1135 +
1006 1136 /**
1007 1137 * Method admin_enqueue_scripts()
1008 1138 *
1009 1139 * Enqueue all Mainwp Admin Scripts.
1010 1140 */
1011 - public function admin_enqueue_scripts() {
1141 + public function admin_enqueue_scripts() { // phpcs:ignore -- NOSONAR - complex function.
1012 1142
1013 1143 $load_cust_scripts = false;
1014 1144
1015 1145 /**
@@ -1034,21 +1164,20 @@
1034 1164 wp_enqueue_script( 'mainwp-posts', MAINWP_PLUGIN_URL . 'assets/js/mainwp-posts.js', array(), $this->current_version, true );
1035 1165 wp_enqueue_script( 'mainwp-users', MAINWP_PLUGIN_URL . 'assets/js/mainwp-users.js', array(), $this->current_version, true );
1036 1166 wp_enqueue_script( 'mainwp-clients', MAINWP_PLUGIN_URL . 'assets/js/mainwp-clients.js', array(), $this->current_version, true );
1037 1167 wp_enqueue_script( 'mainwp-extensions', MAINWP_PLUGIN_URL . 'assets/js/mainwp-extensions.js', array(), $this->current_version, true );
1038 - wp_enqueue_script( 'mainwp-moment', MAINWP_PLUGIN_URL . 'assets/js/moment/moment.min.js', array(), $this->current_version, true );
1168 + wp_enqueue_script( 'moment' );
1039 1169 wp_enqueue_script( 'fomantic-ui', MAINWP_PLUGIN_URL . 'assets/js/fomantic-ui/fomantic-ui.js', array( 'jquery' ), $this->current_version, false );
1040 1170
1041 - wp_enqueue_script( 'datatables', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.js', array( 'jquery' ), $this->current_version, false );
1042 - wp_enqueue_script( 'datatables-semanticui', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.semanticui.js', array( 'datatables' ), $this->current_version, false );
1171 + wp_enqueue_script( 'datatables', MAINWP_PLUGIN_URL . 'assets/js/datatables/datatables.min.js', array( 'jquery' ), $this->current_version, false );
1172 + wp_enqueue_script( 'datatables-semanticui', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.semanticui.min.js', array( 'datatables' ), $this->current_version, false );
1043 1173 wp_enqueue_script( 'datatables-select', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.select.min.js', array( 'datatables' ), $this->current_version, false );
1044 - wp_enqueue_script( 'datatables-add-ons', MAINWP_PLUGIN_URL . 'assets/js/datatables/datatables.min.js', array( 'datatables' ), $this->current_version, false );
1174 + wp_enqueue_script( 'datatables-add-ons', MAINWP_PLUGIN_URL . 'assets/js/datatables/add-ons.datatables.min.js', array( 'datatables' ), $this->current_version, false );
1045 1175
1046 - wp_enqueue_script( 'hamburger', MAINWP_PLUGIN_URL . 'assets/js/hamburger/hamburger.js', array( 'jquery' ), $this->current_version, true );
1047 - wp_enqueue_script( 'datatables-natural-sorting', MAINWP_PLUGIN_URL . 'assets/js/sorting/natural.js', array( 'jquery' ), $this->current_version, true );
1176 + wp_enqueue_script( 'datatables-natural-sorting', MAINWP_PLUGIN_URL . 'assets/js/sorting/natural.min.js', array( 'jquery', 'datatables' ), $this->current_version, true );
1048 1177
1049 - wp_enqueue_script( 'mainwp-clipboard', MAINWP_PLUGIN_URL . 'assets/js/clipboard/clipboard.min.js', array( 'jquery' ), $this->current_version, true );
1050 - wp_enqueue_script( 'mainwp-rest-api', MAINWP_PLUGIN_URL . 'assets/js/mainwp-rest-api.js', array(), $this->current_version, true );
1178 + wp_enqueue_script( 'clipboard' );
1179 + wp_enqueue_script( 'mainwp-rest-api', MAINWP_PLUGIN_URL . 'assets/js/mainwp-rest-api.js', array(), time(), true );
1051 1180
1052 1181 if ( isset( $_GET['page'] ) && 'ManageGroups' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1053 1182 wp_enqueue_script( 'mainwp-groups', MAINWP_PLUGIN_URL . 'assets/js/mainwp-groups.js', array(), $this->current_version, true );
1054 1183 }
@@ -1055,9 +1184,9 @@
1055 1184 $enqueue_scripts = apply_filters( 'mainwp_admin_enqueue_scripts', array() );
1056 1185 if ( is_array( $enqueue_scripts ) && ! empty( $enqueue_scripts['apexcharts'] ) ) {
1057 1186 wp_enqueue_script(
1058 1187 'mainwp-apexcharts',
1059 - MAINWP_PLUGIN_URL . 'assets/js/apexcharts/apexcharts.js',
1188 + MAINWP_PLUGIN_URL . 'assets/js/apexcharts/apexcharts.min.js',
1060 1189 array(
1061 1190 'jquery',
1062 1191 'mainwp',
1063 1192 ),
@@ -1063,24 +1192,633 @@
1063 1192 ),
1064 1193 $this->current_version,
1065 1194 true
1066 1195 );
1196 + static::$defer_js_handle[] = 'mainwp-apexcharts';
1067 1197 }
1068 1198 wp_enqueue_script( 'mainwp-dropzone', MAINWP_PLUGIN_URL . 'assets/js/dropzone/dropzone.min.js', array(), $this->current_version, true );
1199 + wp_enqueue_script( 'mainwp-uptime', MAINWP_PLUGIN_URL . 'assets/js/mainwp-uptime.js', array( 'jquery', 'fomantic-ui' ), $this->current_version, true );
1200 + static::$defer_js_handle = array_merge( static::$defer_js_handle, array( 'mainwp-updates', 'mainwp-managesites-action', 'mainwp-managesites-update', 'mainwp-managesites-import', 'mainwp-plugins-themes', 'mainwp-managesites-import', 'mainwp-plugins-themes', 'mainwp-backups', 'mainwp-posts', 'mainwp-users', 'mainwp-clients', 'fomantic-ui', 'datatables', 'datatables-semanticui', 'datatables-select', 'datatables-add-ons', 'mainwp-dropzone', 'mainwp-uptime' ) );
1201 +
1069 1202 }
1070 1203
1071 1204 if ( $load_cust_scripts ) {
1205 + static::$defer_js_handle[] = 'fomantic-ui';
1072 1206 wp_enqueue_script( 'fomantic-ui', MAINWP_PLUGIN_URL . 'assets/js/fomantic-ui/fomantic-ui.js', array( 'jquery' ), $this->current_version, true );
1073 1207 }
1074 1208
1075 1209 wp_enqueue_script( 'mainwp-ui', MAINWP_PLUGIN_URL . 'assets/js/mainwp-ui.js', array(), $this->current_version, true );
1210 + wp_localize_script(
1211 + 'mainwp-ui',
1212 + 'mainwpWidgetLayout',
1213 + array(
1214 + 'openNonce' => wp_create_nonce( 'mainwp-admin-nonce' ),
1215 + )
1216 + );
1076 1217 wp_enqueue_script( 'mainwp-js-popup', MAINWP_PLUGIN_URL . 'assets/js/mainwp-popup.js', array(), $this->current_version, true );
1077 1218 // to support extension uploader.
1078 1219 wp_enqueue_script( 'mainwp-fileuploader', MAINWP_PLUGIN_URL . 'assets/js/fileuploader.js', array(), $this->current_version ); // phpcs:ignore -- fileuploader scripts need to load at header.
1079 1220 wp_enqueue_script( 'mainwp-filesaver', MAINWP_PLUGIN_URL . 'assets/js/FileSaver.js', array(), $this->current_version, true );
1221 +
1222 + static::$defer_js_handle[] = 'mainwp-ui';
1223 +
1224 + if ( is_array( static::$defer_js_handle ) ) {
1225 + $defer_handle = array_filter( array_unique( static::$defer_js_handle ) );
1226 + foreach ( $defer_handle as $h ) {
1227 + if ( wp_script_is( $h, 'enqueued' ) || wp_script_is( $h, 'registered' ) ) {
1228 + wp_script_add_data( $h, 'strategy', 'defer' ); // adds defer to <script> tag.
1229 + }
1230 + }
1231 + }
1232 +
1233 + $this->enqueue_command_palette_script();
1080 1234 }
1081 1235
1082 1236 /**
1237 + * Enqueue the MainWP command palette integration.
1238 + */
1239 + private function enqueue_command_palette_script() {
1240 +
1241 + if ( ! wp_script_is( 'wp-core-commands', 'enqueued' ) ) {
1242 + return;
1243 + }
1244 +
1245 + $palette_data = $this->get_command_palette_data();
1246 +
1247 + if ( empty( $palette_data['commands'] ) && empty( $palette_data['unregister'] ) ) {
1248 + return;
1249 + }
1250 +
1251 + wp_enqueue_script(
1252 + 'mainwp-command-palette',
1253 + MAINWP_PLUGIN_URL . 'assets/js/mainwp-command-palette.js',
1254 + array( 'wp-core-commands', 'wp-data' ),
1255 + $this->current_version,
1256 + true
1257 + );
1258 +
1259 + wp_add_inline_script(
1260 + 'mainwp-command-palette',
1261 + 'window.mainwpCommandPalette = ' . wp_json_encode( $palette_data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';',
1262 + 'before'
1263 + );
1264 + }
1265 +
1266 + /**
1267 + * Build the MainWP command palette configuration.
1268 + *
1269 + * @return array<string, array<int, mixed>> Command palette configuration.
1270 + */
1271 + private function get_command_palette_data() {
1272 + $registered_pages = $this->get_command_palette_registered_pages();
1273 + $registered_page_urls = array();
1274 +
1275 + foreach ( $registered_pages as $slug => $page_data ) {
1276 + $registered_page_urls[ $slug ] = $page_data['url'];
1277 + }
1278 +
1279 + $commands = $this->get_command_palette_menu_commands( $registered_pages );
1280 +
1281 + if ( empty( $commands ) ) {
1282 + return array(
1283 + 'commands' => array(),
1284 + 'unregister' => array(),
1285 + );
1286 + }
1287 +
1288 + $commands = apply_filters( 'mainwp_command_palette_commands', $commands, $registered_page_urls );
1289 +
1290 + $unregister = array( 'mainwp_tab' );
1291 + foreach ( array_keys( $registered_pages ) as $slug ) {
1292 + $unregister[] = 'mainwp_tab-' . $slug;
1293 + }
1294 +
1295 + $unregister = apply_filters( 'mainwp_command_palette_unregister', array_values( array_unique( $unregister ) ), $registered_page_urls );
1296 +
1297 + return array(
1298 + 'commands' => array_values( array_filter( $commands ) ),
1299 + 'unregister' => array_values( array_filter( $unregister ) ),
1300 + );
1301 + }
1302 +
1303 + /**
1304 + * Get registered MainWP submenu pages that can be opened from the palette.
1305 + *
1306 + * @return array<string, array<string, mixed>> Registered page map.
1307 + */
1308 + private function get_command_palette_registered_pages() {
1309 + global $submenu;
1310 +
1311 + if ( empty( $submenu['mainwp_tab'] ) || ! is_array( $submenu['mainwp_tab'] ) ) {
1312 + return array();
1313 + }
1314 +
1315 + $registered_pages = array();
1316 +
1317 + foreach ( $submenu['mainwp_tab'] as $submenu_item ) {
1318 + if ( empty( $submenu_item[2] ) || ( ! empty( $submenu_item[1] ) && ! current_user_can( $submenu_item[1] ) ) ) {
1319 + continue;
1320 + }
1321 +
1322 + $menu_slug = (string) $submenu_item[2];
1323 + $menu_url = '';
1324 +
1325 + if ( preg_match( '/\.php($|\?)/', $menu_slug ) || $this->is_command_palette_absolute_url( $menu_slug ) ) {
1326 + $menu_url = $menu_slug;
1327 + } elseif ( ! empty( menu_page_url( $menu_slug, false ) ) ) {
1328 + $menu_url = wp_specialchars_decode( menu_page_url( $menu_slug, false ), ENT_QUOTES );
1329 + }
1330 +
1331 + if ( empty( $menu_url ) ) {
1332 + continue;
1333 + }
1334 +
1335 + $raw_label = isset( $submenu_item[0] ) ? (string) $submenu_item[0] : '';
1336 +
1337 + $registered_pages[ $menu_slug ] = array(
1338 + 'label' => $this->clean_command_palette_label( $raw_label ),
1339 + 'url' => $menu_url,
1340 + 'hidden' => false !== strpos( $raw_label, 'mainwp-hidden' ),
1341 + );
1342 + }
1343 +
1344 + return $registered_pages;
1345 + }
1346 +
1347 + /**
1348 + * Build MainWP command-palette commands from runtime menu data.
1349 + *
1350 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1351 + *
1352 + * @return array<int, array<string, mixed>> Command list.
1353 + */
1354 + private function get_command_palette_menu_commands( $registered_pages ) {
1355 + $menu_nodes = $this->get_command_palette_menu_nodes();
1356 +
1357 + if ( empty( $menu_nodes ) ) {
1358 + $menu_nodes = array();
1359 + }
1360 +
1361 + $nodes_by_id = array();
1362 + $nodes_by_slug = array();
1363 +
1364 + foreach ( $menu_nodes as $node ) {
1365 + $nodes_by_id[ $node['id'] ] = $node;
1366 +
1367 + if ( ! empty( $node['slug_key'] ) && ! isset( $nodes_by_slug[ $node['slug_key'] ] ) ) {
1368 + $nodes_by_slug[ $node['slug_key'] ] = $node['id'];
1369 + }
1370 + }
1371 +
1372 + $commands_by_url = array();
1373 + $command_page_slugs = array();
1374 +
1375 + foreach ( $menu_nodes as $node ) {
1376 + $path_titles = $this->get_command_palette_menu_path_titles( $node, $nodes_by_id, $nodes_by_slug );
1377 +
1378 + if ( empty( $path_titles ) ) {
1379 + continue;
1380 + }
1381 +
1382 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), implode( ' > ', $path_titles ) );
1383 +
1384 + $command_name = ! empty( $node['page_slug'] ) && isset( $registered_pages[ $node['page_slug'] ] )
1385 + ? 'mainwp_tab-' . $node['page_slug']
1386 + : 'mainwp-command-' . sanitize_key( ! empty( $node['page_slug'] ) ? $node['page_slug'] : md5( $node['url'] ) );
1387 +
1388 + $command = array(
1389 + 'name' => $command_name,
1390 + 'label' => $label,
1391 + 'url' => $node['url'],
1392 + 'keywords' => $this->get_command_palette_keywords( $path_titles, $node['page_slug'] ),
1393 + 'searchLabel' => $label,
1394 + '_depth' => count( $path_titles ),
1395 + );
1396 +
1397 + if ( ! isset( $commands_by_url[ $node['url'] ] ) || $command['_depth'] > $commands_by_url[ $node['url'] ]['_depth'] ) {
1398 + $commands_by_url[ $node['url'] ] = $command;
1399 + }
1400 +
1401 + if ( ! empty( $node['page_slug'] ) ) {
1402 + $command_page_slugs[ $node['page_slug'] ] = true;
1403 + }
1404 + }
1405 +
1406 + foreach ( $this->get_command_palette_utility_commands( $registered_pages ) as $command ) {
1407 + if ( empty( $command['url'] ) || empty( $command['label'] ) ) {
1408 + continue;
1409 + }
1410 +
1411 + $command['_depth'] = isset( $command['_depth'] ) ? (int) $command['_depth'] : 1;
1412 +
1413 + if (
1414 + ! isset( $commands_by_url[ $command['url'] ] ) ||
1415 + $command['_depth'] >= $commands_by_url[ $command['url'] ]['_depth']
1416 + ) {
1417 + $commands_by_url[ $command['url'] ] = $command;
1418 + }
1419 +
1420 + if ( ! empty( $command['_page_slug'] ) ) {
1421 + $command_page_slugs[ $command['_page_slug'] ] = true;
1422 + }
1423 + }
1424 +
1425 + foreach ( $registered_pages as $page_slug => $page_data ) {
1426 + if (
1427 + isset( $command_page_slugs[ $page_slug ] ) ||
1428 + empty( $page_data['label'] ) ||
1429 + empty( $page_data['url'] ) ||
1430 + ! empty( $page_data['hidden'] )
1431 + ) {
1432 + continue;
1433 + }
1434 +
1435 + if ( isset( $commands_by_url[ $page_data['url'] ] ) ) {
1436 + continue;
1437 + }
1438 +
1439 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), $page_data['label'] );
1440 +
1441 + $commands_by_url[ $page_data['url'] ] = array(
1442 + 'name' => 'mainwp_tab-' . $page_slug,
1443 + 'label' => $label,
1444 + 'url' => $page_data['url'],
1445 + 'keywords' => $this->get_command_palette_keywords( array( $page_data['label'] ), $page_slug ),
1446 + 'searchLabel' => $label,
1447 + '_depth' => 1,
1448 + );
1449 + }
1450 +
1451 + foreach ( $commands_by_url as &$command ) {
1452 + unset( $command['_depth'], $command['_page_slug'] );
1453 + }
1454 + unset( $command );
1455 +
1456 + return array_values( $commands_by_url );
1457 + }
1458 +
1459 + /**
1460 + * Build command-palette commands for MainWP pages that do not reliably exist in the global sidebar tree.
1461 + *
1462 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1463 + *
1464 + * @return array<int, array<string, mixed>> Command list.
1465 + */
1466 + private function get_command_palette_utility_commands( $registered_pages ) {
1467 + return array_merge(
1468 + $this->get_command_palette_settings_commands( $registered_pages ),
1469 + $this->get_command_palette_info_commands( $registered_pages )
1470 + );
1471 + }
1472 +
1473 + /**
1474 + * Build Settings-section command palette commands.
1475 + *
1476 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1477 + *
1478 + * @return array<int, array<string, mixed>> Command list.
1479 + */
1480 + private function get_command_palette_settings_commands( $registered_pages ) {
1481 + $commands = array();
1482 +
1483 + foreach ( MainWP_Settings::get_command_palette_items() as $item ) {
1484 + if ( empty( $item['slug'] ) || empty( $item['path_titles'] ) ) {
1485 + continue;
1486 + }
1487 +
1488 + $commands[] = $this->build_command_palette_registered_command(
1489 + $registered_pages,
1490 + $item['slug'],
1491 + $item['path_titles'],
1492 + array( 'mainwp settings' ),
1493 + isset( $item['href'] ) ? $item['href'] : ''
1494 + );
1495 + }
1496 +
1497 + return array_values( array_filter( $commands ) );
1498 + }
1499 +
1500 + /**
1501 + * Build System Info and profile utility command palette commands.
1502 + *
1503 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1504 + *
1505 + * @return array<int, array<string, mixed>> Command list.
1506 + */
1507 + private function get_command_palette_info_commands( $registered_pages ) {
1508 + $commands = array();
1509 +
1510 + foreach ( MainWP_Server_Information::get_command_palette_items() as $item ) {
1511 + if ( empty( $item['slug'] ) || empty( $item['path_titles'] ) ) {
1512 + continue;
1513 + }
1514 +
1515 + $commands[] = $this->build_command_palette_registered_command(
1516 + $registered_pages,
1517 + $item['slug'],
1518 + $item['path_titles'],
1519 + array( 'system info', 'server info' ),
1520 + isset( $item['href'] ) ? $item['href'] : ''
1521 + );
1522 + }
1523 +
1524 + return array_values( array_filter( $commands ) );
1525 + }
1526 +
1527 + /**
1528 + * Build a command palette command for a registered page.
1529 + *
1530 + * @param array<string, array<string, mixed>> $registered_pages Registered MainWP pages.
1531 + * @param string $page_slug Registered page slug.
1532 + * @param array<int, string> $path_titles Breadcrumb path labels.
1533 + * @param array<int, string> $extra_keywords Additional search keywords.
1534 + * @param string $href Optional command href override.
1535 + *
1536 + * @return array<string, mixed>|null Command configuration.
1537 + */
1538 + private function build_command_palette_registered_command( $registered_pages, $page_slug, $path_titles, $extra_keywords = array(), $href = '' ) {
1539 + if ( empty( $registered_pages[ $page_slug ]['url'] ) ) {
1540 + return null;
1541 + }
1542 +
1543 + $path_titles = array_values(
1544 + array_filter(
1545 + array_map(
1546 + array( $this, 'clean_command_palette_label' ),
1547 + is_array( $path_titles ) ? $path_titles : array()
1548 + )
1549 + )
1550 + );
1551 +
1552 + if ( empty( $path_titles ) ) {
1553 + return null;
1554 + }
1555 +
1556 + $keywords = $this->get_command_palette_keywords( $path_titles, $page_slug );
1557 +
1558 + foreach ( $extra_keywords as $keyword ) {
1559 + $keyword = strtolower( $this->clean_command_palette_label( $keyword ) );
1560 +
1561 + if ( ! empty( $keyword ) ) {
1562 + $keywords[] = $keyword;
1563 + }
1564 + }
1565 +
1566 + $label = sprintf( __( 'Go to: MainWP > %s', 'mainwp' ), implode( ' > ', $path_titles ) );
1567 + $url = ! empty( $href ) ? $this->normalize_command_palette_url( $href, $page_slug ) : $registered_pages[ $page_slug ]['url'];
1568 +
1569 + if ( empty( $url ) ) {
1570 + return null;
1571 + }
1572 +
1573 + return array(
1574 + 'name' => 'mainwp_tab-' . $page_slug,
1575 + 'label' => $label,
1576 + 'url' => $url,
1577 + 'keywords' => array_values( array_unique( array_filter( $keywords ) ) ),
1578 + 'searchLabel' => $label,
1579 + '_depth' => count( $path_titles ),
1580 + '_page_slug' => $page_slug,
1581 + );
1582 + }
1583 +
1584 + /**
1585 + * Collect MainWP menu nodes from the runtime left-menu structures.
1586 + *
1587 + * @return array<int, array<string, string>> Menu nodes.
1588 + */
1589 + private function get_command_palette_menu_nodes() {
1590 + global $mainwp_leftmenu, $mainwp_sub_leftmenu;
1591 +
1592 + $left_menu = apply_filters( 'mainwp_main_menu', is_array( $mainwp_leftmenu ) ? $mainwp_leftmenu : array() );
1593 + $sub_left_menu = apply_filters( 'mainwp_main_menu_submenu', is_array( $mainwp_sub_leftmenu ) ? $mainwp_sub_leftmenu : array() );
1594 + $menu_nodes = array();
1595 +
1596 + if ( ! empty( $left_menu['leftbar'] ) && is_array( $left_menu['leftbar'] ) ) {
1597 + foreach ( $left_menu['leftbar'] as $item ) {
1598 + $this->add_command_palette_menu_node(
1599 + $menu_nodes,
1600 + isset( $item[0] ) ? $item[0] : '',
1601 + isset( $item[2] ) ? $item[2] : '',
1602 + isset( $item[1] ) ? $item[1] : '',
1603 + 'mainwp_tab'
1604 + );
1605 + }
1606 + }
1607 +
1608 + foreach ( $left_menu as $parent_key => $items ) {
1609 + if ( 'leftbar' === $parent_key || ! is_array( $items ) ) {
1610 + continue;
1611 + }
1612 +
1613 + foreach ( $items as $item ) {
1614 + $this->add_command_palette_menu_node(
1615 + $menu_nodes,
1616 + isset( $item[0] ) ? $item[0] : '',
1617 + isset( $item[2] ) ? $item[2] : '',
1618 + isset( $item[1] ) ? $item[1] : '',
1619 + $parent_key
1620 + );
1621 + }
1622 + }
1623 +
1624 + if ( ! empty( $sub_left_menu['leftbar'] ) && is_array( $sub_left_menu['leftbar'] ) ) {
1625 + foreach ( $sub_left_menu['leftbar'] as $parent_key => $items ) {
1626 + if ( ! is_array( $items ) ) {
1627 + continue;
1628 + }
1629 +
1630 + foreach ( $items as $item ) {
1631 + $this->add_command_palette_menu_node(
1632 + $menu_nodes,
1633 + isset( $item[0] ) ? $item[0] : '',
1634 + isset( $item[2] ) ? $item[2] : '',
1635 + isset( $item[1] ) ? $item[1] : '',
1636 + $parent_key
1637 + );
1638 + }
1639 + }
1640 + }
1641 +
1642 + foreach ( $sub_left_menu as $parent_key => $items ) {
1643 + if ( 'leftbar' === $parent_key || ! is_array( $items ) ) {
1644 + continue;
1645 + }
1646 +
1647 + foreach ( $items as $item ) {
1648 + $this->add_command_palette_menu_node(
1649 + $menu_nodes,
1650 + isset( $item[0] ) ? $item[0] : '',
1651 + isset( $item[1] ) ? $item[1] : '',
1652 + isset( $item[4] ) ? $item[4] : '',
1653 + $parent_key
1654 + );
1655 + }
1656 + }
1657 +
1658 + return $menu_nodes;
1659 + }
1660 +
1661 + /**
1662 + * Add a single node to the command palette source list.
1663 + *
1664 + * @param array<int, array<string, string>> $menu_nodes Menu nodes.
1665 + * @param string $title Menu title.
1666 + * @param string $href Menu href.
1667 + * @param string $slug Menu slug.
1668 + * @param string $parent_key Parent menu slug.
1669 + */
1670 + private function add_command_palette_menu_node( &$menu_nodes, $title, $href, $slug, $parent_key ) {
1671 + $title = $this->clean_command_palette_label( $title );
1672 + $url = $this->normalize_command_palette_url( $href, $slug );
1673 +
1674 + if ( empty( $title ) || empty( $url ) ) {
1675 + return;
1676 + }
1677 +
1678 + $page_slug = $this->get_command_palette_page_slug_from_url( $url );
1679 + $slug_key = ! empty( $slug ) ? (string) $slug : $page_slug;
1680 +
1681 + $menu_nodes[] = array(
1682 + 'id' => md5( $parent_key . '|' . $slug_key . '|' . $url . '|' . $title ),
1683 + 'parent' => (string) $parent_key,
1684 + 'page_slug' => $page_slug,
1685 + 'slug_key' => $slug_key,
1686 + 'title' => $title,
1687 + 'url' => $url,
1688 + );
1689 + }
1690 +
1691 + /**
1692 + * Build a node breadcrumb from the MainWP menu tree.
1693 + *
1694 + * @param array<string, string> $node Menu node.
1695 + * @param array<string, array<string, string>> $nodes_by_id Menu nodes indexed by id.
1696 + * @param array<string, string> $nodes_by_slug Menu nodes indexed by slug.
1697 + *
1698 + * @return array<int, string> Path titles.
1699 + */
1700 + private function get_command_palette_menu_path_titles( $node, $nodes_by_id, $nodes_by_slug ) {
1701 + $titles = array();
1702 + $current = $node;
1703 + $seen_nodes = array();
1704 +
1705 + while ( is_array( $current ) && ! empty( $current['id'] ) && ! isset( $seen_nodes[ $current['id'] ] ) ) {
1706 + $seen_nodes[ $current['id'] ] = true;
1707 +
1708 + if ( ! empty( $current['title'] ) ) {
1709 + array_unshift( $titles, $current['title'] );
1710 + }
1711 +
1712 + if ( empty( $current['parent'] ) || 'mainwp_tab' === $current['parent'] || ! isset( $nodes_by_slug[ $current['parent'] ] ) ) {
1713 + break;
1714 + }
1715 +
1716 + $parent_id = $nodes_by_slug[ $current['parent'] ];
1717 + $current = isset( $nodes_by_id[ $parent_id ] ) ? $nodes_by_id[ $parent_id ] : array();
1718 + }
1719 +
1720 + return $titles;
1721 + }
1722 +
1723 + /**
1724 + * Build search keywords for a MainWP command.
1725 + *
1726 + * @param array<int, string> $path_titles Path titles.
1727 + * @param string $page_slug Page slug.
1728 + *
1729 + * @return array<int, string> Search keywords.
1730 + */
1731 + private function get_command_palette_keywords( $path_titles, $page_slug = '' ) {
1732 + $keywords = array( 'mainwp' );
1733 +
1734 + foreach ( $path_titles as $title ) {
1735 + $keywords[] = strtolower( $title );
1736 + }
1737 +
1738 + if ( ! empty( $page_slug ) ) {
1739 + $keywords[] = strtolower( str_replace( array( '-', '_' ), ' ', $page_slug ) );
1740 + }
1741 +
1742 + return array_values( array_unique( array_filter( $keywords ) ) );
1743 + }
1744 +
1745 + /**
1746 + * Normalize menu text into readable plain text.
1747 + *
1748 + * @param string $label Raw menu label.
1749 + *
1750 + * @return string Plain-text label.
1751 + */
1752 + private function clean_command_palette_label( $label ) {
1753 + $label = wp_specialchars_decode( wp_strip_all_tags( (string) $label ), ENT_QUOTES );
1754 + $label = preg_replace( '/\s+/', ' ', $label );
1755 +
1756 + return trim( (string) $label );
1757 + }
1758 +
1759 + /**
1760 + * Check whether a command-palette URL is already absolute.
1761 + *
1762 + * @param string $url URL to check.
1763 + *
1764 + * @return bool True when the URL is an absolute HTTP(S) URL.
1765 + */
1766 + private function is_command_palette_absolute_url( $url ) {
1767 + return 1 === preg_match( '/^https?:\/\//i', trim( (string) $url ) );
1768 + }
1769 +
1770 + /**
1771 + * Normalize a MainWP menu href into a usable URL.
1772 + *
1773 + * @param string $href Menu href.
1774 + * @param string $slug Menu slug.
1775 + *
1776 + * @return string Normalized URL.
1777 + */
1778 + private function normalize_command_palette_url( $href, $slug = '' ) {
1779 + $href = trim( wp_specialchars_decode( (string) $href, ENT_QUOTES ) );
1780 + $slug = trim( (string) $slug );
1781 +
1782 + if ( empty( $href ) || '#' === $href || 0 === strpos( $href, 'javascript:' ) ) {
1783 + if ( empty( $slug ) || preg_match( '/\.php($|\?)/', $slug ) || $this->is_command_palette_absolute_url( $slug ) ) {
1784 + return '';
1785 + }
1786 +
1787 + return admin_url( 'admin.php?page=' . $slug );
1788 + }
1789 +
1790 + if ( $this->is_command_palette_absolute_url( $href ) ) {
1791 + return $href;
1792 + }
1793 +
1794 + return admin_url( ltrim( $href, '/' ) );
1795 + }
1796 +
1797 + /**
1798 + * Extract a page slug from a menu URL when one exists.
1799 + *
1800 + * @param string $url Menu URL.
1801 + *
1802 + * @return string Page slug.
1803 + */
1804 + private function get_command_palette_page_slug_from_url( $url ) {
1805 + $query = wp_parse_url( $url, PHP_URL_QUERY );
1806 +
1807 + if ( empty( $query ) ) {
1808 + return '';
1809 + }
1810 +
1811 + parse_str( $query, $query_args );
1812 +
1813 + if ( ! isset( $query_args['page'] ) ) {
1814 + return '';
1815 + }
1816 +
1817 + return preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $query_args['page'] );
1818 + }
1819 +
1820 + /**
1083 1821 * Method admin_enqueue_scripts_fix_conflict()
1084 1822 *
1085 1823 * Enqueue Admin Scripts fix conflict.
1086 1824 */
@@ -1121,14 +1859,12 @@
1121 1859 wp_enqueue_style( 'mainwp-fonts', MAINWP_PLUGIN_URL . 'assets/css/mainwp-fonts.css', array(), $this->current_version );
1122 1860 wp_enqueue_style( 'fomantic-ui', MAINWP_PLUGIN_URL . 'assets/js/fomantic-ui/fomantic-ui.css', array(), $this->current_version );
1123 1861 wp_enqueue_style( 'mainwp-fomantic', MAINWP_PLUGIN_URL . 'assets/css/mainwp-fomantic.css', array(), $this->current_version );
1124 1862
1125 - wp_enqueue_style( 'datatables', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.dataTables.css', array(), $this->current_version );
1126 - wp_enqueue_style( 'datatables-semanticui', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.semanticui.css', array(), $this->current_version );
1863 + wp_enqueue_style( 'datatables', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.dataTables.min.css', array(), $this->current_version );
1864 + wp_enqueue_style( 'datatables-semanticui', MAINWP_PLUGIN_URL . 'assets/js/datatables/dataTables.semanticui.min.css', array(), $this->current_version );
1127 1865 wp_enqueue_style( 'datatables-select', MAINWP_PLUGIN_URL . 'assets/js/datatables/select.semanticui.min.css', array(), $this->current_version );
1128 - wp_enqueue_style( 'datatables-add-ons', MAINWP_PLUGIN_URL . 'assets/js/datatables/datatables.min.css', array(), $this->current_version );
1129 -
1130 - wp_enqueue_style( 'hamburger', MAINWP_PLUGIN_URL . 'assets/js/hamburger/hamburger.css', array(), $this->current_version );
1866 + wp_enqueue_style( 'datatables-add-ons', MAINWP_PLUGIN_URL . 'assets/js/datatables/add-ons.datatables.min.css', array(), $this->current_version );
1131 1867 // to fix conflict layout.
1132 1868 wp_enqueue_style( 'jquery-ui-style', MAINWP_PLUGIN_URL . 'assets/css/1.11.1/jquery-ui.min.css', array(), '1.11.1' );
1133 1869
1134 1870 // load custom MainWP theme.
@@ -1139,10 +1875,14 @@
1139 1875 } elseif ( 'wpadmin' === $selected_theme ) {
1140 1876 wp_enqueue_style( 'mainwp-custom-dashboard-extension-wp-admin-theme', MAINWP_PLUGIN_URL . 'assets/css/themes/mainwp-wpadmin-theme.css', array(), $this->current_version );
1141 1877 } elseif ( 'minimalistic' === $selected_theme ) {
1142 1878 wp_enqueue_style( 'mainwp-custom-dashboard-extension-minimalistic-theme', MAINWP_PLUGIN_URL . 'assets/css/themes/mainwp-minimalistic-theme.css', array(), $this->current_version );
1879 + } elseif ( 'default-2024' === $selected_theme ) {
1880 + wp_enqueue_style( 'mainwp-custom-dashboard-extension-default-2024-theme', MAINWP_PLUGIN_URL . 'assets/css/themes/mainwp-default-2024-theme.css', array(), $this->current_version );
1143 1881 } elseif ( 'default' === $selected_theme ) {
1144 1882 wp_enqueue_style( 'mainwp-custom-dashboard-extension-default-theme', MAINWP_PLUGIN_URL . 'assets/css/themes/mainwp-default-theme.css', array(), $this->current_version );
1883 + } elseif ( 'default-dark' === $selected_theme ) {
1884 + wp_enqueue_style( 'mainwp-custom-dashboard-extension-default-dark-theme', MAINWP_PLUGIN_URL . 'assets/css/themes/mainwp-default-dark-theme.css', array(), $this->current_version );
1145 1885 } else {
1146 1886 $dirs = MainWP_Settings::get_instance()->get_custom_theme_folder();
1147 1887 $custom_theme_url = $dirs[1];
1148 1888 wp_enqueue_style( 'mainwp-custom-dashboard-theme', $custom_theme_url . $selected_theme, array(), $this->current_version );
@@ -1250,9 +1990,9 @@
1250 1990 }
1251 1991 } elseif ( 'UpdatesManage' === $_GET['page'] || 'mainwp_tab' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1252 1992 $staging_enabled = is_plugin_active( 'mainwp-staging-extension/mainwp-staging-extension.php' ) ? true : false;
1253 1993 if ( $staging_enabled ) {
1254 - $staging_view = MainWP_System_Utility::get_staging_options_sites_view_for_current_users();
1994 + $staging_view = MainWP_System_Utility::get_select_staging_view_sites();
1255 1995 if ( 'staging' === $staging_view ) {
1256 1996 $is_staging = 'yes';
1257 1997 }
1258 1998 }
@@ -1268,10 +2008,8 @@
1268 2008
1269 2009 MainWP_System_View::admin_footer();
1270 2010 MainWP_System_View::render_plugins_install_check();
1271 2011
1272 - MainWP_Menu::init_sub_pages();
1273 -
1274 2012 /**
1275 2013 * MainWP disabled menu items array.
1276 2014 *
1277 2015 * @global object
@@ -1312,8 +2050,17 @@
1312 2050 * Deactivate MainWP.
1313 2051 */
1314 2052 public function deactivation() {
1315 2053 update_option( 'mainwp_extensions_all_activation_cached', '' );
2054 + $useWPCron = ( get_option( 'mainwp_wp_cron' ) === false ) || ( (int) get_option( 'mainwp_wp_cron' ) === 1 );
2055 + if ( $useWPCron ) {
2056 + $jobs = MainWP_System_Cron_Jobs::instance()->get_cron_jobs();
2057 + if ( $jobs ) {
2058 + foreach ( $jobs as $job => $recu ) {
2059 + wp_clear_scheduled_hook( $job );
2060 + }
2061 + }
2062 + }
1316 2063 }
1317 2064
1318 2065 /**
1319 2066 * Method update_install()
@@ -1374,6 +2121,57 @@
1374 2121 * Get MainWP Plugin Slug.
1375 2122 */
1376 2123 public function get_plugin_slug() {
1377 2124 return $this->plugin_slug;
2125 + }
2126 +
2127 + /**
2128 + * Method get_selected_sites_with_allowed_sites.
2129 + *
2130 + * @param array $selected_ids Selected sites IDs.
2131 + * @param array $current_sites Current sites IDs.
2132 + * @return array Not selected but in not allowed sites in current sites ids, and selected sites ids.
2133 + */
2134 + public static function get_selected_sites_with_allowed_sites( $selected_ids, $current_sites_ids = array() ) {
2135 +
2136 + $allowed_sites = apply_filters( 'mainwp_currentuserallowedaccesssites', 'all' );
2137 + if ( 'all' === $allowed_sites ) {
2138 + return $selected_ids;
2139 + }
2140 +
2141 + if ( ! is_array( $selected_ids ) ) {
2142 + $selected_ids = array();
2143 + }
2144 +
2145 + if ( ! is_array( $current_sites_ids ) ) {
2146 + $current_sites_ids = array();
2147 + }
2148 +
2149 + $allowed_sites = MainWP_DB::instance()->get_websites_for_current_user();
2150 + if ( empty( $allowed_sites ) ) {
2151 + return array();
2152 + }
2153 +
2154 + $allowed_ids = array_values(
2155 + array_map(
2156 + function ( $item ) {
2157 + return $item->id;
2158 + },
2159 + $allowed_sites
2160 + )
2161 + );
2162 +
2163 + $client_site_ids = array();
2164 + foreach ( $selected_ids as $id ) {
2165 + if ( in_array( $id, $allowed_ids ) ) {
2166 + $client_site_ids[] = $id;
2167 + }
2168 + }
2169 +
2170 + foreach ( $current_sites_ids as $id ) {
2171 + if ( ! in_array( $id, $selected_ids ) && ! in_array( $id, $allowed_ids ) ) { // If it’s not in the allowed site IDs, the user is not permitted to change it.
2172 + $client_site_ids[] = $id; // keep this site ids.
2173 + }
2174 + }
2175 + return $client_site_ids;
1378 2176 }
1379 2177 }