Completed
Push — develop ( b6add3...0b006f )
by Aristeides
02:13
created

Kirki_Control_Repeater::__construct()   F

Complexity

Conditions 33
Paths 4896

Size

Total Lines 131
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 33
eloc 54
nc 4896
nop 3
dl 0
loc 131
rs 2
c 0
b 0
f 0

How to fix   Long Method    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
 * Customizer Control: repeater.
4
 *
5
 * @package     Kirki
6
 * @subpackage  Controls
7
 * @copyright   Copyright (c) 2016, Aristeides Stathopoulos
8
 * @license     http://opensource.org/licenses/https://opensource.org/licenses/MIT
9
 * @since       2.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Repeater control
19
 */
20
class Kirki_Control_Repeater extends WP_Customize_Control {
21
22
	/**
23
	 * The control type.
24
	 *
25
	 * @access public
26
	 * @var string
27
	 */
28
	public $type = 'repeater';
29
30
	/**
31
	 * Used to automatically generate all CSS output.
32
	 *
33
	 * @access public
34
	 * @var array
35
	 */
36
	public $output = array();
37
38
	/**
39
	 * Data type
40
	 *
41
	 * @access public
42
	 * @var string
43
	 */
44
	public $option_type = 'theme_mod';
45
46
	/**
47
	 * The kirki_config we're using for this control
48
	 *
49
	 * @access public
50
	 * @var string
51
	 */
52
	public $kirki_config = 'global';
53
54
	/**
55
	 * The fields that each container row will contain.
56
	 *
57
	 * @access public
58
	 * @var array
59
	 */
60
	public $fields = array();
61
62
	/**
63
	 * Will store a filtered version of value for advenced fields (like images).
64
	 *
65
	 * @access protected
66
	 * @var array
67
	 */
68
	protected $filtered_value = array();
69
70
	/**
71
	 * The row label
72
	 *
73
	 * @access public
74
	 * @var array
75
	 */
76
	public $row_label = array();
77
78
	/**
79
	 * Constructor.
80
	 * Supplied `$args` override class property defaults.
81
	 * If `$args['settings']` is not defined, use the $id as the setting ID.
82
	 *
83
	 * @param WP_Customize_Manager $manager Customizer bootstrap instance.
84
	 * @param string               $id      Control ID.
85
	 * @param array                $args    {@see WP_Customize_Control::__construct}.
86
	 */
87
	public function __construct( $manager, $id, $args = array() ) {
88
89
		parent::__construct( $manager, $id, $args );
90
91
		// Set up defaults for row labels.
92
		$this->row_label = array(
93
			'type' => 'text',
94
			'value' => $this->l10n( 'row' ),
95
			'field' => false,
96
		);
97
98
		// Validate row-labels.
99
		$this->row_label( $args );
100
101
		if ( empty( $this->button_label ) ) {
102
			$this->button_label = $this->l10n( 'add-new' ) . ' ' . $this->row_label['value'];
103
		}
104
105
		if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
106
			$args['fields'] = array();
107
		}
108
109
		// An array to store keys of fields that need to be filtered.
110
		$media_fields_to_filter = array();
111
112
		foreach ( $args['fields'] as $key => $value ) {
113
			$args['fields'][ $key ]['default'] = ( ! isset( $value['default'] ) ) ? '' :;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ';'
Loading history...
114
			$args['fields'][ $key ]['label']   = ( ! isset( $value['label'] ) ) ? '' :;
115
			$args['fields'][ $key ]['id']      = $key;
116
117
			// We check if the filed is an uploaded media ( image , file, video, etc.. ).
118
			if ( isset( $value['type'] ) ) {
119
				switch ( $value['type'] ) {
120
					case 'image':
121
					case 'cropped_image':
122
					case 'upload':
123
						// We add it to the list of fields that need some extra filtering/processing.
124
						$media_fields_to_filter[ $key ] = true;
125
						break;
126
127
					case 'dropdown-pages':
128
						// If the field is a dropdown-pages field then add it to args.
129
						$dropdown = wp_dropdown_pages(
130
							array(
131
								'name'              => '',
132
								'echo'              => 0,
133
								'show_option_none'  => esc_attr( $this->l10n( 'select-page' ) ),
134
								'option_none_value' => '0',
135
								'selected'          => '',
136
							)
137
						);
138
						// Hackily add in the data link parameter.
139
						$dropdown = str_replace( '<select', '<select data-field="' . esc_attr( $args['fields'][ $key ]['id'] ) . '"' . $this->get_link(), $dropdown );
140
						$args['fields'][ $key ]['dropdown'] = $dropdown;
141
						break;
142
				}
143
			}
144
		}
145
146
		$this->fields = $args['fields'];
147
148
		// Now we are going to filter the fields.
149
		// First we create a copy of the value that would be used otherwise.
150
		$this->filtered_value = $this->value();
151
152
		if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
153
154
			// We iterate over the list of fields.
155
			foreach ( $this->filtered_value as &$filtered_value_field ) {
156
157
				if ( is_array( $filtered_value_field ) && ! empty( $filtered_value_field ) ) {
158
159
					// We iterate over the list of properties for this field.
160
					foreach ( $filtered_value_field as $key => &$value ) {
161
162
						// We check if this field was marked as requiring extra filtering (in this case image, cropped_images, upload).
163
						if ( array_key_exists( $key, $media_fields_to_filter ) ) {
164
165
							// What follows was made this way to preserve backward compatibility.
166
							// The repeater control use to store the URL for images instead of the attachment ID.
167
							// We check if the value look like an ID (otherwise it's probably a URL so don't filter it).
168
							if ( is_numeric( $value ) ) {
169
170
								// "sanitize" the value.
171
								$attachment_id = (int) $value;
172
173
								// Try to get the attachment_url.
174
								$url = wp_get_attachment_url( $attachment_id );
175
176
								$filename = basename( get_attached_file( $attachment_id ) );
177
178
								// If we got a URL.
179
								if ( $url ) {
180
181
									// 'id' is needed for form hidden value, URL is needed to display the image.
182
									$value = array(
183
										'id'  => $attachment_id,
184
										'url' => $url,
185
										'filename' => $filename,
186
									);
187
								}
188
							}
189
						}
190
					}
191
				}
192
			}
193
		}
194
	}
195
196
	/**
197
	 * Refresh the parameters passed to the JavaScript via JSON.
198
	 *
199
	 * @access public
200
	 */
201
	public function to_json() {
202
		parent::to_json();
203
204
		$this->json['default']     = ( isset( $this->default ) ) ? $this->default : $this->setting->default;
205
		$this->json['output']      = $this->output;
206
		$this->json['value']       = $this->value();
207
		$this->json['choices']     = $this->choices;
208
		$this->json['link']        = $this->get_link();
209
		$this->json['id']          = $this->id;
210
		$this->json['kirkiConfig'] = $this->kirki_config;
211
212
		if ( 'user_meta' === $this->option_type ) {
213
			$this->json['value'] = get_user_meta( get_current_user_id(), $this->id, true );
214
		}
215
216
		$this->json['inputAttrs'] = '';
217
		foreach ( $this->input_attrs as $attr => $value ) {
218
			$this->json['inputAttrs'] .= $attr . '="' . esc_attr( $value ) . '" ';
219
		}
220
221
		$fields = $this->fields;
222
223
		$this->json['fields'] = $fields;
224
		$this->json['row_label'] = $this->row_label;
225
226
		// If filtered_value has been set and is not empty we use it instead of the actual value.
227
		if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
228
			$this->json['value'] = $this->filtered_value;
229
		}
230
	}
231
232
	/**
233
	 * Enqueue control related scripts/styles.
234
	 *
235
	 * @access public
236
	 */
