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( '