diff --git a/amp.php b/amp.php index 92c27a5a061..a65a17b51f7 100644 --- a/amp.php +++ b/amp.php @@ -278,7 +278,7 @@ function amp_deactivate() { } } - flush_rewrite_rules(); + flush_rewrite_rules( false ); } /* @@ -333,38 +333,42 @@ function amp_init() { */ do_action( 'amp_init' ); - add_rewrite_endpoint( amp_get_slug(), EP_PERMALINK ); - add_filter( 'allowed_redirect_hosts', array( 'AMP_HTTP', 'filter_allowed_redirect_hosts' ) ); AMP_HTTP::purge_amp_query_vars(); AMP_HTTP::send_cors_headers(); AMP_HTTP::handle_xhr_request(); AMP_Theme_Support::init(); AMP_Validation_Manager::init(); - AMP_Post_Type_Support::add_post_type_support(); - AMP_Story_Post_Type::register(); AMP_Service_Worker::init(); - add_action( 'init', array( 'AMP_Post_Type_Support', 'add_post_type_support' ), 1000 ); // After post types have been defined. - - if ( defined( 'WP_CLI' ) && WP_CLI ) { - WP_CLI::add_command( 'amp', new AMP_CLI() ); - } - - add_filter( 'request', 'amp_force_query_var_value' ); add_action( 'admin_init', 'AMP_Options_Manager::register_settings' ); - add_action( 'wp_loaded', 'amp_editor_core_blocks' ); - add_action( 'wp_loaded', 'amp_post_meta_box' ); - add_action( 'wp_loaded', 'amp_story_templates' ); add_action( 'wp_loaded', 'amp_add_options_menu' ); add_action( 'wp_loaded', 'amp_admin_pointer' ); - add_action( 'parse_query', 'amp_correct_query_when_is_front_page' ); - add_action( 'admin_bar_menu', 'amp_add_admin_bar_view_link', 100 ); + add_action( 'wp_loaded', 'amp_post_meta_box' ); // Used in both Website and Stories experiences. + + if ( AMP_Options_Manager::is_website_experience_enabled() ) { + add_rewrite_endpoint( amp_get_slug(), EP_PERMALINK ); + AMP_Post_Type_Support::add_post_type_support(); + add_action( 'init', array( 'AMP_Post_Type_Support', 'add_post_type_support' ), 1000 ); // After post types have been defined. + add_action( 'parse_query', 'amp_correct_query_when_is_front_page' ); + add_action( 'admin_bar_menu', 'amp_add_admin_bar_view_link', 100 ); + add_action( 'wp_loaded', 'amp_editor_core_blocks' ); + add_filter( 'request', 'amp_force_query_var_value' ); + + // Add actions for reader mode templates. + add_action( 'wp', 'amp_maybe_add_actions' ); + + // Redirect the old url of amp page to the updated url. + add_filter( 'old_slug_redirect_url', 'amp_redirect_old_slug_to_new_url' ); + } - // Redirect the old url of amp page to the updated url. - add_filter( 'old_slug_redirect_url', 'amp_redirect_old_slug_to_new_url' ); + if ( AMP_Options_Manager::is_stories_experience_enabled() ) { + AMP_Story_Post_Type::register(); + add_action( 'wp_loaded', 'amp_story_templates' ); + } - // Add actions for legacy post templates. - add_action( 'wp', 'amp_maybe_add_actions' ); + if ( defined( 'WP_CLI' ) && WP_CLI ) { + WP_CLI::add_command( 'amp', new AMP_CLI() ); + } /* * Broadcast plugin updates. @@ -513,7 +517,7 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) { * * add_theme_support( AMP_Theme_Support::SLUG ); * - * This will serve templates in native AMP, allowing you to use AMP components in your theme templates. + * This will serve templates in AMP-first, allowing you to use AMP components in your theme templates. * If you want to make available in transitional mode, where templates are served in AMP or non-AMP documents, do: * * add_theme_support( AMP_Theme_Support::SLUG, array( @@ -526,7 +530,7 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) { * 'template_dir' => 'amp', * ) ); * - * If you want to have AMP-specific templates in addition to serving native AMP, do: + * If you want to have AMP-specific templates in addition to serving AMP-first, do: * * add_theme_support( AMP_Theme_Support::SLUG, array( * 'paired' => false, @@ -549,7 +553,7 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) { * ) ); * * @see AMP_Theme_Support::read_theme_support() - * @return boolean Whether this is in AMP 'canonical' mode, that is whether it is native and there is not separate AMP URL current URL. + * @return boolean Whether this is in AMP 'canonical' mode, that is whether it is AMP-first and there is not a separate (paired) AMP URL. */ function amp_is_canonical() { if ( ! current_theme_supports( AMP_Theme_Support::SLUG ) ) { @@ -557,8 +561,8 @@ function amp_is_canonical() { } $args = AMP_Theme_Support::get_theme_support_args(); - if ( isset( $args['paired'] ) ) { - return empty( $args['paired'] ); + if ( isset( $args[ AMP_Theme_Support::PAIRED_FLAG ] ) ) { + return empty( $args[ AMP_Theme_Support::PAIRED_FLAG ] ); } // If there is a template_dir, then transitional mode is implied. diff --git a/assets/src/block-editor/index.js b/assets/src/block-editor/index.js index 73bd6087ef3..b50f7b51030 100644 --- a/assets/src/block-editor/index.js +++ b/assets/src/block-editor/index.js @@ -14,22 +14,31 @@ import { addAMPAttributes, addAMPExtraProps, filterBlocksEdit, filterBlocksSave import { getMinimumFeaturedImageDimensions } from '../common/helpers'; import './store'; +const { + isWebsiteEnabled, + isStoriesEnabled, + isStandardMode, +} = select( 'amp/block-editor' ); + const { ampLatestStoriesBlockData } = window; -addFilter( 'blocks.registerBlockType', 'ampEditorBlocks/addAttributes', addAMPAttributes ); -addFilter( 'blocks.getSaveElement', 'ampEditorBlocks/filterSave', filterBlocksSave ); -addFilter( 'editor.BlockEdit', 'ampEditorBlocks/filterEdit', filterBlocksEdit, 20 ); -addFilter( 'blocks.getSaveContent.extraProps', 'ampEditorBlocks/addExtraAttributes', addAMPExtraProps ); -addFilter( 'editor.PostFeaturedImage', 'ampEditorBlocks/withFeaturedImageNotice', withFeaturedImageNotice ); -addFilter( 'editor.MediaUpload', 'ampEditorBlocks/addCroppedFeaturedImage', ( InitialMediaUpload ) => withCroppedFeaturedImage( InitialMediaUpload, getMinimumFeaturedImageDimensions() ) ); +// Add filters if AMP for Website experience is enabled. +if ( isWebsiteEnabled() ) { + const plugins = require.context( './plugins', true, /.*\.js$/ ); -const plugins = require.context( './plugins', true, /.*\.js$/ ); + plugins.keys().forEach( ( modulePath ) => { + const { name, render, icon } = plugins( modulePath ); -plugins.keys().forEach( ( modulePath ) => { - const { name, render, icon } = plugins( modulePath ); + registerPlugin( name, { icon, render } ); + } ); - registerPlugin( name, { icon, render } ); -} ); + addFilter( 'blocks.registerBlockType', 'ampEditorBlocks/addAttributes', addAMPAttributes ); + addFilter( 'blocks.getSaveElement', 'ampEditorBlocks/filterSave', filterBlocksSave ); + addFilter( 'editor.BlockEdit', 'ampEditorBlocks/filterEdit', filterBlocksEdit, 20 ); + addFilter( 'blocks.getSaveContent.extraProps', 'ampEditorBlocks/addExtraAttributes', addAMPExtraProps ); + addFilter( 'editor.PostFeaturedImage', 'ampEditorBlocks/withFeaturedImageNotice', withFeaturedImageNotice ); + addFilter( 'editor.MediaUpload', 'ampEditorBlocks/addCroppedFeaturedImage', ( InitialMediaUpload ) => withCroppedFeaturedImage( InitialMediaUpload, getMinimumFeaturedImageDimensions() ) ); +} /* * If there's no theme support, unregister blocks that are only meant for AMP. @@ -52,14 +61,18 @@ const blocks = require.context( './blocks', true, /(? { const { name, settings } = blocks( modulePath ); - // Prevent registering latest-stories block if not enabled. - if ( 'amp/amp-latest-stories' === name && typeof ampLatestStoriesBlockData === 'undefined' ) { - return; - } + const isLatestStoriesBlock = 'amp/amp-latest-stories' === name; - const blockRequiresAmp = AMP_DEPENDENT_BLOCKS.includes( name ); + const shouldRegister = ( + ( + isWebsiteEnabled() && isStandardMode() && AMP_DEPENDENT_BLOCKS.includes( name ) + ) || + ( + isStoriesEnabled() && isLatestStoriesBlock && typeof ampLatestStoriesBlockData !== 'undefined' + ) + ); - if ( ! blockRequiresAmp || select( 'amp/block-editor' ).isNativeAMP() ) { + if ( shouldRegister ) { registerBlockType( name, settings ); } } ); diff --git a/assets/src/block-editor/store/selectors.js b/assets/src/block-editor/store/selectors.js index cd0520a4375..084cb8d01c6 100644 --- a/assets/src/block-editor/store/selectors.js +++ b/assets/src/block-editor/store/selectors.js @@ -1,41 +1,65 @@ /** - * Returns the block validation errors for a given clientId. + * Returns whether the current theme has AMP support. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Editor state. * - * @return {Array} Block validation errors. + * @return {boolean} Whether the current theme has AMP support. */ -export function getBlockValidationErrors( state, clientId ) { - return state.errorsByClientId[ clientId ] || []; +export function hasThemeSupport( state ) { + return Boolean( state.hasThemeSupport ); } /** - * Returns whether the current theme has AMP support. + * Returns whether the current site is in Standard mode (AMP-first) as opposed to Transitional (paired). * * @param {Object} state Editor state. * - * @return {boolean} Whether the current theme has AMP support. + * @return {boolean} Whether the current site is AMP-first. */ -export function hasThemeSupport( state ) { - return Boolean( state.hasThemeSupport ); +export function isStandardMode( state ) { + return Boolean( state.isStandardMode ); +} + +/** + * Returns whether the website experience is enabled. + * + * @param {Object} state Editor state. + * + * @return {boolean} Whether website experienced enabled. + */ +export function isWebsiteEnabled( state ) { + return Boolean( state.isWebsiteEnabled ); } /** - * Returns whether the current site uses native AMP. + * Returns whether the stories experience is enabled. * * @param {Object} state Editor state. * - * @return {boolean} Whether the current site uses native AMP. + * @return {boolean} Whether stories experienced enabled. */ -export function isNativeAMP( state ) { - return Boolean( state.isNativeAMP ); +export function isStoriesEnabled( state ) { + return Boolean( state.isStoriesEnabled ); } +/** + * Returns the default AMP status. + * + * @param {Object} state Editor state. + * + * @return {string} The default AMP status. + */ export function getDefaultStatus( state ) { return state.defaultStatus; } +/** + * Returns the possible AMP statuses. + * + * @param {Object} state Editor state. + * + * @return {string[]} The possible AMP statuses, 'enabled' and 'disabled'. + */ export function getPossibleStatuses( state ) { return state.possibleStatuses; } diff --git a/assets/src/block-editor/store/test/selectors.js b/assets/src/block-editor/store/test/selectors.js index b299d9316f5..f6bbad97c7c 100644 --- a/assets/src/block-editor/store/test/selectors.js +++ b/assets/src/block-editor/store/test/selectors.js @@ -2,11 +2,47 @@ * Internal dependencies */ import { + hasThemeSupport, + isStandardMode, + isWebsiteEnabled, + isStoriesEnabled, getDefaultStatus, getPossibleStatuses, } from '../selectors'; describe( 'selectors', () => { + describe( 'hasThemeSupport', () => { + it( 'should return whether the theme has AMP support', () => { + const state = { hasThemeSupport: false }; + + expect( hasThemeSupport( state ) ).toBe( false ); + } ); + } ); + + describe( 'isStandardMode', () => { + it( 'should return whether standard mode is enabled', () => { + const state = { isStandardMode: true }; + + expect( isStandardMode( state ) ).toBe( true ); + } ); + } ); + + describe( 'isWebsiteEnabled', () => { + it( 'should return whether the website format is enabled', () => { + const state = { isWebsiteEnabled: false }; + + expect( isWebsiteEnabled( state ) ).toBe( false ); + } ); + } ); + + describe( 'isStoriesEnabled', () => { + it( 'should return whether the stories format is enabled', () => { + const state = { isStoriesEnabled: false }; + + expect( isStoriesEnabled( state ) ).toBe( false ); + } ); + } ); + describe( 'getDefaultStatus', () => { it( 'should return the default AMP status', () => { const state = { defaultStatus: 'enabled' }; diff --git a/assets/src/block-validation/helpers/index.js b/assets/src/block-validation/helpers/index.js index 20c207c1ff6..86fd1ac6f19 100644 --- a/assets/src/block-validation/helpers/index.js +++ b/assets/src/block-validation/helpers/index.js @@ -154,6 +154,7 @@ export const updateValidationErrors = () => { export const maybeDisplayNotice = () => { const { getValidationErrors, isSanitizationAutoAccepted, getReviewLink } = select( 'amp/block-validation' ); const { createWarningNotice } = dispatch( 'core/notices' ); + const { getCurrentPost } = select( 'core/editor' ); const validationErrors = getValidationErrors(); const validationErrorCount = validationErrors.length; @@ -174,54 +175,56 @@ export const maybeDisplayNotice = () => { const blockValidationErrors = validationErrors.filter( ( { clientId } ) => clientId ); const blockValidationErrorCount = blockValidationErrors.length; - if ( blockValidationErrorCount > 0 ) { - noticeMessage += ' ' + sprintf( - /* translators: %s: number of block errors. */ - _n( - '%s issue is directly due to content here.', - '%s issues are directly due to content here.', - blockValidationErrorCount, - 'amp' - ), - blockValidationErrorCount - ); - } else if ( validationErrors.length === 1 ) { - noticeMessage += ' ' + __( 'The issue is not directly due to content here.', 'amp' ); - } else { - noticeMessage += ' ' + __( 'The issues are not directly due to content here.', 'amp' ); - } - - noticeMessage += ' '; - - if ( isSanitizationAutoAccepted() ) { - const rejectedBlockValidationErrors = blockValidationErrors.filter( ( error ) => { - return ( - VALIDATION_ERROR_NEW_REJECTED_STATUS === error.status || - VALIDATION_ERROR_ACK_REJECTED_STATUS === error.status - ); - } ); - - const rejectedValidationErrors = validationErrors.filter( ( error ) => { - return ( - VALIDATION_ERROR_NEW_REJECTED_STATUS === error.status || - VALIDATION_ERROR_ACK_REJECTED_STATUS === error.status + if ( 'amp_story' !== getCurrentPost().type ) { + if ( blockValidationErrorCount > 0 ) { + noticeMessage += ' ' + sprintf( + /* translators: %s: number of block errors. */ + _n( + '%s issue is directly due to content here.', + '%s issues are directly due to content here.', + blockValidationErrorCount, + 'amp' + ), + blockValidationErrorCount ); - } ); - - const totalRejectedErrorsCount = rejectedBlockValidationErrors.length + rejectedValidationErrors.length; + } else if ( validationErrors.length === 1 ) { + noticeMessage += ' ' + __( 'The issue is not directly due to content here.', 'amp' ); + } else { + noticeMessage += ' ' + __( 'The issues are not directly due to content here.', 'amp' ); + } - if ( totalRejectedErrorsCount === 0 ) { - noticeMessage += __( 'However, your site is configured to automatically accept sanitization of the offending markup.', 'amp' ); + noticeMessage += ' '; + + if ( isSanitizationAutoAccepted() ) { + const rejectedBlockValidationErrors = blockValidationErrors.filter( ( error ) => { + return ( + VALIDATION_ERROR_NEW_REJECTED_STATUS === error.status || + VALIDATION_ERROR_ACK_REJECTED_STATUS === error.status + ); + } ); + + const rejectedValidationErrors = validationErrors.filter( ( error ) => { + return ( + VALIDATION_ERROR_NEW_REJECTED_STATUS === error.status || + VALIDATION_ERROR_ACK_REJECTED_STATUS === error.status + ); + } ); + + const totalRejectedErrorsCount = rejectedBlockValidationErrors.length + rejectedValidationErrors.length; + + if ( totalRejectedErrorsCount === 0 ) { + noticeMessage += __( 'However, your site is configured to automatically accept sanitization of the offending markup.', 'amp' ); + } else { + noticeMessage += _n( + 'Your site is configured to automatically accept sanitization errors, but this error could be from when auto-acceptance was not selected, or from manually rejecting an error.', + 'Your site is configured to automatically accept sanitization errors, but these errors could be from when auto-acceptance was not selected, or from manually rejecting an error.', + validationErrors.length, + 'amp' + ); + } } else { - noticeMessage += _n( - 'Your site is configured to automatically accept sanitization errors, but this error could be from when auto-acceptance was not selected, or from manually rejecting an error.', - 'Your site is configured to automatically accept sanitization errors, but these errors could be from when auto-acceptance was not selected, or from manually rejecting an error.', - validationErrors.length, - 'amp' - ); + noticeMessage += __( 'Non-accepted validation errors prevent AMP from being served, and the user will be redirected to the non-AMP version.', 'amp' ); } - } else { - noticeMessage += __( 'Non-accepted validation errors prevent AMP from being served, and the user will be redirected to the non-AMP version.', 'amp' ); } const options = { diff --git a/assets/src/block-validation/store/selectors.js b/assets/src/block-validation/store/selectors.js index 28c66dc5e4a..38977548ce9 100644 --- a/assets/src/block-validation/store/selectors.js +++ b/assets/src/block-validation/store/selectors.js @@ -35,7 +35,7 @@ export function getReviewLink( state ) { /** * Returns whether sanitization errors are auto-accepted. * - * Auto-acceptance is from either checking 'Automatically accept sanitization...' or from being in Native mode. + * Auto-acceptance is from either checking 'Automatically accept sanitization...' or from being in Standard mode. * * @param {Object} state Editor state. * diff --git a/includes/admin/class-amp-admin-pointers.php b/includes/admin/class-amp-admin-pointers.php index fb843352724..77f2b4b1918 100644 --- a/includes/admin/class-amp-admin-pointers.php +++ b/includes/admin/class-amp-admin-pointers.php @@ -64,7 +64,7 @@ private function get_pointers() { 'selector' => '#toplevel_page_amp-options', 'heading' => __( 'AMP', 'amp' ), 'subheading' => __( 'New AMP Template Modes', 'amp' ), - 'description' => __( 'You can now reuse your theme\'s templates and styles in AMP responses, in both “Transitional” and “Native” modes.', 'amp' ), + 'description' => __( 'You can now reuse your theme\'s templates and styles in AMP responses, in both “Transitional” and “Standard” modes.', 'amp' ), 'position' => array( 'align' => 'middle', ), @@ -87,7 +87,7 @@ private function get_pointers() { if ( 'toplevel_page_amp-options' === $hook_suffix ) { return false; } - return ! AMP_Options_Manager::get_option( 'enable_amp_stories' ); + return ! AMP_Options_Manager::is_stories_experience_enabled(); }, ) ), @@ -104,7 +104,7 @@ private function get_pointers() { if ( 'edit.php' === $hook_suffix && AMP_Story_Post_Type::POST_TYPE_SLUG === filter_input( INPUT_GET, 'post_type' ) ) { return false; } - return AMP_Story_Post_Type::has_required_block_capabilities() && AMP_Options_Manager::get_option( 'enable_amp_stories' ); + return AMP_Options_Manager::is_stories_experience_enabled(); }, ) ), diff --git a/includes/admin/class-amp-editor-blocks.php b/includes/admin/class-amp-editor-blocks.php index dc538acc17a..84939bd010f 100644 --- a/includes/admin/class-amp-editor-blocks.php +++ b/includes/admin/class-amp-editor-blocks.php @@ -44,11 +44,11 @@ public function init() { add_filter( 'wp_kses_allowed_html', array( $this, 'whitelist_block_atts_in_wp_kses_allowed_html' ), 10, 2 ); /* - * Dirty AMP is required when a site is in native mode but not all templates are being served + * Dirty AMP is required when a site is in AMP-first mode but not all templates are being served * as AMP. In particular, if a single post is using AMP-specific Gutenberg Blocks which make * use of AMP components, and the singular template is served as AMP but the blog page is not, * then the non-AMP blog page need to load the AMP runtime scripts so that the AMP components - * in the posts displayed there will be rendered properly. This is only relevant on native AMP + * in the posts displayed there will be rendered properly. This is only relevant on AMP-first * sites because the AMP Gutenberg blocks are only made available in that mode; they are not * presented in the Gutenberg inserter in transitional mode. In general, using AMP components in * non-AMP documents is still not officially supported, so it's occurrence is being minimized diff --git a/includes/admin/class-amp-post-meta-box.php b/includes/admin/class-amp-post-meta-box.php index 82c20fd63ce..c07b03ac0fd 100644 --- a/includes/admin/class-amp-post-meta-box.php +++ b/includes/admin/class-amp-post-meta-box.php @@ -230,8 +230,10 @@ public function enqueue_block_assets() { 'possibleStatuses' => array( self::ENABLED_STATUS, self::DISABLED_STATUS ), 'defaultStatus' => $enabled_status, 'errorMessages' => $error_messages, + 'isWebsiteEnabled' => AMP_Options_Manager::is_website_experience_enabled(), + 'isStoriesEnabled' => AMP_Options_Manager::is_stories_experience_enabled(), 'hasThemeSupport' => current_theme_supports( AMP_Theme_Support::SLUG ), - 'isNativeAMP' => amp_is_canonical(), + 'isStandardMode' => amp_is_canonical(), ); wp_localize_script( diff --git a/includes/admin/functions.php b/includes/admin/functions.php index 5c46ae7dd81..beaa1e24bc8 100644 --- a/includes/admin/functions.php +++ b/includes/admin/functions.php @@ -21,7 +21,7 @@ * And this does not need to toggle between the AMP and normal display. */ function amp_init_customizer() { - if ( amp_is_canonical() ) { + if ( amp_is_canonical() || ! AMP_Options_Manager::is_website_experience_enabled() ) { return; } diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index ef6909b64d6..6132f701e60 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -243,7 +243,7 @@ function post_supports_amp( $post ) { * to determine the queried object is able to be served as AMP. If 'amp' theme support is not * present, this function returns true just if the query var is present. If theme support is * present, then it returns true in transitional mode if an AMP template is available and the query - * var is present, or else in native mode if just the template is available. + * var is present, or else in standard mode if just the template is available. * * @return bool Whether it is the AMP endpoint. * @global string $pagenow @@ -318,7 +318,7 @@ function is_amp_endpoint() { return $has_amp_query_var; } - // When there is no query var and AMP is not canonical/native, then this is definitely not an AMP endpoint. + // When there is no query var and AMP is not canonical (AMP-first), then this is definitely not an AMP endpoint. if ( ! $has_amp_query_var && ! amp_is_canonical() ) { return false; } @@ -374,14 +374,22 @@ function amp_get_boilerplate_code() { * @since 1.0 Add template mode. */ function amp_add_generator_metadata() { - if ( amp_is_canonical() ) { - $mode = 'native'; + $content = sprintf( 'AMP Plugin v%s', AMP__VERSION ); + + if ( ! AMP_Options_Manager::is_website_experience_enabled() ) { + $mode = 'none'; + } elseif ( amp_is_canonical() ) { + $mode = 'standard'; } elseif ( current_theme_supports( AMP_Theme_Support::SLUG ) ) { $mode = 'transitional'; } else { $mode = 'reader'; } - printf( '', esc_attr( sprintf( 'AMP Plugin v%s; mode=%s', AMP__VERSION, $mode ) ) ); + $content .= sprintf( '; mode=%s', $mode ); + + $content .= sprintf( '; experiences=%s', implode( ',', AMP_Options_Manager::get_option( 'experiences' ) ) ); + + printf( '', esc_attr( $content ) ); } /** diff --git a/includes/class-amp-story-post-type.php b/includes/class-amp-story-post-type.php index 8e83da82525..90b98fb450b 100644 --- a/includes/class-amp-story-post-type.php +++ b/includes/class-amp-story-post-type.php @@ -120,7 +120,7 @@ public static function has_required_block_capabilities() { * @return void */ public static function register() { - if ( ! AMP_Options_Manager::get_option( 'enable_amp_stories' ) || ! self::has_required_block_capabilities() ) { + if ( ! AMP_Options_Manager::is_stories_experience_enabled() || ! self::has_required_block_capabilities() ) { return; } @@ -495,11 +495,13 @@ public static function enqueue_general_styles() { * @return array Modified editor settings. */ public static function filter_block_editor_settings( $editor_settings, $post ) { - if ( self::POST_TYPE_SLUG === $post->post_type ) { - unset( $editor_settings['fontSizes'], $editor_settings['colors'] ); + if ( self::POST_TYPE_SLUG !== get_current_screen()->post_type ) { + return $editor_settings; } - if ( get_current_screen()->is_block_editor && isset( $editor_settings['styles'] ) ) { + unset( $editor_settings['fontSizes'], $editor_settings['colors'] ); + + if ( isset( $editor_settings['styles'] ) ) { foreach ( $editor_settings['styles'] as $key => $style ) { // If the baseURL is not set or if the URL doesn't include theme styles, move to next. @@ -518,6 +520,7 @@ public static function filter_block_editor_settings( $editor_settings, $post ) { } } } + return $editor_settings; } diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 974efd31beb..63fb3cfcde0 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -54,6 +54,30 @@ class AMP_Theme_Support { */ const CACHE_MISS_URL_OPTION = 'amp_cache_miss_url'; + /** + * Slug identifying standard website mode. + * + * @since 1.2 + * @var string + */ + const STANDARD_MODE_SLUG = 'standard'; + + /** + * Slug identifying transitional website mode. + * + * @since 1.2 + * @var string + */ + const TRANSITIONAL_MODE_SLUG = 'transitional'; + + /** + * Flag used in args passed to add_theme_support('amp') to indicate transitional mode supported. + * + * @since 1.2 + * @var string + */ + const PAIRED_FLAG = 'paired'; + /** * Sanitizer classes. * @@ -110,12 +134,23 @@ class AMP_Theme_Support { protected static $is_output_buffering = false; /** - * Theme support options that were added via option. + * Theme support mode that was added via option. + * + * This should be either null (reader), 'standard', or 'transitional'. * * @since 1.0 - * @var bool + * @var null|string */ - protected static $support_added_via_option = false; + protected static $support_added_via_option = null; + + /** + * Theme support mode which was added via the theme. + * + * This should be either null (reader), 'standard', or 'transitional'. + * + * @var null|string + */ + protected static $support_added_via_theme = null; /** * Initialize. @@ -127,7 +162,7 @@ public static function init() { self::$init_start_time = microtime( true ); - if ( current_theme_supports( self::SLUG ) ) { + if ( AMP_Options_Manager::is_website_experience_enabled() && current_theme_supports( self::SLUG ) ) { // Ensure extra theme support for core themes is in place. AMP_Core_Theme_Sanitizer::extend_theme_support(); @@ -141,7 +176,7 @@ public static function init() { * action to template_redirect--the wp action--is used instead. */ add_action( 'wp', array( __CLASS__, 'finish_init' ), PHP_INT_MAX ); - } elseif ( AMP_Options_Manager::get_option( 'enable_amp_stories' ) ) { + } elseif ( AMP_Options_Manager::is_stories_experience_enabled() ) { add_action( 'wp', function () { @@ -159,28 +194,84 @@ function () { * * @since 1.0 * @see AMP_Theme_Support::read_theme_support() + * @see AMP_Theme_Support::get_support_mode() + * @deprecated Use AMP_Theme_Support::get_support_mode_added_via_option(). * * @return bool Support added via option. */ public static function is_support_added_via_option() { + _deprecated_function( __METHOD__, '1.2', 'AMP_Theme_Support::get_support_mode_added_via_option' ); + return null !== self::$support_added_via_option; + } + + /** + * Get the theme support mode added via admin option. + * + * @return null|string Support added via option, with null meaning Reader, and otherwise being 'standard' or 'transitional'. + * @see AMP_Theme_Support::read_theme_support() + * @see AMP_Theme_Support::TRANSITIONAL_MODE_SLUG + * @see AMP_Theme_Support::STANDARD_MODE_SLUG + * + * @since 1.2 + */ + public static function get_support_mode_added_via_option() { return self::$support_added_via_option; } + /** + * Get the theme support mode added via admin option. + * + * @return null|string Support added via option, with null meaning Reader, and otherwise being 'standard' or 'transitional'. + * @see AMP_Theme_Support::read_theme_support() + * @see AMP_Theme_Support::TRANSITIONAL_MODE_SLUG + * @see AMP_Theme_Support::STANDARD_MODE_SLUG + * + * @since 1.2 + */ + public static function get_support_mode_added_via_theme() { + return self::$support_added_via_theme; + } + + /** + * Get theme support mode. + * + * @return string Theme support mode. + * @see AMP_Theme_Support::read_theme_support() + * @see AMP_Theme_Support::TRANSITIONAL_MODE_SLUG + * @see AMP_Theme_Support::STANDARD_MODE_SLUG + * + * @since 1.2 + */ + public static function get_support_mode() { + $theme_support = self::get_support_mode_added_via_option(); + if ( ! $theme_support ) { + $theme_support = self::get_support_mode_added_via_theme(); + } + if ( ! $theme_support ) { + $theme_support = 'disabled'; + } + return $theme_support; + } + /** * Check theme support args or add theme support if option is set in the admin. * * The DB option is only considered if the theme does not already explicitly support AMP. * - * @see AMP_Theme_Support::is_support_added_via_option() + * @see AMP_Theme_Support::get_support_mode_added_via_theme() + * @see AMP_Theme_Support::get_support_mode_added_via_option() * @see AMP_Post_Type_Support::add_post_type_support() For where post type support is added, since it is irrespective of theme support. */ public static function read_theme_support() { + self::$support_added_via_theme = null; + self::$support_added_via_option = null; + $theme_support_option = AMP_Options_Manager::get_option( 'theme_support' ); if ( current_theme_supports( self::SLUG ) ) { $args = self::get_theme_support_args(); // Validate theme support usage. - $keys = array( 'template_dir', 'comments_live_list', 'paired', 'templates_supported', 'available_callback', 'service_worker', 'nav_menu_toggle', 'nav_menu_dropdown' ); + $keys = array( 'template_dir', 'comments_live_list', self::PAIRED_FLAG, 'templates_supported', 'available_callback', 'service_worker', 'nav_menu_toggle', 'nav_menu_dropdown' ); if ( count( array_diff( array_keys( $args ), $keys ) ) !== 0 ) { _doing_it_wrong( @@ -209,16 +300,33 @@ public static function read_theme_support() { '1.0' ); } - self::$support_added_via_option = false; + + $is_paired = ! empty( $args[ self::PAIRED_FLAG ] ); + + self::$support_added_via_theme = $is_paired ? self::TRANSITIONAL_MODE_SLUG : self::STANDARD_MODE_SLUG; + + /* + * If the theme has transitional support, allow the user to opt for AMP-first mode via an option, since a theme + * in transitional mode entails that it supports serving templates as both AMP and non-AMP, and this it is + * able to serve AMP-first pages just as well as paired pages. Otherwise, consider that the the mode was + * not set at all via option. + */ + self::$support_added_via_option = ( $is_paired && self::STANDARD_MODE_SLUG === $theme_support_option ) ? self::STANDARD_MODE_SLUG : null; + if ( self::STANDARD_MODE_SLUG === self::$support_added_via_option ) { + $args[ self::PAIRED_FLAG ] = false; + add_theme_support( 'amp', $args ); + } } elseif ( 'disabled' !== $theme_support_option ) { + $is_paired = ( self::TRANSITIONAL_MODE_SLUG === $theme_support_option ); add_theme_support( self::SLUG, array( - 'paired' => ( 'paired' === $theme_support_option ), + self::PAIRED_FLAG => $is_paired, ) ); - self::$support_added_via_option = true; + self::$support_added_via_option = $is_paired ? self::TRANSITIONAL_MODE_SLUG : self::STANDARD_MODE_SLUG; } elseif ( AMP_Validation_Manager::is_theme_support_forced() ) { + self::$support_added_via_option = self::STANDARD_MODE_SLUG; add_theme_support( self::SLUG ); } } @@ -239,7 +347,7 @@ public static function get_theme_support_args() { $support = get_theme_support( self::SLUG ); if ( true === $support ) { return array( - 'paired' => false, + self::PAIRED_FLAG => false, ); } if ( ! isset( $support[0] ) || ! is_array( $support[0] ) ) { @@ -303,7 +411,7 @@ public static function ensure_proper_amp_location( $exit = true ) { if ( amp_is_canonical() || is_singular( AMP_Story_Post_Type::POST_TYPE_SLUG ) ) { /* - * When AMP native/canonical, then when there is an /amp/ endpoint or ?amp URL param, + * When AMP-first/canonical, then when there is an /amp/ endpoint or ?amp URL param, * then a redirect needs to be done to the URL without any AMP indicator in the URL. * Permanent redirect is used for unauthenticated users since switching between modes * should happen infrequently. For admin users, this is kept temporary to allow them @@ -2011,7 +2119,7 @@ public static function prepare_response( $response, $args = array() ) { if ( $blocking_error_count > 0 && ! AMP_Validation_Manager::should_validate_response() ) { /* - * In native AMP, strip html@amp attribute to prevent GSC from complaining about a validation error + * In AMP-first, strip html@amp attribute to prevent GSC from complaining about a validation error * already surfaced inside of WordPress. This is intended to not serve dirty AMP, but rather a * non-AMP document (intentionally not valid AMP) that contains the AMP runtime and AMP components. */ diff --git a/includes/options/class-amp-options-manager.php b/includes/options/class-amp-options-manager.php index ab2bc8792a0..aba46d863ff 100644 --- a/includes/options/class-amp-options-manager.php +++ b/includes/options/class-amp-options-manager.php @@ -17,12 +17,25 @@ class AMP_Options_Manager { */ const OPTION_NAME = 'amp-options'; + /** + * Slug for website experience. + */ + const WEBSITE_EXPERIENCE = 'website'; + + /** + * Slug for stories experience. + * + * @var + */ + const STORIES_EXPERIENCE = 'stories'; + /** * Default option values. * * @var array */ protected static $defaults = array( + 'experiences' => array( self::WEBSITE_EXPERIENCE ), 'theme_support' => 'disabled', 'supported_post_types' => array( 'post' ), 'analytics' => array(), @@ -30,7 +43,6 @@ class AMP_Options_Manager { 'all_templates_supported' => true, 'supported_templates' => array( 'is_singular' ), 'enable_response_caching' => true, - 'enable_amp_stories' => false, 'version' => AMP__VERSION, 'story_templates_version' => false, ); @@ -56,7 +68,7 @@ public static function register_settings() { } /** - * Flush rewrite rules if the supported_post_types have changed. + * Flush rewrite rules if the supported_post_types or experiences have changed. * * @since 0.6.2 * @@ -68,8 +80,17 @@ public static function maybe_flush_rewrite_rules( $old_options, $new_options ) { $new_post_types = isset( $new_options['supported_post_types'] ) ? $new_options['supported_post_types'] : array(); sort( $old_post_types ); sort( $new_post_types ); - if ( $old_post_types !== $new_post_types ) { - flush_rewrite_rules( false ); + $old_experiences = isset( $old_options['experiences'] ) ? $old_options['experiences'] : array(); + $new_experiences = isset( $new_options['experiences'] ) ? $new_options['experiences'] : array(); + sort( $old_experiences ); + sort( $new_experiences ); + if ( $old_post_types !== $new_post_types || $old_experiences !== $new_experiences ) { + if ( self::is_website_experience_enabled() ) { + add_rewrite_endpoint( amp_get_slug(), EP_PERMALINK ); + flush_rewrite_rules( false ); + } else { + amp_deactivate(); + } } } @@ -81,10 +102,26 @@ public static function maybe_flush_rewrite_rules( $old_options, $new_options ) { public static function get_options() { $options = get_option( self::OPTION_NAME, array() ); if ( empty( $options ) ) { - $options = array(); + $options = array(); // Ensure empty string becomes array. } self::$defaults['enable_response_caching'] = wp_using_ext_object_cache(); - return array_merge( self::$defaults, $options ); + + $options = array_merge( self::$defaults, $options ); + + // Migrate stories option from 1.2-beta. + if ( ! empty( $options['enable_amp_stories'] ) ) { + $options['experiences'][] = self::STORIES_EXPERIENCE; + unset( $options['enable_amp_stories'] ); + } + + // Migrate theme support slugs. + if ( 'native' === $options['theme_support'] ) { + $options['theme_support'] = AMP_Theme_Support::STANDARD_MODE_SLUG; + } elseif ( 'paired' === $options['theme_support'] ) { + $options['theme_support'] = AMP_Theme_Support::TRANSITIONAL_MODE_SLUG; + } + + return $options; } /** @@ -105,6 +142,32 @@ public static function get_option( $option, $default = false ) { return $amp_options[ $option ]; } + /** + * Determine whether website experience is enabled. + * + * @since 1.2 + * + * @return bool Enabled. + */ + public static function is_website_experience_enabled() { + return in_array( self::WEBSITE_EXPERIENCE, self::get_option( 'experiences' ), true ); + } + + /** + * Determine whether stories experience is enabled. + * + * @since 1.2 + * + * @return bool Enabled. + */ + public static function is_stories_experience_enabled() { + return ( + AMP_Story_Post_Type::has_required_block_capabilities() + && + in_array( self::STORIES_EXPERIENCE, self::get_option( 'experiences' ), true ) + ); + } + /** * Validate options. * @@ -118,11 +181,29 @@ public static function validate_options( $new_options ) { return $options; } + // Experiences. + if ( isset( $new_options['experiences'] ) && is_array( $new_options['experiences'] ) ) { + + // Validate the selected experiences. + $options['experiences'] = array_intersect( + $new_options['experiences'], + array( + self::WEBSITE_EXPERIENCE, + self::STORIES_EXPERIENCE, + ) + ); + + // At least one experience must be selected. + if ( empty( $options['experiences'] ) ) { + $options['experiences'] = array( self::WEBSITE_EXPERIENCE ); + } + } + // Theme support. $recognized_theme_supports = array( 'disabled', - 'paired', - 'native', + AMP_Theme_Support::TRANSITIONAL_MODE_SLUG, + AMP_Theme_Support::STANDARD_MODE_SLUG, ); if ( isset( $new_options['theme_support'] ) && in_array( $new_options['theme_support'], $recognized_theme_supports, true ) ) { $options['theme_support'] = $new_options['theme_support']; @@ -134,16 +215,17 @@ public static function validate_options( $new_options ) { } $options['auto_accept_sanitization'] = ! empty( $new_options['auto_accept_sanitization'] ); - $options['enable_amp_stories'] = ! empty( $new_options['enable_amp_stories'] ); // Validate post type support. - $options['supported_post_types'] = array(); - if ( isset( $new_options['supported_post_types'] ) ) { - foreach ( $new_options['supported_post_types'] as $post_type ) { - if ( ! post_type_exists( $post_type ) ) { - add_settings_error( self::OPTION_NAME, 'unknown_post_type', __( 'Unrecognized post type.', 'amp' ) ); - } else { - $options['supported_post_types'][] = $post_type; + if ( in_array( self::WEBSITE_EXPERIENCE, $options['experiences'], true ) || isset( $new_options['supported_post_types'] ) ) { + $options['supported_post_types'] = array(); + if ( isset( $new_options['supported_post_types'] ) ) { + foreach ( $new_options['supported_post_types'] as $post_type ) { + if ( ! post_type_exists( $post_type ) ) { + add_settings_error( self::OPTION_NAME, 'unknown_post_type', __( 'Unrecognized post type.', 'amp' ) ); + } else { + $options['supported_post_types'][] = $post_type; + } } } } @@ -232,8 +314,11 @@ public static function validate_options( $new_options ) { * @see add_settings_error() */ public static function check_supported_post_type_update_errors() { + if ( ! self::is_website_experience_enabled() ) { + return; + } - // If all templates are supported then skip check since all post types are also supported. This option only applies with native/transitional theme support. + // If all templates are supported then skip check since all post types are also supported. This option only applies with standard/transitional theme support. if ( self::get_option( 'all_templates_supported', false ) && 'disabled' !== self::get_option( 'theme_support' ) ) { return; } @@ -515,13 +600,13 @@ public static function handle_updated_theme_support_option() { AMP_Post_Type_Support::add_post_type_support(); // Ensure theme support flags are set properly according to the new mode so that proper AMP URL can be generated. - $has_theme_support = ( 'native' === $template_mode || 'paired' === $template_mode ); + $has_theme_support = ( AMP_Theme_Support::STANDARD_MODE_SLUG === $template_mode || AMP_Theme_Support::TRANSITIONAL_MODE_SLUG === $template_mode ); if ( $has_theme_support ) { $theme_support = current_theme_supports( AMP_Theme_Support::SLUG ); if ( ! is_array( $theme_support ) ) { $theme_support = array(); } - $theme_support['paired'] = 'paired' === $template_mode; + $theme_support['paired'] = AMP_Theme_Support::TRANSITIONAL_MODE_SLUG === $template_mode; add_theme_support( AMP_Theme_Support::SLUG, $theme_support ); } else { remove_theme_support( AMP_Theme_Support::SLUG ); // So that the amp_get_permalink() will work for reader mode URL. @@ -635,13 +720,13 @@ public static function handle_updated_theme_support_option() { } switch ( $template_mode ) { - case 'native': - $message = esc_html__( 'Native mode activated!', 'amp' ); + case AMP_Theme_Support::STANDARD_MODE_SLUG: + $message = esc_html__( 'Standard mode activated!', 'amp' ); if ( $review_messages ) { $message .= ' ' . join( ' ', $review_messages ); } break; - case 'paired': + case AMP_Theme_Support::TRANSITIONAL_MODE_SLUG: $message = esc_html__( 'Transitional mode activated!', 'amp' ); if ( $review_messages ) { $message .= ' ' . join( ' ', $review_messages ); @@ -651,7 +736,7 @@ public static function handle_updated_theme_support_option() { $message = wp_kses_post( sprintf( /* translators: %s is an AMP URL */ - __( 'Reader mode activated! View the AMP version of a recent post. It is recommended that you upgrade to Native or Transitional mode.', 'amp' ), + __( 'Reader mode activated! View the AMP version of a recent post. It is recommended that you upgrade to Standard or Transitional mode.', 'amp' ), esc_url( $url ) ) ); diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php index 0fe5726f5d8..169b615154f 100644 --- a/includes/options/class-amp-options-menu.php +++ b/includes/options/class-amp-options-menu.php @@ -76,14 +76,25 @@ public function add_menu_items() { AMP_Options_Manager::OPTION_NAME ); + add_settings_field( + 'experiences', + __( 'Experiences', 'amp' ), + array( $this, 'render_experiences' ), + AMP_Options_Manager::OPTION_NAME, + 'general', + array( + 'class' => 'experiences', + ) + ); + add_settings_field( 'theme_support', - __( 'Template Mode', 'amp' ), + __( 'Website Mode', 'amp' ), array( $this, 'render_theme_support' ), AMP_Options_Manager::OPTION_NAME, 'general', array( - 'class' => 'theme_support', + 'class' => 'amp-website-mode', ) ); @@ -109,15 +120,19 @@ public function add_menu_items() { ) ); - add_settings_field( - 'amp_stories', - __( 'Stories', 'amp' ), - array( $this, 'render_amp_stories' ), - AMP_Options_Manager::OPTION_NAME, - 'general', - array( - 'class' => 'amp-stories-field', - ) + add_action( + 'admin_print_styles', + function() { + ?> + + +
+
+
+ > + +
+
+ AMP Websites.', 'amp' ), + esc_url( 'https://amp.dev/about/websites' ) + ) + ); + ?> +
+
+ > + +
+
+ +
+

