From 5a81073c1e4cf9b73e94f149b6b530fe977fa578 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 5 Jun 2018 13:55:45 -0700 Subject: [PATCH 01/17] Prevent making AMP blocks available when AMP is not native --- includes/admin/class-amp-editor-blocks.php | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/includes/admin/class-amp-editor-blocks.php b/includes/admin/class-amp-editor-blocks.php index a93e396b169..6e947b236a7 100644 --- a/includes/admin/class-amp-editor-blocks.php +++ b/includes/admin/class-amp-editor-blocks.php @@ -109,27 +109,28 @@ public function whitelist_block_atts_in_wp_kses_allowed_html( $tags, $context ) */ public function enqueue_block_editor_assets() { - // Styles. - wp_enqueue_style( - 'amp-editor-blocks-style', - amp_get_asset_url( 'css/amp-editor-blocks.css' ), - array(), - AMP__VERSION - ); + // Enqueue script and style for AMP-specific blocks. + if ( amp_is_canonical() ) { + wp_enqueue_style( + 'amp-editor-blocks-style', + amp_get_asset_url( 'css/amp-editor-blocks.css' ), + array(), + AMP__VERSION + ); - // Scripts. - wp_enqueue_script( - 'amp-editor-blocks-build', - amp_get_asset_url( 'js/amp-blocks-compiled.js' ), - array( 'wp-blocks', 'lodash', 'wp-i18n', 'wp-element', 'wp-components' ), - AMP__VERSION - ); + wp_enqueue_script( + 'amp-editor-blocks-build', + amp_get_asset_url( 'js/amp-blocks-compiled.js' ), + array( 'wp-blocks', 'lodash', 'wp-i18n', 'wp-element', 'wp-components' ), + AMP__VERSION + ); - wp_add_inline_script( - 'amp-editor-blocks-build', - 'wp.i18n.setLocaleData( ' . wp_json_encode( gutenberg_get_jed_locale_data( 'amp' ) ) . ', "amp" );', - 'before' - ); + wp_add_inline_script( + 'amp-editor-blocks-build', + 'wp.i18n.setLocaleData( ' . wp_json_encode( gutenberg_get_jed_locale_data( 'amp' ) ) . ', "amp" );', + 'before' + ); + } wp_enqueue_script( 'amp-editor-blocks', From 92424fb72ad90ce75259b49c1a711b554b81de22 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 5 Jun 2018 17:15:00 -0700 Subject: [PATCH 02/17] Remove menu-toggle in Twenty Fifteen if there is nothing to open --- includes/sanitizers/class-amp-core-theme-sanitizer.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/includes/sanitizers/class-amp-core-theme-sanitizer.php b/includes/sanitizers/class-amp-core-theme-sanitizer.php index b07854bb1a7..d645054b1c1 100644 --- a/includes/sanitizers/class-amp-core-theme-sanitizer.php +++ b/includes/sanitizers/class-amp-core-theme-sanitizer.php @@ -810,12 +810,17 @@ public function add_nav_menu_toggle( $args = array() ) { $args ); - $nav_el = $this->dom->getElementById( $args['nav_container_id'] ); + $nav_el = $this->dom->getElementById( $args['nav_container_id'] ); + $button_el = $this->xpath->query( $args['menu_button_xpath'] )->item( 0 ); if ( ! $nav_el ) { + if ( $button_el ) { + + // Remove the button since it won't be used. + $button_el->parentNode->removeChild( $button_el ); + } return; } - $button_el = $this->xpath->query( $args['menu_button_xpath'] )->item( 0 ); if ( ! $button_el ) { return; } From 555c07993c08055ccdd3a73902a552d01fa5a65e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 5 Jun 2018 18:20:12 -0700 Subject: [PATCH 03/17] Add options for enabling amp theme support and configuring validation handling * Add admin settings for picking between disabled, paired, and native theme support. See #1196. * Add checkbox for automatically allowing tree shaking. * Add checkbox for automatically sanitizing all validation errors (including tree shaking). * Make explicitly clear that unaccepted validation errors will block rendering on AMP * Prevent serving dirty AMP by forcibly sanitizing all validation errors when amp_is_canonical(). See #1192. * When validation errors are automatically sanitized, ensure the terms' term_group is updated when re-checking. * Update AMP_Options_Manager::get_options() and to return default values. --- assets/js/amp-block-validation.js | 11 +- includes/admin/class-amp-editor-blocks.php | 12 +- includes/amp-frontend-actions.php | 1 + includes/class-amp-theme-support.php | 27 ++- .../options/class-amp-options-manager.php | 39 +++- includes/options/class-amp-options-menu.php | 185 ++++++++++++++++-- .../sanitizers/class-amp-style-sanitizer.php | 9 +- .../class-amp-invalid-url-post-type.php | 59 ++++-- .../class-amp-validation-error-taxonomy.php | 80 +++++--- .../class-amp-validation-manager.php | 28 ++- tests/test-class-amp-options-manager.php | 11 +- tests/test-class-amp-options-menu.php | 6 +- 12 files changed, 390 insertions(+), 78 deletions(-) diff --git a/assets/js/amp-block-validation.js b/assets/js/amp-block-validation.js index 26cd6bd916e..99e8cf09172 100644 --- a/assets/js/amp-block-validation.js +++ b/assets/js/amp-block-validation.js @@ -19,7 +19,8 @@ var ampBlockValidation = ( function() { // eslint-disable-line no-unused-vars */ data: { i18n: {}, - ampValidityRestField: '' + ampValidityRestField: '', + isCanonical: false }, /** @@ -179,7 +180,13 @@ var ampBlockValidation = ( function() { // eslint-disable-line no-unused-vars ); } - noticeMessage += ' ' + wp.i18n.__( 'Non-accepted validation errors prevent AMP from being served.', 'amp' ); + noticeMessage += ' '; + if ( module.data.isCanonical ) { + noticeMessage += wp.i18n.__( 'The invalid markup will be automatically sanitized to ensure a valid AMP response is served.', 'amp' ); + } else { + noticeMessage += wp.i18n.__( 'Non-accepted validation errors prevent AMP from being served, and the user will be redirected to the non-AMP version.', 'amp' ); + } + noticeElement = wp.element.createElement( 'p', {}, [ noticeMessage + ' ', ampValidity.review_link && wp.element.createElement( diff --git a/includes/admin/class-amp-editor-blocks.php b/includes/admin/class-amp-editor-blocks.php index 6e947b236a7..cf484fb0ac3 100644 --- a/includes/admin/class-amp-editor-blocks.php +++ b/includes/admin/class-amp-editor-blocks.php @@ -43,8 +43,16 @@ public function init() { if ( function_exists( 'gutenberg_init' ) ) { add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) ); add_filter( 'wp_kses_allowed_html', array( $this, 'whitelist_block_atts_in_wp_kses_allowed_html' ), 10, 2 ); - add_filter( 'the_content', array( $this, 'tally_content_requiring_amp_scripts' ) ); - add_action( 'wp_print_footer_scripts', array( $this, 'print_dirty_amp_scripts' ) ); + + /* + * Dirty AMP is currently disabled per . + * Note that when not in native/canonical mode, AMP-specific Gutenberg blocks will not be + * registered for use, and so users will not be likely attempting to serve AMP content in + * non-AMP responses. When/if dirty AMP is allowed, the following can be re-enabled: + * + * add_filter( 'the_content', array( $this, 'tally_content_requiring_amp_scripts' ) ); + * add_action( 'wp_print_footer_scripts', array( $this, 'print_dirty_amp_scripts' ) ); + */ } } diff --git a/includes/amp-frontend-actions.php b/includes/amp-frontend-actions.php index f5d7ebca13d..342cc8763cd 100644 --- a/includes/amp-frontend-actions.php +++ b/includes/amp-frontend-actions.php @@ -10,6 +10,7 @@ /** * Add amphtml link to frontend. * + * @todo If there is a known amp_invalid_url post for this $amp_url that has unaccepted validation errors then this should output nothing. * @todo This function's name is incorrect. It's not about adding a canonical link but adding the amphtml link. * * @since 0.2 diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 5097844e350..326f3a5acb6 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -96,6 +96,7 @@ class AMP_Theme_Support { * @since 0.7 */ public static function init() { + self::apply_options(); if ( ! current_theme_supports( 'amp' ) ) { return; } @@ -117,7 +118,7 @@ public static function init() { $args = array_shift( $support ); if ( ! is_array( $args ) ) { trigger_error( esc_html__( 'Expected AMP theme support arg to be array.', 'amp' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error - } elseif ( count( array_diff( array_keys( $args ), array( 'template_dir', 'available_callback', 'comments_live_list' ) ) ) !== 0 ) { + } elseif ( count( array_diff( array_keys( $args ), array( 'template_dir', 'available_callback', 'comments_live_list', '__added_via_option' ) ) ) !== 0 ) { trigger_error( esc_html__( 'Expected AMP theme support to only have template_dir and/or available_callback.', 'amp' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error } } @@ -132,6 +133,28 @@ public static function init() { add_action( 'wp', array( __CLASS__, 'finish_init' ), PHP_INT_MAX ); } + /** + * Apply options for whether theme support is enabled via admin and what sanitization is performed by default. + * + * @see AMP_Post_Type_Support::add_post_type_support() For where post type support is added, since it is irrespective of theme support. + */ + public static function apply_options() { + if ( ! current_theme_supports( 'amp' ) ) { + $theme_support_option = AMP_Options_Manager::get_option( 'theme_support' ); + if ( 'disabled' === $theme_support_option ) { + return; + } + + $args = array( + '__added_via_option' => true, + ); + if ( 'paired' === $theme_support_option ) { + $args['template_dir'] = './'; + } + add_theme_support( 'amp', $args ); + } + } + /** * Finish initialization once query vars are set. * @@ -1149,6 +1172,8 @@ public static function prepare_response( $response, $args = array() ) { self::ensure_required_markup( $dom ); if ( ! AMP_Validation_Manager::should_validate_response() && AMP_Validation_Manager::has_blocking_validation_errors() ) { + + // Note the canonical check will not currently ever be met because dirty AMP is not yet supported; all validation errors will forcibly be sanitized. if ( amp_is_canonical() ) { $dom->documentElement->removeAttribute( 'amp' ); diff --git a/includes/options/class-amp-options-manager.php b/includes/options/class-amp-options-manager.php index 4461a82f6d8..662521318ce 100644 --- a/includes/options/class-amp-options-manager.php +++ b/includes/options/class-amp-options-manager.php @@ -17,6 +17,19 @@ class AMP_Options_Manager { */ const OPTION_NAME = 'amp-options'; + /** + * Default option values. + * + * @var array + */ + protected static $defaults = array( + 'theme_support' => 'disabled', + 'supported_post_types' => array(), + 'analytics' => array(), + 'force_sanitization' => false, + 'accept_tree_shaking' => false, + ); + /** * Register settings. */ @@ -58,7 +71,11 @@ public static function maybe_flush_rewrite_rules( $old_options, $new_options ) { * @return array Options. */ public static function get_options() { - return get_option( self::OPTION_NAME, array() ); + $options = get_option( self::OPTION_NAME, array() ); + if ( empty( $options ) ) { + $options = array(); + } + return array_merge( self::$defaults, $options ); } /** @@ -86,15 +103,20 @@ public static function get_option( $option, $default = false ) { * @return array Options. */ public static function validate_options( $new_options ) { - $defaults = array( - 'supported_post_types' => array(), - 'analytics' => array(), - ); + $options = self::get_options(); - $options = array_merge( - $defaults, - self::get_options() + // Theme support. + $recognized_theme_supports = array( + 'disabled', + 'paired', + 'native', ); + if ( isset( $new_options['theme_support'] ) && in_array( $new_options['theme_support'], $recognized_theme_supports, true ) ) { + $options['theme_support'] = $new_options['theme_support']; + } + + $options['force_sanitization'] = ! empty( $new_options['force_sanitization'] ); + $options['accept_tree_shaking'] = ! empty( $new_options['accept_tree_shaking'] ); // Validate post type support. if ( isset( $new_options['supported_post_types'] ) ) { @@ -156,7 +178,6 @@ public static function validate_options( $new_options ) { return $options; } - /** * Check for errors with updating the supported post types. * diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php index 3bc556a551f..091b5703774 100644 --- a/includes/options/class-amp-options-menu.php +++ b/includes/options/class-amp-options-menu.php @@ -48,17 +48,43 @@ public function add_menu_items() { ); add_settings_section( - 'post_types', + 'general', false, '__return_false', AMP_Options_Manager::OPTION_NAME ); + + add_settings_field( + 'theme_support', + __( 'Theme Support', 'amp' ), + array( $this, 'render_theme_support' ), + AMP_Options_Manager::OPTION_NAME, + 'general', + array( + 'class' => 'theme_support', + ) + ); + + add_settings_field( + 'validation', + __( 'Validation Handling', 'amp' ), + array( $this, 'render_validation_handling' ), + AMP_Options_Manager::OPTION_NAME, + 'general', + array( + 'class' => 'amp-validation-field', + ) + ); + add_settings_field( 'supported_post_types', __( 'Post Type Support', 'amp' ), array( $this, 'render_post_types_support' ), AMP_Options_Manager::OPTION_NAME, - 'post_types' + 'general', + array( + 'class' => 'amp-post-type-support-field', + ) ); $submenus = array( @@ -71,6 +97,142 @@ public function add_menu_items() { } } + /** + * Render theme support. + * + * @since 1.0 + */ + public function render_theme_support() { + $theme_support = AMP_Options_Manager::get_option( 'theme_support' ); + + $support_args = get_theme_support( 'amp' ); + + $theme_support_mutable = ( + empty( $support_args ) + || + ! empty( $support_args[0]['__added_via_option'] ) + ); + if ( ! $theme_support_mutable ) { + if ( amp_is_canonical() ) { + $theme_support = 'native'; + } else { + $theme_support = 'paired'; + } + } + + $should_have_theme_support = in_array( get_template(), array( 'twentyfifteen', 'twentysixteen', 'twentyseventeen' ), true ); + ?> +
+ +
+

+
+ +
+

+
+ +
+
+ > + +
+
+ +
+
+ > + +
+
+ +
+
+ > + +
+
+ +
+
+
+ +
+ 'non_existent', + ) ); + $forced_tree_shaking = $forced_sanitization || AMP_Validation_Error_Taxonomy::is_validation_error_sanitized( array( + 'code' => AMP_Style_Sanitizer::TREE_SHAKING_ERROR_CODE, + ) ); + ?> + + +
+

