*/
namespace ThemeAtelier\Darkify\Admin;
use ThemeAtelier\Darkify\Admin\ReviewNotice\ReviewNotice;
use ThemeAtelier\Darkify\Admin\ReviewNotice\ThemeAtelier_Offer_Banner;
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;
/**
* 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();
}
add_action('after_setup_theme', array($this, 'init_components'));
add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins'));
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');
}
/**
* 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)
{
$excludes .= ',client_main.js,client_main.min.js';
return $excludes;
}
public function enqueue_styles()
{
$options = get_option('darkify');
$enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
// Block Editor Dark Mode darkens the editor independently of Admin Panel
// Dark Mode, so the stylesheet must load there too.
$block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
$screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
$is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
$allow_admin_dark = $enable_admin_panel_dark_mode || ($is_block_editor && $block_editor_dark_mode);
// Darkify's own screens always get the stylesheet so the admin-bar icon
// can be revealed the moment the option is switched on. It only styles
// `.darkify_*` classes, so it is inert until the engine adds them.
if (($allow_admin_dark || $this->is_darkify_spa_page()) && $this->darkify_is_dark_mode_allowed()) {
if (!wp_style_is('darkify-admin-switch', 'enqueued')) {
wp_enqueue_style('darkify-admin-switch', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $this->min . '.css', array(), $this->darkify_asset_version('src/assets/css/client_main' . $this->min . '.css'), 'all');
}
}
// Review notice CSS
wp_enqueue_style('darkify-review-notice', DARKIFY_DIR_URL . 'src/Admin/ReviewNotice/assets/css/review-notice' . $this->min . '.css', array(), DARKIFY_VERSION, 'all');
}
public function enqueue_scripts($hook)
{
$options = get_option('darkify');
$enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
$is_spa = $this->is_darkify_spa_page();
if (! $this->darkify_is_dark_mode_allowed()) {
return;
}
// Mirror the `current_screen` engine-load rule exactly, so the engine is
// only enqueued where its inline config (header_script.php) is ALSO
// printed. Block Editor Dark Mode is independent of Admin Panel Dark
// Mode: the block editor loads the engine when Block Editor Dark Mode is
// on (regardless of Admin Panel Dark Mode); every other admin screen
// keys on Admin Panel Dark Mode. (A SPA screen always gets the config.)
if (! $is_spa) {
$screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
$is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
$block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
$allow = $is_block_editor ? (bool) $block_editor_dark_mode : (bool) $enable_admin_panel_dark_mode;
if (! $allow) {
return;
}
}
wp_enqueue_script(
'darkify-admin-client-main',
DARKIFY_DIR_URL . 'src/assets/js/client_main' . $this->min . '.js',
array(),
$this->darkify_asset_version('src/assets/js/client_main' . $this->min . '.js')
);
}
/**
* Cache-busting version for a bundled asset: the plugin version plus the
* file's mtime, so rebuilding client_main.js/.css (which does NOT bump the
* plugin version) still changes the URL and browsers fetch the new file
* instead of serving a stale one. Mirrors Admin\Assets::asset_version().
*/
public function darkify_asset_version(string $relative_path): string
{
$abs = DARKIFY_PATH . $relative_path;
$mtime = \is_readable($abs) ? \filemtime($abs) : false;
return $mtime ? DARKIFY_VERSION . '.' . $mtime : DARKIFY_VERSION;
}
/*
Dequeue Darkify scripts and styles on specific admin pages (e.g., MainWP pages)
*/
public function admin_dequeue_for_specefic_pages()
{
$page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
$dequeue_pages = array(
'mainwp_tab',
'managesites',
'ManageClients',
'CostSummary',
'InsightsOverview',
'Extensions',
'RESTAPI',
'mainwp-setup',
'ThemesManage',
'PluginsManage',
'InsightsManage',
'ManageGroups',
'UpdatesManage',
'PluginsInstall',
'PluginsAutoUpdate',
'PluginsIgnore',
'PluginsAbandoned',
'PluginsIgnoredAbandoned',
'ThemesInstall',
'ThemesAutoUpdate',
'ThemesIgnore',
'ThemesAbandoned',
'ThemesIgnoredAbandoned',
'UserBulkManage',
'UserBulkAdd',
'BulkImportUsers',
'UpdateAdminPasswords',
'PostBulkManage',
'Extensions-Mainwp-Backups',
'Extensions-Mainwp-Security',
'Extensions-Mainwp-Analytics',
'Mainwp-Monitoring',
'Extensions-Mainwp-Agency',
'Extensions-Mainwp-Administrative',
'Extensions-Mainwp-Development',
'Extensions-Mainwp-Performance',
'ClientAddNew',
'ClientImport',
'ManageCostTracker',
'CostTrackerAdd',
'AddApiKeys',
);
if (in_array($page, $dequeue_pages, true)) {
wp_deregister_style('darkify-admin-switch');
wp_dequeue_script('darkify-admin-client-main');
}
}
/**
* Replace wp-admin's footer credit with Darkify's review request, on
* Darkify's own screens only.
*
* Screens are matched by page slug (`is_darkify_spa_page()`) rather than by
* a hardcoded screen id, so both the Settings and Get Help SPA screens match
* and the check keeps working if the menu is ever retitled. The slug list is
* the same source of truth the assets already load from.
*
* @param string $text Existing footer text.
*
* @return string
*/
public function darkify_admin_footer($text)
{
if (! $this->is_darkify_spa_page()) {
return $text;
}
return sprintf(
/* translators: 1: start strong tag, 2: close strong tag. 3: start link 4: close link */
__('Enjoying %1$sDarkify?%2$s Please rate us %3$sWordPress.org%4$s. Your positive feedback will help us grow more. Thank you! 😊', 'darkify'),
'',
'',
' ',
''
);
}
// Plugin settings in plugin list
public function add_plugin_action_links(array $links)
{
$new_links = array(
sprintf('' . esc_html__('Settings', 'darkify') . ''),
sprintf('' . esc_html__('Support', 'darkify') . ''),
);
$links[] = sprintf('%s', esc_html__('Go Pro!', 'darkify'));
return array_merge($new_links, $links);
}
/**
* Add plugin row meta link.
*
* @since 2.0
*
* @param array $plugin_meta .
* @param string $file .
*
* @return array
*/
public function after_darkify_row_meta($plugin_meta, $file)
{
if (DARKIFY_BASENAME === $file) {
$plugin_meta[] = '' . __('Live Demo', 'darkify') . '';
}
return $plugin_meta;
}
public function darkify_admin_bar_switch($wp_admin_bar)
{
if (! $this->darkify_is_dark_mode_allowed()) {
return;
}
$options = get_option('darkify');
$enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
// Everywhere but Darkify's own screens the node is only registered when
// the option is on, so it is always visible. On the SPA screens it is
// registered regardless and starts hidden while the option is off — the
// React admin then just flips this class (see WpAdminBarBridge), which is
// what makes the icon appear/disappear without a reload.
$classes = 'darkify_admin_bar_switch_container';
if (! $enable_admin_panel_dark_mode) {
$classes .= ' darkify_admin_bar_hidden';
}
$wp_admin_bar->add_node(array(
'parent' => 'top-secondary',
'id' => 'darkify_admin_bar_switch_container',
'meta' => array(
'class' => $classes,
'onclick' => 'darkify_switch_trigger()',
),
));
}
public function darkify_is_dark_mode_allowed()
{
$options = get_option('darkify');
$options = is_array($options) ? $options : array();
// Guarded read: `disallowed_admin_pages` has no schema default, so the
// React settings save doesn't persist it until the field is used. Reading
// it unguarded would emit an "Undefined array key" warning on every
// dark-mode hook (see the Pro plugin's Admin.php for the full story).
$disallowed_admin_pages = isset($options['disallowed_admin_pages']) ? $options['disallowed_admin_pages'] : '';
/* Disable on Disallowed Admin Plugins */
if ($this->utils->isRestrictedByDisallowedAdminPages($disallowed_admin_pages)) {
return False;
}
return True;
}
public function darkify_admin_header_script()
{
if ($this->darkify_is_dark_mode_allowed()) {
include_once DarkifyUtils::darkify_locate_template('header_script.php');
}
}
public function darkify_admin_footer_script()
{
if ($this->darkify_is_dark_mode_allowed()) {
include_once DarkifyUtils::darkify_locate_template('footer_script.php');
}
}
/**
* Load classic editor scripts.
*
* @since 5.0.0
*/
public function load_classic_editor_scripts()
{
$options = get_option('darkify');
$classic_editor_dark_mode = isset($options['classic_editor_dark_mode']) ? $options['classic_editor_dark_mode'] : '';
if (! current_user_can('edit_posts') && ! current_user_can('edit_pages')) {
return;
}
if (get_user_option('rich_editing') !== 'true') {
return;
}
// Bail if admin_classic_editor is not enabled.
if (! wp_validate_boolean($classic_editor_dark_mode)) {
return;
}
add_filter('mce_external_plugins', function ($plugins) {
$plugins['darkify_button'] = plugins_url('src/assets/js/admin-classic-editor.js', DARKIFY_FILE);
return $plugins;
});
add_filter('mce_buttons', function ($buttons) {
$buttons[] = 'darkify_button';
return $buttons;
});
}
}