diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php
index 321e6ad1c58..9fb2056598c 100644
--- a/.phpstorm.meta.php
+++ b/.phpstorm.meta.php
@@ -40,6 +40,7 @@
'plugin_registry' => \AmpProject\AmpWP\PluginRegistry::class,
'plugin_suppression' => \AmpProject\AmpWP\PluginSuppression::class,
'reader_theme_loader' => \AmpProject\AmpWP\ReaderThemeLoader::class,
+ 'reader_theme_support_features' => \AmpProject\AmpWP\ReaderThemeSupportFeatures::class,
'rest.options_controller' => \AmpProject\AmpWP\OptionsRESTController::class,
'rest.validation_counts_controller' => \AmpProject\AmpWP\Validation\ValidationCountsRestController::class,
'server_timing' => \AmpProject\AmpWP\Instrumentation\ServerTiming::class,
diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php
index 058a745208e..0bcb699b204 100644
--- a/includes/amp-helper-functions.php
+++ b/includes/amp-helper-functions.php
@@ -134,9 +134,10 @@ function () {
$old_version = isset( $options[ Option::VERSION ] ) ? $options[ Option::VERSION ] : '0.0';
if ( AMP__VERSION !== $old_version && is_admin() && current_user_can( 'manage_options' ) ) {
- // This waits to happen until the very end of init to ensure that amp theme support and amp post type support have all been added.
+ // This waits to happen until the very end of admin_init to ensure that amp theme support and amp post type
+ // support have all been added, and that the settings have been registered.
add_action(
- 'init',
+ 'admin_init',
static function () use ( $old_version ) {
/**
* Triggers when after amp_init when the plugin version has updated.
diff --git a/includes/options/class-amp-options-manager.php b/includes/options/class-amp-options-manager.php
index 46df6729b24..21bd8eb7a40 100644
--- a/includes/options/class-amp-options-manager.php
+++ b/includes/options/class-amp-options-manager.php
@@ -279,7 +279,7 @@ static function ( $supported ) {
public static function get_option( $option, $default = false ) {
$amp_options = self::get_options();
- if ( ! isset( $amp_options[ $option ] ) ) {
+ if ( ! array_key_exists( $option, $amp_options ) ) {
return $default;
}
diff --git a/src/AmpWpPlugin.php b/src/AmpWpPlugin.php
index ff0ff1d4240..d131611ac28 100644
--- a/src/AmpWpPlugin.php
+++ b/src/AmpWpPlugin.php
@@ -99,6 +99,7 @@ final class AmpWpPlugin extends ServiceBasedPlugin {
'plugin_registry' => PluginRegistry::class,
'plugin_suppression' => PluginSuppression::class,
'reader_theme_loader' => ReaderThemeLoader::class,
+ 'reader_theme_support_features' => ReaderThemeSupportFeatures::class,
'rest.options_controller' => OptionsRESTController::class,
'rest.validation_counts_controller' => Validation\ValidationCountsRestController::class,
'server_timing' => Instrumentation\ServerTiming::class,
@@ -193,6 +194,7 @@ protected function get_shared_instances() {
DevTools\CallbackReflection::class,
DevTools\FileReflection::class,
ReaderThemeLoader::class,
+ ReaderThemeSupportFeatures::class,
BackgroundTask\BackgroundTaskDeactivator::class,
PairedRouting::class,
Injector::class,
diff --git a/src/Option.php b/src/Option.php
index 9df7ae348c3..d9757734c63 100644
--- a/src/Option.php
+++ b/src/Option.php
@@ -142,6 +142,16 @@ interface Option {
*/
const READER_THEME = 'reader_theme';
+ /**
+ * Theme support features from the primary theme.
+ *
+ * When using a Reader theme, the theme support features from the primary theme are stored in this option so that
+ * they will be available when the Reader theme is active.
+ *
+ * @var string
+ */
+ const PRIMARY_THEME_SUPPORT = 'primary_theme_support';
+
/**
* The key of the option storing whether the setup wizard has been completed.
*
diff --git a/src/ReaderThemeLoader.php b/src/ReaderThemeLoader.php
index 6eca6f7bd3c..57a9939ef4f 100644
--- a/src/ReaderThemeLoader.php
+++ b/src/ReaderThemeLoader.php
@@ -69,22 +69,34 @@ public function __construct( PairedRouting $paired_routing ) {
/**
* Is Reader mode with a Reader theme selected.
*
+ * @param array $options Options to check. If omitted, the currently-saved options are used.
* @return bool Whether new Reader mode.
*/
- public function is_enabled() {
+ public function is_enabled( $options = null ) {
// If the theme was overridden then we know it is enabled. We can't check get_template() at this point because
// it will be identical to $reader_theme.
if ( $this->is_theme_overridden() ) {
return true;
}
+ if ( null === $options ) {
+ $options = AMP_Options_Manager::get_options();
+ }
+
// If Reader mode is not enabled, then a Reader theme is definitely not going to be served.
- if ( AMP_Theme_Support::READER_MODE_SLUG !== AMP_Options_Manager::get_option( Option::THEME_SUPPORT ) ) {
+ if (
+ ! isset( $options[ Option::THEME_SUPPORT ] )
+ ||
+ AMP_Theme_Support::READER_MODE_SLUG !== $options[ Option::THEME_SUPPORT ]
+ ) {
return false;
}
// If the Legacy Reader mode is active, then a Reader theme is not going to be served.
- $reader_theme = AMP_Options_Manager::get_option( Option::READER_THEME );
+ if ( ! isset( $options[ Option::READER_THEME ] ) ) {
+ return false;
+ }
+ $reader_theme = $options[ Option::READER_THEME ];
if ( ReaderThemes::DEFAULT_READER_THEME === $reader_theme ) {
return false;
}
diff --git a/src/ReaderThemeSupportFeatures.php b/src/ReaderThemeSupportFeatures.php
new file mode 100644
index 00000000000..f2eb52ac22a
--- /dev/null
+++ b/src/ReaderThemeSupportFeatures.php
@@ -0,0 +1,383 @@
+ [ self::KEY_SLUG, self::KEY_COLOR ],
+ self::FEATURE_EDITOR_GRADIENT_PRESETS => [ self::KEY_SLUG, self::KEY_GRADIENT ],
+ self::FEATURE_EDITOR_FONT_SIZES => [ self::KEY_SLUG, self::KEY_SIZE ],
+ ];
+
+ /**
+ * Reader theme loader.
+ *
+ * @var ReaderThemeLoader
+ */
+ private $reader_theme_loader;
+
+ /**
+ * ReaderThemeLoader constructor.
+ *
+ * @param ReaderThemeLoader $reader_theme_loader Reader theme loader.
+ */
+ public function __construct( ReaderThemeLoader $reader_theme_loader ) {
+ $this->reader_theme_loader = $reader_theme_loader;
+ }
+
+ /**
+ * Register the service with the system.
+ *
+ * @return void
+ */
+ public function register() {
+ add_filter( 'amp_options_updating', [ $this, 'filter_amp_options_updating' ] );
+ add_action( 'after_switch_theme', [ $this, 'handle_theme_update' ] );
+ add_action( self::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT, [ $this, 'update_cached_theme_support' ] );
+ add_action(
+ 'upgrader_process_complete',
+ function ( $upgrader ) {
+ if ( $upgrader instanceof Theme_Upgrader ) {
+ $this->update_cached_theme_support();
+ }
+ }
+ );
+
+ add_action( 'amp_post_template_head', [ $this, 'print_theme_support_styles' ] );
+ add_action(
+ 'wp_head',
+ [ $this, 'print_theme_support_styles' ],
+ 9 // Because wp_print_styles happens at priority 8, and we want the primary theme's colors to override any conflicting theme color assignments.
+ );
+ }
+
+ /**
+ * Check whether all the required props are present for a given feature item.
+ *
+ * @param string $feature Feature name.
+ * @param array $props Props to check.
+ *
+ * @return bool Whether all are present.
+ */
+ public function has_required_feature_props( $feature, $props ) {
+ if ( empty( $props ) || ! is_array( $props ) ) {
+ return false;
+ }
+ foreach ( self::SUPPORTED_FEATURES[ $feature ] as $required_prop ) {
+ if ( ! array_key_exists( $required_prop, $props ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Filter the AMP options when they are updated to add the primary theme's features.
+ *
+ * @param array $options Options.
+ * @return array Options.
+ */
+ public function filter_amp_options_updating( $options ) {
+ if ( $this->reader_theme_loader->is_enabled( $options ) ) {
+ $options[ Option::PRIMARY_THEME_SUPPORT ] = $this->get_theme_support_features( true );
+ } else {
+ $options[ Option::PRIMARY_THEME_SUPPORT ] = null;
+ }
+ return $options;
+ }
+
+ /**
+ * Handle updating the cached primary_theme_support after updating/switching theme.
+ *
+ * In the case of switching the theme via WP-CLI, it could be that the next request is for an AMP page and
+ * the `check_theme_switched()` function will run in the context of a Reader theme being loaded. In that case,
+ * the added theme support won't be for the primary theme and we need to schedule an immediate event in WP-Cron to
+ * try again in the context of a cron request in which a Reader theme will never be overriding the primary theme.
+ */
+ public function handle_theme_update() {
+ if ( $this->reader_theme_loader->is_theme_overridden() ) {
+ wp_schedule_single_event( time(), self::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT );
+ } else {
+ $this->update_cached_theme_support();
+ }
+ }
+
+ /**
+ * Update primary theme's cached theme support.
+ */
+ public function update_cached_theme_support() {
+ if ( $this->reader_theme_loader->is_enabled() ) {
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, $this->get_theme_support_features( true ) );
+ } else {
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, null );
+ }
+ }
+
+ /**
+ * Get the theme support features.
+ *
+ * @param bool $reduced Whether to reduce the feature props down to just what is required.
+ * @return array Theme support features.
+ */
+ public function get_theme_support_features( $reduced = false ) {
+ $features = [];
+ foreach ( array_keys( self::SUPPORTED_FEATURES ) as $feature_key ) {
+ $feature_value = current( (array) get_theme_support( $feature_key ) );
+ if ( ! is_array( $feature_value ) || empty( $feature_value ) ) {
+ continue;
+ }
+ if ( $reduced ) {
+ $features[ $feature_key ] = [];
+ foreach ( $feature_value as $item ) {
+ if ( $this->has_required_feature_props( $feature_key, $item ) ) {
+ $features[ $feature_key ][] = wp_array_slice_assoc( $item, self::SUPPORTED_FEATURES[ $feature_key ] );
+ }
+ }
+ } else {
+ $features[ $feature_key ] = $feature_value;
+ }
+ }
+ return $features;
+ }
+
+ /**
+ * Determines whether the request is for an AMP page in Reader mode.
+ *
+ * @return bool Whether AMP Reader request.
+ */
+ public function is_reader_request() {
+ return (
+ ( amp_is_legacy() || $this->reader_theme_loader->is_theme_overridden() )
+ &&
+ amp_is_request()
+ );
+ }
+
+ /**
+ * Print theme support styles.
+ */
+ public function print_theme_support_styles() {
+ if ( ! $this->is_reader_request() ) {
+ return;
+ }
+
+ $features = [];
+ if ( $this->reader_theme_loader->is_enabled() ) {
+ $features = AMP_Options_Manager::get_option( Option::PRIMARY_THEME_SUPPORT );
+ } elseif ( amp_is_legacy() ) {
+ $features = $this->get_theme_support_features();
+ }
+
+ if ( empty( $features ) ) {
+ return;
+ }
+
+ foreach ( array_keys( self::SUPPORTED_FEATURES ) as $feature ) {
+ if ( empty( $features[ $feature ] ) || ! is_array( $features[ $feature ] ) ) {
+ continue;
+ }
+ $value = $features[ $feature ];
+ switch ( $feature ) {
+ case self::FEATURE_EDITOR_COLOR_PALETTE:
+ $this->print_editor_color_palette_styles( $value );
+ break;
+ case self::FEATURE_EDITOR_FONT_SIZES:
+ $this->print_editor_font_sizes_styles( $value );
+ break;
+ case self::FEATURE_EDITOR_GRADIENT_PRESETS:
+ $this->print_editor_gradient_presets_styles( $value );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Print editor-color-palette styles.
+ *
+ * @param array $color_palette Color palette.
+ */
+ private function print_editor_color_palette_styles( array $color_palette ) {
+ echo '';
+ }
+
+ /**
+ * Print editor-font-sizes styles.
+ *
+ * @param array $font_sizes Font sizes.
+ */
+ private function print_editor_font_sizes_styles( array $font_sizes ) {
+ echo '';
+ }
+
+ /**
+ * Print editor-gradient-presets styles.
+ *
+ * @param array $gradient_presets Gradient presets.
+ */
+ private function print_editor_gradient_presets_styles( array $gradient_presets ) {
+ echo '';
+ }
+
+ /**
+ * Get relative luminance from color hex value.
+ *
+ * Copied from `\Twenty_Twenty_One_Custom_Colors::get_relative_luminance_from_hex()`.
+ *
+ * @see https://github.com/WordPress/wordpress-develop/blob/acbbbd18b32b5429264622141a6d058b64f3a5ad/src/wp-content/themes/twentytwentyone/classes/class-twenty-twenty-one-custom-colors.php#L138-L156
+ *
+ * @param string $hex Color hex value.
+ * @return int Relative luminance value.
+ */
+ public function get_relative_luminance_from_hex( $hex ) {
+
+ // Remove the "#" symbol from the beginning of the color.
+ $hex = ltrim( $hex, '#' );
+
+ // Make sure there are 6 digits for the below calculations.
+ if ( 3 === strlen( $hex ) ) {
+ $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
+ }
+
+ // Get red, green, blue.
+ $red = hexdec( substr( $hex, 0, 2 ) );
+ $green = hexdec( substr( $hex, 2, 2 ) );
+ $blue = hexdec( substr( $hex, 4, 2 ) );
+
+ // Calculate the luminance.
+ $lum = ( 0.2126 * $red ) + ( 0.7152 * $green ) + ( 0.0722 * $blue );
+ return (int) round( $lum );
+ }
+}
diff --git a/tests/php/src/ReaderThemeLoaderTest.php b/tests/php/src/ReaderThemeLoaderTest.php
index a166df011c2..27342745305 100644
--- a/tests/php/src/ReaderThemeLoaderTest.php
+++ b/tests/php/src/ReaderThemeLoaderTest.php
@@ -63,6 +63,69 @@ public function test_is_enabled() {
$this->assertTrue( $this->instance->is_enabled() );
}
+ /** @covers ::is_enabled() */
+ public function test_is_enabled_with_options_supplied() {
+ $active_theme_slug = 'twentytwenty';
+ $reader_theme_slug = 'twentynineteen';
+ if ( ! wp_get_theme( $active_theme_slug )->exists() || ! wp_get_theme( $reader_theme_slug )->exists() ) {
+ $this->markTestSkipped();
+ }
+
+ switch_theme( $active_theme_slug );
+
+ $this->assertFalse(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::TRANSITIONAL_MODE_SLUG,
+ ]
+ )
+ );
+
+ $this->assertFalse(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ Option::READER_THEME => ReaderThemes::DEFAULT_READER_THEME,
+ ]
+ )
+ );
+
+ $this->assertFalse(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ ]
+ )
+ );
+
+ $this->assertFalse(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ Option::READER_THEME => $active_theme_slug,
+ ]
+ )
+ );
+
+ $this->assertTrue(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ Option::READER_THEME => $reader_theme_slug,
+ ]
+ )
+ );
+
+ $this->assertFalse(
+ $this->instance->is_enabled(
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::STANDARD_MODE_SLUG,
+ Option::READER_THEME => $reader_theme_slug,
+ ]
+ )
+ );
+ }
+
public function test__construct() {
$this->assertInstanceOf( ReaderThemeLoader::class, $this->instance );
$this->assertInstanceOf( Service::class, $this->instance );
diff --git a/tests/php/src/ReaderThemeSupportFeaturesTest.php b/tests/php/src/ReaderThemeSupportFeaturesTest.php
new file mode 100644
index 00000000000..d15b9d758bb
--- /dev/null
+++ b/tests/php/src/ReaderThemeSupportFeaturesTest.php
@@ -0,0 +1,417 @@
+ 'Purple to yellow',
+ 'gradient' => 'linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)',
+ 'slug' => 'purple-to-yellow',
+ ],
+ [
+ 'name' => 'Yellow to purple',
+ 'gradient' => 'linear-gradient(160deg, #EEEADD 0%, #D1D1E4 100%)',
+ 'slug' => 'yellow-to-purple',
+ ],
+ ];
+
+ const TEST_FONT_SIZES = [
+ [
+ 'name' => 'Extra small',
+ 'shortName' => 'XS',
+ 'size' => 16,
+ 'slug' => 'extra-small',
+ ],
+ [
+ 'name' => 'Gigantic',
+ 'shortName' => 'XXXL',
+ 'size' => 144,
+ 'slug' => 'gigantic',
+ ],
+ ];
+
+ const TEST_COLOR_PALETTE = [
+ [
+ 'name' => 'Black',
+ 'slug' => 'black',
+ 'color' => '#000000',
+ ],
+ [
+ 'name' => 'White',
+ 'slug' => 'white',
+ 'color' => '#FFFFFF',
+ ],
+ ];
+
+ const TEST_ALL_THEME_SUPPORTS = [
+ ReaderThemeSupportFeatures::FEATURE_EDITOR_GRADIENT_PRESETS => self::TEST_GRADIENT_PRESETS,
+ ReaderThemeSupportFeatures::FEATURE_EDITOR_COLOR_PALETTE => self::TEST_COLOR_PALETTE,
+ ReaderThemeSupportFeatures::FEATURE_EDITOR_FONT_SIZES => self::TEST_FONT_SIZES,
+ ];
+
+ /**
+ * Primary theme slug.
+ *
+ * @var string
+ */
+ const THEME_PRIMARY = 'twentytwenty';
+
+ /**
+ * Reader theme slug.
+ *
+ * @var string
+ */
+ const THEME_READER = 'twentynineteen';
+
+ /** @var ReaderThemeSupportFeatures */
+ private $instance;
+
+ public function setUp() {
+ parent::setUp();
+ $this->instance = $this->injector->make( ReaderThemeSupportFeatures::class );
+
+ $this->register_core_themes();
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+
+ $this->restore_theme_directories();
+ }
+
+ /** @covers ::__construct() */
+ public function test__construct() {
+ $this->assertInstanceOf( Service::class, $this->instance );
+ $this->assertInstanceOf( Registerable::class, $this->instance );
+ }
+
+ /** @covers ::register() */
+ public function test_register() {
+ remove_all_actions( 'upgrader_process_complete' );
+ $this->instance->register();
+
+ $this->assertSame( 10, has_filter( 'amp_options_updating', [ $this->instance, 'filter_amp_options_updating' ] ) );
+ $this->assertSame( 10, has_action( 'after_switch_theme', [ $this->instance, 'handle_theme_update' ] ) );
+ $this->assertSame( 10, has_action( ReaderThemeSupportFeatures::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT, [ $this->instance, 'update_cached_theme_support' ] ) );
+ $this->assertTrue( has_action( 'upgrader_process_complete' ) );
+
+ $this->assertSame( 10, has_action( 'amp_post_template_head', [ $this->instance, 'print_theme_support_styles' ] ) );
+ $this->assertSame( 9, has_action( 'wp_head', [ $this->instance, 'print_theme_support_styles' ] ) );
+ }
+
+ /** @covers ::has_required_feature_props() */
+ public function test_has_required_feature_props() {
+ foreach ( ReaderThemeSupportFeatures::SUPPORTED_FEATURES as $feature => $required_keys ) {
+ $this->assertFalse( $this->instance->has_required_feature_props( $feature, [] ) );
+ $this->assertFalse( $this->instance->has_required_feature_props( $feature, [ 'foo' => 'bar' ] ) );
+ $this->assertFalse( $this->instance->has_required_feature_props( $feature, [ ReaderThemeSupportFeatures::KEY_SLUG => '' ] ) );
+
+ $this->assertTrue( $this->instance->has_required_feature_props( $feature, array_fill_keys( $required_keys, '' ) ) );
+ $this->assertTrue( $this->instance->has_required_feature_props( $feature, array_merge( array_fill_keys( $required_keys, '' ), [ 'extra' => '' ] ) ) );
+ }
+ }
+
+ /** @return array[] */
+ public function get_data_for_test_filter_amp_options_updating() {
+ return [
+ 'standard' => [
+ self::TEST_ALL_THEME_SUPPORTS,
+ [ Option::THEME_SUPPORT => AMP_Theme_Support::STANDARD_MODE_SLUG ],
+ null,
+ ],
+ 'transitional' => [
+ self::TEST_ALL_THEME_SUPPORTS,
+ [ Option::THEME_SUPPORT => AMP_Theme_Support::TRANSITIONAL_MODE_SLUG ],
+ null,
+ ],
+ 'reader_legacy' => [
+ self::TEST_ALL_THEME_SUPPORTS,
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ Option::READER_THEME => ReaderThemes::DEFAULT_READER_THEME,
+ ],
+ null,
+ ],
+ 'reader_theme' => [
+ self::TEST_ALL_THEME_SUPPORTS,
+ [
+ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG,
+ Option::READER_THEME => self::THEME_READER,
+ ],
+ self::TEST_ALL_THEME_SUPPORTS,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider get_data_for_test_filter_amp_options_updating
+ * @covers ::filter_amp_options_updating()
+ * @covers ::get_theme_support_features()
+ *
+ * @param array $theme_supports Theme supports.
+ * @param array $initial_options Initial options.
+ * @param null|array $primary_theme_support Primary theme support.
+ */
+ public function test_filter_amp_options_updating( $theme_supports, $initial_options, $primary_theme_support ) {
+ if ( ! wp_get_theme( self::THEME_PRIMARY )->exists() || ! wp_get_theme( self::THEME_READER )->exists() ) {
+ $this->markTestSkipped();
+ }
+
+ switch_theme( self::THEME_PRIMARY );
+ $this->add_theme_supports( $theme_supports );
+
+ $filtered = $this->instance->filter_amp_options_updating( $initial_options );
+ $this->assertArraySubset( $initial_options, $filtered );
+ $this->assertArrayHasKey( Option::PRIMARY_THEME_SUPPORT, $filtered );
+ if ( null === $primary_theme_support ) {
+ $this->assertNull( $filtered[ Option::PRIMARY_THEME_SUPPORT ] );
+ } else {
+ $this->assertInternalType( 'array', $filtered[ Option::PRIMARY_THEME_SUPPORT ] );
+ foreach ( $theme_supports as $feature => $supports ) {
+ $this->assertArrayHasKey( $feature, $filtered[ Option::PRIMARY_THEME_SUPPORT ] );
+ $this->assertEquals(
+ array_map(
+ static function ( $item ) use ( $feature ) {
+ return wp_array_slice_assoc( $item, ReaderThemeSupportFeatures::SUPPORTED_FEATURES[ $feature ] );
+ },
+ $primary_theme_support[ $feature ]
+ ),
+ $filtered[ Option::PRIMARY_THEME_SUPPORT ][ $feature ]
+ );
+ }
+ }
+ }
+
+ /**
+ * @covers ::handle_theme_update()
+ * @covers ::update_cached_theme_support()
+ */
+ public function test_handle_theme_update_with_reader_theme_not_enabled() {
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, self::TEST_ALL_THEME_SUPPORTS );
+
+ /** @var ReaderThemeLoader $reader_theme_loader */
+ $reader_theme_loader = $this->get_private_property( $this->instance, 'reader_theme_loader' );
+ $this->assertFalse( $reader_theme_loader->is_enabled() );
+ $this->assertFalse( $reader_theme_loader->is_theme_overridden() );
+ $this->instance->handle_theme_update();
+ $this->assertFalse( wp_next_scheduled( ReaderThemeSupportFeatures::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT ) );
+ $this->assertNull( AMP_Options_Manager::get_option( Option::PRIMARY_THEME_SUPPORT ) );
+ }
+
+ /**
+ * @covers ::handle_theme_update()
+ * @covers ::update_cached_theme_support()
+ */
+ public function test_handle_theme_update_with_reader_theme_enabled_but_not_overriding() {
+ if ( ! wp_get_theme( self::THEME_PRIMARY )->exists() || ! wp_get_theme( self::THEME_READER )->exists() ) {
+ $this->markTestSkipped();
+ }
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, null );
+ switch_theme( self::THEME_PRIMARY );
+ AMP_Options_Manager::update_option( Option::READER_THEME, self::THEME_READER );
+
+ /** @var ReaderThemeLoader $reader_theme_loader */
+ $reader_theme_loader = $this->get_private_property( $this->instance, 'reader_theme_loader' );
+ $this->assertTrue( $reader_theme_loader->is_enabled() );
+ $this->assertFalse( $reader_theme_loader->is_theme_overridden() );
+ $this->instance->handle_theme_update();
+ $this->assertFalse( wp_next_scheduled( ReaderThemeSupportFeatures::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT ) );
+
+ $primary_theme_support = AMP_Options_Manager::get_option( Option::PRIMARY_THEME_SUPPORT );
+ $this->assertInternalType( 'array', $primary_theme_support );
+ $this->assertEqualSets( array_keys( self::TEST_ALL_THEME_SUPPORTS ), array_keys( $primary_theme_support ) );
+ }
+
+ /** @covers ::handle_theme_update() */
+ public function test_handle_theme_update_with_reader_theme_enabled_and_overriding() {
+ if ( ! wp_get_theme( self::THEME_PRIMARY )->exists() || ! wp_get_theme( self::THEME_READER )->exists() ) {
+ $this->markTestSkipped();
+ }
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, null );
+ switch_theme( self::THEME_PRIMARY );
+ AMP_Options_Manager::update_option( Option::READER_THEME, self::THEME_READER );
+
+ /** @var ReaderThemeLoader $reader_theme_loader */
+ $reader_theme_loader = $this->get_private_property( $this->instance, 'reader_theme_loader' );
+
+ /** @var PairedRouting $paired_routing */
+ $paired_routing = $this->get_private_property( $reader_theme_loader, 'paired_routing' );
+
+ $this->go_to( $paired_routing->add_endpoint( home_url() ) );
+ $this->assertTrue( $paired_routing->has_endpoint() );
+
+ $reader_theme_loader->override_theme();
+ $this->assertTrue( $reader_theme_loader->is_enabled() );
+ $this->assertTrue( $reader_theme_loader->is_theme_overridden() );
+
+ $this->instance->handle_theme_update();
+ $next_scheduled = wp_next_scheduled( ReaderThemeSupportFeatures::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT );
+ $this->assertNotFalse( $next_scheduled );
+ $this->assertLessThanOrEqual( time(), wp_next_scheduled( ReaderThemeSupportFeatures::ACTION_UPDATE_CACHED_PRIMARY_THEME_SUPPORT ) );
+ }
+
+ /** @covers ::get_theme_support_features() */
+ public function test_get_theme_support_features() {
+ $this->add_theme_supports( self::TEST_ALL_THEME_SUPPORTS );
+ add_theme_support(
+ 'custom-logo',
+ [
+ 'height' => 480,
+ 'width' => 720,
+ ]
+ );
+ $non_reduced = $this->instance->get_theme_support_features( false );
+ $reduced = $this->instance->get_theme_support_features( true );
+
+ $this->assertEqualSets( array_keys( $non_reduced ), array_keys( self::TEST_ALL_THEME_SUPPORTS ) );
+ $this->assertEqualSets( array_keys( $reduced ), array_keys( self::TEST_ALL_THEME_SUPPORTS ) );
+ foreach ( array_keys( self::TEST_ALL_THEME_SUPPORTS ) as $feature ) {
+ $this->assertNotEquals( $reduced[ $feature ], $non_reduced[ $feature ] );
+ $this->assertArraySubset( $reduced[ $feature ], $non_reduced[ $feature ] );
+ }
+ }
+
+ /** @covers ::is_reader_request() */
+ public function test_is_reader_request() {
+ if ( ! wp_get_theme( self::THEME_PRIMARY )->exists() || ! wp_get_theme( self::THEME_READER )->exists() ) {
+ $this->markTestSkipped();
+ }
+
+ $post_id = self::factory()->post->create();
+
+ /** @var ReaderThemeLoader $reader_theme_loader */
+ $reader_theme_loader = $this->get_private_property( $this->instance, 'reader_theme_loader' );
+
+ AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::STANDARD_MODE_SLUG );
+ $this->go_to( get_permalink( $post_id ) );
+ $this->assertFalse( $this->instance->is_reader_request() );
+
+ AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG );
+ AMP_Options_Manager::update_option( Option::READER_THEME, ReaderThemes::DEFAULT_READER_THEME );
+ $this->go_to( get_permalink( $post_id ) );
+ $this->assertFalse( $this->instance->is_reader_request() );
+
+ $this->go_to( amp_get_permalink( $post_id ) );
+ $reader_theme_loader->override_theme();
+ $this->assertFalse( $reader_theme_loader->is_enabled() );
+ $this->assertTrue( $this->instance->is_reader_request() );
+
+ switch_theme( self::THEME_PRIMARY );
+ AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG );
+ AMP_Options_Manager::update_option( Option::READER_THEME, self::THEME_READER );
+ $this->go_to( amp_get_permalink( $post_id ) );
+ $reader_theme_loader->override_theme();
+ $this->assertTrue( $reader_theme_loader->is_enabled() );
+ $this->assertTrue( $this->instance->is_reader_request() );
+ }
+
+ /** @covers ::print_theme_support_styles() */
+ public function test_print_theme_support_styles_non_reader() {
+ AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::STANDARD_MODE_SLUG );
+ $this->add_theme_supports( self::TEST_ALL_THEME_SUPPORTS );
+ $this->go_to( '/' );
+ $this->assertEmpty( get_echo( [ $this->instance, 'print_theme_support_styles' ] ) );
+ }
+
+ /** @return array */
+ public function get_data_for_test_print_theme_support_styles_reader() {
+ return [
+ 'legacy_theme' => [ true ],
+ 'reader_theme' => [ false ],
+ ];
+ }
+
+ /**
+ * @covers ::print_theme_support_styles()
+ * @covers ::print_editor_color_palette_styles()
+ * @covers ::print_editor_font_sizes_styles()
+ * @covers ::print_editor_gradient_presets_styles()
+ *
+ * @dataProvider get_data_for_test_print_theme_support_styles_reader
+ *
+ * @param int $is_legacy
+ */
+ public function test_print_theme_support_styles_reader( $is_legacy ) {
+ AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG );
+ if ( $is_legacy ) {
+ AMP_Options_Manager::update_option( Option::READER_THEME, ReaderThemes::DEFAULT_READER_THEME );
+ $this->add_theme_supports( self::TEST_ALL_THEME_SUPPORTS );
+ } else {
+ if ( ! wp_get_theme( self::THEME_PRIMARY )->exists() || ! wp_get_theme( self::THEME_READER )->exists() ) {
+ $this->markTestSkipped();
+ }
+ switch_theme( self::THEME_PRIMARY );
+ AMP_Options_Manager::update_option( Option::READER_THEME, self::THEME_READER );
+ AMP_Options_Manager::update_option( Option::PRIMARY_THEME_SUPPORT, self::TEST_ALL_THEME_SUPPORTS );
+ }
+
+ $post_id = self::factory()->post->create();
+ $this->go_to( amp_get_permalink( $post_id ) );
+ $this->assertTrue( amp_is_request() );
+
+ /** @var ReaderThemeLoader $reader_theme_loader */
+ $reader_theme_loader = $this->get_private_property( $this->instance, 'reader_theme_loader' );
+ $reader_theme_loader->override_theme();
+ $this->assertEquals( $is_legacy, amp_is_legacy() );
+ $this->assertEquals( ! $is_legacy, $reader_theme_loader->is_enabled() );
+ $this->assertEquals( ! $is_legacy, $reader_theme_loader->is_theme_overridden() );
+
+ $output = get_echo( [ $this->instance, 'print_theme_support_styles' ] );
+
+ $this->assertStringContains( '