+ ' . $gutenberg . ''; + } + printf( + /* translators: %s: Gutenberg plugin name */ + esc_html__( 'To use stories, you currently must have the latest version of the %s plugin installed and activated.', 'amp' ), + $gutenberg // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +

+
+ + AMP Stories.', 'amp' ), + esc_url( 'https://amp.dev/about/stories' ) + ) + ); + ?> +
+
+ +
+ AMP-first and your canonical URLs are AMP! Depending on your theme/plugins, a varying level of development work may be required.', 'amp' ), esc_url( 'https://amp-wp.org/documentation/developing-wordpress-amp-sites/' ) ); + $standard_description = sprintf( __( 'The active theme integrates AMP as the framework for your site by using its templates and styles to render webpages. This means your site is AMP-first and your canonical URLs are AMP! Depending on your theme/plugins, a varying level of development work may be required.', 'amp' ), esc_url( 'https://amp-wp.org/documentation/developing-wordpress-amp-sites/' ) ); /* translators: %s: URL to the documentation. */ - $transitional_description = sprintf( __( 'Uses the active theme’s templates to generate non-AMP and AMP versions of your content, allowing for each canonical URL to have a corresponding (paired) AMP URL. This mode is useful to progressively transition towards a fully AMP-first site. Depending on your theme/plugins, a varying level of development work may be required.', 'amp' ), esc_url( 'https://amp-wp.org/documentation/developing-wordpress-amp-sites/' ) ); + $transitional_description = sprintf( __( 'The active theme’s templates are used to generate non-AMP and AMP versions of your content, allowing for each canonical URL to have a corresponding (paired) AMP URL. This mode is useful to progressively transition towards a fully AMP-first site. Depending on your theme/plugins, a varying level of development work may be required.', 'amp' ), esc_url( 'https://amp-wp.org/documentation/developing-wordpress-amp-sites/' ) ); $reader_description = __( 'Formerly called the classic mode, this mode generates paired AMP content using simplified templates which may not match the look-and-feel of your site. Only posts/pages can be served as AMP in Reader mode. No redirection is performed for mobile visitors; AMP pages are served by AMP consumption platforms.', 'amp' ); /* translators: %s: URL to the ecosystem page. */ $ecosystem_description = sprintf( __( 'For a list of themes and plugins that are known to be AMP compatible, please see the ecosystem page.' ), esc_url( 'https://amp-wp.org/ecosystem/' ) ); $builtin_support = in_array( get_template(), AMP_Core_Theme_Sanitizer::get_supported_themes(), true ); ?> - +

