PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 1.5.4
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v1.5.4
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
darkify / src / Frontend / Frontend.php

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

488 lines 24.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ThemeAtelier\Darkify\Frontend;
4
5 use ThemeAtelier\Darkify\Includes\DarkifyExternalSupport;
6 use ThemeAtelier\Darkify\Includes\DarkifyUtils;
7
8 if (!defined('ABSPATH')) {
9 die;
10 } // Cannot access directly.
11
12 /**
13 * The public-facing functionality of the plugin.
14 *
15 * @link https://themeatelier.net
16 * @since 1.0.0
17 *
18 * @package Darkify
19 * @subpackage Darkify/public
20 */
21
22 /**
23 * The public-facing functionality of the plugin.
24 *
25 * Defines the plugin name, version, and two examples hooks for how to
26 * enqueue the public-facing stylesheet and JavaScript.
27 *
28 * @package Darkify
29 * @subpackage Darkify/public
30 * @author ThemeAtelier <themeatelierbd@gmail.com>
31 */
32 class Frontend
33 {
34
35 /**
36 * The ID of this plugin.
37 *
38 * @since 1.0.0
39 * @access private
40 * @var string $plugin_name The ID of this plugin.
41 */
42 private $plugin_name;
43
44 /**
45 * The version of this plugin.
46 *
47 * @since 1.0.0
48 * @access private
49 * @var string $version The current version of this plugin.
50 */
51 private $version;
52
53 public $utils;
54 public $settings;
55 public $external_support;
56 public $unique_id;
57
58 /**
59 * Initialize the class and set its properties.
60 *
61 * @since 1.0.0
62 * @param string $plugin_name The name of the plugin.
63 * @param string $version The version of this plugin.
64 */
65 public function __construct($plugin_name, $version)
66 {
67 $this->plugin_name = $plugin_name;
68 $this->version = $version;
69
70 $this->utils = new DarkifyUtils($this);
71 $this->external_support = new DarkifyExternalSupport($this);
72
73 new DarkifyShortcode($this);
74
75 if (function_exists('wp_rand')) {
76 $this->unique_id = wp_rand();
77 }
78
79 $options = get_option('darkify');
80 $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : "";
81 $enable_switcher_in_menu = isset($show_in_menu["enable_switch_in_menu"]) ? $show_in_menu["enable_switch_in_menu"] : false;
82
83 if ($enable_switcher_in_menu) {
84 add_filter('wp_nav_menu_items', array($this, 'darkify_switch_in_menu'), 10, 2);
85 }
86
87 add_action('wp_enqueue_scripts', array($this, 'darkify_client_enqueue'), 100);
88 add_action('login_enqueue_scripts', array($this, 'darkify_client_enqueue'));
89 add_action('register_enqueue_scripts', array($this, 'darkify_client_enqueue'));
90 add_action('wp_head', array($this, 'darkify_client_header_script'), 1);
91 add_action('login_head', array($this, 'darkify_client_header_script'), 1);
92 add_action('register_head', array($this, 'darkify_client_header_script'), 1);
93 add_action('wp_footer', array($this, 'darkify_client_footer_script'));
94 add_action('login_footer', array($this, 'darkify_client_footer_script'));
95 add_action('register_footer', array($this, 'darkify_client_footer_script'));
96
97 add_action('init', array($this, 'darkify_register_switcher_styles'), 5);
98 add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins'));
99 }
100
101 public function darkify_exclude_js_from_cache_plugins($excludes)
102 {
103 $excludes .= ',client_main.js,client_main.min.js';
104 return $excludes;
105 }
106
107 public function darkify_register_switcher_styles()
108 {
109 $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
110 wp_register_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
111 wp_register_style('theme-classic', DARKIFY_DIR_URL . 'src/assets/css/switcher/classic' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
112 wp_register_style('theme-expand', DARKIFY_DIR_URL . 'src/assets/css/switcher/expand' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
113 wp_register_style('theme-inner-moon', DARKIFY_DIR_URL . 'src/assets/css/switcher/inner-moon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
114 wp_register_style('theme-within', DARKIFY_DIR_URL . 'src/assets/css/switcher/within' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
115 wp_register_style('theme-orbit', DARKIFY_DIR_URL . 'src/assets/css/switcher/orbit' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
116 }
117
118 function darkify_client_enqueue()
119 {
120 if ($this->darkify_is_dark_mode_allowed()) {
121 $options = get_option('darkify');
122 $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
123
124 wp_enqueue_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
125 wp_enqueue_script('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main' . $min . '.js', array('jquery'), DARKIFY_VERSION);
126
127 // When "Frontend Iframe Dark Mode" is OFF, use a two-stage inline script
128 // strategy that covers all scenarios — source JS, minified JS,
129 // cross-origin iframes, and same-origin WP pages embedded as iframes.
130 //
131 // ROOT CAUSE: a same-origin WP page loaded inside an <iframe> has its
132 // OWN copy of client_main[.min].js running. That copy calls
133 // darkify_check_preloading() → localStorage.darkify_last_state="1" →
134 // adds darkify_dark_mode_enabled to the iframe's <html> → goes dark.
135 // The parent's darkify_process_iframes guard is irrelevant here.
136 //
137 // Stage 1 — 'before' (runs BEFORE client_main[.min].js on EVERY page):
138 // If this page is rendering inside a frontend iframe, intercept the
139 // localStorage.darkify_last_state property via Object.defineProperty so
140 // that darkify_check_preloading() always reads "0" and returns false.
141 // This prevents the iframe page's own Darkify from initialising dark
142 // mode at all — works with both source and minified JS because
143 // darkify_check_preloading() always reads the property directly.
144 // The source JS also has a _dkf_iframe_disabled guard for belt-and-
145 // suspenders coverage.
146 //
147 // Stage 2 — 'after' (runs AFTER client_main[.min].js on EVERY page):
148 // • Cleans up the localStorage property intercept from Stage 1.
149 // • Overrides the five injection helpers so the PARENT page's
150 // darkify_apply_dark_to_iframe() applyDirect() closures (which look
151 // up these functions by name in the global scope at call time) become
152 // no-ops for cross-origin and not-yet-loaded iframes.
153 $enable_frontend_iframe = isset($options['enable_frontend_iframe_dark_mode']) ? $options['enable_frontend_iframe_dark_mode'] : true;
154 if (!$enable_frontend_iframe) {
155
156 // Stage 1 — intercept localStorage.darkify_last_state BEFORE client_main.
157 wp_add_inline_script(
158 'darkify-client-main',
159 '(function(){' .
160 // Only apply when this window is a child frame, the setting
161 // exists and is OFF, and it is not the admin panel context.
162 'if(window===window.top)return;' .
163 'if(typeof darkify_enable_frontend_iframe_dark_mode==="undefined"' .
164 '||darkify_enable_frontend_iframe_dark_mode==="1")return;' .
165 'if(typeof darkify_is_this_admin_panel!=="undefined"' .
166 '&&darkify_is_this_admin_panel==="1")return;' .
167 // Define an own property on localStorage that intercepts reads
168 // of darkify_last_state. darkify_check_preloading() uses
169 // localStorage.darkify_last_state (property access, not getItem),
170 // so this getter is hit every time the function runs.
171 // Returning "0" makes the function take the "was explicitly set
172 // to 0 → not preloaded" branch and return false.
173 // The setter is silently ignored so state writes in the iframe
174 // don't pollute the shared localStorage for other tabs/pages.
175 'try{' .
176 'Object.defineProperty(localStorage,"darkify_last_state",{' .
177 'get:function(){return"0";},set:function(){},configurable:true' .
178 '});' .
179 '}catch(e){' .
180 // Fallback for environments where Object.defineProperty on
181 // Storage objects is not supported: temporarily set the value.
182 'try{window._dkf_ls_bk=localStorage.darkify_last_state;' .
183 'localStorage.darkify_last_state="0";}catch(e2){}' .
184 '}' .
185 '})();',
186 'before'
187 );
188
189 // Stage 2 — cleanup + injection helper overrides AFTER client_main.
190 wp_add_inline_script(
191 'darkify-client-main',
192 '(function(){' .
193 // Remove the own-property intercept so subsequent reads of
194 // localStorage.darkify_last_state return the real stored value.
195 // "delete" removes the own property, restoring prototype lookup.
196 'if(window!==window.top){' .
197 'try{delete localStorage.darkify_last_state;}catch(e){' .
198 // Fallback: restore saved value
199 'try{if(typeof window._dkf_ls_bk!=="undefined"){' .
200 'if(window._dkf_ls_bk)localStorage.darkify_last_state=window._dkf_ls_bk;' .
201 'else localStorage.removeItem("darkify_last_state");' .
202 '}delete window._dkf_ls_bk;}catch(e2){}' .
203 '}' .
204 '}' .
205 // Override injection helpers so the PARENT page's applyDirect()
206 // closures (which already hold references to these global
207 // functions) become no-ops for frontend iframes.
208 'function _dkfAdmin(){' .
209 'return typeof darkify_is_this_admin_panel!=="undefined"&&darkify_is_this_admin_panel==="1";' .
210 '}' .
211 'if(typeof darkify_process_iframes==="function"){' .
212 'var _op=darkify_process_iframes;' .
213 'darkify_process_iframes=function(){if(_dkfAdmin())return _op();};' .
214 '}' .
215 'if(typeof darkify_inject_css_into_iframe==="function"){' .
216 'var _oi=darkify_inject_css_into_iframe;' .
217 'darkify_inject_css_into_iframe=function(d){if(_dkfAdmin())return _oi(d);};' .
218 '}' .
219 'if(typeof darkify_sync_iframe_vars==="function"){' .
220 'var _ov=darkify_sync_iframe_vars;' .
221 'darkify_sync_iframe_vars=function(d){if(_dkfAdmin())return _ov(d);};' .
222 '}' .
223 'if(typeof darkify_run_engine_on_iframe==="function"){' .
224 'var _oe=darkify_run_engine_on_iframe;' .
225 'darkify_run_engine_on_iframe=function(d){if(_dkfAdmin())return _oe(d);};' .
226 '}' .
227 'if(typeof darkify_inject_block_editor_css_into_iframe==="function"){' .
228 'var _ob=darkify_inject_block_editor_css_into_iframe;' .
229 'darkify_inject_block_editor_css_into_iframe=function(d){if(_dkfAdmin())return _ob(d);};' .
230 '}' .
231 '})();',
232 'after'
233 );
234 }
235
236 // Enqueue the global floating switcher style from settings
237 $enable_dark_mode_switch = isset($options['enable_dark_mode_switch']) ? $options['enable_dark_mode_switch'] : true;
238 if ($enable_dark_mode_switch) {
239 $global_variant = isset($options['enable_dark_switcher']) ? $options['enable_dark_switcher'] : 'classic';
240 wp_enqueue_style('theme-' . $global_variant);
241 }
242
243 // Detect [darkify] shortcode usage in current post and enqueue its variant
244 $post = get_post();
245 if ($post && !empty($post->post_content)) {
246 if (has_shortcode($post->post_content, 'darkify')) {
247 preg_match_all('/\[darkify\b[^\]]*\bswitch=["\']([^"\']+)["\'][^\]]*\]/i', $post->post_content, $matches);
248 $used_switches = !empty($matches[1]) ? $matches[1] : [];
249 foreach ($used_switches as $switch_val) {
250 wp_enqueue_style('theme-' . $this->darkify_switch_to_variant($switch_val));
251 }
252 // Shortcode without switch= attribute defaults to classic
253 preg_match_all('/\[darkify\b/i', $post->post_content, $all_sc);
254 if (count($all_sc[0]) > count($used_switches)) {
255 wp_enqueue_style('theme-classic');
256 }
257 }
258
259 // Detect darkify/switcher Gutenberg block usage and enqueue its variant
260 if (function_exists('has_block') && has_block('darkify/switcher', $post)) {
261 foreach ($this->darkify_get_block_variants($post->post_content) as $variant) {
262 wp_enqueue_style('theme-' . $variant);
263 }
264 }
265 }
266
267 // Detect switcher used in nav menus and enqueue its variant
268 $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : [];
269 $enable_in_menu = isset($show_in_menu['enable_switch_in_menu']) ? $show_in_menu['enable_switch_in_menu'] : false;
270 if ($enable_in_menu) {
271 $select_menus = isset($show_in_menu['select_menus']) ? $show_in_menu['select_menus'] : [];
272 foreach ($select_menus as $select_menu) {
273 $shortcode = $select_menu['darkify_menu_shortcode_helper'] ?? '';
274 if ($shortcode) {
275 preg_match('/\bswitch=["\']([^"\']+)["\']/', $shortcode, $m);
276 $switch_val = $m[1] ?? '1';
277 wp_enqueue_style('theme-' . $this->darkify_switch_to_variant($switch_val));
278 }
279 }
280 }
281 }
282 }
283
284 private function darkify_switch_to_variant($switch_value)
285 {
286 $map = [
287 '1' => 'classic', '2' => 'expand', '3' => 'inner-moon',
288 '4' => 'within', '5' => 'orbit',
289 ];
290 if (is_numeric($switch_value)) {
291 return $map[(string) $switch_value] ?? 'classic';
292 }
293 return $switch_value ?: 'classic';
294 }
295
296 private function darkify_get_block_variants($content)
297 {
298 $variants = [];
299 $this->darkify_collect_block_variants(parse_blocks($content), $variants);
300 return array_unique($variants);
301 }
302
303 private function darkify_collect_block_variants($blocks, &$variants)
304 {
305 foreach ($blocks as $block) {
306 if ('darkify/switcher' === $block['blockName']) {
307 $variants[] = $block['attrs']['style'] ?? 'classic';
308 }
309 if (!empty($block['innerBlocks'])) {
310 $this->darkify_collect_block_variants($block['innerBlocks'], $variants);
311 }
312 }
313
314 $options = get_option('darkify');
315 $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
316
317 // Always register all variant styles so page builders (Elementor, etc.) can reference them.
318 wp_register_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
319 wp_register_style('theme-classic', DARKIFY_DIR_URL . 'src/assets/css/switcher/classic' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
320 wp_register_style('theme-expand', DARKIFY_DIR_URL . 'src/assets/css/switcher/expand' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
321 wp_register_style('theme-inner-moon', DARKIFY_DIR_URL . 'src/assets/css/switcher/inner-moon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
322 wp_register_style('theme-orbit', DARKIFY_DIR_URL . 'src/assets/css/switcher/orbit' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
323 wp_register_style('theme-within', DARKIFY_DIR_URL . 'src/assets/css/switcher/within' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
324
325 // Determine which variants are actually needed on this page.
326 $variants = $this->darkify_collect_page_variants($options);
327
328 if (empty($variants)) {
329 return;
330 }
331
332 // Enqueue main CSS + only the required variant CSS — all resolved before wp_head fires.
333 wp_enqueue_style('darkify-client-main');
334 foreach ($variants as $variant) {
335 wp_enqueue_style("theme-{$variant}");
336 }
337 wp_enqueue_script('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main' . $min . '.js', array('jquery'), DARKIFY_VERSION);
338 }
339
340 /**
341 * Collects all switcher variants needed on the current page so they can be
342 * enqueued during wp_enqueue_scripts (before wp_head), guaranteeing CSS loads
343 * in the <head> regardless of page builder or theme.
344 */
345 private function darkify_collect_page_variants($options)
346 {
347 $all_variants = array('classic', 'expand', 'inner-moon', 'orbit', 'within');
348 $num_map = array('1' => 'classic', '2' => 'expand', '3' => 'inner-moon', '4' => 'within', '5' => 'orbit');
349 $variants = array();
350
351 // 1. Global / floating switcher variant (always present when dark mode is active).
352 $global_variant = isset($options['enable_dark_switcher']) ? $options['enable_dark_switcher'] : 'classic';
353 if (in_array($global_variant, $all_variants, true)) {
354 $variants[] = $global_variant;
355 }
356
357 // 2. Menu switcher — parse variant from each saved shortcode helper string.
358 $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : array();
359 if (!empty($show_in_menu['enable_switch_in_menu']) && !empty($show_in_menu['select_menus'])) {
360 foreach ($show_in_menu['select_menus'] as $menu_item) {
361 $helper = isset($menu_item['darkify_menu_shortcode_helper']) ? $menu_item['darkify_menu_shortcode_helper'] : '';
362 if ($helper && preg_match('/switch=["\']?([^"\'>\s\]]+)["\']?/', $helper, $m)) {
363 $v = isset($num_map[$m[1]]) ? $num_map[$m[1]] : $m[1];
364 if (in_array($v, $all_variants, true)) {
365 $variants[] = $v;
366 }
367 }
368 }
369 }
370
371 // 3. Page-level detection — shortcodes, Gutenberg blocks, Elementor widget.
372 global $post;
373 if ($post) {
374 $content = $post->post_content;
375
376 // Shortcodes: parse switch attribute from every [darkify] tag.
377 if (has_shortcode($content, 'darkify')) {
378 preg_match_all('/\[darkify[^\]]*switch=["\']?([^"\'>\s\]]+)/', $content, $sc_matches);
379 foreach ($sc_matches[1] as $switch_val) {
380 $v = isset($num_map[$switch_val]) ? $num_map[$switch_val] : $switch_val;
381 if (in_array($v, $all_variants, true)) {
382 $variants[] = $v;
383 } else {
384 $variants[] = 'classic'; // default when no valid variant found
385 }
386 }
387 // If [darkify] exists but has no switch attribute, default to classic.
388 if (empty($sc_matches[1]) && has_shortcode($content, 'darkify')) {
389 $variants[] = 'classic';
390 }
391 }
392
393 // Gutenberg blocks: walk the block tree and collect style attributes.
394 if (function_exists('has_block') && has_block('darkify/switcher', $post)) {
395 $blocks = parse_blocks($content);
396 $variants = array_merge($variants, $this->darkify_extract_block_variants($blocks, $all_variants));
397 }
398
399 // Elementor widget: if Darkify widget appears in the page's Elementor data,
400 // load all variants — the specific style is stored as a control value that
401 // would require deep JSON parsing to extract reliably.
402 if (class_exists('\Elementor\Plugin')) {
403 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
404 if ($elementor_data && strpos($elementor_data, '"widgetType":"darkify"') !== false) {
405 return $all_variants;
406 }
407 }
408 }
409
410 return array_values(array_unique(array_filter($variants)));
411 }
412
413 /**
414 * Recursively walks a parsed block tree and collects darkify/darkify style values.
415 */
416 private function darkify_extract_block_variants($blocks, $all_variants)
417 {
418 $variants = array();
419 foreach ($blocks as $block) {
420 if ('darkify/switcher' === $block['blockName']) {
421 $style = isset($block['attrs']['style']) ? $block['attrs']['style'] : 'classic';
422 if (in_array($style, $all_variants, true)) {
423 $variants[] = $style;
424 }
425 }
426 if (!empty($block['innerBlocks'])) {
427 $variants = array_merge($variants, $this->darkify_extract_block_variants($block['innerBlocks'], $all_variants));
428 }
429 }
430 return $variants;
431 }
432 public function darkify_is_dark_mode_allowed()
433 {
434 $options = get_option('darkify');
435 /* Disable if Oxygen Builder is Opened */
436 if (isset($_GET['ct_builder'])) {
437 if ($_GET['ct_builder'] == "true") {
438 if (!isset($_GET['oxygen_iframe'])) {
439 return False;
440 }
441 }
442 }
443
444 return True;
445 }
446
447
448 public function darkify_client_header_script()
449 {
450 if ($this->darkify_is_dark_mode_allowed()) {
451 include_once DarkifyUtils::darkify_locate_template('header_script.php');
452 }
453 }
454
455 public function darkify_client_footer_script()
456 {
457 if ($this->darkify_is_dark_mode_allowed()) {
458 include_once DarkifyUtils::darkify_locate_template('footer_script.php');
459 }
460 }
461
462 function darkify_switch_in_menu($items, $args)
463 {
464 $options = get_option('darkify');
465 $show_in_menu = $options['show_in_menu'];
466 $select_menus = isset($show_in_menu['select_menus']) ? $show_in_menu['select_menus'] : [];
467
468 $last_shortcode = '';
469
470 if ($this->darkify_is_dark_mode_allowed() && isset($args->menu->term_id)) {
471 foreach ($select_menus as $select_menu) {
472
473 $switch_in_menu_location = $select_menu['switch_in_menu_location'] ?? '';
474 $darkify_menu_shortcode_helper = $select_menu['darkify_menu_shortcode_helper'] ?? '';
475 $menu_position = isset($select_menu['select_menu_position']) ? $select_menu['select_menu_position'] : 'after';
476 if ($args->menu->term_id == $switch_in_menu_location) {
477 $last_shortcode = $darkify_menu_shortcode_helper;
478 }
479 }
480 if ($last_shortcode) {
481 $items = ($menu_position == 'before') ? '<li class="menu-item">' . do_shortcode($last_shortcode) . '</li>' . $items : $items . '<li class="menu-item">' . do_shortcode($last_shortcode) . '</li>';
482 }
483 }
484
485 return $items;
486 }
487 }
488