*/ namespace ThemeAtelier\Darkify\Admin; use ThemeAtelier\Darkify\Admin\ReviewNotice\ReviewNotice; use ThemeAtelier\Darkify\Admin\ReviewNotice\ThemeAtelier_Offer_Banner; use ThemeAtelier\Darkify\Admin\Schema\SchemaDefaults; use ThemeAtelier\Darkify\Admin\Views\DarkifyOptions; use ThemeAtelier\Darkify\Includes\DarkifyExternalSupport; use ThemeAtelier\Darkify\Includes\DarkifyUtils; use ThemeAtelier\Darkify\Admin\DBUpdates; /** * The admin class */ class Admin { /** * The min of this plugin. * * @since 1.0.0 * @access private * @var string $min The slug of this plugin. */ private $min; public $utils; public $settings; public $external_support; private $plugin_name; private $version; public $unique_id; /** * Whether enqueue_scripts() actually put the admin engine in the queue on * this request. Read by darkify_restore_no_conflict_scripts(), which must * only re-add handles Darkify itself asked for. * * @var bool */ private $engine_enqueued = false; /** * The class constructor. * * @param string $plugin_name The slug of the plugin. * @param string $version Current version of the plugin. */ public function __construct($plugin_name, $version) { $this->plugin_name = $plugin_name; $this->version = $version; $this->utils = new DarkifyUtils($this); $this->external_support = new DarkifyExternalSupport($this); $this->min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min'; if (function_exists('wp_rand')) { $this->unique_id = wp_rand(); } add_action('current_screen', function ($screen) { $options = get_option('darkify'); $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false; $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false; $is_block_editor = method_exists($screen, 'is_block_editor') && $screen->is_block_editor(); // Block Editor Dark Mode is INDEPENDENT of Admin Panel Dark Mode: // the block editor darkens whenever Block Editor Dark Mode is on, // even if the rest of wp-admin stays light. Every other admin screen // still keys on Admin Panel Dark Mode. $needs_engine = $is_block_editor ? (bool) $block_editor_dark_mode : (bool) $enable_admin_panel_dark_mode; // Darkify's own settings/help screens additionally load it even while // the option is OFF, so flipping "Admin Panel Dark Mode" in the React // admin can show the admin-bar icon (and have it actually work) // immediately instead of only after a reload. Loading it here cannot // darken anything on its own: both the FOUC snippet in // header_script.php and darkify_check_preloading() in client_main.js // are gated on `darkify_admin_panel_dark_enabled`. if ($needs_engine || $this->is_darkify_spa_page()) { add_action('admin_bar_menu', array($this, 'darkify_admin_bar_switch'), 9999); add_action('admin_print_scripts', array($this, 'darkify_admin_header_script'), 1); add_action('admin_footer', array($this, 'darkify_admin_footer_script')); } }); new ReviewNotice(); new DBUpdates(); if (! defined('THEMEATELIER_OFFER_BANNER_LOADED')) { define('THEMEATELIER_OFFER_BANNER_LOADED', true); ThemeAtelier_Offer_Banner::instance(); } // Building the schema is what evaluates every `__()` in // src/Admin/Views/*.php, so the text domain has to be registered first — // Darkify::load_textdomain() runs on this same hook at priority 1. add_action('after_setup_theme', array($this, 'init_components')); add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins')); /* * "No-conflict mode" compatibility. * * Fluent Forms, FluentCRM, Fluent Booking, FluentSMTP, WP Social Ninja * and friends dequeue *every* script served from wp-content/plugins on * their own admin screens (Fluent Forms: app/Hooks/actions.php, on * `wp_print_scripts` priority 1). That runs after `admin_enqueue_scripts`, * so it strips Darkify's engine while the admin-bar node — printed by * `admin_bar_menu` and untouched by the dequeue — keeps its inline * onclick, producing "darkify_switch_trigger is not defined" and a dead * toggle on e.g. admin.php?page=fluent_forms. * * Two layers: the published allow-list filters where a plugin offers one, * and a generic re-enqueue after their sweep for the ones that don't. */ add_filter('fluentform/exclude_js_slugs_from_dequeue', array($this, 'darkify_no_conflict_allow_slugs')); add_filter('fluent_crm_asset_listed_slugs', array($this, 'darkify_no_conflict_allow_slugs')); add_filter('fluent_booking/asset_listed_slugs', array($this, 'darkify_no_conflict_allow_slugs')); add_action('wp_print_scripts', array($this, 'darkify_restore_no_conflict_scripts'), 100); add_action('wp_print_styles', array($this, 'darkify_restore_no_conflict_styles'), 100); add_action('admin_init', [$this, 'load_classic_editor_scripts']); add_filter('admin_footer_text', array($this, 'darkify_admin_footer'), 1, 2); $active_plugins = get_option('active_plugins'); foreach ($active_plugins as $active_plugin) { $_temp = strpos($active_plugin, 'darkify.php'); if (false != $_temp) { add_filter('plugin_action_links_' . DARKIFY_BASENAME, array($this, 'add_plugin_action_links'), 10, 2); add_filter('plugin_row_meta', array($this, 'after_darkify_row_meta'), 10, 4); } } } public function init_components() { DarkifyOptions::options('darkify'); $this->seed_default_options(); } /** * Write the schema's defaults into the `darkify` option on a fresh install. * * Nothing used to create that option until the first save in the admin. Up to * that point the two halves of the plugin disagreed: the React admin resolved * each field from the schema (so the Switch Toggler showed Orbit, the declared * default), while the frontend read the raw option — found nothing — and fell * back to the literal in its own template, rendering the Classic switcher. Same * class of mismatch for every other field whose template fallback differs from * its declared default. * * Seeding makes the stored values the single source both sides read, so a site * ships with exactly what the admin displays before anyone touches Settings. * * Runs on `init` (every request, admin and frontend), right after * the config classes have registered their sections, and is a no-op once the * option exists — so it never overwrites a user's saved settings, and an * upgrade from an older version is left alone. */ private function seed_default_options() { $existing = get_option('darkify'); if (is_array($existing) && ! empty($existing)) { return; } $defaults = SchemaDefaults::for_option('darkify'); if (empty($defaults)) { return; } // `update_option` rather than `add_option`: an install that already holds // an empty/corrupt value (a stray '' or false) gets seeded too. update_option('darkify', $defaults); } /** * Whether the current screen is one of Darkify's own React SPA screens * (Settings / Get Help) — the only place the Admin Panel Dark Mode option can * be toggled, and therefore the only place that needs the engine loaded up * front so the admin-bar icon can react instantly. */ public function is_darkify_spa_page(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check. $page = isset($_GET['page']) ? \sanitize_key(\wp_unslash($_GET['page'])) : ''; return \in_array($page, Assets::SPA_SLUGS, true); } public function darkify_exclude_js_from_cache_plugins($excludes) { // Dark Reader proxies document.styleSheets and injects its own