class-kirki-controls-repeater-control.php (19659B)
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 if ( ! class_exists( 'Kirki_Controls_Repeater_Control' ) ) { 18 19 /** 20 * Repeater control 21 */ 22 class Kirki_Controls_Repeater_Control extends Kirki_Customize_Control { 23 24 /** 25 * The control type. 26 * 27 * @access public 28 * @var string 29 */ 30 public $type = 'repeater'; 31 32 /** 33 * The fields that each container row will contain. 34 * 35 * @access public 36 * @var array 37 */ 38 public $fields = array(); 39 40 /** 41 * Will store a filtered version of value for advenced fields (like images). 42 * 43 * @access protected 44 * @var array 45 */ 46 protected $filtered_value = array(); 47 48 /** 49 * The row label 50 * 51 * @access public 52 * @var array 53 */ 54 public $row_label = array(); 55 56 /** 57 * Constructor. 58 * Supplied `$args` override class property defaults. 59 * If `$args['settings']` is not defined, use the $id as the setting ID. 60 * 61 * @param WP_Customize_Manager $manager Customizer bootstrap instance. 62 * @param string $id Control ID. 63 * @param array $args {@see WP_Customize_Control::__construct}. 64 */ 65 public function __construct( $manager, $id, $args = array() ) { 66 67 parent::__construct( $manager, $id, $args ); 68 69 // Set up defaults for row labels. 70 $this->row_label = array( 71 'type' => 'text', 72 'value' => $this->l10n['row'], 73 'field' => false, 74 ); 75 76 // Validating args for row labels. 77 if ( isset( $args['row_label'] ) && is_array( $args['row_label'] ) && ! empty( $args['row_label'] ) ) { 78 79 // Validating row label type. 80 if ( isset( $args['row_label']['type'] ) && ( 'text' === $args['row_label']['type'] || 'field' === $args['row_label']['type'] ) ) { 81 $this->row_label['type'] = $args['row_label']['type']; 82 } 83 84 // Validating row label type. 85 if ( isset( $args['row_label']['value'] ) && ! empty( $args['row_label']['value'] ) ) { 86 $this->row_label['value'] = esc_attr( $args['row_label']['value'] ); 87 } 88 89 // Validating row label field. 90 if ( isset( $args['row_label']['field'] ) && ! empty( $args['row_label']['field'] ) && isset( $args['fields'][ esc_attr( $args['row_label']['field'] ) ] ) ) { 91 $this->row_label['field'] = esc_attr( $args['row_label']['field'] ); 92 } else { 93 94 // If from field is not set correctly, making sure standard is set as the type. 95 $this->row_label['type'] = 'text'; 96 } 97 } 98 99 if ( empty( $this->button_label ) ) { 100 $this->button_label = $this->l10n['add-new'] . ' ' . $this->row_label['value']; 101 } 102 103 if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) { 104 $args['fields'] = array(); 105 } 106 107 // An array to store keys of fields that need to be filtered. 108 $media_fields_to_filter = array(); 109 110 foreach ( $args['fields'] as $key => $value ) { 111 if ( ! isset( $value['default'] ) ) { 112 $args['fields'][ $key ]['default'] = ''; 113 } 114 115 if ( ! isset( $value['label'] ) ) { 116 $args['fields'][ $key ]['label'] = ''; 117 } 118 $args['fields'][ $key ]['id'] = $key; 119 120 // We check if the filed is an uploaded media ( image , file, video, etc.. ). 121 if ( isset( $value['type'] ) && ( 'image' === $value['type'] || 'cropped_image' === $value['type'] || 'upload' === $value['type'] ) ) { 122 123 // We add it to the list of fields that need some extra filtering/processing. 124 $media_fields_to_filter[ $key ] = true; 125 } 126 127 // If the field is a dropdown-pages field then add it to args. 128 if ( isset( $value['type'] ) && ( 'dropdown-pages' === $value['type'] ) ) { 129 130 $dropdown = wp_dropdown_pages( 131 array( 132 'name' => '', 133 'echo' => 0, 134 'show_option_none' => esc_attr( $this->l10n['select-page'] ), 135 'option_none_value' => '0', 136 'selected' => '', 137 ) 138 ); 139 140 // Hackily add in the data link parameter. 141 $dropdown = str_replace( '<select', '<select data-field="' . esc_attr( $args['fields'][ $key ]['id'] ) . '"' . $this->get_link(), $dropdown ); 142 143 $args['fields'][ $key ]['dropdown'] = $dropdown; 144 } 145 } 146 147 $this->fields = $args['fields']; 148 149 // Now we are going to filter the fields. 150 // First we create a copy of the value that would be used otherwise. 151 $this->filtered_value = $this->value(); 152 153 if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) { 154 155 // We iterate over the list of fields. 156 foreach ( $this->filtered_value as &$filtered_value_field ) { 157 158 if ( is_array( $filtered_value_field ) && ! empty( $filtered_value_field ) ) { 159 160 // We iterate over the list of properties for this field. 161 foreach ( $filtered_value_field as $key => &$value ) { 162 163 // We check if this field was marked as requiring extra filtering (in this case image, cropped_images, upload). 164 if ( array_key_exists( $key, $media_fields_to_filter ) ) { 165 166 // What follows was made this way to preserve backward compatibility. 167 // The repeater control use to store the URL for images instead of the attachment ID. 168 // We check if the value look like an ID (otherwise it's probably a URL so don't filter it). 169 if ( is_numeric( $value ) ) { 170 171 // "sanitize" the value. 172 $attachment_id = (int) $value; 173 174 // Try to get the attachment_url. 175 $url = wp_get_attachment_url( $attachment_id ); 176 177 $filename = basename( get_attached_file( $attachment_id ) ); 178 179 // If we got a URL. 180 if ( $url ) { 181 182 // 'id' is needed for form hidden value, URL is needed to display the image. 183 $value = array( 184 'id' => $attachment_id, 185 'url' => $url, 186 'filename' => $filename, 187 ); 188 } 189 } 190 } 191 } 192 } 193 } 194 } 195 } 196 197 /** 198 * Refresh the parameters passed to the JavaScript via JSON. 199 * 200 * @access public 201 */ 202 public function to_json() { 203 parent::to_json(); 204 205 $fields = $this->fields; 206 207 $this->json['fields'] = $fields; 208 $this->json['row_label'] = $this->row_label; 209 210 // If filtered_value has been set and is not empty we use it instead of the actual value. 211 if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) { 212 $this->json['value'] = $this->filtered_value; 213 } 214 } 215 216 /** 217 * Enqueue control related scripts/styles. 218 * 219 * @access public 220 */ 221 public function enqueue() { 222 223 // If we have a color picker field we need to enqueue the Wordpress Color Picker style and script. 224 if ( is_array( $this->fields ) && ! empty( $this->fields ) ) { 225 foreach ( $this->fields as $field ) { 226 if ( isset( $field['type'] ) && 'color' === $field['type'] ) { 227 wp_enqueue_script( 'wp-color-picker' ); 228 wp_enqueue_style( 'wp-color-picker' ); 229 break; 230 } 231 } 232 233 foreach ( $this->fields as $field ) { 234 if ( isset( $field['type'] ) && 'dropdown-pages' === $field['type'] ) { 235 wp_enqueue_script( 'kirki-dropdown-pages' ); 236 break; 237 } 238 } 239 } 240 241 wp_enqueue_script( 'kirki-repeater' ); 242 } 243 244 /** 245 * Render the control's content. 246 * Allows the content to be overriden without having to rewrite the wrapper in $this->render(). 247 * 248 * @access protected 249 */ 250 protected function render_content() { 251 ?> 252 <?php if ( '' !== $this->tooltip ) : ?> 253 <a href="#" class="tooltip hint--left" data-hint="<?php echo esc_html( $this->tooltip ); ?>"><span class='dashicons dashicons-info'></span></a> 254 <?php endif; ?> 255 <label> 256 <?php if ( ! empty( $this->label ) ) : ?> 257 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> 258 <?php endif; ?> 259 <?php if ( ! empty( $this->description ) ) : ?> 260 <span class="description customize-control-description"><?php echo wp_kses_post( $this->description ); ?></span> 261 <?php endif; ?> 262 <input type="hidden" {{{ data.inputAttrs }}} value="" <?php echo wp_kses_post( $this->get_link() ); ?> /> 263 </label> 264 265 <ul class="repeater-fields"></ul> 266 267 <?php if ( 0 && isset( $this->choices['limit'] ) ) : ?> 268 <p class="limit"><?php printf( esc_html( $this->l10n['limit-rows'] ), esc_html( $this->choices['limit'] ) ); ?></p> 269 <?php endif; ?> 270 <button class="button-secondary repeater-add"><?php echo esc_html( $this->button_label ); ?></button> 271 272 <?php 273 274 $this->repeater_js_template(); 275 276 } 277 278 /** 279 * An Underscore (JS) template for this control's content (but not its container). 280 * Class variables for this control class are available in the `data` JS object. 281 * 282 * @access public 283 */ 284 public function repeater_js_template() { 285 ?> 286 <script type="text/html" class="customize-control-repeater-content"> 287 <# var field; var index = data.index; #> 288 <# data.kirki_config = '<?php echo esc_attr( $this->kirki_config ); ?>'; #> 289 290 <li class="repeater-row minimized" data-row="{{{ index }}}"> 291 292 <div class="repeater-row-header"> 293 <span class="repeater-row-label"></span> 294 <i class="dashicons dashicons-arrow-down repeater-minimize"></i> 295 </div> 296 <div class="repeater-row-content"> 297 <# _.each( data, function( field, i ) { #> 298 299 <div class="repeater-field repeater-field-{{{ field.type }}}"> 300 301 <# if ( 'text' === field.type || 'url' === field.type || 'link' === field.type || 'email' === field.type || 'tel' === field.type || 'date' === field.type ) { #> 302 303 <# if ( 'link' === field.type ) { #> 304 <# field.type = 'url' #> 305 <# } #> 306 307 <label> 308 <# if ( field.label ) { #> 309 <span class="customize-control-title">{{ field.label }}</span> 310 <# } #> 311 <# if ( field.description ) { #> 312 <span class="description customize-control-description">{{ field.description }}</span> 313 <# } #> 314 <input type="{{field.type}}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}"> 315 </label> 316 317 <# } else if ( 'hidden' === field.type ) { #> 318 319 <input type="hidden" data-field="{{{ field.id }}}" <# if ( field.default ) { #> value="{{ field.default }}" <# } #> /> 320 321 <# } else if ( 'checkbox' === field.type ) { #> 322 323 <label> 324 <input type="checkbox" value="true" data-field="{{{ field.id }}}" <# if ( field.default ) { #> checked="checked" <# } #> /> {{ field.label }} 325 <# if ( field.description ) { #> 326 {{ field.description }} 327 <# } #> 328 </label> 329 330 <# } else if ( 'select' === field.type ) { #> 331 332 <label> 333 <# if ( field.label ) { #> 334 <span class="customize-control-title">{{ field.label }}</span> 335 <# } #> 336 <# if ( field.description ) { #> 337 <span class="description customize-control-description">{{ field.description }}</span> 338 <# } #> 339 <select data-field="{{{ field.id }}}"> 340 <# _.each( field.choices, function( choice, i ) { #> 341 <option value="{{{ i }}}" <# if ( field.default == i ) { #> selected="selected" <# } #>>{{ choice }}</option> 342 <# }); #> 343 </select> 344 </label> 345 346 <# } else if ( 'dropdown-pages' === field.type ) { #> 347 348 <label> 349 <# if ( field.label ) { #> 350 <span class="customize-control-title">{{{ data.label }}}</span> 351 <# } #> 352 <# if ( field.description ) { #> 353 <span class="description customize-control-description">{{{ field.description }}}</span> 354 <# } #> 355 <div class="customize-control-content repeater-dropdown-pages">{{{ field.dropdown }}}</div> 356 </label> 357 358 <# } else if ( 'radio' === field.type ) { #> 359 360 <label> 361 <# if ( field.label ) { #> 362 <span class="customize-control-title">{{ field.label }}</span> 363 <# } #> 364 <# if ( field.description ) { #> 365 <span class="description customize-control-description">{{ field.description }}</span> 366 <# } #> 367 368 <# _.each( field.choices, function( choice, i ) { #> 369 <label> 370 <input type="radio" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>> {{ choice }} <br/> 371 </label> 372 <# }); #> 373 </label> 374 375 <# } else if ( 'radio-image' === field.type ) { #> 376 377 <label> 378 <# if ( field.label ) { #> 379 <span class="customize-control-title">{{ field.label }}</span> 380 <# } #> 381 <# if ( field.description ) { #> 382 <span class="description customize-control-description">{{ field.description }}</span> 383 <# } #> 384 385 <# _.each( field.choices, function( choice, i ) { #> 386 <input type="radio" id="{{{ field.id }}}_{{ index }}_{{{ i }}}" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>> 387 <label for="{{{ field.id }}}_{{ index }}_{{{ i }}}"> 388 <img src="{{ choice }}"> 389 </label> 390 </input> 391 <# }); #> 392 </label> 393 394 <# } else if ( 'color' === field.type ) { #> 395 396 <# var defaultValue = ''; 397 if ( field.default ) { 398 if ( '#' !== field.default.substring( 0, 1 ) ) { 399 defaultValue = '#' + field.default; 400 } else { 401 defaultValue = field.default; 402 } 403 defaultValue = ' data-default-color=' + defaultValue; // Quotes added automatically. 404 } #> 405 <label> 406 <# if ( field.label ) { #> 407 <span class="customize-control-title">{{{ field.label }}}</span> 408 <# } #> 409 <# if ( field.description ) { #> 410 <span class="description customize-control-description">{{{ field.description }}}</span> 411 <# } #> 412 <input data-alpha="{{{ field.alpha }}}" 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 }} /> 413 414 </label> 415 416 <# } else if ( 'textarea' === field.type ) { #> 417 418 <# if ( field.label ) { #> 419 <span class="customize-control-title">{{ field.label }}</span> 420 <# } #> 421 <# if ( field.description ) { #> 422 <span class="description customize-control-description">{{ field.description }}</span> 423 <# } #> 424 <textarea rows="5" data-field="{{{ field.id }}}">{{ field.default }}</textarea> 425 426 <# } else if ( field.type === 'image' || field.type === 'cropped_image' ) { #> 427 428 <label> 429 <# if ( field.label ) { #> 430 <span class="customize-control-title">{{ field.label }}</span> 431 <# } #> 432 <# if ( field.description ) { #> 433 <span class="description customize-control-description">{{ field.description }}</span> 434 <# } #> 435 </label> 436 437 <figure class="kirki-image-attachment" data-placeholder="<?php echo esc_attr( $this->l10n['no-image-selected'] ); ?>" > 438 <# if ( field.default ) { #> 439 <# var defaultImageURL = ( field.default.url ) ? field.default.url : field.default; #> 440 <img src="{{{ defaultImageURL }}}"> 441 <# } else { #> 442 <?php echo esc_attr( $this->l10n['no-image-selected'] ); ?> 443 <# } #> 444 </figure> 445 446 <div class="actions"> 447 <button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php echo esc_attr( $this->l10n['remove'] ); ?></button> 448 <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'] ); ?>" > 449 <# if ( field.default ) { #> 450 <?php echo esc_attr( $this->l10n['change-image'] ); ?> 451 <# } else { #> 452 <?php echo esc_attr( $this->l10n['add-image'] ); ?> 453 <# } #> 454 </button> 455 <# if ( field.default.id ) { #> 456 <input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" > 457 <# } else { #> 458 <input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" > 459 <# } #> 460 </div> 461 462 <# } else if ( field.type === 'upload' ) { #> 463 464 <label> 465 <# if ( field.label ) { #> 466 <span class="customize-control-title">{{ field.label }}</span> 467 <# } #> 468 <# if ( field.description ) { #> 469 <span class="description customize-control-description">{{ field.description }}</span> 470 <# } #> 471 </label> 472 473 <figure class="kirki-file-attachment" data-placeholder="<?php echo esc_attr( $this->l10n['no-file-selected'] ); ?>" > 474 <# if ( field.default ) { #> 475 <# var defaultFilename = ( field.default.filename ) ? field.default.filename : field.default; #> 476 <span class="file"><span class="dashicons dashicons-media-default"></span> {{ defaultFilename }}</span> 477 <# } else { #> 478 <?php echo esc_attr( $this->l10n['no-file-selected'] ); ?> 479 <# } #> 480 </figure> 481 482 <div class="actions"> 483 <button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"></button> 484 <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'] ); ?>" > 485 <# if ( field.default ) { #> 486 <?php echo esc_attr( $this->l10n['change-file'] ); ?> 487 <# } else { #> 488 <?php echo esc_attr( $this->l10n['add-file'] ); ?> 489 <# } #> 490 </button> 491 <# if ( field.default.id ) { #> 492 <input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" > 493 <# } else { #> 494 <input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" > 495 <# } #> 496 </div> 497 498 <# } else if ( 'custom' === field.type ) { #> 499 500 <# if ( field.label ) { #> 501 <span class="customize-control-title">{{ field.label }}</span> 502 <# } #> 503 <# if ( field.description ) { #> 504 <span class="description customize-control-description">{{ field.description }}</span> 505 <# } #> 506 <div data-field="{{{ field.id }}}">{{{ field.default }}}</div> 507 508 <# } else if( 'sectionseparator' === field.type ) { #> 509 <div class="one-page-express-separator"> 510 <label> 511 <# if ( field.label ) { #> 512 <span class="customize-control-title">{{ field.label }}</span> 513 <# } #> 514 <# if ( field.description ) { #> 515 <span class="description customize-control-description">{{ field.description }}</span> 516 <# } #> 517 </label> 518 </div> 519 <# } #> 520 521 </div> 522 <# }); #> 523 <button type="button" class="button-link repeater-row-remove"><?php echo esc_attr( $this->l10n['remove'] ); ?></button> 524 </div> 525 </li> 526 </script> 527 <?php 528 } 529 } 530 }