Customize: Allow controls to be registered without any associated settings.
* Improves parity between partials and controls. A partial or control can be settingless if instantiated with `settings` param as empty array (otherwise, if null, then the partial/control ID is used). * Eliminate need to create dummy settings that serve no purpose except to place a control in the UI. * Removes dummy settings for `create_new_menu` and `new_menu_name`. * Introduces `WP_Customize_Control::$capability` and `WP_Customize_Partial::$capability`, and if set checks them in the respective `check_capabilities()` methods. * Prevents PHP fatal error from happening when non-existing settings are provided to control: "Call to a member function `check_capabilities()` on a non-object". * Fixes issue where nav menu items and widgets were no longer working with selective refresh because cap check was failing. See #27355. Fixes #35926. git-svn-id: https://develop.svn.wordpress.org/trunk@36689 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
516756e370
commit
d80ef26b48
@ -1521,18 +1521,25 @@
|
||||
settings = $.map( control.params.settings, function( value ) {
|
||||
return value;
|
||||
});
|
||||
api.apply( api, settings.concat( function () {
|
||||
var key;
|
||||
|
||||
if ( 0 === settings.length ) {
|
||||
control.setting = null;
|
||||
control.settings = {};
|
||||
for ( key in control.params.settings ) {
|
||||
control.settings[ key ] = api( control.params.settings[ key ] );
|
||||
}
|
||||
|
||||
control.setting = control.settings['default'] || null;
|
||||
|
||||
control.embed();
|
||||
}) );
|
||||
} else {
|
||||
api.apply( api, settings.concat( function() {
|
||||
var key;
|
||||
|
||||
control.settings = {};
|
||||
for ( key in control.params.settings ) {
|
||||
control.settings[ key ] = api( control.params.settings[ key ] );
|
||||
}
|
||||
|
||||
control.setting = control.settings['default'] || null;
|
||||
|
||||
control.embed();
|
||||
}) );
|
||||
}
|
||||
|
||||
// After the control is embedded on the page, invoke the "ready" method.
|
||||
control.deferred.embedded.done( function () {
|
||||
|
@ -64,6 +64,18 @@ class WP_Customize_Control {
|
||||
*/
|
||||
public $setting = 'default';
|
||||
|
||||
/**
|
||||
* Capability required to use this control.
|
||||
*
|
||||
* Normally this is empty and the capability is derived from the capabilities
|
||||
* of the associated `$settings`.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $capability;
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @var int
|
||||
@ -187,7 +199,7 @@ class WP_Customize_Control {
|
||||
$this->instance_number = self::$instance_count;
|
||||
|
||||
// Process settings.
|
||||
if ( empty( $this->settings ) ) {
|
||||
if ( ! isset( $this->settings ) ) {
|
||||
$this->settings = $id;
|
||||
}
|
||||
|
||||
@ -196,7 +208,7 @@ class WP_Customize_Control {
|
||||
foreach ( $this->settings as $key => $setting ) {
|
||||
$settings[ $key ] = $this->manager->get_setting( $setting );
|
||||
}
|
||||
} else {
|
||||
} else if ( is_string( $this->settings ) ) {
|
||||
$this->setting = $this->manager->get_setting( $this->settings );
|
||||
$settings['default'] = $this->setting;
|
||||
}
|
||||
@ -299,21 +311,32 @@ class WP_Customize_Control {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the theme supports the control and check user capabilities.
|
||||
* Checks if the user can use this control.
|
||||
*
|
||||
* Returns false if the user cannot manipulate one of the associated settings,
|
||||
* or if one of the associated settings does not exist. Also returns false if
|
||||
* the associated section does not exist or if its capability check returns
|
||||
* false.
|
||||
*
|
||||
* @since 3.4.0
|
||||
*
|
||||
* @return bool False if theme doesn't support the control or user doesn't have the required permissions, otherwise true.
|
||||
*/
|
||||
final public function check_capabilities() {
|
||||
if ( ! empty( $this->capability ) && ! current_user_can( $this->capability ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $this->settings as $setting ) {
|
||||
if ( ! $setting->check_capabilities() )
|
||||
if ( ! $setting || ! $setting->check_capabilities() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$section = $this->manager->get_section( $this->section );
|
||||
if ( isset( $section ) && ! $section->check_capabilities() )
|
||||
if ( isset( $section ) && ! $section->check_capabilities() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -606,28 +606,20 @@ final class WP_Customize_Nav_Menus {
|
||||
'priority' => 999,
|
||||
) ) );
|
||||
|
||||
$this->manager->add_setting( 'new_menu_name', array(
|
||||
'type' => 'new_menu',
|
||||
'default' => '',
|
||||
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
|
||||
) );
|
||||
|
||||
$this->manager->add_control( 'new_menu_name', array(
|
||||
'label' => '',
|
||||
'section' => 'add_menu',
|
||||
'type' => 'text',
|
||||
'settings' => array(),
|
||||
'input_attrs' => array(
|
||||
'class' => 'menu-name-field',
|
||||
'placeholder' => __( 'New menu name' ),
|
||||
),
|
||||
) );
|
||||
|
||||
$this->manager->add_setting( 'create_new_menu', array(
|
||||
'type' => 'new_menu',
|
||||
) );
|
||||
|
||||
$this->manager->add_control( new WP_Customize_New_Menu_Control( $this->manager, 'create_new_menu', array(
|
||||
'section' => 'add_menu',
|
||||
'section' => 'add_menu',
|
||||
'settings' => array(),
|
||||
) ) );
|
||||
}
|
||||
|
||||
@ -851,6 +843,8 @@ final class WP_Customize_Nav_Menus {
|
||||
'type' => 'nav_menu_instance',
|
||||
'render_callback' => array( $this, 'render_nav_menu_partial' ),
|
||||
'container_inclusive' => true,
|
||||
'settings' => array(), // Empty because the nav menu instance may relate to a menu or a location.
|
||||
'capability' => 'edit_theme_options',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1485,16 +1485,18 @@ final class WP_Customize_Widgets {
|
||||
*/
|
||||
public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
|
||||
|
||||
if ( preg_match( '/^widget\[.+\]$/', $partial_id ) ) {
|
||||
if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) {
|
||||
if ( false === $partial_args ) {
|
||||
$partial_args = array();
|
||||
}
|
||||
$partial_args = array_merge(
|
||||
$partial_args,
|
||||
array(
|
||||
'type' => 'widget',
|
||||
'render_callback' => array( $this, 'render_widget_partial' ),
|
||||
'type' => 'widget',
|
||||
'render_callback' => array( $this, 'render_widget_partial' ),
|
||||
'container_inclusive' => true,
|
||||
'settings' => array( $this->get_setting_id( $matches['widget_id'] ) ),
|
||||
'capability' => 'edit_theme_options',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -89,6 +89,18 @@ class WP_Customize_Partial {
|
||||
*/
|
||||
public $primary_setting;
|
||||
|
||||
/**
|
||||
* Capability required to edit this partial.
|
||||
*
|
||||
* Normally this is empty and the capability is derived from the capabilities
|
||||
* of the associated `$settings`.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $capability;
|
||||
|
||||
/**
|
||||
* Render callback.
|
||||
*
|
||||
@ -157,7 +169,7 @@ class WP_Customize_Partial {
|
||||
}
|
||||
|
||||
// Process settings.
|
||||
if ( empty( $this->settings ) ) {
|
||||
if ( ! isset( $this->settings ) ) {
|
||||
$this->settings = array( $id );
|
||||
} else if ( is_string( $this->settings ) ) {
|
||||
$this->settings = array( $this->settings );
|
||||
@ -299,6 +311,9 @@ class WP_Customize_Partial {
|
||||
* or if one of the associated settings does not exist.
|
||||
*/
|
||||
final public function check_capabilities() {
|
||||
if ( ! empty( $this->capability ) && ! current_user_can( $this->capability ) ) {
|
||||
return false;
|
||||
}
|
||||
foreach ( $this->settings as $setting_id ) {
|
||||
$setting = $this->component->manager->get_setting( $setting_id );
|
||||
if ( ! $setting || ! $setting->check_capabilities() ) {
|
||||
|
88
tests/phpunit/tests/customize/control.php
Normal file
88
tests/phpunit/tests/customize/control.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Test_WP_Customize_Control tests.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the Test_WP_Customize_Control class.
|
||||
*
|
||||
* @todo This is missing dedicated tests for all but one of the methods.
|
||||
*
|
||||
* @group customize
|
||||
*/
|
||||
class Test_WP_Customize_Control extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Manager.
|
||||
*
|
||||
* @var WP_Customize_Manager
|
||||
*/
|
||||
public $wp_customize;
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
|
||||
// @codingStandardsIgnoreStart
|
||||
$GLOBALS['wp_customize'] = new WP_Customize_Manager();
|
||||
// @codingStandardsIgnoreEnd
|
||||
$this->wp_customize = $GLOBALS['wp_customize'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WP_Customize_Control::check_capabilities().
|
||||
*
|
||||
* @see WP_Customize_Control::check_capabilities()
|
||||
*/
|
||||
function test_check_capabilities() {
|
||||
wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
|
||||
do_action( 'customize_register', $this->wp_customize );
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'blogname', array(
|
||||
'settings' => array( 'blogname' ),
|
||||
) );
|
||||
$this->assertTrue( $control->check_capabilities() );
|
||||
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'blogname', array(
|
||||
'settings' => array( 'blogname', 'non_existing' ),
|
||||
) );
|
||||
$this->assertFalse( $control->check_capabilities() );
|
||||
|
||||
$this->wp_customize->add_setting( 'top_secret_message', array(
|
||||
'capability' => 'top_secret_clearance',
|
||||
) );
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'blogname', array(
|
||||
'settings' => array( 'blogname', 'top_secret_clearance' ),
|
||||
) );
|
||||
$this->assertFalse( $control->check_capabilities() );
|
||||
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
) );
|
||||
$this->assertTrue( $control->check_capabilities() );
|
||||
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
'capability' => 'top_secret_clearance',
|
||||
) );
|
||||
$this->assertFalse( $control->check_capabilities() );
|
||||
|
||||
$control = new WP_Customize_Control( $this->wp_customize, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
'capability' => 'edit_theme_options',
|
||||
) );
|
||||
$this->assertTrue( $control->check_capabilities() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down.
|
||||
*/
|
||||
function tearDown() {
|
||||
$this->wp_customize = null;
|
||||
unset( $GLOBALS['wp_customize'] );
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
@ -325,6 +325,23 @@ class Test_WP_Customize_Partial extends WP_UnitTestCase {
|
||||
'settings' => array( 'blogname', 'top_secret_clearance' ),
|
||||
) );
|
||||
$this->assertFalse( $partial->check_capabilities() );
|
||||
|
||||
$partial = new WP_Customize_Partial( $this->selective_refresh, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
) );
|
||||
$this->assertTrue( $partial->check_capabilities() );
|
||||
|
||||
$partial = new WP_Customize_Partial( $this->selective_refresh, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
'capability' => 'top_secret_clearance',
|
||||
) );
|
||||
$this->assertFalse( $partial->check_capabilities() );
|
||||
|
||||
$partial = new WP_Customize_Partial( $this->selective_refresh, 'no_setting', array(
|
||||
'settings' => array(),
|
||||
'capability' => 'edit_theme_options',
|
||||
) );
|
||||
$this->assertTrue( $partial->check_capabilities() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -394,11 +394,6 @@ window._wpCustomizeSettings.controls.new_menu_name = {
|
||||
'description': '',
|
||||
'instanceNumber': 46
|
||||
};
|
||||
window._wpCustomizeSettings.settings.new_menu_name = {
|
||||
'value': '',
|
||||
'transport': 'postMessage',
|
||||
'dirty': false
|
||||
};
|
||||
|
||||
// From nav-menu.js
|
||||
window.wpNavMenu = {
|
||||
|
@ -100,6 +100,20 @@ jQuery( window ).load( function (){
|
||||
equal( control.section(), 'fixture-section' );
|
||||
} );
|
||||
|
||||
module( 'Customizer control without associated settings' );
|
||||
test( 'Control can be created without settings', function() {
|
||||
var control = new wp.customize.Control( 'settingless', {
|
||||
params: {
|
||||
content: jQuery( '<li class="settingless">Hello World</li>' ),
|
||||
section: 'fixture-section'
|
||||
}
|
||||
} );
|
||||
wp.customize.control.add( control.id, control );
|
||||
equal( control.deferred.embedded.state(), 'resolved' );
|
||||
ok( null === control.setting );
|
||||
ok( jQuery.isEmptyObject( control.settings ) );
|
||||
} );
|
||||
|
||||
// Begin sections.
|
||||
module( 'Customizer Section in Fixture' );
|
||||
test( 'Fixture section exists', function () {
|
||||
|
Loading…
Reference in New Issue
Block a user