You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

704 lines
22 KiB

  1. <?php
  2. /**
  3. * WordPress Customize Control classes
  4. *
  5. * @package WordPress
  6. * @subpackage Customize
  7. * @since 3.4.0
  8. */
  9. /**
  10. * Customize Control class.
  11. *
  12. * @since 3.4.0
  13. */
  14. class WP_Customize_Control {
  15. /**
  16. * Incremented with each new class instantiation, then stored in $instance_number.
  17. *
  18. * Used when sorting two instances whose priorities are equal.
  19. *
  20. * @since 4.1.0
  21. *
  22. * @static
  23. * @access protected
  24. * @var int
  25. */
  26. protected static $instance_count = 0;
  27. /**
  28. * Order in which this instance was created in relation to other instances.
  29. *
  30. * @since 4.1.0
  31. * @access public
  32. * @var int
  33. */
  34. public $instance_number;
  35. /**
  36. * @access public
  37. * @var WP_Customize_Manager
  38. */
  39. public $manager;
  40. /**
  41. * @access public
  42. * @var string
  43. */
  44. public $id;
  45. /**
  46. * All settings tied to the control.
  47. *
  48. * @access public
  49. * @var array
  50. */
  51. public $settings;
  52. /**
  53. * The primary setting for the control (if there is one).
  54. *
  55. * @access public
  56. * @var string
  57. */
  58. public $setting = 'default';
  59. /**
  60. * Capability required to use this control.
  61. *
  62. * Normally this is empty and the capability is derived from the capabilities
  63. * of the associated `$settings`.
  64. *
  65. * @since 4.5.0
  66. * @access public
  67. * @var string
  68. */
  69. public $capability;
  70. /**
  71. * @access public
  72. * @var int
  73. */
  74. public $priority = 10;
  75. /**
  76. * @access public
  77. * @var string
  78. */
  79. public $section = '';
  80. /**
  81. * @access public
  82. * @var string
  83. */
  84. public $label = '';
  85. /**
  86. * @access public
  87. * @var string
  88. */
  89. public $description = '';
  90. /**
  91. * @todo: Remove choices
  92. *
  93. * @access public
  94. * @var array
  95. */
  96. public $choices = array();
  97. /**
  98. * @access public
  99. * @var array
  100. */
  101. public $input_attrs = array();
  102. /**
  103. * Show UI for adding new content, currently only used for the dropdown-pages control.
  104. *
  105. * @since 4.7.0
  106. * @access public
  107. * @var bool
  108. */
  109. public $allow_addition = false;
  110. /**
  111. * @deprecated It is better to just call the json() method
  112. * @access public
  113. * @var array
  114. */
  115. public $json = array();
  116. /**
  117. * @access public
  118. * @var string
  119. */
  120. public $type = 'text';
  121. /**
  122. * Callback.
  123. *
  124. * @since 4.0.0
  125. * @access public
  126. *
  127. * @see WP_Customize_Control::active()
  128. *
  129. * @var callable Callback is called with one argument, the instance of
  130. * WP_Customize_Control, and returns bool to indicate whether
  131. * the control is active (such as it relates to the URL
  132. * currently being previewed).
  133. */
  134. public $active_callback = '';
  135. /**
  136. * Constructor.
  137. *
  138. * Supplied `$args` override class property defaults.
  139. *
  140. * If `$args['settings']` is not defined, use the $id as the setting ID.
  141. *
  142. * @since 3.4.0
  143. *
  144. * @param WP_Customize_Manager $manager Customizer bootstrap instance.
  145. * @param string $id Control ID.
  146. * @param array $args {
  147. * Optional. Arguments to override class property defaults.
  148. *
  149. * @type int $instance_number Order in which this instance was created in relation
  150. * to other instances.
  151. * @type WP_Customize_Manager $manager Customizer bootstrap instance.
  152. * @type string $id Control ID.
  153. * @type array $settings All settings tied to the control. If undefined, `$id` will
  154. * be used.
  155. * @type string $setting The primary setting for the control (if there is one).
  156. * Default 'default'.
  157. * @type int $priority Order priority to load the control. Default 10.
  158. * @type string $section Section the control belongs to. Default empty.
  159. * @type string $label Label for the control. Default empty.
  160. * @type string $description Description for the control. Default empty.
  161. * @type array $choices List of choices for 'radio' or 'select' type controls, where
  162. * values are the keys, and labels are the values.
  163. * Default empty array.
  164. * @type array $input_attrs List of custom input attributes for control output, where
  165. * attribute names are the keys and values are the values. Not
  166. * used for 'checkbox', 'radio', 'select', 'textarea', or
  167. * 'dropdown-pages' control types. Default empty array.
  168. * @type array $json Deprecated. Use WP_Customize_Control::json() instead.
  169. * @type string $type Control type. Core controls include 'text', 'checkbox',
  170. * 'textarea', 'radio', 'select', and 'dropdown-pages'. Additional
  171. * input types such as 'email', 'url', 'number', 'hidden', and
  172. * 'date' are supported implicitly. Default 'text'.
  173. * }
  174. */
  175. public function __construct( $manager, $id, $args = array() ) {
  176. $keys = array_keys( get_object_vars( $this ) );
  177. foreach ( $keys as $key ) {
  178. if ( isset( $args[ $key ] ) ) {
  179. $this->$key = $args[ $key ];
  180. }
  181. }
  182. $this->manager = $manager;
  183. $this->id = $id;
  184. if ( empty( $this->active_callback ) ) {
  185. $this->active_callback = array( $this, 'active_callback' );
  186. }
  187. self::$instance_count += 1;
  188. $this->instance_number = self::$instance_count;
  189. // Process settings.
  190. if ( ! isset( $this->settings ) ) {
  191. $this->settings = $id;
  192. }
  193. $settings = array();
  194. if ( is_array( $this->settings ) ) {
  195. foreach ( $this->settings as $key => $setting ) {
  196. $settings[ $key ] = $this->manager->get_setting( $setting );
  197. }
  198. } else if ( is_string( $this->settings ) ) {
  199. $this->setting = $this->manager->get_setting( $this->settings );
  200. $settings['default'] = $this->setting;
  201. }
  202. $this->settings = $settings;
  203. }
  204. /**
  205. * Enqueue control related scripts/styles.
  206. *
  207. * @since 3.4.0
  208. */
  209. public function enqueue() {}
  210. /**
  211. * Check whether control is active to current Customizer preview.
  212. *
  213. * @since 4.0.0
  214. * @access public
  215. *
  216. * @return bool Whether the control is active to the current preview.
  217. */
  218. final public function active() {
  219. $control = $this;
  220. $active = call_user_func( $this->active_callback, $this );
  221. /**
  222. * Filters response of WP_Customize_Control::active().
  223. *
  224. * @since 4.0.0
  225. *
  226. * @param bool $active Whether the Customizer control is active.
  227. * @param WP_Customize_Control $control WP_Customize_Control instance.
  228. */
  229. $active = apply_filters( 'customize_control_active', $active, $control );
  230. return $active;
  231. }
  232. /**
  233. * Default callback used when invoking WP_Customize_Control::active().
  234. *
  235. * Subclasses can override this with their specific logic, or they may
  236. * provide an 'active_callback' argument to the constructor.
  237. *
  238. * @since 4.0.0
  239. * @access public
  240. *
  241. * @return true Always true.
  242. */
  243. public function active_callback() {
  244. return true;
  245. }
  246. /**
  247. * Fetch a setting's value.
  248. * Grabs the main setting by default.
  249. *
  250. * @since 3.4.0
  251. *
  252. * @param string $setting_key
  253. * @return mixed The requested setting's value, if the setting exists.
  254. */
  255. final public function value( $setting_key = 'default' ) {
  256. if ( isset( $this->settings[ $setting_key ] ) ) {
  257. return $this->settings[ $setting_key ]->value();
  258. }
  259. }
  260. /**
  261. * Refresh the parameters passed to the JavaScript via JSON.
  262. *
  263. * @since 3.4.0
  264. */
  265. public function to_json() {
  266. $this->json['settings'] = array();
  267. foreach ( $this->settings as $key => $setting ) {
  268. $this->json['settings'][ $key ] = $setting->id;
  269. }
  270. $this->json['type'] = $this->type;
  271. $this->json['priority'] = $this->priority;
  272. $this->json['active'] = $this->active();
  273. $this->json['section'] = $this->section;
  274. $this->json['content'] = $this->get_content();
  275. $this->json['label'] = $this->label;
  276. $this->json['description'] = $this->description;
  277. $this->json['instanceNumber'] = $this->instance_number;
  278. if ( 'dropdown-pages' === $this->type ) {
  279. $this->json['allow_addition'] = $this->allow_addition;
  280. }
  281. }
  282. /**
  283. * Get the data to export to the client via JSON.
  284. *
  285. * @since 4.1.0
  286. *
  287. * @return array Array of parameters passed to the JavaScript.
  288. */
  289. public function json() {
  290. $this->to_json();
  291. return $this->json;
  292. }
  293. /**
  294. * Checks if the user can use this control.
  295. *
  296. * Returns false if the user cannot manipulate one of the associated settings,
  297. * or if one of the associated settings does not exist. Also returns false if
  298. * the associated section does not exist or if its capability check returns
  299. * false.
  300. *
  301. * @since 3.4.0
  302. *
  303. * @return bool False if theme doesn't support the control or user doesn't have the required permissions, otherwise true.
  304. */
  305. final public function check_capabilities() {
  306. if ( ! empty( $this->capability ) && ! current_user_can( $this->capability ) ) {
  307. return false;
  308. }
  309. foreach ( $this->settings as $setting ) {
  310. if ( ! $setting || ! $setting->check_capabilities() ) {
  311. return false;
  312. }
  313. }
  314. $section = $this->manager->get_section( $this->section );
  315. if ( isset( $section ) && ! $section->check_capabilities() ) {
  316. return false;
  317. }
  318. return true;
  319. }
  320. /**
  321. * Get the control's content for insertion into the Customizer pane.
  322. *
  323. * @since 4.1.0
  324. *
  325. * @return string Contents of the control.
  326. */
  327. final public function get_content() {
  328. ob_start();
  329. $this->maybe_render();
  330. return trim( ob_get_clean() );
  331. }
  332. /**
  333. * Check capabilities and render the control.
  334. *
  335. * @since 3.4.0
  336. * @uses WP_Customize_Control::render()
  337. */
  338. final public function maybe_render() {
  339. if ( ! $this->check_capabilities() )
  340. return;
  341. /**
  342. * Fires just before the current Customizer control is rendered.
  343. *
  344. * @since 3.4.0
  345. *
  346. * @param WP_Customize_Control $this WP_Customize_Control instance.
  347. */
  348. do_action( 'customize_render_control', $this );
  349. /**
  350. * Fires just before a specific Customizer control is rendered.
  351. *
  352. * The dynamic portion of the hook name, `$this->id`, refers to
  353. * the control ID.
  354. *
  355. * @since 3.4.0
  356. *
  357. * @param WP_Customize_Control $this WP_Customize_Control instance.
  358. */
  359. do_action( "customize_render_control_{$this->id}", $this );
  360. $this->render();
  361. }
  362. /**
  363. * Renders the control wrapper and calls $this->render_content() for the internals.
  364. *
  365. * @since 3.4.0
  366. */
  367. protected function render() {
  368. $id = 'customize-control-' . str_replace( array( '[', ']' ), array( '-', '' ), $this->id );
  369. $class = 'customize-control customize-control-' . $this->type;
  370. ?><li id="<?php echo esc_attr( $id ); ?>" class="<?php echo esc_attr( $class ); ?>">
  371. <?php $this->render_content(); ?>
  372. </li><?php
  373. }
  374. /**
  375. * Get the data link attribute for a setting.
  376. *
  377. * @since 3.4.0
  378. *
  379. * @param string $setting_key
  380. * @return string Data link parameter, if $setting_key is a valid setting, empty string otherwise.
  381. */
  382. public function get_link( $setting_key = 'default' ) {
  383. if ( ! isset( $this->settings[ $setting_key ] ) )
  384. return '';
  385. return 'data-customize-setting-link="' . esc_attr( $this->settings[ $setting_key ]->id ) . '"';
  386. }
  387. /**
  388. * Render the data link attribute for the control's input element.
  389. *
  390. * @since 3.4.0
  391. * @uses WP_Customize_Control::get_link()
  392. *
  393. * @param string $setting_key
  394. */
  395. public function link( $setting_key = 'default' ) {
  396. echo $this->get_link( $setting_key );
  397. }
  398. /**
  399. * Render the custom attributes for the control's input element.
  400. *
  401. * @since 4.0.0
  402. * @access public
  403. */
  404. public function input_attrs() {
  405. foreach ( $this->input_attrs as $attr => $value ) {
  406. echo $attr . '="' . esc_attr( $value ) . '" ';
  407. }
  408. }
  409. /**
  410. * Render the control's content.
  411. *
  412. * Allows the content to be overridden without having to rewrite the wrapper in `$this::render()`.
  413. *
  414. * Supports basic input types `text`, `checkbox`, `textarea`, `radio`, `select` and `dropdown-pages`.
  415. * Additional input types such as `email`, `url`, `number`, `hidden` and `date` are supported implicitly.
  416. *
  417. * Control content can alternately be rendered in JS. See WP_Customize_Control::print_template().
  418. *
  419. * @since 3.4.0
  420. */
  421. protected function render_content() {
  422. switch( $this->type ) {
  423. case 'checkbox':
  424. ?>
  425. <label>
  426. <input type="checkbox" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); checked( $this->value() ); ?> />
  427. <?php echo esc_html( $this->label ); ?>
  428. <?php if ( ! empty( $this->description ) ) : ?>
  429. <span class="description customize-control-description"><?php echo $this->description; ?></span>
  430. <?php endif; ?>
  431. </label>
  432. <?php
  433. break;
  434. case 'radio':
  435. if ( empty( $this->choices ) )
  436. return;
  437. $name = '_customize-radio-' . $this->id;
  438. if ( ! empty( $this->label ) ) : ?>
  439. <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
  440. <?php endif;
  441. if ( ! empty( $this->description ) ) : ?>
  442. <span class="description customize-control-description"><?php echo $this->description ; ?></span>
  443. <?php endif;
  444. foreach ( $this->choices as $value => $label ) :
  445. ?>
  446. <label>
  447. <input type="radio" value="<?php echo esc_attr( $value ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php $this->link(); checked( $this->value(), $value ); ?> />
  448. <?php echo esc_html( $label ); ?><br/>
  449. </label>
  450. <?php
  451. endforeach;
  452. break;
  453. case 'select':
  454. if ( empty( $this->choices ) )
  455. return;
  456. ?>
  457. <label>
  458. <?php if ( ! empty( $this->label ) ) : ?>
  459. <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
  460. <?php endif;
  461. if ( ! empty( $this->description ) ) : ?>
  462. <span class="description customize-control-description"><?php echo $this->description; ?></span>
  463. <?php endif; ?>
  464. <select <?php $this->link(); ?>>
  465. <?php
  466. foreach ( $this->choices as $value => $label )
  467. echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
  468. ?>
  469. </select>
  470. </label>
  471. <?php
  472. break;
  473. case 'textarea':
  474. ?>
  475. <label>
  476. <?php if ( ! empty( $this->label ) ) : ?>
  477. <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
  478. <?php endif;
  479. if ( ! empty( $this->description ) ) : ?>
  480. <span class="description customize-control-description"><?php echo $this->description; ?></span>
  481. <?php endif; ?>
  482. <textarea rows="5" <?php $this->input_attrs(); ?> <?php $this->link(); ?>><?php echo esc_textarea( $this->value() ); ?></textarea>
  483. </label>
  484. <?php
  485. break;
  486. case 'dropdown-pages':
  487. ?>
  488. <label>
  489. <?php if ( ! empty( $this->label ) ) : ?>
  490. <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
  491. <?php endif;
  492. if ( ! empty( $this->description ) ) : ?>
  493. <span class="description customize-control-description"><?php echo $this->description; ?></span>
  494. <?php endif; ?>
  495. <?php
  496. $dropdown_name = '_customize-dropdown-pages-' . $this->id;
  497. $show_option_none = __( '&mdash; Select &mdash;' );
  498. $option_none_value = '0';
  499. $dropdown = wp_dropdown_pages(
  500. array(
  501. 'name' => $dropdown_name,
  502. 'echo' => 0,
  503. 'show_option_none' => $show_option_none,
  504. 'option_none_value' => $option_none_value,
  505. 'selected' => $this->value(),
  506. )
  507. );
  508. if ( empty( $dropdown ) ) {
  509. $dropdown = sprintf( '<select id="%1$s" name="%1$s">', esc_attr( $dropdown_name ) );
  510. $dropdown .= sprintf( '<option value="%1$s">%2$s</option>', esc_attr( $option_none_value ), esc_html( $show_option_none ) );
  511. $dropdown .= '</select>';
  512. }
  513. // Hackily add in the data link parameter.
  514. $dropdown = str_replace( '<select', '<select ' . $this->get_link(), $dropdown );
  515. // Even more hacikly add auto-draft page stubs.
  516. // @todo Eventually this should be removed in favor of the pages being injected into the underlying get_pages() call. See <https://github.com/xwp/wp-customize-posts/pull/250>.
  517. $nav_menus_created_posts_setting = $this->manager->get_setting( 'nav_menus_created_posts' );
  518. if ( $nav_menus_created_posts_setting && current_user_can( 'publish_pages' ) ) {
  519. $auto_draft_page_options = '';
  520. foreach ( $nav_menus_created_posts_setting->value() as $auto_draft_page_id ) {
  521. $post = get_post( $auto_draft_page_id );
  522. if ( $post && 'page' === $post->post_type ) {
  523. $auto_draft_page_options .= sprintf( '<option value="%1$s">%2$s</option>', esc_attr( $post->ID ), esc_html( $post->post_title ) );
  524. }
  525. }
  526. if ( $auto_draft_page_options ) {
  527. $dropdown = str_replace( '</select>', $auto_draft_page_options . '</select>', $dropdown );
  528. }
  529. }
  530. echo $dropdown;
  531. ?>
  532. </label>
  533. <?php if ( $this->allow_addition && current_user_can( 'publish_pages' ) && current_user_can( 'edit_theme_options' ) ) : // Currently tied to menus functionality. ?>
  534. <button type="button" class="button-link add-new-toggle"><?php
  535. /* translators: %s: add new page label */
  536. printf( __( '+ %s' ), get_post_type_object( 'page' )->labels->add_new_item );
  537. ?></button>
  538. <div class="new-content-item">
  539. <label for="create-input-<?php echo $this->id; ?>"><span class="screen-reader-text"><?php _e( 'New page title' ); ?></span></label>
  540. <input type="text" id="create-input-<?php echo $this->id; ?>" class="create-item-input" placeholder="<?php esc_attr_e( 'New page title&hellip;' ); ?>">
  541. <button type="button" class="button add-content"><?php _e( 'Add' ); ?></button>
  542. </div>
  543. <?php endif;
  544. break;
  545. default:
  546. ?>
  547. <label>
  548. <?php if ( ! empty( $this->label ) ) : ?>
  549. <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
  550. <?php endif;
  551. if ( ! empty( $this->description ) ) : ?>
  552. <span class="description customize-control-description"><?php echo $this->description; ?></span>
  553. <?php endif; ?>
  554. <input type="<?php echo esc_attr( $this->type ); ?>" <?php $this->input_attrs(); ?> value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); ?> />
  555. </label>
  556. <?php
  557. break;
  558. }
  559. }
  560. /**
  561. * Render the control's JS template.
  562. *
  563. * This function is only run for control types that have been registered with
  564. * WP_Customize_Manager::register_control_type().
  565. *
  566. * In the future, this will also print the template for the control's container
  567. * element and be override-able.
  568. *
  569. * @since 4.1.0
  570. */
  571. final public function print_template() {
  572. ?>
  573. <script type="text/html" id="tmpl-customize-control-<?php echo $this->type; ?>-content">
  574. <?php $this->content_template(); ?>
  575. </script>
  576. <?php
  577. }
  578. /**
  579. * An Underscore (JS) template for this control's content (but not its container).
  580. *
  581. * Class variables for this control class are available in the `data` JS object;
  582. * export custom variables by overriding WP_Customize_Control::to_json().
  583. *
  584. * @see WP_Customize_Control::print_template()
  585. *
  586. * @since 4.1.0
  587. */
  588. protected function content_template() {}
  589. }
  590. /** WP_Customize_Color_Control class */
  591. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php' );
  592. /** WP_Customize_Media_Control class */
  593. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php' );
  594. /** WP_Customize_Upload_Control class */
  595. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' );
  596. /** WP_Customize_Image_Control class */
  597. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' );
  598. /** WP_Customize_Background_Image_Control class */
  599. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
  600. /** WP_Customize_Background_Position_Control class */
  601. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php' );
  602. /** WP_Customize_Cropped_Image_Control class */
  603. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
  604. /** WP_Customize_Site_Icon_Control class */
  605. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
  606. /** WP_Customize_Header_Image_Control class */
  607. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
  608. /** WP_Customize_Theme_Control class */
  609. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
  610. /** WP_Widget_Area_Customize_Control class */
  611. require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
  612. /** WP_Widget_Form_Customize_Control class */
  613. require_once( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
  614. /** WP_Customize_Nav_Menu_Control class */
  615. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php' );
  616. /** WP_Customize_Nav_Menu_Item_Control class */
  617. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
  618. /** WP_Customize_Nav_Menu_Location_Control class */
  619. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
  620. /** WP_Customize_Nav_Menu_Name_Control class */
  621. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
  622. /** WP_Customize_Nav_Menu_Auto_Add_Control class */
  623. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
  624. /** WP_Customize_New_Menu_Control class */
  625. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );