diff --git a/.travis.yml b/.travis.yml index 3234f80e..70c258f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,38 @@ sudo: false +notifications: + email: + on_success: never + on_failure: change + +cache: + directories: + - node_modules + - vendor + - $HOME/phpunit-bin + language: - - php - - node_js + - php + - node_js php: - - 5.3 - - 7.0 - -node_js: - - stable + - 5.3 + - 7.0 env: - - WP_VERSION=trunk WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=4.6.1 WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 + - WP_VERSION=trunk WP_MULTISITE=0 + - WP_VERSION=latest WP_MULTISITE=0 + - WP_VERSION=4.6.1 WP_MULTISITE=0 + - WP_VERSION=latest WP_MULTISITE=1 install: - - nvm install 4 && nvm use 4 - - export DEV_LIB_PATH=dev-lib - - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi - - source $DEV_LIB_PATH/travis.install.sh + - nvm install 6 && nvm use 6 + - export DEV_LIB_PATH=dev-lib + - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi + - source $DEV_LIB_PATH/travis.install.sh script: - - source $DEV_LIB_PATH/travis.script.sh + - source $DEV_LIB_PATH/travis.script.sh after_script: - - source $DEV_LIB_PATH/travis.after_script.sh + - source $DEV_LIB_PATH/travis.after_script.sh diff --git a/dev-lib b/dev-lib index 85f9cf48..fc29c351 160000 --- a/dev-lib +++ b/dev-lib @@ -1 +1 @@ -Subproject commit 85f9cf48ec5d2fbdeb3eb26fcee9fc2383f36805 +Subproject commit fc29c35122188d7c98d8949a722498c55b95d040 diff --git a/js/compat/customize-snapshots.js b/js/compat/customize-snapshots.js index 0e1a17f4..5d178ec9 100644 --- a/js/compat/customize-snapshots.js +++ b/js/compat/customize-snapshots.js @@ -1,5 +1,5 @@ -/* global jQuery, wp, _customizeSnapshotsCompatSettings */ -/* eslint consistent-this: ["error", "snapshot"] */ +/* global jQuery, wp, _customizeSnapshotsCompatSettings, JSON */ +/* eslint consistent-this: ["error", "snapshot"], no-magic-numbers: [ "error", { "ignore": [0,1,2] } ] */ ( function( api, $ ) { 'use strict'; @@ -226,6 +226,8 @@ api.previewer.query = function() { var retval = originalQuery.apply( this, arguments ); + retval.customizer_state_query_vars = JSON.stringify( snapshot.getStateQueryVars() ); + if ( api.state( 'snapshot-exists' ).get() ) { retval.customize_snapshot_uuid = snapshot.data.uuid; if ( snapshot.snapshotTitle && snapshot.snapshotTitle.val() ) { diff --git a/js/customize-snapshots.js b/js/customize-snapshots.js index 1e7c4c27..cd8ddf3f 100644 --- a/js/customize-snapshots.js +++ b/js/customize-snapshots.js @@ -1,4 +1,4 @@ -/* global jQuery, wp, _customizeSnapshotsSettings */ +/* global jQuery, wp, JSON, _customizeSnapshotsSettings */ /* eslint no-magic-numbers: [ "error", { "ignore": [0,1,-1] } ], consistent-this: [ "error", "snapshot" ] */ (function( api, $ ) { @@ -98,6 +98,35 @@ } ); }, + /** + * Get state query vars. + * + * @return {{}} Query vars for scroll, device, url, and autofocus. + */ + getStateQueryVars: function() { + var queryVars = { + 'autofocus[control]': null, + 'autofocus[section]': null, + 'autofocus[panel]': null + }; + queryVars.scroll = parseInt( api.previewer.scroll, 10 ) || 0; + queryVars.device = api.previewedDevice.get(); + queryVars.url = api.previewer.previewUrl.get(); + + _.find( [ 'control', 'section', 'panel' ], function( constructType ) { + var found = false; + api[ constructType ].each( function( construct ) { // @todo Core needs to support more Backbone methods on wp.customize.Values(). + if ( ! found && construct.expanded && construct.expanded.get() ) { + queryVars[ 'autofocus[' + constructType + ']' ] = construct.id; + found = true; + } + } ); + return found; + } ); + + return queryVars; + }, + /** * Update snapshot. * @@ -966,6 +995,9 @@ api.previewer.query = function() { var retval = originalQuery.apply( this, arguments ); + + retval.customizer_state_query_vars = JSON.stringify( snapshot.getStateQueryVars() ); + if ( snapshot.editControlSettings( 'title' ).get() ) { retval.customize_changeset_title = snapshot.editControlSettings( 'title' ).get(); } diff --git a/php/class-customize-snapshot-manager.php b/php/class-customize-snapshot-manager.php index 95b1d465..4afbfd3d 100644 --- a/php/class-customize-snapshot-manager.php +++ b/php/class-customize-snapshot-manager.php @@ -104,7 +104,8 @@ function hooks() { 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( 'removable_query_args', array( $this, 'filter_removable_query_args' ) ); - add_action( 'save_post_customize_changeset', array( $this, 'create_initial_changeset_revision' ) ); + add_action( 'save_post_' . $this->get_post_type(), array( $this, 'create_initial_changeset_revision' ) ); + add_action( 'save_post_' . $this->get_post_type(), array( $this, 'save_customizer_state_query_vars' ) ); add_filter( 'wp_insert_post_data', array( $this, 'prepare_snapshot_post_content_for_publish' ) ); } @@ -516,7 +517,7 @@ public function replace_customize_link( $wp_admin_bar ) { $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( $this->get_front_uuid_param() ), $preview_url_query_params['url'] ); + $preview_url_query_params['url'] = rawurlencode( remove_query_arg( array( $this->get_front_uuid_param() ), $preview_url_query_params['url'] ) ); $customize_node->href = preg_replace( '/(?<=\?).*?(?=#|$)/', build_query( $preview_url_query_params ), @@ -524,14 +525,21 @@ public function replace_customize_link( $wp_admin_bar ) { ); } - // Add customize_snapshot_uuid param as param to customize.php itself. - $customize_node->href = add_query_arg( - array( - $this->get_customize_uuid_param() => $this->current_snapshot_uuid, - ), - $customize_node->href + $args = array( + $this->get_customize_uuid_param() => $this->current_snapshot_uuid, ); + $post = $this->snapshot->post(); + + if ( $post ) { + $customizer_state_query_vars = $this->post_type->get_customizer_state_query_vars( $post->ID ); + unset( $customizer_state_query_vars['url'] ); + $args = array_merge( $args, $customizer_state_query_vars ); + } + + // Add customize_snapshot_uuid and preview url params to customize.php itself. + $customize_node->href = add_query_arg( $args, $customize_node->href ); + $customize_node->meta['class'] .= ' ab-customize-snapshots-item'; $wp_admin_bar->add_menu( (array) $customize_node ); } @@ -934,4 +942,23 @@ public function get_front_uuid_param() { public function get_customize_uuid_param() { return constant( get_class( $this->post_type ) . '::CUSTOMIZE_UUID_PARAM_NAME' ); } + + /** + * Save the preview url query vars in changeset meta. + * + * @param int $post_id Post id. + */ + public function save_customizer_state_query_vars( $post_id ) { + if ( ! isset( $_POST['customizer_state_query_vars'] ) ) { + return; + } + + $original_query_vars = json_decode( wp_unslash( $_POST['customizer_state_query_vars'] ), true ); + + if ( empty( $original_query_vars ) || ! is_array( $original_query_vars ) ) { + return; + } + + $this->post_type->set_customizer_state_query_vars( $post_id, $original_query_vars ); + } } diff --git a/php/class-post-type.php b/php/class-post-type.php index a33df21f..20b71d76 100644 --- a/php/class-post-type.php +++ b/php/class-post-type.php @@ -158,12 +158,7 @@ public function add_admin_menu_item() { */ public function filter_post_type_link( $url, $post ) { if ( static::SLUG === $post->post_type ) { - $url = add_query_arg( - array( - static::FRONT_UUID_PARAM_NAME => $post->post_name, - ), - home_url( '/' ) - ); + $url = $this->get_frontend_view_link( $post ); } return $url; } @@ -286,9 +281,13 @@ public function filter_post_row_actions( $actions, $post ) { $post_type_obj = get_post_type_object( static::SLUG ); if ( 'publish' !== $post->post_status && current_user_can( $post_type_obj->cap->edit_post, $post->ID ) ) { - $args = array( - static::CUSTOMIZE_UUID_PARAM_NAME => $post->post_name, + $args = array_merge( + $this->get_customizer_state_query_vars( $post->ID ), + array( + static::CUSTOMIZE_UUID_PARAM_NAME => $post->post_name, + ) ); + $customize_url = add_query_arg( array_map( 'rawurlencode', $args ), wp_customize_url() ); $actions = array_merge( array( @@ -340,14 +339,18 @@ public function render_data_metabox( $post ) { $snapshot_theme = get_post_meta( $post->ID, '_snapshot_theme', true ); if ( ! empty( $snapshot_theme ) && get_stylesheet() !== $snapshot_theme ) { echo '

'; - /* translators: 1 is the theme the snapshot was created for */ - echo sprintf( esc_html__( 'This snapshot was made when a different theme was active (%1$s), so currently it cannot be edited.', 'customize-snapshots' ), esc_html( $snapshot_theme ) ); + /* translators: 1 is the theme the changeset was created for */ + echo sprintf( esc_html__( 'This changeset was made when a different theme was active (%1$s), so currently it cannot be edited.', 'customize-snapshots' ), esc_html( $snapshot_theme ) ); echo '

'; } elseif ( 'publish' !== $post->post_status ) { echo '

'; - $args = array( - static::CUSTOMIZE_UUID_PARAM_NAME => $post->post_name, + $args = array_merge( + $this->get_customizer_state_query_vars( $post->ID ), + array( + static::CUSTOMIZE_UUID_PARAM_NAME => $post->post_name, + ) ); + $customize_url = add_query_arg( array_map( 'rawurlencode', $args ), wp_customize_url() ); echo sprintf( '%s ', @@ -359,7 +362,7 @@ public function render_data_metabox( $post ) { echo sprintf( '%s', esc_url( $frontend_view_url ), - esc_html__( 'Preview Snapshot', 'customize-snapshots' ) + esc_html__( 'Preview Changeset', 'customize-snapshots' ) ); echo '

'; } @@ -681,7 +684,7 @@ public function hide_add_new_changeset_button() { * @return mixed */ public function add_snapshot_bulk_actions( $bulk_actions ) { - $bulk_actions['merge_snapshot'] = __( 'Merge Snapshot', 'customize-snapshots' ); + $bulk_actions['merge_snapshot'] = __( 'Merge Changeset', 'customize-snapshots' ); return $bulk_actions; } @@ -791,7 +794,7 @@ public function admin_show_merge_error() { return; } $error = array( - 1 => __( 'At-least two snapshot required for merge.', 'customize-snapshots' ), + 1 => __( 'At-least two changesets required for merge.', 'customize-snapshots' ), ); $error_code = intval( $_REQUEST['merge-error'] ); // WPCS: input var ok. if ( ! isset( $error[ $error_code ] ) ) { @@ -849,4 +852,76 @@ public function filter_out_settings_if_removed_in_metabox( $content ) { return $content; } + + /** + * Get customizer session state query vars. + * + * @param int $post_id Post id. + * @return array $preview_url_query_vars Preview url query vars. + */ + public function get_customizer_state_query_vars( $post_id ) { + $preview_url_query_vars = get_post_meta( $post_id, '_preview_url_query_vars', true ); + + if ( ! is_array( $preview_url_query_vars ) ) { + $preview_url_query_vars = array(); + } + + return $preview_url_query_vars; + } + + /** + * Set customizer session state query vars. + * + * Supplied query vars are validated and sanitized. + * + * @param int $post_id Post id. + * @param array $query_vars Post id. + * @return array Sanitized query vars. + */ + public function set_customizer_state_query_vars( $post_id, $query_vars ) { + $stored_query_vars = array(); + $autofocus_query_vars = array( 'autofocus[panel]', 'autofocus[section]', 'autofocus[control]' ); + + $this->snapshot_manager->ensure_customize_manager(); + + foreach ( wp_array_slice_assoc( $query_vars, $autofocus_query_vars ) as $key => $value ) { + if ( preg_match( '/^[a-z|\[|\]|_|\-|0-9]+$/', $value ) ) { + $stored_query_vars[ $key ] = $value; + } + } + if ( ! empty( $query_vars['url'] ) && wp_validate_redirect( $query_vars['url'] ) ) { + $stored_query_vars['url'] = esc_url_raw( $query_vars['url'] ); + } + if ( isset( $query_vars['device'] ) && in_array( $query_vars['device'], array_keys( $this->snapshot_manager->customize_manager->get_previewable_devices() ), true ) ) { + $stored_query_vars['device'] = $query_vars['device']; + } + if ( isset( $query_vars['scroll'] ) && is_int( $query_vars['scroll'] ) ) { + $stored_query_vars['scroll'] = $query_vars['scroll']; + } + update_post_meta( $post_id, '_preview_url_query_vars', $stored_query_vars ); + return $stored_query_vars; + } + + /** + * Get frontend view link. + * + * Returns URL to frontend with customize_changeset_uuid param supplied. + * If the changeset was saved in the customizer then the URL being previewed + * will serve as the base URL as opposed to the home URL as normally. + * + * @see Post_Type::filter_post_type_link() + * @param int|\WP_Post $post Changeset post. + * @return string URL. + */ + public function get_frontend_view_link( $post ) { + $post = get_post( $post ); + $preview_url_query_vars = $this->get_customizer_state_query_vars( $post->ID ); + $base_url = isset( $preview_url_query_vars['url'] ) ? $preview_url_query_vars['url'] : home_url( '/' ); + return add_query_arg( + array( + static::FRONT_UUID_PARAM_NAME => $post->post_name, + ), + $base_url + ); + } } diff --git a/tests/php/test-class-customize-snapshot-manager.php b/tests/php/test-class-customize-snapshot-manager.php index 8d9c3c5f..d349e339 100644 --- a/tests/php/test-class-customize-snapshot-manager.php +++ b/tests/php/test-class-customize-snapshot-manager.php @@ -241,6 +241,8 @@ function test_hooks() { $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( 'removable_query_args', array( $manager, 'filter_removable_query_args' ) ) ); + $this->assertEquals( 10, has_action( 'save_post_' . $manager->get_post_type(), array( $manager, 'create_initial_changeset_revision' ) ) ); + $this->assertEquals( 10, has_action( 'save_post_' . $manager->get_post_type(), array( $manager, 'save_customizer_state_query_vars' ) ) ); $this->assertEquals( 10, has_filter( 'wp_insert_post_data', array( $manager, 'prepare_snapshot_post_content_for_publish' ) ) ); } @@ -729,4 +731,55 @@ public function test_replace_customize_link() { $parsed_preview_url = wp_parse_url( $query_params['url'] ); $this->assertArrayNotHasKey( 'query', $parsed_preview_url ); } + + /** + * Test save_customizer_state_query_vars. + * + * @convers \CustomizeSnapshots\Customize_Snapshot_Manager::save_customizer_state_query_vars() + * @convers \CustomizeSnapshots\Post_Type::get_frontend_view_link() + * @convers \CustomizeSnapshots\Post_Type::get_customizer_state_query_vars() + * @convers \CustomizeSnapshots\Post_Type::set_customizer_state_query_vars() + */ + public function test_save_customizer_state_query_vars() { + $post_id = $this->manager->post_type->save( array( + 'uuid' => self::UUID, + 'data' => array( + 'blogname' => array( + 'value' => 'Hello', + ), + ), + 'status' => 'draft', + ) ); + + $original_query_vars = array( + 'scroll' => 123, + 'device' => 'mobile', + 'url' => home_url( 'about/' ), + 'autofocus[panel]' => 'widgets', + 'autofocus[section]' => 'sidebar-widgets-sidebar-1', + 'autofocus[control]' => 'widget_test[123]', + ); + + $this->assertContains( sprintf( '?%s=%s', $this->front_param, self::UUID ), get_permalink( $post_id ) ); + $this->assertEmpty( $this->manager->post_type->get_customizer_state_query_vars( $post_id ) ); + $this->manager->save_customizer_state_query_vars( $post_id ); + $this->assertEmpty( $this->manager->post_type->get_customizer_state_query_vars( $post_id ) ); + + $_POST['customizer_state_query_vars'] = wp_slash( wp_json_encode( $original_query_vars ) ); + $this->manager->save_customizer_state_query_vars( $post_id ); + $this->assertContains( sprintf( 'about/?%s=%s', $this->front_param, self::UUID ), get_permalink( $post_id ) ); + $this->assertEquals( $this->manager->post_type->get_customizer_state_query_vars( $post_id ), $original_query_vars ); + $this->assertEquals( $this->manager->post_type->get_frontend_view_link( $post_id ), get_permalink( $post_id ) ); + + $this->manager->post_type->set_customizer_state_query_vars( $post_id, array( + 'scroll' => 'bad', + 'device' => 'bad', + 'url' => 'http://bogus.example.com/', + 'autofocus[panel]' => 'badid!', + 'autofocus[section]' => '#sobad', + 'autofocus[control]' => '*horrible', + 'unrecognized' => 'yes', + ) ); + $this->assertEmpty( $this->manager->post_type->get_customizer_state_query_vars( $post_id ) ); + } } diff --git a/tests/php/test-class-post-type.php b/tests/php/test-class-post-type.php index 1186159d..f58f23f8 100644 --- a/tests/php/test-class-post-type.php +++ b/tests/php/test-class-post-type.php @@ -313,6 +313,9 @@ public function test_filter_post_row_actions() { 'data' => $data, 'status' => 'draft', ) ); + $post_type->set_customizer_state_query_vars( $post_id, array( + 'url' => home_url( 'hello-beautiful-world/' ), + ) ); $original_actions = array( 'inline hide-if-no-js' => '...', 'edit' => '', @@ -322,7 +325,9 @@ public function test_filter_post_row_actions() { $filtered_actions = apply_filters( 'post_row_actions', $original_actions, get_post( $post_id ) ); $this->assertArrayNotHasKey( 'inline hide-if-no-js', $filtered_actions ); $this->assertArrayHasKey( 'customize', $filtered_actions ); + $this->assertContains( 'hello-beautiful-world', $filtered_actions['customize'] ); $this->assertArrayHasKey( 'front-view', $filtered_actions ); + $this->assertContains( 'hello-beautiful-world', $filtered_actions['front-view'] ); wp_set_current_user( $subscriber_user_id ); $filtered_actions = apply_filters( 'post_row_actions', $original_actions, get_post( $post_id ) ); @@ -423,7 +428,7 @@ public function test_render_data_metabox() { ob_start(); $post_type->render_data_metabox( get_post( $post_id ) ); $metabox_content = ob_get_clean(); - $this->assertContains( 'snapshot was made when a different theme was active', $metabox_content ); + $this->assertContains( 'changeset was made when a different theme was active', $metabox_content ); } /**