Passed
Push — develop ( b45a2c...49739f )
by Aristeides
07:31
created

Kirki_Field::set_js_vars()   C

Complexity

Conditions 14
Paths 48

Size

Total Lines 49
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 20
nc 48
nop 0
dl 0
loc 49
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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