diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index 03801ea..1dbaabb 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -51,9 +51,9 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: '7.2' + php-version: '7.4' extensions: simplexml, mysql - tools: phpunit:7.5.20, phpunit-polyfills + tools: phpunit-polyfills - name: Checkout source code uses: actions/checkout@v2 - name: Install Subversion diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index 1f36a03..b8c4424 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -686,13 +686,29 @@ public function select_page() { die( esc_html__( 'Security check.', 'wp-maintenance-mode' ) ); } - $this->plugin_settings['design']['page_id'] = $_POST['page_id']; - wp_update_post( - array( - 'ID' => $this->plugin_settings['design']['page_id'], - 'page_template' => 'templates/wpmm-page-template.php', - ) - ); + $page_id = isset( $_POST['page_id'] ) ? absint( $_POST['page_id'] ) : 0; + + $previous_page_id = isset( $this->plugin_settings['design']['page_id'] ) ? absint( $this->plugin_settings['design']['page_id'] ) : 0; + + if ( $previous_page_id && $previous_page_id !== $page_id ) { + // hand the previously selected page back before abandoning it, + // otherwise nothing would ever restore it + wpmm_restore_page_state( $previous_page_id ); + } + + $this->plugin_settings['design']['page_id'] = $page_id; + + if ( $page_id ) { + // remember the page's original state before taking it over as a maintenance page + wpmm_record_page_state( $page_id ); + + wp_update_post( + array( + 'ID' => $page_id, + 'page_template' => 'templates/wpmm-page-template.php', + ) + ); + } update_option( 'wpmm_settings', $this->plugin_settings ); @@ -721,9 +737,19 @@ public function insert_template() { die( esc_html__( 'Security check.', 'wp-maintenance-mode' ) ); } - $template_slug = $_POST['template_slug']; - $category = $_POST['category']; - $template = json_decode( file_get_contents( WPMM_TEMPLATES_PATH . $category . '/' . $template_slug . '/blocks-export.json' ) ); + $template_slug = isset( $_POST['template_slug'] ) ? basename( sanitize_text_field( $_POST['template_slug'] ) ) : ''; + $category = isset( $_POST['category'] ) ? basename( sanitize_text_field( $_POST['category'] ) ) : ''; + $template_file = WPMM_TEMPLATES_PATH . $category . '/' . $template_slug . '/blocks-export.json'; + + if ( '' === $template_slug || '' === $category || ! is_readable( $template_file ) ) { + wp_send_json_error( array( 'error' => 'Unknown template' ) ); + } + + $template = json_decode( file_get_contents( $template_file ) ); + + if ( empty( $template->content ) ) { + wp_send_json_error( array( 'error' => 'Unknown template' ) ); + } $blocks = str_replace( '\n', '', $template->content ); @@ -734,12 +760,25 @@ public function insert_template() { 'page_template' => 'templates/wpmm-page-template.php', ); - if ( isset( $this->plugin_settings['design']['page_id'] ) && get_post_status( $this->plugin_settings['design']['page_id'] ) && get_post_status( $this->plugin_settings['design']['page_id'] ) !== 'trash' ) { - $post_arr['ID'] = $this->plugin_settings['design']['page_id']; + $selected_page_id = isset( $this->plugin_settings['design']['page_id'] ) ? absint( $this->plugin_settings['design']['page_id'] ) : 0; + $page_exists = $selected_page_id && get_post_status( $selected_page_id ) && get_post_status( $selected_page_id ) !== 'trash'; + + if ( $page_exists && get_post_meta( $selected_page_id, '_wpmm_generated', true ) ) { + // only pages created by the plugin may be overwritten + $post_arr['ID'] = $selected_page_id; $page_id = wp_update_post( $post_arr ); } else { + if ( $page_exists ) { + // hand the user's page back before switching to a generated one + wpmm_restore_page_state( $selected_page_id ); + } + $post_arr['post_title'] = __( 'Maintenance Page', 'wp-maintenance-mode' ); $page_id = wp_insert_post( $post_arr ); + + if ( $page_id && ! $page_id instanceof WP_Error ) { + update_post_meta( $page_id, '_wpmm_generated', 1 ); + } } if ( $page_id === 0 || $page_id instanceof WP_Error ) { @@ -747,7 +786,10 @@ public function insert_template() { } $this->plugin_settings['design']['page_id'] = $page_id; - CSS_Handler::generate_css_file( $page_id ); + + if ( class_exists( 'ThemeIsle\GutenbergBlocks\CSS\CSS_Handler' ) ) { + CSS_Handler::generate_css_file( $page_id ); + } if ( 'wizard' === $_POST['source'] ) { $this->plugin_settings['general']['status'] = 1; diff --git a/includes/classes/wp-maintenance-mode.php b/includes/classes/wp-maintenance-mode.php index 8933e46..d98553a 100644 --- a/includes/classes/wp-maintenance-mode.php +++ b/includes/classes/wp-maintenance-mode.php @@ -97,12 +97,23 @@ private function __construct() { add_action( 'wp_ajax_wpmm_send_contact', array( $this, 'send_contact' ) ); add_action( 'otter_form_after_submit', array( $this, 'otter_add_subscriber' ) ); - if ( isset( $this->plugin_settings['design']['page_id'] ) && get_option( 'wpmm_new_look' ) && get_post_status( $this->plugin_settings['design']['page_id'] ) === 'private' ) { - add_filter( 'wpo_purge_all_cache_on_update', '__return_true' ); - if ( function_exists( 'wp_functionality_constants' ) ) { - wp_functionality_constants(); + $maintenance_page_status = isset( $this->plugin_settings['design']['page_id'] ) ? get_post_status( $this->plugin_settings['design']['page_id'] ) : false; + + if ( $maintenance_page_status && $maintenance_page_status !== 'trash' && get_option( 'wpmm_new_look' ) ) { + // remember the page's original state so it can be restored when maintenance mode is disabled + wpmm_record_page_state( (int) $this->plugin_settings['design']['page_id'] ); + + if ( get_post_meta( $this->plugin_settings['design']['page_id'], '_wp_page_template', true ) !== 'templates/wpmm-page-template.php' ) { + update_post_meta( $this->plugin_settings['design']['page_id'], '_wp_page_template', 'templates/wpmm-page-template.php' ); + } + + if ( get_post_status( $this->plugin_settings['design']['page_id'] ) === 'private' ) { + add_filter( 'wpo_purge_all_cache_on_update', '__return_true' ); + if ( function_exists( 'wp_functionality_constants' ) ) { + wp_functionality_constants(); + } + wp_publish_post( $this->plugin_settings['design']['page_id'] ); } - wp_publish_post( $this->plugin_settings['design']['page_id'] ); } update_option( 'show_on_front', 'page' ); @@ -141,21 +152,14 @@ function ( $value ) { add_action( 'wpmm_before_scripts', array( $this, 'add_bot_extras' ) ); add_action( 'wpmm_footer', array( $this, 'add_js_files' ) ); } else { - // make maintenance page private when maintenance mode is disabled + // restore the maintenance page to its original state when maintenance mode is disabled add_action( 'init', function() { if ( ! isset( $this->plugin_settings['design']['page_id'] ) ) { return; } - if ( get_post_status( $this->plugin_settings['design']['page_id'] ) === 'publish' ) { - wp_update_post( - array( - 'ID' => $this->plugin_settings['design']['page_id'], - 'post_status' => 'private', - ) - ); - } + wpmm_restore_page_state( (int) $this->plugin_settings['design']['page_id'] ); } ); } @@ -671,6 +675,12 @@ public static function single_activate( $network_wide = false ) { * @since 2.0.0 */ public static function single_deactivate() { + // give the selected page back to the site in its original state + $settings = get_option( 'wpmm_settings' ); + if ( ! empty( $settings['design']['page_id'] ) ) { + wpmm_restore_page_state( (int) $settings['design']['page_id'] ); + } + wpmm_delete_cache(); } diff --git a/includes/functions/helpers.php b/includes/functions/helpers.php index 40a2dbc..8e31af5 100644 --- a/includes/functions/helpers.php +++ b/includes/functions/helpers.php @@ -513,6 +513,73 @@ function sanitize_hex_color( $color ) { } } +/** + * Record the current state of the selected maintenance page so it can be + * restored when maintenance mode is disabled or the plugin is deactivated. + * + * A previously recorded state is never overwritten: the page may already be + * carrying plugin-made changes, and recording those would poison the original + * state. The record is cleared when the page is restored. + * + * @since 2.6.23 + * @param int $page_id page ID + */ +function wpmm_record_page_state( $page_id ) { + $status = get_post_status( $page_id ); + + if ( ! $status ) { + return; + } + + $template = get_post_meta( $page_id, '_wp_page_template', true ); + + // the unique flag makes the status key a first-write-wins claim on the + // record, so a racing request cannot save the plugin-modified state + if ( ! add_post_meta( $page_id, '_wpmm_original_status', $status, true ) ) { + return; + } + + update_post_meta( $page_id, '_wpmm_original_template', $template ); +} + +/** + * Restore the selected maintenance page to its recorded original state. + * Pages without a recorded state are left untouched. + * + * @since 2.6.23 + * @param int $page_id page ID + */ +function wpmm_restore_page_state( $page_id ) { + $original_status = get_post_meta( $page_id, '_wpmm_original_status', true ); + + if ( empty( $original_status ) || ! get_post( $page_id ) ) { + return; + } + + // a trashed page reflects user intent; the plugin never trashes, so keep the + // status but still hand back the template and clear the record below + if ( get_post_status( $page_id ) !== 'trash' && get_post_status( $page_id ) !== $original_status ) { + wp_update_post( + array( + 'ID' => $page_id, + 'post_status' => $original_status, + ) + ); + } + + $original_template = get_post_meta( $page_id, '_wpmm_original_template', true ); + + if ( empty( $original_template ) ) { + delete_post_meta( $page_id, '_wp_page_template' ); + } else { + update_post_meta( $page_id, '_wp_page_template', $original_template ); + } + + // clear the record so the next take-over captures the page's state afresh + delete_post_meta( $page_id, '_wpmm_original_status' ); + delete_post_meta( $page_id, '_wpmm_original_template' ); +} + /** * Get option page URL. */ diff --git a/tests/page-state-test.php b/tests/page-state-test.php new file mode 100644 index 0000000..a3b78ac --- /dev/null +++ b/tests/page-state-test.php @@ -0,0 +1,479 @@ +setAccessible( true ); + $instance->setValue( null, null ); + + return WP_Maintenance_Mode::get_instance(); + } + + /** + * Minimal settings array for the code paths under test. + * + * @param int $status Maintenance mode status (0/1). + * @param int $page_id Selected page ID. + * @return array + */ + private function make_settings( $status, $page_id ) { + $settings = WP_Maintenance_Mode::get_instance()->default_settings(); + + $settings['general']['status'] = $status; + $settings['design']['page_id'] = $page_id; + + return $settings; + } + + public function test_disabled_mode_leaves_selected_published_page_public() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); + + $this->assertSame( 'publish', get_post_status( $page_id ) ); + } + + public function test_enable_then_disable_returns_private_page_to_private() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'private', + ) + ); + + // Enabling maintenance mode publishes the private page so it can be served. + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + $this->assertSame( 'publish', get_post_status( $page_id ) ); + + // Disabling it puts the page back to private, not just any page to private. + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); + $this->assertSame( 'private', get_post_status( $page_id ) ); + } + + public function test_user_page_template_is_restored_on_disable_and_reapplied_on_enable() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'custom-template.php' ); + + // What select_page() does when the user picks an existing page. + wpmm_record_page_state( $page_id ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + + // Disabling maintenance mode restores the page's own template and status. + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); + $this->assertSame( 'custom-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + $this->assertSame( 'publish', get_post_status( $page_id ) ); + + // Re-enabling applies the maintenance template again. + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + $this->assertSame( 'templates/wpmm-page-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + } + + /** + * Boot a fresh admin instance and simulate the insert-template AJAX call. + * + * @param array $settings Value for the wpmm_settings option. + * @return void + */ + private function apply_template( $settings, $template_slug = 'coming-soon-1', $category = 'coming-soon' ) { + $this->boot_plugin( $settings ); + + if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { + require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; + } + + $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); + $instance->setAccessible( true ); + $instance->setValue( null, null ); + + $admin = WP_Maintenance_Mode_Admin::get_instance(); + $admin->load_default_settings(); + + $_POST = array( + '_wpnonce' => wp_create_nonce( 'tab-design' ), + 'source' => 'tab-design', + 'template_slug' => $template_slug, + 'category' => $category, + ); + + $die_handler = function () { + return function () { + throw new WPDieException( 'json response sent' ); + }; + }; + // Outside of admin-ajax.php, wp_send_json_*() ends with a bare `die`; + // claiming AJAX context routes it through a catchable die handler instead. + add_filter( 'wp_doing_ajax', '__return_true' ); + add_filter( 'wp_die_ajax_handler', $die_handler ); + + ob_start(); + try { + $admin->insert_template(); + } catch ( WPDieException $e ) { + // wp_send_json_*() ends the request with wp_die(); expected. + } + ob_end_clean(); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + remove_filter( 'wp_die_ajax_handler', $die_handler ); + $_POST = array(); + } + + /** + * Simulate the select-page AJAX call the Design tab dropdown fires. + * + * @param array $settings Value for the wpmm_settings option. + * @param int $page_id Page being selected. + * @return void + */ + private function select_page( $settings, $page_id ) { + $this->boot_plugin( $settings ); + + if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { + require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; + } + + $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); + $instance->setAccessible( true ); + $instance->setValue( null, null ); + + $admin = WP_Maintenance_Mode_Admin::get_instance(); + $admin->load_default_settings(); + + $_POST = array( + '_wpnonce' => wp_create_nonce( 'tab-design' ), + 'page_id' => (string) $page_id, + ); + + $die_handler = function () { + return function () { + throw new WPDieException( 'json response sent' ); + }; + }; + add_filter( 'wp_doing_ajax', '__return_true' ); + add_filter( 'wp_die_ajax_handler', $die_handler ); + + ob_start(); + try { + $admin->select_page(); + } catch ( WPDieException $e ) { + // wp_send_json_*() ends the request with wp_die(); expected. + } + ob_end_clean(); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + remove_filter( 'wp_die_ajax_handler', $die_handler ); + $_POST = array(); + } + + public function test_select_page_workflow_leaves_selected_homepage_public() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => 'Real homepage content.', + ) + ); + + // 1. The user selects their existing homepage in Design > Select page. + $this->select_page( $this->make_settings( 0, 0 ), $page_id ); + + $settings = get_option( 'wpmm_settings' ); + $this->assertSame( $page_id, (int) $settings['design']['page_id'] ); + + // 2. Maintenance mode is enabled... + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + + // 3. ...then disabled, and another request runs the init callback. + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); + + // The homepage is publicly available in its original state. + $this->assertSame( 'publish', get_post_status( $page_id ) ); + $this->assertSame( 'Real homepage content.', get_post( $page_id )->post_content ); + $this->assertSame( '', get_post_meta( $page_id, '_wp_page_template', true ) ); + } + + public function test_changing_selection_restores_the_previously_selected_page() { + $first_page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + update_post_meta( $first_page_id, '_wp_page_template', 'custom-template.php' ); + + $second_page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + $this->select_page( $this->make_settings( 0, 0 ), $first_page_id ); + $this->assertSame( 'templates/wpmm-page-template.php', get_post_meta( $first_page_id, '_wp_page_template', true ) ); + + $this->select_page( $this->make_settings( 0, $first_page_id ), $second_page_id ); + + // The first page is handed back with its own template, and its record is cleared. + $this->assertSame( 'custom-template.php', get_post_meta( $first_page_id, '_wp_page_template', true ) ); + $this->assertFalse( metadata_exists( 'post', $first_page_id, '_wpmm_original_status' ) ); + + $settings = get_option( 'wpmm_settings' ); + $this->assertSame( $second_page_id, (int) $settings['design']['page_id'] ); + } + + public function test_clearing_selection_restores_the_previously_selected_page() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'private', + ) + ); + + $this->select_page( $this->make_settings( 0, 0 ), $page_id ); + + // Simulate the state an active maintenance mode leaves the page in. + wp_publish_post( $page_id ); + + $this->select_page( $this->make_settings( 0, $page_id ), 0 ); + + $this->assertSame( 'private', get_post_status( $page_id ) ); + $this->assertFalse( metadata_exists( 'post', $page_id, '_wpmm_original_status' ) ); + } + + public function test_restoring_a_trashed_page_keeps_trash_but_clears_template_and_record() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'custom-template.php' ); + + wpmm_record_page_state( $page_id ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + + // The user deliberately trashes the page, then the plugin hands it back. + wp_trash_post( $page_id ); + wpmm_restore_page_state( $page_id ); + + // The status is untouched, but the page no longer carries plugin state. + $this->assertSame( 'trash', get_post_status( $page_id ) ); + $this->assertSame( 'custom-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + $this->assertFalse( metadata_exists( 'post', $page_id, '_wpmm_original_status' ) ); + $this->assertFalse( metadata_exists( 'post', $page_id, '_wpmm_original_template' ) ); + } + + public function test_applying_template_to_user_page_creates_new_page_instead_of_overwriting() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => 'My real homepage content.', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'custom-template.php' ); + + // What select_page() does when the user picks an existing page. + wpmm_record_page_state( $page_id ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + + $this->apply_template( $this->make_settings( 0, $page_id ) ); + + // The user's page is handed back untouched. + $this->assertSame( 'publish', get_post_status( $page_id ) ); + $this->assertSame( 'My real homepage content.', get_post( $page_id )->post_content ); + $this->assertSame( 'custom-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + + // The template landed on a newly created, plugin-owned page instead. + $settings = get_option( 'wpmm_settings' ); + $new_page_id = (int) $settings['design']['page_id']; + + $this->assertNotSame( $page_id, $new_page_id ); + $this->assertSame( 'private', get_post_status( $new_page_id ) ); + $this->assertNotEmpty( get_post_meta( $new_page_id, '_wpmm_generated', true ) ); + $this->assertNotEmpty( get_post( $new_page_id )->post_content ); + } + + public function test_applying_template_to_generated_page_updates_it_in_place() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'private', + ) + ); + update_post_meta( $page_id, '_wpmm_generated', 1 ); + + $this->apply_template( $this->make_settings( 0, $page_id ) ); + + $settings = get_option( 'wpmm_settings' ); + + $this->assertSame( $page_id, (int) $settings['design']['page_id'] ); + $this->assertNotEmpty( get_post( $page_id )->post_content ); + } + + public function test_applying_unknown_template_is_rejected() { + $pages_before = count( get_posts( array( 'post_type' => 'page', 'post_status' => 'any', 'numberposts' => -1 ) ) ); + + $this->apply_template( $this->make_settings( 0, 0 ), '../../nonexistent', 'coming-soon' ); + + $settings = get_option( 'wpmm_settings' ); + $pages_after = count( get_posts( array( 'post_type' => 'page', 'post_status' => 'any', 'numberposts' => -1 ) ) ); + + $this->assertSame( 0, (int) $settings['design']['page_id'], 'selection must not change' ); + $this->assertSame( $pages_before, $pages_after, 'no page must be created' ); + } + + public function test_enabled_mode_with_deleted_page_writes_nothing() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + wp_delete_post( $page_id, true ); + + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + + $this->assertFalse( metadata_exists( 'post', $page_id, '_wp_page_template' ) ); + $this->assertFalse( metadata_exists( 'post', $page_id, '_wpmm_original_status' ) ); + } + + public function test_trashed_page_is_not_resurrected_on_disable() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + wpmm_record_page_state( $page_id ); + + // The user deliberately trashes the page while it is selected. + wp_trash_post( $page_id ); + + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); + + $this->assertSame( 'trash', get_post_status( $page_id ) ); + } + + public function test_enabled_mode_with_trashed_page_records_nothing() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + wp_trash_post( $page_id ); + + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + + $this->assertFalse( metadata_exists( 'post', $page_id, '_wpmm_original_status' ) ); + } + + public function test_recording_twice_does_not_poison_the_original_state() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'custom-template.php' ); + + wpmm_record_page_state( $page_id ); + + // The plugin takes the page over... + wp_update_post( + array( + 'ID' => $page_id, + 'post_status' => 'private', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + + // ...and the user re-selects the same page while it is in that state. + wpmm_record_page_state( $page_id ); + + wpmm_restore_page_state( $page_id ); + + $this->assertSame( 'publish', get_post_status( $page_id ) ); + $this->assertSame( 'custom-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + } + + public function test_page_edits_after_restore_become_the_new_baseline() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + update_post_meta( $page_id, '_wp_page_template', 'custom-template.php' ); + + // First take-over / hand-back cycle. + wpmm_record_page_state( $page_id ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + wpmm_restore_page_state( $page_id ); + + // The user changes the page's template, then the plugin takes it over again. + update_post_meta( $page_id, '_wp_page_template', 'new-template.php' ); + wpmm_record_page_state( $page_id ); + update_post_meta( $page_id, '_wp_page_template', 'templates/wpmm-page-template.php' ); + wpmm_restore_page_state( $page_id ); + + $this->assertSame( 'new-template.php', get_post_meta( $page_id, '_wp_page_template', true ) ); + } + + public function test_plugin_deactivation_restores_selected_page() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + update_option( 'wpmm_settings', $this->make_settings( 0, $page_id ) ); + wpmm_record_page_state( $page_id ); + + // Simulate the state a broken site is in: selected page left private. + wp_update_post( + array( + 'ID' => $page_id, + 'post_status' => 'private', + ) + ); + + WP_Maintenance_Mode::single_deactivate(); + + $this->assertSame( 'publish', get_post_status( $page_id ) ); + } +} diff --git a/views/settings.php b/views/settings.php index 6c53766..6260401 100644 --- a/views/settings.php +++ b/views/settings.php @@ -297,7 +297,8 @@ $will_replace = isset( $this->plugin_settings['design']['page_id'] ) && ! ( ! get_post( $this->plugin_settings['design']['page_id'] ) || empty( trim( get_post( $this->plugin_settings['design']['page_id'] )->post_content ) ) || - get_post( $this->plugin_settings['design']['page_id'] )->post_status === 'trash' ); + get_post( $this->plugin_settings['design']['page_id'] )->post_status === 'trash' ) && + get_post_meta( (int) $this->plugin_settings['design']['page_id'], '_wpmm_generated', true ); foreach ( $categories as $category => $label ) { $templates = list_files( WPMM_TEMPLATES_PATH . $category . '/', 1 );