PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.1.0
Kirki – Freeform Page Builder, Website Builder & Customizer v6.1.0
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / customizer / packages / compatibility / src / Field.php
kirki / customizer / packages / compatibility / src Last commit date
deprecated 1 month ago Aliases.php 2 months ago Config.php 5 months ago Control.php 5 months ago Deprecated.php 5 months ago Field.php 2 months ago Framework.php 5 months ago Init.php 2 months ago Kirki.php 2 months ago Modules.php 2 months ago Pro_Namespace_Compatibility.php 5 months ago Sanitize_Values.php 5 months ago Scripts.php 2 months ago Settings.php 5 months ago Values.php 5 months ago
Field.php
710 lines
1 <?php
2 /**
3 * Creates and validates field parameters.
4 *
5 * @package Kirki
6 * @category Core
7 * @author Themeum
8 * @copyright Copyright (c) 2023, Themeum
9 * @license https://opensource.org/licenses/MIT
10 * @since 1.0
11 */
12
13 namespace Kirki\Compatibility;
14
15 use Kirki\Compatibility\Kirki;
16
17 /**
18 * Please do not use this class directly.
19 * You should instead extend it per-field-type.
20 */
21 class Field {
22
23 /**
24 * An array of the field arguments.
25 *
26 * @access protected
27 * @var array
28 */
29 public $label;
30 public $row_label;
31 public $button_label;
32 public $description;
33 public $help;
34 public $compiler;
35
36 protected $args = array();
37
38 /**
39 * The ID of the kirki_config we're using.
40 *
41 * @see Kirki\Compatibility\Config
42 * @access protected
43 * @var string
44 */
45 protected $kirki_config = 'global';
46
47 /**
48 * The capability required so that users can edit this field.
49 *
50 * @access protected
51 * @var string
52 */
53 protected $capability = 'edit_theme_options';
54
55 /**
56 * If we're using options instead of theme_mods
57 * and we want them serialized, this is the option that
58 * will saved in the db.
59 *
60 * @access protected
61 * @var string
62 */
63 protected $option_name = '';
64
65 /**
66 * Custom input attributes (defined as an array).
67 *
68 * @access protected
69 * @var array
70 */
71 protected $input_attrs = array();
72
73 /**
74 * Preset choices.
75 *
76 * @access protected
77 * @var array
78 */
79 protected $preset = array();
80
81 /**
82 * CSS Variables.
83 *
84 * @access protected
85 * @var array
86 */
87 protected $css_vars = array();
88
89 /**
90 * Use "theme_mod" or "option".
91 *
92 * @access protected
93 * @var string
94 */
95 protected $option_type = 'theme_mod';
96
97 /**
98 * The name of this setting (id for the db).
99 *
100 * @access protected
101 * @var string|array
102 */
103 protected $settings = '';
104
105 /**
106 * Set to true if you want to disable all CSS output for this field.
107 *
108 * @access protected
109 * @var bool
110 */
111 protected $disable_output = false;
112
113 /**
114 * The field type.
115 *
116 * @access protected
117 * @var string
118 */
119 protected $type = 'kirki-generic';
120
121 /**
122 * Some fields require options to be set.
123 * We're whitelisting the property here
124 * and suggest you validate this in a child class.
125 *
126 * @access protected
127 * @var array
128 */
129 protected $choices = array();
130
131 /**
132 * Assign this field to a section.
133 * Fields not assigned to a section will not be displayed in the customizer.
134 *
135 * @access protected
136 * @var string
137 */
138 protected $section = '';
139
140 /**
141 * The default value for this field.
142 *
143 * @access protected
144 * @var string|array|bool
145 */
146 protected $default = '';
147
148 /**
149 * Priority determines the position of a control inside a section.
150 * Lower priority numbers move the control to the top.
151 *
152 * @access protected
153 * @var int
154 */
155 protected $priority = 10;
156
157 /**
158 * Unique ID for this field.
159 * This is auto-calculated from the $settings argument.
160 *
161 * @access protected
162 * @var string
163 */
164 protected $id = '';
165
166 /**
167 * Use if you want to automatically generate CSS from this field's value.
168 *
169 * @see https://docs.themeum.com/kirki/arguments/output/
170 * @access protected
171 * @var array
172 */
173 protected $output = array();
174
175 /**
176 * Use to automatically generate postMessage scripts.
177 * Not necessary to use if you use 'transport' => 'auto'
178 * and have already set an array for the 'output' argument.
179 *
180 * @see https://docs.themeum.com/kirki/arguments/js_vars/
181 * @access protected
182 * @var array
183 */
184 protected $js_vars = array();
185
186 /**
187 * If you want to use a CSS compiler, then use this to set the variable names.
188 *
189 * @see https://docs.themeum.com/kirki/arguments/output/
190 * @access protected
191 * @var array
192 */
193 protected $variables = array();
194
195 /**
196 * Text that will be used in a tooltip to provide extra info for this field.
197 *
198 * @access protected
199 * @var string
200 */
201 protected $tooltip = '';
202
203 /**
204 * A custom callback to determine if the field should be visible or not.
205 *
206 * @access protected
207 * @var string|array
208 */
209 protected $active_callback = '__return_true';
210
211 /**
212 * A custom sanitize callback that will be used to properly save the values.
213 *
214 * @access protected
215 * @var string|array
216 */
217 protected $sanitize_callback = '';
218
219 /**
220 * Use 'refresh', 'postMessage' or 'auto'.
221 * 'auto' will automatically geberate any 'js_vars' from the 'output' argument.
222 *
223 * @access protected
224 * @var string
225 */
226 protected $transport = 'refresh';
227
228 /**
229 * Define dependencies to show/hide this field based on the values of other fields.
230 *
231 * @access protected
232 * @var array
233 */
234 protected $required = array();
235
236 /**
237 * Partial Refreshes array.
238 *
239 * @access protected
240 * @var array
241 */
242 protected $partial_refresh = array();
243
244 /**
245 * Gutenberg support flag.
246 *
247 * @access public
248 * @var bool
249 */
250 public $gutenberg_support = false;
251 public $disable_loader = false;
252
253 /**
254 * Field name.
255 *
256 * @access public
257 * @var string
258 */
259 public $name = '';
260
261 /**
262 * Data tags for the field.
263 *
264 * @access public
265 * @var array
266 */
267 public $data_tags = array();
268
269 /**
270 * The class constructor.
271 * Parses and sanitizes all field arguments.
272 * Then it adds the field to Kirki::$fields.
273 *
274 * @access public
275 * @param string $config_id The ID of the config we want to use.
276 * Defaults to "global".
277 * Configs are handled by the Kirki\Compatibility\Config class.
278 * @param array $args The arguments of the field.
279 */
280 public function __construct( $config_id = 'global', $args = array() ) {
281
282 /**
283 * In case the user only provides 1 argument,
284 * assume that the provided argument is $args and set $config_id = 'global'.
285 */
286 if ( is_array( $config_id ) && empty( $args ) ) {
287 $args = $config_id;
288 $config_id = isset( $args['kirki_config'] ) ? $args['kirki_config'] : 'global';
289 }
290
291 if ( isset( $args['setting'] ) && ! empty( $args['setting'] ) && ( ! isset( $args['settings'] ) || empty( $args['settings'] ) ) ) {
292 /* translators: %s represents the field ID where the error occurs. */
293 _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Typo found in field %s - setting instead of settings.', 'kirki' ), esc_html( $args['settings'] ) ), '3.0.10' );
294 $args['settings'] = $args['setting'];
295 unset( $args['setting'] );
296 }
297
298 $args['kirki_config'] = $config_id;
299
300 $this->kirki_config = $config_id;
301
302 if ( '' === $config_id ) {
303 /* translators: %1$s represents the field ID where the error occurs. %2$s is the URL in the documentation site. */
304 _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Config not defined for field %1$s - See %2$s for details on how to properly add fields.', 'kirki' ), esc_html( $args['settings'] ), 'https://aristath.github.io/kirki/docs/getting-started/fields.html' ), '3.0.10' );
305 $this->kirki_config = 'global';
306 }
307
308 // Get defaults from the class.
309 $defaults = get_class_vars( __CLASS__ );
310
311 // Get the config arguments, and merge them with the defaults.
312 $config_defaults = ( isset( Kirki::$config['global'] ) ) ? Kirki::$config['global'] : array();
313
314 if ( 'global' !== $this->kirki_config && isset( Kirki::$config[ $this->kirki_config ] ) ) {
315 $config_defaults = Kirki::$config[ $this->kirki_config ];
316 }
317
318 $config_defaults = ( is_array( $config_defaults ) ) ? $config_defaults : array();
319
320 foreach ( $config_defaults as $key => $value ) {
321 if ( isset( $defaults[ $key ] ) && ! empty( $value ) && $value !== $defaults[ $key ] ) {
322 $defaults[ $key ] = $value;
323 }
324 }
325
326 // Merge our args with the defaults.
327 $args = wp_parse_args( $args, $defaults );
328
329 // Set the class properties using the parsed args.
330 foreach ( $args as $key => $value ) {
331 $this->{$key} = $value;
332 }
333
334 $this->args = $args;
335
336 $this->set_field();
337
338 // Instantiate the \Kirki\Field to apply hooks.
339 new \Kirki\Field\None( $this->args );
340
341 }
342
343 /**
344 * Processes the field arguments
345 *
346 * @access protected
347 */
348 protected function set_field() {
349
350 $properties = get_class_vars( __CLASS__ );
351
352 // Some things must run before the others.
353 $this->set_option_type();
354 $this->set_settings();
355
356 // Sanitize the properties, skipping the ones that have already run above.
357 foreach ( array_keys( $properties ) as $property ) {
358 if ( in_array( $property, array( 'option_name', 'option_type', 'settings' ), true ) ) {
359 continue;
360 }
361 if ( method_exists( $this, 'set_' . $property ) ) {
362 $method_name = 'set_' . $property;
363 $this->$method_name();
364 }
365 }
366
367 // Get all arguments with their values.
368 $args = get_object_vars( $this );
369
370 foreach ( array_keys( $args ) as $key ) {
371 $args[ $key ] = $this->$key;
372 }
373
374 // Add the field to the static $fields variable properly indexed.
375 Kirki::$fields[ $this->settings ] = $args;
376
377 }
378
379 /**
380 * Escape the $section.
381 *
382 * @access protected
383 */
384 protected function set_input_attrs() {
385 $this->input_attrs = (array) $this->input_attrs;
386 }
387
388 /**
389 * Make sure we're using the correct option_type
390 *
391 * @access protected
392 */
393 protected function set_option_type() {
394
395 // Take care of common typos.
396 if ( 'options' === $this->option_type ) {
397 $this->option_type = 'option';
398 }
399
400 // Take care of common typos.
401 if ( 'theme_mods' === $this->option_type ) {
402 /* translators: %1$s represents the field ID where the error occurs. */
403 _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Typo found in field %s - "theme_mods" vs "theme_mod"', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
404 $this->option_type = 'theme_mod';
405 }
406 }
407
408 /**
409 * Modifications for partial refreshes.
410 *
411 * @access protected
412 */
413 protected function set_partial_refresh() {
414 if ( ! is_array( $this->partial_refresh ) ) {
415 $this->partial_refresh = array();
416 }
417 foreach ( $this->partial_refresh as $id => $args ) {
418 if ( ! is_array( $args ) || ! isset( $args['selector'] ) || ! isset( $args['render_callback'] ) || ! is_callable( $args['render_callback'] ) ) {
419 /* translators: %1$s represents the field ID where the error occurs. */
420 _doing_it_wrong( __METHOD__, sprintf( esc_html__( '"partial_refresh" invalid entry in field %s', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
421 unset( $this->partial_refresh[ $id ] );
422 continue;
423 }
424 }
425 if ( ! empty( $this->partial_refresh ) ) {
426 $this->transport = 'postMessage';
427 }
428 }
429
430 /**
431 * Sets the settings.
432 * If we're using serialized options it makes sure that settings are properly formatted.
433 * We'll also be escaping all setting names here for consistency.
434 *
435 * @access protected
436 */
437 protected function set_settings() {
438
439 // If settings is not an array, temporarily convert it to an array.
440 // This is just to allow us to process everything the same way and avoid code duplication.
441 // if settings is not an array then it will not be set as an array in the end.
442 if ( ! is_array( $this->settings ) ) {
443 $this->settings = array(
444 'kirki_placeholder_setting' => $this->settings,
445 );
446 }
447 $settings = array();
448 foreach ( $this->settings as $setting_key => $setting_value ) {
449 $settings[ $setting_key ] = $setting_value;
450
451 // If we're using serialized options then we need to spice this up.
452 if ( 'option' === $this->option_type && '' !== $this->option_name && ( false === strpos( $setting_key, '[' ) ) ) {
453 $settings[ $setting_key ] = "{$this->option_name}[{$setting_value}]";
454 }
455 }
456 $this->settings = $settings;
457 if ( isset( $this->settings['kirki_placeholder_setting'] ) ) {
458 $this->settings = $this->settings['kirki_placeholder_setting'];
459 }
460 }
461
462 /**
463 * Sets the active_callback
464 * If we're using the $required argument,
465 * Then this is where the switch is made to our evaluation method.
466 *
467 * @access protected
468 */
469 protected function set_active_callback() {
470
471 if ( is_array( $this->active_callback ) ) {
472
473 if ( ! is_callable( $this->active_callback ) ) {
474
475 // Bugfix for https://github.com/aristath/kirki/issues/1961.
476 foreach ( $this->active_callback as $key => $val ) {
477 if ( is_callable( $val ) ) {
478 unset( $this->active_callback[ $key ] );
479 }
480 }
481 if ( isset( $this->active_callback[0] ) ) {
482 $this->required = $this->active_callback;
483 }
484 }
485 }
486
487 if ( ! empty( $this->required ) ) {
488 $this->active_callback = '__return_true';
489 return;
490 }
491 // No need to proceed any further if we're using the default value.
492 if ( '__return_true' === $this->active_callback ) {
493 return;
494 }
495
496 // Make sure the function is callable, otherwise fallback to __return_true.
497 if ( ! is_callable( $this->active_callback ) ) {
498 $this->active_callback = '__return_true';
499 }
500
501 }
502
503 /**
504 * Sets the $id.
505 * Setting the ID should happen after the 'settings' sanitization.
506 * This way we can also properly handle cases where the option_type is set to 'option'
507 * and we're using an array instead of individual options.
508 *
509 * @access protected
510 */
511 protected function set_id() {
512 $this->id = sanitize_key( str_replace( '[', '-', str_replace( ']', '', $this->settings ) ) );
513 }
514
515 /**
516 * Sets the $choices.
517 *
518 * @access protected
519 */
520 protected function set_choices() {
521 if ( ! is_array( $this->choices ) ) {
522 $this->choices = array();
523 }
524 }
525
526 /**
527 * Escapes the $disable_output.
528 *
529 * @access protected
530 */
531 protected function set_disable_output() {
532 $this->disable_output = (bool) $this->disable_output;
533 }
534
535 /**
536 * Sets the $sanitize_callback
537 *
538 * @access protected
539 */
540 protected function set_output() {
541 if ( empty( $this->output ) ) {
542 return;
543 }
544 if ( ! is_array( $this->output ) ) {
545 /* translators: The field ID where the error occurs. */
546 _doing_it_wrong( __METHOD__, sprintf( esc_html__( '"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
547 $this->output = array(
548 array(
549 'element' => $this->output,
550 ),
551 );
552 }
553
554 // Convert to array of arrays if needed.
555 if ( isset( $this->output['element'] ) ) {
556 /* translators: The field ID where the error occurs. */
557 _doing_it_wrong( __METHOD__, sprintf( esc_html__( '"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
558 $this->output = array( $this->output );
559 }
560
561 foreach ( $this->output as $key => $output ) {
562 if ( empty( $output ) || ! isset( $output['element'] ) ) {
563 unset( $this->output[ $key ] );
564 continue;
565 }
566 if ( ! isset( $output['sanitize_callback'] ) && isset( $output['callback'] ) ) {
567 $this->output[ $key ]['sanitize_callback'] = $output['callback'];
568 }
569
570 // Convert element arrays to strings.
571 if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
572 $this->output[ $key ]['element'] = array_unique( $this->output[ $key ]['element'] );
573 sort( $this->output[ $key ]['element'] );
574
575 // Trim each element in the array.
576 foreach ( $this->output[ $key ]['element'] as $index => $element ) {
577 $this->output[ $key ]['element'][ $index ] = trim( $element );
578 }
579 $this->output[ $key ]['element'] = implode( ',', $this->output[ $key ]['element'] );
580 }
581
582 // Fix for https://github.com/aristath/kirki/issues/1659#issuecomment-346229751.
583 $this->output[ $key ]['element'] = str_replace( array( "\t", "\n", "\r", "\0", "\x0B" ), ' ', $this->output[ $key ]['element'] );
584 $this->output[ $key ]['element'] = trim( preg_replace( '/\s+/', ' ', $this->output[ $key ]['element'] ) );
585 }
586 }
587
588 /**
589 * Sets the $js_vars
590 *
591 * @access protected
592 */
593 protected function set_js_vars() {
594 if ( ! is_array( $this->js_vars ) ) {
595 $this->js_vars = array();
596 }
597
598 // Check if transport is set to auto.
599 // If not, then skip the auto-calculations and exit early.
600 if ( 'auto' !== $this->transport ) {
601 return;
602 }
603
604 // Set transport to refresh initially.
605 // Serves as a fallback in case we failed to auto-calculate js_vars.
606 $this->transport = 'refresh';
607
608 $js_vars = array();
609
610 // Try to auto-generate js_vars.
611 // First we need to check if js_vars are empty, and that output is not empty.
612 if ( empty( $this->js_vars ) && ! empty( $this->output ) ) {
613
614 // Start going through each item in the $output array.
615 foreach ( $this->output as $output ) {
616 $output['function'] = ( isset( $output['function'] ) ) ? $output['function'] : 'style';
617
618 // If 'element' is not defined, skip this.
619 if ( ! isset( $output['element'] ) ) {
620 continue;
621 }
622 if ( is_array( $output['element'] ) ) {
623 $output['element'] = implode( ',', $output['element'] );
624 }
625
626 // If there's a sanitize_callback defined skip this, unless we also have a js_callback defined.
627 if ( isset( $output['sanitize_callback'] ) && ! empty( $output['sanitize_callback'] ) && ! isset( $output['js_callback'] ) ) {
628 continue;
629 }
630
631 // If we got this far, it's safe to add this.
632 $js_vars[] = $output;
633 }
634
635 // Did we manage to get all the items from 'output'?
636 // If not, then we're missing something so don't add this.
637 if ( count( $js_vars ) !== count( $this->output ) ) {
638 return;
639 }
640 $this->js_vars = $js_vars;
641 $this->transport = 'postMessage';
642 }
643 }
644
645 /**
646 * Sets the $variables
647 *
648 * @access protected
649 */
650 protected function set_variables() {
651 if ( ! is_array( $this->variables ) ) {
652 $variable = ( is_string( $this->variables ) && ! empty( $this->variables ) ) ? $this->variables : false;
653 $this->variables = array();
654 if ( $variable && empty( $this->variables ) ) {
655 $this->variables[0]['name'] = $variable;
656 }
657 }
658 }
659
660 /**
661 * Sets the $transport
662 *
663 * @access protected
664 */
665 protected function set_transport() {
666 if ( 'postmessage' === trim( strtolower( $this->transport ) ) ) {
667 $this->transport = 'postMessage';
668 }
669 }
670
671 /**
672 * Sets the $required
673 *
674 * @access protected
675 */
676 protected function set_required() {
677 if ( ! is_array( $this->required ) ) {
678 $this->required = array();
679 }
680 }
681
682 /**
683 * Sets the $priority
684 *
685 * @access protected
686 */
687 protected function set_priority() {
688 $this->priority = absint( $this->priority );
689 }
690
691 /**
692 * Sets the $css_vars
693 *
694 * @access protected
695 */
696 protected function set_css_vars() {
697 if ( is_string( $this->css_vars ) ) {
698 $this->css_vars = array( $this->css_vars );
699 }
700 if ( isset( $this->css_vars[0] ) && is_string( $this->css_vars[0] ) ) {
701 $this->css_vars = array( $this->css_vars );
702 }
703 foreach ( $this->css_vars as $key => $val ) {
704 if ( ! isset( $val[1] ) ) {
705 $this->css_vars[ $key ][1] = '$';
706 }
707 }
708 }
709 }
710