title), for the settings checklist. */ public const SEEN_OPTION = 'featherdash_dashboard_widgets'; /** * @var array */ private const CONTEXTS = [ 'normal', 'side', 'column3', 'column4' ]; /** * @var array */ private const NETWORK_WIDGETS = ['network_dashboard_right_now', 'network_dashboard_wordpress_events_and_news']; /** * Widgets that are WordPress' own warnings (outdated browser or PHP): * never hidden and never listed. * * @var array */ private const ALWAYS_SHOWN = ['dashboard_browser_nag', 'dashboard_php_nag']; /** * Dashboard widgets of known plugins: id => [widget title, plugin name, * plugin file or const:NAME to tell the plugin is active]. * * @var array */ private const KNOWN_WIDGETS = [ 'cn_dashboard_stats' => ['Cookie Compliance', 'Cookie Notice', 'cookie-notice/cookie-notice.php'], 'cookiebot_status' => ['Cookiebot status', 'Cookiebot', 'cookiebot/cookiebot.php'], 'rg_forms_dashboard' => ['Forms', 'Gravity Forms', 'gravityforms/gravityforms.php'], 'iubenda-compliance-status' => ['Compliance status', 'iubenda', 'iubenda-cookie-law-solution/iubenda_cookie_solution.php'], 'monsterinsights_reports_widget' => ['Reports', 'MonsterInsights', 'google-analytics-for-wordpress/googleanalytics.php'], 'tribe_dashboard_widget' => ['News', 'The Events Calendar', 'the-events-calendar/the-events-calendar.php'], 'wc_admin_dashboard_setup' => ['Store setup', 'WooCommerce', 'woocommerce/woocommerce.php'], 'woocommerce_dashboard_status' => ['Status', 'WooCommerce', 'woocommerce/woocommerce.php'], 'woocommerce_dashboard_recent_reviews' => ['Recent reviews', 'WooCommerce', 'woocommerce/woocommerce.php'], 'wpconsent_onboarding_reminder' => ['Setup', 'WPConsent', 'wpconsent-cookies-banner-privacy-suite/wpconsent.php'], 'wpseo-dashboard-overview' => ['Posts overview', 'Yoast SEO', 'wordpress-seo/wp-seo.php'], 'wpseo-wincher-dashboard-overview' => ['Wincher top keyphrases', 'Yoast SEO', 'wordpress-seo/wp-seo.php'], ]; /** * FeatherDash's own widgets (dashboard.featherdash). Each module adds and hides * its widget itself; listed here so they never count as plugin widgets. * * @var array */ public const FEATHERDASH_WIDGETS = ['featherdash_analytics', 'featherdash_welcome', 'featherdash_request']; private Settings $settings; public function register( Settings $settings ) : void { $this->settings = $settings; // Last, so widgets every plugin added are in place. add_action( 'wp_dashboard_setup', [$this, 'setup'], PHP_INT_MAX ); add_action( 'wp_network_dashboard_setup', [$this, 'networkSetup'], PHP_INT_MAX ); } public function setup() : void { $show = $this->visibleCore(); if ( !in_array( 'wp_welcome_panel', $show, true ) ) { remove_action( 'welcome_panel', 'wp_welcome_panel' ); } foreach ( self::siteCoreWidgets() as $widget ) { if ( $widget !== 'wp_welcome_panel' && !in_array( $widget, $show, true ) ) { $this->remove( $widget, 'dashboard' ); } } } public function networkSetup() : void { $show = $this->visibleCore(); if ( !in_array( 'network_dashboard_right_now', $show, true ) ) { $this->remove( 'network_dashboard_right_now', 'dashboard-network' ); } if ( !in_array( 'network_dashboard_wordpress_events_and_news', $show, true ) ) { $this->remove( 'dashboard_primary', 'dashboard-network' ); } } /** * Options for the "Visible widgets added by plugins" checklist: * known plugin widgets (greyed out when the plugin is inactive) plus any * other plugin widgets recorded at runtime. * * @return array */ public static function pluginWidgetOptions() : array { if ( !function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $options = []; /** * Filters the plugin dashboard widgets listed on the Dashboard tab even * before they're seen: `id => [label, plugin name, plugin file or 'const:NAME']`. * * @param array $known */ $known = (array) apply_filters( 'featherdash/dashboard/known_widgets', self::KNOWN_WIDGETS ); foreach ( $known as $id => [$label, $plugin, $check] ) { $active = ( str_starts_with( $check, 'const:' ) ? defined( substr( $check, 6 ) ) : is_plugin_active( $check ) ); $options[$id] = [ 'label' => $label, 'plugin' => $plugin, 'active' => $active, ]; } $seen = get_option( self::SEEN_OPTION, [] ); foreach ( ( is_array( $seen ) ? $seen : [] ) as $id => $label ) { $options[(string) $id] ??= [ 'label' => ( (string) $label ?: (string) $id ), 'plugin' => '', 'active' => true, ]; } uasort( $options, static fn( array $a, array $b ): int => [!$a['active'], ( $a['plugin'] ?: $a['label'] ), $a['label']] <=> [!$b['active'], ( $b['plugin'] ?: $b['label'] ), $b['label']] ); return $options; } /** * @param array $found */ private function remember( array $found ) : void { if ( $found === [] || !current_user_can( 'manage_options' ) ) { return; } $seen = get_option( self::SEEN_OPTION, [] ); $seen = ( is_array( $seen ) ? $seen : [] ); $merged = $seen + $found; if ( $merged != $seen ) { ksort( $merged ); update_option( self::SEEN_OPTION, $merged, false ); } } private function remove( string $widget, string $screen ) : void { foreach ( self::CONTEXTS as $context ) { remove_meta_box( $widget, $screen, $context ); } } /** * @return array */ private function visibleCore() : array { return array_map( 'strval', (array) $this->settings->get( 'dashboard.show', [] ) ); } /** * Core widgets of the site dashboard (the options of dashboard.show * minus the network ones). * * @return array */ private static function siteCoreWidgets() : array { $options = Schema::fieldTypes()['dashboard.show']['options'] ?? []; return array_values( array_diff( $options, self::NETWORK_WIDGETS ) ); } }