diff --git a/css/customize-snapshots-preview.css b/css/customize-snapshots-preview.css new file mode 100644 index 00000000..0fc5951f --- /dev/null +++ b/css/customize-snapshots-preview.css @@ -0,0 +1,8 @@ +form[method="post"] button[type="submit"], +form[method="post"] button[type=""], +form[method="post"] input[type="submit"] { + cursor: not-allowed; +} +form[method="post"] button:not([type]) { + cursor: not-allowed; +} diff --git a/customize-snapshots.php b/customize-snapshots.php index 79008c40..9ab41359 100644 --- a/customize-snapshots.php +++ b/customize-snapshots.php @@ -44,7 +44,7 @@ * Admin notice for incompatible versions of PHP. */ function customize_snapshots_php_version_error() { - printf( '

%s

', customize_snapshots_php_version_text() ); + printf( '

%s

', customize_snapshots_php_version_text() ); // WPCS: XSS OK. } /** diff --git a/instance.php b/instance.php index 38928caf..d1c8fa65 100644 --- a/instance.php +++ b/instance.php @@ -23,3 +23,31 @@ function get_plugin_instance() { global $customize_snapshots_plugin; return $customize_snapshots_plugin; } + +/** + * Convenience function for whether settings are being previewed. + * + * @see Customize_Snapshot_Manager::is_previewing_settings() + * @see Customize_Snapshot_Manager::preview_snapshot_settings() + * + * @return bool Whether previewing settings. + */ +function is_previewing_settings() { + return get_plugin_instance()->customize_snapshot_manager->is_previewing_settings(); +} + +/** + * Convenience function to get the current snapshot UUID. + * + * @see Customize_Snapshot_Manager::$current_snapshot_uuid + * + * @return string|null The current snapshot UUID or null if no snapshot. + */ +function current_snapshot_uuid() { + $customize_snapshot_uuid = get_plugin_instance()->customize_snapshot_manager->current_snapshot_uuid; + if ( empty( $customize_snapshot_uuid ) ) { + return null; + } else { + return $customize_snapshot_uuid; + } +} diff --git a/js/customize-snapshots-frontend.js b/js/customize-snapshots-frontend.js new file mode 100644 index 00000000..aa3abbaa --- /dev/null +++ b/js/customize-snapshots-frontend.js @@ -0,0 +1,335 @@ +/* global jQuery, confirm */ +/* exported CustomizeSnapshotsFrontend */ +/* eslint consistent-this: [ "error", "section" ], no-magic-numbers: [ "error", { "ignore": [-1,0,1] } ] */ +/* eslint-disable no-alert */ + +/* + * The code here is derived from the initial Transactions pull request: https://github.com/xwp/wordpress-develop/pull/61 + * See https://github.com/xwp/wordpress-develop/blob/97fd5019c488a0713d34b517bdbff67c62c48a5d/src/wp-includes/js/customize-preview.js#L98-L111 + */ + +var CustomizeSnapshotsFrontend = ( function( $ ) { + 'use strict'; + + var component = { + data: { + uuid: '', + home_url: { + scheme: '', + host: '', + path: '' + }, + l10n: { + restoreSessionPrompt: '' + } + } + }; + + /** + * Init. + * + * @param {object} args Args. + * @param {string} args.uuid UUID. + * @returns {void} + */ + component.init = function init( args ) { + _.extend( component.data, args ); + + component.hasSessionStorage = 'undefined' !== typeof sessionStorage; + + component.keepSessionAlive(); + component.rememberSessionSnapshot(); + component.injectSnapshotIntoLinks(); + component.handleExitSnapshotSessionLink(); + component.injectSnapshotIntoAjaxRequests(); + component.injectSnapshotIntoForms(); + }; + + /** + * Prompt to restore session. + * + * @returns {void} + */ + component.keepSessionAlive = function keepSessionAlive() { + var currentSnapshotUuid, urlParser, adminBarItem; + if ( ! component.hasSessionStorage ) { + return; + } + currentSnapshotUuid = sessionStorage.getItem( 'customize_snapshot_uuid' ); + if ( ! currentSnapshotUuid || component.data.uuid ) { + return; + } + + urlParser = document.createElement( 'a' ); + urlParser.href = location.href; + if ( urlParser.search.length > 1 ) { + urlParser.search += '&'; + } + urlParser.search += 'customize_snapshot_uuid=' + encodeURIComponent( sessionStorage.getItem( 'customize_snapshot_uuid' ) ); + + $( function() { + adminBarItem = $( '#wp-admin-bar-resume-customize-snapshot' ); + if ( adminBarItem.length ) { + adminBarItem.find( '> a' ).prop( 'href', urlParser.href ); + adminBarItem.show(); + } else if ( confirm( component.data.l10n.restoreSessionPrompt ) ) { + location.replace( urlParser.href ); + } else { + sessionStorage.removeItem( 'customize_snapshot_uuid' ); + } + } ); + }; + + /** + * Remember the session's snapshot. + * + * Persist the snapshot UUID in session storage so that we can prompt to restore the snapshot query param if inadvertently dropped. + * + * @returns {void} + */ + component.rememberSessionSnapshot = function rememberSessionSnapshot() { + if ( ! component.hasSessionStorage || ! component.data.uuid ) { + return; + } + sessionStorage.setItem( 'customize_snapshot_uuid', component.data.uuid ); + }; + + /** + * Inject the snapshot UUID into links in the document. + * + * @returns {void} + */ + component.injectSnapshotIntoLinks = function injectSnapshotIntoLinks() { + var linkSelectors = 'a, area'; + + if ( ! component.data.uuid ) { + return; + } + $( function() { + + // Inject links into initial document. + $( document.body ).find( linkSelectors ).each( function() { + component.injectSnapshotLinkParam( this ); + } ); + + // Inject links for new elements added to the page + if ( 'undefined' !== typeof MutationObserver ) { + component.mutationObserver = new MutationObserver( function( mutations ) { + _.each( mutations, function( mutation ) { + $( mutation.target ).find( linkSelectors ).each( function() { + component.injectSnapshotLinkParam( this ); + } ); + } ); + } ); + component.mutationObserver.observe( document.documentElement, { + childList: true, + subtree: true + } ); + } else { + + // If mutation observers aren't available, fallback to just-in-time injection. + $( document.documentElement ).on( 'click focus mouseover', linkSelectors, function() { + component.injectSnapshotLinkParam( this ); + } ); + } + } ); + }; + + /** + * Is matching base URL (host and path)? + * + * @param {HTMLAnchorElement} parsedUrl Parsed URL. + * @param {string} parsedUrl.hostname Host. + * @param {string} parsedUrl.pathname Path. + * @returns {boolean} Whether matched. + */ + component.isMatchingBaseUrl = function isMatchingBaseUrl( parsedUrl ) { + return parsedUrl.hostname === component.data.home_url.host && 0 === parsedUrl.pathname.indexOf( component.data.home_url.path ); + }; + + /** + * Should the supplied link have a snapshot UUID added (or does it have one already)? + * + * @param {HTMLAnchorElement|HTMLAreaElement} element Link element. + * @param {string} element.search Query string. + * @param {string} element.pathname Path. + * @param {string} element.hostname Hostname. + * @returns {boolean} Is appropriate for snapshot link. + */ + component.shouldLinkHaveSnapshotParam = function shouldLinkHaveSnapshotParam( element ) { + if ( ! component.isMatchingBaseUrl( element ) ) { + return false; + } + + // Skip wp login and signup pages. + if ( /\/wp-(login|signup)\.php$/.test( element.pathname ) ) { + return false; + } + + // Allow links to admin ajax as faux frontend URLs. + if ( /\/wp-admin\/admin-ajax\.php$/.test( element.pathname ) ) { + return true; + } + + // Disallow links to admin. + if ( /\/wp-admin(\/|$)/.test( element.pathname ) ) { + return false; + } + + // Skip links in admin bar. + if ( $( element ).closest( '#wpadminbar' ).length ) { + return false; + } + + return true; + }; + + /** + * Return whether the supplied link element has the snapshot query param. + * + * @param {HTMLAnchorElement|HTMLAreaElement} element Link element. + * @param {object} element.search Query string. + * @returns {boolean} Whether query param is present. + */ + component.doesLinkHaveSnapshotQueryParam = function( element ) { + return /(^|&)customize_snapshot_uuid=/.test( element.search.substr( 1 ) ); + }; + + /** + * Inject the customize_snapshot_uuid query param into links on the frontend. + * + * @param {HTMLAnchorElement|HTMLAreaElement} element Link element. + * @param {object} element.search Query string. + * @returns {void} + */ + component.injectSnapshotLinkParam = function injectSnapshotLinkParam( element ) { + if ( component.doesLinkHaveSnapshotQueryParam( element ) || ! component.shouldLinkHaveSnapshotParam( element ) ) { + return; + } + + if ( element.search.length > 1 ) { + element.search += '&'; + } + element.search += 'customize_snapshot_uuid=' + encodeURIComponent( component.data.uuid ); + }; + + /** + * Handle electing to exit from the snapshot session. + * + * @returns {void} + */ + component.handleExitSnapshotSessionLink = function handleExitSnapshotSessionLink() { + $( function() { + if ( ! component.hasSessionStorage ) { + return; + } + $( '#wpadminbar' ).on( 'click', '#wp-admin-bar-exit-customize-snapshot', function() { + sessionStorage.removeItem( 'customize_snapshot_uuid' ); + } ); + } ); + }; + + /** + * Inject the snapshot UUID into Ajax requests. + * + * @return {void} + */ + component.injectSnapshotIntoAjaxRequests = function injectSnapshotIntoAjaxRequests() { + $.ajaxPrefilter( component.prefilterAjax ); + }; + + /** + * Rewrite Ajax requests to inject Customizer state. + * + * @param {object} options Options. + * @param {string} options.type Type. + * @param {string} options.url URL. + * @returns {void} + */ + component.prefilterAjax = function prefilterAjax( options ) { + var urlParser; + if ( ! component.data.uuid ) { + return; + } + + urlParser = document.createElement( 'a' ); + urlParser.href = options.url; + + // Abort if the request is not for this site. + if ( ! component.isMatchingBaseUrl( urlParser ) ) { + return; + } + + // Skip if snapshot UUID already in URL. + if ( -1 !== urlParser.search.indexOf( 'customize_snapshot_uuid=' + component.data.uuid ) ) { + return; + } + + if ( urlParser.search.substr( 1 ).length > 0 ) { + urlParser.search += '&'; + } + urlParser.search += 'customize_snapshot_uuid=' + component.data.uuid; + + options.url = urlParser.href; + }; + + /** + * Inject snapshot into forms, allowing preview to persist through submissions. + * + * @returns {void} + */ + component.injectSnapshotIntoForms = function injectSnapshotIntoForms() { + if ( ! component.data.uuid ) { + return; + } + $( function() { + + // Inject inputs for forms in initial document. + $( document.body ).find( 'form' ).each( function() { + component.injectSnapshotFormInput( this ); + } ); + + // Inject inputs for new forms added to the page. + if ( 'undefined' !== typeof MutationObserver ) { + component.mutationObserver = new MutationObserver( function( mutations ) { + _.each( mutations, function( mutation ) { + $( mutation.target ).find( 'form' ).each( function() { + component.injectSnapshotFormInput( this ); + } ); + } ); + } ); + component.mutationObserver.observe( document.documentElement, { + childList: true, + subtree: true + } ); + } + } ); + }; + + /** + * Inject snapshot into form inputs. + * + * @param {HTMLFormElement} form Form. + * @returns {void} + */ + component.injectSnapshotFormInput = function injectSnapshotFormInput( form ) { + var urlParser; + if ( $( form ).find( 'input[name=customize_snapshot_uuid]' ).length > 0 ) { + return; + } + urlParser = document.createElement( 'a' ); + urlParser.href = form.action; + if ( ! component.isMatchingBaseUrl( urlParser ) ) { + return; + } + + $( form ).prepend( $( '', { + type: 'hidden', + name: 'customize_snapshot_uuid', + value: component.data.uuid + } ) ); + }; + + return component; +} )( jQuery ); + diff --git a/js/customize-snapshots-preview.js b/js/customize-snapshots-preview.js new file mode 100644 index 00000000..80a8f18e --- /dev/null +++ b/js/customize-snapshots-preview.js @@ -0,0 +1,201 @@ +/* global jQuery, JSON */ +/* exported CustomizeSnapshotsPreview */ +/* eslint consistent-this: [ "error", "section" ], no-magic-numbers: [ "error", { "ignore": [-1,0,1] } ] */ + +/* + * The code here is derived from Customize REST Resources: https://github.com/xwp/wp-customize-rest-resources + */ + +var CustomizeSnapshotsPreview = (function( api, $ ) { + 'use strict'; + + var component = { + data: { + home_url: { + scheme: '', + host: '', + path: '' + }, + rest_api_url: { + scheme: '', + host: '', + path: '' + }, + admin_ajax_url: { + scheme: '', + host: '', + path: '' + }, + initial_dirty_settings: [] + } + }; + + /** + * Init. + * + * @param {object} args Args. + * @param {string} args.uuid UUID. + * @returns {void} + */ + component.init = function init( args ) { + _.extend( component.data, args ); + + component.injectSnapshotIntoAjaxRequests(); + component.handleFormSubmissions(); + }; + + /** + * Get customize query vars. + * + * @see wp.customize.previewer.query + * + * @returns {{ + * customized: string, + * nonce: string, + * wp_customize: string, + * theme: string + * }} Query vars. + */ + component.getCustomizeQueryVars = function getCustomizeQueryVars() { + var customized = {}; + api.each( function( setting ) { + if ( setting._dirty || -1 !== _.indexOf( component.data.initial_dirty_settings, setting.id ) ) { + customized[ setting.id ] = setting.get(); + } + } ); + return { + wp_customize: 'on', + theme: api.settings.theme.stylesheet, + nonce: api.settings.nonce.preview, + customized: JSON.stringify( customized ) + }; + }; + + /** + * Inject the snapshot UUID into Ajax requests. + * + * @return {void} + */ + component.injectSnapshotIntoAjaxRequests = function injectSnapshotIntoAjaxRequests() { + $.ajaxPrefilter( component.prefilterAjax ); + }; + + /** + * Rewrite Ajax requests to inject Customizer state. + * + * This will not work 100% of the time, such as if an Admin Ajax handler is + * specifically looking for a $_GET param vs a $_POST param. + * + * @param {object} options Options. + * @param {string} options.type Type. + * @param {string} options.url URL. + * @param {object} originalOptions Original options. + * @param {XMLHttpRequest} xhr XHR. + * @returns {void} + */ + component.prefilterAjax = function prefilterAjax( options, originalOptions, xhr ) { + var requestMethod, urlParser, queryVars, isMatchingHomeUrl, isMatchingRestUrl, isMatchingAdminAjaxUrl; + + urlParser = document.createElement( 'a' ); + urlParser.href = options.url; + + isMatchingHomeUrl = urlParser.host === component.data.home_url.host && 0 === urlParser.pathname.indexOf( component.data.home_url.path ); + isMatchingRestUrl = urlParser.host === component.data.rest_api_url.host && 0 === urlParser.pathname.indexOf( component.data.rest_api_url.path ); + isMatchingAdminAjaxUrl = urlParser.host === component.data.admin_ajax_url.host && 0 === urlParser.pathname.indexOf( component.data.admin_ajax_url.path ); + + if ( ! isMatchingHomeUrl && ! isMatchingRestUrl && ! isMatchingAdminAjaxUrl ) { + return; + } + + requestMethod = options.type.toUpperCase(); + + // Customizer currently requires POST requests, so use override (force Backbone.emulateHTTP). + if ( 'POST' !== requestMethod ) { + xhr.setRequestHeader( 'X-HTTP-Method-Override', requestMethod ); + options.type = 'POST'; + } + + if ( options.data && 'GET' === requestMethod ) { + /* + * Make sure the query vars for the REST API persist in GET (since + * REST API explicitly look at $_GET['filter']). + * We have to make sure the REST query vars are added as GET params + * when the method is GET as otherwise they won't be parsed properly. + * The issue lies in \WP_REST_Request::get_parameter_order() which + * only is looking at \WP_REST_Request::$method instead of $_SERVER['REQUEST_METHOD']. + * @todo Improve \WP_REST_Request::get_parameter_order() to be more aware of X-HTTP-Method-Override + */ + if ( urlParser.search.substr( 1 ).length > 1 ) { + urlParser.search += '&'; + } + urlParser.search += options.data; + } + + // Add Customizer post data. + if ( options.data ) { + options.data += '&'; + } else { + options.data = ''; + } + queryVars = component.getCustomizeQueryVars(); + queryVars.wp_customize_preview_ajax = 'true'; + options.data += $.param( queryVars ); + }; + + /** + * Handle form submissions. + * + * This fixes Core ticket {@link https://core.trac.wordpress.org/ticket/20714|#20714: Theme customizer: Impossible to preview a search results page} + * Implements todo in {@link https://github.com/xwp/wordpress-develop/blob/4.5.3/src/wp-includes/js/customize-preview.js#L69-L73} + * + * @returns {void} + */ + component.handleFormSubmissions = function handleFormSubmissions() { + $( function() { + + // Defer so that we can be sure that our event handler will come after any other event handlers. + _.defer( function() { + component.replaceFormSubmitHandler(); + } ); + } ); + }; + + /** + * Replace form submit handler. + * + * @returns {void} + */ + component.replaceFormSubmitHandler = function replaceFormSubmitHandler() { + var body = $( document.body ); + body.off( 'submit.preview' ); + body.on( 'submit.preview', 'form', function( event ) { + var urlParser; + + /* + * If the default wasn't prevented already (in which case the form + * submission is already being handled by JS), and if it has a GET + * request method, then take the serialized form data and add it as + * a query string to the action URL and send this in a url message + * to the Customizer pane so that it will be loaded. If the form's + * action points to a non-previewable URL, the the Customizer pane's + * previewUrl setter will reject it so that the form submission is + * a no-op, which is the same behavior as when clicking a link to an + * external site in the preview. + */ + if ( ! event.isDefaultPrevented() && 'GET' === this.method.toUpperCase() ) { + urlParser = document.createElement( 'a' ); + urlParser.href = this.action; + if ( urlParser.search.substr( 1 ).length > 1 ) { + urlParser.search += '&'; + } + urlParser.search += $( this ).serialize(); + api.preview.send( 'url', urlParser.href ); + } + + // Now preventDefault as is done on the normal submit.preview handler in customize-preview.js. + event.preventDefault(); + }); + }; + + return component; +} )( wp.customize, jQuery ); diff --git a/php/class-customize-snapshot-manager.php b/php/class-customize-snapshot-manager.php index bf854973..80640577 100644 --- a/php/class-customize-snapshot-manager.php +++ b/php/class-customize-snapshot-manager.php @@ -61,6 +61,13 @@ class Customize_Snapshot_Manager { */ public $current_snapshot_uuid; + /** + * Whether the snapshot settings are being previewed. + * + * @var bool + */ + protected $previewing_settings = false; + /** * The originally active theme. * @@ -92,64 +99,152 @@ function init() { add_action( 'template_redirect', array( $this, 'show_theme_switch_error' ) ); + add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_controls_scripts' ) ); + add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) ); + add_action( 'customize_controls_init', array( $this, 'add_snapshot_uuid_to_return_url' ) ); - add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_templates' ) ); add_action( 'customize_save', array( $this, 'check_customize_publish_authorization' ), 10, 0 ); add_filter( 'customize_refresh_nonces', array( $this, 'filter_customize_refresh_nonces' ) ); add_action( 'admin_bar_menu', array( $this, 'customize_menu' ), 41 ); + add_action( 'admin_bar_menu', array( $this, 'remove_all_non_snapshot_admin_bar_links' ), 100000 ); + add_action( 'wp_before_admin_bar_render', array( $this, 'print_admin_bar_styles' ) ); add_filter( 'wp_insert_post_data', array( $this, 'prepare_snapshot_post_content_for_publish' ) ); add_action( 'customize_save_after', array( $this, 'publish_snapshot_with_customize_save_after' ) ); add_action( 'transition_post_status', array( $this, 'save_settings_with_publish_snapshot' ), 10, 3 ); + add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'handle_update_snapshot_request' ) ); + if ( $this->read_current_snapshot_uuid() ) { + $this->load_snapshot(); + } elseif ( is_customize_preview() && isset( $_REQUEST['wp_customize_preview_ajax'] ) && 'true' === $_REQUEST['wp_customize_preview_ajax'] ) { + add_action( 'wp_loaded', array( $this, 'setup_preview_ajax_requests' ), 12 ); + } + } + + /** + * Read the current snapshot UUID from the request. + * + * @returns bool Whether a valid snapshot was read. + */ + public function read_current_snapshot_uuid() { if ( isset( $_REQUEST['customize_snapshot_uuid'] ) ) { // WPCS: input var ok. $uuid = sanitize_key( wp_unslash( $_REQUEST['customize_snapshot_uuid'] ) ); // WPCS: input var ok. if ( static::is_valid_uuid( $uuid ) ) { $this->current_snapshot_uuid = $uuid; + return true; } } + $this->current_snapshot_uuid = null; + return false; + } - if ( $this->current_snapshot_uuid ) { - $this->ensure_customize_manager(); + /** + * Load snapshot. + */ + public function load_snapshot() { + $this->ensure_customize_manager(); + $this->snapshot = new Customize_Snapshot( $this, $this->current_snapshot_uuid ); - add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'handle_update_snapshot_request' ) ); + if ( ! $this->should_import_and_preview_snapshot( $this->snapshot ) ) { + return; + } - $this->snapshot = new Customize_Snapshot( $this, $this->current_snapshot_uuid ); + $this->add_widget_setting_preview_filters(); + $this->add_nav_menu_setting_preview_filters(); - if ( true === $this->should_import_and_preview_snapshot( $this->snapshot ) ) { + /* + * Populate post values. + * + * Note we have to defer until setup_theme since the transaction + * can be set beforehand, and wp_magic_quotes() would not have + * been called yet, resulting in a $_POST['customized'] that is + * double-escaped. Note that this happens at priority 1, which + * is immediately after Customize_Snapshot_Manager::store_customized_post_data + * which happens at setup_theme priority 0, so that the initial + * POST data can be preserved. + */ + if ( did_action( 'setup_theme' ) ) { + $this->import_snapshot_data(); + } else { + add_action( 'setup_theme', array( $this, 'import_snapshot_data' ) ); + } - $this->add_widget_setting_preview_filters(); - $this->add_nav_menu_setting_preview_filters(); + // Block the robots. + add_action( 'wp_head', 'wp_no_robots' ); - /* - * Populate post values. - * - * Note we have to defer until setup_theme since the transaction - * can be set beforehand, and wp_magic_quotes() would not have - * been called yet, resulting in a $_POST['customized'] that is - * double-escaped. Note that this happens at priority 1, which - * is immediately after Customize_Snapshot_Manager::store_customized_post_data - * which happens at setup_theme priority 0, so that the initial - * POST data can be preserved. - */ - if ( did_action( 'setup_theme' ) ) { - $this->import_snapshot_data(); - } else { - add_action( 'setup_theme', array( $this, 'import_snapshot_data' ) ); - } + // Preview post values. + if ( did_action( 'wp_loaded' ) ) { + $this->preview_snapshot_settings(); + } else { + add_action( 'wp_loaded', array( $this, 'preview_snapshot_settings' ), 11 ); + } + } - // Block the robots. - add_action( 'wp_head', 'wp_no_robots' ); + /** + * Setup previewing of Ajax requests in the Customizer preview. + * + * @global \WP_Customize_Manager $wp_customize + */ + public function setup_preview_ajax_requests() { + global $wp_customize, $pagenow; - // Preview post values. - if ( did_action( 'wp_loaded' ) ) { - $this->preview_snapshot_settings(); - } else { - add_action( 'wp_loaded', array( $this, 'preview_snapshot_settings' ), 11 ); - } - } + /* + * When making admin-ajax requests from the frontend, settings won't be + * previewed because is_admin() and the call to preview will be + * short-circuited in \WP_Customize_Manager::wp_loaded(). + */ + if ( ! did_action( 'customize_preview_init' ) ) { + $wp_customize->customize_preview_init(); } + + // Note that using $pagenow is easier to test vs DOING_AJAX. + if ( ! empty( $pagenow ) && 'admin-ajax.php' === $pagenow ) { + $this->override_request_method(); + } else { + add_action( 'parse_request', array( $this, 'override_request_method' ), 5 ); + } + + $wp_customize->remove_preview_signature(); + } + + /** + * Attempt to convert the current request environment into another environment. + * + * @global \WP $wp + * + * @return bool Whether the override was applied. + */ + public function override_request_method() { + global $wp; + + // Skip of X-HTTP-Method-Override request header is not present. + if ( ! isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { + return false; + } + + // Skip if REST API request since it has built-in support for overriding the request method. + if ( ! empty( $wp ) && ! empty( $wp->query_vars['rest_route'] ) ) { + return false; + } + + // Skip if the request method is not GET or POST, or the override is the same as the original. + $original_request_method = $_SERVER['REQUEST_METHOD']; + $override_request_method = strtoupper( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ); + if ( ! in_array( $override_request_method, array( 'GET', 'POST' ), true ) || $original_request_method === $override_request_method ) { + return false; + } + + // Convert a POST request into a GET request. + if ( 'GET' === $override_request_method && 'POST' === $original_request_method ) { + $_SERVER['REQUEST_METHOD'] = $override_request_method; + $_GET = array_merge( $_GET, $_POST ); + $_SERVER['QUERY_STRING'] = build_query( array_map( 'rawurlencode', wp_unslash( $_GET ) ) ); + return true; + } + + return false; } /** @@ -195,6 +290,12 @@ public function is_theme_active() { * @return true|\WP_Error Returns true if previewable, or `WP_Error` if cannot. */ public function should_import_and_preview_snapshot( Customize_Snapshot $snapshot ) { + global $pagenow; + + // Ignore if in the admin, but not Admin Ajax or Customizer. + if ( is_admin() && ! in_array( $pagenow, array( 'admin-ajax.php', 'customize.php' ), true ) ) { + return false; + } if ( is_wp_error( $this->get_theme_switch_error( $snapshot ) ) ) { return false; @@ -215,7 +316,7 @@ public function should_import_and_preview_snapshot( Customize_Snapshot $snapshot * Note that wp.customize.Snapshots.extendPreviewerQuery() will extend the * previewer data to include the current snapshot UUID. */ - if ( count( $this->customize_manager->unsanitized_post_values() ) > 0 ) { + if ( $this->customize_manager && count( $this->customize_manager->unsanitized_post_values() ) > 0 ) { return false; } @@ -262,6 +363,33 @@ function( $value ) { } } + /** + * Is previewing settings. + * + * Plugins and themes may currently only use `is_customize_preview()` to + * decide whether or not they can store a value in the object cache. For + * example, see `Twenty_Eleven_Ephemera_Widget::widget()`. However, when + * viewing a snapshot on the frontend, the `is_customize_preview()` method + * will return `false`. Plugins and themes that store values in the object + * cache must either skip doing this when `$this->previewing` is `true`, + * or include the `$this->current_snapshot_uuid` (`current_snapshot_uuid()`) + * in the cache key when it is `true`. Note that if the `customize_preview_init` action + * was done, this means that the settings have been previewed in the regular + * Customizer preview. + * + * @see Twenty_Eleven_Ephemera_Widget::widget() + * @see WP_Customize_Manager::is_previewing_settings() + * @see is_previewing_settings() + * @see current_snapshot_uuid()() + * @see WP_Customize_Manager::customize_preview_init() + * @see Customize_Snapshot_Manager::$previewing_settings + * + * @return bool Whether previewing settings. + */ + public function is_previewing_settings() { + return $this->previewing_settings || did_action( 'customize_preview_init' ); + } + /** * Preview the snapshot settings. * @@ -269,11 +397,10 @@ function( $value ) { * can look at whether the `customize_preview_init` action was done. */ public function preview_snapshot_settings() { - - // Short-circuit because if customize_preview_init happened, then all settings have been previewed. - if ( did_action( 'customize_preview_init' ) ) { + if ( $this->is_previewing_settings() ) { return; } + $this->previewing_settings = true; /* * Note that we need to preview the settings outside the Customizer preview @@ -403,17 +530,6 @@ public function preview_early_nav_menus_in_customizer() { } } - /** - * Get the current URL. - * - * @return string - */ - public function current_url() { - $http_host = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : parse_url( home_url(), PHP_URL_HOST ); // WPCS: input var ok; sanitization ok. - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/'; // WPCS: input var ok; sanitization ok. - return ( is_ssl() ? 'https://' : 'http://' ) . $http_host . $request_uri; - } - /** * Add snapshot UUID the Customizer return URL. * @@ -436,15 +552,6 @@ public function add_snapshot_uuid_to_return_url() { } } - /** - * Get the clean version of current URL. - * - * @return string - */ - public function remove_snapshot_uuid_from_current_url() { - return remove_query_arg( array( 'customize_snapshot_uuid' ), $this->current_url() ); - } - /** * Show the theme switch error if there is one. */ @@ -518,15 +625,15 @@ static public function encode_json( $value ) { * @action customize_controls_enqueue_scripts * @global \WP_Customize_Manager $wp_customize */ - public function enqueue_scripts() { + public function enqueue_controls_scripts() { // Prevent loading the Snapshot interface if the theme is not active. if ( ! $this->is_theme_active() ) { return; } - wp_enqueue_style( $this->plugin->slug ); - wp_enqueue_script( $this->plugin->slug ); + wp_enqueue_style( 'customize-snapshots' ); + wp_enqueue_script( 'customize-snapshots' ); if ( $this->snapshot ) { $post = $this->snapshot->post(); $this->override_post_date_default_data( $post ); @@ -568,6 +675,62 @@ public function enqueue_scripts() { ); } + /** + * Set up Customizer preview. + */ + public function customize_preview_init() { + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); + } + + /** + * Enqueue Customizer preview scripts. + * + * @global \WP_Customize_Manager $wp_customize + */ + public function enqueue_preview_scripts() { + global $wp_customize; + + $handle = 'customize-snapshots-preview'; + wp_enqueue_script( $handle ); + wp_enqueue_style( $handle ); + + $exports = array( + 'home_url' => wp_parse_url( home_url( '/' ) ), + 'rest_api_url' => wp_parse_url( rest_url( '/' ) ), + 'admin_ajax_url' => wp_parse_url( admin_url( 'admin-ajax.php' ) ), + 'initial_dirty_settings' => array_keys( $wp_customize->unsanitized_post_values() ), + ); + wp_add_inline_script( + $handle, + sprintf( 'CustomizeSnapshotsPreview.init( %s )', wp_json_encode( $exports ) ), + 'after' + ); + } + + /** + * Enqueue Customizer frontend scripts. + */ + public function enqueue_frontend_scripts() { + if ( ! $this->snapshot ) { + return; + } + $handle = 'customize-snapshots-frontend'; + wp_enqueue_script( $handle ); + + $exports = array( + 'uuid' => $this->snapshot ? $this->snapshot->uuid() : null, + 'home_url' => wp_parse_url( home_url( '/' ) ), + 'l10n' => array( + 'restoreSessionPrompt' => __( 'It seems you may have inadvertently navigated away from previewing a customized state. Would you like to restore the snapshot context?', 'customize-snapshots' ), + ), + ); + wp_add_inline_script( + $handle, + sprintf( 'CustomizeSnapshotsFrontend.init( %s )', wp_json_encode( $exports ) ), + 'after' + ); + } + /** * Include the snapshot nonce in the Customizer nonces. * @@ -1033,9 +1196,90 @@ static public function is_valid_uuid( $uuid ) { * @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. */ public function customize_menu( $wp_admin_bar ) { + add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' ); $this->replace_customize_link( $wp_admin_bar ); + $this->add_resume_snapshot_link( $wp_admin_bar ); $this->add_post_edit_screen_link( $wp_admin_bar ); - add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' ); + $this->add_snapshot_exit_link( $wp_admin_bar ); + } + + /** + * Print admin bar styles. + */ + public function print_admin_bar_styles() { + ?> + + snapshot ) ) { + return; + } + + $customize_node = $wp_admin_bar->get_node( 'customize' ); + if ( empty( $customize_node ) ) { + return; + } + + // Remove customize_snapshot_uuuid query param from url param to be previewed in Customizer. + $preview_url_query_params = array(); + $preview_url_parsed = wp_parse_url( $customize_node->href ); + parse_str( $preview_url_parsed['query'], $preview_url_query_params ); + if ( ! empty( $preview_url_query_params['url'] ) ) { + $preview_url_query_params['url'] = remove_query_arg( array( 'customize_snapshot_uuid' ), $preview_url_query_params['url'] ); + $customize_node->href = preg_replace( + '/(?<=\?).*?(?=#|$)/', + build_query( $preview_url_query_params ), + $customize_node->href + ); + } + + // Add customize_snapshot_uuid param as param to customize.php itself. + $customize_node->href = add_query_arg( + array( 'customize_snapshot_uuid' => $this->current_snapshot_uuid ), + $customize_node->href + ); + + $customize_node->meta['class'] .= ' ab-customize-snapshots-item'; + $wp_admin_bar->add_menu( (array) $customize_node ); + } + + /** + * Adds a link to resume snapshot previewing. + * + * @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. + */ + public function add_resume_snapshot_link( $wp_admin_bar ) { + $wp_admin_bar->add_menu( array( + 'id' => 'resume-customize-snapshot', + 'title' => __( 'Resume Snapshot Preview', 'customize-snapshots' ), + 'href' => '#', + 'meta' => array( + 'class' => 'ab-item ab-customize-snapshots-item', + ), + ) ); } /** @@ -1051,43 +1295,63 @@ public function add_post_edit_screen_link( $wp_admin_bar ) { if ( ! $post ) { return; } - $wp_admin_bar->add_node( array( - 'parent' => 'customize', - 'id' => 'snapshot-view-link', + $wp_admin_bar->add_menu( array( + 'id' => 'inspect-customize-snapshot', 'title' => __( 'Inspect Snapshot', 'customize-snapshots' ), 'href' => get_edit_post_link( $post->ID, 'raw' ), + 'meta' => array( + 'class' => 'ab-item ab-customize-snapshots-item', + ), ) ); } /** - * Replaces the "Customize" link in the Toolbar. + * Adds an "Exit Snapshot" link to the Toolbar when in Snapshot mode. * * @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. */ - public function replace_customize_link( $wp_admin_bar ) { - // Don't show for users who can't access the customizer or when in the admin. - if ( ! current_user_can( 'customize' ) || is_admin() ) { + public function add_snapshot_exit_link( $wp_admin_bar ) { + if ( ! $this->snapshot ) { return; } + $wp_admin_bar->add_menu( array( + 'id' => 'exit-customize-snapshot', + 'title' => __( 'Exit Snapshot Preview', 'customize-snapshots' ), + 'href' => remove_query_arg( 'customize_snapshot_uuid' ), + 'meta' => array( + 'class' => 'ab-item ab-customize-snapshots-item', + ), + ) ); + } - $args = array(); - if ( $this->current_snapshot_uuid ) { - $args['customize_snapshot_uuid'] = $this->current_snapshot_uuid; + /** + * Remove all admin bar nodes that have links and which aren't for snapshots. + * + * @param \WP_Admin_Bar $wp_admin_bar Admin bar. + */ + public function remove_all_non_snapshot_admin_bar_links( $wp_admin_bar ) { + if ( empty( $this->snapshot ) ) { + return; } + $snapshot_admin_bar_node_ids = array( 'customize', 'exit-customize-snapshot', 'inspect-customize-snapshot' ); + foreach ( $wp_admin_bar->get_nodes() as $node ) { + if ( in_array( $node->id, $snapshot_admin_bar_node_ids, true ) || '#' === substr( $node->href, 0, 1 ) ) { + continue; + } - $args['url'] = esc_url_raw( $this->remove_snapshot_uuid_from_current_url() ); - $customize_url = add_query_arg( array_map( 'rawurlencode', $args ), wp_customize_url() ); - - $wp_admin_bar->add_menu( - array( - 'id' => 'customize', - 'title' => __( 'Customize', 'customize-snapshots' ), - 'href' => $customize_url, - 'meta' => array( - 'class' => 'hide-if-no-customize', - ), - ) - ); + $parsed_link_url = wp_parse_url( $node->href ); + $parsed_home_url = wp_parse_url( home_url( '/' ) ); + $is_external_link = ( + isset( $parsed_link_url['host'] ) && $parsed_link_url['host'] !== $parsed_home_url['host'] + || + isset( $parsed_link_url['path'] ) && 0 !== strpos( $parsed_link_url['path'], $parsed_home_url['path'] ) + || + ( ! isset( $parsed_link_url['query'] ) || ! preg_match( '#(^|&)customize_snapshot_uuid=#', $parsed_link_url['query'] ) ) + ); + if ( $is_external_link ) { + $wp_admin_bar->remove_node( $node->id ); + } + } } /** diff --git a/php/class-plugin.php b/php/class-plugin.php index a2b543b7..0afb3bf6 100644 --- a/php/class-plugin.php +++ b/php/class-plugin.php @@ -65,9 +65,21 @@ public function init() { */ public function register_scripts( \WP_Scripts $wp_scripts ) { $min = ( SCRIPT_DEBUG ? '' : '.min' ); + + $handle = 'customize-snapshots'; $src = $this->dir_url . 'js/customize-snapshots' . $min . '.js'; $deps = array( 'jquery', 'jquery-ui-dialog', 'wp-util', 'customize-controls' ); - $wp_scripts->add( $this->slug, $src, $deps ); + $wp_scripts->add( $handle, $src, $deps ); + + $handle = 'customize-snapshots-preview'; + $src = $this->dir_url . 'js/customize-snapshots-preview' . $min . '.js'; + $deps = array( 'customize-preview' ); + $wp_scripts->add( $handle, $src, $deps ); + + $handle = 'customize-snapshots-frontend'; + $src = $this->dir_url . 'js/customize-snapshots-frontend' . $min . '.js'; + $deps = array( 'jquery', 'underscore' ); + $wp_scripts->add( $handle, $src, $deps ); } /** @@ -79,8 +91,15 @@ public function register_scripts( \WP_Scripts $wp_scripts ) { */ public function register_styles( \WP_Styles $wp_styles ) { $min = ( SCRIPT_DEBUG ? '' : '.min' ); + + $handle = 'customize-snapshots'; $src = $this->dir_url . 'css/customize-snapshots' . $min . '.css'; $deps = array( 'wp-jquery-ui-dialog' ); - $wp_styles->add( $this->slug, $src, $deps ); + $wp_styles->add( $handle, $src, $deps ); + + $handle = 'customize-snapshots-preview'; + $src = $this->dir_url . 'css/customize-snapshots-preview' . $min . '.css'; + $deps = array( 'customize-preview' ); + $wp_styles->add( $handle, $src, $deps ); } } diff --git a/php/class-post-type.php b/php/class-post-type.php index b085465a..fb0782cb 100644 --- a/php/class-post-type.php +++ b/php/class-post-type.php @@ -105,7 +105,7 @@ public function register() { register_post_type( static::SLUG, $args ); add_filter( 'post_type_link', array( $this, 'filter_post_type_link' ), 10, 2 ); - add_action( 'add_meta_boxes_' . static::SLUG, array( $this, 'remove_publish_metabox' ), 100 ); + add_action( 'add_meta_boxes_' . static::SLUG, array( $this, 'remove_slug_metabox' ), 100 ); add_action( 'load-revision.php', array( $this, 'suspend_kses_for_snapshot_revision_restore' ) ); add_filter( 'get_the_excerpt', array( $this, 'filter_snapshot_excerpt' ), 10, 2 ); add_filter( 'post_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 ); @@ -176,7 +176,7 @@ public function setup_metaboxes() { * * @codeCoverageIgnore */ - public function remove_publish_metabox() { + public function remove_slug_metabox() { remove_meta_box( 'slugdiv', static::SLUG, 'normal' ); } @@ -599,20 +599,20 @@ public function filter_user_has_cap( $allcaps, $caps ) { /** * Display snapshot save error on post list table. * - * @param array $status Display status. - * @param \WP_Post $post Post object. + * @param array $states Display states. + * @param \WP_Post $post Post object. * * @return mixed */ - public function display_post_states( $status, $post ) { + public function display_post_states( $states, $post ) { if ( static::SLUG !== $post->post_type ) { - return $status; + return $states; } $maybe_error = get_post_meta( $post->ID, 'snapshot_error_on_publish', true ); if ( $maybe_error ) { - $status['snapshot_error'] = __( 'Error on publish', 'customize-snapshots' ); + $states['snapshot_error'] = __( 'Error on publish', 'customize-snapshots' ); } - return $status; + return $states; } /** @@ -643,7 +643,7 @@ public function show_publish_error_admin_notice() { * Disable the revision revert UI for published posts. */ public function disable_revision_ui_for_published_posts() { - if ( 'publish' !== get_post_status() ) { + if ( 'publish' !== get_post_status() || self::SLUG !== get_post_type() ) { return; } ?> @@ -661,7 +661,7 @@ public function disable_revision_ui_for_published_posts() { * @param \WP_Post $post Current post. */ public function hide_disabled_publishing_actions( $post ) { - if ( 'publish' !== $post->post_status ) { + if ( 'publish' !== $post->post_status || self::SLUG !== $post->post_type ) { return; } ?> diff --git a/readme.md b/readme.md index 4270e025..efcb34c5 100644 --- a/readme.md +++ b/readme.md @@ -15,11 +15,41 @@ Allow Customizer states to be drafted, and previewed with a private URL. ## Description ## -Customize Snapshots save the state of a Customizer session so it can be shared or even publish at a future date. A snapshot can be shared with a private URL to both authenticated and non authenticated users. This means anyone can preview a snapshot's settings on the front-end without loading the Customizer, and authenticated users can load the snapshot into the Customizer and publish or amend the settings at any time. +Customize Snapshots save the state of a Customizer session so it can be shared or even published at a future date. A snapshot can be shared with a private URL to both authenticated and non authenticated users. This means anyone can preview a snapshot's settings on the front-end without loading the Customizer, and authenticated users can load the snapshot into the Customizer and publish or amend the settings at any time. + +Requires PHP 5.3+. **Development of this plugin is done [on GitHub](https://github.com/xwp/wp-customize-snapshots). Pull requests welcome. Please see [issues](https://github.com/xwp/wp-customize-snapshots) reported there before going to the [plugin forum](https://wordpress.org/support/plugin/customize-snapshots).** +### Persistent Object Caching ### +Plugins and themes may currently only use `is_customize_preview()` to +decide whether or not they can store a value in the object cache. For +example, see `Twenty_Eleven_Ephemera_Widget::widget()`. However, when +viewing a snapshot on the frontend, the `is_customize_preview()` method +will return `false`. Plugins and themes that store values in the object +cache must either skip storing in the object cache when `CustomizeSnapshots\is_previewing_settings()` +is `true`, or they should include the `CustomizeSnapshots\current_snapshot_uuid()` in the cache key. + +Example of bypassing object cache when previewing settings inside the Customizer preview or on the frontend via snapshots: + +```php +if ( function_exists( 'CustomizeSnapshots\is_previewing_settings' ) ) { + $bypass_object_cache = CustomizeSnapshots\is_previewing_settings(); +} else { + $bypass_object_cache = is_customize_preview(); +} +$contents = null; +if ( ! $bypass_object_cache ) { + $contents = wp_cache_get( 'something', 'myplugin' ); +} +if ( ! $contents ) { + ob_start(); + myplugin_do_something(); + $contents = ob_get_clean(); + echo $contents; +} +if ( ! $bypass_object_cache ) { + wp_cache_set( 'something', $contents, 'myplugin', HOUR_IN_SECONDS ); +} +``` -Requires PHP 5.3+. - -**Development of this plugin is done [on GitHub](https://github.com/xwp/wp-customize-snapshots). Pull requests welcome. Please see [issues](https://github.com/xwp/wp-customize-snapshots) reported there before going to the [plugin forum](https://wordpress.org/support/plugin/customize-snapshots).** ## Screenshots ## diff --git a/readme.txt b/readme.txt index 97ce5866..94614c87 100644 --- a/readme.txt +++ b/readme.txt @@ -11,12 +11,42 @@ Allow Customizer states to be drafted, and previewed with a private URL. == Description == -Customize Snapshots save the state of a Customizer session so it can be shared or even publish at a future date. A snapshot can be shared with a private URL to both authenticated and non authenticated users. This means anyone can preview a snapshot's settings on the front-end without loading the Customizer, and authenticated users can load the snapshot into the Customizer and publish or amend the settings at any time. - -Requires PHP 5.3+. - -**Development of this plugin is done [on GitHub](https://github.com/xwp/wp-customize-snapshots). Pull requests welcome. Please see [issues](https://github.com/xwp/wp-customize-snapshots) reported there before going to the [plugin forum](https://wordpress.org/support/plugin/customize-snapshots).** - +Customize Snapshots save the state of a Customizer session so it can be shared or even published at a future date. A snapshot can be shared with a private URL to both authenticated and non authenticated users. This means anyone can preview a snapshot's settings on the front-end without loading the Customizer, and authenticated users can load the snapshot into the Customizer and publish or amend the settings at any time. + +Requires PHP 5.3+. **Development of this plugin is done [on GitHub](https://github.com/xwp/wp-customize-snapshots). Pull requests welcome. Please see [issues](https://github.com/xwp/wp-customize-snapshots) reported there before going to the [plugin forum](https://wordpress.org/support/plugin/customize-snapshots).** + += Persistent Object Caching = + +Plugins and themes may currently only use `is_customize_preview()` to +decide whether or not they can store a value in the object cache. For +example, see `Twenty_Eleven_Ephemera_Widget::widget()`. However, when +viewing a snapshot on the frontend, the `is_customize_preview()` method +will return `false`. Plugins and themes that store values in the object +cache must either skip storing in the object cache when `CustomizeSnapshots\is_previewing_settings()` +is `true`, or they should include the `CustomizeSnapshots\current_snapshot_uuid()` in the cache key. + +Example of bypassing object cache when previewing settings inside the Customizer preview or on the frontend via snapshots: + +
+if ( function_exists( 'CustomizeSnapshots\is_previewing_settings' ) ) {
+	$bypass_object_cache = CustomizeSnapshots\is_previewing_settings();
+} else {
+	$bypass_object_cache = is_customize_preview();
+}
+$contents = null;
+if ( ! $bypass_object_cache ) {
+	$contents = wp_cache_get( 'something', 'myplugin' );
+}
+if ( ! $contents ) {
+	ob_start();
+	myplugin_do_something();
+	$contents = ob_get_clean();
+	echo $contents;
+}
+if ( ! $bypass_object_cache ) {
+	wp_cache_set( 'something', $contents, 'myplugin', HOUR_IN_SECONDS );
+}
+
== Screenshots == diff --git a/tests/php/test-class-ajax-customize-snapshot-manager.php b/tests/php/test-class-ajax-customize-snapshot-manager.php index 5e8b6d0a..4b6ac33f 100644 --- a/tests/php/test-class-ajax-customize-snapshot-manager.php +++ b/tests/php/test-class-ajax-customize-snapshot-manager.php @@ -55,6 +55,7 @@ public function setUp() { parent::setUp(); remove_all_actions( 'wp_ajax_customize_save' ); + remove_all_actions( 'wp_ajax_customize_update_snapshot' ); $this->plugin = new Plugin(); $this->set_input_vars(); $this->plugin->init(); diff --git a/tests/php/test-class-customize-snapshot-manager.php b/tests/php/test-class-customize-snapshot-manager.php index 4d52ffe4..24854795 100644 --- a/tests/php/test-class-customize-snapshot-manager.php +++ b/tests/php/test-class-customize-snapshot-manager.php @@ -93,6 +93,17 @@ function setUp() { } } + /** + * Clean up global scope. + */ + function clean_up_global_scope() { + unset( $GLOBALS['wp_scripts'] ); + unset( $GLOBALS['wp_styles'] ); + unset( $_REQUEST['customize_snapshot_uuid'] ); + unset( $_REQUEST['wp_customize_preview_ajax'] ); + parent::clean_up_global_scope(); + } + /** * Tear down. */ @@ -100,7 +111,6 @@ function tearDown() { $this->wp_customize = null; $this->manager = null; unset( $GLOBALS['wp_customize'] ); - unset( $GLOBALS['wp_scripts'] ); unset( $GLOBALS['screen'] ); $_REQUEST = array(); parent::tearDown(); @@ -158,7 +168,7 @@ function test_construct_with_customize() { $this->assertInstanceOf( 'CustomizeSnapshots\Post_Type', $manager->post_type ); $this->assertInstanceOf( 'CustomizeSnapshots\Customize_Snapshot', $manager->snapshot() ); $this->assertEquals( 0, has_action( 'init', array( $manager, 'create_post_type' ) ) ); - $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', array( $manager, 'enqueue_scripts' ) ) ); + $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', array( $manager, 'enqueue_controls_scripts' ) ) ); $this->assertEquals( 10, has_action( 'wp_ajax_customize_update_snapshot', array( $manager, 'handle_update_snapshot_request' ) ) ); } @@ -178,12 +188,141 @@ function test_construct_with_customize_bootstrapped() { } /** - * Tests init. + * Tests init hooks. * * @covers Customize_Snapshot_Manager::init() */ - public function test_init() { - $this->markTestIncomplete(); + public function test_init_hooks() { + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + + $this->assertInstanceOf( __NAMESPACE__ . '\Post_Type', $manager->post_type ); + $this->assertEquals( 10, has_action( 'template_redirect', array( $manager, 'show_theme_switch_error' ) ) ); + $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', array( $manager, 'enqueue_controls_scripts' ) ) ); + $this->assertEquals( 10, has_action( 'customize_preview_init', array( $manager, 'customize_preview_init' ) ) ); + $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $manager, 'enqueue_frontend_scripts' ) ) ); + + $this->assertEquals( 10, has_action( 'customize_controls_init', array( $manager, 'add_snapshot_uuid_to_return_url' ) ) ); + $this->assertEquals( 10, has_action( 'customize_controls_print_footer_scripts', array( $manager, 'render_templates' ) ) ); + $this->assertEquals( 10, has_action( 'customize_save', array( $manager, 'check_customize_publish_authorization' ) ) ); + $this->assertEquals( 10, has_filter( 'customize_refresh_nonces', array( $manager, 'filter_customize_refresh_nonces' ) ) ); + $this->assertEquals( 41, has_action( 'admin_bar_menu', array( $manager, 'customize_menu' ) ) ); + $this->assertEquals( 100000, has_action( 'admin_bar_menu', array( $manager, 'remove_all_non_snapshot_admin_bar_links' ) ) ); + $this->assertEquals( 10, has_action( 'wp_before_admin_bar_render', array( $manager, 'print_admin_bar_styles' ) ) ); + + $this->assertEquals( 10, has_filter( 'wp_insert_post_data', array( $manager, 'prepare_snapshot_post_content_for_publish' ) ) ); + $this->assertEquals( 10, has_action( 'customize_save_after', array( $manager, 'publish_snapshot_with_customize_save_after' ) ) ); + $this->assertEquals( 10, has_action( 'transition_post_status', array( $manager, 'save_settings_with_publish_snapshot' ) ) ); + $this->assertEquals( 10, has_action( 'wp_ajax_customize_update_snapshot', array( $manager, 'handle_update_snapshot_request' ) ) ); + } + + /** + * Tests init hooks. + * + * @covers Customize_Snapshot_Manager::init() + * @covers Customize_Snapshot_Manager::read_current_snapshot_uuid() + */ + public function test_read_current_snapshot_uuid() { + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertFalse( $manager->read_current_snapshot_uuid() ); + $this->assertNull( $manager->current_snapshot_uuid ); + + $_REQUEST['customize_snapshot_uuid'] = 'bad'; + $this->assertFalse( $manager->read_current_snapshot_uuid() ); + $this->assertNull( $manager->current_snapshot_uuid ); + + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $this->assertTrue( $manager->read_current_snapshot_uuid() ); + $this->assertEquals( self::UUID, $manager->current_snapshot_uuid ); + + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $this->assertEquals( self::UUID, $manager->current_snapshot_uuid ); + } + + /** + * Tests load_snapshot. + * + * @covers Customize_Snapshot_Manager::init() + * @covers Customize_Snapshot_Manager::load_snapshot() + */ + public function test_load_snapshot() { + global $wp_actions; + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $this->plugin->customize_snapshot_manager->post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( + 'blogname' => array( 'value' => 'Hello' ), + ), + 'status' => 'draft', + ) ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + unset( $wp_actions['setup_theme'] ); + unset( $wp_actions['wp_loaded'] ); + $manager->init(); + $this->assertNotEmpty( $manager->customize_manager ); + $this->assertNotEmpty( $manager->snapshot ); + + $this->assertEquals( 10, has_action( 'setup_theme', array( $manager, 'import_snapshot_data' ) ) ); + $this->assertEquals( 10, has_action( 'wp_head', 'wp_no_robots' ) ); + $this->assertEquals( 11, has_action( 'wp_loaded', array( $manager, 'preview_snapshot_settings' ) ) ); + } + + /** + * Tests setup_preview_ajax_requests. + * + * @covers Customize_Snapshot_Manager::init() + * @covers Customize_Snapshot_Manager::setup_preview_ajax_requests() + */ + public function test_setup_preview_ajax_requests() { + wp_set_current_user( $this->user_id ); + $_REQUEST['wp_customize_preview_ajax'] = 'true'; + $_POST['customized'] = wp_slash( wp_json_encode( array( 'blogname' => 'Foo' ) ) ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->do_customize_boot_actions( true ); + $this->assertTrue( is_customize_preview() ); + $manager->init(); + $this->assertEquals( 12, has_action( 'wp_loaded', array( $manager, 'setup_preview_ajax_requests' ) ) ); + do_action( 'wp_loaded' ); + + $this->assertFalse( has_action( 'shutdown', array( $this->wp_customize, 'customize_preview_signature' ) ) ); + $this->assertEquals( 5, has_action( 'parse_request', array( $manager, 'override_request_method' ) ) ); + } + + /** + * Tests override_request_method. + * + * @covers Customize_Snapshot_Manager::override_request_method() + */ + public function test_override_request_method() { + global $wp; + + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertFalse( $manager->override_request_method() ); + + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'GET'; + $wp->query_vars['rest_route'] = '/wp/v1/foo'; + $this->assertFalse( $manager->override_request_method() ); + unset( $wp->query_vars['rest_route'] ); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'GET'; + $this->assertFalse( $manager->override_request_method() ); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'BAD'; + $this->assertFalse( $manager->override_request_method() ); + + $_GET = wp_slash( array( 'foo' => '1' ) ); + $_POST = wp_slash( array( 'bar' => '2' ) ); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'GET'; + $this->assertTrue( $manager->override_request_method() ); + $this->assertEquals( 'GET', $_SERVER['REQUEST_METHOD'] ); + $this->assertEquals( 'foo=1&bar=2', $_SERVER['QUERY_STRING'] ); + $this->assertArrayHasKey( 'foo', $_GET ); + $this->assertArrayHasKey( 'bar', $_GET ); } /** @@ -192,7 +331,14 @@ public function test_init() { * @covers Customize_Snapshot_Manager::doing_customize_save_ajax() */ public function test_doing_customize_save_ajax() { - $this->markTestIncomplete(); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertFalse( $manager->doing_customize_save_ajax() ); + + $_REQUEST['action'] = 'foo'; + $this->assertFalse( $manager->doing_customize_save_ajax() ); + + $_REQUEST['action'] = 'customize_save'; + $this->assertTrue( $manager->doing_customize_save_ajax() ); } /** @@ -201,7 +347,13 @@ public function test_doing_customize_save_ajax() { * @covers Customize_Snapshot_Manager::ensure_customize_manager() */ public function test_ensure_customize_manager() { - $this->markTestIncomplete(); + global $wp_customize; + $wp_customize = null; // WPCS: global override ok. + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertEmpty( $manager->customize_manager ); + $manager->ensure_customize_manager(); + $this->assertInstanceOf( 'WP_Customize_Manager', $manager->customize_manager ); + $this->assertInstanceOf( 'WP_Customize_Manager', $wp_customize ); } /** @@ -210,7 +362,13 @@ public function test_ensure_customize_manager() { * @covers Customize_Snapshot_Manager::is_theme_active() */ public function test_is_theme_active() { - $this->markTestIncomplete(); + global $wp_customize; + $wp_customize = null; // WPCS: global override ok. + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertTrue( $manager->is_theme_active() ); + + $manager->ensure_customize_manager(); + $this->assertTrue( $manager->is_theme_active() ); } /** @@ -219,7 +377,84 @@ public function test_is_theme_active() { * @covers Customize_Snapshot_Manager::should_import_and_preview_snapshot() */ public function test_should_import_and_preview_snapshot() { - $this->markTestIncomplete(); + global $pagenow, $wp_customize; + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $manager = $this->plugin->customize_snapshot_manager; + $post_id = $manager->post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( 'blogname' => array( 'value' => 'Foo' ) ), + ) ); + $snapshot = new Customize_Snapshot( $manager, self::UUID ); + + // Not if admin. + set_current_screen( 'posts' ); + $pagenow = 'posts.php'; // WPCS: global override ok. + $this->assertTrue( is_admin() ); + $this->assertFalse( $manager->should_import_and_preview_snapshot( $snapshot ) ); + + // Not if theme switch error. + set_current_screen( 'customize' ); + $pagenow = 'customize.php'; // WPCS: global override ok. + update_post_meta( $post_id, '_snapshot_theme', 'Foo' ); + $this->assertFalse( $manager->should_import_and_preview_snapshot( $snapshot ) ); + delete_post_meta( $post_id, '_snapshot_theme' ); + + // Not if customize_save. + $_REQUEST['action'] = 'customize_save'; + $this->assertFalse( $manager->should_import_and_preview_snapshot( $snapshot ) ); + unset( $_REQUEST['action'] ); + + // Not if published snapshot. + $manager->post_type->save( array( + 'uuid' => self::UUID, + 'status' => 'publish', + ) ); + $this->assertFalse( $manager->should_import_and_preview_snapshot( $snapshot ) ); + $manager->post_type->save( array( + 'uuid' => self::UUID, + 'status' => 'draft', + ) ); + + // Not if unsanitized post values is not empty. + $manager->customize_manager = new \WP_Customize_Manager(); + $wp_customize = $manager->customize_manager; // WPCS: global override ok. + $wp_customize->set_post_value( 'name', 'value' ); + $this->assertNotEmpty( $manager->customize_manager->unsanitized_post_values() ); + $this->assertFalse( $manager->should_import_and_preview_snapshot( $snapshot ) ); + + // OK. + $manager->customize_manager = new \WP_Customize_Manager(); + $wp_customize = $manager->customize_manager; // WPCS: global override ok. + $this->assertTrue( $manager->should_import_and_preview_snapshot( $snapshot ) ); + } + + /** + * Tests is_previewing_settings. + * + * @covers Customize_Snapshot_Manager::is_previewing_settings() + */ + public function test_is_previewing_settings() { + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $this->plugin->customize_snapshot_manager->post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( 'blogname' => array( 'value' => 'Foo' ) ), + ) ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $manager->preview_snapshot_settings(); + $this->assertTrue( $manager->is_previewing_settings() ); + } + + /** + * Tests is_previewing_settings. + * + * @covers Customize_Snapshot_Manager::is_previewing_settings() + */ + public function test_is_previewing_settings_via_preview_init() { + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $this->assertFalse( $manager->is_previewing_settings() ); + do_action( 'customize_preview_init' ); + $this->assertTrue( $manager->is_previewing_settings() ); } /** @@ -285,22 +520,6 @@ public function test_add_snapshot_uuid_to_return_url() { } } - /** - * Test remove snapshot uuid from current url. - * - * @covers Customize_Snapshot_Manager::remove_snapshot_uuid_from_current_url() - * @covers Customize_Snapshot_Manager::current_url() - */ - function test_remove_snapshot_uuid_from_current_url() { - $this->go_to( home_url( '?customize_snapshot_uuid=' . self::UUID ) ); - ob_start(); - $manager = new Customize_Snapshot_Manager( $this->plugin ); - $this->assertContains( 'customize_snapshot_uuid', $manager->current_url() ); - echo $manager->remove_snapshot_uuid_from_current_url(); // WPCS: xss ok. - $buffer = ob_get_clean(); - $this->assertEquals( home_url( '/' ), $buffer ); - } - /** * Tests show_theme_switch_error. * @@ -342,18 +561,40 @@ function test_encode_json() { } /** - * Test enqueue scripts. + * Test enqueue controls scripts. * - * @see Customize_Snapshot_Manager::enqueue_scripts() + * @see Customize_Snapshot_Manager::enqueue_controls_scripts() */ - function test_enqueue_scripts() { + function test_enqueue_controls_scripts() { $this->plugin->register_scripts( wp_scripts() ); $this->plugin->register_styles( wp_styles() ); $manager = new Customize_Snapshot_Manager( $this->plugin ); $manager->init(); - $manager->enqueue_scripts(); - $this->assertTrue( wp_script_is( $this->plugin->slug, 'enqueued' ) ); - $this->assertTrue( wp_style_is( $this->plugin->slug, 'enqueued' ) ); + $manager->enqueue_controls_scripts(); + $this->assertTrue( wp_script_is( 'customize-snapshots', 'enqueued' ) ); + $this->assertTrue( wp_style_is( 'customize-snapshots', 'enqueued' ) ); + } + + /** + * Test enqueue frontend scripts. + * + * @see Customize_Snapshot_Manager::enqueue_frontend_scripts() + */ + function test_enqueue_frontend_scripts() { + $this->plugin->register_scripts( wp_scripts() ); + $this->plugin->register_styles( wp_styles() ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $this->assertFalse( wp_script_is( 'customize-snapshots-frontend', 'enqueued' ) ); + $manager->enqueue_frontend_scripts(); + $this->assertFalse( wp_script_is( 'customize-snapshots-frontend', 'enqueued' ) ); + + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $this->assertFalse( wp_script_is( 'customize-snapshots-frontend', 'enqueued' ) ); + $manager->enqueue_frontend_scripts(); + $this->assertTrue( wp_script_is( 'customize-snapshots-frontend', 'enqueued' ) ); } /** @@ -530,21 +771,27 @@ public function test_is_valid_uuid() { */ public function test_customize_menu() { set_current_screen( 'front' ); - $customize_url = admin_url( 'customize.php' ) . '?customize_snapshot_uuid=' . self::UUID . '&url=' . urlencode( esc_url( home_url( '/' ) ) ); + $preview_url = home_url( '/' ); $_REQUEST['customize_snapshot_uuid'] = self::UUID; $manager = new Customize_Snapshot_Manager( $this->plugin ); $manager->init(); require_once( ABSPATH . WPINC . '/class-wp-admin-bar.php' ); - $wp_admin_bar = new \WP_Admin_Bar(); + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. $this->assertInstanceOf( 'WP_Admin_Bar', $wp_admin_bar ); wp_set_current_user( $this->user_id ); $this->go_to( home_url( '?customize_snapshot_uuid=' . self::UUID ) ); + $wp_admin_bar->initialize(); + $wp_admin_bar->add_menus(); do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) ); - $this->assertEquals( $customize_url, $wp_admin_bar->get_node( 'customize' )->href ); + $parsed_url = wp_parse_url( $wp_admin_bar->get_node( 'customize' )->href ); + $query_params = array(); + wp_parse_str( $parsed_url['query'], $query_params ); + $this->assertEquals( $preview_url, $query_params['url'] ); + $this->assertEquals( self::UUID, $query_params['customize_snapshot_uuid'] ); } /** @@ -554,7 +801,7 @@ public function test_customize_menu() { */ public function test_customize_menu_return() { require_once( ABSPATH . WPINC . '/class-wp-admin-bar.php' ); - $wp_admin_bar = new \WP_Admin_Bar; + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. $this->assertInstanceOf( 'WP_Admin_Bar', $wp_admin_bar ); wp_set_current_user( $this->factory()->user->create( array( 'role' => 'editor' ) ) ); @@ -565,12 +812,17 @@ public function test_customize_menu_return() { } /** - * Test add_post_edit_screen_link. + * Tests print_admin_bar_styles. * - * @covers Customize_Snapshot_Manager::add_post_edit_screen_link() + * @covers Customize_Snapshot_Manager::print_admin_bar_styles() */ - public function test_add_post_edit_screen_link() { - $this->markTestIncomplete(); + public function test_print_admin_bar_styles() { + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + ob_start(); + $manager->print_admin_bar_styles(); + $contents = ob_get_clean(); + $this->assertContains( 'markTestIncomplete(); + global $wp_admin_bar; + set_current_screen( 'front' ); + + require_once ABSPATH . WPINC . '/class-wp-admin-bar.php'; + remove_all_actions( 'admin_bar_menu' ); + $this->go_to( home_url( '?customize_snapshot_uuid=' . self::UUID ) ); + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + + // Ensure customize link remains unknown if user lacks cap. + wp_set_current_user( 0 ); + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. + $wp_admin_bar->initialize(); + $wp_admin_bar->add_menus(); + do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) ); + $this->assertEmpty( $wp_admin_bar->get_node( 'customize' ) ); + + // Ensure customize link modified. + wp_set_current_user( $this->user_id ); + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. + $wp_admin_bar->initialize(); + $wp_admin_bar->add_menus(); + do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) ); + $node = $wp_admin_bar->get_node( 'customize' ); + $this->assertTrue( is_object( $node ) ); + $parsed_url = wp_parse_url( $node->href ); + $query_params = array(); + parse_str( $parsed_url['query'], $query_params ); + $this->assertArrayHasKey( 'customize_snapshot_uuid', $query_params ); + $this->assertEquals( self::UUID, $query_params['customize_snapshot_uuid'] ); + $this->assertArrayHasKey( 'url', $query_params ); + $parsed_preview_url = wp_parse_url( $query_params['url'] ); + $this->assertArrayNotHasKey( 'query', $parsed_preview_url ); + } + + /** + * Test misc admin bar extensions. + * + * @covers Customize_Snapshot_Manager::add_post_edit_screen_link() + * @covers Customize_Snapshot_Manager::add_snapshot_exit_link() + * @covers Customize_Snapshot_Manager::add_resume_snapshot_link() + * @covers Customize_Snapshot_Manager::remove_all_non_snapshot_admin_bar_links() + */ + public function test_add_post_edit_and_exit_links() { + global $wp_admin_bar; + set_current_screen( 'front' ); + require_once ABSPATH . WPINC . '/class-wp-admin-bar.php'; + + $this->manager->post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( + 'blogname' => array( + 'value' => 'Hello', + ), + ), + 'status' => 'draft', + ) ); + + remove_all_actions( 'admin_bar_menu' ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. + $wp_admin_bar->initialize(); + $wp_admin_bar->add_menus(); + do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) ); + $this->assertEmpty( $wp_admin_bar->get_node( 'inspect-customize-snapshot' ) ); + $this->assertEmpty( $wp_admin_bar->get_node( 'exit-customize-snapshot' ) ); + $this->assertNotEmpty( $wp_admin_bar->get_node( 'wporg' ) ); + $this->assertNotEmpty( $wp_admin_bar->get_node( 'resume-customize-snapshot' ) ); + + $this->go_to( home_url( '?customize_snapshot_uuid=' . self::UUID ) ); + $_REQUEST['customize_snapshot_uuid'] = self::UUID; + remove_all_actions( 'admin_bar_menu' ); + $manager = new Customize_Snapshot_Manager( $this->plugin ); + $manager->init(); + $wp_admin_bar = new \WP_Admin_Bar(); // WPCS: Override OK. + $wp_admin_bar->initialize(); + $wp_admin_bar->add_menus(); + do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) ); + $this->assertNotEmpty( $wp_admin_bar->get_node( 'inspect-customize-snapshot' ) ); + $this->assertNotEmpty( $wp_admin_bar->get_node( 'exit-customize-snapshot' ) ); + $this->assertEmpty( $wp_admin_bar->get_node( 'wporg' ) ); + $this->assertNotEmpty( $wp_admin_bar->get_node( 'resume-customize-snapshot' ) ); } /** diff --git a/tests/php/test-class-post-type.php b/tests/php/test-class-post-type.php index db7c1bcd..b2027887 100644 --- a/tests/php/test-class-post-type.php +++ b/tests/php/test-class-post-type.php @@ -49,7 +49,7 @@ public function test_register() { $this->assertTrue( post_type_exists( Post_Type::SLUG ) ); $this->assertEquals( 10, has_filter( 'post_type_link', array( $post_type, 'filter_post_type_link' ) ) ); - $this->assertEquals( 100, has_action( 'add_meta_boxes_' . Post_Type::SLUG, array( $post_type, 'remove_publish_metabox' ) ) ); + $this->assertEquals( 100, has_action( 'add_meta_boxes_' . Post_Type::SLUG, array( $post_type, 'remove_slug_metabox' ) ) ); $this->assertEquals( 10, has_action( 'load-revision.php', array( $post_type, 'suspend_kses_for_snapshot_revision_restore' ) ) ); $this->assertEquals( 10, has_filter( 'get_the_excerpt', array( $post_type, 'filter_snapshot_excerpt' ) ) ); $this->assertEquals( 10, has_filter( 'post_row_actions', array( $post_type, 'filter_post_row_actions' ) ) ); @@ -129,23 +129,9 @@ public function test_setup_metaboxes() { $this->assertTrue( ! empty( $wp_meta_boxes[ Post_Type::SLUG ]['normal']['high'][ $metabox_id ] ) ); } - /** - * Tests remove_publish_metabox. - * - * @covers Post_Type::remove_publish_metabox() - */ - public function test_remove_publish_metabox() { - $this->markTestIncomplete(); - } + /* Note: Code coverage ignored on Post_Type::remove_publish_metabox(). */ - /** - * Tests suspend_kses_for_snapshot_revision_restore. - * - * @covers Post_Type::suspend_kses_for_snapshot_revision_restore() - */ - public function test_suspend_kses_for_snapshot_revision_restore() { - $this->markTestIncomplete(); - } + /* Note: Code coverage ignored on Post_Type::suspend_kses_for_snapshot_revision_restore(). */ /** * Test include the setting IDs in the excerpt. @@ -643,7 +629,18 @@ function test_filter_user_has_cap() { * @covers Post_Type::display_post_states() */ public function test_display_post_states() { - $this->markTestIncomplete(); + $post_type = new Post_Type( $this->plugin->customize_snapshot_manager ); + + $post_id = $post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( 'foo' => array( 'value' => 'bar' ) ), + ) ); + $states = $post_type->display_post_states( array(), get_post( $post_id ) ); + $this->assertArrayNotHasKey( 'snapshot_error', $states ); + + update_post_meta( $post_id, 'snapshot_error_on_publish', true ); + $states = $post_type->display_post_states( array(), get_post( $post_id ) ); + $this->assertArrayHasKey( 'snapshot_error', $states ); } /** diff --git a/tests/test-customize-snapshots.php b/tests/test-customize-snapshots.php index 9e4e8fc9..bf391a53 100644 --- a/tests/test-customize-snapshots.php +++ b/tests/test-customize-snapshots.php @@ -5,11 +5,24 @@ * @package CustomizeSnapshots */ +namespace CustomizeSnapshots; + /** * Class Test_Customize_Snapshots */ class Test_Customize_Snapshots extends \WP_UnitTestCase { + /** + * Clean up global scope. + */ + function clean_up_global_scope() { + global $customize_snapshots_plugin; + unset( $_REQUEST['customize_snapshot_uuid'] ); + parent::clean_up_global_scope(); + $customize_snapshots_plugin = new Plugin(); + $customize_snapshots_plugin->init(); + } + /** * Test customize_snapshots_php_version_error. * @@ -30,4 +43,30 @@ function test_customize_snapshots_php_version_error() { function test_customize_snapshots_php_version_text() { $this->assertContains( 'Customize Snapshots plugin error:', customize_snapshots_php_version_text() ); } + + /** + * Tests is_previewing_settings(). + * + * @covers is_previewing_settings() + */ + public function test_is_previewing_settings() { + $this->assertFalse( is_previewing_settings() ); + do_action( 'customize_preview_init' ); + $this->assertTrue( is_previewing_settings() ); + } + + /** + * Tests current_snapshot_uuid(). + * + * @covers current_snapshot_uuid() + */ + public function test_current_snapshot_uuid() { + global $customize_snapshots_plugin; + $this->assertNull( current_snapshot_uuid() ); + $uuid = '65aee1ff-af47-47df-9e14-9c69b3017cd3'; + $_REQUEST['customize_snapshot_uuid'] = $uuid; + $customize_snapshots_plugin = new Plugin(); + $customize_snapshots_plugin->init(); + $this->assertEquals( $uuid, current_snapshot_uuid() ); + } }