237
	public function enqueue() {
238
239
		// If we have a color picker field we need to enqueue the Wordpress Color Picker style and script.
240
		if ( is_array( $this->fields ) && ! empty( $this->fields ) ) {
241
			foreach ( $this->fields as $field ) {
242
				if ( isset( $field['type'] ) && 'color' === $field['type'] ) {
243
					wp_enqueue_script( 'wp-color-picker' );
244
					wp_enqueue_style( 'wp-color-picker' );
245
					break;
246
				}
247
			}
248
249
			foreach ( $this->fields as $field ) {
250
				if ( isset( $field['type'] ) && 'dropdown-pages' === $field['type'] ) {
251
					wp_enqueue_script( 'kirki-dropdown-pages' );
252
					break;
253
				}
254
			}
255
		}
256
257
		wp_enqueue_script( 'kirki-repeater', trailingslashit( Kirki::$url ) . 'controls/repeater/repeater.js', array( 'jquery', 'customize-base', 'jquery-ui-core', 'jquery-ui-sortable' ), false, true );
258
		wp_enqueue_style( 'kirki-repeater-css', trailingslashit( Kirki::$url ) . 'controls/repeater/repeater.css', null );
259
	}
260
261
	/**
262
	 * Render the control's content.
263
	 * Allows the content to be overriden without having to rewrite the wrapper in $this->render().
264
	 *
265
	 * @access protected
266
	 */
267
	protected function render_content() {
268
		?>
269
		<label>
270
			<?php if ( ! empty( $this->label ) ) : ?>
271
				<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
272
			<?php endif; ?>
273
			<?php if ( ! empty( $this->description ) ) : ?>
274
				<span class="description customize-control-description"><?php echo wp_kses_post( $this->description ); ?></span>
275
			<?php endif; ?>
276
			<input type="hidden" {{{ data.inputAttrs }}} value="" <?php echo wp_kses_post( $this->get_link() ); ?> />
277
		</label>
278
279
		<ul class="repeater-fields"></ul>
280
281
		<?php if ( isset( $this->choices['limit'] ) ) : ?>
282
			<p class="limit"><?php printf( esc_html( $this->l10n( 'limit-rows' ) ), esc_html( $this->choices['limit'] ) ); ?></p>
283
		<?php endif; ?>
284
		<button class="button-secondary repeater-add"><?php echo esc_html( $this->button_label ); ?></button>
285
286
		<?php
287
288
		$this->repeater_js_template();
289
290
	}
291
292
	/**
293
	 * An Underscore (JS) template for this control's content (but not its container).
294
	 * Class variables for this control class are available in the `data` JS object.
295
	 *
296
	 * @access public
297
	 */