+ +

+
+ + +
+

+ +

+

+ +

+
+ + + + + +
+

+ +

+

+ +

+
+ + + +
+ +
name}"; - $is_builtin = amp_is_canonical() || in_array( $post_type->name, $builtin_support, true ); + $is_builtin = in_array( $post_type->name, $builtin_support, true ); ?> @@ -103,13 +270,7 @@ public function render_post_types_support() {

- +

diff --git a/includes/sanitizers/class-amp-style-sanitizer.php b/includes/sanitizers/class-amp-style-sanitizer.php index 1b17b2a65e7..cf9c3223a98 100644 --- a/includes/sanitizers/class-amp-style-sanitizer.php +++ b/includes/sanitizers/class-amp-style-sanitizer.php @@ -25,6 +25,13 @@ */ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer { + /** + * Error code for tree shaking. + * + * @var string + */ + const TREE_SHAKING_ERROR_CODE = 'removed_unused_css_rules'; + /** * Array of flags used to control sanitization. * @@ -1869,7 +1876,7 @@ private function finalize_stylesheet_set( $stylesheet_set ) { if ( $is_too_much_css && $should_tree_shake ) { $should_tree_shake = $this->should_sanitize_validation_error( array( - 'code' => 'removed_unused_css_rules', + 'code' => self::TREE_SHAKING_ERROR_CODE, ) ); } diff --git a/includes/validation/class-amp-invalid-url-post-type.php b/includes/validation/class-amp-invalid-url-post-type.php index aa9e9b71f1d..b2b87485161 100644 --- a/includes/validation/class-amp-invalid-url-post-type.php +++ b/includes/validation/class-amp-invalid-url-post-type.php @@ -312,29 +312,40 @@ public static function store_validation_errors( $validation_errors, $url ) { */ $stored_validation_errors = array(); + // Prevent Kses from corrupting JSON in description. + $has_pre_term_description_filter = has_filter( 'pre_term_description', 'wp_filter_kses' ); + if ( false !== $has_pre_term_description_filter ) { + remove_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter ); + } + $terms = array(); foreach ( $validation_errors as $data ) { - $term_data = AMP_Validation_Error_Taxonomy::prepare_validation_error_taxonomy_term( $data ); - $term_slug = $term_data['slug']; + $sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( $data ); + $term_data = AMP_Validation_Error_Taxonomy::prepare_validation_error_taxonomy_term( $data ); + $term_slug = $term_data['slug']; + + /* + * If the sanitization for the error is forced, make sure the term is created/updated with that group since + * the user will not be able to change it from NEW to ACCEPTED or REJECTED. + */ + if ( $sanitization['forced'] ) { + $term_data['term_group'] = $sanitization['status']; + } + if ( ! isset( $terms[ $term_slug ] ) ) { // Not using WP_Term_Query since more likely individual terms are cached and wp_insert_term() will itself look at this cache anyway. $term = get_term_by( 'slug', $term_slug, AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG ); if ( ! ( $term instanceof WP_Term ) ) { - $has_pre_term_description_filter = has_filter( 'pre_term_description', 'wp_filter_kses' ); - if ( false !== $has_pre_term_description_filter ) { - remove_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter ); - } $r = wp_insert_term( $term_slug, AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG, wp_slash( $term_data ) ); - if ( false !== $has_pre_term_description_filter ) { - add_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter ); - } if ( is_wp_error( $r ) ) { continue; } $term_id = $r['term_id']; update_term_meta( $term_id, 'created_date_gmt', current_time( 'mysql', true ) ); $term = get_term( $term_id ); + } elseif ( $term->term_group !== $term_data['term_group'] ) { + wp_update_term( $term->term_id, AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG, wp_slash( $term_data ) ); } $terms[ $term_slug ] = $term; } @@ -342,6 +353,11 @@ public static function store_validation_errors( $validation_errors, $url ) { $stored_validation_errors[] = compact( 'term_slug', 'data' ); } + // Finish preventing Kses from corrupting JSON in description. + if ( false !== $has_pre_term_description_filter ) { + add_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter ); + } + $post_content = wp_json_encode( $stored_validation_errors ); $placeholder = 'amp_invalid_url_content_placeholder' . wp_rand(); @@ -692,10 +708,10 @@ public static function print_admin_notice() { $count_urls_tested = isset( $_GET[ self::URLS_TESTED ] ) ? intval( $_GET[ self::URLS_TESTED ] ) : 1; // WPCS: CSRF ok. $errors_remain = ! empty( $_GET[ self::REMAINING_ERRORS ] ); // WPCS: CSRF ok. if ( $errors_remain ) { + $message = _n( 'The rechecked URL still has unaccepted validation errors.', 'The rechecked URLs still have unaccepted validation errors.', $count_urls_tested, 'amp' ); $class = 'notice-warning'; - $message = _n( 'The rechecked URL still has blocking validation errors.', 'The rechecked URLs still have validation errors.', $count_urls_tested, 'amp' ); } else { - $message = _n( 'The rechecked URL has no blocking validation errors.', 'The rechecked URLs have no validation errors.', $count_urls_tested, 'amp' ); + $message = _n( 'The rechecked URL is free of unaccepted validation errors.', 'The rechecked URLs are free of unaccepted validation errors.', $count_urls_tested, 'amp' ); $class = 'updated'; } @@ -773,7 +789,7 @@ function( $result ) { * Re-check invalid URL post for whether it has blocking validation errors. * * @param int|WP_Post $post Post. - * @return array|WP_Error List of blocking validation resukts, or a WP_Error in the case of failure. + * @return array|WP_Error List of blocking validation results, or a WP_Error in the case of failure. */ public static function recheck_post( $post ) { $post = get_post( $post ); @@ -963,7 +979,7 @@ public static function print_status_meta_box( $post ) { public static function print_validation_errors_meta_box( $post ) { $validation_errors = self::get_invalid_url_validation_errors( $post ); - $can_serve_amp = 0 === count( array_filter( $validation_errors, function( $validation_error ) { + $has_unaccepted_errors = 0 !== count( array_filter( $validation_errors, function( $validation_error ) { return AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS !== $validation_error['status']; } ) ); ?> @@ -977,14 +993,21 @@ public static function print_validation_errors_meta_box( $post ) { } - + + +
+

+
+ +
+

+
+ + +

- -
-

-

diff --git a/includes/validation/class-amp-validation-error-taxonomy.php b/includes/validation/class-amp-validation-error-taxonomy.php index e63c36ee148..7295cb30a48 100644 --- a/includes/validation/class-amp-validation-error-taxonomy.php +++ b/includes/validation/class-amp-validation-error-taxonomy.php @@ -190,7 +190,7 @@ public static function prepare_validation_error_taxonomy_term( $error ) { */ public static function is_validation_error_sanitized( $error ) { $sanitization = self::get_validation_error_sanitization( $error ); - return self::VALIDATION_ERROR_ACCEPTED_STATUS === $sanitization['status']; + return self::VALIDATION_ERROR_ACCEPTED_STATUS === $sanitization['status']; // @todo Change this so when amp_is_canonical() that NEW issues are sanitized by default? } /** @@ -248,50 +248,52 @@ public static function get_validation_error_sanitization( $error ) { return compact( 'status', 'forced' ); } + /** + * Whether optionally-accepted validation errors are being suppressed. + * + * @var bool + */ + public static $suppress_optional_validation_error_acceptance = false; + /** * Automatically (forcibly) accept validation errors that arise. * * @since 1.0 * @see AMP_Core_Theme_Sanitizer::get_acceptable_errors() * - * @param array $acceptable_errors Acceptable validation errors, where keys are codes and values are either `true` or sparse array to check as subset. + * @param array|true $acceptable_errors Acceptable validation errors, where keys are codes and values are either `true` or sparse array to check as subset. If just true, then all validation errors are accepted. + * @param array $args { + * Args. + * + * @var bool $optional Whether the validation errors are accepted via option. + * } */ - public static function accept_validation_errors( $acceptable_errors ) { + public static function accept_validation_errors( $acceptable_errors, $args = array() ) { if ( empty( $acceptable_errors ) ) { return; } - /** - * Check if one array is a sparse subset of another array. - * - * @param array $superset Superset array. - * @param array $subset Subset array. - * - * @return bool Whether subset is contained in superset. - */ - $is_array_subset = function( $superset, $subset ) use ( &$is_array_subset ) { - foreach ( $subset as $key => $subset_value ) { - if ( ! isset( $superset[ $key ] ) || gettype( $subset_value ) !== gettype( $superset[ $key ] ) ) { - return false; - } - if ( is_array( $subset_value ) ) { - if ( ! $is_array_subset( $superset[ $key ], $subset_value ) ) { - return false; - } - } elseif ( $superset[ $key ] !== $subset_value ) { - return false; - } + $args = array_merge( + array( 'optional' => false ), + $args + ); + + $optional = ! empty( $args['optional'] ); + add_filter( 'amp_validation_error_sanitized', function( $sanitized, $error ) use ( $acceptable_errors, $optional ) { + if ( $optional && AMP_Validation_Error_Taxonomy::$suppress_optional_validation_error_acceptance ) { + return $sanitized; + } + + if ( true === $acceptable_errors ) { + return true; } - return true; - }; - add_filter( 'amp_validation_error_sanitized', function( $sanitized, $error ) use ( $is_array_subset, $acceptable_errors ) { if ( isset( $acceptable_errors[ $error['code'] ] ) ) { if ( true === $acceptable_errors[ $error['code'] ] ) { return true; } foreach ( $acceptable_errors[ $error['code'] ] as $acceptable_error_props ) { - if ( $is_array_subset( $error, $acceptable_error_props ) ) { + if ( AMP_Validation_Error_Taxonomy::is_array_subset( $error, $acceptable_error_props ) ) { return true; } } @@ -300,6 +302,30 @@ public static function accept_validation_errors( $acceptable_errors ) { }, 10, 2 ); } + /** + * Check if one array is a sparse subset of another array. + * + * @param array $superset Superset array. + * @param array $subset Subset array. + * + * @return bool Whether subset is contained in superset. + */ + public static function is_array_subset( $superset, $subset ) { + foreach ( $subset as $key => $subset_value ) { + if ( ! isset( $superset[ $key ] ) || gettype( $subset_value ) !== gettype( $superset[ $key ] ) ) { + return false; + } + if ( is_array( $subset_value ) ) { + if ( ! self::is_array_subset( $superset[ $key ], $subset_value ) ) { + return false; + } + } elseif ( $superset[ $key ] !== $subset_value ) { + return false; + } + } + return true; + } + /** * Get the count of validation error terms, optionally restricted by term group (e.g. accepted or rejected). * diff --git a/includes/validation/class-amp-validation-manager.php b/includes/validation/class-amp-validation-manager.php index d39e7258a80..c6de332ef55 100644 --- a/includes/validation/class-amp-validation-manager.php +++ b/includes/validation/class-amp-validation-manager.php @@ -159,6 +159,25 @@ public static function init( $args = array() ) { } } ); + // Set sanitization options. + if ( amp_is_canonical() || AMP_Options_Manager::get_option( 'force_sanitization' ) ) { + AMP_Validation_Error_Taxonomy::accept_validation_errors( true, array( 'optional' => true ) ); + } elseif ( AMP_Options_Manager::get_option( 'accept_tree_shaking' ) ) { + AMP_Validation_Error_Taxonomy::accept_validation_errors( + array( + AMP_Style_Sanitizer::TREE_SHAKING_ERROR_CODE => true, + ), + array( 'optional' => true ) + ); + } + + // @todo This is not great. + // Allow the validation errors to be modified in the admin. + AMP_Validation_Error_Taxonomy::$suppress_optional_validation_error_acceptance = true; + add_action( 'template_redirect', function() { + AMP_Validation_Error_Taxonomy::$suppress_optional_validation_error_acceptance = false; + } ); + if ( self::$should_locate_sources ) { self::add_validation_error_sourcing(); } @@ -485,7 +504,13 @@ public static function print_edit_form_validation_status( $post ) { echo '

'; echo '

'; - esc_html_e( 'There is content which fails AMP validation. Non-accepted validation errors prevent AMP from being served.', 'amp' ); + esc_html_e( 'There is content which fails AMP validation.', 'amp' ); + echo ' '; + if ( amp_is_canonical() ) { + esc_html_e( 'The invalid markup will be automatically sanitized to ensure a valid AMP response is served.', 'amp' ); + } else { + esc_html_e( 'Non-accepted validation errors prevent AMP from being served, and the user will be redirected to the non-AMP version.', 'amp' ); + } echo sprintf( ' %s', esc_url( get_edit_post_link( $invalid_url_post ) ), @@ -1386,6 +1411,7 @@ public static function enqueue_block_validation() { $data = wp_json_encode( array( 'i18n' => gutenberg_get_jed_locale_data( 'amp' ), // @todo POT file. 'ampValidityRestField' => self::VALIDITY_REST_FIELD_NAME, + 'isCanonical' => amp_is_canonical(), ) ); wp_add_inline_script( $slug, sprintf( 'ampBlockValidation.boot( %s );', $data ) ); } diff --git a/tests/test-class-amp-options-manager.php b/tests/test-class-amp-options-manager.php index 38cac867af2..18cc655fd03 100644 --- a/tests/test-class-amp-options-manager.php +++ b/tests/test-class-amp-options-manager.php @@ -90,7 +90,16 @@ public function test_get_and_set_options() { AMP_Options_Manager::register_settings(); // Adds validate_options as filter. delete_option( AMP_Options_Manager::OPTION_NAME ); - $this->assertSame( array(), AMP_Options_Manager::get_options() ); + $this->assertSame( + array( + 'theme_support' => 'disabled', + 'supported_post_types' => array(), + 'analytics' => array(), + 'force_sanitization' => false, + 'accept_tree_shaking' => false, + ), + AMP_Options_Manager::get_options() + ); $this->assertSame( false, AMP_Options_Manager::get_option( 'foo' ) ); $this->assertSame( 'default', AMP_Options_Manager::get_option( 'foo', 'default' ) ); diff --git a/tests/test-class-amp-options-menu.php b/tests/test-class-amp-options-menu.php index 035706e3b85..aff7e0294bf 100644 --- a/tests/test-class-amp-options-menu.php +++ b/tests/test-class-amp-options-menu.php @@ -72,12 +72,12 @@ public function test_add_menu_items() { // Test add_setting_section(). $this->assertArrayHasKey( 'amp-options', $wp_settings_sections ); - $this->assertArrayHasKey( 'post_types', $wp_settings_sections['amp-options'] ); + $this->assertArrayHasKey( 'general', $wp_settings_sections['amp-options'] ); // Test add_setting_field(). $this->assertArrayHasKey( 'amp-options', $wp_settings_fields ); - $this->assertArrayHasKey( 'post_types', $wp_settings_fields['amp-options'] ); - $this->assertArrayHasKey( 'supported_post_types', $wp_settings_fields['amp-options']['post_types'] ); + $this->assertArrayHasKey( 'general', $wp_settings_fields['amp-options'] ); + $this->assertArrayHasKey( 'supported_post_types', $wp_settings_fields['amp-options']['general'] ); } /** From 5bae33e55bf41e709bd46541c011d3ca36b42fc8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 5 Jun 2018 22:57:01 -0700 Subject: [PATCH 04/17] Remove dirty AMP support; rename Disabled to Classic --- includes/admin/class-amp-editor-blocks.php | 38 --------------------- includes/options/class-amp-options-menu.php | 2 +- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/includes/admin/class-amp-editor-blocks.php b/includes/admin/class-amp-editor-blocks.php index cf484fb0ac3..7e880cea840 100644 --- a/includes/admin/class-amp-editor-blocks.php +++ b/includes/admin/class-amp-editor-blocks.php @@ -43,16 +43,6 @@ public function init() { if ( function_exists( 'gutenberg_init' ) ) { add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) ); add_filter( 'wp_kses_allowed_html', array( $this, 'whitelist_block_atts_in_wp_kses_allowed_html' ), 10, 2 ); - - /* - * Dirty AMP is currently disabled per . - * Note that when not in native/canonical mode, AMP-specific Gutenberg blocks will not be - * registered for use, and so users will not be likely attempting to serve AMP content in - * non-AMP responses. When/if dirty AMP is allowed, the following can be re-enabled: - * - * add_filter( 'the_content', array( $this, 'tally_content_requiring_amp_scripts' ) ); - * add_action( 'wp_print_footer_scripts', array( $this, 'print_dirty_amp_scripts' ) ); - */ } } @@ -155,32 +145,4 @@ public function enqueue_block_editor_assets() { ) ) ) ); } - - /** - * Tally the AMP component scripts that are needed in a dirty AMP document. - * - * @param string $content Content. - * @return string Content (unmodified). - */ - public function tally_content_requiring_amp_scripts( $content ) { - if ( ! is_amp_endpoint() ) { - $pattern = sprintf( '/<(%s)\b.*?>/s', join( '|', $this->amp_blocks ) ); - if ( preg_match_all( $pattern, $content, $matches ) ) { - $this->content_required_amp_scripts = array_merge( - $this->content_required_amp_scripts, - $matches[1] - ); - } - } - return $content; - } - - /** - * Print AMP scripts required for AMP components used in a non-AMP document (dirty AMP). - */ - public function print_dirty_amp_scripts() { - if ( ! is_amp_endpoint() && ! empty( $this->content_required_amp_scripts ) ) { - wp_scripts()->do_items( $this->content_required_amp_scripts ); - } - } } diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php index 091b5703774..52654ab584e 100644 --- a/includes/options/class-amp-options-menu.php +++ b/includes/options/class-amp-options-menu.php @@ -136,7 +136,7 @@ public function render_theme_support() {

>
From a5756d9e227cc6b9bae7ec8f4619ecf2e51b9dc0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 6 Jun 2018 00:33:07 -0700 Subject: [PATCH 05/17] Add link to settings screen among plugin action links --- includes/options/class-amp-options-menu.php | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php index 52654ab584e..028b0be5ad3 100644 --- a/includes/options/class-amp-options-menu.php +++ b/includes/options/class-amp-options-menu.php @@ -23,6 +23,28 @@ class AMP_Options_Menu { public function init() { add_action( 'admin_post_amp_analytics_options', 'AMP_Options_Manager::handle_analytics_submit' ); add_action( 'admin_menu', array( $this, 'add_menu_items' ), 9 ); + + $plugin_file = preg_replace( '#.+/(?=.+?/.+?)#', '', AMP__FILE__ ); + add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'add_plugin_action_links' ) ); + } + + /** + * Add plugin action links. + * + * @param array $links Links. + * @return array Modified links. + */ + public function add_plugin_action_links( $links ) { + return array_merge( + array( + 'settings' => sprintf( + '%2$s', + esc_url( add_query_arg( 'page', AMP_Options_Manager::OPTION_NAME, admin_url( 'admin.php' ) ) ), + __( 'Settings', 'amp' ) + ), + ), + $links + ); } /** From 0068e7946a7cb037e535da4be243ad1343b97b54 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 6 Jun 2018 09:14:53 -0700 Subject: [PATCH 06/17] Rename Theme Support to Template Mode --- includes/options/class-amp-options-menu.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/options/class-amp-options-menu.php b/includes/options/class-amp-options-menu.php index 028b0be5ad3..f8ae3f3a515 100644 --- a/includes/options/class-amp-options-menu.php +++ b/includes/options/class-amp-options-menu.php @@ -78,7 +78,7 @@ public function add_menu_items() { add_settings_field( 'theme_support', - __( 'Theme Support', 'amp' ), + __( 'Template Mode', 'amp' ), array( $this, 'render_theme_support' ), AMP_Options_Manager::OPTION_NAME, 'general', From be0557bde759f1686d892e90138d941a121f0387 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 6 Jun 2018 11:31:43 -0700 Subject: [PATCH 07/17] Fix undefined index notice --- includes/sanitizers/class-amp-style-sanitizer.php | 2 +- includes/validation/class-amp-invalid-url-post-type.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/sanitizers/class-amp-style-sanitizer.php b/includes/sanitizers/class-amp-style-sanitizer.php index cf9c3223a98..4091279b916 100644 --- a/includes/sanitizers/class-amp-style-sanitizer.php +++ b/includes/sanitizers/class-amp-style-sanitizer.php @@ -211,7 +211,7 @@ public static function get_css_parser_validation_error_codes() { 'illegal_css_at_rule', 'illegal_css_important', 'illegal_css_property', - 'removed_unused_css_rules', + self::TREE_SHAKING_ERROR_CODE, 'unrecognized_css', 'disallowed_file_extension', 'file_path_not_found', diff --git a/includes/validation/class-amp-invalid-url-post-type.php b/includes/validation/class-amp-invalid-url-post-type.php index b2b87485161..a0371d8459b 100644 --- a/includes/validation/class-amp-invalid-url-post-type.php +++ b/includes/validation/class-amp-invalid-url-post-type.php @@ -344,7 +344,7 @@ public static function store_validation_errors( $validation_errors, $url ) { $term_id = $r['term_id']; update_term_meta( $term_id, 'created_date_gmt', current_time( 'mysql', true ) ); $term = get_term( $term_id ); - } elseif ( $term->term_group !== $term_data['term_group'] ) { + } elseif ( isset( $term_data['term_group'] ) && $term->term_group !== $term_data['term_group'] ) { wp_update_term( $term->term_id, AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG, wp_slash( $term_data ) ); } $terms[ $term_slug ] = $term; From 9971e22518524d5a28e3793496cf54b66604df1b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 7 Jun 2018 12:26:54 -0700 Subject: [PATCH 08/17] Omit displaying the amphtml link if there are known unaccepted validation errors --- includes/amp-helper-functions.php | 15 +++++- .../class-amp-invalid-url-post-type.php | 4 ++ .../class-amp-validation-manager.php | 4 +- tests/test-amp-helper-functions.php | 48 ++++++++++++++----- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index fb1638c6be1..f9519f398c2 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -169,7 +169,8 @@ function amp_remove_endpoint( $url ) { /** * Add amphtml link. * - * @todo If there is a known amp_invalid_url post for this $amp_url that has unaccepted validation errors then this should output nothing. + * If there are known validation errors for the current URL then do not output anything. + * * @since 1.0 */ function amp_add_amphtml_link() { @@ -184,11 +185,21 @@ function amp_add_amphtml_link() { return; } + $current_url = amp_get_current_url(); + + // Check to see if there are known unaccepted validation errors for this URL. + if ( current_theme_supports( 'amp' ) ) { + $invalid_url_post = AMP_Invalid_URL_Post_Type::get_invalid_url_post( $current_url ); + if ( $invalid_url_post && count( AMP_Invalid_URL_Post_Type::get_invalid_url_validation_errors( $invalid_url_post, array( 'ignore_accepted' => true ) ) ) > 0 ) { + return; + } + } + $amp_url = null; if ( is_singular() ) { $amp_url = amp_get_permalink( get_queried_object_id() ); } else { - $amp_url = add_query_arg( amp_get_slug(), '', amp_get_current_url() ); + $amp_url = add_query_arg( amp_get_slug(), '', $current_url ); } if ( $amp_url ) { printf( '', esc_url( $amp_url ) ); diff --git a/includes/validation/class-amp-invalid-url-post-type.php b/includes/validation/class-amp-invalid-url-post-type.php index 8c7e8163e47..2b018021646 100644 --- a/includes/validation/class-amp-invalid-url-post-type.php +++ b/includes/validation/class-amp-invalid-url-post-type.php @@ -198,7 +198,11 @@ public static function get_invalid_url_validation_errors( $post, $args = array() if ( ! isset( $stored_validation_error['term_slug'] ) ) { continue; } + $term = get_term_by( 'slug', $stored_validation_error['term_slug'], AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG ); + if ( ! $term ) { + continue; + } $sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( $stored_validation_error['data'] ); if ( $args['ignore_accepted'] && AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS === $sanitization['status'] ) { diff --git a/includes/validation/class-amp-validation-manager.php b/includes/validation/class-amp-validation-manager.php index 2a383a03132..6b18f3d4151 100644 --- a/includes/validation/class-amp-validation-manager.php +++ b/includes/validation/class-amp-validation-manager.php @@ -141,8 +141,8 @@ public static function init( $args = array() ) { self::$should_locate_sources = $args['should_locate_sources']; - add_action( 'init', array( 'AMP_Invalid_URL_Post_Type', 'register' ) ); - add_action( 'init', array( 'AMP_Validation_Error_Taxonomy', 'register' ) ); + AMP_Invalid_URL_Post_Type::register(); + AMP_Validation_Error_Taxonomy::register(); add_action( 'save_post', array( __CLASS__, 'handle_save_post_prompting_validation' ), 10, 2 ); add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_validation' ) ); diff --git a/tests/test-amp-helper-functions.php b/tests/test-amp-helper-functions.php index 9d3e5a096a7..5b7756bda14 100644 --- a/tests/test-amp-helper-functions.php +++ b/tests/test-amp-helper-functions.php @@ -267,21 +267,47 @@ public function get_amphtml_urls() { * @param string $amphtml_url The amphtml URL. */ public function test_amp_add_amphtml_link( $canonical_url, $amphtml_url ) { + $test = $this; // For PHP 5.3. + + $get_amp_html_link = function() { + ob_start(); + amp_add_amphtml_link(); + return ob_get_clean(); + }; + + $assert_amphtml_link_present = function() use ( $test, $amphtml_url, $get_amp_html_link ) { + $test->assertEquals( + sprintf( '', esc_url( $amphtml_url ) ), + $get_amp_html_link() + ); + }; + $this->go_to( $canonical_url ); - ob_start(); - amp_add_amphtml_link(); - $output = ob_get_clean(); - $this->assertEquals( - sprintf( '', esc_url( $amphtml_url ) ), - $output - ); + $assert_amphtml_link_present(); // Make sure adding the filter hides the amphtml link. add_filter( 'amp_frontend_show_canonical', '__return_false' ); - ob_start(); - amp_add_amphtml_link(); - $output = ob_get_clean(); - $this->assertEmpty( $output ); + $this->assertEmpty( $get_amp_html_link() ); + remove_filter( 'amp_frontend_show_canonical', '__return_false' ); + $assert_amphtml_link_present(); + + // Make sure that the link is not provided when there are validation errors associated with the URL. + add_theme_support( 'amp', array( + 'template_dir' => './', + ) ); + AMP_Theme_Support::init(); + $invalid_url_post_id = AMP_Invalid_URL_Post_Type::store_validation_errors( + array( + array( 'code' => 'foo' ), + ), + $canonical_url + ); + $this->assertNotInstanceOf( 'WP_Error', $invalid_url_post_id ); + + // Allow the URL when the errors are forcibly sanitized. + $this->assertEmpty( $get_amp_html_link() ); + add_filter( 'amp_validation_error_sanitized', '__return_true' ); + $assert_amphtml_link_present(); } /** From 32a093e50454b2db64c78c061af0bde353c2c313 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 7 Jun 2018 13:11:10 -0700 Subject: [PATCH 09/17] Include HTML comment explaining that amphtml version is not available (and why) * Indicate the count of validation errors that are blocking AMP from being available. * Allow passing URL string in addition to amp_invalid_url post to AMP_Invalid_URL_Post_Type::get_invalid_url_validation_errors(). --- includes/amp-helper-functions.php | 41 +++++++++++++++---- includes/class-amp-theme-support.php | 4 +- .../class-amp-invalid-url-post-type.php | 23 ++++++++--- tests/test-amp-helper-functions.php | 2 +- tests/test-class-amp-theme-support.php | 3 +- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index f9519f398c2..0bc418687e4 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -187,20 +187,45 @@ function amp_add_amphtml_link() { $current_url = amp_get_current_url(); + $amp_url = null; + if ( current_theme_supports( 'amp' ) ) { + if ( AMP_Theme_Support::is_paired_available() ) { + $amp_url = add_query_arg( amp_get_slug(), '', $current_url ); + } + } else { + if ( is_singular() ) { + $amp_url = amp_get_permalink( get_queried_object_id() ); + } else { + $amp_url = add_query_arg( amp_get_slug(), '', $current_url ); + } + } + + if ( ! $amp_url ) { + printf( '', esc_html__( 'There is no amphtml version available for this URL.', 'amp' ) ); + return; + } + // Check to see if there are known unaccepted validation errors for this URL. if ( current_theme_supports( 'amp' ) ) { - $invalid_url_post = AMP_Invalid_URL_Post_Type::get_invalid_url_post( $current_url ); - if ( $invalid_url_post && count( AMP_Invalid_URL_Post_Type::get_invalid_url_validation_errors( $invalid_url_post, array( 'ignore_accepted' => true ) ) ) > 0 ) { + $validation_errors = AMP_Invalid_URL_Post_Type::get_invalid_url_validation_errors( $current_url, array( 'ignore_accepted' => true ) ); + $error_count = count( $validation_errors ); + if ( $error_count > 0 ) { + echo ""; return; } } - $amp_url = null; - if ( is_singular() ) { - $amp_url = amp_get_permalink( get_queried_object_id() ); - } else { - $amp_url = add_query_arg( amp_get_slug(), '', $current_url ); - } if ( $amp_url ) { printf( '', esc_url( $amp_url ) ); } diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 6b48f4be3c9..823e26d4a00 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -162,9 +162,7 @@ public static function apply_options() { */ public static function finish_init() { if ( ! is_amp_endpoint() ) { - if ( self::is_paired_available() ) { - amp_add_frontend_actions(); - } + amp_add_frontend_actions(); return; } diff --git a/includes/validation/class-amp-invalid-url-post-type.php b/includes/validation/class-amp-invalid-url-post-type.php index 2b018021646..56994823c60 100644 --- a/includes/validation/class-amp-invalid-url-post-type.php +++ b/includes/validation/class-amp-invalid-url-post-type.php @@ -172,28 +172,39 @@ public static function add_admin_menu_new_invalid_url_count() { /** * Gets validation errors for a given invalid URL post. * - * @param int|WP_Post $post Post of amp_invalid_url type. - * @param array $args { + * @param string|int|WP_Post $url Either the URL string or a post (ID or WP_Post) of amp_invalid_url type. + * @param array $args { * Args. * * @type bool $ignore_accepted Exclude validation errors that are accepted. Default false. * } * @return array List of errors, with keys for term, data, status, and (sanitization) forced. */ - public static function get_invalid_url_validation_errors( $post, $args = array() ) { - $args = array_merge( + public static function get_invalid_url_validation_errors( $url, $args = array() ) { + $args = array_merge( array( 'ignore_accepted' => false, ), $args ); - $post = get_post( $post ); - $errors = array(); + // Look up post by URL or ensure the amp_invalid_url object. + if ( is_string( $url ) ) { + $post = self::get_invalid_url_post( $url ); + } else { + $post = get_post( $url ); + } + if ( ! $post || self::POST_TYPE_SLUG !== $post->post_type ) { + return array(); + } + + // Skip when parse error. $stored_validation_errors = json_decode( $post->post_content, true ); if ( ! is_array( $stored_validation_errors ) ) { return array(); } + + $errors = array(); foreach ( $stored_validation_errors as $stored_validation_error ) { if ( ! isset( $stored_validation_error['term_slug'] ) ) { continue; diff --git a/tests/test-amp-helper-functions.php b/tests/test-amp-helper-functions.php index 5b7756bda14..5df1ddaa730 100644 --- a/tests/test-amp-helper-functions.php +++ b/tests/test-amp-helper-functions.php @@ -305,7 +305,7 @@ public function test_amp_add_amphtml_link( $canonical_url, $amphtml_url ) { $this->assertNotInstanceOf( 'WP_Error', $invalid_url_post_id ); // Allow the URL when the errors are forcibly sanitized. - $this->assertEmpty( $get_amp_html_link() ); + $this->assertContains( '