PluginProbe
Countdown Timer – Widget Countdown / trunk
Countdown Timer – Widget Countdown vtrunk
2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.0 All 182 releases
widget-countdown / includes / admin / Fields.php

Fields.php in Countdown Timer – Widget Countdown trunk, at includes/admin/Fields.php

874 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit;
3
4 /**
5 * Admin Fields — renders form field HTML for admin settings pages.
6 *
7 * Every output is properly escaped. Each method is self-contained.
8 * Also provides color and animation utility helpers used across the plugin.
9 */
10 class Wpda_Countdown_Admin_Fields {
11
12 /**
13 * Top admin-page banner with pro upgrade card (free only) + support link.
14 * Called from every admin page EXCEPT Featured Plugins and Hire an Expert.
15 */
16 public static function render_pro_banner() {
17 ?>
18 <div class="wpdevart_plugins_header div-for-clear">
19 <?php if ( ! WPDA_COUNTDOWN_IS_PRO ) : ?>
20 <div class="wpdevart_plugins_get_pro div-for-clear">
21 <div class="wpdevart_plugins_get_pro_info">
22 <h3>WpDevArt Countdown Premium</h3>
23 <p>Powerful and Customizable Countdown Timer</p>
24 </div>
25 <a target="_blank" href="https://wpdevart.com/wordpress-countdown-plugin/" class="wpdevart_upgrade">Upgrade</a>
26 </div>
27 <?php endif; ?>
28 <a target="_blank" href="<?php echo esc_url( wpdevart_countdown_support_url ); ?>" class="wpdevart_support">Have any Questions? Get quick support!</a>
29 </div>
30 <?php
31 // Free-only: alert users when they pick a Pro-labeled option from a dropdown
32 if ( ! WPDA_COUNTDOWN_IS_PRO ) {
33 self::print_pro_lock_script();
34 }
35 }
36
37 /**
38 * Free-mode interactive locks:
39 * 1. A `(Pro)` option inside a dropdown reverts with an alert.
40 * 2. Any interaction inside a fully pro-locked page (Popup, Sticky Bar)
41 * intercepts and shows the same alert.
42 * 3. Styles the "(pro)" badge appended by description_cell().
43 */
44 private static function print_pro_lock_script() {
45 $alert_msg = 'This feature is available only in the Pro version. Click the Upgrade button at the top of the page to learn more.';
46 ?>
47 <style>
48 .wpda_pro_badge{display:inline-block;margin-left:6px;padding:1px 7px;font-size:11px;font-weight:600;color:#7052fb;background:rgba(112,82,251,0.08);border:1px solid rgba(112,82,251,0.25);border-radius:10px;vertical-align:middle;letter-spacing:.3px;}
49 .wpda_pro_locked_page{position:relative;}
50 </style>
51 <script>
52 (function(){
53 var PRO_ALERT = <?php echo wp_json_encode( $alert_msg ); ?>;
54 var lastAlertAt = 0;
55 function showProAlert(){
56 var now = Date.now();
57 if (now - lastAlertAt < 400) return; // de-dup rapid event bursts
58 lastAlertAt = now;
59 alert(PRO_ALERT);
60 }
61
62 function lockProOptions(){
63 document.querySelectorAll('select').forEach(function(sel){
64 var hasPro = Array.prototype.some.call(sel.options, function(o){
65 return o.textContent.indexOf('(Pro)') !== -1;
66 });
67 if (!hasPro) return;
68 sel.addEventListener('focus', function(){ this._prevVal = this.value; });
69 sel.addEventListener('change', function(){
70 var opt = this.options[this.selectedIndex];
71 if (opt && opt.textContent.indexOf('(Pro)') !== -1) {
72 showProAlert();
73 this.value = (typeof this._prevVal !== 'undefined') ? this._prevVal : '';
74 if (!this.value) {
75 for (var i = 0; i < this.options.length; i++) {
76 if (this.options[i].textContent.indexOf('(Pro)') === -1) {
77 this.selectedIndex = i;
78 break;
79 }
80 }
81 }
82 }
83 });
84 });
85 }
86
87 function lockFullPage(){
88 var containers = document.querySelectorAll('.wpda_pro_locked_page');
89 if (!containers.length) return;
90 var stop = function(e){
91 // Allow collapsible section headers to open/close so users can browse settings
92 if (e.target.closest && e.target.closest('.head_panel_div')) return;
93 e.preventDefault();
94 e.stopPropagation();
95 showProAlert();
96 };
97 containers.forEach(function(c){
98 ['mousedown','click','keydown','change','input'].forEach(function(evt){
99 c.addEventListener(evt, stop, true);
100 });
101 });
102 }
103
104 function lockProRows(){
105 // Mark any row containing a pro field so CSS + event hooks can find it
106 document.querySelectorAll('.wpda_pro_field').forEach(function(cell){
107 var row = cell.closest('tr');
108 if (row) row.classList.add('wpda_pro_locked_row');
109 });
110 var stop = function(e){
111 e.preventDefault();
112 e.stopPropagation();
113 showProAlert();
114 };
115 document.querySelectorAll('.wpda_pro_locked_row').forEach(function(row){
116 ['mousedown','click','keydown','change','input'].forEach(function(evt){
117 row.addEventListener(evt, stop, true);
118 });
119 });
120 }
121
122 function init(){
123 lockProOptions();
124 lockFullPage();
125 lockProRows();
126 }
127 if (document.readyState === 'loading') {
128 document.addEventListener('DOMContentLoaded', init);
129 } else {
130 init();
131 }
132 })();
133 </script>
134 <?php
135 }
136
137 /**
138 * Section heading row.
139 */
140 public static function heading( $name, $group ) {
141 ?>
142 <tr class="<?php echo esc_attr( $group ); ?> tr_heading">
143 <th colspan="2"><?php echo esc_html( $name ); ?></th>
144 </tr>
145 <?php
146 }
147
148 /**
149 * Left-side description cell (shared by all field types).
150 *
151 * Full-pro field — $args['pro'] === true. Row gets .wpda_pro_field and the
152 * JS lock blocks every interaction (e.g. popup/sticky fields, theme Pro
153 * spacing/sizing, vertical/circle/flip groups).
154 *
155 * Partial-pro field — at least one option in $args['values'] carries "(Pro)"
156 * but others are free (e.g. Timer type, Repeat, After-timer action, theme
157 * countdown-type). Only the badge shows; option-level lock (lockProOptions)
158 * blocks pro option picks while still letting the user choose free options.
159 */
160 public static function description_cell( $args ) {
161 $is_full_pro = ! empty( $args['pro'] ) && $args['pro'] === true;
162 $has_pro_option = false;
163 if ( ! $is_full_pro && ! empty( $args['values'] ) && is_array( $args['values'] ) ) {
164 foreach ( $args['values'] as $v ) {
165 if ( is_string( $v ) && strpos( $v, '(Pro)' ) !== false ) { $has_pro_option = true; break; }
166 if ( is_array( $v ) ) {
167 foreach ( $v as $sub ) {
168 if ( is_string( $sub ) && strpos( $sub, '(Pro)' ) !== false ) { $has_pro_option = true; break 2; }
169 }
170 }
171 }
172 }
173 $show_badge = $is_full_pro || $has_pro_option;
174 ?>
175 <td class="td_option_description<?php echo $is_full_pro ? ' wpda_pro_field' : ''; ?>">
176 <?php if ( ! empty( $args['description'] ) ) : ?>
177 <span class="wpdevart-info-container">?<span class="wpdevart-info"><?php echo wp_kses_post( $args['description'] ); ?></span></span>
178 <?php endif; ?>
179 <span class="wpdevart-title"><?php echo esc_html( $args['title'] ); ?></span>
180 <?php if ( $show_badge ) : ?>
181 <span class="wpda_pro_badge">(pro)</span>
182 <?php endif; ?>
183 </td>
184 <?php
185 }
186
187 /**
188 * Simple text/number input.
189 */
190 public static function simple_input( $args ) {
191 $type = isset( $args['type'] ) ? $args['type'] : 'text';
192 $value = isset( $args['value'] ) ? $args['value'] : '';
193 $small = isset( $args['small_text'] ) ? $args['small_text'] : '';
194 ?>
195 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
196 <?php self::description_cell( $args ); ?>
197 <td>
198 <input type="<?php echo esc_attr( $type ); ?>"
199 value="<?php echo esc_attr( $value ); ?>"
200 id="<?php echo esc_attr( $args['name'] ); ?>"
201 name="<?php echo esc_attr( $args['name'] ); ?>">
202 <?php if ( $small ) : ?>
203 <small><?php echo esc_html( $small ); ?></small>
204 <?php endif; ?>
205 </td>
206 </tr>
207 <?php
208 }
209
210 /**
211 * Hidden input (no visible row).
212 */
213 public static function hidden_input( $args ) {
214 // no visible output — value loaded/saved via the options system
215 }
216
217 /**
218 * Number input with a unit dropdown (px / %).
219 */
220 public static function input_with_unit( $args ) {
221 $input_name = $args['name'];
222 $unit_name = isset( $args['unit_name'] ) ? $args['unit_name'] : '';
223 $input_val = isset( $args['value'] ) ? $args['value'] : '';
224 $unit_val = isset( $args['unit_value'] ) ? $args['unit_value'] : ( isset( $args['unit_default'] ) ? $args['unit_default'] : '' );
225 $units = isset( $args['units'] ) ? $args['units'] : array();
226 ?>
227 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
228 <?php self::description_cell( $args ); ?>
229 <td>
230 <div style="display:inline-flex;align-items:center;gap:4px;">
231 <input type="number"
232 value="<?php echo esc_attr( $input_val ); ?>"
233 id="<?php echo esc_attr( $input_name ); ?>"
234 name="<?php echo esc_attr( $input_name ); ?>"
235 style="width:80px;">
236 <select id="<?php echo esc_attr( $unit_name ); ?>"
237 name="<?php echo esc_attr( $unit_name ); ?>"
238 style="min-width:70px;">
239 <?php foreach ( $units as $k => $v ) : ?>
240 <option value="<?php echo esc_attr( $k ); ?>" <?php selected( $k, $unit_val ); ?>>
241 <?php echo esc_html( $v ); ?>
242 </option>
243 <?php endforeach; ?>
244 </select>
245 </div>
246 </td>
247 </tr>
248 <?php
249 }
250
251 /**
252 * Multiple small inputs in one row (e.g., days/hours/minutes).
253 */
254 public static function many_inputs( $args ) {
255 ?>
256 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
257 <?php self::description_cell( $args ); ?>
258 <td>
259 <?php foreach ( $args['values'] as $key => $default ) :
260 $type = isset( $args['type'][ $key ] ) ? $args['type'][ $key ] : 'text';
261 $val = isset( $args['value'][ $key ] ) ? $args['value'][ $key ] : ( isset( $args['default_value'][ $key ] ) ? $args['default_value'][ $key ] : '' );
262 $small = isset( $args['small_text'][ $key ] ) ? $args['small_text'][ $key ] : '';
263 ?>
264 <input type="<?php echo esc_attr( $type ); ?>"
265 value="<?php echo esc_attr( $val ); ?>"
266 id="<?php echo esc_attr( $args['name'] . '_' . $key . '_id' ); ?>"
267 name="<?php echo esc_attr( $args['name'] . '[' . $key . ']' ); ?>">
268 <?php if ( $small ) : ?>
269 <small><?php echo esc_html( $small ); ?></small>
270 <?php endif; ?>
271 <?php endforeach; ?>
272 </td>
273 </tr>
274 <?php
275 }
276
277 /**
278 * Color picker input.
279 */
280 public static function color_input( $args ) {
281 $value = isset( $args['value'] ) ? $args['value'] : '';
282 $default = isset( $args['default_value'] ) ? $args['default_value'] : $value;
283 ?>
284 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
285 <?php self::description_cell( $args ); ?>
286 <td class="wpda_color-picker">
287 <input type="text" class="color"
288 value="<?php echo esc_attr( $value ); ?>"
289 data-default-color="<?php echo esc_attr( $default ); ?>"
290 id="<?php echo esc_attr( $args['name'] ); ?>"
291 name="<?php echo esc_attr( $args['name'] ); ?>">
292 <script type="text/javascript">
293 jQuery(document).ready(function() {
294 jQuery('#<?php echo esc_js( $args['name'] ); ?>').wpColorPicker();
295 });
296 </script>
297 </td>
298 </tr>
299 <?php
300 }
301
302 /**
303 * Standard <select> dropdown.
304 */
305 public static function simple_select( $args ) {
306 $current = isset( $args['value'] ) ? $args['value'] : '';
307 ?>
308 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
309 <?php self::description_cell( $args ); ?>
310 <td>
311 <select id="<?php echo esc_attr( $args['name'] ); ?>"
312 name="<?php echo esc_attr( $args['name'] ); ?>">
313 <?php foreach ( $args['values'] as $key => $value ) :
314 if ( ! is_array( $value ) ) : ?>
315 <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $key, $current ); ?>>
316 <?php echo esc_html( $value ); ?>
317 </option>
318 <?php else : ?>
319 <optgroup label="<?php echo esc_attr( str_replace( '_', ' ', $key ) ); ?>">
320 <?php foreach ( $value as $k1 => $v1 ) : ?>
321 <option value="<?php echo esc_attr( $k1 ); ?>" <?php selected( $k1, $current ); ?>>
322 <?php echo esc_html( $v1 ); ?>
323 </option>
324 <?php endforeach; ?>
325 </optgroup>
326 <?php endif;
327 endforeach; ?>
328 </select>
329 </td>
330 </tr>
331 <?php
332 }
333
334 /**
335 * Checkbox group (e.g., days of week).
336 */
337 public static function checkbox_group( $args ) {
338 $current = is_array( $args['value'] ) ? $args['value'] : ( is_string( $args['value'] ) ? json_decode( $args['value'], true ) : array() );
339 if ( ! is_array( $current ) ) $current = array();
340 ?>
341 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
342 <?php self::description_cell( $args ); ?>
343 <td>
344 <div style="display:flex;flex-wrap:wrap;gap:6px 16px;">
345 <?php foreach ( $args['values'] as $key => $label ) : ?>
346 <label style="display:inline-flex;align-items:center;gap:4px;cursor:pointer;font-size:13px;">
347 <input type="checkbox"
348 name="<?php echo esc_attr( $args['name'] ); ?>[]"
349 value="<?php echo esc_attr( $key ); ?>"
350 <?php checked( in_array( $key, $current ) ); ?>>
351 <?php echo esc_html( $label ); ?>
352 </label>
353 <?php endforeach; ?>
354 </div>
355 </td>
356 </tr>
357 <?php
358 }
359
360 /**
361 * Multi-select with chosen.js.
362 */
363 public static function multiplay_select( $args ) {
364 ?>
365 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
366 <?php self::description_cell( $args ); ?>
367 <td class="wpda_color-picker">
368 <select id="<?php echo esc_attr( $args['name'] ); ?>"
369 name="<?php echo esc_attr( $args['name'] ); ?>"
370 class="chosen-select" multiple="" tabindex="-1" style="display: none;">
371 <?php echo self::build_page_options( isset( $args['value'] ) ? $args['value'] : array() ); ?>
372 </select>
373 <script type="text/javascript">
374 jQuery(document).ready(function() {
375 jQuery('#<?php echo esc_js( $args['name'] ); ?>').chosen({width: '98%'});
376 });
377 </script>
378 </td>
379 </tr>
380 <?php
381 }
382
383 /**
384 * Font family select.
385 */
386 public static function font_select( $args ) {
387 $current = isset( $args['value'] ) ? $args['value'] : '';
388 ?>
389 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
390 <?php self::description_cell( $args ); ?>
391 <td>
392 <select id="<?php echo esc_attr( $args['name'] ); ?>"
393 name="<?php echo esc_attr( $args['name'] ); ?>">
394 <?php foreach ( $args['values'] as $key => $label ) : ?>
395 <option style="font-family:<?php echo esc_attr( $key ); ?>"
396 value="<?php echo esc_attr( $key ); ?>"
397 <?php selected( $key, $current ); ?>>
398 <?php echo esc_html( $label ); ?>
399 </option>
400 <?php endforeach; ?>
401 </select>
402 </td>
403 </tr>
404 <?php
405 }
406
407 /**
408 * Checkbox list (legacy format with key=>value checked array).
409 */
410 public static function simple_checkbox( $args ) {
411 $current = isset( $args['value'] ) ? $args['value'] : array();
412 if ( ! is_array( $current ) ) $current = array();
413 ?>
414 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option checkbox_tr">
415 <?php self::description_cell( $args ); ?>
416 <td class="td_value">
417 <?php foreach ( $args['values'] as $key => $label ) : ?>
418 <div>
419 <span>
420 <input <?php checked( isset( $current[ $key ] ) || in_array( $key, $current ) ); ?>
421 type="checkbox"
422 name="<?php echo esc_attr( $args['name'] . '[' . $key . ']' ); ?>"
423 id="<?php echo esc_attr( $args['name'] . $key . '_id' ); ?>"
424 value="<?php echo esc_attr( $key ); ?>">
425 <label for="<?php echo esc_attr( $args['name'] . $key . '_id' ); ?>">
426 <?php echo esc_html( $label ); ?>
427 </label>
428 </span>
429 </div>
430 <?php endforeach; ?>
431 </td>
432 </tr>
433 <?php
434 }
435
436 /**
437 * Padding/margin four-sided input.
438 */
439 public static function padding_margin_input( $args ) {
440 $sides = array( 'top', 'right', 'bottom', 'left' );
441 ?>
442 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option tr_pading_margin">
443 <?php self::description_cell( $args ); ?>
444 <td class="td_value">
445 <?php foreach ( $sides as $side ) :
446 if ( ! isset( $args['values'][ $side ] ) ) continue;
447 $val = isset( $args['value'][ $side ] ) ? $args['value'][ $side ] : '0';
448 ?>
449 <span>
450 <input type="text"
451 name="<?php echo esc_attr( $args['name'] . '[' . $side . ']' ); ?>"
452 id="<?php echo esc_attr( $args['name'] . '_' . $side . '_id' ); ?>"
453 value="<?php echo esc_attr( $val ); ?>">
454 <label for="<?php echo esc_attr( $args['name'] . '_' . $side . '_id' ); ?>">
455 <?php echo esc_html( ucfirst( $side ) . '(px)' ); ?>
456 </label>
457 </span>
458 <?php endforeach; ?>
459 </td>
460 </tr>
461 <?php
462 }
463
464 /**
465 * Select with icon/font preview.
466 */
467 public static function simple_select_pro_font_size( $args ) {
468 $current = isset( $args['value'] ) ? $args['value'] : '';
469 ?>
470 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
471 <?php self::description_cell( $args ); ?>
472 <td>
473 <select style="font-family: 'Material Icons','FontAwesome',Arial;"
474 id="<?php echo esc_attr( $args['name'] ); ?>"
475 name="<?php echo esc_attr( $args['name'] ); ?>">
476 <?php foreach ( $args['values'] as $key => $value ) : ?>
477 <option class="<?php echo esc_attr( $value[1] ); ?>"
478 value="<?php echo esc_attr( $key ); ?>"
479 <?php selected( $key, $current ); ?>>
480 <?php echo esc_html( $value[0] ); ?>
481 </option>
482 <?php endforeach; ?>
483 </select>
484 </td>
485 </tr>
486 <?php
487 }
488
489 /**
490 * Range slider input.
491 */
492 public static function range_input( $args ) {
493 $value = isset( $args['value'] ) ? $args['value'] : '';
494 $min = isset( $args['min_value'] ) ? $args['min_value'] : '';
495 $max = isset( $args['max_value'] ) ? $args['max_value'] : '';
496 $small = isset( $args['small_text'] ) ? $args['small_text'] : '';
497 ?>
498 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
499 <?php self::description_cell( $args ); ?>
500 <td class="range_option_td">
501 <input oninput="document.getElementById('<?php echo esc_js( $args['name'] ); ?>_conect').innerHTML=this.value"
502 type="range"
503 id="<?php echo esc_attr( $args['name'] ); ?>"
504 name="<?php echo esc_attr( $args['name'] ); ?>"
505 value="<?php echo esc_attr( $value ); ?>"
506 <?php echo $min !== '' ? 'min="' . esc_attr( $min ) . '"' : ''; ?>
507 <?php echo $max !== '' ? 'max="' . esc_attr( $max ) . '"' : ''; ?>>
508 <output id="<?php echo esc_attr( $args['name'] ); ?>_conect"><?php echo esc_html( $value ); ?></output>
509 <?php if ( $small ) : ?>
510 <small><?php echo esc_html( $small ); ?></small>
511 <?php endif; ?>
512 </td>
513 </tr>
514 <?php
515 }
516
517 /**
518 * Time input (HH:MM).
519 */
520 public static function time_input( $args ) {
521 $value = isset( $args['value'] ) ? $args['value'] : '';
522 $min = isset( $args['min_value'] ) ? $args['min_value'] : '';
523 $max = isset( $args['max_value'] ) ? $args['max_value'] : '';
524 $small = isset( $args['small_text'] ) ? $args['small_text'] : '';
525 ?>
526 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
527 <?php self::description_cell( $args ); ?>
528 <td class="range_option_td">
529 <input type="time"
530 id="<?php echo esc_attr( $args['name'] ); ?>"
531 name="<?php echo esc_attr( $args['name'] ); ?>"
532 value="<?php echo esc_attr( $value ); ?>"
533 <?php echo $min !== '' ? 'min="' . esc_attr( $min ) . '"' : ''; ?>
534 <?php echo $max !== '' ? 'max="' . esc_attr( $max ) . '"' : ''; ?>>
535 <?php if ( $small ) : ?>
536 <small><?php echo esc_html( $small ); ?></small>
537 <?php endif; ?>
538 </td>
539 </tr>
540 <?php
541 }
542
543 /**
544 * Upload input with media library button.
545 */
546 public static function upload_input( $args ) {
547 $value = isset( $args['value'] ) ? $args['value'] : '';
548 ?>
549 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
550 <?php self::description_cell( $args ); ?>
551 <td class="upload_option_td">
552 <input type="text" class="upload"
553 id="<?php echo esc_attr( $args['name'] ); ?>"
554 name="<?php echo esc_attr( $args['name'] ); ?>"
555 value="<?php echo esc_attr( $value ); ?>">
556 <input class="upload-button button" type="button" value="Upload">
557 <img src="<?php echo esc_url( $value ); ?>" class="cont_button_uploaded_img">
558 </td>
559 </tr>
560 <?php
561 }
562
563 /**
564 * Date-time picker input.
565 */
566 public static function calendar_input( $args ) {
567 $value = isset( $args['value'] ) ? $args['value'] : '';
568 $type = isset( $args['type'] ) ? $args['type'] : 'text';
569 ?>
570 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
571 <?php self::description_cell( $args ); ?>
572 <td>
573 <input class="wpda_datepicker_timer"
574 type="<?php echo esc_attr( $type ); ?>"
575 value="<?php echo esc_attr( $value ); ?>"
576 name="<?php echo esc_attr( $args['name'] ); ?>">
577 </td>
578 </tr>
579 <?php
580 }
581
582 /**
583 * WordPress TinyMCE editor.
584 */
585 public static function tinmce( $args ) {
586 $value = isset( $args['value'] ) ? $args['value'] : '';
587 ?>
588 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
589 <td colspan="2" style="padding: 4px;">
590 <?php wp_editor( $value, $args['name'], array() ); ?>
591 </td>
592 </tr>
593 <?php
594 }
595
596 /**
597 * Sortable ordering list.
598 */
599 public static function oredering( $args ) {
600 $ordering_info = json_decode( stripslashes( $args['value'] ), true );
601 if ( ! is_array( $ordering_info ) ) $ordering_info = array();
602 $elements = $args['values'];
603 ?>
604 <tr class="<?php echo esc_attr( isset( $args['heading_group'] ) ? $args['heading_group'] : '' ); ?> tr_option">
605 <?php self::description_cell( $args ); ?>
606 <td style="padding: 4px;">
607 <ul class="wpdevart_sortable" id="<?php echo esc_attr( $args['name'] ); ?>_ul">
608 <?php foreach ( $ordering_info as $key => $value ) :
609 $active_class = $value[0] ? ' control_active ' : ' control_deactive ';
610 ?>
611 <li date-value="<?php echo esc_attr( $key ); ?>"
612 class="ui-state-default<?php echo esc_attr( $active_class ); ?>">
613 <?php echo esc_html( isset( $elements[ $key ] ) ? $elements[ $key ] : $key ); ?>
614 <span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
615 </li>
616 <?php endforeach; ?>
617 </ul>
618 <input type="hidden"
619 name="<?php echo esc_attr( $args['name'] ); ?>"
620 id="<?php echo esc_attr( $args['name'] ); ?>"
621 value='<?php echo esc_attr( stripslashes( $args['value'] ) ); ?>'>
622 </td>
623 </tr>
624 <?php
625 }
626
627 // ─── Static helpers ─────────────────────────────────────────
628
629 /**
630 * Render a field by calling the appropriate method based on function_name.
631 */
632 public static function render( $args ) {
633 $method = $args['function_name'];
634 if ( method_exists( __CLASS__, $method ) ) {
635 self::$method( $args );
636 }
637 }
638
639 /**
640 * Build the multi-select options for pages/posts/categories/taxonomies/etc.
641 */
642 public static function build_page_options( $selected = array() ) {
643 if ( ! is_array( $selected ) ) $selected = array();
644
645 $groups = array(
646 'pages' => 'Pages',
647 'posts' => 'Posts',
648 'categories' => 'Categories',
649 'custom_post_type' => 'Custom Post Types',
650 'taxonomy' => 'Taxonomies',
651 'other' => 'Other',
652 'device' => 'Device',
653 'user' => 'User',
654 );
655
656 $options = array(
657 'pages' => self::get_pages_list(),
658 'posts' => self::get_posts_list(),
659 'categories' => self::get_categories_list(),
660 'custom_post_type' => self::get_cpt_list(),
661 'taxonomy' => self::get_taxonomy_list(),
662 'other' => self::get_other_list(),
663 'device' => array( 'mobile' => 'Mobile', 'desktop' => 'Desktop' ),
664 'user' => array( 'logged_in' => 'Logged in users', 'logged_out' => 'Logged out users' ),
665 );
666
667 $html = '';
668 foreach ( $groups as $group_key => $group_label ) {
669 $html .= '<optgroup label="' . esc_attr( $group_label ) . '">';
670 if ( isset( $options[ $group_key ] ) ) {
671 foreach ( $options[ $group_key ] as $opt_key => $opt_label ) {
672 $sel = in_array( $opt_key, $selected ) ? ' selected="selected"' : '';
673 $html .= '<option label="' . esc_attr( $group_label ) . '" value="' . esc_attr( $opt_key ) . '"' . $sel . '>'
674 . esc_html( $opt_label ) . '</option>';
675 }
676 }
677 $html .= '</optgroup>';
678 }
679 return $html;
680 }
681
682 private static function get_pages_list() {
683 $list = array();
684 $pages = get_pages( array( 'sort_order' => 'ASC', 'sort_column' => 'post_title', 'post_status' => 'publish' ) );
685 foreach ( $pages as $p ) {
686 $list[ 'page_' . $p->ID ] = $p->post_title;
687 }
688 return $list;
689 }
690
691 private static function get_posts_list() {
692 $list = array();
693 $posts = get_posts( array( 'orderby' => 'title', 'order' => 'ASC', 'post_type' => 'post', 'post_status' => 'publish', 'numberposts' => -1 ) );
694 foreach ( $posts as $p ) {
695 $list[ 'post_' . $p->ID ] = $p->post_title;
696 }
697 return $list;
698 }
699
700 private static function get_categories_list() {
701 $list = array();
702 $cats = get_categories( array( 'hide_empty' => false ) );
703 foreach ( $cats as $c ) {
704 $list[ 'category_' . $c->cat_ID ] = $c->cat_name;
705 }
706 return $list;
707 }
708
709 private static function get_cpt_list() {
710 $list = array();
711 $types = get_post_types( array( 'public' => true ), 'objects' );
712 foreach ( $types as $type ) {
713 $list[ 'custom_post_type_' . $type->name ] = $type->label;
714 }
715 return $list;
716 }
717
718 private static function get_taxonomy_list() {
719 $list = array();
720 $taxes = get_taxonomies( array( 'public' => true ), 'objects' );
721 foreach ( $taxes as $tax ) {
722 $list[ 'taxonomy_' . $tax->name ] = $tax->label;
723 }
724 return $list;
725 }
726
727 private static function get_other_list() {
728 return array(
729 'front_page' => 'Front Page',
730 'blog_page' => 'Blog Page',
731 'single_post' => 'Single Posts',
732 'sticky_post' => 'Sticky Posts',
733 'date_archive' => 'Date Archive',
734 'author_archive' => 'Author Archive',
735 'search_page' => 'Search Page',
736 '404_page' => '404 Page',
737 );
738 }
739
740 /**
741 * Get font family choices for selects.
742 */
743 public static function font_choices() {
744 return array(
745 'Arial,Helvetica Neue,Helvetica,sans-serif' => 'Arial *',
746 'Arial Black,Arial Bold,Arial,sans-serif' => 'Arial Black *',
747 'Arial Narrow,Arial,Helvetica Neue,Helvetica,sans-serif' => 'Arial Narrow *',
748 'Courier,Verdana,sans-serif' => 'Courier *',
749 'Georgia,Times New Roman,Times,serif' => 'Georgia *',
750 'Times New Roman,Times,Georgia,serif' => 'Times New Roman *',
751 'Trebuchet MS,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Arial,sans-serif' => 'Trebuchet MS *',
752 'Verdana,sans-serif' => 'Verdana *',
753 'American Typewriter,Georgia,serif' => 'American Typewriter',
754 'Andale Mono,Consolas,Monaco,Courier,Courier New,Verdana,sans-serif' => 'Andale Mono',
755 'Baskerville,Times New Roman,Times,serif' => 'Baskerville',
756 'Bookman Old Style,Georgia,Times New Roman,Times,serif' => 'Bookman Old Style',
757 'Calibri,Helvetica Neue,Helvetica,Arial,Verdana,sans-serif' => 'Calibri',
758 'Cambria,Georgia,Times New Roman,Times,serif' => 'Cambria',
759 'Candara,Verdana,sans-serif' => 'Candara',
760 'Century Gothic,Apple Gothic,Verdana,sans-serif' => 'Century Gothic',
761 'Century Schoolbook,Georgia,Times New Roman,Times,serif' => 'Century Schoolbook',
762 'Consolas,Andale Mono,Monaco,Courier,Courier New,Verdana,sans-serif' => 'Consolas',
763 'Constantia,Georgia,Times New Roman,Times,serif' => 'Constantia',
764 'Corbel,Lucida Grande,Lucida Sans Unicode,Arial,sans-serif' => 'Corbel',
765 'Franklin Gothic Medium,Arial,sans-serif' => 'Franklin Gothic Medium',
766 'Garamond,Hoefler Text,Times New Roman,Times,serif' => 'Garamond',
767 'Gill Sans MT,Gill Sans,Calibri,Trebuchet MS,sans-serif' => 'Gill Sans MT',
768 'Helvetica Neue,Helvetica,Arial,sans-serif' => 'Helvetica Neue',
769 'Hoefler Text,Garamond,Times New Roman,Times,sans-serif' => 'Hoefler Text',
770 'Lucida Bright,Cambria,Georgia,Times New Roman,Times,serif' => 'Lucida Bright',
771 'Lucida Grande,Lucida Sans,Lucida Sans Unicode,sans-serif' => 'Lucida Grande',
772 'Palatino Linotype,Palatino,Georgia,Times New Roman,Times,serif' => 'Palatino Linotype',
773 'Tahoma,Geneva,Verdana,sans-serif' => 'Tahoma',
774 'Rockwell, Arial Black, Arial Bold, Arial, sans-serif' => 'Rockwell',
775 'Segoe UI' => 'Segoe UI',
776 );
777 }
778
779 /**
780 * List of CSS animation names.
781 */
782 public static function animation_list() {
783 return array(
784 'bounce', 'flash', 'pulse', 'rubberBand', 'shake', 'swing', 'tada', 'wobble',
785 'bounceIn', 'bounceInDown', 'bounceInLeft', 'bounceInRight', 'bounceInUp',
786 'fadeIn', 'fadeInDown', 'fadeInDownBig', 'fadeInLeft', 'fadeInLeftBig',
787 'fadeInRight', 'fadeInRightBig', 'fadeInUp', 'fadeInUpBig',
788 'flip', 'flipInX', 'flipInY', 'lightSpeedIn',
789 'rotateIn', 'rotateInDownLeft', 'rotateInDownRight', 'rotateInUpLeft', 'rotateInUpRight',
790 'rollIn', 'zoomIn', 'zoomInDown', 'zoomInLeft', 'zoomInRight', 'zoomInUp',
791 );
792 }
793
794 /**
795 * Get a random animation name.
796 */
797 public static function random_animation() {
798 $list = self::animation_list();
799 return $list[ array_rand( $list ) ];
800 }
801
802 /**
803 * Validate and sanitize a hex color. Returns '' if invalid (fallback to default).
804 */
805 public static function sanitize_hex_color( $color ) {
806 if ( '' === $color || ! is_string( $color ) ) return '';
807 if ( preg_match( '/^#([A-Fa-f0-9]{3}){1,2}$/', $color ) ) return $color;
808 return '';
809 }
810
811 /**
812 * Sanitize a single field value based on its function_name (field type).
813 * Used by all controllers on save to apply type-appropriate sanitization.
814 */
815 public static function sanitize_field_value( $field, $raw ) {
816 $fn = isset( $field['function_name'] ) ? $field['function_name'] : '';
817
818 if ( is_array( $raw ) ) {
819 return array_map( 'sanitize_text_field', $raw );
820 }
821
822 switch ( $fn ) {
823 case 'tinmce':
824 return wp_kses_post( stripslashes( $raw ) );
825 case 'color_input':
826 $v = self::sanitize_hex_color( trim( stripslashes( $raw ) ) );
827 return $v !== '' ? $v : ( isset( $field['default_value'] ) ? $field['default_value'] : '' );
828 case 'range_input':
829 return (string) intval( $raw );
830 default:
831 return sanitize_text_field( stripslashes( $raw ) );
832 }
833 }
834
835 /**
836 * Convert hex color to rgba.
837 */
838 public static function hex2rgba( $color, $opacity = false ) {
839 $default = 'rgb(0,0,0)';
840 if ( empty( $color ) ) return $default;
841
842 if ( $color[0] === '#' ) $color = substr( $color, 1 );
843
844 if ( strlen( $color ) === 6 ) {
845 $hex = array( $color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5] );
846 } elseif ( strlen( $color ) === 3 ) {
847 $hex = array( $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] );
848 } else {
849 return $default;
850 }
851
852 $rgb = array_map( 'hexdec', $hex );
853 $opacity = min( $opacity, 1 );
854
855 return 'rgba(' . implode( ',', $rgb ) . ',' . $opacity . ')';
856 }
857
858 /**
859 * Darken a hex color by a percentage.
860 */
861 public static function darken_color( $color, $percent ) {
862 $color = ltrim( $color, '#' );
863 if ( strlen( $color ) !== 6 ) return '#' . $color;
864
865 $r = max( 0, (int) ( hexdec( substr( $color, 0, 2 ) ) * ( 1 - $percent / 100 ) ) );
866 $g = max( 0, (int) ( hexdec( substr( $color, 2, 2 ) ) * ( 1 - $percent / 100 ) ) );
867 $b = max( 0, (int) ( hexdec( substr( $color, 4, 2 ) ) * ( 1 - $percent / 100 ) ) );
868
869 return '#' . str_pad( dechex( $r ), 2, '0', STR_PAD_LEFT )
870 . str_pad( dechex( $g ), 2, '0', STR_PAD_LEFT )
871 . str_pad( dechex( $b ), 2, '0', STR_PAD_LEFT );
872 }
873 }
874