class-kirki-field.php (28568B)
1 <?php 2 /** 3 * Creates and validates field parameters. 4 * 5 * @package Kirki 6 * @category Core 7 * @author Aristeides Stathopoulos 8 * @copyright Copyright (c) 2016, Aristeides Stathopoulos 9 * @license http://opensource.org/licenses/https://opensource.org/licenses/MIT 10 * @since 1.0 11 */ 12 13 if ( ! class_exists('Kirki_Field')) { 14 15 /** 16 * Please do not use this class directly. 17 * You should instead extend it per-field-type. 18 */ 19 class Kirki_Field 20 { 21 22 /** 23 * The ID of the kirki_config we're using. 24 * 25 * @see Kirki_Config 26 * @access protected 27 * @var string 28 */ 29 protected $kirki_config = 'global'; 30 31 /** 32 * Thje capability required so that users can edit this field. 33 * 34 * @access protected 35 * @var string 36 */ 37 protected $capability = 'edit_theme_options'; 38 39 /** 40 * If we're using options instead of theme_mods 41 * and we want them serialized, this is the option that 42 * will saved in the db. 43 * 44 * @access protected 45 * @var string 46 */ 47 protected $option_name = ''; 48 49 /** 50 * Vustom input attributes (defined as an array). 51 * 52 * @access protected 53 * @var array 54 */ 55 protected $input_attrs = array(); 56 57 /** 58 * Use "theme_mod" or "option". 59 * 60 * @access protected 61 * @var string 62 */ 63 protected $option_type = 'theme_mod'; 64 65 /** 66 * The name of this setting (id for the db). 67 * 68 * @access protected 69 * @var string|array 70 */ 71 protected $settings = ''; 72 73 /** 74 * Set to true if you want to disable all CSS output for this field. 75 * 76 * @access protected 77 * @var bool 78 */ 79 protected $disable_output = false; 80 81 /** 82 * The field type. 83 * 84 * @access protected 85 * @var string 86 */ 87 protected $type = 'kirki-generic'; 88 89 /** 90 * Some fields require options to be set. 91 * We're whitelisting the property here 92 * and suggest you validate this in a child class. 93 * 94 * @access protected 95 * @var array 96 */ 97 protected $choices = array(); 98 99 /** 100 * Assign this field to a section. 101 * Fields not assigned to a section will not be displayed in the customizer. 102 * 103 * @access protected 104 * @var string 105 */ 106 protected $section = ''; 107 108 /** 109 * The default value for this field. 110 * 111 * @access protected 112 * @var string|array 113 */ 114 protected $default = ''; 115 116 /** 117 * Priority determines the position of a control inside a section. 118 * Lower priority numbers move the control to the top. 119 * 120 * @access protected 121 * @var int 122 */ 123 protected $priority = 10; 124 125 /** 126 * Unique ID for this field. 127 * This is auto-calculated from the $settings argument. 128 * 129 * @access protected 130 * @var string 131 */ 132 protected $id = ''; 133 134 /** 135 * Use if you want to automatically generate CSS from this field's value. 136 * 137 * @see https://kirki.org/docs/arguments/output 138 * @access protected 139 * @var array 140 */ 141 protected $output = array(); 142 143 /** 144 * Use to automatically generate postMessage scripts. 145 * Not necessary to use if you use 'transport' => 'auto' 146 * and have already set an array for the 'output' argument. 147 * 148 * @see https://kirki.org/docs/arguments/js_vars 149 * @access protected 150 * @var array 151 */ 152 protected $js_vars = array(); 153 154 /** 155 * If you want to use a CSS compiler, then use this to set the variable names. 156 * 157 * @see https://kirki.org/docs/arguments/variables 158 * @access protected 159 * @var array 160 */ 161 protected $variables = array(); 162 163 /** 164 * Text that will be used in a tooltip to provide extra info for this field. 165 * 166 * @access protected 167 * @var string 168 */ 169 protected $tooltip = ''; 170 171 /** 172 * Whitelisting for backwards-compatibility. 173 * 174 * @access protected 175 * @var string 176 */ 177 protected $help = ''; 178 179 /** 180 * Whitelisting for backwards-compatibility. 181 * 182 * @access protected 183 * @var string 184 */ 185 protected $mode = ''; 186 187 /** 188 * A custom callback to determine if the field should be visible or not. 189 * 190 * @access protected 191 * @var string|array 192 */ 193 protected $active_callback = '__return_true'; 194 195 /** 196 * A custom sanitize callback that will be used to properly save the values. 197 * 198 * @access protected 199 * @var string|array 200 */ 201 protected $sanitize_callback = ''; 202 203 /** 204 * Use 'refresh', 'postMessage' or 'auto'. 205 * 'auto' will automatically geberate any 'js_vars' from the 'output' argument. 206 * 207 * @access protected 208 * @var string 209 */ 210 protected $transport = 'refresh'; 211 212 /** 213 * Define dependencies to show/hide this field based on the values of other fields. 214 * 215 * @access protected 216 * @var array 217 */ 218 protected $required = array(); 219 220 /** 221 * Use only on select controls. 222 * Defines if this is a multi-select or not. 223 * If value is > 1, then the maximum number of selectable options 224 * is the number defined here. 225 * 226 * @access protected 227 * @var int 228 */ 229 protected $multiple = 1; 230 231 /** 232 * Suggested width for cropped image. 233 * 234 * @access protected 235 * @var int 236 */ 237 protected $width = 150; 238 239 /** 240 * Suggested height for cropped image. 241 * 242 * @access protected 243 * @var int 244 */ 245 protected $height = 150; 246 247 /** 248 * Whether the width is flexible for cropped image. 249 * 250 * @access protected 251 * @var bool 252 */ 253 protected $flex_width = false; 254 255 /** 256 * Whether the height is flexible for cropped image. 257 * 258 * @access protected 259 * @var bool 260 */ 261 protected $flex_height = false; 262 263 /** 264 * Contain the settings for the repeater rows labels 265 * 266 * @access protected 267 * @var array 268 */ 269 protected $row_label = array(); 270 271 protected $in_row_with = array(); 272 protected $group = ""; 273 /** 274 * Partial Refreshes array. 275 * 276 * @access protected 277 * @var array 278 */ 279 protected $partial_refresh = array(); 280 281 /** 282 * Use only on image, cropped_image, upload controls. 283 * Limit the Media library to a specific mime type 284 * 285 * @access protected 286 * @var array 287 */ 288 protected $mime_type = ''; 289 290 /** 291 * The class constructor. 292 * Parses and sanitizes all field arguments. 293 * Then it adds the field to Kirki::$fields. 294 * 295 * @access public 296 * 297 * @param string $config_id The ID of the config we want to use. 298 * Defaults to "global". 299 * Configs are handled by the Kirki_Config class. 300 * @param array $args The arguments of the field. 301 */ 302 public function __construct($config_id = 'global', $args = array()) 303 { 304 305 if (isset($args['setting']) && ! empty($args['setting']) && ( ! isset($args['settings']) || empty($args['settings']))) { 306 $args['settings'] = $args['setting']; 307 unset($args['setting']); 308 error_log('Kirki: Typo found in field ' . $args['settings'] . ' ("setting" instead of "settings").'); 309 } 310 311 if (is_string($config_id)) { 312 $args['kirki_config'] = $config_id; 313 } 314 315 // In case the user only provides 1 argument, 316 // assume that the provided argument is $args and set $config_id = 'global'. 317 if (is_array($config_id) && empty($args)) { 318 $args = $config_id; 319 $this->kirki_config = 'global'; 320 } 321 $this->kirki_config = trim(esc_attr($config_id)); 322 if ('' === $config_id) { 323 $this->kirki_config = 'global'; 324 } 325 326 // Get defaults from the class. 327 $defaults = get_class_vars(__CLASS__); 328 329 // Get the config arguments, and merge them with the defaults. 330 $config_defaults = (isset(Kirki::$config['global'])) ? Kirki::$config['global'] : array(); 331 if ('global' !== $this->kirki_config && isset(Kirki::$config[$this->kirki_config])) { 332 $config_defaults = Kirki::$config[$this->kirki_config]; 333 } 334 $config_defaults = (is_array($config_defaults)) ? $config_defaults : array(); 335 foreach ($config_defaults as $key => $value) { 336 if (isset($defaults[$key])) { 337 if ( ! empty($value) && $value != $defaults[$key]) { 338 $defaults[$key] = $value; 339 } 340 } 341 } 342 343 // Merge our args with the defaults. 344 $args = wp_parse_args($args, $defaults); 345 346 // Set the class properties using the parsed args. 347 foreach ($args as $key => $value) { 348 $this->$key = $value; 349 } 350 351 // An array of whitelisted properties that don't need to be sanitized here. 352 // Format: $key => $default_value. 353 $whitelisted = apply_filters('kirki/' . $this->kirki_config . '/fields/properties_whitelist', array( 354 'group' => '', // This is sanitized later in the controls themselves. 355 'in_row_with' => array(), // This is sanitized later in the controls themselves. 356 'always_active' => false, // This is sanitized later in the controls themselves. 357 'label' => '', // This is sanitized later in the controls themselves. 358 'description' => '', // This is sanitized later in the controls themselves. 359 'mode' => '', // Only used for backwards-compatibility reasons. 360 'fields' => array(), // Used in repeater fields. 361 'row_label' => array(), // Used in repeater fields. 362 )); 363 364 if (isset($args['active_callback'])) { 365 $whitelisted["active_callback_vars"] = $args['active_callback']; 366 367 } 368 369 if (isset($args['update'])) { 370 $whitelisted["update"] = $args['update']; 371 } 372 373 $this->set_field($whitelisted); 374 375 } 376 377 /** 378 * Processes the field arguments 379 * 380 * @param array $whitelisted_properties Defines an array of arguments that will skip validation at this point. 381 */ 382 protected function set_field($whitelisted_properties = array()) 383 { 384 385 $properties = get_class_vars(__CLASS__); 386 // Remove any whitelisted properties from above. 387 // These will get a free pass, completely unfiltered. 388 foreach ($whitelisted_properties as $key => $default_value) { 389 if (isset($properties[$key])) { 390 unset($properties[$key]); 391 } 392 } 393 394 // Some things must run before the others. 395 $priorities = array( 396 'option_name', 397 'option_type', 398 'settings', 399 ); 400 401 foreach ($priorities as $priority) { 402 if (method_exists($this, 'set_' . $priority)) { 403 $method_name = 'set_' . $priority; 404 $this->$method_name(); 405 } 406 } 407 408 // Sanitize the properties, skipping the ones run from the $priorities. 409 foreach ($properties as $property => $value) { 410 if (in_array($property, $priorities, true)) { 411 continue; 412 } 413 if (method_exists($this, 'set_' . $property)) { 414 $method_name = 'set_' . $property; 415 $this->$method_name(); 416 } 417 } 418 419 // Get all arguments with their values. 420 $args = get_class_vars(__CLASS__); 421 foreach ($args as $key => $default_value) { 422 $args[$key] = $this->$key; 423 } 424 425 // Add the whitelisted properties through the back door. 426 foreach ($whitelisted_properties as $key => $default_value) { 427 if ( ! isset($this->$key)) { 428 $this->$key = $default_value; 429 } 430 $args[$key] = $this->$key; 431 } 432 433 // Add the field to the static $fields variable properly indexed. 434 435 436 Kirki::$fields[$this->settings] = $args; 437 438 if ('background' === $this->type) { 439 // Build the background fields. 440 Kirki::$fields = Kirki_Explode_Background_Field::process_fields(Kirki::$fields); 441 } 442 443 } 444 445 /** 446 * This allows us to process this on a field-basis 447 * by using sub-classes which can override this method. 448 * 449 * @access protected 450 */ 451 protected function set_default() 452 { 453 } 454 455 /** 456 * Escape $kirki_config. 457 * 458 * @access protected 459 */ 460 protected function set_kirki_config() 461 { 462 463 $this->kirki_config = esc_attr($this->kirki_config); 464 465 } 466 467 /** 468 * Escape $option_name. 469 * 470 * @access protected 471 */ 472 protected function set_option_name() 473 { 474 475 $this->option_name = esc_attr($this->option_name); 476 477 } 478 479 /** 480 * Escape the $section. 481 * 482 * @access protected 483 */ 484 protected function set_section() 485 { 486 487 $this->section = sanitize_key($this->section); 488 489 } 490 491 /** 492 * Escape the $section. 493 * 494 * @access protected 495 */ 496 protected function set_input_attrs() 497 { 498 499 if ( ! is_array($this->input_attrs)) { 500 $this->input_attrs = array(); 501 } 502 503 } 504 505 /** 506 * Checks the capability chosen is valid. 507 * If not, then falls back to 'edit_theme_options' 508 * 509 * @access protected 510 */ 511 protected function set_capability() 512 { 513 // Early exit if we're using 'edit_theme_options'. 514 if ('edit_theme_options' === $this->capability) { 515 return; 516 } 517 // Escape & trim the capability. 518 $this->capability = trim(esc_attr($this->capability)); 519 } 520 521 /** 522 * Make sure we're using the correct option_type 523 * 524 * @access protected 525 */ 526 protected function set_option_type() 527 { 528 529 // Take care of common typos. 530 if ('options' === $this->option_type) { 531 $this->option_type = 'option'; 532 } 533 // Take care of common typos. 534 if ('theme_mods' === $this->option_type) { 535 $this->option_type = 'theme_mod'; 536 } 537 } 538 539 /** 540 * Modifications for partial refreshes. 541 * 542 * @access protected 543 */ 544 protected function set_partial_refresh() 545 { 546 547 if ( ! apply_filters('materialis_enable_kirki_selective_refresh', true)) { 548 return; 549 } 550 551 if ( ! is_array($this->partial_refresh)) { 552 $this->partial_refresh = array(); 553 } 554 foreach ($this->partial_refresh as $id => $args) { 555 if ( ! is_array($args) || ! isset($args['selector']) || ! isset($args['render_callback']) || ! is_callable($args['render_callback'])) { 556 unset($this->partial_refresh[$id]); 557 continue; 558 } 559 } 560 if ( ! empty($this->partial_refresh)) { 561 $this->transport = 'postMessage'; 562 } 563 } 564 565 /** 566 * Sets the settings. 567 * If we're using serialized options it makes sure that settings are properly formatted. 568 * We'll also be escaping all setting names here for consistency. 569 * 570 * @access protected 571 */ 572 protected function set_settings() 573 { 574 575 // If settings is not an array, temporarily convert it to an array. 576 // This is just to allow us to process everything the same way and avoid code duplication. 577 // if settings is not an array then it will not be set as an array in the end. 578 if ( ! is_array($this->settings)) { 579 $this->settings = array('kirki_placeholder_setting' => $this->settings); 580 } 581 $settings = array(); 582 foreach ($this->settings as $setting_key => $setting_value) { 583 $settings[sanitize_key($setting_key)] = esc_attr($setting_value); 584 // If we're using serialized options then we need to spice this up. 585 if ('option' === $this->option_type && '' !== $this->option_name && (false === strpos($setting_key, '['))) { 586 $settings[sanitize_key($setting_key)] = esc_attr($this->option_name) . '[' . esc_attr($setting_value) . ']'; 587 } 588 } 589 $this->settings = $settings; 590 if (isset($this->settings['kirki_placeholder_setting'])) { 591 $this->settings = $this->settings['kirki_placeholder_setting']; 592 } 593 594 } 595 596 /** 597 * Escapes the tooltip messages. 598 * 599 * @access protected 600 */ 601 protected function set_tooltip() 602 { 603 604 if ('' !== $this->tooltip) { 605 $this->tooltip = wp_strip_all_tags($this->tooltip); 606 607 return; 608 } 609 610 } 611 612 /** 613 * Sets the active_callback 614 * If we're using the $required argument, 615 * Then this is where the switch is made to our evaluation method. 616 * 617 * @access protected 618 */ 619 protected function set_active_callback() 620 { 621 622 if (is_array($this->active_callback) && ! is_callable($this->active_callback)) { 623 if (isset($this->active_callback[0])) { 624 $this->required = $this->active_callback; 625 } 626 } 627 628 if ( ! empty($this->required)) { 629 $this->active_callback = array('Kirki_Active_Callback', 'evaluate'); 630 631 return; 632 } 633 // No need to proceed any further if we're using the default value. 634 if ('__return_true' === $this->active_callback) { 635 return; 636 } 637 // Make sure the function is callable, otherwise fallback to __return_true. 638 if ( ! is_callable($this->active_callback)) { 639 $this->active_callback = '__return_true'; 640 } 641 642 } 643 644 /** 645 * Sets the control type. 646 * 647 * @access protected 648 */ 649 protected function set_type() 650 { 651 652 // Escape the control type (it doesn't hurt to be sure). 653 $this->type = esc_attr($this->type); 654 655 } 656 657 /** 658 * Sets the $id. 659 * Setting the ID should happen after the 'settings' sanitization. 660 * This way we can also properly handle cases where the option_type is set to 'option' 661 * and we're using an array instead of individual options. 662 * 663 * @access protected 664 */ 665 protected function set_id() 666 { 667 668 $this->id = sanitize_key(str_replace('[', '-', str_replace(']', '', $this->settings))); 669 670 } 671 672 /** 673 * Sets the $sanitize_callback 674 * 675 * @access protected 676 */ 677 protected function set_sanitize_callback() 678 { 679 680 // If a custom sanitize_callback has been defined, 681 // then we don't need to proceed any further. 682 if ( ! empty($this->sanitize_callback)) { 683 return; 684 } 685 686 $default_callbacks = array( 687 'kirki-multicheck' => array('Kirki_Sanitize_Values', 'multicheck'), 688 'kirki-sortable' => array('Kirki_Sanitize_Values', 'sortable'), 689 'kirki-typography' => array('Kirki_Sanitize_Values', 'typography'), 690 ); 691 692 if (array_key_exists($this->type, $default_callbacks)) { 693 $this->sanitize_callback = $default_callbacks[$this->type]; 694 } 695 696 } 697 698 /** 699 * Sets the $choices. 700 * 701 * @access protected 702 */ 703 protected function set_choices() 704 { 705 706 if ( ! is_array($this->choices)) { 707 $this->choices = array(); 708 } 709 710 } 711 712 /** 713 * Escapes the $disable_output. 714 * 715 * @access protected 716 */ 717 protected function set_disable_output() 718 { 719 720 $this->disable_output = (bool)$this->disable_output; 721 722 } 723 724 /** 725 * Sets the $sanitize_callback 726 * 727 * @access protected 728 */ 729 protected function set_output() 730 { 731 732 if (empty($this->output)) { 733 return; 734 } 735 if ( ! empty($this->output) && ! is_array($this->output)) { 736 $this->output = array(array('element' => $this->output)); 737 } 738 // Convert to array of arrays if needed. 739 if (isset($this->output['element'])) { 740 $this->output = array($this->output); 741 } 742 $outputs = array(); 743 foreach ($this->output as $output) { 744 if ( ! isset($output['element'])) { 745 continue; 746 } 747 if ( ! isset($output['property']) && ! in_array($this->type, array('kirki-typography', 'background'), true)) { 748 continue; 749 } 750 if ( ! isset($output['sanitize_callback']) && isset($output['callback'])) { 751 $output['sanitize_callback'] = $output['callback']; 752 } 753 // Convert element arrays to strings. 754 if (is_array($output['element'])) { 755 $output['element'] = array_unique($output['element']); 756 sort($output['element']); 757 $output['element'] = implode(',', $output['element']); 758 } 759 $outputs[] = array( 760 'element' => $output['element'], 761 'property' => (isset($output['property'])) ? $output['property'] : '', 762 'media_query' => (isset($output['media_query'])) ? $output['media_query'] : 'global', 763 'sanitize_callback' => (isset($output['sanitize_callback'])) ? $output['sanitize_callback'] : '', 764 'units' => (isset($output['units'])) ? $output['units'] : '', 765 'prefix' => (isset($output['prefix'])) ? $output['prefix'] : '', 766 'suffix' => (isset($output['suffix'])) ? $output['suffix'] : '', 767 'exclude' => (isset($output['exclude'])) ? $output['exclude'] : false, 768 ); 769 } 770 771 } 772 773 /** 774 * Sets the $js_vars 775 * 776 * @access protected 777 */ 778 protected function set_js_vars() 779 { 780 781 if ( ! is_array($this->js_vars)) { 782 $this->js_vars = array(); 783 } 784 785 // Check if transport is set to auto. 786 // If not, then skip the auto-calculations and exit early. 787 if ('auto' !== $this->transport) { 788 return; 789 } 790 791 // Set transport to refresh initially. 792 // Serves as a fallback in case we failt to auto-calculate js_vars. 793 $this->transport = 'refresh'; 794 795 $js_vars = array(); 796 797 // Try to auto-generate js_vars. 798 // First we need to check if js_vars are empty, and that output is not empty. 799 if (empty($this->js_vars) && ! empty($this->output)) { 800 801 // Start going through each item in the $output array. 802 foreach ($this->output as $output) { 803 $output['function'] = 'css'; 804 805 // If 'element' or 'property' are not defined, skip this. 806 if ( ! isset($output['element']) || ! isset($output['property'])) { 807 continue; 808 } 809 if (is_array($output['element'])) { 810 $output['element'] = implode(',', $output['element']); 811 } 812 if (false !== strpos($output['element'], ':')) { 813 $output['function'] = 'style'; 814 } 815 816 // If there's a sanitize_callback defined, skip this. 817 if (isset($output['sanitize_callback']) && ! empty($output['sanitize_callback'])) { 818 continue; 819 } 820 821 // If we got this far, it's safe to add this. 822 $js_vars[] = $output; 823 } 824 825 // Did we manage to get all the items from 'output'? 826 // If not, then we're missing something so don't add this. 827 if (count($js_vars) !== count($this->output)) { 828 return; 829 } 830 $this->js_vars = $js_vars; 831 $this->transport = 'postMessage'; 832 833 } 834 835 } 836 837 /** 838 * Sets the $variables 839 * 840 * @access protected 841 */ 842 protected function set_variables() 843 { 844 $variable = ''; 845 if ( ! is_array($this->variables)) { 846 $variable = (is_string($this->variables) && ! empty($this->variables)) ? $this->variables : false; 847 $this->variables = array(); 848 if ($variable && empty($this->variables)) { 849 $this->variables[0]['name'] = $variable; 850 } 851 } 852 } 853 854 /** 855 * This is a fallback method: 856 * $help has now become $tooltip, so this just migrates the data 857 * 858 * @access protected 859 */ 860 protected function set_help() 861 { 862 863 if ('' !== $this->tooltip) { 864 return; 865 } 866 if ('' !== $this->help) { 867 $this->tooltip = wp_strip_all_tags($this->help); 868 // $help has been deprecated 869 $this->help = ''; 870 871 return; 872 } 873 874 } 875 876 /** 877 * Sets the $transport 878 * 879 * @access protected 880 */ 881 protected function set_transport() 882 { 883 884 if ('postmessage' === trim(strtolower($this->transport))) { 885 $this->transport = 'postMessage'; 886 } 887 888 } 889 890 /** 891 * Sets the $required 892 * 893 * @access protected 894 */ 895 protected function set_required() 896 { 897 898 if ( ! is_array($this->required)) { 899 $this->required = array(); 900 } 901 902 } 903 904 /** 905 * Sets the $multiple 906 * 907 * @access protected 908 */ 909 protected function set_multiple() 910 { 911 912 $this->multiple = absint($this->multiple); 913 914 } 915 916 /** 917 * Sets the $priority 918 * 919 * @access protected 920 */ 921 protected function set_priority() 922 { 923 924 $this->priority = absint($this->priority); 925 926 } 927 } 928 }