- +

- - - - - - - +

>
-

+

-

- -

+ + +

+ +

+
- > -
- +
- > + > @@ -206,49 +317,57 @@ public function render_theme_support() {
-
- > - -
-
- - - publish > 0 ) : ?> -
-

- %s', - esc_url( add_query_arg( 'post_type', AMP_Validated_URL_Post_Type::POST_TYPE_SLUG, admin_url( 'edit.php' ) ) ), - esc_html( get_post_type_object( AMP_Validated_URL_Post_Type::POST_TYPE_SLUG )->labels->name ) - ), + +

+ > + +
+
+ + + publish > 0 ) : ?> +
+

+ %s', - esc_url( - add_query_arg( - array( - 'taxonomy' => AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG, - 'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG, - ), - admin_url( 'edit-tags.php' ) - ) + /* translators: %1: link to invalid URLs. 2: link to validation errors. */ + __( 'View current site compatibility results for standard and transitional modes: %1$s and %2$s.', 'amp' ), + sprintf( + '%s', + esc_url( add_query_arg( 'post_type', AMP_Validated_URL_Post_Type::POST_TYPE_SLUG, admin_url( 'edit.php' ) ) ), + esc_html( get_post_type_object( AMP_Validated_URL_Post_Type::POST_TYPE_SLUG )->labels->name ) ), - esc_html( get_taxonomy( AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG )->labels->name ) + sprintf( + '%s', + esc_url( + add_query_arg( + array( + 'taxonomy' => AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG, + 'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG, + ), + admin_url( 'edit-tags.php' ) + ) + ), + esc_html( get_taxonomy( AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG )->labels->name ) + ) ) - ) - ); - ?> -

-
- -
+ ); + ?> +

+ + + +
+ + +

+ +

+
" value="">
-

+

@@ -314,26 +433,26 @@ public function render_validation_handling() {

> - +