298
	public function repeater_js_template() {
299
		?>
300
		<script type="text/html" class="customize-control-repeater-content">
301
			<# var field; var index = data.index; #>
302
			<# data.kirki_config = '<?php echo esc_attr( $this->kirki_config ); ?>'; #>
303
304
			<li class="repeater-row minimized" data-row="{{{ index }}}">
305
306
				<div class="repeater-row-header">
307
					<span class="repeater-row-label"></span>
308
					<i class="dashicons dashicons-arrow-down repeater-minimize"></i>
309
				</div>
310
				<div class="repeater-row-content">
311
					<# _.each( data, function( field, i ) { #>
312
313
						<div class="repeater-field repeater-field-{{{ field.type }}}">
314
315
							<# if ( 'text' === field.type || 'url' === field.type || 'link' === field.type || 'email' === field.type || 'tel' === field.type || 'date' === field.type ) { #>
316
317
								<# if ( 'link' === field.type ) { #>
318
									<# field.type = 'url' #>
319
								<# } #>
320
321
								<label>
322
									<# if ( field.label ) { #>
323
										<span class="customize-control-title">{{ field.label }}</span>
324
									<# } #>
325
									<# if ( field.description ) { #>
326
										<span class="description customize-control-description">{{ field.description }}</span>
327
									<# } #>
328
									<input type="{{field.type}}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}">
329
								</label>
330
331
							<# } else if ( 'hidden' === field.type ) { #>
332
333
								<input type="hidden" data-field="{{{ field.id }}}" <# if ( field.default ) { #> value="{{{ field.default }}}" <# } #> />
334
335
							<# } else if ( 'checkbox' === field.type ) { #>
336
337
								<label>
338
									<input type="checkbox" value="true" data-field="{{{ field.id }}}" <# if ( field.default ) { #> checked="checked" <# } #> /> {{ field.label }}
339
									<# if ( field.description ) { #>
340
										{{ field.description }}
341
									<# } #>
342
								</label>
343
344
							<# } else if ( 'select' === field.type ) { #>
345
346
								<label>
347
									<# if ( field.label ) { #>
348
										<span class="customize-control-title">{{ field.label }}</span>
349
									<# } #>
350
									<# if ( field.description ) { #>
351
										<span class="description customize-control-description">{{ field.description }}</span>
352
									<# } #>
353
									<select data-field="{{{ field.id }}}">
354
										<# _.each( field.choices, function( choice, i ) { #>
355
											<option value="{{{ i }}}" <# if ( field.default == i ) { #> selected="selected" <# } #>>{{ choice }}</option>
356
										<# }); #>
357
									</select>
358
								</label>
359
360
							<# } else if ( 'dropdown-pages' === field.type ) { #>
361
362
								<label>
363
									<# if ( field.label ) { #>
364
										<span class="customize-control-title">{{{ data.label }}}</span>
365
									<# } #>
366
									<# if ( field.description ) { #>
367
										<span class="description customize-control-description">{{{ field.description }}}</span>
368
									<# } #>
369
									<div class="customize-control-content repeater-dropdown-pages">{{{ field.dropdown }}}</div>
370
								</label>
371
372
							<# } else if ( 'radio' === field.type ) { #>
373
374
								<label>
375
									<# if ( field.label ) { #>
376
										<span class="customize-control-title">{{ field.label }}</span>
377
									<# } #>
378
									<# if ( field.description ) { #>
379
										<span class="description customize-control-description">{{ field.description }}</span>
380
									<# } #>
381
382
									<# _.each( field.choices, function( choice, i ) { #>
383
										<label>
384
											<input type="radio" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>> {{ choice }} <br/>
385
										</label>
386
									<# }); #>
387
								</label>
388
389
							<# } else if ( 'radio-image' === field.type ) { #>
390
391
								<label>
392
									<# if ( field.label ) { #>
393
										<span class="customize-control-title">{{ field.label }}</span>
394
									<# } #>
395
									<# if ( field.description ) { #>
396
										<span class="description customize-control-description">{{ field.description }}</span>
397
									<# } #>
398
399
									<# _.each( field.choices, function( choice, i ) { #>
400
										<input type="radio" id="{{{ field.id }}}_{{ index }}_{{{ i }}}" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>>
401
											<label for="{{{ field.id }}}_{{ index }}_{{{ i }}}">
402
												<img src="{{ choice }}">
403
											</label>
404
										</input>
405
									<# }); #>
406
								</label>
407
408
							<# } else if ( 'color' === field.type ) { #>
409
410
								<# var defaultValue = '';
411
						        if ( field.default ) {
412
						            if ( '#' !== field.default.substring( 0, 1 ) ) {
413
						                defaultValue = '#' + field.default;
414
						            } else {
415
						                defaultValue = field.default;
416
						            }
417
						            defaultValue = ' data-default-color=' + defaultValue; // Quotes added automatically.
418
						        } #>
419
						        <label>
420
						            <# if ( field.label ) { #>
421
						                <span class="customize-control-title">{{{ field.label }}}</span>
422
						            <# } #>
423
						            <# if ( field.description ) { #>
424
						                <span class="description customize-control-description">{{{ field.description }}}</span>
425
						            <# } #>
426
						            <input class="color-picker-hex" type="text" maxlength="7" placeholder="<?php echo esc_attr( $this->l10n( 'hex-value' ) ); ?>"  value="{{{ field.default }}}" data-field="{{{ field.id }}}" {{ defaultValue }} />
427
428
						        </label>
429
430
							<# } else if ( 'textarea' === field.type ) { #>
431
432
								<# if ( field.label ) { #>
433
									<span class="customize-control-title">{{ field.label }}</span>
434
								<# } #>
435
								<# if ( field.description ) { #>
436
									<span class="description customize-control-description">{{ field.description }}</span>
437
								<# } #>
438
								<textarea rows="5" data-field="{{{ field.id }}}">{{ field.default }}</textarea>
439
440
							<# } else if ( field.type === 'image' || field.type === 'cropped_image' ) { #>
441
442
								<label>
443
									<# if ( field.label ) { #>
444
										<span class="customize-control-title">{{ field.label }}</span>
445
									<# } #>
446
									<# if ( field.description ) { #>
447
										<span class="description customize-control-description">{{ field.description }}</span>
448
									<# } #>
449
								</label>
450
451
								<figure class="kirki-image-attachment" data-placeholder="<?php echo esc_attr( $this->l10n( 'no-image-selected' ) ); ?>" >
452
									<# if ( field.default ) { #>
453
										<# var defaultImageURL = ( field.default.url ) ? field.default.url : field.default; #>
454
										<img src="{{{ defaultImageURL }}}">
455
									<# } else { #>
456
										<?php echo esc_attr( $this->l10n( 'no-image-selected' ) ); ?>
457
									<# } #>
458
								</figure>
459
460
								<div class="actions">
461
									<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php echo esc_attr( $this->l10n( 'remove' ) ); ?></button>
462
									<button type="button" class="button upload-button" data-label=" <?php echo esc_attr( $this->l10n( 'add-image' ) ); ?>" data-alt-label="<?php echo esc_attr( $this->l10n( 'change-image' ) ); ?>" >
463
										<# if ( field.default ) { #>
464
											<?php echo esc_attr( $this->l10n( 'change-image' ) ); ?>
465
										<# } else { #>
466
											<?php echo esc_attr( $this->l10n( 'add-image' ) ); ?>
467
										<# } #>
468
									</button>
469
									<# if ( field.default.id ) { #>
470
										<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
471
									<# } else { #>
472
										<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
473
									<# } #>
474
								</div>
475
476
							<# } else if ( field.type === 'upload' ) { #>
477
478
								<label>
479
									<# if ( field.label ) { #>
480
										<span class="customize-control-title">{{ field.label }}</span>
481
									<# } #>
482
									<# if ( field.description ) { #>
483
										<span class="description customize-control-description">{{ field.description }}</span>
484
									<# } #>
485
								</label>
486
487
								<figure class="kirki-file-attachment" data-placeholder="<?php echo esc_attr( $this->l10n( 'no-file-selected' ) ); ?>" >
488
									<# if ( field.default ) { #>
489
										<# var defaultFilename = ( field.default.filename ) ? field.default.filename : field.default; #>
490
										<span class="file"><span class="dashicons dashicons-media-default"></span> {{ defaultFilename }}</span>
491
									<# } else { #>
492
										<?php echo esc_attr( $this->l10n( 'no-file-selected' ) ); ?>
493
									<# } #>
494
								</figure>
495
496
								<div class="actions">
497
									<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"></button>
498
									<button type="button" class="button upload-button" data-label="<?php echo esc_attr( $this->l10n( 'add-file' ) ); ?>" data-alt-label="<?php echo esc_attr( $this->l10n( 'change-file' ) ); ?>" >
499
										<# if ( field.default ) { #>
500
											<?php echo esc_attr( $this->l10n( 'change-file' ) ); ?>
501
										<# } else { #>
502
											<?php echo esc_attr( $this->l10n( 'add-file' ) ); ?>
503
										<# } #>
504
									</button>
505
									<# if ( field.default.id ) { #>
506
										<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
507
									<# } else { #>
508
										<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
509
									<# } #>
510
								</div>
511
512
							<# } else if ( 'custom' === field.type ) { #>
513
514
								<# if ( field.label ) { #>
515
									<span class="customize-control-title">{{ field.label }}</span>
516
								<# } #>
517
								<# if ( field.description ) { #>
518
									<span class="description customize-control-description">{{ field.description }}</span>
519
								<# } #>
520
								<div data-field="{{{ field.id }}}">{{{ field.default }}}</div>
521
522
							<# } #>
523
524
						</div>
525
					<# }); #>
526
					<button type="button" class="button-link repeater-row-remove"><?php echo esc_attr( $this->l10n( 'remove' ) ); ?></button>
527
				</div>
528
			</li>
529
		</script>
530
		<?php
531
	}
