From 3fb04abf1574c9eaddae071fe93eb98aafb209ca Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Tue, 15 Oct 2019 16:41:51 +0000 Subject: [PATCH] Customize: Ensure that `WP_Customize_Manager::import_theme_starter_content()` properly handles starter content with (nested) arrays as values. Previously, searching for symbol references to replace with post or attachment IDs in array values resulted in a PHP warning. Props timph, JarretC, SergeyBiryukov. Fixes #45484. git-svn-id: https://develop.svn.wordpress.org/trunk@46548 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-customize-manager.php | 51 +++++++++++- tests/phpunit/tests/customize/manager.php | 82 ++++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index d1cde4ae43d3..36b9c7bea1ff 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -1517,7 +1517,27 @@ function import_theme_starter_content( $starter_content = array() ) { // Options. foreach ( $options as $name => $value ) { - if ( preg_match( '/^{{(?P.+)}}$/', $value, $matches ) ) { + + // Serialize the value to check for post symbols. + $value = maybe_serialize( $value ); + + if ( is_serialized( $value ) ) { + if ( preg_match( '/s:\d+:"{{(?P.+)}}"/', $value, $matches ) ) { + if ( isset( $posts[ $matches['symbol'] ] ) ) { + $symbol_match = $posts[ $matches['symbol'] ]['ID']; + } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) { + $symbol_match = $attachment_ids[ $matches['symbol'] ]; + } + + // If we have any symbol matches, update the values. + if ( isset( $symbol_match ) ) { + // Replace found string matches with post IDs. + $value = str_replace( $matches[0], "i:{$symbol_match}", $value ); + } else { + continue; + } + } + } elseif ( preg_match( '/^{{(?P.+)}}$/', $value, $matches ) ) { if ( isset( $posts[ $matches['symbol'] ] ) ) { $value = $posts[ $matches['symbol'] ]['ID']; } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) { @@ -1527,6 +1547,9 @@ function import_theme_starter_content( $starter_content = array() ) { } } + // Unserialize values after checking for post symbols, so they can be properly referenced. + $value = maybe_unserialize( $value ); + if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) { $this->set_post_value( $name, $value ); $this->pending_starter_content_settings_ids[] = $name; @@ -1535,7 +1558,28 @@ function import_theme_starter_content( $starter_content = array() ) { // Theme mods. foreach ( $theme_mods as $name => $value ) { - if ( preg_match( '/^{{(?P.+)}}$/', $value, $matches ) ) { + + // Serialize the value to check for post symbols. + $value = maybe_serialize( $value ); + + // Check if value was serialized. + if ( is_serialized( $value ) ) { + if ( preg_match( '/s:\d+:"{{(?P.+)}}"/', $value, $matches ) ) { + if ( isset( $posts[ $matches['symbol'] ] ) ) { + $symbol_match = $posts[ $matches['symbol'] ]['ID']; + } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) { + $symbol_match = $attachment_ids[ $matches['symbol'] ]; + } + + // If we have any symbol matches, update the values. + if ( isset( $symbol_match ) ) { + // Replace found string matches with post IDs. + $value = str_replace( $matches[0], "i:{$symbol_match}", $value ); + } else { + continue; + } + } + } elseif ( preg_match( '/^{{(?P.+)}}$/', $value, $matches ) ) { if ( isset( $posts[ $matches['symbol'] ] ) ) { $value = $posts[ $matches['symbol'] ]['ID']; } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) { @@ -1545,6 +1589,9 @@ function import_theme_starter_content( $starter_content = array() ) { } } + // Unserialize values after checking for post symbols, so they can be properly referenced. + $value = maybe_unserialize( $value ); + // Handle header image as special case since setting has a legacy format. if ( 'header_image' === $name ) { $name = 'header_image_data'; diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php index bbcadcc15d71..bca6decdc482 100644 --- a/tests/phpunit/tests/customize/manager.php +++ b/tests/phpunit/tests/customize/manager.php @@ -741,7 +741,8 @@ function test_import_theme_starter_content() { // Ensure that re-importing doesn't cause auto-drafts to balloon. $wp_customize->import_theme_starter_content(); $changeset_data = $wp_customize->changeset_data(); - $this->assertEqualSets( array_values( $posts_by_name ), $changeset_data['nav_menus_created_posts']['value'] ); // Auto-drafts should not get re-created and amended with each import. + // Auto-drafts should not get re-created and amended with each import. + $this->assertEqualSets( array_values( $posts_by_name ), $changeset_data['nav_menus_created_posts']['value'] ); // Test that saving non-starter content on top of the changeset clears the starter_content flag. $wp_customize->save_changeset_post( @@ -755,7 +756,10 @@ function test_import_theme_starter_content() { $this->assertArrayNotHasKey( 'starter_content', $changeset_data['blogname'] ); $this->assertArrayHasKey( 'starter_content', $changeset_data['blogdescription'] ); - // Test that adding blogname starter content is ignored now that it is modified, but updating a non-modified starter content blog description passes. + /* + * Test that adding blogname starter content is ignored now that it is modified, + * but updating a non-modified starter content blog description passes. + */ $previous_blogname = $changeset_data['blogname']['value']; $previous_blogdescription = $changeset_data['blogdescription']['value']; $wp_customize->import_theme_starter_content( @@ -800,6 +804,80 @@ function test_import_theme_starter_content() { $this->assertEmpty( get_post_meta( $posts_by_name['waffles'], '_customize_draft_post_name', true ) ); } + /** + * Test WP_Customize_Manager::import_theme_starter_content() with nested arrays. + * + * @ticket 45484 + * @covers WP_Customize_Manager::import_theme_starter_content() + */ + function test_import_theme_starter_content_with_nested_arrays() { + wp_set_current_user( self::$admin_user_id ); + + $existing_published_home_page_id = $this->factory()->post->create( + array( + 'post_name' => 'home', + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + global $wp_customize; + $wp_customize = new WP_Customize_Manager(); + $starter_content_config = array( + 'posts' => array( + 'home', + ), + 'options' => array( + 'array_option' => array( + 0, + 1, + 'home_page_id' => '{{home}}', + ), + 'nested_array_option' => array( + 0, + 1, + array( + 2, + 'home_page_id' => '{{home}}', + ), + ), + ), + 'theme_mods' => array( + 'array_theme_mod' => array( + 0, + 1, + 'home_page_id' => '{{home}}', + ), + 'nested_array_theme_mod' => array( + 0, + 1, + array( + 2, + 'home_page_id' => '{{home}}', + ), + ), + ), + ); + + add_theme_support( 'starter-content', $starter_content_config ); + $this->assertEmpty( $wp_customize->unsanitized_post_values() ); + $wp_customize->import_theme_starter_content(); + $changeset_values = $wp_customize->unsanitized_post_values(); + $expected_setting_ids = array( + 'array_option', + 'array_theme_mod', + 'nav_menus_created_posts', + 'nested_array_option', + 'nested_array_theme_mod', + ); + $this->assertEqualSets( $expected_setting_ids, array_keys( $changeset_values ) ); + + $this->assertSame( $existing_published_home_page_id, $changeset_values['array_option']['home_page_id'] ); + $this->assertSame( $existing_published_home_page_id, $changeset_values['nested_array_option'][2]['home_page_id'] ); + $this->assertSame( $existing_published_home_page_id, $changeset_values['array_theme_mod']['home_page_id'] ); + $this->assertSame( $existing_published_home_page_id, $changeset_values['nested_array_theme_mod'][2]['home_page_id'] ); + } + /** * Test WP_Customize_Manager::customize_preview_init(). *