From fcb72448ba6f0df6e13ff5780addd755e1c7b860 Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Tue, 1 Mar 2022 15:12:24 +0100 Subject: [PATCH 01/21] Use `wp_unique_id`-based block class names instead of `uniqid` This transforms hash-based block class names into a more predictable class names based on `wp_unique_id()`. This should allow for re-enabling automatic parsed CSS transient caching. --- .phpstorm.meta.php | 115 +++++------ src/AmpWpPlugin.php | 117 +++++------ src/BlockUniqidClassNameTransformer.php | 188 ++++++++++++++++++ .../BlockUniqidClassNameTransformerTest.php | 158 +++++++++++++++ 4 files changed, 463 insertions(+), 115 deletions(-) create mode 100644 src/BlockUniqidClassNameTransformer.php create mode 100644 tests/php/src/BlockUniqidClassNameTransformerTest.php diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index c65e3ae2f92..54d4f0527ca 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -7,63 +7,64 @@ // TODO: I'd like to use AmpWpPlugin::SERVICES directly here but it doesn't seem to work. map( [ - 'admin.analytics_menu' => \AmpProject\AmpWP\Admin\AnalyticsOptionsSubmenu::class, - 'admin.google_fonts' => \AmpProject\AmpWP\Admin\GoogleFonts::class, - 'admin.onboarding_menu' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenu::class, - 'admin.onboarding_wizard' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenuPage::class, - 'admin.options_menu' => \AmpProject\AmpWP\Admin\OptionsMenu::class, - 'admin.support_screen' => \AmpProject\AmpWP\Admin\SupportScreen::class, - 'admin.support' => \AmpProject\AmpWP\Admin\SupportLink::class, - 'admin.paired_browsing' => \AmpProject\AmpWP\Admin\PairedBrowsing::class, - 'admin.plugin_row_meta' => \AmpProject\AmpWP\Admin\PluginRowMeta::class, - 'admin.polyfills' => \AmpProject\AmpWP\Admin\Polyfills::class, - 'admin.user_rest_endpoint_extension' => \AmpProject\AmpWP\Admin\UserRESTEndpointExtension::class, - 'admin.validation_counts' => \AmpProject\AmpWP\Admin\ValidationCounts::class, - 'amp_slug_customization_watcher' => \AmpProject\AmpWP\AmpSlugCustomizationWatcher::class, - 'background_task_deactivator' => \AmpProject\AmpWP\BackgroundTask\BackgroundTaskDeactivator::class, - 'cli.command_namespace' => \AmpProject\AmpWP\CliCli\CommandNamespaceRegistration::class, - 'cli.optimizer_command' => \AmpProject\AmpWP\CliCli\OptimizerCommand::class, - 'cli.transformer_command' => \AmpProject\AmpWP\CliCli\TransformerCommand::class, - 'cli.validation_command' => \AmpProject\AmpWP\CliCli\ValidationCommand::class, - 'css_transient_cache.ajax_handler' => \AmpProject\AmpWP\Admin\ReenableCssTransientCachingAjaxAction::class, - 'css_transient_cache.monitor' => \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching::class, - 'dependency_support' => \AmpProject\AmpWP\DependencySupport::class, - 'dev_tools.block_sources' => \AmpProject\AmpWP\DevTools\BlockSources::class, - 'dev_tools.callback_reflection' => \AmpProject\AmpWP\DevTools\CallbackReflection::class, - 'dev_tools.error_page' => \AmpProject\AmpWP\DevTools\ErrorPage::class, - 'dev_tools.file_reflection' => \AmpProject\AmpWP\DevTools\FileReflection::class, - 'dev_tools.likely_culprit_detector' => \AmpProject\AmpWP\DevTools\LikelyCulpritDetector::class, - 'dev_tools.user_access' => \AmpProject\AmpWP\DevTools\UserAccess::class, - 'editor.editor_support' => \AmpProject\AmpWP\Editor\EditorSupport::class, - 'extra_theme_and_plugin_headers' => \AmpProject\AmpWP\ExtraThemeAndPluginHeaders::class, - 'injector' => \AmpProject\AmpWP\Infrastructure\Injector::class, - 'loading_error' => \AmpProject\AmpWP\LoadingError::class, - 'mobile_redirection' => \AmpProject\AmpWP\MobileRedirection::class, - 'obsolete_block_attribute_remover' => \AmpProject\AmpWP\ObsoleteBlockAttributeRemover::class, - 'optimizer' => \AmpProject\AmpWP\Optimizer\OptimizerService::class, - 'optimizer.hero_candidate_filtering' => \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::class, - 'paired_routing' => \AmpProject\AmpWP\PairedRouting::class, - 'paired_url' => \AmpProject\AmpWP\PairedUrl::class, - 'plugin_activation_notice' => \AmpProject\AmpWP\Admin\PluginActivationNotice::class, - 'plugin_activation_site_scan' => \AmpProject\AmpWP\Admin\PluginActivationSiteScan::class, - '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.scannable_urls_controller' => \AmpProject\AmpWP\Validation\ScannableURLsRestController::class, - 'rest.validation_counts_controller' => \AmpProject\AmpWP\Validation\ValidationCountsRestController::class, - 'sandboxing' => \AmpProject\AmpWP\Sandboxing::class, - 'server_timing' => \AmpProject\AmpWP\Instrumentation\ServerTiming::class, - 'site_health_integration' => \AmpProject\AmpWP\Admin\SiteHealth::class, - 'support' => \AmpProject\AmpWP\Support\SupportCliCommand::class, - 'support_rest_controller' => \AmpProject\AmpWP\Support\SupportRESTController::class, - 'url_validation_cron' => \AmpProject\AmpWP\Validation\URLValidationCron::class, - 'url_validation_rest_controller' => \AmpProject\AmpWP\Validation\URLValidationRESTController::class, - 'validated_url_stylesheet_gc' => \AmpProject\AmpWP\BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, - 'validation_data_gc' => \AmpProject\AmpWP\BackgroundTask\ValidationDataGarbageCollection::class, - 'validation.scannable_url_provider' => \AmpProject\AmpWP\Validation\ScannableURLProvider::class, - 'validation.url_validation_provider' => \AmpProject\AmpWP\Validation\URLValidationProvider::class, + 'admin.analytics_menu' => \AmpProject\AmpWP\Admin\AnalyticsOptionsSubmenu::class, + 'admin.google_fonts' => \AmpProject\AmpWP\Admin\GoogleFonts::class, + 'admin.onboarding_menu' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenu::class, + 'admin.onboarding_wizard' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenuPage::class, + 'admin.options_menu' => \AmpProject\AmpWP\Admin\OptionsMenu::class, + 'admin.support_screen' => \AmpProject\AmpWP\Admin\SupportScreen::class, + 'admin.support' => \AmpProject\AmpWP\Admin\SupportLink::class, + 'admin.paired_browsing' => \AmpProject\AmpWP\Admin\PairedBrowsing::class, + 'admin.plugin_row_meta' => \AmpProject\AmpWP\Admin\PluginRowMeta::class, + 'admin.polyfills' => \AmpProject\AmpWP\Admin\Polyfills::class, + 'admin.user_rest_endpoint_extension' => \AmpProject\AmpWP\Admin\UserRESTEndpointExtension::class, + 'admin.validation_counts' => \AmpProject\AmpWP\Admin\ValidationCounts::class, + 'amp_slug_customization_watcher' => \AmpProject\AmpWP\AmpSlugCustomizationWatcher::class, + 'background_task_deactivator' => \AmpProject\AmpWP\BackgroundTask\BackgroundTaskDeactivator::class, + 'block_uniqid_class_name_transformer' => \AmpProject\AmpWP\BlockUniqidClassNameTransformer::class, + 'cli.command_namespace' => \AmpProject\AmpWP\CliCli\CommandNamespaceRegistration::class, + 'cli.optimizer_command' => \AmpProject\AmpWP\CliCli\OptimizerCommand::class, + 'cli.transformer_command' => \AmpProject\AmpWP\CliCli\TransformerCommand::class, + 'cli.validation_command' => \AmpProject\AmpWP\CliCli\ValidationCommand::class, + 'css_transient_cache.ajax_handler' => \AmpProject\AmpWP\Admin\ReenableCssTransientCachingAjaxAction::class, + 'css_transient_cache.monitor' => \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching::class, + 'dependency_support' => \AmpProject\AmpWP\DependencySupport::class, + 'dev_tools.block_sources' => \AmpProject\AmpWP\DevTools\BlockSources::class, + 'dev_tools.callback_reflection' => \AmpProject\AmpWP\DevTools\CallbackReflection::class, + 'dev_tools.error_page' => \AmpProject\AmpWP\DevTools\ErrorPage::class, + 'dev_tools.file_reflection' => \AmpProject\AmpWP\DevTools\FileReflection::class, + 'dev_tools.likely_culprit_detector' => \AmpProject\AmpWP\DevTools\LikelyCulpritDetector::class, + 'dev_tools.user_access' => \AmpProject\AmpWP\DevTools\UserAccess::class, + 'editor.editor_support' => \AmpProject\AmpWP\Editor\EditorSupport::class, + 'extra_theme_and_plugin_headers' => \AmpProject\AmpWP\ExtraThemeAndPluginHeaders::class, + 'injector' => \AmpProject\AmpWP\Infrastructure\Injector::class, + 'loading_error' => \AmpProject\AmpWP\LoadingError::class, + 'mobile_redirection' => \AmpProject\AmpWP\MobileRedirection::class, + 'obsolete_block_attribute_remover' => \AmpProject\AmpWP\ObsoleteBlockAttributeRemover::class, + 'optimizer' => \AmpProject\AmpWP\Optimizer\OptimizerService::class, + 'optimizer.hero_candidate_filtering' => \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::class, + 'paired_routing' => \AmpProject\AmpWP\PairedRouting::class, + 'paired_url' => \AmpProject\AmpWP\PairedUrl::class, + 'plugin_activation_notice' => \AmpProject\AmpWP\Admin\PluginActivationNotice::class, + 'plugin_activation_site_scan' => \AmpProject\AmpWP\Admin\PluginActivationSiteScan::class, + '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.scannable_urls_controller' => \AmpProject\AmpWP\Validation\ScannableURLsRestController::class, + 'rest.validation_counts_controller' => \AmpProject\AmpWP\Validation\ValidationCountsRestController::class, + 'sandboxing' => \AmpProject\AmpWP\Sandboxing::class, + 'server_timing' => \AmpProject\AmpWP\Instrumentation\ServerTiming::class, + 'site_health_integration' => \AmpProject\AmpWP\Admin\SiteHealth::class, + 'support' => \AmpProject\AmpWP\Support\SupportCliCommand::class, + 'support_rest_controller' => \AmpProject\AmpWP\Support\SupportRESTController::class, + 'url_validation_cron' => \AmpProject\AmpWP\Validation\URLValidationCron::class, + 'url_validation_rest_controller' => \AmpProject\AmpWP\Validation\URLValidationRESTController::class, + 'validated_url_stylesheet_gc' => \AmpProject\AmpWP\BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, + 'validation_data_gc' => \AmpProject\AmpWP\BackgroundTask\ValidationDataGarbageCollection::class, + 'validation.scannable_url_provider' => \AmpProject\AmpWP\Validation\ScannableURLProvider::class, + 'validation.url_validation_provider' => \AmpProject\AmpWP\Validation\URLValidationProvider::class, ] ) ); diff --git a/src/AmpWpPlugin.php b/src/AmpWpPlugin.php index 01575d06ecd..e2cf14736eb 100644 --- a/src/AmpWpPlugin.php +++ b/src/AmpWpPlugin.php @@ -69,64 +69,65 @@ final class AmpWpPlugin extends ServiceBasedPlugin { * @var string[] */ const SERVICES = [ - 'admin.analytics_menu' => Admin\AnalyticsOptionsSubmenu::class, - 'admin.google_fonts' => Admin\GoogleFonts::class, - 'admin.onboarding_menu' => Admin\OnboardingWizardSubmenu::class, - 'admin.onboarding_wizard' => Admin\OnboardingWizardSubmenuPage::class, - 'admin.options_menu' => Admin\OptionsMenu::class, - 'admin.paired_browsing' => Admin\PairedBrowsing::class, - 'admin.plugin_row_meta' => Admin\PluginRowMeta::class, - 'admin.support_screen' => Admin\SupportScreen::class, - 'admin.support' => Admin\SupportLink::class, - 'admin.polyfills' => Admin\Polyfills::class, - 'admin.user_rest_endpoint_extension' => Admin\UserRESTEndpointExtension::class, - 'admin.validation_counts' => Admin\ValidationCounts::class, - 'admin.amp_plugins' => Admin\AmpPlugins::class, - 'admin.amp_themes' => Admin\AmpThemes::class, - 'amp_slug_customization_watcher' => AmpSlugCustomizationWatcher::class, - 'background_task_deactivator' => BackgroundTaskDeactivator::class, - 'cli.command_namespace' => Cli\CommandNamespaceRegistration::class, - 'cli.optimizer_command' => Cli\OptimizerCommand::class, - 'cli.transformer_command' => Cli\TransformerCommand::class, - 'cli.validation_command' => Cli\ValidationCommand::class, - 'css_transient_cache.ajax_handler' => Admin\ReenableCssTransientCachingAjaxAction::class, - 'css_transient_cache.monitor' => BackgroundTask\MonitorCssTransientCaching::class, - 'dependency_support' => DependencySupport::class, - 'dev_tools.block_sources' => DevTools\BlockSources::class, - 'dev_tools.callback_reflection' => DevTools\CallbackReflection::class, - 'dev_tools.error_page' => DevTools\ErrorPage::class, - 'dev_tools.file_reflection' => DevTools\FileReflection::class, - 'dev_tools.likely_culprit_detector' => DevTools\LikelyCulpritDetector::class, - 'dev_tools.user_access' => DevTools\UserAccess::class, - 'editor.editor_support' => Editor\EditorSupport::class, - 'extra_theme_and_plugin_headers' => ExtraThemeAndPluginHeaders::class, - 'loading_error' => LoadingError::class, - 'mobile_redirection' => MobileRedirection::class, - 'obsolete_block_attribute_remover' => ObsoleteBlockAttributeRemover::class, - 'optimizer' => OptimizerService::class, - 'optimizer.hero_candidate_filtering' => HeroCandidateFiltering::class, - 'paired_routing' => PairedRouting::class, - 'paired_url' => PairedUrl::class, - 'plugin_activation_notice' => Admin\PluginActivationNotice::class, - 'plugin_activation_site_scan' => Admin\PluginActivationSiteScan::class, - '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.scannable_urls_controller' => Validation\ScannableURLsRestController::class, - 'rest.validation_counts_controller' => Validation\ValidationCountsRestController::class, - 'sandboxing' => Sandboxing::class, - 'server_timing' => Instrumentation\ServerTiming::class, - 'site_health_integration' => Admin\SiteHealth::class, - 'support' => SupportCliCommand::class, - 'support_rest_controller' => SupportRESTController::class, - 'url_validation_cron' => URLValidationCron::class, - 'url_validation_rest_controller' => Validation\URLValidationRESTController::class, - 'validated_url_stylesheet_gc' => BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, - 'validated_data_gc' => BackgroundTask\ValidationDataGarbageCollection::class, - 'validation.scannable_url_provider' => ScannableURLProvider::class, - 'validation.url_validation_provider' => URLValidationProvider::class, + 'admin.analytics_menu' => Admin\AnalyticsOptionsSubmenu::class, + 'admin.google_fonts' => Admin\GoogleFonts::class, + 'admin.onboarding_menu' => Admin\OnboardingWizardSubmenu::class, + 'admin.onboarding_wizard' => Admin\OnboardingWizardSubmenuPage::class, + 'admin.options_menu' => Admin\OptionsMenu::class, + 'admin.paired_browsing' => Admin\PairedBrowsing::class, + 'admin.plugin_row_meta' => Admin\PluginRowMeta::class, + 'admin.support_screen' => Admin\SupportScreen::class, + 'admin.support' => Admin\SupportLink::class, + 'admin.polyfills' => Admin\Polyfills::class, + 'admin.user_rest_endpoint_extension' => Admin\UserRESTEndpointExtension::class, + 'admin.validation_counts' => Admin\ValidationCounts::class, + 'admin.amp_plugins' => Admin\AmpPlugins::class, + 'admin.amp_themes' => Admin\AmpThemes::class, + 'amp_slug_customization_watcher' => AmpSlugCustomizationWatcher::class, + 'background_task_deactivator' => BackgroundTaskDeactivator::class, + 'block_uniqid_class_name_transformer' => BlockUniqidClassNameTransformer::class, + 'cli.command_namespace' => Cli\CommandNamespaceRegistration::class, + 'cli.optimizer_command' => Cli\OptimizerCommand::class, + 'cli.transformer_command' => Cli\TransformerCommand::class, + 'cli.validation_command' => Cli\ValidationCommand::class, + 'css_transient_cache.ajax_handler' => Admin\ReenableCssTransientCachingAjaxAction::class, + 'css_transient_cache.monitor' => BackgroundTask\MonitorCssTransientCaching::class, + 'dependency_support' => DependencySupport::class, + 'dev_tools.block_sources' => DevTools\BlockSources::class, + 'dev_tools.callback_reflection' => DevTools\CallbackReflection::class, + 'dev_tools.error_page' => DevTools\ErrorPage::class, + 'dev_tools.file_reflection' => DevTools\FileReflection::class, + 'dev_tools.likely_culprit_detector' => DevTools\LikelyCulpritDetector::class, + 'dev_tools.user_access' => DevTools\UserAccess::class, + 'editor.editor_support' => Editor\EditorSupport::class, + 'extra_theme_and_plugin_headers' => ExtraThemeAndPluginHeaders::class, + 'loading_error' => LoadingError::class, + 'mobile_redirection' => MobileRedirection::class, + 'obsolete_block_attribute_remover' => ObsoleteBlockAttributeRemover::class, + 'optimizer' => OptimizerService::class, + 'optimizer.hero_candidate_filtering' => HeroCandidateFiltering::class, + 'paired_routing' => PairedRouting::class, + 'paired_url' => PairedUrl::class, + 'plugin_activation_notice' => Admin\PluginActivationNotice::class, + 'plugin_activation_site_scan' => Admin\PluginActivationSiteScan::class, + '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.scannable_urls_controller' => Validation\ScannableURLsRestController::class, + 'rest.validation_counts_controller' => Validation\ValidationCountsRestController::class, + 'sandboxing' => Sandboxing::class, + 'server_timing' => Instrumentation\ServerTiming::class, + 'site_health_integration' => Admin\SiteHealth::class, + 'support' => SupportCliCommand::class, + 'support_rest_controller' => SupportRESTController::class, + 'url_validation_cron' => URLValidationCron::class, + 'url_validation_rest_controller' => Validation\URLValidationRESTController::class, + 'validated_url_stylesheet_gc' => BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, + 'validated_data_gc' => BackgroundTask\ValidationDataGarbageCollection::class, + 'validation.scannable_url_provider' => ScannableURLProvider::class, + 'validation.url_validation_provider' => URLValidationProvider::class, ]; /** diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php new file mode 100644 index 00000000000..92b88ef805c --- /dev/null +++ b/src/BlockUniqidClassNameTransformer.php @@ -0,0 +1,188 @@ +=' ) + ) + && + amp_is_request() + ); + } + + /** + * Return a name of an action hook used to register block inline styles. + * + * For block based themes, inline block styles are loaded in head. + * For classic themes, styles are loaded in the body because the `wp_head` + * action (and `wp_enqueue_scripts`) happens before the `render_block`. + * + * @return string Action hook name. + */ + private static function get_block_inline_style_registration_hook_name() { + if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { + return 'wp_enqueue_scripts'; + } + + return 'wp_footer'; + } + + /** + * Register the service with the system. + * + * @return void + */ + public function register() { + add_filter( + 'render_block', + [ $this, 'transform_class_names_in_block_content' ], + PHP_INT_MAX, + 1 + ); + add_filter( + self::get_block_inline_style_registration_hook_name(), + [ $this, 'transform_class_names_in_inline_styles' ], + defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound + ); + } + + /** + * Get regular expression pattern for matching uniqid-based class name. + * + * Note that `uniqid()` returns a string consisting of 13 hex characters. + * + * @return string Regular expression pattern. + */ + private static function get_class_name_regexp_pattern() { + $combined_block_prefixes = implode( '|', self::CLASS_NAME_PREFIXES ); + + return "/($combined_block_prefixes)[a-f0-9]{13}/"; + } + + /** + * Transform uniqid-based class names in block content. + * + * @param string $block_content Block content. + * + * @return string + */ + public function transform_class_names_in_block_content( $block_content ) { + preg_match( self::get_class_name_regexp_pattern(), $block_content, $matches ); + + if ( empty( $matches ) ) { + return $block_content; + } + + list( $class_name, $class_name_prefix ) = $matches; + + $new_class_name = wp_unique_id( $class_name_prefix ); + $this->class_name_mapping[ $class_name ] = $new_class_name; + + $new_block_content = str_replace( $class_name, $new_class_name, $block_content ); + + return $new_block_content; + } + + /** + * Transform uniqid-based class names in inline styles. + * + * @return void + */ + public function transform_class_names_in_inline_styles() { + global $wp_styles; + + $mapped_handles = array_intersect( + $wp_styles->queue, + array_keys( $this->class_name_mapping ) + ); + + foreach ( $mapped_handles as $handle ) { + if ( empty( $wp_styles->registered[ $handle ]->extra['after'] ) ) { + continue; + } + + $new_class_name = $this->class_name_mapping[ $handle ]; + + foreach ( $wp_styles->registered[ $handle ]->extra['after'] as &$inline_styles ) { + $inline_styles = str_replace( $handle, $new_class_name, $inline_styles ); + } + } + } +} diff --git a/tests/php/src/BlockUniqidClassNameTransformerTest.php b/tests/php/src/BlockUniqidClassNameTransformerTest.php new file mode 100644 index 00000000000..c6006e8d439 --- /dev/null +++ b/tests/php/src/BlockUniqidClassNameTransformerTest.php @@ -0,0 +1,158 @@ +markTestSkipped( 'Block uniqid class name transformer requires WordPress 5.0.3.' ); + } + + $this->instance = new BlockUniqidClassNameTransformer(); + } + + public function test_it_can_be_initialized() { + $this->assertInstanceOf( Conditional::class, $this->instance ); + $this->assertInstanceOf( Delayed::class, $this->instance ); + $this->assertInstanceOf( Registerable::class, $this->instance ); + $this->assertInstanceOf( Service::class, $this->instance ); + } + + /** @covers ::get_registration_action() */ + public function test_get_registration_action() { + $this->assertEquals( 'wp', BlockUniqidClassNameTransformer::get_registration_action() ); + } + + /** + * @covers ::is_needed() + * @covers ::has_gutenberg_plugin() + */ + public function test_is_needed() { + if ( + ! defined( 'GUTENBERG_VERSION' ) + && + version_compare( get_bloginfo( 'version' ), '5.9', '<' ) + ) { + $this->assertFalse( BlockUniqidClassNameTransformer::is_needed() ); + } else { + AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG ); + $post_id = self::factory()->post->create(); + + $this->go_to( get_permalink( $post_id ) ); + $this->assertFalse( amp_is_request() ); + $this->assertFalse( BlockUniqidClassNameTransformer::is_needed() ); + + $this->go_to( amp_get_permalink( $post_id ) ); + $this->assertTrue( amp_is_request() ); + $this->assertTrue( BlockUniqidClassNameTransformer::is_needed() ); + } + } + + /** + * @covers ::register() + * @covers ::get_block_inline_style_registration_hook_name() + */ + public function test_register() { + $this->instance->register(); + $this->assertEquals( PHP_INT_MAX, has_filter( 'render_block', [ $this->instance, 'transform_class_names_in_block_content' ] ) ); + + $expected_hook_name = function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ? 'wp_enqueue_scripts' : 'wp_footer'; + $this->assertEquals( + defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX, // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound + has_filter( $expected_hook_name, [ $this->instance, 'transform_class_names_in_inline_styles' ] ) + ); + } + + /** @return array */ + public function get_block_data() { + return [ + 'transform_duotone_class_name' => [ + 'block_content' => '
', + 'expected_block_content' => '
', + 'style_handle' => 'wp-duotone-621e12fb51e3a', + 'style_content' => ".wp-duotone-621e12fb51e3a > .wp-block-cover__image-background, .wp-duotone-621e12fb51e3a > .wp-block-cover__video-background{filter:url(\'#wp-duotone-621e12fb51e3a\') !important;}", + 'expected_style_content' => ".wp-duotone-1 > .wp-block-cover__image-background, .wp-duotone-1 > .wp-block-cover__video-background{filter:url(\'#wp-duotone-1\') !important;}", + ], + 'transform_container_class_name' => [ + 'block_content' => '
', + 'expected_block_content' => '
', + 'style_handle' => null, + 'style_content' => null, + 'expected_style_content' => null, + ], + 'ignore_class_names_without_hash' => [ + 'block_content' => '
', + 'expected_block_content' => '
', + 'style_handle' => null, + 'style_content' => null, + 'expected_style_content' => null, + ], + 'ignore_already_transformed_class_names' => [ + 'block_content' => '
', + 'expected_block_content' => '
', + 'style_handle' => null, + 'style_content' => null, + 'expected_style_content' => null, + ], + ]; + } + + /** + * @covers ::transform_class_names_in_block_content() + * @covers ::transform_class_names_in_inline_styles() + * @covers ::get_class_name_regexp_pattern() + * + * @dataProvider get_block_data + * + * @param string $block_content + * @param string $expected_block_content + * @param string|null $style_handle + * @param string|null $style_content + * @param string|null $expected_style_content + */ + public function test_transform_class_names( $block_content, $expected_block_content, $style_handle, $style_content, $expected_style_content ) { + $transformed_block_content = $this->instance->transform_class_names_in_block_content( $block_content ); + $this->assertEqualMarkup( $expected_block_content, $transformed_block_content ); + + if ( ! empty( $style_handle ) && ! empty( $style_content ) && ! empty( $expected_style_content ) ) { + global $wp_styles; + + wp_register_style( $style_handle, '', [], '1' ); + wp_add_inline_style( $style_handle, $style_content ); + wp_enqueue_style( $style_handle ); + + $this->assertTrue( wp_style_is( $style_handle ) ); + + $this->instance->transform_class_names_in_inline_styles(); + + $this->assertEquals( + $expected_style_content, + $wp_styles->registered[ $style_handle ]->extra['after'][0] + ); + } + } +} From 41fa8776cb01103df0f18eaf39eeae57c037f5a7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 11:04:59 -0800 Subject: [PATCH 02/21] Utilize static variable for constructed regex --- src/BlockUniqidClassNameTransformer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php index 92b88ef805c..2bd1c658f57 100644 --- a/src/BlockUniqidClassNameTransformer.php +++ b/src/BlockUniqidClassNameTransformer.php @@ -131,9 +131,13 @@ public function register() { * @return string Regular expression pattern. */ private static function get_class_name_regexp_pattern() { - $combined_block_prefixes = implode( '|', self::CLASS_NAME_PREFIXES ); + static $pattern = null; + if ( null === $pattern ) { + $combined_block_prefixes = implode( '|', self::CLASS_NAME_PREFIXES ); - return "/($combined_block_prefixes)[a-f0-9]{13}/"; + $pattern = "/($combined_block_prefixes)[a-f0-9]{13}/"; + } + return $pattern; } /** From 54746e3163447f2bc304bda6b2540229a4e21d6a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 12:10:21 -0800 Subject: [PATCH 03/21] Reorganize transform_class_names_in_block_content slightly --- src/BlockUniqidClassNameTransformer.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php index 2bd1c658f57..dea02499398 100644 --- a/src/BlockUniqidClassNameTransformer.php +++ b/src/BlockUniqidClassNameTransformer.php @@ -144,24 +144,18 @@ private static function get_class_name_regexp_pattern() { * Transform uniqid-based class names in block content. * * @param string $block_content Block content. - * - * @return string + * @return string Transformed block content. */ public function transform_class_names_in_block_content( $block_content ) { - preg_match( self::get_class_name_regexp_pattern(), $block_content, $matches ); - - if ( empty( $matches ) ) { - return $block_content; - } + if ( preg_match( self::get_class_name_regexp_pattern(), $block_content, $matches ) ) { + list( $class_name, $class_name_prefix ) = $matches; - list( $class_name, $class_name_prefix ) = $matches; + $new_class_name = wp_unique_id( $class_name_prefix ); + $this->class_name_mapping[ $class_name ] = $new_class_name; - $new_class_name = wp_unique_id( $class_name_prefix ); - $this->class_name_mapping[ $class_name ] = $new_class_name; - - $new_block_content = str_replace( $class_name, $new_class_name, $block_content ); - - return $new_block_content; + $block_content = str_replace( $class_name, $new_class_name, $block_content ); + } + return $block_content; } /** From e7c349fa660fa9872ce8c75723d7ea94b1807b6c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 12:19:43 -0800 Subject: [PATCH 04/21] Add failing test case for class being incorrectly transformed in text content --- .../src/BlockUniqidClassNameTransformerTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/php/src/BlockUniqidClassNameTransformerTest.php b/tests/php/src/BlockUniqidClassNameTransformerTest.php index c6006e8d439..90a689a1dd0 100644 --- a/tests/php/src/BlockUniqidClassNameTransformerTest.php +++ b/tests/php/src/BlockUniqidClassNameTransformerTest.php @@ -91,29 +91,29 @@ public function test_register() { public function get_block_data() { return [ 'transform_duotone_class_name' => [ - 'block_content' => '
', - 'expected_block_content' => '
', + 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', 'style_handle' => 'wp-duotone-621e12fb51e3a', 'style_content' => ".wp-duotone-621e12fb51e3a > .wp-block-cover__image-background, .wp-duotone-621e12fb51e3a > .wp-block-cover__video-background{filter:url(\'#wp-duotone-621e12fb51e3a\') !important;}", 'expected_style_content' => ".wp-duotone-1 > .wp-block-cover__image-background, .wp-duotone-1 > .wp-block-cover__video-background{filter:url(\'#wp-duotone-1\') !important;}", ], 'transform_container_class_name' => [ - 'block_content' => '
', - 'expected_block_content' => '
', + 'block_content' => '
This is a super cool class name: wp-container-0123456789abc!
', + 'expected_block_content' => '
This is a super cool class name: wp-container-0123456789abc!
', 'style_handle' => null, 'style_content' => null, 'expected_style_content' => null, ], 'ignore_class_names_without_hash' => [ - 'block_content' => '
', - 'expected_block_content' => '
', + 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', 'style_handle' => null, 'style_content' => null, 'expected_style_content' => null, ], 'ignore_already_transformed_class_names' => [ - 'block_content' => '
', - 'expected_block_content' => '
', + 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', 'style_handle' => null, 'style_content' => null, 'expected_style_content' => null, From b441abe0592badc3fd997cbf8f0abeaf60b2ed72 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 12:40:38 -0800 Subject: [PATCH 05/21] Restrict replacements to class attribute values in start tags --- src/BlockUniqidClassNameTransformer.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php index dea02499398..7b656fc0edd 100644 --- a/src/BlockUniqidClassNameTransformer.php +++ b/src/BlockUniqidClassNameTransformer.php @@ -135,7 +135,10 @@ private static function get_class_name_regexp_pattern() { if ( null === $pattern ) { $combined_block_prefixes = implode( '|', self::CLASS_NAME_PREFIXES ); - $pattern = "/($combined_block_prefixes)[a-f0-9]{13}/"; + $pattern = sprintf( + '/(?P<\w+[^>]*?\sclass="[^"]*?)(?P%s)(?P[a-f0-9]{13})\b/', + $combined_block_prefixes + ); } return $pattern; } @@ -147,15 +150,18 @@ private static function get_class_name_regexp_pattern() { * @return string Transformed block content. */ public function transform_class_names_in_block_content( $block_content ) { - if ( preg_match( self::get_class_name_regexp_pattern(), $block_content, $matches ) ) { - list( $class_name, $class_name_prefix ) = $matches; + return preg_replace_callback( + self::get_class_name_regexp_pattern(), + function( $matches ) { + $old_class_name = $matches['class_name_prefix'] . $matches['uniqid']; + $new_class_name = wp_unique_id( $matches['class_name_prefix'] ); - $new_class_name = wp_unique_id( $class_name_prefix ); - $this->class_name_mapping[ $class_name ] = $new_class_name; + $this->class_name_mapping[ $old_class_name ] = $new_class_name; - $block_content = str_replace( $class_name, $new_class_name, $block_content ); - } - return $block_content; + return $matches['start_tag_prefix'] . $new_class_name; + }, + $block_content + ); } /** From 473a8e769900cf5472c4fb4bc847f1618dae6dc4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 12:44:41 -0800 Subject: [PATCH 06/21] Provide polyfill for wp_unique_id() for WP<5.0.3 --- src/BlockUniqidClassNameTransformer.php | 28 ++++++++++++++----- .../BlockUniqidClassNameTransformerTest.php | 5 +--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php index 7b656fc0edd..23af15330f8 100644 --- a/src/BlockUniqidClassNameTransformer.php +++ b/src/BlockUniqidClassNameTransformer.php @@ -71,11 +71,6 @@ public static function has_gutenberg_plugin() { * @return bool Whether the conditional object is needed. */ public static function is_needed() { - // At least WP 5.0.3 is needed since `wp_unique_id` has been introduced there. - if ( version_compare( get_bloginfo( 'version' ), '5.0.3', '<' ) ) { - return false; - } - return ( ( self::has_gutenberg_plugin() @@ -154,7 +149,7 @@ public function transform_class_names_in_block_content( $block_content ) { self::get_class_name_regexp_pattern(), function( $matches ) { $old_class_name = $matches['class_name_prefix'] . $matches['uniqid']; - $new_class_name = wp_unique_id( $matches['class_name_prefix'] ); + $new_class_name = $this->unique_id( $matches['class_name_prefix'] ); $this->class_name_mapping[ $old_class_name ] = $new_class_name; @@ -185,8 +180,27 @@ public function transform_class_names_in_inline_styles() { $new_class_name = $this->class_name_mapping[ $handle ]; foreach ( $wp_styles->registered[ $handle ]->extra['after'] as &$inline_styles ) { - $inline_styles = str_replace( $handle, $new_class_name, $inline_styles ); + $inline_styles = str_replace( "{$handle}", "{$new_class_name}", $inline_styles ); } } } + + /** + * Gets unique ID. + * + * This is a polyfill for WordPress <5.0.3. + * + * @see wp_unique_id() + * + * @param string $prefix Prefix for the returned ID. + * @return string Unique ID. + */ + private static function unique_id( $prefix = '' ) { + if ( function_exists( 'wp_unique_id' ) ) { + return wp_unique_id( $prefix ); + } else { + static $id_counter = 0; + return $prefix . (string) ++$id_counter; + } + } } diff --git a/tests/php/src/BlockUniqidClassNameTransformerTest.php b/tests/php/src/BlockUniqidClassNameTransformerTest.php index 90a689a1dd0..8c748460d18 100644 --- a/tests/php/src/BlockUniqidClassNameTransformerTest.php +++ b/tests/php/src/BlockUniqidClassNameTransformerTest.php @@ -28,10 +28,6 @@ final class BlockUniqidClassNameTransformerTest extends TestCase { public function setUp() { parent::setUp(); - if ( version_compare( get_bloginfo( 'version' ), '5.0.3', '<' ) ) { - $this->markTestSkipped( 'Block uniqid class name transformer requires WordPress 5.0.3.' ); - } - $this->instance = new BlockUniqidClassNameTransformer(); } @@ -125,6 +121,7 @@ public function get_block_data() { * @covers ::transform_class_names_in_block_content() * @covers ::transform_class_names_in_inline_styles() * @covers ::get_class_name_regexp_pattern() + * @covers ::unique_id() * * @dataProvider get_block_data * From e41fa08057053c3c388c2d65dd9071f0d553935d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Mar 2022 17:17:27 -0800 Subject: [PATCH 07/21] Implement AMP_Block_Uniqid_Sanitizer --- .phpstorm.meta.php | 116 +++++----- .../class-amp-block-uniqid-sanitizer.php | 200 +++++++++++++++++ src/AmpWpPlugin.php | 118 +++++----- src/BlockUniqidClassNameTransformer.php | 206 ------------------ src/BlockUniqidTransformer.php | 74 +++++++ ...est.php => BlockUniqidTransformerTest.php} | 43 ++-- 6 files changed, 412 insertions(+), 345 deletions(-) create mode 100644 includes/sanitizers/class-amp-block-uniqid-sanitizer.php delete mode 100644 src/BlockUniqidClassNameTransformer.php create mode 100644 src/BlockUniqidTransformer.php rename tests/php/src/{BlockUniqidClassNameTransformerTest.php => BlockUniqidTransformerTest.php} (79%) diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index faf0df26ea0..253a6382fd9 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -7,64 +7,64 @@ // TODO: I'd like to use AmpWpPlugin::SERVICES directly here but it doesn't seem to work. map( [ - 'admin.analytics_menu' => \AmpProject\AmpWP\Admin\AnalyticsOptionsSubmenu::class, - 'admin.after_activation_site_scan' => \AmpProject\AmpWP\Admin\AfterActivationSiteScan::class, - 'admin.google_fonts' => \AmpProject\AmpWP\Admin\GoogleFonts::class, - 'admin.onboarding_menu' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenu::class, - 'admin.onboarding_wizard' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenuPage::class, - 'admin.options_menu' => \AmpProject\AmpWP\Admin\OptionsMenu::class, - 'admin.support_screen' => \AmpProject\AmpWP\Admin\SupportScreen::class, - 'admin.support' => \AmpProject\AmpWP\Admin\SupportLink::class, - 'admin.paired_browsing' => \AmpProject\AmpWP\Admin\PairedBrowsing::class, - 'admin.plugin_row_meta' => \AmpProject\AmpWP\Admin\PluginRowMeta::class, - 'admin.polyfills' => \AmpProject\AmpWP\Admin\Polyfills::class, - 'admin.user_rest_endpoint_extension' => \AmpProject\AmpWP\Admin\UserRESTEndpointExtension::class, - 'admin.validation_counts' => \AmpProject\AmpWP\Admin\ValidationCounts::class, - 'amp_slug_customization_watcher' => \AmpProject\AmpWP\AmpSlugCustomizationWatcher::class, - 'background_task_deactivator' => \AmpProject\AmpWP\BackgroundTask\BackgroundTaskDeactivator::class, - 'block_uniqid_class_name_transformer' => \AmpProject\AmpWP\BlockUniqidClassNameTransformer::class, - 'cli.command_namespace' => \AmpProject\AmpWP\CliCli\CommandNamespaceRegistration::class, - 'cli.optimizer_command' => \AmpProject\AmpWP\CliCli\OptimizerCommand::class, - 'cli.transformer_command' => \AmpProject\AmpWP\CliCli\TransformerCommand::class, - 'cli.validation_command' => \AmpProject\AmpWP\CliCli\ValidationCommand::class, - 'css_transient_cache.ajax_handler' => \AmpProject\AmpWP\Admin\ReenableCssTransientCachingAjaxAction::class, - 'css_transient_cache.monitor' => \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching::class, - 'dependency_support' => \AmpProject\AmpWP\DependencySupport::class, - 'dev_tools.block_sources' => \AmpProject\AmpWP\DevTools\BlockSources::class, - 'dev_tools.callback_reflection' => \AmpProject\AmpWP\DevTools\CallbackReflection::class, - 'dev_tools.error_page' => \AmpProject\AmpWP\DevTools\ErrorPage::class, - 'dev_tools.file_reflection' => \AmpProject\AmpWP\DevTools\FileReflection::class, - 'dev_tools.likely_culprit_detector' => \AmpProject\AmpWP\DevTools\LikelyCulpritDetector::class, - 'dev_tools.user_access' => \AmpProject\AmpWP\DevTools\UserAccess::class, - 'editor.editor_support' => \AmpProject\AmpWP\Editor\EditorSupport::class, - 'extra_theme_and_plugin_headers' => \AmpProject\AmpWP\ExtraThemeAndPluginHeaders::class, - 'injector' => \AmpProject\AmpWP\Infrastructure\Injector::class, - 'loading_error' => \AmpProject\AmpWP\LoadingError::class, - 'mobile_redirection' => \AmpProject\AmpWP\MobileRedirection::class, - 'obsolete_block_attribute_remover' => \AmpProject\AmpWP\ObsoleteBlockAttributeRemover::class, - 'optimizer' => \AmpProject\AmpWP\Optimizer\OptimizerService::class, - 'optimizer.hero_candidate_filtering' => \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::class, - 'paired_routing' => \AmpProject\AmpWP\PairedRouting::class, - 'paired_url' => \AmpProject\AmpWP\PairedUrl::class, - 'plugin_activation_notice' => \AmpProject\AmpWP\Admin\PluginActivationNotice::class, - '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.scannable_urls_controller' => \AmpProject\AmpWP\Validation\ScannableURLsRestController::class, - 'rest.validation_counts_controller' => \AmpProject\AmpWP\Validation\ValidationCountsRestController::class, - 'sandboxing' => \AmpProject\AmpWP\Sandboxing::class, - 'server_timing' => \AmpProject\AmpWP\Instrumentation\ServerTiming::class, - 'site_health_integration' => \AmpProject\AmpWP\Admin\SiteHealth::class, - 'support' => \AmpProject\AmpWP\Support\SupportCliCommand::class, - 'support_rest_controller' => \AmpProject\AmpWP\Support\SupportRESTController::class, - 'url_validation_cron' => \AmpProject\AmpWP\Validation\URLValidationCron::class, - 'url_validation_rest_controller' => \AmpProject\AmpWP\Validation\URLValidationRESTController::class, - 'validated_url_stylesheet_gc' => \AmpProject\AmpWP\BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, - 'validation_data_gc' => \AmpProject\AmpWP\BackgroundTask\ValidationDataGarbageCollection::class, - 'validation.scannable_url_provider' => \AmpProject\AmpWP\Validation\ScannableURLProvider::class, - 'validation.url_validation_provider' => \AmpProject\AmpWP\Validation\URLValidationProvider::class, + 'admin.analytics_menu' => \AmpProject\AmpWP\Admin\AnalyticsOptionsSubmenu::class, + 'admin.after_activation_site_scan' => \AmpProject\AmpWP\Admin\AfterActivationSiteScan::class, + 'admin.google_fonts' => \AmpProject\AmpWP\Admin\GoogleFonts::class, + 'admin.onboarding_menu' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenu::class, + 'admin.onboarding_wizard' => \AmpProject\AmpWP\Admin\OnboardingWizardSubmenuPage::class, + 'admin.options_menu' => \AmpProject\AmpWP\Admin\OptionsMenu::class, + 'admin.support_screen' => \AmpProject\AmpWP\Admin\SupportScreen::class, + 'admin.support' => \AmpProject\AmpWP\Admin\SupportLink::class, + 'admin.paired_browsing' => \AmpProject\AmpWP\Admin\PairedBrowsing::class, + 'admin.plugin_row_meta' => \AmpProject\AmpWP\Admin\PluginRowMeta::class, + 'admin.polyfills' => \AmpProject\AmpWP\Admin\Polyfills::class, + 'admin.user_rest_endpoint_extension' => \AmpProject\AmpWP\Admin\UserRESTEndpointExtension::class, + 'admin.validation_counts' => \AmpProject\AmpWP\Admin\ValidationCounts::class, + 'amp_slug_customization_watcher' => \AmpProject\AmpWP\AmpSlugCustomizationWatcher::class, + 'background_task_deactivator' => \AmpProject\AmpWP\BackgroundTask\BackgroundTaskDeactivator::class, + 'block_uniqid_transformer' => \AmpProject\AmpWP\BlockUniqidTransformer::class, + 'cli.command_namespace' => \AmpProject\AmpWP\CliCli\CommandNamespaceRegistration::class, + 'cli.optimizer_command' => \AmpProject\AmpWP\CliCli\OptimizerCommand::class, + 'cli.transformer_command' => \AmpProject\AmpWP\CliCli\TransformerCommand::class, + 'cli.validation_command' => \AmpProject\AmpWP\CliCli\ValidationCommand::class, + 'css_transient_cache.ajax_handler' => \AmpProject\AmpWP\Admin\ReenableCssTransientCachingAjaxAction::class, + 'css_transient_cache.monitor' => \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching::class, + 'dependency_support' => \AmpProject\AmpWP\DependencySupport::class, + 'dev_tools.block_sources' => \AmpProject\AmpWP\DevTools\BlockSources::class, + 'dev_tools.callback_reflection' => \AmpProject\AmpWP\DevTools\CallbackReflection::class, + 'dev_tools.error_page' => \AmpProject\AmpWP\DevTools\ErrorPage::class, + 'dev_tools.file_reflection' => \AmpProject\AmpWP\DevTools\FileReflection::class, + 'dev_tools.likely_culprit_detector' => \AmpProject\AmpWP\DevTools\LikelyCulpritDetector::class, + 'dev_tools.user_access' => \AmpProject\AmpWP\DevTools\UserAccess::class, + 'editor.editor_support' => \AmpProject\AmpWP\Editor\EditorSupport::class, + 'extra_theme_and_plugin_headers' => \AmpProject\AmpWP\ExtraThemeAndPluginHeaders::class, + 'injector' => \AmpProject\AmpWP\Infrastructure\Injector::class, + 'loading_error' => \AmpProject\AmpWP\LoadingError::class, + 'mobile_redirection' => \AmpProject\AmpWP\MobileRedirection::class, + 'obsolete_block_attribute_remover' => \AmpProject\AmpWP\ObsoleteBlockAttributeRemover::class, + 'optimizer' => \AmpProject\AmpWP\Optimizer\OptimizerService::class, + 'optimizer.hero_candidate_filtering' => \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::class, + 'paired_routing' => \AmpProject\AmpWP\PairedRouting::class, + 'paired_url' => \AmpProject\AmpWP\PairedUrl::class, + 'plugin_activation_notice' => \AmpProject\AmpWP\Admin\PluginActivationNotice::class, + '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.scannable_urls_controller' => \AmpProject\AmpWP\Validation\ScannableURLsRestController::class, + 'rest.validation_counts_controller' => \AmpProject\AmpWP\Validation\ValidationCountsRestController::class, + 'sandboxing' => \AmpProject\AmpWP\Sandboxing::class, + 'server_timing' => \AmpProject\AmpWP\Instrumentation\ServerTiming::class, + 'site_health_integration' => \AmpProject\AmpWP\Admin\SiteHealth::class, + 'support' => \AmpProject\AmpWP\Support\SupportCliCommand::class, + 'support_rest_controller' => \AmpProject\AmpWP\Support\SupportRESTController::class, + 'url_validation_cron' => \AmpProject\AmpWP\Validation\URLValidationCron::class, + 'url_validation_rest_controller' => \AmpProject\AmpWP\Validation\URLValidationRESTController::class, + 'validated_url_stylesheet_gc' => \AmpProject\AmpWP\BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, + 'validation_data_gc' => \AmpProject\AmpWP\BackgroundTask\ValidationDataGarbageCollection::class, + 'validation.scannable_url_provider' => \AmpProject\AmpWP\Validation\ScannableURLProvider::class, + 'validation.url_validation_provider' => \AmpProject\AmpWP\Validation\URLValidationProvider::class, ] ) ); diff --git a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php new file mode 100644 index 00000000000..6b20ecdada5 --- /dev/null +++ b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php @@ -0,0 +1,200 @@ +key_pattern = sprintf( + '/\b(?P%s)(?P[0-9a-f]{13})\b/', + implode( + '|', + self::KEY_PREFIXES + ) + ); + } + + /** + * Sanitize. + */ + public function sanitize() { + $elements = $this->dom->xpath->query( + sprintf( + '//*[ %s ]', + implode( + ' or ', + array_map( + static function ( $class_name_prefix ) { + return sprintf( + 'contains( @class, "%s" )', + $class_name_prefix + ); + }, + self::KEY_PREFIXES + ) + ) + ) + ); + + $replaced_count = 0; + foreach ( $elements as $element ) { + if ( $this->transform_element_with_class_attribute( $element ) ) { + $replaced_count++; + } + } + + if ( $replaced_count > 0 ) { + $this->transform_styles(); + } + } + + /** + * Transform element with class. + * + * @param Element $element Element. + */ + public function transform_element_with_class_attribute( Element $element ) { + $class_name = $element->getAttribute( Attribute::CLASS_ ); + + $count = 0; + + $new_class_name = preg_replace_callback( + $this->key_pattern, + function ( $matches ) { + $old_key = $matches[0]; + + if ( ! isset( $this->key_mapping[ $old_key ] ) ) { + $this->key_mapping[ $old_key ] = self::unique_id( $matches['prefix'] ); + } + $new_key = $this->key_mapping[ $old_key ]; + + if ( 'wp-duotone-' === $matches['prefix'] ) { + $this->transform_duotone_filter( $old_key, $new_key ); + } + + return $new_key; + }, + $class_name, + -1, + $count + ); + if ( 0 === $count ) { + return false; + } else { + $element->setAttribute( Attribute::CLASS_, $new_class_name ); + return true; + } + } + + /** + * Transform duotone filter by updating its ID. + * + * @param string $old_key Old identifier. + * @param string $new_key New identifier. + * + * @return void + */ + public function transform_duotone_filter( $old_key, $new_key ) { + $svg_filter = $this->dom->getElementById( $old_key ); + if ( $svg_filter instanceof Element && Tag::FILTER === $svg_filter->tagName ) { + $svg_filter->setAttribute( Attribute::ID, $new_key ); + } + } + + /** + * Transform styles. + */ + public function transform_styles() { + $styles = $this->dom->xpath->query( + sprintf( + '//style[ %s ]', + implode( + ' or ', + array_map( + static function ( $key_prefix ) { + return sprintf( + 'contains( text(), "%s" )', + $key_prefix + ); + }, + self::KEY_PREFIXES + ) + ) + ) + ); + + foreach ( $styles as $style ) { + $style->textContent = str_replace( + array_keys( $this->key_mapping ), + array_values( $this->key_mapping ), + $style->textContent + ); + } + } + + /** + * Gets unique ID. + * + * This is a polyfill for WordPress <5.0.3. + * + * @see wp_unique_id() + * + * @param string $prefix Prefix for the returned ID. + * @return string Unique ID. + */ + private static function unique_id( $prefix = '' ) { + if ( function_exists( 'wp_unique_id' ) ) { + return wp_unique_id( $prefix ); + } else { + static $id_counter = 0; + return $prefix . (string) ++$id_counter; + } + } +} diff --git a/src/AmpWpPlugin.php b/src/AmpWpPlugin.php index 440e503afa7..ee3a0a736f3 100644 --- a/src/AmpWpPlugin.php +++ b/src/AmpWpPlugin.php @@ -69,65 +69,65 @@ final class AmpWpPlugin extends ServiceBasedPlugin { * @var string[] */ const SERVICES = [ - 'admin.analytics_menu' => Admin\AnalyticsOptionsSubmenu::class, - 'admin.after_activation_site_scan' => Admin\AfterActivationSiteScan::class, - 'admin.google_fonts' => Admin\GoogleFonts::class, - 'admin.onboarding_menu' => Admin\OnboardingWizardSubmenu::class, - 'admin.onboarding_wizard' => Admin\OnboardingWizardSubmenuPage::class, - 'admin.options_menu' => Admin\OptionsMenu::class, - 'admin.paired_browsing' => Admin\PairedBrowsing::class, - 'admin.plugin_row_meta' => Admin\PluginRowMeta::class, - 'admin.support_screen' => Admin\SupportScreen::class, - 'admin.support' => Admin\SupportLink::class, - 'admin.polyfills' => Admin\Polyfills::class, - 'admin.user_rest_endpoint_extension' => Admin\UserRESTEndpointExtension::class, - 'admin.validation_counts' => Admin\ValidationCounts::class, - 'admin.amp_plugins' => Admin\AmpPlugins::class, - 'admin.amp_themes' => Admin\AmpThemes::class, - 'amp_slug_customization_watcher' => AmpSlugCustomizationWatcher::class, - 'background_task_deactivator' => BackgroundTaskDeactivator::class, - 'block_uniqid_class_name_transformer' => BlockUniqidClassNameTransformer::class, - 'cli.command_namespace' => Cli\CommandNamespaceRegistration::class, - 'cli.optimizer_command' => Cli\OptimizerCommand::class, - 'cli.transformer_command' => Cli\TransformerCommand::class, - 'cli.validation_command' => Cli\ValidationCommand::class, - 'css_transient_cache.ajax_handler' => Admin\ReenableCssTransientCachingAjaxAction::class, - 'css_transient_cache.monitor' => BackgroundTask\MonitorCssTransientCaching::class, - 'dependency_support' => DependencySupport::class, - 'dev_tools.block_sources' => DevTools\BlockSources::class, - 'dev_tools.callback_reflection' => DevTools\CallbackReflection::class, - 'dev_tools.error_page' => DevTools\ErrorPage::class, - 'dev_tools.file_reflection' => DevTools\FileReflection::class, - 'dev_tools.likely_culprit_detector' => DevTools\LikelyCulpritDetector::class, - 'dev_tools.user_access' => DevTools\UserAccess::class, - 'editor.editor_support' => Editor\EditorSupport::class, - 'extra_theme_and_plugin_headers' => ExtraThemeAndPluginHeaders::class, - 'loading_error' => LoadingError::class, - 'mobile_redirection' => MobileRedirection::class, - 'obsolete_block_attribute_remover' => ObsoleteBlockAttributeRemover::class, - 'optimizer' => OptimizerService::class, - 'optimizer.hero_candidate_filtering' => HeroCandidateFiltering::class, - 'paired_routing' => PairedRouting::class, - 'paired_url' => PairedUrl::class, - 'plugin_activation_notice' => Admin\PluginActivationNotice::class, - '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.scannable_urls_controller' => Validation\ScannableURLsRestController::class, - 'rest.validation_counts_controller' => Validation\ValidationCountsRestController::class, - 'sandboxing' => Sandboxing::class, - 'server_timing' => Instrumentation\ServerTiming::class, - 'site_health_integration' => Admin\SiteHealth::class, - 'support' => SupportCliCommand::class, - 'support_rest_controller' => SupportRESTController::class, - 'url_validation_cron' => URLValidationCron::class, - 'url_validation_rest_controller' => Validation\URLValidationRESTController::class, - 'validated_url_stylesheet_gc' => BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, - 'validated_data_gc' => BackgroundTask\ValidationDataGarbageCollection::class, - 'validation.scannable_url_provider' => ScannableURLProvider::class, - 'validation.url_validation_provider' => URLValidationProvider::class, + 'admin.analytics_menu' => Admin\AnalyticsOptionsSubmenu::class, + 'admin.after_activation_site_scan' => Admin\AfterActivationSiteScan::class, + 'admin.google_fonts' => Admin\GoogleFonts::class, + 'admin.onboarding_menu' => Admin\OnboardingWizardSubmenu::class, + 'admin.onboarding_wizard' => Admin\OnboardingWizardSubmenuPage::class, + 'admin.options_menu' => Admin\OptionsMenu::class, + 'admin.paired_browsing' => Admin\PairedBrowsing::class, + 'admin.plugin_row_meta' => Admin\PluginRowMeta::class, + 'admin.support_screen' => Admin\SupportScreen::class, + 'admin.support' => Admin\SupportLink::class, + 'admin.polyfills' => Admin\Polyfills::class, + 'admin.user_rest_endpoint_extension' => Admin\UserRESTEndpointExtension::class, + 'admin.validation_counts' => Admin\ValidationCounts::class, + 'admin.amp_plugins' => Admin\AmpPlugins::class, + 'admin.amp_themes' => Admin\AmpThemes::class, + 'amp_slug_customization_watcher' => AmpSlugCustomizationWatcher::class, + 'background_task_deactivator' => BackgroundTaskDeactivator::class, + 'block_uniqid_transformer' => BlockUniqidTransformer::class, + 'cli.command_namespace' => Cli\CommandNamespaceRegistration::class, + 'cli.optimizer_command' => Cli\OptimizerCommand::class, + 'cli.transformer_command' => Cli\TransformerCommand::class, + 'cli.validation_command' => Cli\ValidationCommand::class, + 'css_transient_cache.ajax_handler' => Admin\ReenableCssTransientCachingAjaxAction::class, + 'css_transient_cache.monitor' => BackgroundTask\MonitorCssTransientCaching::class, + 'dependency_support' => DependencySupport::class, + 'dev_tools.block_sources' => DevTools\BlockSources::class, + 'dev_tools.callback_reflection' => DevTools\CallbackReflection::class, + 'dev_tools.error_page' => DevTools\ErrorPage::class, + 'dev_tools.file_reflection' => DevTools\FileReflection::class, + 'dev_tools.likely_culprit_detector' => DevTools\LikelyCulpritDetector::class, + 'dev_tools.user_access' => DevTools\UserAccess::class, + 'editor.editor_support' => Editor\EditorSupport::class, + 'extra_theme_and_plugin_headers' => ExtraThemeAndPluginHeaders::class, + 'loading_error' => LoadingError::class, + 'mobile_redirection' => MobileRedirection::class, + 'obsolete_block_attribute_remover' => ObsoleteBlockAttributeRemover::class, + 'optimizer' => OptimizerService::class, + 'optimizer.hero_candidate_filtering' => HeroCandidateFiltering::class, + 'paired_routing' => PairedRouting::class, + 'paired_url' => PairedUrl::class, + 'plugin_activation_notice' => Admin\PluginActivationNotice::class, + '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.scannable_urls_controller' => Validation\ScannableURLsRestController::class, + 'rest.validation_counts_controller' => Validation\ValidationCountsRestController::class, + 'sandboxing' => Sandboxing::class, + 'server_timing' => Instrumentation\ServerTiming::class, + 'site_health_integration' => Admin\SiteHealth::class, + 'support' => SupportCliCommand::class, + 'support_rest_controller' => SupportRESTController::class, + 'url_validation_cron' => URLValidationCron::class, + 'url_validation_rest_controller' => Validation\URLValidationRESTController::class, + 'validated_url_stylesheet_gc' => BackgroundTask\ValidatedUrlStylesheetDataGarbageCollection::class, + 'validated_data_gc' => BackgroundTask\ValidationDataGarbageCollection::class, + 'validation.scannable_url_provider' => ScannableURLProvider::class, + 'validation.url_validation_provider' => URLValidationProvider::class, ]; /** diff --git a/src/BlockUniqidClassNameTransformer.php b/src/BlockUniqidClassNameTransformer.php deleted file mode 100644 index 23af15330f8..00000000000 --- a/src/BlockUniqidClassNameTransformer.php +++ /dev/null @@ -1,206 +0,0 @@ -=' ) - ) - && - amp_is_request() - ); - } - - /** - * Return a name of an action hook used to register block inline styles. - * - * For block based themes, inline block styles are loaded in head. - * For classic themes, styles are loaded in the body because the `wp_head` - * action (and `wp_enqueue_scripts`) happens before the `render_block`. - * - * @return string Action hook name. - */ - private static function get_block_inline_style_registration_hook_name() { - if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { - return 'wp_enqueue_scripts'; - } - - return 'wp_footer'; - } - - /** - * Register the service with the system. - * - * @return void - */ - public function register() { - add_filter( - 'render_block', - [ $this, 'transform_class_names_in_block_content' ], - PHP_INT_MAX, - 1 - ); - add_filter( - self::get_block_inline_style_registration_hook_name(), - [ $this, 'transform_class_names_in_inline_styles' ], - defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound - ); - } - - /** - * Get regular expression pattern for matching uniqid-based class name. - * - * Note that `uniqid()` returns a string consisting of 13 hex characters. - * - * @return string Regular expression pattern. - */ - private static function get_class_name_regexp_pattern() { - static $pattern = null; - if ( null === $pattern ) { - $combined_block_prefixes = implode( '|', self::CLASS_NAME_PREFIXES ); - - $pattern = sprintf( - '/(?P<\w+[^>]*?\sclass="[^"]*?)(?P%s)(?P[a-f0-9]{13})\b/', - $combined_block_prefixes - ); - } - return $pattern; - } - - /** - * Transform uniqid-based class names in block content. - * - * @param string $block_content Block content. - * @return string Transformed block content. - */ - public function transform_class_names_in_block_content( $block_content ) { - return preg_replace_callback( - self::get_class_name_regexp_pattern(), - function( $matches ) { - $old_class_name = $matches['class_name_prefix'] . $matches['uniqid']; - $new_class_name = $this->unique_id( $matches['class_name_prefix'] ); - - $this->class_name_mapping[ $old_class_name ] = $new_class_name; - - return $matches['start_tag_prefix'] . $new_class_name; - }, - $block_content - ); - } - - /** - * Transform uniqid-based class names in inline styles. - * - * @return void - */ - public function transform_class_names_in_inline_styles() { - global $wp_styles; - - $mapped_handles = array_intersect( - $wp_styles->queue, - array_keys( $this->class_name_mapping ) - ); - - foreach ( $mapped_handles as $handle ) { - if ( empty( $wp_styles->registered[ $handle ]->extra['after'] ) ) { - continue; - } - - $new_class_name = $this->class_name_mapping[ $handle ]; - - foreach ( $wp_styles->registered[ $handle ]->extra['after'] as &$inline_styles ) { - $inline_styles = str_replace( "{$handle}", "{$new_class_name}", $inline_styles ); - } - } - } - - /** - * Gets unique ID. - * - * This is a polyfill for WordPress <5.0.3. - * - * @see wp_unique_id() - * - * @param string $prefix Prefix for the returned ID. - * @return string Unique ID. - */ - private static function unique_id( $prefix = '' ) { - if ( function_exists( 'wp_unique_id' ) ) { - return wp_unique_id( $prefix ); - } else { - static $id_counter = 0; - return $prefix . (string) ++$id_counter; - } - } -} diff --git a/src/BlockUniqidTransformer.php b/src/BlockUniqidTransformer.php new file mode 100644 index 00000000000..0091694d070 --- /dev/null +++ b/src/BlockUniqidTransformer.php @@ -0,0 +1,74 @@ +=' ) + ) + ); + } + + /** + * Register the service with the system. + * + * @return void + */ + public function register() { + add_filter( + 'amp_content_sanitizers', + static function ( $sanitizers ) { + $sanitizers = array_merge( + [ AMP_Block_Uniqid_Sanitizer::class => [] ], + $sanitizers + ); + return $sanitizers; + } + ); + } +} diff --git a/tests/php/src/BlockUniqidClassNameTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php similarity index 79% rename from tests/php/src/BlockUniqidClassNameTransformerTest.php rename to tests/php/src/BlockUniqidTransformerTest.php index 8c748460d18..6f9c795fbb9 100644 --- a/tests/php/src/BlockUniqidClassNameTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -2,22 +2,22 @@ namespace AmpProject\AmpWP\Tests; +use AMP_Block_Uniqid_Sanitizer; use AMP_Options_Manager; use AMP_Theme_Support; -use AmpProject\AmpWP\BlockUniqidClassNameTransformer; +use AmpProject\AmpWP\BlockUniqidTransformer; use AmpProject\AmpWP\Infrastructure\Conditional; -use AmpProject\AmpWP\Infrastructure\Delayed; use AmpProject\AmpWP\Infrastructure\Registerable; use AmpProject\AmpWP\Infrastructure\Service; use AmpProject\AmpWP\Option; use AmpProject\AmpWP\Tests\Helpers\MarkupComparison; -/** @coversDefaultClass \AmpProject\AmpWP\BlockUniqidClassNameTransformer */ -final class BlockUniqidClassNameTransformerTest extends TestCase { +/** @coversDefaultClass \AmpProject\AmpWP\BlockUniqidTransformer */ +final class BlockUniqidTransformerTest extends TestCase { use MarkupComparison; - /** @var BlockUniqidClassNameTransformer */ + /** @var BlockUniqidTransformer */ private $instance; /** @@ -28,21 +28,15 @@ final class BlockUniqidClassNameTransformerTest extends TestCase { public function setUp() { parent::setUp(); - $this->instance = new BlockUniqidClassNameTransformer(); + $this->instance = new BlockUniqidTransformer(); } public function test_it_can_be_initialized() { $this->assertInstanceOf( Conditional::class, $this->instance ); - $this->assertInstanceOf( Delayed::class, $this->instance ); $this->assertInstanceOf( Registerable::class, $this->instance ); $this->assertInstanceOf( Service::class, $this->instance ); } - /** @covers ::get_registration_action() */ - public function test_get_registration_action() { - $this->assertEquals( 'wp', BlockUniqidClassNameTransformer::get_registration_action() ); - } - /** * @covers ::is_needed() * @covers ::has_gutenberg_plugin() @@ -53,33 +47,36 @@ public function test_is_needed() { && version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) { - $this->assertFalse( BlockUniqidClassNameTransformer::is_needed() ); + $this->assertFalse( BlockUniqidTransformer::is_needed() ); } else { AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG ); $post_id = self::factory()->post->create(); $this->go_to( get_permalink( $post_id ) ); $this->assertFalse( amp_is_request() ); - $this->assertFalse( BlockUniqidClassNameTransformer::is_needed() ); + $this->assertFalse( BlockUniqidTransformer::is_needed() ); $this->go_to( amp_get_permalink( $post_id ) ); $this->assertTrue( amp_is_request() ); - $this->assertTrue( BlockUniqidClassNameTransformer::is_needed() ); + $this->assertTrue( BlockUniqidTransformer::is_needed() ); } } /** * @covers ::register() - * @covers ::get_block_inline_style_registration_hook_name() */ public function test_register() { - $this->instance->register(); - $this->assertEquals( PHP_INT_MAX, has_filter( 'render_block', [ $this->instance, 'transform_class_names_in_block_content' ] ) ); + remove_all_filters( 'amp_content_sanitizers' ); - $expected_hook_name = function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ? 'wp_enqueue_scripts' : 'wp_footer'; - $this->assertEquals( - defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX, // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound - has_filter( $expected_hook_name, [ $this->instance, 'transform_class_names_in_inline_styles' ] ) + $this->assertArrayNotHasKey( + AMP_Block_Uniqid_Sanitizer::class, + amp_get_content_sanitizers() + ); + + $this->instance->register(); + $this->assertArrayHasKey( + AMP_Block_Uniqid_Sanitizer::class, + amp_get_content_sanitizers() ); } @@ -132,6 +129,8 @@ public function get_block_data() { * @param string|null $expected_style_content */ public function test_transform_class_names( $block_content, $expected_block_content, $style_handle, $style_content, $expected_style_content ) { + $this->markTestIncomplete( 'Test neds to migrate over to tests for AMP_Block_Uniqid_Sanitizer.' ); + $transformed_block_content = $this->instance->transform_class_names_in_block_content( $block_content ); $this->assertEqualMarkup( $expected_block_content, $transformed_block_content ); From 9fb0510cc06e6295a83cd275b59be798f33a966c Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 15:07:26 +0100 Subject: [PATCH 08/21] Use transformer only with some versions of Gutenberg Co-authored-by: Weston Ruter --- src/BlockUniqidTransformer.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/BlockUniqidTransformer.php b/src/BlockUniqidTransformer.php index 0091694d070..f096d6b8eea 100644 --- a/src/BlockUniqidTransformer.php +++ b/src/BlockUniqidTransformer.php @@ -36,7 +36,13 @@ final class BlockUniqidTransformer implements Conditional, Service, Registerable * @return bool */ public static function has_gutenberg_plugin() { - return defined( 'GUTENBERG_VERSION' ); + return ( + defined( 'GUTENBERG_VERSION' ) + && + version_compare( GUTENBERG_VERSION, '10.7', '>=' ) + && + version_compare( GUTENBERG_VERSION, '12.7', '<' ) + ); } /** From 703e8532e4efe81e531a0fe33168a8c3f385cf09 Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 16:28:47 +0100 Subject: [PATCH 09/21] Transform uniqid in WordPress 5.8 through 6.0 --- src/BlockUniqidTransformer.php | 35 +++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/BlockUniqidTransformer.php b/src/BlockUniqidTransformer.php index f096d6b8eea..9706238b926 100644 --- a/src/BlockUniqidTransformer.php +++ b/src/BlockUniqidTransformer.php @@ -31,11 +31,16 @@ final class BlockUniqidTransformer implements Conditional, Service, Registerable { /** - * Check whether the Gutenberg plugin is present. + * Check whether the Gutenberg plugin is present and if its one of the affected versions. + * + * Elements was added in 10.7 via WordPress/gutenberg#31524 + * Layout was added in 11.2 via WordPress/gutenberg#33359 + * Duotone was added in 11.7 via WordPress/gutenberg#34667 + * `uniqid` has been replaced by `wp_unique_id` in 12.7 via WordPress/gutenberg#38891 * * @return bool */ - public static function has_gutenberg_plugin() { + public static function is_affected_gutenberg_version() { return ( defined( 'GUTENBERG_VERSION' ) && @@ -45,6 +50,24 @@ public static function has_gutenberg_plugin() { ); } + /** + * Check whether WordPress version is affected by the `uniqid` issue. + * + * The affected WordPress version is 5.9. However, the duotone filter was first + * introduced in WordPress 5.8 and it makes use of the `uniqid`, too. + * + * @todo Once the `uniqid` to `wp_unique_id` fix is backported to core, upper version boundary should be updated (it's set to 6.0 for now). + * + * @return bool + */ + public static function is_affected_wordpress_version() { + return ( + version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) + && + version_compare( get_bloginfo( 'version' ), '6.0', '<' ) + ); + } + /** * Check whether the conditional object is currently needed. * @@ -52,11 +75,9 @@ public static function has_gutenberg_plugin() { */ public static function is_needed() { return ( - ( - self::has_gutenberg_plugin() - || - version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) - ) + self::is_affected_gutenberg_version() + || + self::is_affected_wordpress_version() ); } From f68c8e2d1759a7dfd315f2b57bd0391a7f95f0d2 Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 16:32:36 +0100 Subject: [PATCH 10/21] Add support for legacy version of duotone prefix --- includes/sanitizers/class-amp-block-uniqid-sanitizer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php index 6b20ecdada5..0972f6534b0 100644 --- a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php +++ b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php @@ -28,6 +28,7 @@ class AMP_Block_Uniqid_Sanitizer extends AMP_Base_Sanitizer { const KEY_PREFIXES = [ 'wp-container-', 'wp-duotone-', + 'wp-duotone-filter-', 'wp-elements-', ]; @@ -115,7 +116,7 @@ function ( $matches ) { } $new_key = $this->key_mapping[ $old_key ]; - if ( 'wp-duotone-' === $matches['prefix'] ) { + if ( in_array( $matches['prefix'], [ 'wp-duotone-', 'wp-duotone-filter-' ], true ) ) { $this->transform_duotone_filter( $old_key, $new_key ); } From 76c14156ea1cc290645a698f081f9b57353e1a03 Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 17:06:58 +0100 Subject: [PATCH 11/21] Test for correct WP and Gutenberg versions --- tests/php/src/BlockUniqidTransformerTest.php | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 6f9c795fbb9..6064df95e21 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -39,16 +39,25 @@ public function test_it_can_be_initialized() { /** * @covers ::is_needed() - * @covers ::has_gutenberg_plugin() + * @covers ::is_affected_gutenberg_version() + * @covers ::is_affected_wordpress_version() */ public function test_is_needed() { if ( - ! defined( 'GUTENBERG_VERSION' ) - && - version_compare( get_bloginfo( 'version' ), '5.9', '<' ) + ( + defined( 'GUTENBERG_VERSION' ) + && + version_compare( GUTENBERG_VERSION, '10.7', '>=' ) + && + version_compare( GUTENBERG_VERSION, '12.7', '<' ) + ) + || + ( + version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) + && + version_compare( get_bloginfo( 'version' ), '6.0', '<' ) + ) ) { - $this->assertFalse( BlockUniqidTransformer::is_needed() ); - } else { AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG ); $post_id = self::factory()->post->create(); @@ -59,6 +68,8 @@ public function test_is_needed() { $this->go_to( amp_get_permalink( $post_id ) ); $this->assertTrue( amp_is_request() ); $this->assertTrue( BlockUniqidTransformer::is_needed() ); + } else { + $this->assertFalse( BlockUniqidTransformer::is_needed() ); } } From af994b18b06d6371fc8cb5a6fcf6bb5eda000168 Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 17:17:00 +0100 Subject: [PATCH 12/21] Do not check if test is dealing with AMP request --- tests/php/src/BlockUniqidTransformerTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 6064df95e21..915a86fc298 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -58,15 +58,6 @@ public function test_is_needed() { version_compare( get_bloginfo( 'version' ), '6.0', '<' ) ) ) { - AMP_Options_Manager::update_option( Option::THEME_SUPPORT, AMP_Theme_Support::READER_MODE_SLUG ); - $post_id = self::factory()->post->create(); - - $this->go_to( get_permalink( $post_id ) ); - $this->assertFalse( amp_is_request() ); - $this->assertFalse( BlockUniqidTransformer::is_needed() ); - - $this->go_to( amp_get_permalink( $post_id ) ); - $this->assertTrue( amp_is_request() ); $this->assertTrue( BlockUniqidTransformer::is_needed() ); } else { $this->assertFalse( BlockUniqidTransformer::is_needed() ); From 5c7cffe391e11628bce1ee1440d7d452d6c6ebfe Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Fri, 11 Mar 2022 17:38:35 +0100 Subject: [PATCH 13/21] Migrate block uniqid tests cases to `AMP_Block_Uniqid_Sanitizer` --- tests/php/src/BlockUniqidTransformerTest.php | 72 -------------- tests/php/test-amp-block-uniqid-sanitizer.php | 98 +++++++++++++++++++ 2 files changed, 98 insertions(+), 72 deletions(-) create mode 100644 tests/php/test-amp-block-uniqid-sanitizer.php diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 915a86fc298..82754a42783 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -81,76 +81,4 @@ public function test_register() { amp_get_content_sanitizers() ); } - - /** @return array */ - public function get_block_data() { - return [ - 'transform_duotone_class_name' => [ - 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'style_handle' => 'wp-duotone-621e12fb51e3a', - 'style_content' => ".wp-duotone-621e12fb51e3a > .wp-block-cover__image-background, .wp-duotone-621e12fb51e3a > .wp-block-cover__video-background{filter:url(\'#wp-duotone-621e12fb51e3a\') !important;}", - 'expected_style_content' => ".wp-duotone-1 > .wp-block-cover__image-background, .wp-duotone-1 > .wp-block-cover__video-background{filter:url(\'#wp-duotone-1\') !important;}", - ], - 'transform_container_class_name' => [ - 'block_content' => '
This is a super cool class name: wp-container-0123456789abc!
', - 'expected_block_content' => '
This is a super cool class name: wp-container-0123456789abc!
', - 'style_handle' => null, - 'style_content' => null, - 'expected_style_content' => null, - ], - 'ignore_class_names_without_hash' => [ - 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'style_handle' => null, - 'style_content' => null, - 'expected_style_content' => null, - ], - 'ignore_already_transformed_class_names' => [ - 'block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'expected_block_content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', - 'style_handle' => null, - 'style_content' => null, - 'expected_style_content' => null, - ], - ]; - } - - /** - * @covers ::transform_class_names_in_block_content() - * @covers ::transform_class_names_in_inline_styles() - * @covers ::get_class_name_regexp_pattern() - * @covers ::unique_id() - * - * @dataProvider get_block_data - * - * @param string $block_content - * @param string $expected_block_content - * @param string|null $style_handle - * @param string|null $style_content - * @param string|null $expected_style_content - */ - public function test_transform_class_names( $block_content, $expected_block_content, $style_handle, $style_content, $expected_style_content ) { - $this->markTestIncomplete( 'Test neds to migrate over to tests for AMP_Block_Uniqid_Sanitizer.' ); - - $transformed_block_content = $this->instance->transform_class_names_in_block_content( $block_content ); - $this->assertEqualMarkup( $expected_block_content, $transformed_block_content ); - - if ( ! empty( $style_handle ) && ! empty( $style_content ) && ! empty( $expected_style_content ) ) { - global $wp_styles; - - wp_register_style( $style_handle, '', [], '1' ); - wp_add_inline_style( $style_handle, $style_content ); - wp_enqueue_style( $style_handle ); - - $this->assertTrue( wp_style_is( $style_handle ) ); - - $this->instance->transform_class_names_in_inline_styles(); - - $this->assertEquals( - $expected_style_content, - $wp_styles->registered[ $style_handle ]->extra['after'][0] - ); - } - } } diff --git a/tests/php/test-amp-block-uniqid-sanitizer.php b/tests/php/test-amp-block-uniqid-sanitizer.php new file mode 100644 index 00000000000..47e294c7cd8 --- /dev/null +++ b/tests/php/test-amp-block-uniqid-sanitizer.php @@ -0,0 +1,98 @@ + [ + 'content' => + ' +
This is a super cool class name: wp-duotone-0123456789abc!
+ + + ', + 'expected' => + ' +
This is a super cool class name: wp-duotone-0123456789abc!
+ + + ', + ], + 'transform_legacy_duotone_class_name' => [ + 'content' => + ' +
This is a super cool class name: wp-duotone-filter-622b62d997e58!
+ + + ', + 'expected' => + ' +
This is a super cool class name: wp-duotone-filter-622b62d997e58!
+ + + ', + ], + 'transform_container_class_name' => [ + 'content' => '
This is a super cool class name: wp-container-0123456789abc!
', + 'expected' => '
This is a super cool class name: wp-container-0123456789abc!
', + ], + 'transform_elements_class_name' => [ + 'content' => + ' + + + ', + 'expected' => + ' + + + ', + ], + 'ignore_class_names_without_hash' => [ + 'content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + 'expected' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + ], + 'ignore_already_transformed_class_names' => [ + 'content' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + 'expected' => '
This is a super cool class name: wp-duotone-0123456789abc!
', + ], + ]; + } + + /** + * @covers ::transform_class_names_in_content() + * @covers ::transform_class_names_in_inline_styles() + * @covers ::get_class_name_regexp_pattern() + * @covers ::unique_id() + * + * @dataProvider get_block_data + * + * @param string $content + * @param string $expected + */ + public function test_sanitize( $content, $expected ) { + $dom = AMP_DOM_Utils::get_dom_from_content( $content ); + $sanitizer = new AMP_Block_Uniqid_Sanitizer( $dom ); + + $sanitizer->sanitize(); + $content = AMP_DOM_Utils::get_content_from_dom( $dom ); + + $this->assertEqualMarkup( $expected, $content ); + } +} From 605a7461f24951cc375b5c7f6f89e271c1477c59 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 16 Mar 2022 20:56:13 -0700 Subject: [PATCH 14/21] Extend MonitorCssTransientCaching with BlockUniqidTransformer awareness --- .../class-amp-block-uniqid-sanitizer.php | 1 + .../MonitorCssTransientCaching.php | 68 +++++++++++++++++-- src/BlockUniqidTransformer.php | 49 ++++++++----- .../MonitorCssTransientCachingTest.php | 20 ++++-- tests/php/src/BlockUniqidTransformerTest.php | 44 ++++++++---- 5 files changed, 142 insertions(+), 40 deletions(-) diff --git a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php index 0972f6534b0..952d0473dfa 100644 --- a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php +++ b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php @@ -14,6 +14,7 @@ * Work around use of uniqid() in blocks which breaks parsed CSS caching. * * @link https://github.com/ampproject/amp-wp/issues/6925 + * @link https://github.com/WordPress/gutenberg/issues/38889 * * @since 2.2.2 * @internal diff --git a/src/BackgroundTask/MonitorCssTransientCaching.php b/src/BackgroundTask/MonitorCssTransientCaching.php index a2965aefe84..859a9472585 100644 --- a/src/BackgroundTask/MonitorCssTransientCaching.php +++ b/src/BackgroundTask/MonitorCssTransientCaching.php @@ -8,6 +8,7 @@ namespace AmpProject\AmpWP\BackgroundTask; use AMP_Options_Manager; +use AmpProject\AmpWP\BlockUniqidTransformer; use AmpProject\AmpWP\Option; use DateTimeImmutable; use DateTimeInterface; @@ -54,6 +55,32 @@ final class MonitorCssTransientCaching extends RecurringBackgroundTask { */ const DEFAULT_SAMPLING_RANGE = 14; + /** + * @string + */ + const WP_VERSION = 'wp_version'; + + /** + * @string + */ + const GUTENBERG_VERSION = 'gutenberg_version'; + + /** + * @var BlockUniqidTransformer + */ + private $block_uniqid_transformer; + + /** + * Constructor. + * + * @param BackgroundTaskDeactivator $background_task_deactivator Deactivator. + * @param BlockUniqidTransformer $block_uniqid_transformer Transformer. + */ + public function __construct( BackgroundTaskDeactivator $background_task_deactivator, BlockUniqidTransformer $block_uniqid_transformer ) { + parent::__construct( $background_task_deactivator ); + $this->block_uniqid_transformer = $block_uniqid_transformer; + } + /** * Register the service with the system. * @@ -134,14 +161,20 @@ public function process( ...$args ) { * @return bool Whether transient caching of stylesheets is disabled. */ private function is_css_transient_caching_disabled() { - return AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); + return (bool) AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); } /** * Disable transient caching of stylesheets. */ private function disable_css_transient_caching() { - AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); + AMP_Options_Manager::update_option( + Option::DISABLE_CSS_TRANSIENT_CACHING, + [ + self::WP_VERSION => get_bloginfo( 'version' ), + self::GUTENBERG_VERSION => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null, + ] + ); } /** @@ -164,8 +197,35 @@ public function query_css_transient_count() { * @param string $old_version Old version. */ public function handle_plugin_update( $old_version ) { - // Reset the disabling of the CSS caching subsystem when updating from versions 1.5.0 or 1.5.1. - if ( version_compare( $old_version, '1.5.0', '>=' ) && version_compare( $old_version, '1.5.2', '<' ) ) { + $disabled = AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); + if ( empty( $disabled ) ) { + return; + } + + // Obtain the version of WordPress and Gutenberg at which time the functionality was disabled, if available. + $wp_version = isset( $disabled[ self::WP_VERSION ] ) ? $disabled[ self::WP_VERSION ] : null; + $gutenberg_version = isset( $disabled[ self::GUTENBERG_VERSION ] ) ? $disabled[ self::GUTENBERG_VERSION ] : null; + + if ( + // Reset the disabling of the CSS caching subsystem when updating from versions 1.5.0 or 1.5.1. + ( + version_compare( $old_version, '1.5.0', '>=' ) + && + version_compare( $old_version, '1.5.2', '<' ) + ) + || + // Reset when it was disabled prior to the versions of WP/Gutenberg being captured, + // or if the captured versions were affected at the time of disabling. + ( + empty( $gutenberg_version ) + || + $this->block_uniqid_transformer->is_affected_gutenberg_version( $gutenberg_version ) + || + empty( $wp_version ) + || + $this->block_uniqid_transformer->is_affected_wordpress_version( $wp_version ) + ) + ) { AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); } } diff --git a/src/BlockUniqidTransformer.php b/src/BlockUniqidTransformer.php index 9706238b926..c32ee05e4b1 100644 --- a/src/BlockUniqidTransformer.php +++ b/src/BlockUniqidTransformer.php @@ -7,10 +7,9 @@ namespace AmpProject\AmpWP; -use AmpProject\AmpWP\Infrastructure\Conditional; +use AMP_Block_Uniqid_Sanitizer; use AmpProject\AmpWP\Infrastructure\Registerable; use AmpProject\AmpWP\Infrastructure\Service; -use AMP_Block_Uniqid_Sanitizer; /** * Transform uniqid-based IDs into cacheable IDs based on wp_unique_id. @@ -23,12 +22,13 @@ * are more predictable and the CSS transient caching works as expected. * * @link https://github.com/ampproject/amp-wp/pull/6925 + * @link https://github.com/WordPress/gutenberg/issues/38889 * * @package AmpProject\AmpWP * @since 2.2.2 * @internal */ -final class BlockUniqidTransformer implements Conditional, Service, Registerable { +final class BlockUniqidTransformer implements Service, Registerable { /** * Check whether the Gutenberg plugin is present and if its one of the affected versions. @@ -38,15 +38,22 @@ final class BlockUniqidTransformer implements Conditional, Service, Registerable * Duotone was added in 11.7 via WordPress/gutenberg#34667 * `uniqid` has been replaced by `wp_unique_id` in 12.7 via WordPress/gutenberg#38891 * - * @return bool + * @param string|null $version Gutenberg version to check. If null, current version is used. + * @return bool Whether affected Gutenberg version. */ - public static function is_affected_gutenberg_version() { + public function is_affected_gutenberg_version( $version = null ) { + if ( empty( $version ) && defined( 'GUTENBERG_VERSION' ) ) { + $version = GUTENBERG_VERSION; + } + + if ( empty( $version ) ) { + return false; + } + return ( - defined( 'GUTENBERG_VERSION' ) - && - version_compare( GUTENBERG_VERSION, '10.7', '>=' ) + version_compare( $version, '10.7', '>=' ) && - version_compare( GUTENBERG_VERSION, '12.7', '<' ) + version_compare( $version, '12.7', '<' ) ); } @@ -58,26 +65,30 @@ public static function is_affected_gutenberg_version() { * * @todo Once the `uniqid` to `wp_unique_id` fix is backported to core, upper version boundary should be updated (it's set to 6.0 for now). * - * @return bool + * @param string|null $version WordPress core version to check. If null, current version is used. + * @return bool Whether affected WP version. */ - public static function is_affected_wordpress_version() { + public function is_affected_wordpress_version( $version = null ) { + if ( empty( $version ) ) { + $version = get_bloginfo( 'version' ); + } return ( - version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) + version_compare( $version, '5.8', '>=' ) && - version_compare( get_bloginfo( 'version' ), '6.0', '<' ) + version_compare( $version, '6.0', '<' ) ); } /** - * Check whether the conditional object is currently needed. + * Check whether the transformer is needed. * * @return bool Whether the conditional object is needed. */ - public static function is_needed() { + public function is_needed() { return ( - self::is_affected_gutenberg_version() + $this->is_affected_gutenberg_version() || - self::is_affected_wordpress_version() + $this->is_affected_wordpress_version() ); } @@ -87,6 +98,10 @@ public static function is_needed() { * @return void */ public function register() { + if ( ! $this->is_needed() ) { + return; + } + add_filter( 'amp_content_sanitizers', static function ( $sanitizers ) { diff --git a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php index 98e3928447f..73454be6ed2 100644 --- a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php +++ b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php @@ -6,14 +6,13 @@ namespace AmpProject\AmpWP\Tests\BackgroundTask; use AMP_Options_Manager; -use AmpProject\AmpWP\BackgroundTask\BackgroundTaskDeactivator; use AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching; use AmpProject\AmpWP\Option; -use AmpProject\AmpWP\Tests\TestCase; +use AmpProject\AmpWP\Tests\DependencyInjectedTestCase; use DateTime; /** @coversDefaultClass \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching */ -class MonitorCssTransientCachingTest extends TestCase { +class MonitorCssTransientCachingTest extends DependencyInjectedTestCase { /** * Whether external object cache is being used. @@ -51,7 +50,7 @@ public function test_event_gets_scheduled_and_unscheduled() { wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); $this->assertFalse( wp_next_scheduled( MonitorCssTransientCaching::EVENT_NAME ) ); - $monitor = new MonitorCssTransientCaching( new BackgroundTaskDeactivator() ); + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); $monitor->schedule_event(); $timestamp = wp_next_scheduled( MonitorCssTransientCaching::EVENT_NAME ); @@ -69,7 +68,7 @@ public function test_event_gets_scheduled_and_unscheduled() { public function test_event_can_be_processed() { delete_option( MonitorCssTransientCaching::TIME_SERIES_OPTION_KEY ); - $monitor = new MonitorCssTransientCaching( new BackgroundTaskDeactivator() ); + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); $monitor->process(); $this->assertNotFalse( get_option( MonitorCssTransientCaching::TIME_SERIES_OPTION_KEY ) ); @@ -97,7 +96,7 @@ static function () { } ); - $monitor = new MonitorCssTransientCaching( new BackgroundTaskDeactivator() ); + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); // Moving average should be 0. $monitor->process( new DateTime( '2000-01-01' ), 5 ); @@ -137,6 +136,13 @@ static function () { ], get_option( MonitorCssTransientCaching::TIME_SERIES_OPTION_KEY ) ); - $this->assertTrue( AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING ) ); + + $expected = [ + MonitorCssTransientCaching::WP_VERSION => get_bloginfo( 'version' ), + MonitorCssTransientCaching::GUTENBERG_VERSION => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null, + ]; + + $this->assertTrue( (bool) AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING ) ); + $this->assertEquals( $expected, AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING ) ); } } diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 82754a42783..286299a13f9 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -3,17 +3,13 @@ namespace AmpProject\AmpWP\Tests; use AMP_Block_Uniqid_Sanitizer; -use AMP_Options_Manager; -use AMP_Theme_Support; use AmpProject\AmpWP\BlockUniqidTransformer; -use AmpProject\AmpWP\Infrastructure\Conditional; use AmpProject\AmpWP\Infrastructure\Registerable; use AmpProject\AmpWP\Infrastructure\Service; -use AmpProject\AmpWP\Option; use AmpProject\AmpWP\Tests\Helpers\MarkupComparison; /** @coversDefaultClass \AmpProject\AmpWP\BlockUniqidTransformer */ -final class BlockUniqidTransformerTest extends TestCase { +final class BlockUniqidTransformerTest extends DependencyInjectedTestCase { use MarkupComparison; @@ -32,7 +28,6 @@ public function setUp() { } public function test_it_can_be_initialized() { - $this->assertInstanceOf( Conditional::class, $this->instance ); $this->assertInstanceOf( Registerable::class, $this->instance ); $this->assertInstanceOf( Service::class, $this->instance ); } @@ -43,6 +38,8 @@ public function test_it_can_be_initialized() { * @covers ::is_affected_wordpress_version() */ public function test_is_needed() { + $instance = $this->injector->make( BlockUniqidTransformer::class ); + if ( ( defined( 'GUTENBERG_VERSION' ) @@ -58,12 +55,26 @@ public function test_is_needed() { version_compare( get_bloginfo( 'version' ), '6.0', '<' ) ) ) { - $this->assertTrue( BlockUniqidTransformer::is_needed() ); + $this->assertTrue( $instance->is_needed() ); } else { - $this->assertFalse( BlockUniqidTransformer::is_needed() ); + $this->assertFalse( $instance->is_needed() ); } } + /** + * @covers ::is_affected_gutenberg_version() + */ + public function test_is_affected_gutenberg_version() { + $this->markTestIncomplete(); + } + + /** + * @covers ::is_affected_wordpress_version() + */ + public function test_is_affected_wordpress_version() { + $this->markTestIncomplete(); + } + /** * @covers ::register() */ @@ -75,10 +86,19 @@ public function test_register() { amp_get_content_sanitizers() ); + // @todo This needs to force the WP version. $this->instance->register(); - $this->assertArrayHasKey( - AMP_Block_Uniqid_Sanitizer::class, - amp_get_content_sanitizers() - ); + + if ( $this->instance->is_needed() ) { + $this->assertArrayHasKey( + AMP_Block_Uniqid_Sanitizer::class, + amp_get_content_sanitizers() + ); + } else { + $this->assertArrayNotHasKey( + AMP_Block_Uniqid_Sanitizer::class, + amp_get_content_sanitizers() + ); + } } } From 4a6846a3a4b15a192490c0e1a3866c691565e7fb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 15:35:38 -0700 Subject: [PATCH 15/21] Ignore wp_unique_id() polyfill code for coverage --- includes/sanitizers/class-amp-block-uniqid-sanitizer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php index 952d0473dfa..9ce4692fbff 100644 --- a/includes/sanitizers/class-amp-block-uniqid-sanitizer.php +++ b/includes/sanitizers/class-amp-block-uniqid-sanitizer.php @@ -195,8 +195,10 @@ private static function unique_id( $prefix = '' ) { if ( function_exists( 'wp_unique_id' ) ) { return wp_unique_id( $prefix ); } else { + // @codeCoverageIgnoreStart static $id_counter = 0; return $prefix . (string) ++$id_counter; + // @codeCoverageIgnoreEnd } } } From a8e8f84a9da2620280a079c9b545082810796413 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 17:38:51 -0700 Subject: [PATCH 16/21] Add tests for MonitorCssTransientCaching and BlockUniqidTransformer --- .../MonitorCssTransientCaching.php | 14 +- src/BlockUniqidTransformer.php | 26 ++- .../MonitorCssTransientCachingTest.php | 181 +++++++++++++++++- tests/php/src/BlockUniqidTransformerTest.php | 135 ++++++++----- 4 files changed, 302 insertions(+), 54 deletions(-) diff --git a/src/BackgroundTask/MonitorCssTransientCaching.php b/src/BackgroundTask/MonitorCssTransientCaching.php index 859a9472585..8a9b1aa2903 100644 --- a/src/BackgroundTask/MonitorCssTransientCaching.php +++ b/src/BackgroundTask/MonitorCssTransientCaching.php @@ -160,14 +160,21 @@ public function process( ...$args ) { * * @return bool Whether transient caching of stylesheets is disabled. */ - private function is_css_transient_caching_disabled() { + public function is_css_transient_caching_disabled() { return (bool) AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); } + /** + * Enable transient caching of stylesheets. + */ + public function enable_css_transient_caching() { + AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); + } + /** * Disable transient caching of stylesheets. */ - private function disable_css_transient_caching() { + public function disable_css_transient_caching() { AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, [ @@ -197,6 +204,7 @@ public function query_css_transient_count() { * @param string $old_version Old version. */ public function handle_plugin_update( $old_version ) { + // Note: We cannot use the is_css_transient_caching_disabled method because we need to get the underlying stored value. $disabled = AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); if ( empty( $disabled ) ) { return; @@ -226,7 +234,7 @@ public function handle_plugin_update( $old_version ) { $this->block_uniqid_transformer->is_affected_wordpress_version( $wp_version ) ) ) { - AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); + $this->enable_css_transient_caching(); } } diff --git a/src/BlockUniqidTransformer.php b/src/BlockUniqidTransformer.php index c32ee05e4b1..c547eed696a 100644 --- a/src/BlockUniqidTransformer.php +++ b/src/BlockUniqidTransformer.php @@ -30,6 +30,22 @@ */ final class BlockUniqidTransformer implements Service, Registerable { + /** + * Gutenberg version. + * + * @var string + */ + private $gutenberg_version = null; + + /** + * Construct. + */ + public function __construct() { + if ( defined( 'GUTENBERG_VERSION' ) ) { + $this->gutenberg_version = GUTENBERG_VERSION; + } + } + /** * Check whether the Gutenberg plugin is present and if its one of the affected versions. * @@ -42,8 +58,8 @@ final class BlockUniqidTransformer implements Service, Registerable { * @return bool Whether affected Gutenberg version. */ public function is_affected_gutenberg_version( $version = null ) { - if ( empty( $version ) && defined( 'GUTENBERG_VERSION' ) ) { - $version = GUTENBERG_VERSION; + if ( empty( $version ) ) { + $version = $this->gutenberg_version; } if ( empty( $version ) ) { @@ -80,11 +96,11 @@ public function is_affected_wordpress_version( $version = null ) { } /** - * Check whether the transformer is needed. + * Check whether the transformer is necessary. * * @return bool Whether the conditional object is needed. */ - public function is_needed() { + public function is_necessary() { return ( $this->is_affected_gutenberg_version() || @@ -98,7 +114,7 @@ public function is_needed() { * @return void */ public function register() { - if ( ! $this->is_needed() ) { + if ( ! $this->is_necessary() ) { return; } diff --git a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php index 73454be6ed2..92f9cf557bb 100644 --- a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php +++ b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php @@ -6,14 +6,19 @@ namespace AmpProject\AmpWP\Tests\BackgroundTask; use AMP_Options_Manager; +use AMP_Style_Sanitizer; use AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching; use AmpProject\AmpWP\Option; use AmpProject\AmpWP\Tests\DependencyInjectedTestCase; +use AmpProject\AmpWP\Tests\Helpers\PrivateAccess; +use AmpProject\Dom\Document; use DateTime; /** @coversDefaultClass \AmpProject\AmpWP\BackgroundTask\MonitorCssTransientCaching */ class MonitorCssTransientCachingTest extends DependencyInjectedTestCase { + use PrivateAccess; + /** * Whether external object cache is being used. * @@ -40,6 +45,31 @@ public function tearDown() { wp_using_ext_object_cache( $this->was_wp_using_ext_object_cache ); } + /** + * @covers ::register() + */ + public function test_register() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $monitor->register(); + $this->assertEquals( 10, has_action( 'amp_plugin_update', [ $monitor, 'handle_plugin_update' ] ) ); + } + + /** + * @covers ::get_interval() + */ + public function test_get_interval() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $this->assertIsString( $this->call_private_method( $monitor, 'get_interval' ) ); + } + + /** + * @covers ::get_event_name() + */ + public function test_get_event_name() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $this->assertIsString( $this->call_private_method( $monitor, 'get_event_name' ) ); + } + /** * Test whether an event is actually scheduled when the monitor is registered. * @@ -65,7 +95,7 @@ public function test_event_gets_scheduled_and_unscheduled() { * * @covers ::process() */ - public function test_event_can_be_processed() { + public function test_process_causes_time_series_to_be_stored() { delete_option( MonitorCssTransientCaching::TIME_SERIES_OPTION_KEY ); $monitor = $this->injector->make( MonitorCssTransientCaching::class ); @@ -78,8 +108,14 @@ public function test_event_can_be_processed() { * Test whether transient caching is disabled once it hits the threshold. * * @covers ::process() + * @covers ::get_time_series() + * @covers ::get_sampling_range() + * @covers ::persist_time_series() + * @covers ::calculate_average() + * @covers ::get_threshold() + * @covers ::disable_css_transient_caching() */ - public function test_transient_caching_is_disabled() { + public function test_process_disables_transient_caching_once_threshold_is_reached() { delete_option( MonitorCssTransientCaching::TIME_SERIES_OPTION_KEY ); AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, false ); @@ -145,4 +181,145 @@ static function () { $this->assertTrue( (bool) AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING ) ); $this->assertEquals( $expected, AMP_Options_Manager::get_option( Option::DISABLE_CSS_TRANSIENT_CACHING ) ); } + + /** + * @covers ::enable_css_transient_caching() + * @covers ::disable_css_transient_caching() + * @covers ::is_css_transient_caching_disabled() + */ + public function test_enable_disable_is_css_transient_caching_disabled() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + $monitor->disable_css_transient_caching(); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->enable_css_transient_caching(); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + } + + /** + * @covers ::query_css_transient_count() + */ + public function test_query_css_transient_count() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + + $this->assertEquals( 0, $monitor->query_css_transient_count() ); + + $dom = new Document(); + $dom->loadHTML( + ' + + + + + ' + ); + $style_sanitizer = new AMP_Style_Sanitizer( + $dom, + [ 'use_document_element' => true ] + ); + $style_sanitizer->sanitize(); + + $this->assertEquals( 2, $monitor->query_css_transient_count() ); + + $dom = new Document(); + $dom->loadHTML( + ' + + + + + ' + ); + $style_sanitizer = new AMP_Style_Sanitizer( + $dom, + [ 'use_document_element' => true ] + ); + $style_sanitizer->sanitize(); + + $this->assertEquals( 3, $monitor->query_css_transient_count() ); + } + + /** + * @covers ::handle_plugin_update() + */ + public function test_handle_plugin_update() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + + // Short-circuit condition. + $monitor->enable_css_transient_caching(); + $monitor->handle_plugin_update( '2.2.1' ); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + + // First condition when in range. + AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '1.5.1' ); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + + // First condition when not in range. + AMP_Options_Manager::update_option( + Option::DISABLE_CSS_TRANSIENT_CACHING, + [ + MonitorCssTransientCaching::WP_VERSION => '999.9', + MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', + ] + ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '1.5.2' ); // Should no-op. + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + + // Second condition before storing meta. + AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '2.2.1' ); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + + // Second condition after storing meta. + AMP_Options_Manager::update_option( + Option::DISABLE_CSS_TRANSIENT_CACHING, + [ + MonitorCssTransientCaching::WP_VERSION => '999.0', + MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', + ] + ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '2.2.2' ); // Should no-op. + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + } + + /** + * @covers ::get_default_threshold() + */ + public function test_get_default_threshold() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $this->assertIsFloat( $monitor->get_default_threshold() ); + } + + /** + * @covers ::get_default_sampling_range() + */ + public function test_get_default_sampling_range() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + $this->assertIsInt( $monitor->get_default_sampling_range() ); + } + + /** + * @covers ::get_time_series() + * @covers ::persist_time_series() + */ + public function test_get_and_persist_time_series() { + $monitor = $this->injector->make( MonitorCssTransientCaching::class ); + + $this->assertEquals( [], $this->call_private_method( $monitor, 'get_time_series' ) ); + + $time_series = [ + '20220101' => 10, + '20220102' => 20, + '20220103' => 30, + ]; + + $this->call_private_method( $monitor, 'persist_time_series', [ $time_series ] ); + + $this->assertEquals( $time_series, $this->call_private_method( $monitor, 'get_time_series' ) ); + } } diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 286299a13f9..7818126c4a8 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -7,78 +7,122 @@ use AmpProject\AmpWP\Infrastructure\Registerable; use AmpProject\AmpWP\Infrastructure\Service; use AmpProject\AmpWP\Tests\Helpers\MarkupComparison; +use AmpProject\AmpWP\Tests\Helpers\PrivateAccess; /** @coversDefaultClass \AmpProject\AmpWP\BlockUniqidTransformer */ final class BlockUniqidTransformerTest extends DependencyInjectedTestCase { use MarkupComparison; - - /** @var BlockUniqidTransformer */ - private $instance; - - /** - * Setup. - * - * @inheritdoc - */ - public function setUp() { - parent::setUp(); - - $this->instance = new BlockUniqidTransformer(); - } + use PrivateAccess; public function test_it_can_be_initialized() { - $this->assertInstanceOf( Registerable::class, $this->instance ); - $this->assertInstanceOf( Service::class, $this->instance ); + $instance = $this->injector->make( BlockUniqidTransformer::class ); + $this->assertSame( + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null, + $this->get_private_property( $instance, 'gutenberg_version' ) + ); + $this->assertInstanceOf( Registerable::class, $instance ); + $this->assertInstanceOf( Service::class, $instance ); } /** - * @covers ::is_needed() + * @covers ::is_necessary() * @covers ::is_affected_gutenberg_version() * @covers ::is_affected_wordpress_version() */ - public function test_is_needed() { + public function test_is_necessary() { $instance = $this->injector->make( BlockUniqidTransformer::class ); - if ( - ( - defined( 'GUTENBERG_VERSION' ) - && - version_compare( GUTENBERG_VERSION, '10.7', '>=' ) - && - version_compare( GUTENBERG_VERSION, '12.7', '<' ) - ) - || - ( - version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) - && - version_compare( get_bloginfo( 'version' ), '6.0', '<' ) - ) - ) { - $this->assertTrue( $instance->is_needed() ); - } else { - $this->assertFalse( $instance->is_needed() ); + $gutenberg_data = $this->get_data_to_test_is_affected_gutenberg_version(); + $wp_data = $this->get_data_to_test_is_affected_wordpress_version(); + + foreach ( $wp_data as list( $wp_version, $wp_expected ) ) { + $GLOBALS['wp_version'] = $wp_version; + foreach ( $gutenberg_data as list( $gb_version, $gb_expected ) ) { + $this->set_private_property( $instance, 'gutenberg_version', $gb_version ); + + $this->assertSame( + $wp_expected || $gb_expected, + $instance->is_necessary(), + "Unexpected for WP $wp_version and Gutenberg $gb_version" + ); + } } } + /** @return array */ + public function get_data_to_test_is_affected_gutenberg_version() { + return [ + 'none' => [ null, false ], + '10.6' => [ '10.6', false ], + '10.7' => [ '10.7', true ], + '11.0' => [ '11.0', true ], + '12.0' => [ '12.0', true ], + '12.6' => [ '12.6', true ], + '12.7' => [ '12.7', false ], + '13.0' => [ '13.0', false ], + ]; + } + /** * @covers ::is_affected_gutenberg_version() + * @dataProvider get_data_to_test_is_affected_gutenberg_version + * @param string $gutenberg_version Gutenberg version. + * @param bool $is_affected Is affected. */ - public function test_is_affected_gutenberg_version() { - $this->markTestIncomplete(); + public function test_is_affected_gutenberg_version( $gutenberg_version, $is_affected ) { + $instance = $this->injector->make( BlockUniqidTransformer::class ); + + // If Gutenberg is active for the tests, ignore the GUTENBERG_VERSION constant. + $this->set_private_property( $instance, 'gutenberg_version', null ); + + $this->assertSame( $is_affected, $instance->is_affected_gutenberg_version( $gutenberg_version ) ); + } + + /** @return array */ + public function get_data_to_test_is_affected_wordpress_version() { + return [ + '5.7.0' => [ '5.7.0', false ], + '5.8.0' => [ '5.8.0', true ], + '5.9.0' => [ '5.9.0', true ], + '5.9.1' => [ '5.9.1', true ], + '5.9.2' => [ '5.9.2', true ], + '6.0.0' => [ '6.0.0', false ], + ]; } /** * @covers ::is_affected_wordpress_version() + * @dataProvider get_data_to_test_is_affected_wordpress_version + * @param string $wp_version WP version. + * @param bool $is_affected Is affected. */ - public function test_is_affected_wordpress_version() { - $this->markTestIncomplete(); + public function test_is_affected_wordpress_version( $wp_version, $is_affected ) { + $instance = $this->injector->make( BlockUniqidTransformer::class ); + $this->assertSame( $is_affected, $instance->is_affected_wordpress_version( $wp_version ) ); + } + + /** @return array */ + public function get_data_to_test_register() { + return [ + 'necessary' => [ + '5.9.0', + true, + ], + 'unnecessary' => [ + '6.0.0', + false, + ], + ]; } /** * @covers ::register() + * @dataProvider get_data_to_test_register + * @param string $wp_version WP version. + * @param bool $expected Expected. */ - public function test_register() { + public function test_register( $wp_version, $expected ) { remove_all_filters( 'amp_content_sanitizers' ); $this->assertArrayNotHasKey( @@ -86,10 +130,13 @@ public function test_register() { amp_get_content_sanitizers() ); - // @todo This needs to force the WP version. - $this->instance->register(); + $instance = $this->injector->make( BlockUniqidTransformer::class ); + $this->set_private_property( $instance, 'gutenberg_version', null ); + $GLOBALS['wp_version'] = $wp_version; - if ( $this->instance->is_needed() ) { + $instance->register(); + $this->assertSame( $expected, $instance->is_necessary() ); + if ( $expected ) { $this->assertArrayHasKey( AMP_Block_Uniqid_Sanitizer::class, amp_get_content_sanitizers() From f95a1af592740f69255281cba2d7117cc5433a33 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 17:43:58 -0700 Subject: [PATCH 17/21] Normalize to allow AMP_Block_Uniqid_Sanitizer tests to run in isolation --- tests/php/test-amp-block-uniqid-sanitizer.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/php/test-amp-block-uniqid-sanitizer.php b/tests/php/test-amp-block-uniqid-sanitizer.php index 47e294c7cd8..36ca776045c 100644 --- a/tests/php/test-amp-block-uniqid-sanitizer.php +++ b/tests/php/test-amp-block-uniqid-sanitizer.php @@ -43,14 +43,14 @@ public function get_block_data() { ', 'expected' => ' -
This is a super cool class name: wp-duotone-filter-622b62d997e58!
- - +
This is a super cool class name: wp-duotone-filter-622b62d997e58!
+ + ', ], 'transform_container_class_name' => [ 'content' => '
This is a super cool class name: wp-container-0123456789abc!
', - 'expected' => '
This is a super cool class name: wp-container-0123456789abc!
', + 'expected' => '
This is a super cool class name: wp-container-0123456789abc!
', ], 'transform_elements_class_name' => [ 'content' => @@ -60,8 +60,8 @@ public function get_block_data() { ', 'expected' => ' - - + + ', ], 'ignore_class_names_without_hash' => [ @@ -93,6 +93,9 @@ public function test_sanitize( $content, $expected ) { $sanitizer->sanitize(); $content = AMP_DOM_Utils::get_content_from_dom( $dom ); + // Normalize auto-incrementing ID to allow tests to be run in isolation. + $content = preg_replace( '/-\d+\b/', '-1', $content ); + $this->assertEqualMarkup( $expected, $content ); } } From 8a8ae96a46c5a91479b97e7836c922d7d203c8d6 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 17:45:30 -0700 Subject: [PATCH 18/21] Use proper coversDefaultClass phpdoc tag --- tests/php/test-amp-block-uniqid-sanitizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/php/test-amp-block-uniqid-sanitizer.php b/tests/php/test-amp-block-uniqid-sanitizer.php index 36ca776045c..c54e5a2ebc3 100644 --- a/tests/php/test-amp-block-uniqid-sanitizer.php +++ b/tests/php/test-amp-block-uniqid-sanitizer.php @@ -11,7 +11,7 @@ /** * Test AMP_Block_Uniqid_Sanitizer * - * @covers AMP_Block_Uniqid_Sanitizer + * @coversDefaultClass AMP_Block_Uniqid_Sanitizer */ class AMP_Block_Uniqid_Sanitizer_Test extends TestCase { From c27435c9581bd58b5924c0a31455a563a6d4d585 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 18:06:22 -0700 Subject: [PATCH 19/21] Restore wp_version global after tests --- .../MonitorCssTransientCachingTest.php | 5 +++++ tests/php/src/BlockUniqidTransformerTest.php | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php index 92f9cf557bb..9c58774a92e 100644 --- a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php +++ b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php @@ -26,6 +26,9 @@ class MonitorCssTransientCachingTest extends DependencyInjectedTestCase { */ private $was_wp_using_ext_object_cache; + /** @var string */ + private $original_wp_version; + /** * Set up the tests by clearing the list of scheduled events. */ @@ -34,6 +37,7 @@ public function setUp() { _set_cron_array( [] ); $this->was_wp_using_ext_object_cache = wp_using_ext_object_cache(); wp_using_ext_object_cache( false ); + $this->original_wp_version = $GLOBALS['wp_version']; } /** @@ -43,6 +47,7 @@ public function tearDown() { parent::tearDown(); _set_cron_array( [] ); wp_using_ext_object_cache( $this->was_wp_using_ext_object_cache ); + $GLOBALS['wp_version'] = $this->original_wp_version; } /** diff --git a/tests/php/src/BlockUniqidTransformerTest.php b/tests/php/src/BlockUniqidTransformerTest.php index 7818126c4a8..542ebfb533f 100644 --- a/tests/php/src/BlockUniqidTransformerTest.php +++ b/tests/php/src/BlockUniqidTransformerTest.php @@ -15,6 +15,25 @@ final class BlockUniqidTransformerTest extends DependencyInjectedTestCase { use MarkupComparison; use PrivateAccess; + /** @var string */ + private $original_wp_version; + + /** + * Set up. + */ + public function setUp() { + parent::setUp(); + $this->original_wp_version = $GLOBALS['wp_version']; + } + + /** + * Tear down. + */ + public function tearDown() { + $GLOBALS['wp_version'] = $this->original_wp_version; + parent::tearDown(); + } + public function test_it_can_be_initialized() { $instance = $this->injector->make( BlockUniqidTransformer::class ); $this->assertSame( From 35dda9d7204ecf8e94ec2bfb252edb91ea43430f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 20:15:55 -0700 Subject: [PATCH 20/21] Use data provider for test_handle_plugin_update --- .../MonitorCssTransientCachingTest.php | 107 +++++++++++------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php index 9c58774a92e..84c824b2b14 100644 --- a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php +++ b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php @@ -244,52 +244,75 @@ public function test_query_css_transient_count() { $this->assertEquals( 3, $monitor->query_css_transient_count() ); } + /** @return array */ + public function get_data_to_test_handle_plugin_update() { + return [ + 'not_disabled' => [ + function ( MonitorCssTransientCaching $monitor ) { + $monitor->enable_css_transient_caching(); + $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '2.2.1' ); + }, + false, + ], + 'old_reset_condition_in_range' => [ + function ( MonitorCssTransientCaching $monitor ) { + AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '1.5.1' ); + }, + false, + ], + 'old_reset_condition_outside_range' => [ + function ( MonitorCssTransientCaching $monitor ) { + AMP_Options_Manager::update_option( + Option::DISABLE_CSS_TRANSIENT_CACHING, + [ + MonitorCssTransientCaching::WP_VERSION => '999.9', + MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', + ] + ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '1.5.2' ); // Should no-op. + }, + true, + ], + 'uniqid_before_storing_meta' => [ + function ( MonitorCssTransientCaching $monitor ) { + AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '2.2.1' ); + }, + false, + ], + 'uniqid_after_storing_meta' => [ + function ( MonitorCssTransientCaching $monitor ) { + AMP_Options_Manager::update_option( + Option::DISABLE_CSS_TRANSIENT_CACHING, + [ + MonitorCssTransientCaching::WP_VERSION => '999.0', + MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', + ] + ); + $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $monitor->handle_plugin_update( '2.2.2' ); // Should no-op. + }, + true, + ], + ]; + } + /** + * @dataProvider get_data_to_test_handle_plugin_update * @covers ::handle_plugin_update() + * + * @param callable $set_up + * @param bool $expected_disabled */ - public function test_handle_plugin_update() { + public function test_handle_plugin_update( $set_up, $expected_disabled ) { $monitor = $this->injector->make( MonitorCssTransientCaching::class ); - - // Short-circuit condition. - $monitor->enable_css_transient_caching(); - $monitor->handle_plugin_update( '2.2.1' ); - $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); - - // First condition when in range. - AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); - $monitor->handle_plugin_update( '1.5.1' ); - $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); - - // First condition when not in range. - AMP_Options_Manager::update_option( - Option::DISABLE_CSS_TRANSIENT_CACHING, - [ - MonitorCssTransientCaching::WP_VERSION => '999.9', - MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', - ] - ); - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); - $monitor->handle_plugin_update( '1.5.2' ); // Should no-op. - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); - - // Second condition before storing meta. - AMP_Options_Manager::update_option( Option::DISABLE_CSS_TRANSIENT_CACHING, true ); - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); - $monitor->handle_plugin_update( '2.2.1' ); - $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); - - // Second condition after storing meta. - AMP_Options_Manager::update_option( - Option::DISABLE_CSS_TRANSIENT_CACHING, - [ - MonitorCssTransientCaching::WP_VERSION => '999.0', - MonitorCssTransientCaching::GUTENBERG_VERSION => '999.9', - ] - ); - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); - $monitor->handle_plugin_update( '2.2.2' ); // Should no-op. - $this->assertTrue( $monitor->is_css_transient_caching_disabled() ); + $set_up( $monitor ); + $this->assertSame( $expected_disabled, $monitor->is_css_transient_caching_disabled() ); } /** From d87b78b1b8d3c096fc27eb53ca2f33fceb733cc3 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 17 Mar 2022 21:18:32 -0700 Subject: [PATCH 21/21] Ensure CSS transients disabling is not reset with each version update --- .../options/class-amp-options-manager.php | 6 -- .../MonitorCssTransientCaching.php | 49 +++++++-- .../MonitorCssTransientCachingTest.php | 100 +++++++++++++++++- 3 files changed, 137 insertions(+), 18 deletions(-) diff --git a/includes/options/class-amp-options-manager.php b/includes/options/class-amp-options-manager.php index 857af7c4665..93ef7a5fca8 100644 --- a/includes/options/class-amp-options-manager.php +++ b/includes/options/class-amp-options-manager.php @@ -351,12 +351,6 @@ public static function validate_options( $new_options ) { } } - if ( array_key_exists( Option::DISABLE_CSS_TRANSIENT_CACHING, $new_options ) && true === $new_options[ Option::DISABLE_CSS_TRANSIENT_CACHING ] ) { - $options[ Option::DISABLE_CSS_TRANSIENT_CACHING ] = true; - } else { - unset( $options[ Option::DISABLE_CSS_TRANSIENT_CACHING ] ); - } - /** * Filter the options being updated, so services can handle the sanitization and validation of * their respective options. diff --git a/src/BackgroundTask/MonitorCssTransientCaching.php b/src/BackgroundTask/MonitorCssTransientCaching.php index 8a9b1aa2903..643a7658fca 100644 --- a/src/BackgroundTask/MonitorCssTransientCaching.php +++ b/src/BackgroundTask/MonitorCssTransientCaching.php @@ -88,6 +88,7 @@ public function __construct( BackgroundTaskDeactivator $background_task_deactiva */ public function register() { add_action( 'amp_plugin_update', [ $this, 'handle_plugin_update' ] ); + add_filter( 'amp_options_updating', [ $this, 'sanitize_disabled_option' ], 10, 2 ); parent::register(); } @@ -184,6 +185,38 @@ public function disable_css_transient_caching() { ); } + /** + * Sanitize the option. + * + * @param array $options Existing options. + * @param array $new_options New options. + * @return array Sanitized options. + */ + public function sanitize_disabled_option( $options, $new_options ) { + $value = null; + + if ( array_key_exists( Option::DISABLE_CSS_TRANSIENT_CACHING, $new_options ) ) { + $unsanitized_value = $new_options[ Option::DISABLE_CSS_TRANSIENT_CACHING ]; + + if ( is_bool( $unsanitized_value ) ) { + $value = (bool) $unsanitized_value; + } elseif ( is_array( $unsanitized_value ) ) { + $value = []; + foreach ( wp_array_slice_assoc( $unsanitized_value, [ self::WP_VERSION, self::GUTENBERG_VERSION ] ) as $key => $version ) { + $value[ $key ] = preg_replace( '/[^a-z0-9_\-.]/', '', $version ); + } + } + } + + if ( empty( $value ) ) { + unset( $options[ Option::DISABLE_CSS_TRANSIENT_CACHING ] ); + } else { + $options[ Option::DISABLE_CSS_TRANSIENT_CACHING ] = $value; + } + + return $options; + } + /** * Query the number of transients containing cache stylesheets. * @@ -225,13 +258,15 @@ public function handle_plugin_update( $old_version ) { // Reset when it was disabled prior to the versions of WP/Gutenberg being captured, // or if the captured versions were affected at the time of disabling. ( - empty( $gutenberg_version ) - || - $this->block_uniqid_transformer->is_affected_gutenberg_version( $gutenberg_version ) - || - empty( $wp_version ) - || - $this->block_uniqid_transformer->is_affected_wordpress_version( $wp_version ) + version_compare( strtok( $old_version, '-' ), '2.2.2', '<' ) + && + ( + ! is_array( $disabled ) + || + $this->block_uniqid_transformer->is_affected_gutenberg_version( $gutenberg_version ) + || + $this->block_uniqid_transformer->is_affected_wordpress_version( $wp_version ) + ) ) ) { $this->enable_css_transient_caching(); diff --git a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php index 84c824b2b14..64531a49ab2 100644 --- a/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php +++ b/tests/php/src/BackgroundTask/MonitorCssTransientCachingTest.php @@ -57,6 +57,7 @@ public function test_register() { $monitor = $this->injector->make( MonitorCssTransientCaching::class ); $monitor->register(); $this->assertEquals( 10, has_action( 'amp_plugin_update', [ $monitor, 'handle_plugin_update' ] ) ); + $this->assertEquals( 10, has_filter( 'amp_options_updating', [ $monitor, 'sanitize_disabled_option' ] ) ); } /** @@ -201,6 +202,81 @@ public function test_enable_disable_is_css_transient_caching_disabled() { $this->assertFalse( $monitor->is_css_transient_caching_disabled() ); } + /** @return array */ + public function get_data_to_test_sanitize_disabled_option() { + return [ + 'true' => [ + true, + true, + ], + 'false' => [ + false, + null, + ], + 'number' => [ + 123, + null, + ], + 'bad_array' => [ + [ 1, 2, 3 ], + null, + ], + 'partial_good_array' => [ + [ + MonitorCssTransientCaching::WP_VERSION => '123-\o/', + MonitorCssTransientCaching::GUTENBERG_VERSION => '45.21