532
533
	/**
534
	 * Validate row-labels.
535
	 *
536
	 * @access protected
537
	 * @since 2.4.0
538
	 * @param array $args {@see WP_Customize_Control::__construct}.
539
	 */
540
	protected function row_label( $args ) {
541
542
		// Validating args for row labels.
543
		if ( isset( $args['row_label'] ) && is_array( $args['row_label'] ) && ! empty( $args['row_label'] ) ) {
544
545
			// Validating row label type.
546
			if ( isset( $args['row_label']['type'] ) && ( 'text' === $args['row_label']['type'] || 'field' === $args['row_label']['type'] ) ) {
547
				$this->row_label['type'] = $args['row_label']['type'];
548
			}
549
550
			// Validating row label type.
551
			if ( isset( $args['row_label']['value'] ) && ! empty( $args['row_label']['value'] ) ) {
552
				$this->row_label['value'] = esc_attr( $args['row_label']['value'] );
553
			}
554
555
			// Validating row label field.
556
			if ( isset( $args['row_label']['field'] ) && ! empty( $args['row_label']['field'] ) && isset( $args['fields'][ esc_attr( $args['row_label']['field'] ) ] ) ) {
557
				$this->row_label['field'] = esc_attr( $args['row_label']['field'] );
558
			} else {
559
				// If from field is not set correctly, making sure standard is set as the type.
560
				$this->row_label['type'] = 'text';
561
			}
562
		}
563
	}
