PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.1.3
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.1.3
2.1.3 2.1.2 2.1.1 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 trunk 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 All 57 releases
← All changes | src/Admin/Admin.php +528 -148 1.3.02.1.3 View file →
@@ -12,13 +12,15 @@
12 12 */
13 13
14 14 namespace ThemeAtelier\Darkify\Admin;
15 15
16 -use ThemeAtelier\Darkify\Admin\TADiscountPage\TADiscountPage;
16 +use ThemeAtelier\Darkify\Admin\ReviewNotice\ReviewNotice;
17 +use ThemeAtelier\Darkify\Admin\ReviewNotice\ThemeAtelier_Offer_Banner;
18 +use ThemeAtelier\Darkify\Admin\Schema\SchemaDefaults;
17 19 use ThemeAtelier\Darkify\Admin\Views\DarkifyOptions;
18 -use ThemeAtelier\Darkify\Helpers\DarkifyExternalSupport;
19 -use ThemeAtelier\Darkify\Helpers\DarkifyUtils;
20 -use ThemeAtelier\Darkify\Helpers\Helpers;
20 +use ThemeAtelier\Darkify\Includes\DarkifyExternalSupport;
21 +use ThemeAtelier\Darkify\Includes\DarkifyUtils;
22 +use ThemeAtelier\Darkify\Admin\DBUpdates;
21 23
22 24 /**
23 25 * The admin class
24 26 */
@@ -25,17 +27,8 @@
25 27 class Admin
26 28 {
27 29
28 30 /**
29 - * The slug of this plugin.
30 - *
31 - * @since 1.0.0
32 - * @access private
33 - * @var string $plugin_slug The slug of this plugin.
34 - */
35 - private $plugin_slug;
36 -
37 - /**
38 31 * The min of this plugin.
39 32 *
40 33 * @since 1.0.0
41 34 * @access private
@@ -41,155 +34,538 @@
41 34 * @access private
42 35 * @var string $min The slug of this plugin.
43 36 */
44 37 private $min;
45 -
46 - /**
47 - * The version of this plugin.
48 - *
49 - * @since 1.0.0
50 - * @access private
51 - * @var string $version The current version of this plugin.
52 - */
53 - private $version;
54 38 public $utils;
55 39 public $settings;
56 40 public $external_support;
57 -
41 + private $plugin_name;
42 + private $version;
58 43 public $unique_id;
59 44
60 - private $plugin_name;
45 + /**
46 + * Whether enqueue_scripts() actually put the admin engine in the queue on
47 + * this request. Read by darkify_restore_no_conflict_scripts(), which must
48 + * only re-add handles Darkify itself asked for.
49 + *
50 + * @var bool
51 + */
52 + private $engine_enqueued = false;
61 53
62 54 /**
63 55 * The class constructor.
64 56 *
65 - * @param string $plugin_slug The slug of the plugin.
57 + * @param string $plugin_name The slug of the plugin.
66 58 * @param string $version Current version of the plugin.
67 59 */
68 - function __construct($plugin_slug, $version)
60 + public function __construct($plugin_name, $version)
69 61 {
62 + $this->plugin_name = $plugin_name;
63 + $this->version = $version;
70 64 $this->utils = new DarkifyUtils($this);
71 65 $this->external_support = new DarkifyExternalSupport($this);
66 + $this->min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
72 67 if (function_exists('wp_rand')) {
73 68 $this->unique_id = wp_rand();
74 69 }
70 +
71 + add_action('current_screen', function ($screen) {
72 + $options = get_option('darkify');
73 + $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
74 + $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
75 + $is_block_editor = method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
76 +
77 + // Block Editor Dark Mode is INDEPENDENT of Admin Panel Dark Mode:
78 + // the block editor darkens whenever Block Editor Dark Mode is on,
79 + // even if the rest of wp-admin stays light. Every other admin screen
80 + // still keys on Admin Panel Dark Mode.
81 + $needs_engine = $is_block_editor
82 + ? (bool) $block_editor_dark_mode
83 + : (bool) $enable_admin_panel_dark_mode;
84 +
85 + // Darkify's own settings/help screens additionally load it even while
86 + // the option is OFF, so flipping "Admin Panel Dark Mode" in the React
87 + // admin can show the admin-bar icon (and have it actually work)
88 + // immediately instead of only after a reload. Loading it here cannot
89 + // darken anything on its own: both the FOUC snippet in
90 + // header_script.php and darkify_check_preloading() in client_main.js
91 + // are gated on `darkify_admin_panel_dark_enabled`.
92 + if ($needs_engine || $this->is_darkify_spa_page()) {
93 + add_action('admin_bar_menu', array($this, 'darkify_admin_bar_switch'), 9999);
94 + add_action('admin_print_scripts', array($this, 'darkify_admin_header_script'), 1);
95 + add_action('admin_footer', array($this, 'darkify_admin_footer_script'));
96 + }
97 + });
98 +
99 + new ReviewNotice();
100 + new DBUpdates();
101 + if (! defined('THEMEATELIER_OFFER_BANNER_LOADED')) {
102 + define('THEMEATELIER_OFFER_BANNER_LOADED', true);
103 + ThemeAtelier_Offer_Banner::instance();
104 + }
105 +
106 + // Building the schema is what evaluates every `__()` in
107 + // src/Admin/Views/*.php, so the text domain has to be registered first —
108 + // Darkify::load_textdomain() runs on this same hook at priority 1.
109 + add_action('after_setup_theme', array($this, 'init_components'));
110 + add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins'));
111 +
112 + /*
113 + * "No-conflict mode" compatibility.
114 + *
115 + * Fluent Forms, FluentCRM, Fluent Booking, FluentSMTP, WP Social Ninja
116 + * and friends dequeue *every* script served from wp-content/plugins on
117 + * their own admin screens (Fluent Forms: app/Hooks/actions.php, on
118 + * `wp_print_scripts` priority 1). That runs after `admin_enqueue_scripts`,
119 + * so it strips Darkify's engine while the admin-bar node — printed by
120 + * `admin_bar_menu` and untouched by the dequeue — keeps its inline
121 + * onclick, producing "darkify_switch_trigger is not defined" and a dead
122 + * toggle on e.g. admin.php?page=fluent_forms.
123 + *
124 + * Two layers: the published allow-list filters where a plugin offers one,
125 + * and a generic re-enqueue after their sweep for the ones that don't.
126 + */
127 + add_filter('fluentform/exclude_js_slugs_from_dequeue', array($this, 'darkify_no_conflict_allow_slugs'));
128 + add_filter('fluent_crm_asset_listed_slugs', array($this, 'darkify_no_conflict_allow_slugs'));
129 + add_filter('fluent_booking/asset_listed_slugs', array($this, 'darkify_no_conflict_allow_slugs'));
130 + add_action('wp_print_scripts', array($this, 'darkify_restore_no_conflict_scripts'), 100);
131 + add_action('wp_print_styles', array($this, 'darkify_restore_no_conflict_styles'), 100);
132 +
133 + add_action('admin_init', [$this, 'load_classic_editor_scripts']);
134 + add_filter('admin_footer_text', array($this, 'darkify_admin_footer'), 1, 2);
135 + $active_plugins = get_option('active_plugins');
136 + foreach ($active_plugins as $active_plugin) {
137 + $_temp = strpos($active_plugin, 'darkify.php');
138 + if (false != $_temp) {
139 + add_filter('plugin_action_links_' . DARKIFY_BASENAME, array($this, 'add_plugin_action_links'), 10, 2);
140 + add_filter('plugin_row_meta', array($this, 'after_darkify_row_meta'), 10, 4);
141 + }
142 + }
143 + }
144 +
145 + public function init_components()
146 + {
75 147 DarkifyOptions::options('darkify');
148 + $this->seed_default_options();
149 + }
76 150
151 + /**
152 + * Write the schema's defaults into the `darkify` option on a fresh install.
153 + *
154 + * Nothing used to create that option until the first save in the admin. Up to
155 + * that point the two halves of the plugin disagreed: the React admin resolved
156 + * each field from the schema (so the Switch Toggler showed Orbit, the declared
157 + * default), while the frontend read the raw option — found nothing — and fell
158 + * back to the literal in its own template, rendering the Classic switcher. Same
159 + * class of mismatch for every other field whose template fallback differs from
160 + * its declared default.
161 + *
162 + * Seeding makes the stored values the single source both sides read, so a site
163 + * ships with exactly what the admin displays before anyone touches Settings.
164 + *
165 + * Runs on `init` (every request, admin and frontend), right after
166 + * the config classes have registered their sections, and is a no-op once the
167 + * option exists — so it never overwrites a user's saved settings, and an
168 + * upgrade from an older version is left alone.
169 + */
170 + private function seed_default_options()
171 + {
172 + $existing = get_option('darkify');
173 + if (is_array($existing) && ! empty($existing)) {
174 + return;
175 + }
176 +
177 + $defaults = SchemaDefaults::for_option('darkify');
178 + if (empty($defaults)) {
179 + return;
180 + }
181 +
182 + // `update_option` rather than `add_option`: an install that already holds
183 + // an empty/corrupt value (a stray '' or false) gets seeded too.
184 + update_option('darkify', $defaults);
185 + }
186 +
187 + /**
188 + * Whether the current screen is one of Darkify's own React SPA screens
189 + * (Settings / Get Help) — the only place the Admin Panel Dark Mode option can
190 + * be toggled, and therefore the only place that needs the engine loaded up
191 + * front so the admin-bar icon can react instantly.
192 + */
193 + public function is_darkify_spa_page(): bool
194 + {
195 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check.
196 + $page = isset($_GET['page']) ? \sanitize_key(\wp_unslash($_GET['page'])) : '';
197 + return \in_array($page, Assets::SPA_SLUGS, true);
198 + }
199 +
200 + public function darkify_exclude_js_from_cache_plugins($excludes)
201 + {
202 + // Dark Reader proxies document.styleSheets and injects its own <style>
203 + // elements; an optimizer that concatenates or defers it changes when it
204 + // sees the page's sheets, which shows up as a half-darkened admin.
205 + $excludes .= ',client_main.js,client_main.min.js'
206 + . ',darkreader.js,darkreader.min.js'
207 + . ',admin_darkreader.js,admin_darkreader.min.js';
208 + return $excludes;
209 + }
210 +
211 + public function enqueue_styles()
212 + {
77 213 $options = get_option('darkify');
78 - $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
79 - $this->plugin_name = $plugin_slug;
214 + $enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
215 + // Block Editor Dark Mode darkens the editor independently of Admin Panel
216 + // Dark Mode, so the stylesheet must load there too.
217 + $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
218 + $screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
219 + $is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
220 + $allow_admin_dark = $enable_admin_panel_dark_mode || ($is_block_editor && $block_editor_dark_mode);
221 + // Darkify's own screens always get the stylesheet so the admin-bar icon
222 + // can be revealed the moment the option is switched on. It only styles
223 + // `.darkify_*` classes, so it is inert until the engine adds them.
224 + if (($allow_admin_dark || $this->is_darkify_spa_page()) && $this->darkify_is_dark_mode_allowed()) {
225 + if (!wp_style_is('darkify-admin-switch', 'enqueued')) {
226 + 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');
227 + }
228 + }
229 + // Review notice CSS
230 + wp_enqueue_style('darkify-review-notice', DARKIFY_DIR_URL . 'src/Admin/ReviewNotice/assets/css/review-notice' . $this->min . '.css', array(), DARKIFY_VERSION, 'all');
231 + }
80 232
81 - add_filter('plugin_action_links', array($this, 'add_plugin_action_links'), 10, 2);
233 + public function enqueue_scripts($hook)
234 + {
235 + $options = get_option('darkify');
236 + $enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
237 + $is_spa = $this->is_darkify_spa_page();
238 + if (! $this->darkify_is_dark_mode_allowed()) {
239 + return;
240 + }
82 241
83 - add_action('admin_enqueue_scripts', array($this, 'darkify_admin_enqueue'));
242 + // Mirror the `current_screen` engine-load rule exactly, so the engine is
243 + // only enqueued where its inline config (header_script.php) is ALSO
244 + // printed. Block Editor Dark Mode is independent of Admin Panel Dark
245 + // Mode: the block editor loads the engine when Block Editor Dark Mode is
246 + // on (regardless of Admin Panel Dark Mode); every other admin screen
247 + // keys on Admin Panel Dark Mode. (A SPA screen always gets the config.)
248 + if (! $is_spa) {
249 + $screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
250 + $is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
251 + $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
252 + $allow = $is_block_editor ? (bool) $block_editor_dark_mode : (bool) $enable_admin_panel_dark_mode;
253 + if (! $allow) {
254 + return;
255 + }
256 + }
84 257
85 - if ($enable_admin_panel_dark_mode) {
86 - add_action('admin_bar_menu', array($this, 'darkify_admin_bar_switch'), 9999);
87 - add_action('admin_print_scripts', array($this, 'darkify_admin_header_script'), 1);
88 - add_action('admin_footer', array($this, 'darkify_admin_footer_script'));
258 + /*
259 + * wp-admin runs the Dark Reader engine, NOT the per-element classifier in
260 + * client_main.js. That engine walks every element and reads
261 + * getComputedStyle on each, then re-walks on DOM mutation; wp-admin (the
262 + * block editor especially) mutates continuously, so it never settles and
263 + * the editor locks up. Dark Reader works per stylesheet instead, so the
264 + * cost does not scale with element churn at all.
265 + *
266 + * The frontend is untouched and still runs client_main.js.
267 + * See src/assets/js/admin_darkreader.js for the full rationale.
268 + */
269 + $darkify_lib_rel = 'src/assets/js/vendor/darkreader.min.js';
270 + $darkify_engine_rel = 'src/assets/js/admin_darkreader' . $this->min . '.js';
271 + $darkify_lib_url = DARKIFY_DIR_URL . $darkify_lib_rel;
272 +
273 + /*
274 + * Both in <head> (in_footer = false), deliberately. Dark Reader does not
275 + * need a parsed body to start, and enabling it before first paint is what
276 + * keeps the admin from flashing light and then darkening.
277 + */
278 + wp_enqueue_script(
279 + 'darkify-darkreader',
280 + $darkify_lib_url,
281 + array(),
282 + $this->darkify_asset_version($darkify_lib_rel),
283 + false
284 + );
285 +
286 + /*
287 + * The block editor canvas and the classic editor's TinyMCE body are
288 + * separate documents, and Dark Reader binds the realm it was loaded into.
289 + * The engine injects its own copy into each same-origin frame, so it
290 + * needs a URL it can point a <script> at from inside that frame.
291 + *
292 + * Darkify's own React settings screens are excluded from Dark Reader:
293 + * they ship a full dark theme of their own (`.dark` in
294 + * darkify-react/src/index.css) which also covers the surrounding wp-admin
295 + * chrome, so darkening them again produces a double-dark UI — and because
296 + * Dark Reader 4.9 cannot parse the `oklch()` values those tokens use, the
297 + * unresolved properties settle on `--destructive` and paint card borders
298 + * and toggles red. The engine still loads there: it owns the toggle the
299 + * admin-bar icon calls, and the SPA mirrors the class onto `.dark` itself.
300 + */
301 + wp_add_inline_script(
302 + 'darkify-darkreader',
303 + 'window.darkifyDarkReaderSrc = ' . wp_json_encode($darkify_lib_url) . ';'
304 + . 'window.darkifyAdminSelfThemed = ' . ($is_spa ? 'true' : 'false') . ';',
305 + 'before'
306 + );
307 +
308 + wp_enqueue_script(
309 + 'darkify-admin-engine',
310 + DARKIFY_DIR_URL . $darkify_engine_rel,
311 + array('darkify-darkreader'),
312 + $this->darkify_asset_version($darkify_engine_rel),
313 + false
314 + );
315 +
316 + $this->engine_enqueued = true;
317 + }
318 +
319 + /**
320 + * Add Darkify to a no-conflict allow-list.
321 + *
322 + * Both filter shapes this is attached to pass an array of substrings matched
323 + * against the script `src`, so the plugin folder name is the right token.
324 + *
325 + * @param array $slugs Allowed slugs.
326 + *
327 + * @return array
328 + */
329 + public function darkify_no_conflict_allow_slugs($slugs)
330 + {
331 + if (! is_array($slugs)) {
332 + return $slugs;
89 333 }
90 334
91 - add_action('admin_menu', array($this, 'add_plugin_page'));
92 - new TADiscountPage();
335 + $slugs[] = 'darkify';
336 +
337 + return array_unique($slugs);
93 338 }
94 339
95 - public function enqueue_styles()
340 + /**
341 + * Put the admin engine back after a no-conflict sweep dropped it.
342 + *
343 + * Runs on `wp_print_scripts` at 100 — after the sweeps, which all hook it at
344 + * 1 — and before `do_items()`, so a re-enqueue here still prints. Gated on
345 + * `$engine_enqueued`, so it never resurrects a script Darkify itself chose
346 + * not to load (or dequeued in admin_dequeue_for_specefic_pages).
347 + */
348 + public function darkify_restore_no_conflict_scripts()
96 349 {
97 - wp_enqueue_style('ico-font', DARKIFY_DIR_URL . 'src/assets/css/icofont.css', array(), DARKIFY_VERSION, 'all');
98 - wp_enqueue_style('darkify-admin', DARKIFY_DIR_URL . 'src/Admin/assets/css/darkify-admin.css', array(), DARKIFY_VERSION, 'all');
350 + if (! $this->engine_enqueued || ! is_admin()) {
351 + return;
352 + }
353 +
354 + foreach (array('darkify-darkreader', 'darkify-admin-engine') as $handle) {
355 + if (wp_script_is($handle, 'registered') && ! wp_script_is($handle, 'enqueued')) {
356 + wp_enqueue_script($handle);
357 + }
358 + }
99 359 }
100 360
101 - public function enqueue_scripts($hook)
361 + /**
362 + * Same sweep, styles side: the engine adds `.darkify_dark_mode_enabled` and
363 + * the switch classes, and client_main.css is what they mean. Losing only the
364 + * stylesheet leaves a half-styled admin bar icon.
365 + */
366 + public function darkify_restore_no_conflict_styles()
102 367 {
103 - if ($hook !== 'toplevel_page_darkify') {
368 + if (! $this->engine_enqueued || ! is_admin()) {
104 369 return;
105 370 }
106 - wp_enqueue_script('darkify-admin', DARKIFY_DIR_URL . 'src/Admin/assets/js/darkify-admin.js', array(), DARKIFY_VERSION, 'all');
371 +
372 + if (wp_style_is('darkify-admin-switch', 'registered') && ! wp_style_is('darkify-admin-switch', 'enqueued')) {
373 + wp_enqueue_style('darkify-admin-switch');
374 + }
107 375 }
108 376
109 - public function add_plugin_action_links($links, $file)
377 + /**
378 + * Cache-busting version for a bundled asset: the plugin version plus the
379 + * file's mtime, so rebuilding client_main.js/.css (which does NOT bump the
380 + * plugin version) still changes the URL and browsers fetch the new file
381 + * instead of serving a stale one. Mirrors Admin\Assets::asset_version().
382 + */
383 + public function darkify_asset_version(string $relative_path): string
110 384 {
111 - if (DARKIFY_BASENAME === $file) {
112 - $new_links = array(
113 - sprintf('<a href="%s">%s</a>', admin_url('admin.php?page=darkify#tab=control'), __('Settings', 'darkify')),
114 - );
115 - return array_merge($new_links, $links);
385 + $abs = DARKIFY_PATH . $relative_path;
386 + $mtime = \is_readable($abs) ? \filemtime($abs) : false;
387 + return $mtime ? DARKIFY_VERSION . '.' . $mtime : DARKIFY_VERSION;
388 + }
389 +
390 + /*
391 + Dequeue Darkify scripts and styles on specific admin pages (e.g., MainWP pages)
392 + */
393 +
394 + public function admin_dequeue_for_specefic_pages()
395 + {
396 + $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
397 + $dequeue_pages = array(
398 + 'mainwp_tab',
399 + 'managesites',
400 + 'ManageClients',
401 + 'CostSummary',
402 + 'InsightsOverview',
403 + 'Extensions',
404 + 'RESTAPI',
405 + 'mainwp-setup',
406 + 'ThemesManage',
407 + 'PluginsManage',
408 + 'InsightsManage',
409 + 'ManageGroups',
410 + 'UpdatesManage',
411 + 'PluginsInstall',
412 + 'PluginsAutoUpdate',
413 + 'PluginsIgnore',
414 + 'PluginsAbandoned',
415 + 'PluginsIgnoredAbandoned',
416 + 'ThemesInstall',
417 + 'ThemesAutoUpdate',
418 + 'ThemesIgnore',
419 + 'ThemesAbandoned',
420 + 'ThemesIgnoredAbandoned',
421 + 'UserBulkManage',
422 + 'UserBulkAdd',
423 + 'BulkImportUsers',
424 + 'UpdateAdminPasswords',
425 + 'PostBulkManage',
426 + 'Extensions-Mainwp-Backups',
427 + 'Extensions-Mainwp-Security',
428 + 'Extensions-Mainwp-Analytics',
429 + 'Mainwp-Monitoring',
430 + 'Extensions-Mainwp-Agency',
431 + 'Extensions-Mainwp-Administrative',
432 + 'Extensions-Mainwp-Development',
433 + 'Extensions-Mainwp-Performance',
434 + 'ClientAddNew',
435 + 'ClientImport',
436 + 'ManageCostTracker',
437 + 'CostTrackerAdd',
438 + 'AddApiKeys',
439 + );
440 +
441 +
442 + if (in_array($page, $dequeue_pages, true)) {
443 + wp_deregister_style('darkify-admin-switch');
444 + wp_dequeue_script('darkify-admin-client-main');
445 + wp_dequeue_script('darkify-admin-engine');
446 + wp_dequeue_script('darkify-darkreader');
447 + // Keep darkify_restore_no_conflict_scripts() from undoing this.
448 + $this->engine_enqueued = false;
116 449 }
117 - return $links;
118 450 }
119 451
120 - public function darkify_admin_enqueue($page)
452 + /**
453 + * Replace wp-admin's footer credit with Darkify's review request, on
454 + * Darkify's own screens only.
455 + *
456 + * Screens are matched by page slug (`is_darkify_spa_page()`) rather than by
457 + * a hardcoded screen id, so both the Settings and Get Help SPA screens match
458 + * and the check keeps working if the menu is ever retitled. The slug list is
459 + * the same source of truth the assets already load from.
460 + *
461 + * @param string $text Existing footer text.
462 + *
463 + * @return string
464 + */
465 + public function darkify_admin_footer($text)
121 466 {
122 - $options = get_option('darkify');
123 - $enable_default_dark_mode = $options['enable_default_dark_mode'];
124 - $enable_time = $options['enable_time'];
125 - $enable_time_based_dark = isset($enable_time['enable_time_based_dark']) ? $enable_time['enable_time_based_dark'] : "";
126 - $time_based_dark_start = isset($enable_time['time_based_dark_start']['from']) ? $enable_time['time_based_dark_start']['from'] : "";
127 - $time_based_dark_stop = isset($enable_time['time_based_dark_start']['to']) ? $enable_time['time_based_dark_start']['to'] : "";
128 - $grayscale = $options['brightness'];
129 - $enable_low_image_brightness = isset($grayscale['enable_low_image_brightness']) ? $grayscale['enable_low_image_brightness'] : "";
130 - $low_image_brightness_label = isset($grayscale['low_image_brightness_label']) ? $grayscale['low_image_brightness_label'] : "";
131 - $grayscale = isset($options['grayscale']);
132 - $enable_image_grayscale = isset($grayscale['enable_image_grayscale']) ? $grayscale['enable_image_grayscale'] : "";
133 - $image_grayscale_label = isset($grayscale['image_grayscale_label']) ? $grayscale['image_grayscale_label'] : "";
134 - $disallowed_low_grayscale_images = isset($grayscale['disallowed_low_grayscale_images']) ? $grayscale['disallowed_low_grayscale_images'] : "";
135 - $darken_background = isset($options['darken_background']);
136 - $enable_low_image_darken = isset($darken_background['enable_low_image_darken']) ? $darken_background['enable_low_image_darken'] : "";
137 - $low_image_darken_label = isset($darken_background['low_image_darken_label']) ? $darken_background['low_image_darken_label'] : "";
138 - $video_brightness = isset($options['video_brightness']);
139 - $enable_low_video_brightness = isset($video_brightness['enable_low_video_brightness']) ? $video_brightness['enable_low_video_brightness'] : "";
140 - $low_video_brightness_label = isset($video_brightness['low_video_brightness_label']) ? $video_brightness['low_video_brightness_label'] : "";
141 - $video_grayscale = isset($options['video_grayscale']);
142 - $enable_video_grayscale = isset($video_grayscale['enable_video_grayscale']) ? $video_grayscale['enable_video_grayscale'] : "";
467 + if (! $this->is_darkify_spa_page()) {
468 + return $text;
469 + }
143 470
144 - if ($options["enable_admin_panel_dark_mode"]) {
145 - if ($this->darkify_is_dark_mode_allowed()) {
146 - if (!wp_style_is('darkify-admin-switch', 'enqueued')) {
147 - wp_enqueue_style('darkify-admin-switch', DARKIFY_DIR_URL . 'src/assets/css/client_main.css', array(), DARKIFY_VERSION, 'all');
148 - }
149 - wp_enqueue_script('darkify-admin-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main.js', array(), DARKIFY_VERSION);
471 + return sprintf(
472 + /* translators: 1: start strong tag, 2: close strong tag. 3: start link 4: close link */
473 + __('<i>Enjoying %1$sDarkify?%2$s Please rate us %3$sWordPress.org%4$s. Your positive feedback will help us grow more. Thank you! 😊</i>', 'darkify'),
474 + '<strong>',
475 + '</strong>',
476 + '<span class="greet-footer-text-star">★★★★★</span> <a href="https://wordpress.org/support/plugin/darkify/reviews/#new-post" target="_blank" rel="noopener noreferrer">',
477 + '</a>'
478 + );
479 + }
150 480
151 - $darkify_data = array(
152 - 'darkify_switch_unique_id' => $this->unique_id,
153 - 'darkify_is_this_admin_panel' => is_admin() ? "1" : "0",
154 - 'darkify_enable_default_dark_mode' => $enable_default_dark_mode,
155 - 'darkify_enable_os_aware' => $options["enable_os_aware"],
156 - 'darkify_enable_keyboard_shortcut' => $options["enable_keyboard_shortcut"],
157 - 'darkify_enable_time_based_dark' => $enable_time_based_dark,
158 - 'darkify_time_based_dark_start' => $time_based_dark_start ? $time_based_dark_start : "19:00",
159 - 'darkify_time_based_dark_stop' => $time_based_dark_stop ? $time_based_dark_stop : "07:00",
160 - 'darkify_alternative_dark_mode_switch' => $options["alternative_dark_mode_switcher"],
161 - 'darkify_enable_low_image_brightness' => $enable_low_image_brightness,
162 - 'darkify_image_brightness_to' => $low_image_brightness_label,
163 - 'darkify_enable_image_grayscale' => $enable_image_grayscale,
164 - 'darkify_image_grayscale_to' => $image_grayscale_label,
165 - 'darkify_disallowed_grayscale_images' => $disallowed_low_grayscale_images,
166 - 'darkify_enable_bg_image_darken' => $enable_low_image_darken,
167 - 'darkify_bg_image_darken_to' => $low_image_darken_label,
168 - 'darkify_enable_invert_inline_svg' => $options["enable_invert_inline_svg"],
169 - 'darkify_enable_low_video_brightness' => $enable_low_video_brightness,
170 - 'darkify_video_brightness_to' => $low_video_brightness_label,
171 - 'darkify_enable_video_grayscale' => $enable_video_grayscale,
172 - // Add more variables as needed...
173 - );
174 - wp_localize_script('darkify-admin-client-main', 'darkify_data', $darkify_data);
175 - }
481 + // Plugin settings in plugin list
482 + public function add_plugin_action_links(array $links)
483 + {
484 + $new_links = array(
485 + sprintf('<a href="' . esc_url(admin_url('admin.php?page=darkify')) . '">' . esc_html__('Settings', 'darkify') . '</a>'),
486 + sprintf('<a target="_blank" href="https://wordpress.org/support/plugin/darkify/">' . esc_html__('Support', 'darkify') . '</a>'),
487 + );
488 +
489 + $links[] = sprintf('<a style="font-weight: bold;color:#35b747" target="_blank" href="https://darkifywp.com/pricing/?utm_source=darkify_plugin&utm_medium=action_link&utm_campaign=regular">%s</a>', esc_html__('Go Pro!', 'darkify'));
490 +
491 + return array_merge($new_links, $links);
492 + }
493 +
494 + /**
495 + * Add plugin row meta link.
496 + *
497 + * @since 2.0
498 + *
499 + * @param array $plugin_meta .
500 + * @param string $file .
501 + *
502 + * @return array
503 + */
504 + public function after_darkify_row_meta($plugin_meta, $file)
505 + {
506 +
507 + if (DARKIFY_BASENAME === $file) {
508 + $plugin_meta[] = '<a href="' . DARKIFY_DEMO_URL . '" target="_blank">' . __('Live Demo', 'darkify') . '</a>';
176 509 }
510 +
511 + return $plugin_meta;
177 512 }
178 513
179 514 public function darkify_admin_bar_switch($wp_admin_bar)
180 515 {
181 - if ($this->darkify_is_dark_mode_allowed()) {
182 - $args = array(
183 - 'parent' => 'top-secondary',
184 - 'id' => 'darkify_admin_bar_switch_container',
185 - 'meta' => array(
186 - 'class' => 'darkify_admin_bar_switch_container',
187 - 'onclick' => 'darkify_switch_trigger()',
188 - )
189 - );
190 - $wp_admin_bar->add_node($args);
516 + if (! $this->darkify_is_dark_mode_allowed()) {
517 + return;
191 518 }
519 +
520 + /*
521 + * The block editor already carries its own toggle in the top toolbar
522 + * (see Frontend/Templates/footer_script.php), firing the same
523 + * darkify_switch_trigger() this node would — two controls for one state,
524 + * side by side.
525 + *
526 + * The toolbar one is the keeper, not this one: Gutenberg hides the WP
527 + * admin bar in fullscreen mode, which is the default, so this node is
528 + * simply absent for most people editing a post. Dropping the toolbar
529 + * button instead would leave them no toggle at all.
530 + *
531 + * The classic editor is deliberately not included here. Its TinyMCE moon
532 + * toggles only the content frame, while this switch toggles the whole
533 + * admin — two different controls, so both belong on screen.
534 + */
535 + $screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
536 + if ($screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor()) {
537 + return;
538 + }
539 +
540 + $options = get_option('darkify');
541 + $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
542 +
543 + // Everywhere but Darkify's own screens the node is only registered when
544 + // the option is on, so it is always visible. On the SPA screens it is
545 + // registered regardless and starts hidden while the option is off — the
546 + // React admin then just flips this class (see WpAdminBarBridge), which is
547 + // what makes the icon appear/disappear without a reload.
548 + $classes = 'darkify_admin_bar_switch_container';
549 + if (! $enable_admin_panel_dark_mode) {
550 + $classes .= ' darkify_admin_bar_hidden';
551 + }
552 +
553 + $wp_admin_bar->add_node(array(
554 + 'parent' => 'top-secondary',
555 + 'id' => 'darkify_admin_bar_switch_container',
556 + 'meta' => array(
557 + 'class' => $classes,
558 + // Guarded call: a plugin's "no-conflict mode" can still strip the
559 + // engine on its own screens (see the compatibility hooks in the
560 + // constructor). A missing function should be an inert click, not
561 + // an uncaught ReferenceError in every visitor's console.
562 + // No quotes in the expression: the admin bar escapes `meta`
563 + // attributes, and the escaped quotes come back as literal
564 + // entities inside the handler ("Invalid or unexpected token").
565 + 'onclick' => 'window.darkify_switch_trigger&&window.darkify_switch_trigger()',
566 + ),
567 + ));
192 568 }
193 569
194 570 public function darkify_is_dark_mode_allowed()
195 571 {
@@ -194,9 +570,14 @@
194 570 public function darkify_is_dark_mode_allowed()
195 571 {
196 572
197 573 $options = get_option('darkify');
198 - $disallowed_admin_pages = isset($options["disallowed_admin_pages"]) ? $options["disallowed_admin_pages"] : "";
574 + $options = is_array($options) ? $options : array();
575 + // Guarded read: `disallowed_admin_pages` has no schema default, so the
576 + // React settings save doesn't persist it until the field is used. Reading
577 + // it unguarded would emit an "Undefined array key" warning on every
578 + // dark-mode hook (see the Pro plugin's Admin.php for the full story).
579 + $disallowed_admin_pages = isset($options['disallowed_admin_pages']) ? $options['disallowed_admin_pages'] : '';
199 580 /* Disable on Disallowed Admin Plugins */
200 581 if ($this->utils->isRestrictedByDisallowedAdminPages($disallowed_admin_pages)) {
201 582 return False;
202 583 }
@@ -205,9 +586,9 @@
205 586
206 587 public function darkify_admin_header_script()
207 588 {
208 589 if ($this->darkify_is_dark_mode_allowed()) {
209 - include_once Helpers::darkify_locate_template('header_script.php');
590 + include_once DarkifyUtils::darkify_locate_template('header_script.php');
210 591 }
211 592 }
212 593
213 594 public function darkify_admin_footer_script()
@@ -212,44 +593,43 @@
212 593
213 594 public function darkify_admin_footer_script()
214 595 {
215 596 if ($this->darkify_is_dark_mode_allowed()) {
216 - include_once Helpers::darkify_locate_template('footer_script.php');
597 + include_once DarkifyUtils::darkify_locate_template('footer_script.php');
217 598 }
218 599 }
219 600
220 - public function add_plugin_page()
601 + /**
602 + * Load classic editor scripts.
603 + *
604 + * @since 5.0.0
605 + */
606 + public function load_classic_editor_scripts()
221 607 {
222 - // This page will be under "Settings"
223 - add_menu_page(
224 - esc_html__('Darkify', 'darkify'),
225 - esc_html__('Darkify', 'darkify'),
226 - 'manage_options',
227 - 'darkify',
228 - 'darkify_settings',
229 - 'data:image/svg+xml;base64,' . base64_encode('<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
230 - <path d="M23.5 1.5C23.5 0.671573 22.8284 0 22 0C21.1716 0 20.5 0.671573 20.5 1.5V4.5C20.5 5.32843 21.1716 6 22 6C22.8284 6 23.5 5.32843 23.5 4.5V1.5Z" fill="white" />
231 - <path d="M34 22C34 28.6274 28.6274 34 22 34C15.3726 34 10 28.6274 10 22C10 15.3726 15.3726 10 22 10C28.6274 10 34 15.3726 34 22Z" fill="white" />
232 - <path d="M22 38C22.8284 38 23.5 38.6716 23.5 39.5V42.5C23.5 43.3284 22.8284 44 22 44C21.1716 44 20.5 43.3284 20.5 42.5V39.5C20.5 38.6716 21.1716 38 22 38Z" fill="white" />
233 - <path d="M10.6863 10.6863C10.1005 11.2721 9.15074 11.2721 8.56495 10.6863L6.44363 8.56498C5.85784 7.97919 5.85784 7.02945 6.44363 6.44366C7.02942 5.85787 7.97916 5.85787 8.56495 6.44366L10.6863 8.56498C11.2721 9.15077 11.2721 10.1005 10.6863 10.6863Z" fill="white" />
234 - <path d="M35.435 37.5564C36.0208 38.1421 36.9705 38.1421 37.5563 37.5564C38.1421 36.9706 38.1421 36.0208 37.5563 35.435L35.435 33.3137C34.8492 32.7279 33.8995 32.7279 33.3137 33.3137C32.7279 33.8995 32.7279 34.8493 33.3137 35.435L35.435 37.5564Z" fill="white" />
235 - <path d="M10.6863 33.3137C11.2721 33.8995 11.2721 34.8492 10.6863 35.435L8.56495 37.5563C7.97916 38.1421 7.02942 38.1421 6.44363 37.5563C5.85784 36.9706 5.85784 36.0208 6.44363 35.435L8.56495 33.3137C9.15074 32.7279 10.1005 32.7279 10.6863 33.3137Z" fill="white" />
236 - <path d="M37.5563 8.56496C38.1421 7.97918 38.1421 7.02943 37.5563 6.44364C36.9705 5.85786 36.0208 5.85786 35.435 6.44364L33.3137 8.56496C32.7279 9.15075 32.7279 10.1005 33.3137 10.6863C33.8995 11.2721 34.8492 11.2721 35.435 10.6863L37.5563 8.56496Z" fill="white" />
237 - <path d="M6 22C6 22.8284 5.32843 23.5 4.5 23.5H1.5C0.671573 23.5 0 22.8284 0 22C0 21.1716 0.671573 20.5 1.5 20.5H4.5C5.32843 20.5 6 21.1716 6 22Z" fill="white" />
238 - <path d="M42.5 23.5C43.3284 23.5 44 22.8284 44 22C44 21.1716 43.3284 20.5 42.5 20.5H39.5C38.6716 20.5 38 21.1716 38 22C38 22.8284 38.6716 23.5 39.5 23.5H42.5Z" fill="white" />
239 -</svg>'),
240 - 9
241 - );
242 - do_action('darkify_before_upgrade_pro_menu');
243 - add_submenu_page(
244 - 'darkify',
245 - __(
246 - '👑 Upgrade to Pro!',
247 - 'darkify'
248 - ),
249 - sprintf('<span style="color: #35b747;font-weight: bold;" class="darkify-get-pro-text">%s</span>', __('👑 Upgrade to Pro!', 'darkify')),
250 - 'manage_options',
251 - 'https://1.envato.market/darkify'
252 - );
253 - do_action('darkify_after_upgrade_pro_menu');
608 + $options = get_option('darkify');
609 + $classic_editor_dark_mode = isset($options['classic_editor_dark_mode']) ? $options['classic_editor_dark_mode'] : '';
610 + if (! current_user_can('edit_posts') && ! current_user_can('edit_pages')) {
611 + return;
612 + }
613 + if (get_user_option('rich_editing') !== 'true') {
614 + return;
615 + }
616 +
617 + // Bail if admin_classic_editor is not enabled.
618 + if (! wp_validate_boolean($classic_editor_dark_mode)) {
619 + return;
620 + }
621 +
622 + add_filter('mce_external_plugins', function ($plugins) {
623 + // Honour the same debug switch as every other bundled asset; gulp
624 + // builds the .min beside it and this was the one enqueue still
625 + // pointing at the readable copy in production.
626 + $plugins['darkify_button'] = plugins_url('src/assets/js/admin-classic-editor' . $this->min . '.js', DARKIFY_FILE);
627 + return $plugins;
628 + });
629 +
630 + add_filter('mce_buttons', function ($buttons) {
631 + $buttons[] = 'darkify_button';
632 + return $buttons;
633 + });
254 634 }
255 635 }