PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.0.3
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.0.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 1.3.1 All 56 releases
darkify / src / Admin / Admin.php

Admin.php in Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) 2.0.3, at src/Admin/Admin.php

460 lines 18.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The admin-facing functionality of the plugin.
5 *
6 * @link https://themeatelier.net
7 * @since 1.0.0
8 *
9 * @package darkify
10 * @subpackage darkify/Admin
11 * @author ThemeAtelier<themeatelierbd@gmail.com>
12 */
13
14 namespace ThemeAtelier\Darkify\Admin;
15
16 use ThemeAtelier\Darkify\Admin\ReviewNotice\ReviewNotice;
17 use ThemeAtelier\Darkify\Admin\ReviewNotice\ThemeAtelier_Offer_Banner;
18 use ThemeAtelier\Darkify\Admin\Schema\SchemaDefaults;
19 use ThemeAtelier\Darkify\Admin\Views\DarkifyOptions;
20 use ThemeAtelier\Darkify\Includes\DarkifyExternalSupport;
21 use ThemeAtelier\Darkify\Includes\DarkifyUtils;
22 use ThemeAtelier\Darkify\Admin\DBUpdates;
23
24 /**
25 * The admin class
26 */
27 class Admin
28 {
29
30 /**
31 * The min of this plugin.
32 *
33 * @since 1.0.0
34 * @access private
35 * @var string $min The slug of this plugin.
36 */
37 private $min;
38 public $utils;
39 public $settings;
40 public $external_support;
41 private $plugin_name;
42 private $version;
43 public $unique_id;
44
45 /**
46 * The class constructor.
47 *
48 * @param string $plugin_name The slug of the plugin.
49 * @param string $version Current version of the plugin.
50 */
51 public function __construct($plugin_name, $version)
52 {
53 $this->plugin_name = $plugin_name;
54 $this->version = $version;
55 $this->utils = new DarkifyUtils($this);
56 $this->external_support = new DarkifyExternalSupport($this);
57 $this->min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
58 if (function_exists('wp_rand')) {
59 $this->unique_id = wp_rand();
60 }
61
62 add_action('current_screen', function ($screen) {
63 $options = get_option('darkify');
64 $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
65 $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
66 $is_block_editor = method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
67
68 // Block Editor Dark Mode is INDEPENDENT of Admin Panel Dark Mode:
69 // the block editor darkens whenever Block Editor Dark Mode is on,
70 // even if the rest of wp-admin stays light. Every other admin screen
71 // still keys on Admin Panel Dark Mode.
72 $needs_engine = $is_block_editor
73 ? (bool) $block_editor_dark_mode
74 : (bool) $enable_admin_panel_dark_mode;
75
76 // Darkify's own settings/help screens additionally load it even while
77 // the option is OFF, so flipping "Admin Panel Dark Mode" in the React
78 // admin can show the admin-bar icon (and have it actually work)
79 // immediately instead of only after a reload. Loading it here cannot
80 // darken anything on its own: both the FOUC snippet in
81 // header_script.php and darkify_check_preloading() in client_main.js
82 // are gated on `darkify_admin_panel_dark_enabled`.
83 if ($needs_engine || $this->is_darkify_spa_page()) {
84 add_action('admin_bar_menu', array($this, 'darkify_admin_bar_switch'), 9999);
85 add_action('admin_print_scripts', array($this, 'darkify_admin_header_script'), 1);
86 add_action('admin_footer', array($this, 'darkify_admin_footer_script'));
87 }
88 });
89
90 new ReviewNotice();
91 new DBUpdates();
92 if (! defined('THEMEATELIER_OFFER_BANNER_LOADED')) {
93 define('THEMEATELIER_OFFER_BANNER_LOADED', true);
94 ThemeAtelier_Offer_Banner::instance();
95 }
96
97 // Building the schema is what evaluates every `__()` in
98 // src/Admin/Views/*.php, so the text domain has to be registered first —
99 // Darkify::load_textdomain() runs on this same hook at priority 1.
100 add_action('after_setup_theme', array($this, 'init_components'));
101 add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins'));
102 add_action('admin_init', [$this, 'load_classic_editor_scripts']);
103 add_filter('admin_footer_text', array($this, 'darkify_admin_footer'), 1, 2);
104 $active_plugins = get_option('active_plugins');
105 foreach ($active_plugins as $active_plugin) {
106 $_temp = strpos($active_plugin, 'darkify.php');
107 if (false != $_temp) {
108 add_filter('plugin_action_links_' . DARKIFY_BASENAME, array($this, 'add_plugin_action_links'), 10, 2);
109 add_filter('plugin_row_meta', array($this, 'after_darkify_row_meta'), 10, 4);
110 }
111 }
112 }
113
114 public function init_components()
115 {
116 DarkifyOptions::options('darkify');
117 $this->seed_default_options();
118 }
119
120 /**
121 * Write the schema's defaults into the `darkify` option on a fresh install.
122 *
123 * Nothing used to create that option until the first save in the admin. Up to
124 * that point the two halves of the plugin disagreed: the React admin resolved
125 * each field from the schema (so the Switch Toggler showed Orbit, the declared
126 * default), while the frontend read the raw option — found nothing — and fell
127 * back to the literal in its own template, rendering the Classic switcher. Same
128 * class of mismatch for every other field whose template fallback differs from
129 * its declared default.
130 *
131 * Seeding makes the stored values the single source both sides read, so a site
132 * ships with exactly what the admin displays before anyone touches Settings.
133 *
134 * Runs on `init` (every request, admin and frontend), right after
135 * the config classes have registered their sections, and is a no-op once the
136 * option exists — so it never overwrites a user's saved settings, and an
137 * upgrade from an older version is left alone.
138 */
139 private function seed_default_options()
140 {
141 $existing = get_option('darkify');
142 if (is_array($existing) && ! empty($existing)) {
143 return;
144 }
145
146 $defaults = SchemaDefaults::for_option('darkify');
147 if (empty($defaults)) {
148 return;
149 }
150
151 // `update_option` rather than `add_option`: an install that already holds
152 // an empty/corrupt value (a stray '' or false) gets seeded too.
153 update_option('darkify', $defaults);
154 }
155
156 /**
157 * Whether the current screen is one of Darkify's own React SPA screens
158 * (Settings / Get Help) — the only place the Admin Panel Dark Mode option can
159 * be toggled, and therefore the only place that needs the engine loaded up
160 * front so the admin-bar icon can react instantly.
161 */
162 public function is_darkify_spa_page(): bool
163 {
164 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check.
165 $page = isset($_GET['page']) ? \sanitize_key(\wp_unslash($_GET['page'])) : '';
166 return \in_array($page, Assets::SPA_SLUGS, true);
167 }
168
169 public function darkify_exclude_js_from_cache_plugins($excludes)
170 {
171 $excludes .= ',client_main.js,client_main.min.js';
172 return $excludes;
173 }
174
175 public function enqueue_styles()
176 {
177 $options = get_option('darkify');
178 $enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
179 // Block Editor Dark Mode darkens the editor independently of Admin Panel
180 // Dark Mode, so the stylesheet must load there too.
181 $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
182 $screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
183 $is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
184 $allow_admin_dark = $enable_admin_panel_dark_mode || ($is_block_editor && $block_editor_dark_mode);
185 // Darkify's own screens always get the stylesheet so the admin-bar icon
186 // can be revealed the moment the option is switched on. It only styles
187 // `.darkify_*` classes, so it is inert until the engine adds them.
188 if (($allow_admin_dark || $this->is_darkify_spa_page()) && $this->darkify_is_dark_mode_allowed()) {
189 if (!wp_style_is('darkify-admin-switch', 'enqueued')) {
190 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');
191 }
192 }
193 // Review notice CSS
194 wp_enqueue_style('darkify-review-notice', DARKIFY_DIR_URL . 'src/Admin/ReviewNotice/assets/css/review-notice' . $this->min . '.css', array(), DARKIFY_VERSION, 'all');
195 }
196
197 public function enqueue_scripts($hook)
198 {
199 $options = get_option('darkify');
200 $enable_admin_panel_dark_mode = isset($options["enable_admin_panel_dark_mode"]) ? $options["enable_admin_panel_dark_mode"] : false;
201 $is_spa = $this->is_darkify_spa_page();
202 if (! $this->darkify_is_dark_mode_allowed()) {
203 return;
204 }
205
206 // Mirror the `current_screen` engine-load rule exactly, so the engine is
207 // only enqueued where its inline config (header_script.php) is ALSO
208 // printed. Block Editor Dark Mode is independent of Admin Panel Dark
209 // Mode: the block editor loads the engine when Block Editor Dark Mode is
210 // on (regardless of Admin Panel Dark Mode); every other admin screen
211 // keys on Admin Panel Dark Mode. (A SPA screen always gets the config.)
212 if (! $is_spa) {
213 $screen = \function_exists('get_current_screen') ? \get_current_screen() : null;
214 $is_block_editor = $screen && \method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
215 $block_editor_dark_mode = isset($options['block_editor_dark_mode']) ? $options['block_editor_dark_mode'] : false;
216 $allow = $is_block_editor ? (bool) $block_editor_dark_mode : (bool) $enable_admin_panel_dark_mode;
217 if (! $allow) {
218 return;
219 }
220 }
221
222 wp_enqueue_script(
223 'darkify-admin-client-main',
224 DARKIFY_DIR_URL . 'src/assets/js/client_main' . $this->min . '.js',
225 array(),
226 $this->darkify_asset_version('src/assets/js/client_main' . $this->min . '.js')
227 );
228 }
229
230 /**
231 * Cache-busting version for a bundled asset: the plugin version plus the
232 * file's mtime, so rebuilding client_main.js/.css (which does NOT bump the
233 * plugin version) still changes the URL and browsers fetch the new file
234 * instead of serving a stale one. Mirrors Admin\Assets::asset_version().
235 */
236 public function darkify_asset_version(string $relative_path): string
237 {
238 $abs = DARKIFY_PATH . $relative_path;
239 $mtime = \is_readable($abs) ? \filemtime($abs) : false;
240 return $mtime ? DARKIFY_VERSION . '.' . $mtime : DARKIFY_VERSION;
241 }
242
243 /*
244 Dequeue Darkify scripts and styles on specific admin pages (e.g., MainWP pages)
245 */
246
247 public function admin_dequeue_for_specefic_pages()
248 {
249 $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
250 $dequeue_pages = array(
251 'mainwp_tab',
252 'managesites',
253 'ManageClients',
254 'CostSummary',
255 'InsightsOverview',
256 'Extensions',
257 'RESTAPI',
258 'mainwp-setup',
259 'ThemesManage',
260 'PluginsManage',
261 'InsightsManage',
262 'ManageGroups',
263 'UpdatesManage',
264 'PluginsInstall',
265 'PluginsAutoUpdate',
266 'PluginsIgnore',
267 'PluginsAbandoned',
268 'PluginsIgnoredAbandoned',
269 'ThemesInstall',
270 'ThemesAutoUpdate',
271 'ThemesIgnore',
272 'ThemesAbandoned',
273 'ThemesIgnoredAbandoned',
274 'UserBulkManage',
275 'UserBulkAdd',
276 'BulkImportUsers',
277 'UpdateAdminPasswords',
278 'PostBulkManage',
279 'Extensions-Mainwp-Backups',
280 'Extensions-Mainwp-Security',
281 'Extensions-Mainwp-Analytics',
282 'Mainwp-Monitoring',
283 'Extensions-Mainwp-Agency',
284 'Extensions-Mainwp-Administrative',
285 'Extensions-Mainwp-Development',
286 'Extensions-Mainwp-Performance',
287 'ClientAddNew',
288 'ClientImport',
289 'ManageCostTracker',
290 'CostTrackerAdd',
291 'AddApiKeys',
292 );
293
294
295 if (in_array($page, $dequeue_pages, true)) {
296 wp_deregister_style('darkify-admin-switch');
297 wp_dequeue_script('darkify-admin-client-main');
298 }
299 }
300
301 /**
302 * Replace wp-admin's footer credit with Darkify's review request, on
303 * Darkify's own screens only.
304 *
305 * Screens are matched by page slug (`is_darkify_spa_page()`) rather than by
306 * a hardcoded screen id, so both the Settings and Get Help SPA screens match
307 * and the check keeps working if the menu is ever retitled. The slug list is
308 * the same source of truth the assets already load from.
309 *
310 * @param string $text Existing footer text.
311 *
312 * @return string
313 */
314 public function darkify_admin_footer($text)
315 {
316 if (! $this->is_darkify_spa_page()) {
317 return $text;
318 }
319
320 return sprintf(
321 /* translators: 1: start strong tag, 2: close strong tag. 3: start link 4: close link */
322 __('<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'),
323 '<strong>',
324 '</strong>',
325 '<span class="greet-footer-text-star">�
326
327
328
329
330 </span> <a href="https://wordpress.org/support/plugin/darkify/reviews/#new-post" target="_blank" rel="noopener noreferrer">',
331 '</a>'
332 );
333 }
334
335 // Plugin settings in plugin list
336 public function add_plugin_action_links(array $links)
337 {
338 $new_links = array(
339 sprintf('<a href="' . esc_url(admin_url('admin.php?page=darkify')) . '">' . esc_html__('Settings', 'darkify') . '</a>'),
340 sprintf('<a target="_blank" href="https://wordpress.org/support/plugin/darkify/">' . esc_html__('Support', 'darkify') . '</a>'),
341 );
342
343 $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'));
344
345 return array_merge($new_links, $links);
346 }
347
348 /**
349 * Add plugin row meta link.
350 *
351 * @since 2.0
352 *
353 * @param array $plugin_meta .
354 * @param string $file .
355 *
356 * @return array
357 */
358 public function after_darkify_row_meta($plugin_meta, $file)
359 {
360
361 if (DARKIFY_BASENAME === $file) {
362 $plugin_meta[] = '<a href="' . DARKIFY_DEMO_URL . '" target="_blank">' . __('Live Demo', 'darkify') . '</a>';
363 }
364
365 return $plugin_meta;
366 }
367
368 public function darkify_admin_bar_switch($wp_admin_bar)
369 {
370 if (! $this->darkify_is_dark_mode_allowed()) {
371 return;
372 }
373
374 $options = get_option('darkify');
375 $enable_admin_panel_dark_mode = isset($options['enable_admin_panel_dark_mode']) ? $options['enable_admin_panel_dark_mode'] : false;
376
377 // Everywhere but Darkify's own screens the node is only registered when
378 // the option is on, so it is always visible. On the SPA screens it is
379 // registered regardless and starts hidden while the option is off — the
380 // React admin then just flips this class (see WpAdminBarBridge), which is
381 // what makes the icon appear/disappear without a reload.
382 $classes = 'darkify_admin_bar_switch_container';
383 if (! $enable_admin_panel_dark_mode) {
384 $classes .= ' darkify_admin_bar_hidden';
385 }
386
387 $wp_admin_bar->add_node(array(
388 'parent' => 'top-secondary',
389 'id' => 'darkify_admin_bar_switch_container',
390 'meta' => array(
391 'class' => $classes,
392 'onclick' => 'darkify_switch_trigger()',
393 ),
394 ));
395 }
396
397 public function darkify_is_dark_mode_allowed()
398 {
399
400 $options = get_option('darkify');
401 $options = is_array($options) ? $options : array();
402 // Guarded read: `disallowed_admin_pages` has no schema default, so the
403 // React settings save doesn't persist it until the field is used. Reading
404 // it unguarded would emit an "Undefined array key" warning on every
405 // dark-mode hook (see the Pro plugin's Admin.php for the full story).
406 $disallowed_admin_pages = isset($options['disallowed_admin_pages']) ? $options['disallowed_admin_pages'] : '';
407 /* Disable on Disallowed Admin Plugins */
408 if ($this->utils->isRestrictedByDisallowedAdminPages($disallowed_admin_pages)) {
409 return False;
410 }
411 return True;
412 }
413
414 public function darkify_admin_header_script()
415 {
416 if ($this->darkify_is_dark_mode_allowed()) {
417 include_once DarkifyUtils::darkify_locate_template('header_script.php');
418 }
419 }
420
421 public function darkify_admin_footer_script()
422 {
423 if ($this->darkify_is_dark_mode_allowed()) {
424 include_once DarkifyUtils::darkify_locate_template('footer_script.php');
425 }
426 }
427
428 /**
429 * Load classic editor scripts.
430 *
431 * @since 5.0.0
432 */
433 public function load_classic_editor_scripts()
434 {
435 $options = get_option('darkify');
436 $classic_editor_dark_mode = isset($options['classic_editor_dark_mode']) ? $options['classic_editor_dark_mode'] : '';
437 if (! current_user_can('edit_posts') && ! current_user_can('edit_pages')) {
438 return;
439 }
440 if (get_user_option('rich_editing') !== 'true') {
441 return;
442 }
443
444 // Bail if admin_classic_editor is not enabled.
445 if (! wp_validate_boolean($classic_editor_dark_mode)) {
446 return;
447 }
448
449 add_filter('mce_external_plugins', function ($plugins) {
450 $plugins['darkify_button'] = plugins_url('src/assets/js/admin-classic-editor.js', DARKIFY_FILE);
451 return $plugins;
452 });
453
454 add_filter('mce_buttons', function ($buttons) {
455 $buttons[] = 'darkify_button';
456 return $buttons;
457 });
458 }
459 }
460