564
565
	/**
566
	 * Returns an array of translation strings.
567
	 *
568
	 * @access protected
569
	 * @since 2.4.0
570
	 * @param string|false $id The string-ID.
571
	 * @return string
572
	 */
573
	protected function l10n( $id = false ) {
574
		$translation_strings = array(
575
			'row'               => esc_attr__( 'row', 'kirki' ),
576
			'add-new'           => esc_attr__( 'Add new', 'kirki' ),
577
			'select-page'       => esc_attr__( 'Select a Page', 'kirki' ),
578
			'limit-rows'        => esc_attr__( 'Limit: %s rows', 'kirki' ),
579
			'hex-value'         => esc_attr__( 'Hex Value', 'kirki' ),
580
			'no-image-selected' => esc_attr__( 'No Image Selected', 'kirki' ),
581
			'remove'            => esc_attr__( 'Remove', 'kirki' ),
582
			'add-image'         => esc_attr__( 'Add Image', 'kirki' ),
583
			'change-image'      => esc_attr__( 'Change Image', 'kirki' ),
584
			'no-file-selected'  => esc_attr__( 'No File Selected', 'kirki' ),
585
			'add-file'          => esc_attr__( 'Add File', 'kirki' ),
586
			'change-file'       => esc_attr__( 'Change File', 'kirki' ),
587
		);
588
		$translation_strings = apply_filters( 'kirki/' . $this->kirki_config . '/l10n', $translation_strings );
589
		if ( false === $id ) {
590
			return $translation_strings;
591
		}
592
		return $translation_strings[ $id ];
593
	}
594
}
595