<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[31370] trunk: Customizer: Introduce an API to create WP_Customize_Settings for dynamically-created settings.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/31370">31370</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/31370","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>ocean90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2015-02-08 23:10:05 +0000 (Sun, 08 Feb 2015)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Customizer: Introduce an API to create WP_Customize_Settings for dynamically-created settings.
* Introduce WP_Customize_Manager::add_dynamic_settings() to register dynamically-created settings.
* Introduce `customize_dynamic_setting_args` filter to pass an array of args to a dynamic setting's constructor.
* Add unit tests for WP_Customize_Manager and WP_Customize_Widgets.
* See WP_Customize_Widgets as an example.
props westonruter.
fixes <a href="https://core.trac.wordpress.org/ticket/30936">#30936</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizesettingphp">trunk/src/wp-includes/class-wp-customize-setting.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizewidgetsphp">trunk/src/wp-includes/class-wp-customize-widgets.php</a></li>
<li><a href="#trunktestsphpunittestscustomizemanagerphp">trunk/tests/phpunit/tests/customize/manager.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestscustomizewidgetsphp">trunk/tests/phpunit/tests/customize/widgets.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-manager.php 2015-02-08 16:58:14 UTC (rev 31369)
+++ trunk/src/wp-includes/class-wp-customize-manager.php 2015-02-08 23:10:05 UTC (rev 31370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -65,7 +65,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Unsanitized values for Customize Settings parsed from $_POST['customized'].
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @var array|false
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @var array
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> private $_post_values;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -102,6 +102,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'customize_register', array( $this, 'register_controls' ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
</ins><span class="cx" style="display: block; padding: 0 10px"> add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -110,11 +111,23 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Return true if it's an AJAX request.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.2.0 Added $action param.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param string|null $action whether the supplied Ajax action is being run.
</ins><span class="cx" style="display: block; padding: 0 10px"> * @return bool
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function doing_ajax() {
- return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function doing_ajax( $action = null ) {
+ $doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
+ if ( ! $doing_ajax ) {
+ return false;
+ }
+
+ if ( ! $action ) {
+ return true;
+ } else {
+ // Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need to check before admin-ajax.php gets to that point
+ return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -411,8 +424,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> if ( isset( $_POST['customized'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( empty( $this->_post_values ) ) { // if not isset or of JSON error
- $this->_post_values = false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
+ $this->_post_values = array();
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> if ( empty( $this->_post_values ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -442,6 +455,19 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Override a setting's (unsanitized) value as found in any incoming $_POST['customized']
+ *
+ * @since 4.2.0
+ *
+ * @param string $setting_id The ID for the WP_Customize_Setting instance.
+ * @param mixed $value
+ */
+ public function set_post_value( $setting_id, $value ) {
+ $this->unsanitized_post_values();
+ $this->_post_values[ $setting_id ] = $value;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Print JavaScript settings.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -727,6 +753,65 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Register any dynamically-created settings, such as those from $_POST['customized'] that have no corresponding setting created.
+ *
+ * This is a mechanism to "wake up" settings that have been dynamically created
+ * on the frontend and have been sent to WordPress in $_POST['customized']. When WP
+ * loads, the dynamically-created settings then will get created and previewed
+ * even though they are not directly created statically with code.
+ *
+ * @since 4.2.0
+ *
+ * @param string[] $setting_ids The setting IDs to add.
+ * @return WP_Customize_Setting[] The settings added.
+ */
+ public function add_dynamic_settings( $setting_ids ) {
+ $new_settings = array();
+ foreach ( $setting_ids as $setting_id ) {
+ // Skip settings already created
+ if ( $this->get_setting( $setting_id ) ) {
+ continue;
+ }
+
+ $setting_args = false;
+ $setting_class = 'WP_Customize_Setting';
+
+ /**
+ * Filter a dynamic setting's constructor args.
+ *
+ * For a dynamic setting to be registered, this filter must be employed
+ * to override the default false value with an array of args to pass to
+ * the WP_Customize_Setting constructor.
+ *
+ * @since 4.2.0
+ *
+ * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
+ * @param string $setting_id ID for dynamic setting, usually coming from $_POST['customized'].
+ */
+ $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
+ if ( false === $setting_args ) {
+ continue;
+ }
+
+ /**
+ * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
+ *
+ * @since 4.2.0
+ *
+ * @param string $setting_class WP_Customize_Setting or a subclass.
+ * @param string $setting_id ID for dynamic setting, usually coming from $_POST['customized'].
+ * @param string $setting_args WP_Customize_Setting or a subclass.
+ */
+ $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
+
+ $setting = new $setting_class( $this, $setting_id, $setting_args );
+ $this->add_setting( $setting );
+ $new_settings[] = $setting;
+ }
+ return $new_settings;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Retrieve a customize setting.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -735,8 +820,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return WP_Customize_Setting
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function get_setting( $id ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( isset( $this->settings[ $id ] ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( isset( $this->settings[ $id ] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> return $this->settings[ $id ];
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1275,6 +1361,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
+ *
+ * @since 4.2.0
+ */
+ public function register_dynamic_settings() {
+ $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Callback for validating the header_textcolor value.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizesettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-setting.php 2015-02-08 16:58:14 UTC (rev 31369)
+++ trunk/src/wp-includes/class-wp-customize-setting.php 2015-02-08 23:10:05 UTC (rev 31370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -55,14 +55,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> protected $id_data = array();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Cached and sanitized $_POST value for the setting.
- *
- * @access private
- * @var mixed
- */
- private $_post_value;
-
- /**
</del><span class="cx" style="display: block; padding: 0 10px"> * Constructor.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Any supplied $args override class property defaults.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -163,7 +155,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function _preview_filter( $original ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $undefined = new stdClass(); // symbol hack
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $post_value = $this->manager->post_value( $this, $undefined );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $post_value = $this->post_value( $undefined );
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( $undefined === $post_value ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $value = $this->_original_value;
</span><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -211,17 +203,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return mixed The default value on failure, otherwise the sanitized value.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> final public function post_value( $default = null ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Check for a cached value
- if ( isset( $this->_post_value ) )
- return $this->_post_value;
-
- // Call the manager for the post value
- $result = $this->manager->post_value( $this );
-
- if ( isset( $result ) )
- return $this->_post_value = $result;
- else
- return $default;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return $this->manager->post_value( $this, $default );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizewidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-widgets.php 2015-02-08 16:58:14 UTC (rev 31369)
+++ trunk/src/wp-includes/class-wp-customize-widgets.php 2015-02-08 23:10:05 UTC (rev 31370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -35,37 +35,35 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @access protected
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @var
- */
- protected $_customized;
-
- /**
- * @since 3.9.0
- * @access protected
</del><span class="cx" style="display: block; padding: 0 10px"> * @var array
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- protected $_prepreview_added_filters = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected $rendered_sidebars = array();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @access protected
</span><span class="cx" style="display: block; padding: 0 10px"> * @var array
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- protected $rendered_sidebars = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected $rendered_widgets = array();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @access protected
</span><span class="cx" style="display: block; padding: 0 10px"> * @var array
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- protected $rendered_widgets = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected $old_sidebars_widgets = array();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 3.9.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Mapping of setting type to setting ID pattern.
+ *
+ * @since 4.2.0
</ins><span class="cx" style="display: block; padding: 0 10px"> * @access protected
</span><span class="cx" style="display: block; padding: 0 10px"> * @var array
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- protected $old_sidebars_widgets = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected $setting_id_patterns = array(
+ 'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
+ 'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
+ );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Initial loader.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -78,7 +76,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> public function __construct( $manager ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $this->manager = $manager;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- add_action( 'after_setup_theme', array( $this, 'setup_widget_addition_previews' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
+ add_action( 'after_setup_theme', array( $this, 'register_settings' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_loaded', array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'customize_register', array( $this, 'schedule_customize_register' ), 1 );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -95,196 +94,95 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Get an unslashed post value or return a default.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the widget setting type given a setting ID.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 3.9.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.2.0
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @access protected
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param $setting_id
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param string $name Post value.
- * @param mixed $default Default post value.
- * @return mixed Unslashed post value or default value.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @return string|null
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- protected function get_post_value( $name, $default = null ) {
- if ( ! isset( $_POST[ $name ] ) ) {
- return $default;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected function get_setting_type( $setting_id ) {
+ static $cache = array();
+ if ( isset( $cache[ $setting_id ] ) ) {
+ return $cache[ $setting_id ];
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- return wp_unslash( $_POST[$name] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ foreach ( $this->setting_id_patterns as $type => $pattern ) {
+ if ( preg_match( $pattern, $setting_id ) ) {
+ $cache[ $setting_id ] = $type;
+ return $type;
+ }
+ }
+ return null;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Set up widget addition previews.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Since the widgets get registered on 'widgets_init' before the Customizer
- * settings are set up on 'customize_register', we have to filter the options
- * similarly to how the setting previewer will filter the options later.
- *
- * @since 3.9.0
- *
- * @access public
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.2.0
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function setup_widget_addition_previews() {
- $is_customize_preview = false;
-
- if ( ! empty( $this->manager ) && ! is_admin() && 'on' === $this->get_post_value( 'wp_customize' ) ) {
- $is_customize_preview = check_ajax_referer( 'preview-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function register_settings() {
+ $widget_setting_ids = array();
+ $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
+ foreach ( $incoming_setting_ids as $setting_id ) {
+ if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) {
+ $widget_setting_ids[] = $setting_id;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- $is_ajax_widget_update = false;
- if ( $this->manager->doing_ajax() && 'update-widget' === $this->get_post_value( 'action' ) ) {
- $is_ajax_widget_update = check_ajax_referer( 'update-widget', 'nonce', false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) {
+ $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $is_ajax_customize_save = false;
- if ( $this->manager->doing_ajax() && 'customize_save' === $this->get_post_value( 'action' ) ) {
- $is_ajax_customize_save = check_ajax_referer( 'save-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $is_valid_request = ( $is_ajax_widget_update || $is_customize_preview || $is_ajax_customize_save );
- if ( ! $is_valid_request ) {
- return;
- }
-
- // Input from Customizer preview.
- if ( isset( $_POST['customized'] ) ) {
- $this->_customized = json_decode( $this->get_post_value( 'customized' ), true );
- } else { // Input from ajax widget update request.
- $this->_customized = array();
- $id_base = $this->get_post_value( 'id_base' );
- $widget_number = $this->get_post_value( 'widget_number', false );
- $option_name = 'widget_' . $id_base;
- $this->_customized[ $option_name ] = array();
- if ( preg_match( '/^[0-9]+$/', $widget_number ) ) {
- $option_name .= '[' . $widget_number . ']';
- $this->_customized[ $option_name ][ $widget_number ] = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+ * Preview settings right away so that widgets and sidebars will get registered properly.
+ * But don't do this if a customize_save because this will cause WP to think there is nothing
+ * changed that needs to be saved.
+ */
+ if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
+ foreach ( $settings as $setting ) {
+ $setting->preview();
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- $function = array( $this, 'prepreview_added_sidebars_widgets' );
-
- $hook = 'option_sidebars_widgets';
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
-
- $hook = 'default_option_sidebars_widgets';
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
-
- $function = array( $this, 'prepreview_added_widget_instance' );
- foreach ( $this->_customized as $setting_id => $value ) {
- if ( preg_match( '/^(widget_.+?)(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
- $option = $matches[1];
-
- $hook = sprintf( 'option_%s', $option );
- if ( ! has_filter( $hook, $function ) ) {
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
- }
-
- $hook = sprintf( 'default_option_%s', $option );
- if ( ! has_filter( $hook, $function ) ) {
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
- }
-
- /*
- * Make sure the option is registered so that the update_option()
- * won't fail due to the filters providing a default value, which
- * causes the update_option() to get confused.
- */
- add_option( $option, array() );
- }
- }
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Ensure that newly-added widgets will appear in the widgets_sidebars.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determine the arguments for a dynamically-created setting.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * This is necessary because the Customizer's setting preview filters
- * are added after the widgets_init action, which is too late for the
- * widgets to be set up properly.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.2.0
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 3.9.0
- * @access public
- *
- * @param array $sidebars_widgets Associative array of sidebars and their widgets.
- * @return array Filtered array of sidebars and their widgets.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param false|array $args
+ * @param string $setting_id
+ * @return false|array
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function prepreview_added_sidebars_widgets( $sidebars_widgets ) {
- foreach ( $this->_customized as $setting_id => $value ) {
- if ( preg_match( '/^sidebars_widgets\[(.+?)\]$/', $setting_id, $matches ) ) {
- $sidebar_id = $matches[1];
- $sidebars_widgets[ $sidebar_id ] = $value;
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
+ if ( $this->get_setting_type( $setting_id ) ) {
+ $args = $this->get_setting_args( $setting_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- return $sidebars_widgets;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return $args;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Ensure newly-added widgets have empty instances so they
- * will be recognized.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get an unslashed post value or return a default.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * This is necessary because the Customizer's setting preview
- * filters are added after the widgets_init action, which is
- * too late for the widgets to be set up properly.
- *
</del><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @access public
</del><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param array|bool|mixed $value Widget instance(s), false if open was empty.
- * @return array|mixed Widget instance(s) with additions.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @access protected
+ *
+ * @param string $name Post value.
+ * @param mixed $default Default post value.
+ * @return mixed Unslashed post value or default value.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function prepreview_added_widget_instance( $value = false ) {
- if ( ! preg_match( '/^(?:default_)?option_(widget_(.+))/', current_filter(), $matches ) ) {
- return $value;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected function get_post_value( $name, $default = null ) {
+ if ( ! isset( $_POST[ $name ] ) ) {
+ return $default;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $id_base = $matches[2];
</del><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- foreach ( $this->_customized as $setting_id => $setting ) {
- $parsed_setting_id = $this->parse_widget_setting_id( $setting_id );
- if ( is_wp_error( $parsed_setting_id ) || $id_base !== $parsed_setting_id['id_base'] ) {
- continue;
- }
- $widget_number = $parsed_setting_id['number'];
-
- if ( is_null( $widget_number ) ) {
- // Single widget.
- if ( false === $value ) {
- $value = array();
- }
- } else {
- // Multi widget.
- if ( empty( $value ) ) {
- $value = array( '_multiwidget' => 1 );
- }
- if ( ! isset( $value[ $widget_number ] ) ) {
- $value[ $widget_number ] = array();
- }
- }
- }
-
- return $value;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return wp_unslash( $_POST[ $name ] );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Remove pre-preview filters.
- *
- * Removes filters added in setup_widget_addition_previews()
- * to ensure widgets are populating the options during
- * 'widgets_init'.
- *
- * @since 3.9.0
- * @access public
- */
- public function remove_prepreview_filters() {
- foreach ( $this->_prepreview_added_filters as $prepreview_added_filter ) {
- remove_filter( $prepreview_added_filter['hook'], $prepreview_added_filter['function'] );
- }
- $this->_prepreview_added_filters = array();
- }
-
- /**
</del><span class="cx" style="display: block; padding: 0 10px"> * Override sidebars_widgets for theme switch.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * When switching a theme via the Customizer, supply any previously-configured
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -380,7 +278,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @access public
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function schedule_customize_register() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( is_admin() ) { // @todo for some reason, $wp_customize->is_preview() is true here?
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_admin() ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->customize_register();
</span><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp', array( $this, 'customize_register' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -412,12 +310,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $setting_id = $this->get_setting_id( $widget_id );
</span><span class="cx" style="display: block; padding: 0 10px"> $setting_args = $this->get_setting_args( $setting_id );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- $setting_args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
- $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
-
- $this->manager->add_setting( $setting_id, $setting_args );
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! $this->manager->get_setting( $setting_id ) ) {
+ $this->manager->add_setting( $setting_id, $setting_args );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> $new_setting_ids[] = $setting_id;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -452,11 +347,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> if ( $is_registered_sidebar || $is_inactive_widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
</span><span class="cx" style="display: block; padding: 0 10px"> $setting_args = $this->get_setting_args( $setting_id );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- $setting_args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
- $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
-
- $this->manager->add_setting( $setting_id, $setting_args );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! $this->manager->get_setting( $setting_id ) ) {
+ $this->manager->add_setting( $setting_id, $setting_args );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> $new_setting_ids[] = $setting_id;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Add section to contain controls.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -523,16 +416,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- /*
- * We have to register these settings later than customize_preview_init
- * so that other filters have had a chance to run.
- */
- if ( did_action( 'customize_preview_init' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> foreach ( $new_setting_ids as $new_setting_id ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $this->manager->get_setting( $new_setting_id )->preview();
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->remove_prepreview_filters();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -804,6 +694,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'transport' => 'refresh',
</span><span class="cx" style="display: block; padding: 0 10px"> 'default' => array(),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
+ $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
+ $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
+ } else if ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
+ $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
+ $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $args = array_merge( $args, $overrides );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -831,15 +730,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return array Array of sanitized widget IDs.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function sanitize_sidebar_widgets( $widget_ids ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- global $wp_registered_widgets;
-
- $widget_ids = array_map( 'strval', (array) $widget_ids );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $widget_ids = array_map( 'strval', (array) $widget_ids );
</ins><span class="cx" style="display: block; padding: 0 10px"> $sanitized_widget_ids = array();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px"> foreach ( $widget_ids as $widget_id ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( array_key_exists( $widget_id, $wp_registered_widgets ) ) {
- $sanitized_widget_ids[] = $widget_id;
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> return $sanitized_widget_ids;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -974,7 +868,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @access public
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function customize_preview_init() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
</del><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_print_styles', array( $this, 'print_preview_css' ), 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_footer', array( $this, 'export_preview_data' ), 20 );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1315,8 +1208,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Clean up any input vars that were manually added
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( $added_input_vars as $key ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- unset( $_POST[$key] );
- unset( $_REQUEST[$key] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ unset( $_POST[ $key ] );
+ unset( $_REQUEST[ $key ] );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Make sure the expected option was updated.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1333,25 +1226,31 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Obtain the widget instance.
+ $option = $this->get_captured_option( $option_name );
+ if ( null !== $parsed_id['number'] ) {
+ $instance = $option[ $parsed_id['number'] ];
+ } else {
+ $instance = $option;
+ }
+
+ /*
+ * Override the incoming $_POST['customized'] for a newly-created widget's
+ * setting with the new $instance so that the preview filter currently
+ * in place from WP_Customize_Setting::preview() will use this value
+ * instead of the default widget instance value (an empty array).
+ */
+ $setting_id = $this->get_setting_id( $widget_id );
+ $this->manager->set_post_value( $setting_id, $instance );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Obtain the widget control with the updated instance in place.
</span><span class="cx" style="display: block; padding: 0 10px"> ob_start();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- $form = $wp_registered_widget_controls[$widget_id];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $form = $wp_registered_widget_controls[ $widget_id ];
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( $form ) {
</span><span class="cx" style="display: block; padding: 0 10px"> call_user_func_array( $form['callback'], $form['params'] );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px"> $form = ob_get_clean();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Obtain the widget instance.
- $option = get_option( $option_name );
-
- if ( null !== $parsed_id['number'] ) {
- $instance = $option[$parsed_id['number']];
- } else {
- $instance = $option;
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> $this->stop_capturing_option_updates();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return compact( 'instance', 'form' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1383,8 +1282,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> wp_die( -1 );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( ! isset( $_POST['widget-id'] ) ) {
- wp_send_json_error();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( empty( $_POST['widget-id'] ) ) {
+ wp_send_json_error( 'missing_widget-id' );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /** This action is documented in wp-admin/includes/ajax-actions.php */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1398,15 +1297,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $widget_id = $this->get_post_value( 'widget-id' );
</span><span class="cx" style="display: block; padding: 0 10px"> $parsed_id = $this->parse_widget_id( $widget_id );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $id_base = $parsed_id['id_base'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $id_base = $parsed_id['id_base'];
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
- wp_send_json_error();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $is_updating_widget_template = (
+ isset( $_POST[ 'widget-' . $id_base ] )
+ &&
+ is_array( $_POST[ 'widget-' . $id_base ] )
+ &&
+ preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
+ );
+ if ( $is_updating_widget_template ) {
+ wp_send_json_error( 'template_widget_not_updatable' );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_wp_error( $updated_widget ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_send_json_error();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_send_json_error( $updated_widget->get_error_message() );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $form = $updated_widget['form'];
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1463,6 +1369,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the option that was captured from being saved.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param string $option_name Option name.
+ * @param mixed $default Optional. Default value to return if the option does not exist.
+ * @return mixed Value set for the option.
+ */
+ protected function get_captured_option( $option_name, $default = false ) {
+ if ( array_key_exists( $option_name, $this->_captured_options ) ) {
+ $value = $this->_captured_options[ $option_name ];
+ } else {
+ $value = $default;
+ }
+ return $value;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Get the number of captured widget option updates.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1496,21 +1421,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @access public
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param mixed $new_value
- * @param string $option_name
- * @param mixed $old_value
- * @return mixed
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param mixed $new_value The new option value.
+ * @param string $option_name Name of the option.
+ * @param mixed $old_value The old option value.
+ * @return mixed Filtered option value.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( $this->is_option_capture_ignored( $option_name ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( ! isset( $this->_captured_options[$option_name] ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->_captured_options[$option_name] = $new_value;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->_captured_options[ $option_name ] = $new_value;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return $old_value;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1521,14 +1446,14 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @access public
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param mixed $value Option
- * @return mixed
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param mixed $value Value to return instead of the option value.
+ * @return mixed Filtered option value.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function capture_filter_pre_get_option( $value ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $option_name = preg_replace( '/^pre_option_/', '', current_filter() );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( isset( $this->_captured_options[$option_name] ) ) {
- $value = $this->_captured_options[$option_name];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( isset( $this->_captured_options[ $option_name ] ) ) {
+ $value = $this->_captured_options[ $option_name ];
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /** This filter is documented in wp-includes/option.php */
</span><span class="cx" style="display: block; padding: 0 10px"> $value = apply_filters( 'option_' . $option_name, $value );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1557,4 +1482,36 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->_captured_options = array();
</span><span class="cx" style="display: block; padding: 0 10px"> $this->_is_capturing_option_updates = false;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function setup_widget_addition_previews() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function prepreview_added_sidebars_widgets() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function prepreview_added_widget_instance() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function remove_prepreview_filters() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/manager.php 2015-02-08 16:58:14 UTC (rev 31369)
+++ trunk/tests/phpunit/tests/customize/manager.php 2015-02-08 23:10:05 UTC (rev 31370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -32,8 +32,38 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Test WP_Customize_Manager::unsanitized_post_values()
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Test WP_Customize_Manager::doing_ajax().
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @group ajax
+ */
+ function test_doing_ajax() {
+ if ( ! defined( 'DOING_AJAX' ) ) {
+ define( 'DOING_AJAX', true );
+ }
+
+ $manager = $this->instantiate();
+ $this->assertTrue( $manager->doing_ajax() );
+
+ $_REQUEST['action'] = 'customize_save';
+ $this->assertTrue( $manager->doing_ajax( 'customize_save' ) );
+ $this->assertFalse( $manager->doing_ajax( 'update-widget' ) );
+ }
+
+ /**
+ * Test ! WP_Customize_Manager::doing_ajax().
+ */
+ function test_not_doing_ajax() {
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ $this->markTestSkipped( 'Cannot test when DOING_AJAX' );
+ }
+
+ $manager = $this->instantiate();
+ $this->assertFalse( $manager->doing_ajax() );
+ }
+
+ /**
+ * Test WP_Customize_Manager::unsanitized_post_values().
+ *
</ins><span class="cx" style="display: block; padding: 0 10px"> * @ticket 30988
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function test_unsanitized_post_values() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -49,7 +79,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Test the WP_Customize_Manager::post_value() method
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Test the WP_Customize_Manager::post_value() method.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @ticket 30988
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -71,5 +101,78 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( 'post_value_bar_default', $manager->post_value( $bar_setting, 'post_value_bar_default' ), 'Expected post_value($bar_setting, $default) to return $default since no value supplied in $_POST[customized][bar]' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+ * Test the WP_Customize_Manager::add_dynamic_settings() method.
+ *
+ * @ticket 30936
+ */
+ function test_add_dynamic_settings() {
+ $manager = $this->instantiate();
+ $setting_ids = array( 'foo', 'bar' );
+ $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
+ $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected there to not be a bar setting up front.' );
+ $manager->add_dynamic_settings( $setting_ids );
+ $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected the bar setting to remain absent since filters not added.' );
+
+ $this->action_customize_register_for_dynamic_settings();
+ $manager->add_dynamic_settings( $setting_ids );
+ $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected bar setting to be created since filters were added.' );
+ $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->default, 'Expected static foo setting to not get overridden by dynamic setting.' );
+ $this->assertEquals( 'dynamic_bar_default', $manager->get_setting( 'bar' )->default, 'Expected dynamic setting bar to have default providd by filter.' );
+ }
+
+ /**
+ * Test the WP_Customize_Manager::register_dynamic_settings() method.
+ *
+ * This is similar to test_add_dynamic_settings, except the settings are passed via $_POST['customized'].
+ *
+ * @ticket 30936
+ */
+ function test_register_dynamic_settings() {
+ $posted_settings = array(
+ 'foo' => 'OOF',
+ 'bar' => 'RAB',
+ );
+ $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) );
+
+ add_action( 'customize_register', array( $this, 'action_customize_register_for_dynamic_settings' ) );
+
+ $manager = $this->instantiate();
+ $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
+
+ $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to not be registered.' );
+ do_action( 'customize_register', $manager );
+ $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to be automatically registered after customize_register action.' );
+ $this->assertEmpty( $manager->get_setting( 'baz' ), 'Expected unrecognized dynamic setting "baz" to remain unregistered.' );
+ }
+
+ /**
+ * In lieu of closures, callback for customize_register action added in test_register_dynamic_settings().
+ */
+ function action_customize_register_for_dynamic_settings() {
+ add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args_for_test_dynamic_settings' ), 10, 2 );
+ add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_customize_dynamic_setting_class_for_test_dynamic_settings' ), 10, 3 );
+ }
+
+ /**
+ * In lieu of closures, callback for customize_dynamic_setting_args filter added for test_register_dynamic_settings().
+ */
+ function filter_customize_dynamic_setting_args_for_test_dynamic_settings( $setting_args, $setting_id ) {
+ $this->assertEquals( false, $setting_args, 'Expected $setting_args to be false by default.' );
+ $this->assertInternalType( 'string', $setting_id );
+ if ( in_array( $setting_id, array( 'foo', 'bar' ) ) ) {
+ $setting_args = array( 'default' => "dynamic_{$setting_id}_default" );
+ }
+ return $setting_args;
+ }
+
+ /**
+ * In lieu of closures, callback for customize_dynamic_setting_class filter added for test_register_dynamic_settings().
+ */
+ function filter_customize_dynamic_setting_class_for_test_dynamic_settings( $setting_class, $setting_id, $setting_args ) {
+ $this->assertEquals( 'WP_Customize_Setting', $setting_class );
+ $this->assertInternalType( 'string', $setting_id );
+ $this->assertInternalType( 'array', $setting_args );
+ return $setting_class;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del></span></pre></div>
<a id="trunktestsphpunittestscustomizewidgetsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/customize/widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/widgets.php (rev 0)
+++ trunk/tests/phpunit/tests/customize/widgets.php 2015-02-08 23:10:05 UTC (rev 31370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,197 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Tests for the WP_Customize_Widgets class.
+ *
+ * @group customize
+ */
+class Tests_WP_Customize_Widgets extends WP_UnitTestCase {
+
+ /**
+ * @var WP_Customize_Manager
+ */
+ protected $manager;
+
+ function setUp() {
+ parent::setUp();
+ require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
+ $GLOBALS['wp_customize'] = new WP_Customize_Manager();
+ $this->manager = $GLOBALS['wp_customize'];
+
+ unset( $GLOBALS['_wp_sidebars_widgets'] ); // clear out cache set by wp_get_sidebars_widgets()
+ $sidebars_widgets = wp_get_sidebars_widgets();
+ $this->assertEqualSets( array( 'wp_inactive_widgets', 'sidebar-1' ), array_keys( wp_get_sidebars_widgets() ) );
+ $this->assertContains( 'search-2', $sidebars_widgets['sidebar-1'] );
+ $this->assertContains( 'categories-2', $sidebars_widgets['sidebar-1'] );
+ $this->assertArrayHasKey( 2, get_option( 'widget_search' ) );
+ $widget_categories = get_option( 'widget_categories' );
+ $this->assertArrayHasKey( 2, $widget_categories );
+ $this->assertEquals( '', $widget_categories[2]['title'] );
+
+ remove_action( 'after_setup_theme', 'twentyfifteen_setup' ); // @todo We should not be including a theme anyway
+
+ $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $user_id );
+ }
+
+ function tearDown() {
+ parent::tearDown();
+ $this->manager = null;
+ unset( $GLOBALS['wp_customize'] );
+ }
+
+ function set_customized_post_data( $customized ) {
+ $_POST['customized'] = wp_slash( wp_json_encode( $customized ) );
+ }
+
+ function do_customize_boot_actions() {
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ do_action( 'setup_theme' );
+ $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->manager->theme()->get_stylesheet() );
+ do_action( 'after_setup_theme' );
+ do_action( 'init' );
+ do_action( 'wp_loaded' );
+ do_action( 'wp', $GLOBALS['wp'] );
+ }
+
+ /**
+ * Test WP_Customize_Widgets::__construct()
+ */
+ function test_construct() {
+ $this->assertInstanceOf( 'WP_Customize_Widgets', $this->manager->widgets );
+ $this->assertEquals( $this->manager, $this->manager->widgets->manager );
+ }
+
+ /**
+ * Test WP_Customize_Widgets::register_settings()
+ *
+ * @ticket 30988
+ */
+ function test_register_settings() {
+
+ $raw_widget_customized = array(
+ 'widget_categories[2]' => array(
+ 'title' => 'Taxonomies Brand New Value',
+ 'count' => 0,
+ 'hierarchical' => 0,
+ 'dropdown' => 0,
+ ),
+ 'widget_search[3]' => array(
+ 'title' => 'Not as good as Google!',
+ ),
+ );
+ $customized = array();
+ foreach ( $raw_widget_customized as $setting_id => $instance ) {
+ $customized[ $setting_id ] = $this->manager->widgets->sanitize_widget_js_instance( $instance );
+ }
+
+ $this->set_customized_post_data( $customized );
+ $this->do_customize_boot_actions();
+ $this->assertTrue( is_customize_preview() );
+
+ $this->assertNotEmpty( $this->manager->get_setting( 'widget_categories[2]' ), 'Expected setting for pre-existing widget category-2, being customized.' );
+ $this->assertNotEmpty( $this->manager->get_setting( 'widget_search[2]' ), 'Expected setting for pre-existing widget search-2, not being customized.' );
+ $this->assertNotEmpty( $this->manager->get_setting( 'widget_search[3]' ), 'Expected dynamic setting for non-existing widget search-3, being customized.' );
+
+ $widget_categories = get_option( 'widget_categories' );
+ $this->assertEquals( $raw_widget_customized['widget_categories[2]'], $widget_categories[2], 'Expected $wp_customize->get_setting(widget_categories[2])->preview() to have been called.' );
+ }
+
+ /**
+ * Test WP_Customize_Widgets::get_setting_args()
+ */
+ function test_get_setting_args() {
+
+ add_filter( 'widget_customizer_setting_args', array( $this, 'filter_widget_customizer_setting_args' ), 10, 2 );
+
+ $default_args = array(
+ 'type' => 'option',
+ 'capability' => 'edit_theme_options',
+ 'transport' => 'refresh',
+ 'default' => array(),
+ 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ),
+ 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ),
+ );
+
+ $args = $this->manager->widgets->get_setting_args( 'widget_foo[2]' );
+ foreach ( $default_args as $key => $default_value ) {
+ $this->assertEquals( $default_value, $args[ $key ] );
+ }
+ $this->assertEquals( 'WIDGET_FOO[2]', $args['uppercase_id_set_by_filter'] );
+
+ $override_args = array(
+ 'type' => 'theme_mod',
+ 'capability' => 'edit_posts',
+ 'transport' => 'postMessage',
+ 'default' => array( 'title' => 'asd' ),
+ 'sanitize_callback' => '__return_empty_array',
+ 'sanitize_js_callback' => '__return_empty_array',
+ );
+ $args = $this->manager->widgets->get_setting_args( 'widget_bar[3]', $override_args );
+ foreach ( $override_args as $key => $override_value ) {
+ $this->assertEquals( $override_value, $args[ $key ] );
+ }
+ $this->assertEquals( 'WIDGET_BAR[3]', $args['uppercase_id_set_by_filter'] );
+
+ $default_args = array(
+ 'type' => 'option',
+ 'capability' => 'edit_theme_options',
+ 'transport' => 'refresh',
+ 'default' => array(),
+ 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets' ),
+ 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets_js_instance' ),
+ );
+ $args = $this->manager->widgets->get_setting_args( 'sidebars_widgets[sidebar-1]' );
+ foreach ( $default_args as $key => $default_value ) {
+ $this->assertEquals( $default_value, $args[ $key ] );
+ }
+ $this->assertEquals( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] );
+
+ $override_args = array(
+ 'type' => 'theme_mod',
+ 'capability' => 'edit_posts',
+ 'transport' => 'postMessage',
+ 'default' => array( 'title' => 'asd' ),
+ 'sanitize_callback' => '__return_empty_array',
+ 'sanitize_js_callback' => '__return_empty_array',
+ );
+ $args = $this->manager->widgets->get_setting_args( 'sidebars_widgets[sidebar-2]', $override_args );
+ foreach ( $override_args as $key => $override_value ) {
+ $this->assertEquals( $override_value, $args[ $key ] );
+ }
+ $this->assertEquals( 'SIDEBARS_WIDGETS[SIDEBAR-2]', $args['uppercase_id_set_by_filter'] );
+ }
+
+ function filter_widget_customizer_setting_args( $args, $id ) {
+ $args['uppercase_id_set_by_filter'] = strtoupper( $id );
+ return $args;
+ }
+
+ /**
+ * Test WP_Customize_Widgets::sanitize_widget_js_instance() and WP_Customize_Widgets::sanitize_widget_instance()
+ */
+ function test_sanitize_widget_js_instance() {
+ $this->do_customize_boot_actions();
+
+ $new_categories_instance = array(
+ 'title' => 'Taxonomies Brand New Value',
+ 'count' => '1',
+ 'hierarchical' => '1',
+ 'dropdown' => '1',
+ );
+
+ $sanitized_for_js = $this->manager->widgets->sanitize_widget_js_instance( $new_categories_instance );
+ $this->assertArrayHasKey( 'encoded_serialized_instance', $sanitized_for_js );
+ $this->assertTrue( is_serialized( base64_decode( $sanitized_for_js['encoded_serialized_instance'] ), true ) );
+ $this->assertEquals( $new_categories_instance['title'], $sanitized_for_js['title'] );
+ $this->assertTrue( $sanitized_for_js['is_widget_customizer_js_value'] );
+ $this->assertArrayHasKey( 'instance_hash_key', $sanitized_for_js );
+
+ $corrupted_sanitized_for_js = $sanitized_for_js;
+ $corrupted_sanitized_for_js['encoded_serialized_instance'] = base64_encode( serialize( array( 'title' => 'EVIL' ) ) );
+ $this->assertNull( $this->manager->widgets->sanitize_widget_instance( $corrupted_sanitized_for_js ), 'Expected sanitize_widget_instance to reject corrupted data.' );
+
+ $unsanitized_from_js = $this->manager->widgets->sanitize_widget_instance( $sanitized_for_js );
+ $this->assertEquals( $unsanitized_from_js, $new_categories_instance );
+ }
+}
</ins></span></pre>
</div>
</div>
</body>
</html>