diff --git a/assets/src/setup/pages/choose-reader-theme/index.js b/assets/src/setup/pages/choose-reader-theme/index.js
index 597cdc54a9b..444896851f1 100644
--- a/assets/src/setup/pages/choose-reader-theme/index.js
+++ b/assets/src/setup/pages/choose-reader-theme/index.js
@@ -1,9 +1,14 @@
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { useEffect, useContext, useMemo } from '@wordpress/element';
+/**
+ * External dependencies
+ */
+import { AMP_QUERY_VAR, DEFAULT_AMP_QUERY_VAR, LEGACY_THEME_SLUG, AMP_QUERY_VAR_CUSTOMIZED_LATE } from 'amp-setup'; // From WP inline script.
+
/**
* Internal dependencies
*/
@@ -32,10 +37,15 @@ export function ChooseReaderTheme() {
return;
}
- if ( themes && readerTheme && canGoForward === false ) {
- if ( themes.map( ( { slug } ) => slug ).includes( readerTheme ) ) {
- setCanGoForward( true );
- }
+ if (
+ themes &&
+ readerTheme &&
+ canGoForward === false &&
+ ! AMP_QUERY_VAR_CUSTOMIZED_LATE
+ ? themes.map( ( { slug } ) => slug ).includes( readerTheme )
+ : readerTheme === LEGACY_THEME_SLUG
+ ) {
+ setCanGoForward( true );
}
}, [ canGoForward, setCanGoForward, readerTheme, themes, themeSupport ] );
@@ -43,7 +53,7 @@ export function ChooseReaderTheme() {
const { availableThemes, unavailableThemes } = useMemo(
() => themes.reduce(
( collections, theme ) => {
- if ( theme.availability === 'non-installable' ) {
+ if ( ( AMP_QUERY_VAR_CUSTOMIZED_LATE && theme.slug !== LEGACY_THEME_SLUG ) || theme.availability === 'non-installable' ) {
collections.unavailableThemes.push( theme );
} else {
collections.availableThemes.push( theme );
@@ -96,7 +106,23 @@ export function ChooseReaderTheme() {
{ __( 'Unavailable themes', 'amp' ) }
- { __( 'The following themes are compatible but cannot be installed automatically. Please install them manually, or contact your host if you are not able to do so.', 'amp' ) }
+ { AMP_QUERY_VAR_CUSTOMIZED_LATE
+ /* dangerouslySetInnerHTML reason: Injection of code tags. */
+ ? ${ AMP_QUERY_VAR }`,
+ `${ DEFAULT_AMP_QUERY_VAR }
`,
+ 'AMP_QUERY_VAR
',
+ 'amp_query_var
',
+ 'plugins_loaded
',
+ ),
+ } }
+ />
+ : __( 'The following themes are compatible but cannot be installed automatically. Please install them manually, or contact your host if you are not able to do so.', 'amp' )
+ }
{ unavailableThemes.map( ( theme ) => (
diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php
index 7fb27916b48..f71c60bd31c 100644
--- a/includes/amp-helper-functions.php
+++ b/includes/amp-helper-functions.php
@@ -538,10 +538,6 @@ function amp_redirect_old_slug_to_new_url( $link ) {
* @return string Slug used for query var, endpoint, and post type support.
*/
function amp_get_slug() {
- if ( defined( 'AMP_QUERY_VAR' ) ) {
- return AMP_QUERY_VAR;
- }
-
/**
* Filter the AMP query variable.
*
@@ -551,11 +547,7 @@ function amp_get_slug() {
*
* @param string $query_var The AMP query variable.
*/
- $query_var = apply_filters( 'amp_query_var', QueryVars::AMP );
-
- define( 'AMP_QUERY_VAR', $query_var );
-
- return $query_var;
+ return apply_filters( 'amp_query_var', defined( 'AMP_QUERY_VAR' ) ? AMP_QUERY_VAR : QueryVars::AMP );
}
/**
diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php
index 4f7fe5dd757..42856efd8b1 100644
--- a/includes/options/class-amp-options-menu.php
+++ b/includes/options/class-amp-options-menu.php
@@ -5,7 +5,10 @@
* @package AMP
*/
+use AmpProject\AmpWP\AmpSlugCustomizationWatcher;
use AmpProject\AmpWP\Option;
+use AmpProject\AmpWP\QueryVars;
+use AmpProject\AmpWP\Services;
/**
* AMP_Options_Menu class.
@@ -134,6 +137,9 @@ public function add_menu_items() {
public function render_theme_support() {
$theme_support = AMP_Options_Manager::get_option( Option::THEME_SUPPORT );
+ /** @var AmpSlugCustomizationWatcher $amp_slug_customization_watcher */
+ $amp_slug_customization_watcher = Services::get( 'amp_slug_customization_watcher' );
+
/* translators: %s: URL to the documentation. */
$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. */
@@ -205,15 +211,51 @@ public function render_theme_support() {
-
-
-
-
+
-
+
+
+
+ did_customize_late() ) : ?>
+
+
+ %s”', esc_html( amp_get_slug() ) ),
+ sprintf( '“%s
”', QueryVars::AMP )
+ )
+ );
+ ?>
+
+
+ did_customize_early() ) : ?>
+
+
+ %s”', esc_html( amp_get_slug() ) ),
+ sprintf( '“%s
”', QueryVars::AMP )
+ )
+ );
+ ?>
+
+
diff --git a/includes/options/views/class-amp-setup-wizard-submenu-page.php b/includes/options/views/class-amp-setup-wizard-submenu-page.php
index 65fc723d8e8..bb4f3622fb1 100644
--- a/includes/options/views/class-amp-setup-wizard-submenu-page.php
+++ b/includes/options/views/class-amp-setup-wizard-submenu-page.php
@@ -7,6 +7,10 @@
*/
use AmpProject\AmpWP\Admin\DevToolsUserAccess;
+use AmpProject\AmpWP\AmpSlugCustomizationWatcher;
+use AmpProject\AmpWP\Option;
+use AmpProject\AmpWP\QueryVars;
+use AmpProject\AmpWP\Services;
/**
* AMP setup wizard submenu page class.
@@ -104,6 +108,9 @@ public function enqueue_assets( $hook_suffix ) {
return;
}
+ /** @var AmpSlugCustomizationWatcher $amp_slug_customization_watcher */
+ $amp_slug_customization_watcher = Services::get( 'amp_slug_customization_watcher' );
+
$asset_file = AMP__DIR__ . '/assets/js/' . self::ASSET_HANDLE . '.asset.php';
$asset = require $asset_file;
$dependencies = $asset['dependencies'];
@@ -145,6 +152,9 @@ public function enqueue_assets( $hook_suffix ) {
$setup_wizard_data = [
'AMP_OPTIONS_KEY' => AMP_Options_Manager::OPTION_NAME,
'AMP_QUERY_VAR' => amp_get_slug(),
+ 'DEFAULT_AMP_QUERY_VAR' => QueryVars::AMP,
+ 'AMP_QUERY_VAR_CUSTOMIZED_LATE' => $amp_slug_customization_watcher->did_customize_late(),
+ 'LEGACY_THEME_SLUG' => AMP_Reader_Themes::DEFAULT_READER_THEME,
'APP_ROOT_ID' => self::APP_ROOT_ID,
'CUSTOMIZER_LINK' => add_query_arg(
[
diff --git a/src/AmpSlugCustomizationWatcher.php b/src/AmpSlugCustomizationWatcher.php
new file mode 100644
index 00000000000..5846c7885a7
--- /dev/null
+++ b/src/AmpSlugCustomizationWatcher.php
@@ -0,0 +1,96 @@
+is_customized_early;
+ }
+
+ /**
+ * Whether the slug was customized early (at after_setup_theme action, priority 4).
+ *
+ * @return bool
+ */
+ public function did_customize_late() {
+ return $this->is_customized_late;
+ }
+
+ /**
+ * Determine if the slug was customized early.
+ *
+ * Early customization happens by plugins_loaded action at priority 8; this is required in order for the slug to be
+ * used by `ReaderThemeLoader::override_theme()` which runs at priority 9; this method in turn must run before
+ * before `_wp_customize_include()` which runs at plugins_loaded priority 10. At that point the current theme gets
+ * determined, so for Reader themes to apply the logic in `ReaderThemeLoader` must run beforehand.
+ */
+ public function determine_early_customization() {
+ if ( QueryVars::AMP !== amp_get_slug() ) {
+ $this->is_customized_early = true;
+ } else {
+ add_action( 'after_setup_theme', [ $this, 'determine_late_customization' ], 4 );
+ }
+ }
+
+ /**
+ * Determine if the slug was defined late.
+ *
+ * Late slug customization often happens when a theme itself defines `AMP_QUERY_VAR`. This is too late for the plugin
+ * to be able to offer Reader themes which must have `AMP_QUERY_VAR` defined by plugins_loaded priority 9. Also,
+ * defining `AMP_QUERY_VAR` is fundamentally incompatible since loading a Reader theme means preventing the original
+ * theme from ever being loaded, and thus the theme's customized `AMP_QUERY_VAR` will never be read.
+ *
+ * This method must run before `amp_after_setup_theme()` which runs at the after_setup_theme action priority 5. In
+ * this function, the `amp_get_slug()` function is called which will then set the query var for the remainder of the
+ * request.
+ *
+ * @see amp_after_setup_theme()
+ */
+ public function determine_late_customization() {
+ if ( QueryVars::AMP !== amp_get_slug() ) {
+ $this->is_customized_late = true;
+ }
+ }
+}
diff --git a/src/AmpWpPlugin.php b/src/AmpWpPlugin.php
index 909cdebf86c..763472cfc46 100644
--- a/src/AmpWpPlugin.php
+++ b/src/AmpWpPlugin.php
@@ -63,6 +63,7 @@ protected function get_service_classes() {
'plugin_suppression' => PluginSuppression::class,
'mobile_redirection' => MobileRedirection::class,
'reader_theme_loader' => ReaderThemeLoader::class,
+ 'amp_slug_customization_watcher' => AmpSlugCustomizationWatcher::class,
];
}
diff --git a/src/ReaderThemeLoader.php b/src/ReaderThemeLoader.php
index 9238488ea57..3ba83213e83 100644
--- a/src/ReaderThemeLoader.php
+++ b/src/ReaderThemeLoader.php
@@ -51,8 +51,6 @@ public static function is_needed() {
AMP_Theme_Support::READER_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT )
&&
AMP_Reader_Themes::DEFAULT_READER_THEME !== AMP_Options_Manager::get_option( Option::READER_THEME )
- &&
- isset( $_GET[ amp_get_slug() ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
);
}
@@ -85,6 +83,11 @@ public function get_reader_theme() {
* @see WP_Customize_Manager::start_previewing_theme() which provides for much of the inspiration here.
*/
public function override_theme() {
+ // Short-circuit if the request does not include the AMP query var.
+ if ( ! isset( $_GET[ amp_get_slug() ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ return;
+ }
+
$theme = $this->get_reader_theme();
if ( ! $theme instanceof WP_Theme ) {
return;
diff --git a/tests/php/src/Unit/AmpSlugCustomizationWatcherTest.php b/tests/php/src/Unit/AmpSlugCustomizationWatcherTest.php
new file mode 100644
index 00000000000..d76718649f5
--- /dev/null
+++ b/tests/php/src/Unit/AmpSlugCustomizationWatcherTest.php
@@ -0,0 +1,87 @@
+instance = new AmpSlugCustomizationWatcher();
+ }
+
+ /** @covers AmpSlugCustomizationWatcher::__construct() */
+ public function test__construct() {
+ $this->assertInstanceOf( AmpSlugCustomizationWatcher::class, $this->instance );
+ $this->assertInstanceOf( Service::class, $this->instance );
+ $this->assertInstanceOf( Registerable::class, $this->instance );
+ }
+
+ /** @covers AmpSlugCustomizationWatcher::register() */
+ public function test_register() {
+ $this->instance->register();
+ $this->assertEquals( 8, has_action( 'plugins_loaded', [ $this->instance, 'determine_early_customization' ] ) );
+ }
+
+ /** @covers AmpSlugCustomizationWatcher::determine_early_customization() */
+ public function test_determine_early_customization() {
+ $this->assertFalse( $this->instance->did_customize_early() );
+ $this->assertFalse( $this->instance->did_customize_late() );
+
+ $early_slug = 'early';
+ $this->add_query_var_filter( $early_slug );
+ $this->assertEquals( $early_slug, amp_get_slug() );
+ $this->instance->determine_early_customization();
+ $this->assertFalse( has_action( 'after_setup_theme', [ $this->instance, 'determine_late_customization' ] ) );
+
+ $this->assertTrue( $this->instance->did_customize_early() );
+ $this->assertFalse( $this->instance->did_customize_late() );
+ }
+
+ /**
+ * @covers AmpSlugCustomizationWatcher::determine_early_customization()
+ * @covers AmpSlugCustomizationWatcher::determine_late_customization()
+ */
+ public function test_determine_late_customization() {
+ $this->assertFalse( $this->instance->did_customize_early() );
+ $this->assertFalse( $this->instance->did_customize_late() );
+
+ $this->instance->determine_early_customization();
+ $this->assertEquals( 4, has_action( 'after_setup_theme', [ $this->instance, 'determine_late_customization' ] ) );
+
+ $late_slug = 'late';
+ $this->add_query_var_filter( $late_slug );
+ $this->assertEquals( $late_slug, amp_get_slug() );
+
+ $this->instance->determine_late_customization();
+
+ $this->assertFalse( $this->instance->did_customize_early() );
+ $this->assertTrue( $this->instance->did_customize_late() );
+ }
+
+ /**
+ * Add query var filter.
+ *
+ * @param string $value Query var.
+ */
+ private function add_query_var_filter( $value ) {
+ remove_all_filters( 'amp_query_var' );
+ add_filter(
+ 'amp_query_var',
+ static function () use ( $value ) {
+ return $value;
+ }
+ );
+ }
+}