PluginProbe
JCH Optimize / trunk
JCH Optimize vtrunk
6.0.2 6.0.1 trunk 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.10.0 2.2.0 2.2.1 All 58 releases
jch-optimize / src / Admin / Settings / ControlRenderer.php

ControlRenderer.php in JCH Optimize trunk, at src/Admin/Settings/ControlRenderer.php

759 lines 30.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * JCH Optimize - Performs several front-end optimizations for fast downloads
5 *
6 * @package jchoptimize/wordpress-platform
7 * @author Samuel Marshall <samuel@jch-optimize.net>
8 * @copyright Copyright (c) 2020 Samuel Marshall / JCH Optimize
9 * @license GNU/GPLv3, or later. See LICENSE file
10 *
11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
12 */
13
14 namespace JchOptimize\WordPress\Admin\Settings;
15
16 use JchOptimize\Core\Admin\MultiSelectItems;
17 use JchOptimize\Core\FeatureHelpers\ResponsiveImages;
18 use JchOptimize\WordPress\Container\ContainerFactory;
19
20 use function __;
21 use function array_key_exists;
22 use function array_unshift;
23 use function call_user_func;
24 use function call_user_func_array;
25 use function esc_attr_e;
26 use function explode;
27 use function get_option;
28 use function htmlspecialchars;
29 use function in_array;
30 use function is_array;
31 use function is_multisite;
32 use function ucfirst;
33 use function wp_create_nonce;
34
35 use const JSON_HEX_AMP;
36 use const JSON_HEX_APOS;
37 use const JSON_HEX_QUOT;
38 use const JSON_HEX_TAG;
39 use const JSON_UNESCAPED_SLASHES;
40 use const JSON_UNESCAPED_UNICODE;
41
42 class ControlRenderer
43 {
44 /**
45 * @param string $key
46 * @param string $settingName
47 * @param array|string $defaultValue
48 * @param mixed ...$args
49 *
50 * @return void
51 * @psalm-suppress InvalidGlobal
52 */
53 public static function render(string $key, string $settingName, array|string $defaultValue, ...$args): void
54 {
55 if (defined('JCH_OPTIMIZE_GET_WORDPRESS_SETTINGS')) {
56 global $jchParams;
57
58 $jchParams[$settingName] = $defaultValue;
59
60 return;
61 }
62
63 list($function, $proOnly) = static::extract($key);
64
65 if ($proOnly && !JCH_PRO) {
66 $html = '<div>
67 <em style="padding: 5px; background-color: white; border: 1px #ccc;">'
68 . __('Only available in Pro Version!', 'jch-optimize')
69 . ' </em>
70 </div>';
71
72 echo $html;
73
74 return;
75 }
76
77 $aSavedSettings = get_option('jch-optimize_settings');
78
79 if (!isset($aSavedSettings[$settingName])) {
80 $activeValue = $defaultValue;
81 } else {
82 $activeValue = $aSavedSettings[$settingName];
83 }
84
85 $callable = [__CLASS__, $function];
86
87 //prepend $settingName, $activeValue to arguments
88 array_unshift($args, $settingName, $activeValue);
89
90 call_user_func_array($callable, $args);
91 }
92
93 protected static function extract(string $key): array
94 {
95 $parts = explode('.', $key);
96
97 $function = $parts[0];
98 $proOnly = isset($parts[1]) && $parts[1] === 'pro';
99
100 return [$function, $proOnly];
101 }
102
103 public static function description(string $title, string $description, bool $new = false): string
104 {
105 $popOver = $description
106 ? "class=\"hasPopover pe-1\" data-bs-content=\"$description\" data-bs-title=\"$title\""
107 : '';
108 $html = "<div $popOver style=\"width: fit-content;\">" . $title;
109
110 if ($description) {
111 $html .= '<span>
112 <span class="far fa-question-circle text-muted opacity-75 ms-1"></span>
113 </span>';
114 }
115
116 if ($new) {
117 $html .= '<span class="badge bg-danger ms-1">New!</span>';
118 }
119
120 $html .= '</div>';
121
122 return $html;
123 }
124
125 public static function radio(string $settingName, string $activeValue, string $class = ''): void
126 {
127 $disabled = ($settingName == 'pro_capture_cache_enable' && is_multisite()) ? 'disabled' : '';
128 ?>
129 <fieldset id="jch-optimize_settings_<?= $settingName; ?>" class="btn-group <?= $class ?>"
130 role="group" aria-label="Radio toggle button group">
131 <input type="radio" id="jch-optimize_settings_<?= $settingName; ?>0"
132 name="<?= "jch-optimize_settings[{$settingName}]"; ?>" class="btn-check" value="0"
133 <?= ($activeValue == '0' ? 'checked' : ''); ?> <?= $disabled; ?> >
134 <label for="jch-optimize_settings_<?= $settingName ?>0" class="btn btn-outline-secondary">
135 <?php _e('No', 'jch-optimize'); ?>
136 </label>
137 <input type="radio" id="jch-optimize_settings_<?= $settingName ?>1"
138 name="<?= "jch-optimize_settings[{$settingName}]"; ?>" class="btn-check" value="1"
139 <?= ($activeValue == '1' ? 'checked' : ''); ?> <?= $disabled; ?> >
140 <label for="jch-optimize_settings_<?= $settingName; ?>1" class="btn btn-outline-secondary">
141 <?php _e('Yes', 'jch-optimize'); ?>
142 </label>
143 </fieldset>
144 <?php
145 }
146
147 public static function switch(string $settingName, string $activeValue, string $class = ''): void
148 {
149 $disabled = ($settingName == 'pro_capture_cache_enable' && is_multisite()) ? 'disabled' : '';
150 ?>
151 <div class="form-check form-switch d-flex align-items-center <?= $class ?>">
152 <input class="form-check-input me-2" type="checkbox" role="switch"
153 id="jch-optimize_settings_<?= $settingName; ?>"
154 name="<?= "jch-optimize_settings[{$settingName}]"; ?>" <?= $activeValue == '1' ? 'checked' : ''; ?>
155 value="1" <?= $disabled; ?>>
156 <label class="form-check-label" for="jch-optimize_settings_<?= $settingName; ?>">
157 <?php
158 $activeValue == '1' ? _e('Yes', 'jch-optimize') : _e('No', 'jch-optimize'); ?>
159 </label>
160 <?php
161 if ($activeValue != '1') : ?>
162 <input type="hidden" name="<?= "jch-optimize_settings[{$settingName}]"; ?>" value="0">
163 <?php
164 endif; ?>
165 <script>
166 document.getElementById('jch-optimize_settings_<?= $settingName; ?>').addEventListener('click', (e) => {
167 const label = document.querySelector('label[for="jch-optimize_settings_<?= $settingName; ?>"]')
168 if (e.target.checked) {
169 label.innerText = "<?php _e('Yes', 'jch-optimize'); ?>"
170 const input = document.querySelector('input[type="hidden"][name="jch-optimize_settings[<?= $settingName; ?>]"]')
171 if (input) {
172 input.remove()
173 }
174 } else {
175 label.innerText = "<?php _e('No', 'jch-optimize'); ?>"
176 const input = document.createElement('input')
177 input.type = 'hidden'
178 input.name = 'jch-optimize_settings[<?= $settingName; ?>]'
179 input.value = '0'
180 label.after(input)
181 }
182 })
183 </script>
184 </div>
185
186 <?php
187 }
188
189 public static function select(
190 string $settingName,
191 string $activeValue,
192 array $options,
193 string $class = '',
194 array $conditions = []
195 ): void {
196 ?>
197 <select id="jch-optimize_settings_<?= $settingName; ?>"
198 name="<?= "jch-optimize_settings[{$settingName}]"; ?>"
199 class="<?= $class; ?> form-select">
200 <?= self::option($options, $activeValue, $conditions); ?>
201 </select>
202 <?php
203 }
204
205 public static function option(array $options, ?string $activeValue, array $conditions = []): string
206 {
207 $html = '';
208
209 foreach ($options as $key => $value) {
210 $selected = $activeValue == $key ? ' selected' : '';
211 $disabled = '';
212
213 if (!empty($conditions) && array_key_exists($key, $conditions)) {
214 if (!call_user_func($conditions[$key])) {
215 $disabled = ' disabled';
216 }
217 }
218
219 $html .= '<option value="' . esc_attr($key) . '"'
220 . $selected
221 . $disabled
222 . '>' . $value . '</option>';
223 }
224
225 return $html;
226 }
227
228 public static function multiselect(
229 string $settingName,
230 array $activeValues,
231 string $type,
232 string $group,
233 string $class = ''
234 ): void {
235 $container = ContainerFactory::getInstance();
236 $multiSelect = $container->buildObject(MultiSelectItems::class);
237 ?>
238 <div id="div-<?= $settingName ?>">
239 <select id="jch-optimize_settings_<?= $settingName; ?>"
240 name="<?= "jch-optimize_settings[{$settingName}]"; ?>[]"
241 class="jch-multiselect chzn-custom-value <?= $class ?>" multiple="multiple" size="5"
242 data-jch_type="<?= $type; ?>" data-jch_group="<?= $group; ?>"
243 data-jch_param="<?= $settingName; ?>">
244 <option value="">Type or click to select</option>
245 <?php
246 foreach ($activeValues as $value) :
247 $option = $multiSelect->{'prepare' . ucfirst($group) . 'Values'}($value);
248 ?>
249 <option value="<?php
250 esc_attr_e($value) ?>" selected><?= $option; ?></option>
251 <?php endforeach; ?>
252 </select>
253 <button id="btn-<?= $settingName; ?>" class="btn btn-secondary btn-sm jch-multiselect-add-button"
254 type="button">Add item
255 </button>
256 </div>
257 <?php
258 }
259
260 public static function multiselectjs(
261 string $settingName,
262 array $activeValues,
263 string $type,
264 string $group,
265 string $valueType,
266 array $subfields,
267 string $class = ''
268 ): void {
269 $container = ContainerFactory::getInstance();
270 $multiSelect = $container->buildObject(MultiSelectItems::class);
271 $nextIndex = count($activeValues);
272 $i = 0;
273 $jsSubfields = [];
274 $subfieldsCount = count($subfields);
275 foreach ($subfields as $sf) {
276 $cfg = [
277 'name' => $sf['name'],
278 'type' => $sf['type'],
279 ];
280 if (!empty($sf['class'])) {
281 $cfg['class'] = $sf['class'];
282 }
283 if (array_key_exists('checked', $sf)) {
284 $cfg['checked'] = $sf['checked'];
285 }
286 if (array_key_exists('defaultValue', $sf)) {
287 $cfg['defaultValue'] = $sf['defaultValue'];
288 }
289 if (!empty($sf['options']) && is_array($sf['options'])) {
290 $cfg['options'] = $sf['options'];
291 }
292
293 $jsSubfields[] = $cfg;
294 }
295 $json = json_encode(
296 $jsSubfields,
297 JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_AMP | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
298 );
299 ?>
300 <script type="application/json" id="subfields-<?= $settingName; ?>">
301 <?= $json; ?>
302
303
304
305
306
307
308
309
310
311 </script>
312 <fieldset id="fieldset-<?= $settingName; ?>" data-index="<?= $nextIndex; ?>"
313 data-value-type="<?php esc_attr_e($valueType); ?>"
314 class="mb-1 mt-2 jch-ms-fieldset-grid" style="--jch-ms-subfield-count: <?= $subfieldsCount ?>"
315 >
316 <?php if ($subfields) : ?>
317 <?php foreach ($subfields as $subfield) : ?>
318 <?php if (!empty($subfield['header'])) : ?>
319 <span class="jch-ms-<?php esc_attr_e($subfield['name']); ?>-header jch-ms-cell jch-ms-header">
320 &nbsp;&nbsp;<?= $subfield['header'] ?>&nbsp;&nbsp;&nbsp;
321 </span>
322 <?php endif; ?>
323 <?php endforeach; ?>
324 <?php endif; ?>
325 <?php foreach ($activeValues as $value) : ?>
326 <?php
327 if (isset($value[$valueType]) && is_string($value[$valueType])) : ?>
328 <?php
329 $method = 'prepare' . ucfirst($group) . 'Values';
330 if ($multiSelect && method_exists($multiSelect, $method)) {
331 $dataValue = $multiSelect->$method($value[$valueType]);
332 } else {
333 $dataValue = $value[$valueType];
334 }
335 ?>
336 <span class="group<?php esc_attr_e($i) ?> jch-ms-excludes jch-ms-cell">
337 <span>
338 <input type="text" readonly
339 value="<?php esc_attr_e($value[$valueType]); ?>"
340 name="<?= "jch-optimize_settings[{$settingName}][{$i}][{$valueType}]"; ?>"
341 >
342 <?= $dataValue; ?>
343 <button type="button" class="jch-multiselect-remove-button">
344 <?php _e('Remove Item', 'jch-optimize'); ?>
345 </button>
346 </span>
347 </span>
348 <?php foreach ($subfields as $subfield) : ?>
349 <?php
350 self::subFormField(
351 $subfield['type'],
352 'jch-ms-' . $subfield['name'],
353 $value,
354 $settingName,
355 $i,
356 $subfield['name'],
357 $subfield
358 ); ?>
359 <?php endforeach; ?>
360 <?php $i++; ?>
361 <?php endif; ?>
362 <?php endforeach; ?>
363 </fieldset>
364 <div id="div-<?= $settingName ?>">
365 <select id="jch-optimize_settings_<?= $settingName; ?>"
366 name="<?= "jch-optimize_settings[{$settingName}]"; ?>[]"
367 class="jch-multiselect chzn-custom-value <?= $class ?>" multiple="multiple" size="5"
368 data-jch_type="<?= $type; ?>" data-jch_group="<?= $group; ?>"
369 data-jch_param="<?= $settingName; ?>">
370 <option value="">Type or click to select</option>
371 </select>
372 <button id="btn-<?= $settingName; ?>"
373 class="btn btn-secondary btn-sm jch-multiselect-add-button" type="button"
374 ><?php _e('Add item', 'jch-optimize'); ?></button>
375 </div>
376
377 <?php
378 }
379
380 private static function subFormField(
381 string $type,
382 string $subFieldClass,
383 array $value,
384 string $settingName,
385 int $index,
386 string $option,
387 array $config
388 ): void {
389 if ($type == 'checkbox') :
390 ?>
391 <span class="group<?php esc_attr_e((string)$index) ?> <?php esc_attr_e(
392 $subFieldClass
393 ) ?> jch-ms-cell has-subfield has-checkbox">
394 <input type="checkbox"
395 class="subfield m-0 p-0 fs-6 form-check-input <?php esc_attr_e($config['class'] ?? '') ?>"
396 name="<?= "jch-optimize_settings[{$settingName}][{$index}][{$option}]" ?>"
397 <?= isset($value[$option]) ? 'checked' : '' ?>>
398 </span>
399 <?php elseif ($type == 'text') : ?>
400 <span class="group<?php esc_attr_e((string)$index) ?> <?php esc_attr_e(
401 $subFieldClass
402 ) ?> jch-ms-cell has-subfield has-text">
403 <input type="text" name="jch-optimize_settings[<?= $settingName ?>][<?= $index ?>][<?= $option ?>]"
404 class="subfield form-control form-control-sm <?php esc_attr_e($config['class'] ?? '') ?>"
405 value="<?= $value[$option] ?? ResponsiveImages::$breakpoints[0] ?>">
406 </span>
407 <?php elseif ($type == 'select') : ?>
408 <?php
409 $current = $value[$option] ?? ($config['defaultValue'] ?? null);
410 if ($current === null && isset($config['legacy'])) {
411 $current = '';
412 foreach ($config['legacy'] as $legacyKey) {
413 if (!empty($value[$legacyKey])) {
414 $current = (string)$legacyKey;
415 break;
416 }
417 }
418 }
419 $options = $config['options'] ?? [];
420 ?>
421 <span class="group<?php esc_attr_e($index); ?> <?php esc_attr_e(
422 $subFieldClass
423 ) ?> jch-ms-cell has-subfield has-select">
424 <select name="jch-optimize_settings[<?= $settingName ?>][<?= $index ?>][<?= $option ?>]"
425 class="subfield form-select form-select-sm <?php esc_attr_e($config['class'] ?? '') ?>">
426 <?php foreach ($options as $opt) : ?>
427 <?php
428 $val = (string)($opt['value'] ?? '');
429 $text = (string)($opt['text'] ?? $val);
430 $selected = ($val === $current);
431 ?>
432 <option value="<?php esc_attr_e($val); ?>" <?= $selected ? 'selected' : ''; ?>>
433 <?= htmlspecialchars($text); ?>
434 </option>
435 <?php endforeach; ?>
436 </select>
437 </span>
438 <?php
439 endif;
440 }
441
442 public static function text(string $settingName, string $activeValue, string $size = '30', string $class = ''): void
443 {
444 ?>
445 <input type="text" id="jch-optimize_settings_<?= $settingName; ?>"
446 name="<?= "jch-optimize_settings[{$settingName}]"; ?>"
447 value="<?php esc_attr_e($activeValue); ?>" size="<?= $size; ?>" class="form-control <?= $class; ?>">
448 <?php
449 }
450
451 public static function hidden(string $settingName, string $activeValue): void
452 {
453 ?>
454 <input type="hidden" id="jch-optimize_settings_<?= $settingName; ?>"
455 name="<?= "jch-optimize_settings[{$settingName}]"; ?>"
456 value="<?php esc_attr_e($activeValue); ?>">
457 <?php
458 }
459
460 public static function input(
461 string $settingName,
462 string $activeValue,
463 string $type = 'text',
464 array $attr = []
465 ): void {
466 ?>
467 <input type="<?= $type; ?>"
468 id="jch-optimize_settings_<?= $settingName; ?>"
469 name="<?= "jch-optimize_settings[{$settingName}]"; ?>"
470 value="<?php esc_attr_e($activeValue); ?>"
471 class="form-control"
472 <?= self::attrArrayToString($attr); ?>>
473
474 <?php
475 }
476
477 private static function attrArrayToString(array $attr): string
478 {
479 $attrString = '';
480
481 foreach ($attr as $name => $value) {
482 $attrString .= $name . '="' . $value . '" ';
483 }
484
485 return $attrString;
486 }
487
488 public static function checkbox(string $settingName, string $activeValue, string $class = ''): void
489 {
490 $checked = $activeValue == '1' ? 'checked="checked"' : '';
491 ?>
492 <input type="checkbox" id="jch-optimize_settings_<?= $settingName; ?>"
493 class="m-0 p-2 form-check-input <?= $class; ?>"
494 name="<?= "jch-optimize_settings[{$settingName}]"; ?>" data-toggle="toggle"
495 data-onstyle="success" data-offstyle="danger"
496 data-on="<?php _e('Yes', 'jch-optimize'); ?>"
497 data-off="<?php _e('No', 'jch-optimize'); ?>" value="1" <?= $checked; ?>>
498 <?php
499 }
500
501 public static function checkboxes(
502 string $settingName,
503 array $activeValues,
504 array $options,
505 string $class = ''
506 ): void {
507 $i = 0;
508 $cols = count($options);
509 if ($cols > 7) {
510 $cols = ceil($cols / 2);
511 }
512
513 ?>
514 <fieldset id="jch-optimize_settings_<?= $settingName; ?>" class="<?= $class; ?> float-start">
515 <ul class="grid p-0" style="--bs-columns: <?= $cols ?>; --bs-gap: 0.3rem;">
516 <?php
517 foreach ($options as $key => $value) :
518 $checked = (in_array($key, $activeValues)) ? 'checked' : '';
519 ?>
520 <li class="g-col-1">
521 <input type="checkbox" id="jch-optimize_settings_<?= $settingName . $i; ?>"
522 class="mt-0 p-2 form-check-input"
523 name="<?= "jch-optimize_settings[{$settingName}]"; ?>[]"
524 value="<?php esc_attr_e($key); ?>" <?= $checked; ?>>
525 <label for="jch-optimize_settings_<?= $settingName . $i; ?>">
526 <?= $value; ?>
527 </label>
528
529 </li>
530 <?php
531 $i++;
532 endforeach;
533 ?>
534 </ul>
535 </fieldset>
536 <?php
537 }
538
539 public static function textarea(string $settingName, $activeValues): void
540 {
541 ?>
542 <textarea name="<?= "jch-optimize_settings[{$settingName}]"; ?>" cols="35" rows="3" class="form-control">
543 <?= $activeValues ?>
544 </textarea>
545 <?php
546 }
547
548 public static function criticaljsmodalbutton(string $settingName, $activeValue): void
549 {
550 ?>
551 <button type="button" class="button button-primary button-large" id="criticalJsModalLaunchButton">
552 Open
553 </button>
554 </div>
555 <?php
556 }
557
558 public static function verifycftoken(string $settingName, $activeValue): void
559 {
560 $cfVerifyNonce = wp_create_nonce('jch_optimize_verify_cf_token')
561 ?>
562 <style>
563 .cf-verify-wrap { /* already position-relative from markup */
564 }
565
566 .cf-float-alert {
567 position: absolute;
568 left: 0;
569 right: 0;
570 top: -0.5rem; /* sits just above the input */
571 transform: translateY(-100%);
572 z-index: 1060; /* above form controls; < modal (1050–1060) */
573 pointer-events: none; /* don’t steal focus/mouse */
574 width: 400px;
575 }
576
577 .cf-float-alert .alert {
578 pointer-events: auto; /* allow dismiss button if you add one later */
579 box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
580 }
581
582 .cf-verify-wrap .password-group {
583 width: 75%;
584 }
585 </style>
586 <div class="cf-verify-wrap d-flex align-items-start gap-2 position-relative">
587 <?php self::input($settingName, $activeValue, 'password') ?>
588 <!-- New verify button beside the field -->
589 <button type="button" class="btn btn-outline-secondary btn-sm" id="cfVerifyBtn"
590 aria-label="Verify Cloudflare API token">
591 Verify Token
592 </button>
593
594 <!-- Floating alert container (JS will fill it) -->
595 <div class="cf-float-alert" aria-live="polite" aria-atomic="true"></div>
596 </div>
597 <script>
598 (() => {
599 const JchVerifyCfApi = () => {
600 window.JCH_CF_VERIFY_ENDPOINT = window.JCH_CF_VERIFY_ENDPOINT ||
601 ajaxurl + '?action=jch_cf_verify&_wpnonce=<?= $cfVerifyNonce ?>'
602
603 const btn = document.getElementById('cfVerifyBtn') // existing button
604 const tokenEl = document.getElementById('jch-optimize_settings_cf_api_token') // token input
605 const zoneEl = document.getElementById('jch-optimize_settings_cf_zone_id') // zone input
606 const wrap = btn?.closest('.cf-verify-wrap') // from earlier markup
607 const alertHost = wrap?.querySelector('.cf-float-alert')
608 const badge = document.getElementById('cfZoneVerifiedBadge') // optional badge near zone field
609 const modeSwitcherEnabled = () =>
610 document.getElementById('jch-optimize_settings_cf_mode_switcher_integration').checked
611
612 function showAlert (html, type = 'success', ttl = 4000) {
613 if (!alertHost) return
614 alertHost.innerHTML = ''
615 const el = document.createElement('div')
616 el.className = `alert alert-${type} fade show`
617 el.role = 'alert'
618 el.innerHTML = html
619 alertHost.appendChild(el)
620 setTimeout(() => {
621 el.classList.remove('show')
622 setTimeout(() => alertHost.innerHTML = '', 200)
623 }, ttl)
624 }
625
626 function setBusy (b, busy) {
627 if (!b) return
628 if (busy) {
629 b.disabled = true
630 b.dataset._orig = b.innerHTML
631 b.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
632 <span class="ms-2">Verifying…</span>`
633 } else {
634 b.disabled = false
635 if (b.dataset._orig) b.innerHTML = b.dataset._orig
636 }
637 }
638
639 btn?.addEventListener('click', async () => {
640 const token = tokenEl?.value.trim() || ''
641 const zone = zoneEl?.value.trim() || ''
642
643 if (!token) {
644 showAlert('Please enter your Cloudflare API token.', 'warning')
645 return
646 }
647
648 // Joomla CSRF token name (J4/J5)
649 const csrfName = (window.Joomla && Joomla.getOptions)
650 ? (Joomla.getOptions('csrf.token') || '') : ''
651
652 setBusy(btn, true)
653 badge?.classList.add('d-none')
654
655 try {
656 const fd = new FormData()
657 fd.append('token', token)
658 if (zone) fd.append('zone_id', zone)
659 if (csrfName) fd.append(csrfName, '1')
660
661 const res = await fetch(window.JCH_CF_VERIFY_ENDPOINT, {
662 method: 'POST',
663 body: fd,
664 credentials: 'same-origin'
665 })
666 const json = await res.json().catch(() => ({}))
667
668 // Expect shape from PHP: { success, token_status, zone_read?, purge_ok?, verified_at? }
669 const ok = res.ok && (json.success === true || (json.data && json.data.success === true))
670 const data = (json.data && json.data.success !== undefined) ? json.data : json
671
672 if (ok) {
673 // Build a nice message
674 const getAlertConfig = (data) => {
675 const steps = [
676 {
677 key: 'token',
678 check: () => data.token_status,
679 failStatus: 'danger',
680 fatal: true
681 },
682 {
683 key: 'zone',
684 check: () => data.zone_read,
685 failStatus: 'warning',
686 fatal: true
687 },
688 { key: 'purge', check: () => data.purge_ok, failStatus: 'warning' },
689 {
690 key: 'settings',
691 check: () => data.settings_edit_ok,
692 failStatus: 'warning',
693 condition: modeSwitcherEnabled
694 }
695 ]
696
697 let status = 'success'
698 const lines = []
699
700 for (const { key, check, failStatus, condition, fatal } of steps) {
701 if (condition && !condition()) continue // Skip if optional condition fails
702
703 lines.push(data.messages[key])
704
705 if (!check()) {
706 status = failStatus
707
708 if (fatal) {
709 break // Stop processing further steps on failure
710 }
711 }
712 }
713
714 return { message: lines.join('<br>'), status }
715 }
716
717 // Usage
718 const { message, status } = getAlertConfig(data)
719 showAlert(message, status, 6000)
720
721 // Show "Verified" badge when zone was part of the check and both read+purge passed
722 if (zone && data.zone_read && data.purge_ok && badge) {
723 badge.classList.remove('d-none')
724 // Optional: show time since verification
725 if (data.verified_at) {
726 const dt = new Date(data.verified_at)
727 if (!isNaN(+dt)) {
728 const mins = Math.max(0, Math.round((Date.now() - dt.getTime()) / 60000))
729 badge.textContent = mins < 1 ? 'Verified just now'
730 : mins === 1 ? 'Verified 1 min ago'
731 : `Verified ${mins} mins ago`
732 } else {
733 badge.textContent = 'Verified'
734 }
735 } else {
736 badge.textContent = 'Verified'
737 }
738 }
739 } else {
740 const msg = json.message || (json.data && json.data.message) || 'Verification failed.'
741 showAlert(msg, 'danger')
742 }
743 } catch (err) {
744 showAlert('Network error. Please try again.', 'danger')
745 } finally {
746 setBusy(btn, false)
747 }
748 })
749 }
750
751 (document.readyState === 'loading')
752 ? document.addEventListener('DOMContentLoaded', JchVerifyCfApi, { once: true })
753 : JchVerifyCfApi()
754 })()
755 </script>
756 <?php
757 }
758 }
759