diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 1f5da60f8d..694873f524 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -1961,10 +1961,6 @@ final class WP_Customize_Manager { } $changeset_post_id = $this->changeset_post_id(); - if ( $changeset_post_id && in_array( get_post_status( $changeset_post_id ), array( 'publish', 'trash' ) ) ) { - wp_send_json_error( 'changeset_already_published' ); - } - if ( empty( $changeset_post_id ) ) { if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) { wp_send_json_error( 'cannot_create_changeset_post' ); @@ -1999,13 +1995,8 @@ final class WP_Customize_Manager { wp_send_json_error( 'bad_customize_changeset_status', 400 ); } $is_publish = ( 'publish' === $changeset_status || 'future' === $changeset_status ); - if ( $is_publish ) { - if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { - wp_send_json_error( 'changeset_publish_unauthorized', 403 ); - } - if ( false === has_action( 'transition_post_status', '_wp_customize_publish_changeset' ) ) { - wp_send_json_error( 'missing_publish_callback', 500 ); - } + if ( $is_publish && ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { + wp_send_json_error( 'changeset_publish_unauthorized', 403 ); } } @@ -2034,20 +2025,6 @@ final class WP_Customize_Manager { } $changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp ); } - $now = gmdate( 'Y-m-d H:i:59' ); - - $is_future_dated = ( mysql2date( 'U', $changeset_date_gmt, false ) > mysql2date( 'U', $now, false ) ); - if ( ! $is_future_dated ) { - wp_send_json_error( 'not_future_date', 400 ); // Only future dates are allowed. - } - - if ( ! $this->is_theme_active() && ( 'future' === $changeset_status || $is_future_dated ) ) { - wp_send_json_error( 'cannot_schedule_theme_switches', 400 ); // This should be allowed in the future, when theme is a regular setting. - } - $will_remain_auto_draft = ( ! $changeset_status && ( ! $changeset_post_id || 'auto-draft' === get_post_status( $changeset_post_id ) ) ); - if ( $changeset_date && $will_remain_auto_draft ) { - wp_send_json_error( 'cannot_supply_date_for_auto_draft_changeset', 400 ); - } } $r = $this->save_changeset_post( array( @@ -2057,7 +2034,15 @@ final class WP_Customize_Manager { 'data' => $input_changeset_data, ) ); if ( is_wp_error( $r ) ) { - $response = $r->get_error_data(); + $response = array( + 'message' => $r->get_error_message(), + 'code' => $r->get_error_code(), + ); + if ( is_array( $r->get_error_data() ) ) { + $response = array_merge( $response, $r->get_error_data() ); + } else { + $response['data'] = $r->get_error_data(); + } } else { $response = $r; @@ -2132,9 +2117,43 @@ final class WP_Customize_Manager { $changeset_post_id = $this->changeset_post_id(); $existing_changeset_data = array(); if ( $changeset_post_id ) { + $existing_status = get_post_status( $changeset_post_id ); + if ( 'publish' === $existing_status || 'trash' === $existing_status ) { + return new WP_Error( 'changeset_already_published' ); + } + $existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); } + // Fail if attempting to publish but publish hook is missing. + if ( 'publish' === $args['status'] && false === has_action( 'transition_post_status', '_wp_customize_publish_changeset' ) ) { + return new WP_Error( 'missing_publish_callback' ); + } + + // Validate date. + $now = gmdate( 'Y-m-d H:i:59' ); + if ( $args['date_gmt'] ) { + $is_future_dated = ( mysql2date( 'U', $args['date_gmt'], false ) > mysql2date( 'U', $now, false ) ); + if ( ! $is_future_dated ) { + return new WP_Error( 'not_future_date' ); // Only future dates are allowed. + } + + if ( ! $this->is_theme_active() && ( 'future' === $args['status'] || $is_future_dated ) ) { + return new WP_Error( 'cannot_schedule_theme_switches' ); // This should be allowed in the future, when theme is a regular setting. + } + $will_remain_auto_draft = ( ! $args['status'] && ( ! $changeset_post_id || 'auto-draft' === get_post_status( $changeset_post_id ) ) ); + if ( $will_remain_auto_draft ) { + return new WP_Error( 'cannot_supply_date_for_auto_draft_changeset' ); + } + } elseif ( $changeset_post_id && 'future' === $args['status'] ) { + + // Fail if the new status is future but the existing post's date is not in the future. + $changeset_post = get_post( $changeset_post_id ); + if ( mysql2date( 'U', $changeset_post->post_date_gmt, false ) <= mysql2date( 'U', $now, false ) ) { + return new WP_Error( 'not_future_date' ); + } + } + // The request was made via wp.customize.previewer.save(). $update_transactionally = (bool) $args['status']; $allow_revision = (bool) $args['status']; @@ -2347,7 +2366,12 @@ final class WP_Customize_Manager { if ( $args['status'] ) { $post_array['post_status'] = $args['status']; } - if ( $args['date_gmt'] ) { + + // Reset post date to now if we are publishing, otherwise pass post_date_gmt and translate for post_date. + if ( 'publish' === $args['status'] ) { + $post_array['post_date_gmt'] = '0000-00-00 00:00:00'; + $post_array['post_date'] = '0000-00-00 00:00:00'; + } elseif ( $args['date_gmt'] ) { $post_array['post_date_gmt'] = $args['date_gmt']; $post_array['post_date'] = get_date_from_gmt( $args['date_gmt'] ); } diff --git a/tests/phpunit/tests/ajax/CustomizeManager.php b/tests/phpunit/tests/ajax/CustomizeManager.php index a2b2b9f2e9..20ffc6ddd7 100644 --- a/tests/phpunit/tests/ajax/CustomizeManager.php +++ b/tests/phpunit/tests/ajax/CustomizeManager.php @@ -164,7 +164,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase { $wp_customize->save_changeset_post( array( 'status' => 'publish' ) ); $this->make_ajax_call( 'customize_save' ); $this->assertFalse( $this->_last_response_parsed['success'] ); - $this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data'] ); + $this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data']['code'] ); wp_update_post( array( 'ID' => $wp_customize->changeset_post_id(), 'post_status' => 'auto-draft' ) ); // User cannot edit. @@ -213,7 +213,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase { $_POST['customize_changeset_date'] = '2010-01-01 00:00:00'; $this->make_ajax_call( 'customize_save' ); $this->assertFalse( $this->_last_response_parsed['success'] ); - $this->assertEquals( 'not_future_date', $this->_last_response_parsed['data'] ); + $this->assertEquals( 'not_future_date', $this->_last_response_parsed['data']['code'] ); $_POST['customize_changeset_date'] = ( gmdate( 'Y' ) + 1 ) . '-01-01 00:00:00'; $this->make_ajax_call( 'customize_save' ); $this->assertTrue( $this->_last_response_parsed['success'] ); @@ -307,4 +307,80 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase { $this->assertEquals( 'New Site Title', get_option( 'blogname' ) ); $this->assertEquals( 'Published', get_post( $post_id )->post_title ); } + + /** + * Test WP_Customize_Manager::save(). + * + * @ticket 38943 + * @covers WP_Customize_Manager::save() + */ + function test_success_save_post_date() { + $uuid = wp_generate_uuid4(); + $post_id = $this->factory()->post->create( array( + 'post_name' => $uuid, + 'post_title' => 'Original', + 'post_type' => 'customize_changeset', + 'post_status' => 'auto-draft', + 'post_content' => wp_json_encode( array( + 'blogname' => array( + 'value' => 'New Site Title', + ), + ) ), + ) ); + $wp_customize = $this->set_up_valid_state( $uuid ); + + // Success future schedule date. + $future_date = ( gmdate( 'Y' ) + 1 ) . '-01-01 00:00:00'; + $_POST['customize_changeset_status'] = 'future'; + $_POST['customize_changeset_title'] = 'Future date'; + $_POST['customize_changeset_date'] = $future_date; + $this->make_ajax_call( 'customize_save' ); + $this->assertTrue( $this->_last_response_parsed['success'] ); + $changeset_post_schedule = get_post( $post_id ); + $this->assertEquals( $future_date, $changeset_post_schedule->post_date ); + + // Success future changeset change to draft keeping existing date. + unset( $_POST['customize_changeset_date'] ); + $_POST['customize_changeset_status'] = 'draft'; + $this->make_ajax_call( 'customize_save' ); + $this->assertTrue( $this->_last_response_parsed['success'] ); + $changeset_post_draft = get_post( $post_id ); + $this->assertEquals( $future_date, $changeset_post_draft->post_date ); + + // Success if date is not passed with schedule changeset and stored changeset have future date. + $_POST['customize_changeset_status'] = 'future'; + $this->make_ajax_call( 'customize_save' ); + $this->assertTrue( $this->_last_response_parsed['success'] ); + $changeset_post_schedule = get_post( $post_id ); + $this->assertEquals( $future_date, $changeset_post_schedule->post_date ); + // Success if draft with past date. + $now = current_time( 'mysql' ); + wp_update_post( array( + 'ID' => $post_id, + 'post_status' => 'draft', + 'post_date' => $now, + 'post_date_gmt' => get_gmt_from_date( $now ), + ) ); + + // Fail if future request and existing date is past. + $_POST['customize_changeset_status'] = 'future'; + unset( $_POST['customize_changeset_date'] ); + $this->make_ajax_call( 'customize_save' ); + $this->assertFalse( $this->_last_response_parsed['success'] ); + $this->assertEquals( 'not_future_date', $this->_last_response_parsed['data']['code'] ); + + // Success publish changeset reset date to current. + wp_update_post( array( + 'ID' => $post_id, + 'post_status' => 'future', + 'post_date' => $future_date, + 'post_date_gmt' => get_gmt_from_date( $future_date ), + ) ); + unset( $_POST['customize_changeset_date'] ); + $_POST['customize_changeset_status'] = 'publish'; + $this->make_ajax_call( 'customize_save' ); + $this->assertTrue( $this->_last_response_parsed['success'] ); + $changeset_post_publish = get_post( $post_id ); + $this->assertNotEquals( $future_date, $changeset_post_publish->post_date ); + } } diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php index f4879903e9..45931932f9 100644 --- a/tests/phpunit/tests/customize/manager.php +++ b/tests/phpunit/tests/customize/manager.php @@ -627,10 +627,11 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase { 'custom' => 'something', ), ); + $date = ( gmdate( 'Y' ) + 1 ) . '-12-01 00:00:00'; $r = $manager->save_changeset_post( array( 'status' => 'auto-draft', 'title' => 'Auto Draft', - 'date_gmt' => '2010-01-01 00:00:00', + 'date_gmt' => $date, 'data' => $pre_saved_data, ) ); $this->assertInternalType( 'array', $r ); @@ -651,7 +652,7 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase { } $this->assertEquals( 'Auto Draft', get_post( $post_id )->post_title ); $this->assertEquals( 'auto-draft', get_post( $post_id )->post_status ); - $this->assertEquals( '2010-01-01 00:00:00', get_post( $post_id )->post_date_gmt ); + $this->assertEquals( $date, get_post( $post_id )->post_date_gmt ); $this->assertNotEquals( 'Changeset Title', get_option( 'blogname' ) ); $this->assertArrayHasKey( 'setting_validities', $r );