PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 1.4.6
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v1.4.6
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/Frontend/Frontend.php +32 -508 trunk1.4.6 View file →
@@ -1,15 +1,11 @@
1 1 <?php
2 2
3 3 namespace ThemeAtelier\Darkify\Frontend;
4 4
5 -use ThemeAtelier\Darkify\Includes\DarkifyExternalSupport;
6 -use ThemeAtelier\Darkify\Includes\DarkifyUtils;
5 +use ThemeAtelier\Darkify\Helpers\DarkifyExternalSupport;
6 +use ThemeAtelier\Darkify\Helpers\DarkifyUtils;
7 7
8 -if (!defined('ABSPATH')) {
9 - die;
10 -} // Cannot access directly.
11 -
12 8 /**
13 9 * The public-facing functionality of the plugin.
14 10 *
15 11 * @link https://themeatelier.net
@@ -75,20 +71,16 @@
75 71 if (function_exists('wp_rand')) {
76 72 $this->unique_id = wp_rand();
77 73 }
78 74
79 - // Register unconditionally and gate inside the callback. The constructor
80 - // runs at plugin boot (before `init`), so the option read here is always
81 - // the SAVED value — deciding registration here means the admin Live
82 - // Preview, which swaps in unsaved values only on `init` (see
83 - // PreviewRest::maybe_enable_preview), could never show a newly enabled
84 - // menu switch until saved. The filter runs at render time, after `init`,
85 - // so re-reading the option in the callback sees the preview values.
86 - add_filter('wp_nav_menu_items', array($this, 'darkify_switch_in_menu'), 10, 2);
87 - // Block-theme and Kadence navigations never fire `wp_nav_menu_items` —
88 - // they are blocks, not wp_nav_menu() calls — so they need their own hook.
89 - add_filter('render_block', array($this, 'darkify_switch_in_block_menu'), 10, 2);
75 + $options = get_option('darkify');
76 + $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : "";
77 + $enable_switcher_in_menu = isset($show_in_menu["enable_switch_in_menu"]) ? $show_in_menu["enable_switch_in_menu"] : false;
90 78
79 + if ($enable_switcher_in_menu) {
80 + add_filter('wp_nav_menu_items', array($this, 'darkify_switch_in_menu'), 10, 2);
81 + }
82 +
91 83 add_action('wp_enqueue_scripts', array($this, 'darkify_client_enqueue'), 100);
92 84 add_action('login_enqueue_scripts', array($this, 'darkify_client_enqueue'));
93 85 add_action('register_enqueue_scripts', array($this, 'darkify_client_enqueue'));
94 86 add_action('wp_head', array($this, 'darkify_client_header_script'), 1);
@@ -96,13 +88,13 @@
96 88 add_action('register_head', array($this, 'darkify_client_header_script'), 1);
97 89 add_action('wp_footer', array($this, 'darkify_client_footer_script'));
98 90 add_action('login_footer', array($this, 'darkify_client_footer_script'));
99 91 add_action('register_footer', array($this, 'darkify_client_footer_script'));
100 -
101 - add_action('init', array($this, 'darkify_register_switcher_styles'), 5);
92 +
102 93 add_filter('autoptimize_filter_js_exclude', array($this, 'darkify_exclude_js_from_cache_plugins'));
103 94 }
104 95
96 +
105 97 public function darkify_exclude_js_from_cache_plugins($excludes)
106 98 {
107 99 $excludes .= ',client_main.js,client_main.min.js';
108 100 return $excludes;
@@ -107,356 +99,37 @@
107 99 $excludes .= ',client_main.js,client_main.min.js';
108 100 return $excludes;
109 101 }
110 102
111 - public function darkify_register_switcher_styles()
112 - {
113 - $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
114 - wp_register_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
115 - wp_register_style('theme-classic', DARKIFY_DIR_URL . 'src/assets/css/switcher/classic' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
116 - wp_register_style('theme-expand', DARKIFY_DIR_URL . 'src/assets/css/switcher/expand' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
117 - wp_register_style('theme-inner-moon', DARKIFY_DIR_URL . 'src/assets/css/switcher/inner-moon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
118 - wp_register_style('theme-within', DARKIFY_DIR_URL . 'src/assets/css/switcher/within' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
119 - wp_register_style('theme-orbit', DARKIFY_DIR_URL . 'src/assets/css/switcher/orbit' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
120 - }
121 103
122 - function darkify_client_enqueue()
104 + public function darkify_client_enqueue()
123 105 {
124 106 if ($this->darkify_is_dark_mode_allowed()) {
125 107 $options = get_option('darkify');
108 + $enable_dark_switcher = isset($options['enable_dark_switcher']) ? $options['enable_dark_switcher'] : 'classic';
109 +
126 110 $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
111 + wp_enqueue_style("theme-{$enable_dark_switcher}", DARKIFY_DIR_URL . 'src/assets/css/switcher/' . $enable_dark_switcher . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
127 112
113 + wp_register_style('theme-around', DARKIFY_DIR_URL . 'src/assets/css/switcher/around' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
114 + wp_register_style('theme-classic', DARKIFY_DIR_URL . 'src/assets/css/switcher/classic' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
115 + wp_register_style('theme-dark-inner', DARKIFY_DIR_URL . 'src/assets/css/switcher/dark-inner' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
116 + wp_register_style('theme-dark-side', DARKIFY_DIR_URL . 'src/assets/css/switcher/dark-side' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
117 + wp_register_style('theme-eclipse', DARKIFY_DIR_URL . 'src/assets/css/switcher/eclipse' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
118 + wp_register_style('theme-expand', DARKIFY_DIR_URL . 'src/assets/css/switcher/expand' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
119 + wp_register_style('theme-half-sun', DARKIFY_DIR_URL . 'src/assets/css/switcher/half-sun' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
120 + wp_register_style('theme-horizon', DARKIFY_DIR_URL . 'src/assets/css/switcher/horizon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
121 + wp_register_style('theme-inner-moon', DARKIFY_DIR_URL . 'src/assets/css/switcher/inner-moon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
122 + wp_register_style('theme-lightbulb', DARKIFY_DIR_URL . 'src/assets/css/switcher/lightbulb' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
123 + wp_register_style('theme-simple', DARKIFY_DIR_URL . 'src/assets/css/switcher/simple' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
124 + wp_register_style('theme-within', DARKIFY_DIR_URL . 'src/assets/css/switcher/within' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
128 125 wp_enqueue_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
129 - wp_enqueue_script('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main' . $min . '.js', array(), DARKIFY_VERSION);
130 -
131 - // When "Frontend Iframe Dark Mode" is OFF, use a two-stage inline script
132 - // strategy that covers all scenarios — source JS, minified JS,
133 - // cross-origin iframes, and same-origin WP pages embedded as iframes.
134 - //
135 - // ROOT CAUSE: a same-origin WP page loaded inside an <iframe> has its
136 - // OWN copy of client_main[.min].js running. That copy calls
137 - // darkify_check_preloading() → localStorage.darkify_last_state="1" →
138 - // adds darkify_dark_mode_enabled to the iframe's <html> → goes dark.
139 - // The parent's darkify_process_iframes guard is irrelevant here.
140 - //
141 - // Stage 1 — 'before' (runs BEFORE client_main[.min].js on EVERY page):
142 - // If this page is rendering inside a frontend iframe, intercept the
143 - // localStorage.darkify_last_state property via Object.defineProperty so
144 - // that darkify_check_preloading() always reads "0" and returns false.
145 - // This prevents the iframe page's own Darkify from initialising dark
146 - // mode at all — works with both source and minified JS because
147 - // darkify_check_preloading() always reads the property directly.
148 - // The source JS also has a _dkf_iframe_disabled guard for belt-and-
149 - // suspenders coverage.
150 - //
151 - // Stage 2 — 'after' (runs AFTER client_main[.min].js on EVERY page):
152 - // • Cleans up the localStorage property intercept from Stage 1.
153 - // • Overrides the five injection helpers so the PARENT page's
154 - // darkify_apply_dark_to_iframe() applyDirect() closures (which look
155 - // up these functions by name in the global scope at call time) become
156 - // no-ops for cross-origin and not-yet-loaded iframes.
157 - $enable_frontend_iframe = isset($options['enable_frontend_iframe_dark_mode']) ? $options['enable_frontend_iframe_dark_mode'] : true;
158 - if (!$enable_frontend_iframe) {
159 -
160 - // Stage 1 — intercept localStorage.darkify_last_state BEFORE client_main.
161 - wp_add_inline_script(
162 - 'darkify-client-main',
163 - '(function(){' .
164 - // Only apply when this window is a child frame, the setting
165 - // exists and is OFF, and it is not the admin panel context.
166 - 'if(window===window.top)return;' .
167 - 'if(typeof darkify_enable_frontend_iframe_dark_mode==="undefined"' .
168 - '||darkify_enable_frontend_iframe_dark_mode==="1")return;' .
169 - 'if(typeof darkify_is_this_admin_panel!=="undefined"' .
170 - '&&darkify_is_this_admin_panel==="1")return;' .
171 - // Define an own property on localStorage that intercepts reads
172 - // of darkify_last_state. darkify_check_preloading() uses
173 - // localStorage.darkify_last_state (property access, not getItem),
174 - // so this getter is hit every time the function runs.
175 - // Returning "0" makes the function take the "was explicitly set
176 - // to 0 → not preloaded" branch and return false.
177 - // The setter is silently ignored so state writes in the iframe
178 - // don't pollute the shared localStorage for other tabs/pages.
179 - 'try{' .
180 - 'Object.defineProperty(localStorage,"darkify_last_state",{' .
181 - 'get:function(){return"0";},set:function(){},configurable:true' .
182 - '});' .
183 - '}catch(e){' .
184 - // Fallback for environments where Object.defineProperty on
185 - // Storage objects is not supported: temporarily set the value.
186 - 'try{window._dkf_ls_bk=localStorage.darkify_last_state;' .
187 - 'localStorage.darkify_last_state="0";}catch(e2){}' .
188 - '}' .
189 - '})();',
190 - 'before'
191 - );
192 -
193 - // Stage 2 — cleanup + injection helper overrides AFTER client_main.
194 - wp_add_inline_script(
195 - 'darkify-client-main',
196 - '(function(){' .
197 - // Remove the own-property intercept so subsequent reads of
198 - // localStorage.darkify_last_state return the real stored value.
199 - // "delete" removes the own property, restoring prototype lookup.
200 - 'if(window!==window.top){' .
201 - 'try{delete localStorage.darkify_last_state;}catch(e){' .
202 - // Fallback: restore saved value
203 - 'try{if(typeof window._dkf_ls_bk!=="undefined"){' .
204 - 'if(window._dkf_ls_bk)localStorage.darkify_last_state=window._dkf_ls_bk;' .
205 - 'else localStorage.removeItem("darkify_last_state");' .
206 - '}delete window._dkf_ls_bk;}catch(e2){}' .
207 - '}' .
208 - '}' .
209 - // Override injection helpers so the PARENT page's applyDirect()
210 - // closures (which already hold references to these global
211 - // functions) become no-ops for frontend iframes.
212 - 'function _dkfAdmin(){' .
213 - 'return typeof darkify_is_this_admin_panel!=="undefined"&&darkify_is_this_admin_panel==="1";' .
214 - '}' .
215 - 'if(typeof darkify_process_iframes==="function"){' .
216 - 'var _op=darkify_process_iframes;' .
217 - 'darkify_process_iframes=function(){if(_dkfAdmin())return _op();};' .
218 - '}' .
219 - 'if(typeof darkify_inject_css_into_iframe==="function"){' .
220 - 'var _oi=darkify_inject_css_into_iframe;' .
221 - 'darkify_inject_css_into_iframe=function(d){if(_dkfAdmin())return _oi(d);};' .
222 - '}' .
223 - 'if(typeof darkify_sync_iframe_vars==="function"){' .
224 - 'var _ov=darkify_sync_iframe_vars;' .
225 - 'darkify_sync_iframe_vars=function(d){if(_dkfAdmin())return _ov(d);};' .
226 - '}' .
227 - 'if(typeof darkify_run_engine_on_iframe==="function"){' .
228 - 'var _oe=darkify_run_engine_on_iframe;' .
229 - 'darkify_run_engine_on_iframe=function(d){if(_dkfAdmin())return _oe(d);};' .
230 - '}' .
231 - 'if(typeof darkify_inject_block_editor_css_into_iframe==="function"){' .
232 - 'var _ob=darkify_inject_block_editor_css_into_iframe;' .
233 - 'darkify_inject_block_editor_css_into_iframe=function(d){if(_dkfAdmin())return _ob(d);};' .
234 - '}' .
235 - '})();',
236 - 'after'
237 - );
238 - }
239 -
240 - // Enqueue the global floating switcher style from settings
241 - $enable_dark_mode_switch = isset($options['enable_dark_mode_switch']) ? $options['enable_dark_mode_switch'] : true;
242 - if ($enable_dark_mode_switch) {
243 - $global_variant = isset($options['enable_dark_switcher']) ? $options['enable_dark_switcher'] : 'classic';
244 - wp_enqueue_style('theme-' . $global_variant);
245 -
246 - // Separate mobile floating switcher (Different Switch in Mobile) — its toggler
247 - // style may differ from the default, so enqueue that variant's CSS as well.
248 - if (!empty($options['dark_switcher_switch_in_mobile'])) {
249 - $mobile_variant = isset($options['enable_dark_switcher_in_mobile']) ? $options['enable_dark_switcher_in_mobile'] : 'classic';
250 - wp_enqueue_style('theme-' . $this->darkify_switch_to_variant($mobile_variant));
251 - }
252 - }
253 -
254 - // Detect [darkify] shortcode usage in current post and enqueue its variant
255 - $post = get_post();
256 - if ($post && !empty($post->post_content)) {
257 - if (has_shortcode($post->post_content, 'darkify')) {
258 - preg_match_all('/\[darkify\b[^\]]*\bswitch=["\']([^"\']+)["\'][^\]]*\]/i', $post->post_content, $matches);
259 - $used_switches = !empty($matches[1]) ? $matches[1] : [];
260 - foreach ($used_switches as $switch_val) {
261 - wp_enqueue_style('theme-' . $this->darkify_switch_to_variant($switch_val));
262 - }
263 - // Shortcode without switch= attribute defaults to classic
264 - preg_match_all('/\[darkify\b/i', $post->post_content, $all_sc);
265 - if (count($all_sc[0]) > count($used_switches)) {
266 - wp_enqueue_style('theme-classic');
267 - }
268 - }
269 -
270 - // Detect darkify/switcher Gutenberg block usage and enqueue its variant
271 - if (function_exists('has_block') && has_block('darkify/switcher', $post)) {
272 - foreach ($this->darkify_get_block_variants($post->post_content) as $variant) {
273 - wp_enqueue_style('theme-' . $variant);
274 - }
275 - }
276 - }
277 -
278 - // Detect switcher used in nav menus and enqueue its variant
279 - $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : [];
280 - $enable_in_menu = isset($show_in_menu['enable_switch_in_menu']) ? $show_in_menu['enable_switch_in_menu'] : false;
281 - if ($enable_in_menu) {
282 - $select_menus = isset($show_in_menu['select_menus']) ? $show_in_menu['select_menus'] : [];
283 - foreach ($select_menus as $select_menu) {
284 - $shortcode = $select_menu['darkify_menu_shortcode_helper'] ?? '';
285 - if ($shortcode) {
286 - preg_match('/\bswitch=["\']([^"\']+)["\']/', $shortcode, $m);
287 - $switch_val = $m[1] ?? '1';
288 - wp_enqueue_style('theme-' . $this->darkify_switch_to_variant($switch_val));
289 - }
290 - }
291 - }
126 + wp_enqueue_script('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main' . $min . '.js', array('jquery'), DARKIFY_VERSION,false);
292 127 }
293 128 }
294 -
295 - private function darkify_switch_to_variant($switch_value)
296 - {
297 - $map = [
298 - '1' => 'classic', '2' => 'expand', '3' => 'inner-moon',
299 - '4' => 'within', '5' => 'orbit',
300 - ];
301 - if (is_numeric($switch_value)) {
302 - return $map[(string) $switch_value] ?? 'classic';
303 - }
304 - return $switch_value ?: 'classic';
305 - }
306 -
307 - private function darkify_get_block_variants($content)
308 - {
309 - $variants = [];
310 - $this->darkify_collect_block_variants(parse_blocks($content), $variants);
311 - return array_unique($variants);
312 - }
313 -
314 - private function darkify_collect_block_variants($blocks, &$variants)
315 - {
316 - foreach ($blocks as $block) {
317 - if ('darkify/switcher' === $block['blockName']) {
318 - $variants[] = $block['attrs']['style'] ?? 'classic';
319 - }
320 - if (!empty($block['innerBlocks'])) {
321 - $this->darkify_collect_block_variants($block['innerBlocks'], $variants);
322 - }
323 - }
324 -
325 - $options = get_option('darkify');
326 - $min = (apply_filters('darkify_dev_mode', false) || WP_DEBUG) ? '' : '.min';
327 -
328 - // Always register all variant styles so page builders (Elementor, etc.) can reference them.
329 - wp_register_style('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/css/client_main' . $min . '.css', array(), DARKIFY_VERSION);
330 - wp_register_style('theme-classic', DARKIFY_DIR_URL . 'src/assets/css/switcher/classic' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
331 - wp_register_style('theme-expand', DARKIFY_DIR_URL . 'src/assets/css/switcher/expand' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
332 - wp_register_style('theme-inner-moon', DARKIFY_DIR_URL . 'src/assets/css/switcher/inner-moon' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
333 - wp_register_style('theme-orbit', DARKIFY_DIR_URL . 'src/assets/css/switcher/orbit' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
334 - wp_register_style('theme-within', DARKIFY_DIR_URL . 'src/assets/css/switcher/within' . $min . '.css', array('darkify-client-main'), DARKIFY_VERSION, 'all');
335 -
336 - // Determine which variants are actually needed on this page.
337 - $variants = $this->darkify_collect_page_variants($options);
338 -
339 - if (empty($variants)) {
340 - return;
341 - }
342 -
343 - // Enqueue main CSS + only the required variant CSS — all resolved before wp_head fires.
344 - wp_enqueue_style('darkify-client-main');
345 - foreach ($variants as $variant) {
346 - wp_enqueue_style("theme-{$variant}");
347 - }
348 - wp_enqueue_script('darkify-client-main', DARKIFY_DIR_URL . 'src/assets/js/client_main' . $min . '.js', array(), DARKIFY_VERSION);
349 - }
350 -
351 - /**
352 - * Collects all switcher variants needed on the current page so they can be
353 - * enqueued during wp_enqueue_scripts (before wp_head), guaranteeing CSS loads
354 - * in the <head> regardless of page builder or theme.
355 - */
356 - private function darkify_collect_page_variants($options)
357 - {
358 - $all_variants = array('classic', 'expand', 'inner-moon', 'orbit', 'within');
359 - $num_map = array('1' => 'classic', '2' => 'expand', '3' => 'inner-moon', '4' => 'within', '5' => 'orbit');
360 - $variants = array();
361 -
362 - // 1. Global / floating switcher variant (always present when dark mode is active).
363 - $global_variant = isset($options['enable_dark_switcher']) ? $options['enable_dark_switcher'] : 'classic';
364 - if (in_array($global_variant, $all_variants, true)) {
365 - $variants[] = $global_variant;
366 - }
367 -
368 - // 2. Menu switcher — parse variant from each saved shortcode helper string.
369 - $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : array();
370 - if (!empty($show_in_menu['enable_switch_in_menu']) && !empty($show_in_menu['select_menus'])) {
371 - foreach ($show_in_menu['select_menus'] as $menu_item) {
372 - $helper = isset($menu_item['darkify_menu_shortcode_helper']) ? $menu_item['darkify_menu_shortcode_helper'] : '';
373 - if ($helper && preg_match('/switch=["\']?([^"\'>\s\]]+)["\']?/', $helper, $m)) {
374 - $v = isset($num_map[$m[1]]) ? $num_map[$m[1]] : $m[1];
375 - if (in_array($v, $all_variants, true)) {
376 - $variants[] = $v;
377 - }
378 - }
379 - }
380 - }
381 -
382 - // 3. Page-level detection — shortcodes, Gutenberg blocks, Elementor widget.
383 - global $post;
384 - if ($post) {
385 - $content = $post->post_content;
386 -
387 - // Shortcodes: parse switch attribute from every [darkify] tag.
388 - if (has_shortcode($content, 'darkify')) {
389 - preg_match_all('/\[darkify[^\]]*switch=["\']?([^"\'>\s\]]+)/', $content, $sc_matches);
390 - foreach ($sc_matches[1] as $switch_val) {
391 - $v = isset($num_map[$switch_val]) ? $num_map[$switch_val] : $switch_val;
392 - if (in_array($v, $all_variants, true)) {
393 - $variants[] = $v;
394 - } else {
395 - $variants[] = 'classic'; // default when no valid variant found
396 - }
397 - }
398 - // If [darkify] exists but has no switch attribute, default to classic.
399 - if (empty($sc_matches[1]) && has_shortcode($content, 'darkify')) {
400 - $variants[] = 'classic';
401 - }
402 - }
403 -
404 - // Gutenberg blocks: walk the block tree and collect style attributes.
405 - if (function_exists('has_block') && has_block('darkify/switcher', $post)) {
406 - $blocks = parse_blocks($content);
407 - $variants = array_merge($variants, $this->darkify_extract_block_variants($blocks, $all_variants));
408 - }
409 -
410 - // Elementor widget: if Darkify widget appears in the page's Elementor data,
411 - // load all variants — the specific style is stored as a control value that
412 - // would require deep JSON parsing to extract reliably.
413 - if (class_exists('\Elementor\Plugin')) {
414 - $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
415 - if ($elementor_data && strpos($elementor_data, '"widgetType":"darkify"') !== false) {
416 - return $all_variants;
417 - }
418 - }
419 - }
420 -
421 - return array_values(array_unique(array_filter($variants)));
422 - }
423 -
424 - /**
425 - * Recursively walks a parsed block tree and collects darkify/darkify style values.
426 - */
427 - private function darkify_extract_block_variants($blocks, $all_variants)
428 - {
429 - $variants = array();
430 - foreach ($blocks as $block) {
431 - if ('darkify/switcher' === $block['blockName']) {
432 - $style = isset($block['attrs']['style']) ? $block['attrs']['style'] : 'classic';
433 - if (in_array($style, $all_variants, true)) {
434 - $variants[] = $style;
435 - }
436 - }
437 - if (!empty($block['innerBlocks'])) {
438 - $variants = array_merge($variants, $this->darkify_extract_block_variants($block['innerBlocks'], $all_variants));
439 - }
440 - }
441 - return $variants;
442 - }
443 129 public function darkify_is_dark_mode_allowed()
444 130 {
445 131 $options = get_option('darkify');
446 -
447 - // "Frontend Dark Mode" (enable_dark_mode_switch) is the MASTER control
448 - // for the whole frontend feature. When it's off, load NOTHING on the
449 - // front end — no CSS, no JS, no inline output — since every asset and
450 - // inline script path is gated through this method. (Admin dark mode is a
451 - // separate option handled elsewhere and is unaffected.)
452 - $enable_dark_mode_switch = isset($options['enable_dark_mode_switch'])
453 - ? $options['enable_dark_mode_switch']
454 - : true;
455 - if (! $enable_dark_mode_switch) {
456 - return False;
457 - }
458 -
459 132 /* Disable if Oxygen Builder is Opened */
460 133 if (isset($_GET['ct_builder'])) {
461 134 if ($_GET['ct_builder'] == "true") {
462 135 if (!isset($_GET['oxygen_iframe'])) {
@@ -485,24 +158,16 @@
485 158
486 159 function darkify_switch_in_menu($items, $args)
487 160 {
488 161 $options = get_option('darkify');
489 - $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : '';
490 -
491 - // Gate here (not at filter registration) so the live preview's unsaved
492 - // "Enable Menu Switch" toggle takes effect without saving.
493 - $enable_switcher_in_menu = isset($show_in_menu["enable_switch_in_menu"]) ? $show_in_menu["enable_switch_in_menu"] : false;
494 - if (! $enable_switcher_in_menu) {
495 - return $items;
496 - }
497 -
162 + $show_in_menu = $options['show_in_menu'];
498 163 $select_menus = isset($show_in_menu['select_menus']) ? $show_in_menu['select_menus'] : [];
499 -
164 +
500 165 $last_shortcode = '';
501 -
166 +
502 167 if ($this->darkify_is_dark_mode_allowed() && isset($args->menu->term_id)) {
503 168 foreach ($select_menus as $select_menu) {
504 -
169 +
505 170 $switch_in_menu_location = $select_menu['switch_in_menu_location'] ?? '';
506 171 $darkify_menu_shortcode_helper = $select_menu['darkify_menu_shortcode_helper'] ?? '';
507 172 $menu_position = isset($select_menu['select_menu_position']) ? $select_menu['select_menu_position'] : 'after';
508 173 if ($args->menu->term_id == $switch_in_menu_location) {
@@ -514,147 +179,6 @@
514 179 }
515 180 }
516 181
517 182 return $items;
518 - }
519 -
520 - /**
521 - * Put the switch inside a block-theme or Kadence navigation.
522 - *
523 - * `wp_nav_menu_items` only fires for classic wp_nav_menu() output, so on a
524 - * block theme (and on any site using Kadence Navigation) the Menu Switch
525 - * feature had nothing to hook: the menu rendered, the setting was saved, and
526 - * no switch ever appeared. These navigations are blocks, so this is the
527 - * equivalent hook for them.
528 - *
529 - * The selection is matched on the namespaced `<post_type>:<id>` key written
530 - * by the picker (see AbstractRestController::darkify_block_menu_sources()),
531 - * which is what keeps block and Kadence menu POST ids from being confused
532 - * with classic menu TERM ids.
533 - *
534 - * @param string $block_content Rendered HTML for this block.
535 - * @param array $block Parsed block, including its attributes.
536 - * @return string
537 - */
538 - function darkify_switch_in_block_menu($block_content, $block)
539 - {
540 - // render_block fires for every block on the page, so reject as early and
541 - // as cheaply as possible: two string comparisons for the overwhelming
542 - // majority of blocks, and nothing else.
543 - if (empty($block['blockName'])) {
544 - return $block_content;
545 - }
546 -
547 - if ($block['blockName'] === 'core/navigation') {
548 - $source = 'wp_navigation';
549 - $ref = isset($block['attrs']['ref']) ? (int) $block['attrs']['ref'] : 0;
550 - } elseif ($block['blockName'] === 'kadence/navigation') {
551 - $source = 'kadence_navigation';
552 - $ref = isset($block['attrs']['id']) ? (int) $block['attrs']['id'] : 0;
553 - } else {
554 - return $block_content;
555 - }
556 -
557 - // A navigation block with no saved menu behind it (unsaved, or rendering
558 - // its fallback) has nothing this setting could have been pointed at.
559 - if ($ref <= 0) {
560 - return $block_content;
561 - }
562 -
563 - $options = get_option('darkify');
564 - $show_in_menu = isset($options['show_in_menu']) ? $options['show_in_menu'] : '';
565 -
566 - // Gated here rather than at filter registration for the same reason as
567 - // the classic path: the live preview toggles this without saving.
568 - if (empty($show_in_menu['enable_switch_in_menu'])) {
569 - return $block_content;
570 - }
571 -
572 - if (! $this->darkify_is_dark_mode_allowed()) {
573 - return $block_content;
574 - }
575 -
576 - $select_menus = isset($show_in_menu['select_menus']) ? $show_in_menu['select_menus'] : [];
577 - $needle = $source . ':' . $ref;
578 - $shortcode = '';
579 - $position = 'after';
580 -
581 - foreach ((array) $select_menus as $select_menu) {
582 - if (($select_menu['switch_in_menu_location'] ?? '') !== $needle) {
583 - continue;
584 - }
585 - // Last match wins, matching the classic path's behaviour. Unlike
586 - // that path the position is read from the row that actually
587 - // matched, so it cannot be taken from an unrelated row.
588 - $shortcode = $select_menu['darkify_menu_shortcode_helper'] ?? '';
589 - $position = $select_menu['select_menu_position'] ?? 'after';
590 - }
591 -
592 - if (! $shortcode) {
593 - return $block_content;
594 - }
595 -
596 - $item = '<li class="menu-item">' . do_shortcode($shortcode) . '</li>';
597 -
598 - $injected = $this->darkify_inject_menu_item($block_content, $item, $position);
599 -
600 - // Unrecognised markup is left exactly as the block rendered it: a
601 - // navigation with no switch is a far better outcome than a broken one.
602 - return ($injected === null) ? $block_content : $injected;
603 - }
604 -
605 - /**
606 - * A `<ul>` or `</ul>` tag, with capture 1 set to "/" on a closing tag.
607 - *
608 - * The alternation skips over quoted attribute values rather than using a
609 - * plain `[^>]*`: an attribute may legitimately contain a `>` (`data-x="a>b"`),
610 - * and a bare `[^>]*` ends the match in the middle of that value. The open
611 - * tag would then appear to end mid-attribute, so a "before" insertion landed
612 - * inside the attribute and broke the markup, and a `>` inside a submenu's
613 - * own `<ul>` attributes miscounted the nesting depth below.
614 - */
615 - const DARKIFY_UL_TAG = '/<(\/?)ul\b(?:[^>"\']|"[^"]*"|\'[^\']*\')*>/i';
616 -
617 - /**
618 - * Insert a list item into the first top-level `<ul>` of a rendered block.
619 - *
620 - * The closing tag is found by balancing `<ul>`/`</ul>` rather than by
621 - * searching for the last `</ul>`: navigations nest submenus, so the last one
622 - * closes a submenu and the switch would land inside a dropdown.
623 - *
624 - * @return string|null Null when the markup has no usable list.
625 - */
626 - private function darkify_inject_menu_item($html, $item, $position)
627 - {
628 - if (! preg_match(self::DARKIFY_UL_TAG, $html, $open, PREG_OFFSET_CAPTURE)) {
629 - return null;
630 - }
631 -
632 - // A fragment that opens on a closing tag has no list to add to.
633 - if ($open[1][0] === '/') {
634 - return null;
635 - }
636 -
637 - $after_open = $open[0][1] + strlen($open[0][0]);
638 -
639 - if ($position === 'before') {
640 - return substr($html, 0, $after_open) . $item . substr($html, $after_open);
641 - }
642 -
643 - $depth = 1;
644 - $offset = $after_open;
645 - $tag = null;
646 -
647 - while ($depth > 0 && preg_match(self::DARKIFY_UL_TAG, $html, $tag, PREG_OFFSET_CAPTURE, $offset)) {
648 - $depth += ($tag[1][0] === '/') ? -1 : 1;
649 - $offset = $tag[0][1] + strlen($tag[0][0]);
650 - }
651 -
652 - if ($depth !== 0 || $tag === null) {
653 - return null;
654 - }
655 -
656 - $before_close = $offset - strlen($tag[0][0]);
657 -
658 - return substr($html, 0, $before_close) . $item . substr($html, $before_close);
659 183 }
660 184 }