true, 'on' => true, 'id' => true, 'layout' => true, 'width' => true, 'height' => true, 'sizes' => true, ); /** * List of non-amp URLs * * @since 1.0.0 * @var array */ private static $non_amp_urls = array(); /** * Store tabindex number * * @var int * * @since 1.0.0 */ public $tabindex = 10; /** * Store HTML tags list * * @var array * * @since 1.0 */ public $tags = array(); /** * @since 1.0.0 */ const PATTERN_REL_WP_ATTACHMENT = '#wp-att-([\d]+)#'; public function __construct(Amp_WP_Html_Util $dom) { $this->dom = $dom; } /** * Prepare HTML content for amp version it removes: * 1) invalid tags * 2) invalid attributes * 3) invalid URL protocols * * @since 1.0.0 */ public function sanitize() { $blacklisted_attributes = $this->get_blacklisted_attributes(); $this->sanitize_document(); $tags = array(); include AMP_WP_TEMPLATE_DIR_PATH . '/includes/tags-list.php'; $this->tags = $tags; $this->strip_attributes_recursive( $this->dom->get_body_node(), $blacklisted_attributes ); $this->tags = array(); } /** * List of blacklisted attributes * * @since 1.0.0 * * @return array */ private function get_blacklisted_attributes() { return array( 'style', 'size', ); } /** * Stripes attributes on nodes and children * * @param DOMElement $node * @param array $bad_attributes * * @since 1.0.0 */ private function strip_attributes_recursive($node, $bad_attributes) { if( !isset( $node->nodeType ) || $node->nodeType !== XML_ELEMENT_NODE ) { return; } if( !isset( $this->tags[$node->tagName] ) ) { // remove invalid tag self::remove_element($node); return; } $node_name = $node->nodeName; // Some nodes may contain valid content but are themselves invalid. // Remove the node but preserve the children. if( $node->hasAttributes() ) { $length = $node->attributes->length; for ($i = $length - 1; $i >= 0; $i --) { $attribute = $node->attributes->item($i); $attribute_name = strtolower($attribute->name); if( 'style' === $attribute_name ) { $this->save_element_style($node, $attribute); } if(in_array($attribute_name, $bad_attributes)) { $node->removeAttribute($attribute_name); continue; } // on* attributes (like onclick) are a special case if( 0 === stripos( $attribute_name, 'on' ) && $attribute_name != 'on' ) { $node->removeAttribute($attribute_name); continue; } } } $length = $node->childNodes->length; for( $i = $length - 1; $i >= 0; $i -- ) { $child_node = $node->childNodes->item($i); $this->strip_attributes_recursive($child_node, $bad_attributes); } if ('font' === $node_name) { $this->replace_node_with_children($node); } } /** * Remove the wrapper of node * * @param $node * * @since 1.0.0 */ private function replace_node_with_children($node) { // If the node has children and also has a parent node, // clone and re-add all the children just before current node. if ($node->hasChildNodes() && $node->parentNode) { foreach ($node->childNodes as $child_node) { $new_child = $child_node->cloneNode(true); $node->parentNode->insertBefore($new_child, $node); } } // Remove the node from the parent, if defined. if ($node->parentNode) { $node->parentNode->removeChild($node); } } /** * Check string to end with * * @param $haystack * @param $needle * * @since 1.0.0 * * @return bool */ public static function endswith( $haystack, $needle ) { return '' !== $haystack && '' !== $needle && $needle === substr($haystack, - strlen($needle)); } /** * Sanitize the dimensions to be AMP valid * * @param $value * @param $dimension * * @since 1.0.0 * * @return float|int|string */ public static function sanitize_dimension($value, $dimension) { if (empty($value)) { return $value; } if (false !== filter_var($value, FILTER_VALIDATE_INT)) { return absint($value); } if (self::endswith($value, 'px')) { return absint($value); } if (self::endswith($value, '%')) { if ('width' === $dimension) { $percentage = absint($value) / 100; return round($percentage * amp_wp_get_container_width()); } } return ''; } /** * Convert $url to amp version if: * * 1) $url was internal * 2) disable flag is not true {@see turn_url_transform_off_on} * * @param string $url * * @since 1.0.0 * * @return string transformed amp URL on success or passed $url otherwise. */ public static function transform_to_amp_url( $url ) { if( !self::$enable_url_transform ) { return $url; } // Don't transform non-amp URLs to amp URL { if( isset( self::$non_amp_urls['general'][rtrim($url, '/')] ) ) { return $url; } if( !empty( self::$non_amp_urls['start_with'] ) ) { if( preg_match( '#^'.self::$non_amp_urls['start_with'].'#i', $url ) ) { return $url; } } } if( !amp_wp_get_permalink_structure() ) { return add_query_arg( Amp_WP_Public::SLUG, true, $url ); } if( 'end-point' === amp_wp_url_format() ) { if ( $transformed = self::transform_to_end_point_amp( $url ) ) { return $transformed; } } else { if ( $transformed = self::transform_to_start_point_amp( $url ) ) { return $transformed; } } return $url; } /** * Transform given URL to AMP Permalink on Starting Point. * * @param string $url * @since 1.0.4 * * @return bool|string URL on success or false on failure. */ protected static function transform_to_start_point_amp( $url ) { // Check is url internal? // todo support parked domains $matched = array(); if( !preg_match( '#^https?://w*\.?' . self::regex_url() . '/?([^/]*)/?([^/]*)/?(.*?)$#', $url, $matched ) ) { return false; } // If URL was not AMP $exclude_sub_dirs = (array) apply_filters( 'amp_wp_transformer_exclude_subdir', array() ); $sub_dir_excluded = in_array( $matched[1], $exclude_sub_dirs ); $first_valid_dir = $sub_dir_excluded ? $matched[2] : $matched[1]; // It's already AMP URL if( Amp_WP_Public::SLUG === $first_valid_dir ) { return false; } // Do not convert link which is started with wp-content if( 'wp-content' === $matched[1] ) { return false; } $before_sp = ''; $path = '/'; if( $matched[1] ) { $matched[0] = ''; if( $sub_dir_excluded ) { $before_sp = $matched[1]; $matched[1] = ''; } $path = implode( '/', array_filter( $matched ) ); } $transformed_url = $path . ( substr( $url, -1 ) === '/' ? '/' : '' ); return amp_wp_site_url( amp_wp_url_trim_end_slash( $transformed_url ), $before_sp ); } /** * Transform Given URL to End Point. * * @param string $url * * @since 1.0.4 * * @return bool */ protected static function transform_to_end_point_amp( $url ) { if( !preg_match( '#^https?://w*\.?' . self::regex_url() . '/?#', $url ) ) { return false; } $parsed = parse_url( $url ); $path = isset( $parsed['path'] ) ? $parsed['path'] : '/'; $query = isset( $parsed['query'] ) ? $parsed['query'] : ''; if( Amp_WP_Public::SLUG === basename( $path ) ) { return false; } $url = sprintf( '%s://%s/%s', $parsed['scheme'], $parsed['host'], ltrim( $path, '/' ) ); $url = trailingslashit( $url ) . Amp_WP_Public::SLUG . '/'; if ( $query ) { $url .= '?'; $url .= $parsed['query']; } return amp_wp_url_trim_end_slash( $url ); } /** * * @param string $delimiter * @since 1.0.4 * * @return string */ public static function regex_url( $delimiter = '#' ) { $site_domain = str_replace( array( 'http://www.', 'https://www.', 'http://', 'https://', ), '', home_url() ); return preg_quote( rtrim( $site_domain, '/' ), $delimiter ); } /** * Convert AMP $url to Non-AMP version if $url was internal * * @param string $url * @param bool $strict * * @since 1.0.0 * * @return string transformed non-amp URL on success or passed $url otherwise. */ public static function transform_to_non_amp_url( $url, $strict = false ) { if( amp_wp_get_permalink_structure() ) { if( $strict ) { $url = self::remove_end_point_amp( $url, $url ); $url = self::remove_start_point_amp( $url, $url ); } elseif ( 'end-point' === amp_wp_url_format() ) { $url = self::remove_end_point_amp( $url, $url ); } else { $url = self::remove_start_point_amp( $url, $url ); } } return remove_query_arg( Amp_WP_Public::SLUG, $url ); } /** * Remove AMP from the start of the URL * * @param string $url * @param mixed $default * @since 1.0.5 * @since 1.2.1 Added $default bool|string parameter * * @return bool|string non AMP URL on success or false on error. */ public static function remove_start_point_amp( $url, $default = false ) { if( empty( $url ) ) { return $default; } $prefix = amp_wp_permalink_prefix(); $matched = array(); if( !preg_match( '#^https?://w*\.?' . self::regex_url() . '/?' . $prefix . '([^/]*)/?(.*?)$#', $url, $matched ) ) { return $default; } // If URL was not amp if( Amp_WP_Public::SLUG !== $matched[1] ) { return $default; } if( $matched[1] ) { $matched[0] = ''; unset( $matched[1] ); $path = implode( '/', $matched ); } else { $path = '/'; } return home_url( rtrim( $prefix, '/' ) . $path ); } /** * Remove AMP from the end of the URL * * @param string $url * @param mixed $default * @since 1.0.5 * @since 1.2.1 Added bool|string parameter * * @return bool|string Non AMP URL on success or false on error. */ public static function remove_end_point_amp( $url, $default = false ) { if( empty( $url ) ) { return $default; } if( !preg_match( '#^https?://w*\.?' . self::regex_url() . '/?#', $url ) ) { return $default; } $parsed = parse_url( $url ); if( empty( $parsed['path'] ) ) { return $default; } if( basename( $parsed['path'] ) !== Amp_WP_Public::SLUG ) { if( $transformed = self::single_post_pagination_non_amp_url( $parsed['path'] ) ) { return $transformed; } if( $transformed = self::pagination_non_amp_url( $parsed['path'] ) ) { return $transformed; } return $default; } return trailingslashit( sprintf( '%s://%s%s', $parsed['scheme'], $parsed['host'], dirname( $parsed['path'] ) ) ); } /** * Convert the Following URL * * [single post]/[page-number]/amp * * to * * [single post]/amp/[page-number] * * @param string $url_path * @since 1.2.1 * * @return string|bool. */ protected static function single_post_pagination_non_amp_url( $url_path ) { global $wp_rewrite; $single_post_format = str_replace( $wp_rewrite->rewritecode, $wp_rewrite->rewritereplace, get_option( 'permalink_structure' ) ); $test_pattern = '(' . $single_post_format . ')'; // Capture as the first item $match[1] $test_pattern .= AMP_WP_Public::SLUG; $test_pattern .= '/+(\d+)/?'; // Capture as the last item array_pop( $match ) if( preg_match( "#^$test_pattern$#", $url_path, $match ) ) { $page_number = array_pop( $match ); $non_amp_request_url = $match[1]; return home_url( $non_amp_request_url . $page_number ); } return false; } /** * Remove AMP End Point in paginated pages. * * @param string $url_path * @since 1.2.1 * @return bool|string */ public static function pagination_non_amp_url( $url_path ) { if( preg_match( '#(.+)/' . Amp_WP_Public::SLUG . '(/page/\d+/?)#', $url_path, $match ) ) { return home_url( $match[1] . $match[2] ); } return false; } /** * Replace internal links with amp version just in href attribute * * @param array $attr list of attributes * * @since 1.0.0 * * @return array */ public static function replace_href_with_amp($attr) { if( isset( $attr['href'] ) ) { $attr['href'] = self::transform_to_amp_url( $attr['href'] ); } return $attr; } /** * Trigger URL transform status on/off * * @see transform_to_amp_url * * @param bool $is_on * * @since 1.0.0 * * @return bool previous situation */ public static function turn_url_transform_off_on( $is_on ) { $prev = self::$enable_url_transform; self::$enable_url_transform = $is_on; return $prev; } /** * Callback function for preg_replace_callback * to replace html href="" links to amp version * * @param array $match pattern matches * * @access private * * @since 1.0.0 * * @return string */ private static function _preg_replace_link_callback( $match ) { $url = empty($match[4]) ? $match[3] : $match[4]; $url = self::transform_to_amp_url( $url ); $atts = &$match[1]; $q = &$match[2]; return sprintf(']+)) # if quote found, match up to next matching # quote, otherwise match up to next space 'isx"; return preg_replace_callback( $pattern, array(__CLASS__, '_preg_replace_link_callback'), $content ); } /** * @param $element * * @since 1.0.0 */ public static function remove_element($element) { $element->parentNode->removeChild($element); } /** * @param array $element_atts * @param DOMElement $element * * @since 1.0.0 * @return array */ public function get_invalid_attrs($element_atts, $element) { $invalid_attrs = array(); switch( $element->tagName ) { case 'amp-img': if( isset($element_atts['width']) && $element_atts['width'] === 'auto' ) { $invalid_attrs[] = 'width'; } break; } return $invalid_attrs; } /** * @since 1.0.0 */ public function sanitize_document() { $prev_tag_name = false; $rules = array(); $white_list_atts = array( 'submitting', 'submit-success', 'submit-error', 'style', // style attributies will collect and sanitize @see save_element_style ); include AMP_WP_TEMPLATE_DIR_PATH . '/includes/sanitizer-rules.php'; if( $rules ) { foreach( $rules as $rule ) { if( $prev_tag_name !== $rule['tag_name'] ) { $elements = $this->dom->getElementsByTagName( $rule['tag_name'] ); $prev_tag_name = $rule['tag_name']; } if( $nodes_count = $elements->length ) { foreach( $rule['attrs'] as $atts ) { if( empty( $atts['name'] ) ) { continue; } for ($i = $nodes_count - 1; $i >= 0; $i --) { $element = $elements->item($i); if( !$element ) { // if element was deleted break 2; } $element_atts = self::get_node_attributes($element); $atts2remove = $this->get_invalid_attrs($element_atts, $element); $new_atts = array(); $mandatory = false; foreach( $atts2remove as $attr ) { unset( $element_atts[$attr] ); } /** * STEP 1) remove height=auto images */ if( $rule['tag_name'] === 'amp-img' && isset($element_atts['height']) && $element_atts['height'] === 'auto' ) { self::remove_element($element); // Remove invalid element continue; } /** * STEP 2) Sanitize layout attribute */ if( !empty( $rule['layouts']['supported_layouts'] ) ) { if( !empty( $element_atts['layout'] ) ) { $layout = strtoupper($element_atts['layout']); if( in_array( $layout, $rule['layouts']['supported_layouts'] ) ) { // $this->sanitize_layout_attribute( $layout, $element, $element_atts ); } else { // invalid layout attribute value if( !empty($element_atts['width']) && !empty($element_atts['height'])) { $new_atts['layout'] = 'responsive'; } else { $new_atts['layout'] = 'fill'; } } } else { if( isset($element_atts['width'] ) && $element_atts['width'] === 'auto' && !empty( $element_atts['height'] ) ) { // default layout is FIXED-HEIGHT if( !in_array('FIXED-HEIGHT', $rule['layouts']['supported_layouts'] ) ) { $atts2remove[] = 'width'; } } } } /** * STEP 3) search for single required attributes */ if( !empty($atts['mandatory']) ) { // if attribute is required if( !isset( $element_atts[$atts['name']] ) ) { self::remove_element($element); // Remove invalid element continue; } $mandatory = true; } /** * STEP 4) search for alternative required attributes */ if (!empty($atts['mandatory_oneof'])) { if (!array_intersect_key($element_atts, $atts['mandatory_oneof'])) { // no required attribute was found if (empty($atts['value'])) { self::remove_element($element); // Remove invalid element continue; } else { // add required attribute to element if attribute value exists $new_atts[$atts['name']] = $atts['value']; } } else { $mandatory = true; } } /** * STEP 5) Sanitize attribute value */ if (!empty($element_atts[$atts['name']])) { $remove_element = false; foreach( array('value_regex', 'value_regex_case') as $regex_field ) { if( !empty( $atts[$regex_field] ) ) { $modifier = 'value_regex_case' === $regex_field ? 'i' : ''; if (!preg_match('#^' . $atts[$regex_field] . '$#' . $modifier, $element_atts[$atts['name']])) { if ($mandatory) { $remove_element = true; } else { $atts2remove[] = $atts['name']; break; } } } } if( $remove_element ) { self::remove_element($element); // Remove invalid element continue; } if( !empty($atts['blacklisted_value_regex'] ) ) { // Check blacklist if( !preg_match( '/' . $atts['blacklisted_value_regex'] . '/', $element_atts[$atts['name']] ) ) { $atts2remove[] = $atts['name']; } } } /** * STEP 6) Sanitize URL value * */ if (!empty($atts['value_url'])) { $val = isset($element_atts[$atts['name']]) ? $element_atts[$atts['name']] : null; $parsed = $val ? parse_url($val) : array(); // check empty url value if (isset($atts['value_url']['allow_empty']) && !$atts['value_url']['allow_empty']) { // empty url is not allowed if (empty($element_atts[$atts['name']])) { // is url relative ? if ($mandatory) { $remove_element = true; } else { $atts2remove[] = $atts['name']; } } } // check URL protocol if (!empty($atts['value_url']['allowed_protocol'])) { if (isset($parsed['scheme'])) { if (!in_array($parsed['scheme'], $atts['value_url']['allowed_protocol'])) { // invalid url protocol if ($mandatory) { $remove_element = true; } else { $atts2remove[] = $atts['name']; } } } } if (isset($atts['value_url']['allow_relative']) && !$atts['value_url']['allow_relative']) { // relative url is not allowed if (empty($parsed['host'])) { // is url relative ? if ($mandatory) { $remove_element = true; } else { $atts2remove[] = $atts['name']; } } } // Force schema for amp-iframe tag if (empty($parsed['scheme']) && $rule['tag_name'] == 'amp-iframe') { $parsed['scheme'] = 'https'; } if (!empty($remove_element)) { self::remove_element($element); // Remove invalid element continue; } else { $val = amp_wp_unparse_url($parsed); if (!empty($val)) { $element->setAttribute($atts['name'], $val); $element_atts[$atts['name']] = $val; } } } /** * STEP 7) Sanitize attribute with fixed value */ if (isset($atts['value']) && isset($element_atts[$atts['name']])) { if ($element_atts[$atts['name']] !== $atts['value']) { // is current value invalid? $new_atts[$atts['name']] = $atts['value']; // set valid value } } /** * STEP 8) Filter attributes list */ if (sizeof($atts) === 1) { // check is attribute boolean if ($element_atts) { $el_atts = $this->_get_rule_attrs_list($rule); foreach ($element_atts as $k => $v) { if (isset($this->general_attrs[$k])) { continue; } if (substr($k, 0, 5) !== 'data-') { $atts2remove[$k] = $v; } } $atts2remove = array_diff_key($atts2remove, $el_atts); // Filter extra attrs $atts2remove = array_keys($atts2remove); } } /** * STEP 9) Sanitize elements with on attribute */ if (!empty($element_atts['on'])) { if (substr($element_atts['on'], 0, 4) === 'tap:') { // now role & tabindex attribute is required if (empty($element_atts['tabindex'])) { $new_atts['tabindex'] = $this->tabindex ++; } if (empty($element_atts['role'])) { $new_atts['role'] = $rule['tag_name']; } } } /** * STEP 10) Sanitize Percentage Width */ if (isset($element_atts['width']) && stristr($element_atts['width'], '%')) { $new_atts['width'] = self::sanitize_dimension($element_atts['width'], 'width'); } if( $atts2remove ) { $atts2remove = array_diff($atts2remove, $white_list_atts); // Skip white list items $this->dom->remove_attributes($element, $atts2remove); // Remove invalid attributes } if ($new_atts) { $this->dom->add_attributes($element, $new_atts); // add/ update element attribute } } } } } } $body = $this->dom->get_body_node(); if( $body ) { /** * Remove all extra tags */ $extra_tags = array( 'script', 'svg', 'canvas', 'link', ); foreach( $extra_tags as $tag_name ) { $elements = $body->getElementsByTagName( $tag_name ); if( $elements->length ) { for( $i = $elements->length - 1; $i >= 0; $i-- ) { $element = $elements->item($i); if( $tag_name === 'script' && ( $element->parentNode->tagName === 'amp-analytics' || $element->parentNode->tagName === 'amp-geo' || $element->parentNode->tagName === 'amp-consent' ) ) { $atts = self::get_node_attributes( $element ); if( isset($atts['type']) && $atts['type'] === 'application/json' ) { continue; } } self::remove_element( $element ); } } } /** * Remove extra style tags and collect their contents */ $elements = $body->getElementsByTagName('style'); if ($elements->length) { for ($i = $elements->length - 1; $i >= 0; $i --) { $element = $elements->item($i); $style = preg_replace('/\s*!\s*important/', '', $element->nodeValue); // Remove !important amp_wp_add_inline_style($style); self::remove_element($element); } } /** * Sanitize Form Tag */ $elements = $body->getElementsByTagName('form'); if( $elements->length ) { amp_wp_enqueue_script('amp-form', 'https://cdn.ampproject.org/v0/amp-form-0.1.js'); $valid_target_values = array( '_blank' => true, '_top' => true, ); for( $i = $elements->length - 1; $i >= 0; $i-- ) { $action = ''; $element = $elements->item($i); $element_atts = self::get_node_attributes( $element ); if( !empty( $element_atts['action'] ) ) { $element->removeAttribute('action'); $action = $element_atts['action']; } if( !empty( $element_atts['action-xhr'] ) ) { $action = $element_atts['action-xhr']; } $action_xhr = ''; if( $action ) { $parsed_action = parse_url( $action ); if( !isset( $parsed_action['scheme'] ) && !empty( $parsed_action['path'] )) { $action_xhr = $parsed_action['path']; } elseif ( isset( $parsed_action['scheme'] ) && 'https' === $parsed_action['scheme'] ) { $action_xhr = '//' . $parsed_action['host'] . $parsed_action['path']; } elseif ( $_parsed = self::parse_internal_url( $action ) ) { if( isset( $_parsed['query'] ) ) { $action_xhr = empty($_parsed['path']) ? '/' : '//'.$_parsed['host'].$_parsed['path'].'?'.$_parsed['query']; } else { $action_xhr = empty($_parsed['path']) ? '/' : '//'.$_parsed['host'].$_parsed['path']; } } else { // invalid element - cannot detect action self::remove_element($element); continue; } // Append error handler if not found. } else { $action_xhr = add_query_arg(false, false); // relative path to current page } $action_attr_name = 'action-xhr'; if( !isset( $element_atts['method'] ) || strtolower( $element_atts['method'] ) === 'get' ) { // Swap action-xr with action on get methods $action_attr_name = 'action'; } $element->setAttribute($action_attr_name, $action_xhr); /** * Sanitize target attribute */ if ( ( isset($element_atts['target']) && !isset($valid_target_values[$element_atts['target']]) ) || !isset($element_atts['target']) ) { $element->setAttribute('target', '_top'); } } } /** * Replace audio/video tag with amp-audio/amp-video */ $replaceTags = array( 'audio' => array( 'amp-audio', 'https://cdn.ampproject.org/v0/amp-audio-0.1.js' ), 'video' => array( 'amp-video', 'https://cdn.ampproject.org/v0/amp-video-0.1.js' ) ); foreach( $replaceTags as $tag_name => $tag_info ) { $elements = $body->getElementsByTagName( $tag_name ); if( $elements->length ) { $enqueue = true; /** * @var DOMElement $element */ for( $i = $elements->length - 1; $i >= 0; $i -- ) { $element = $elements->item($i); if( $element->parentNode->tagName !== 'noscript' ) { if( !$source = $element->getAttribute( 'src' ) ) { if( !$source = Amp_WP_Html_Util::get_child_tag_attribute( $element, 'source', 'src' ) ) { $source = Amp_WP_Html_Util::get_child_tag_attribute( $element, 'a', 'href' ); } } if( empty( $source ) || !preg_match( '#^\s*https://#', $source ) ) { self::remove_element( $element ); continue; } $element->setAttribute( 'src', $source ); Amp_WP_Html_Util::renameElement( $element, $tag_info[0] ); if( $enqueue ) { amp_wp_enqueue_script( $tag_info[0], $tag_info[1] ); $enqueue = false; } } } } } } } /** * Parse URL if given URL was an internal URL * * @param string $url * * @todo Check Subdirectory * * @since 1.0.0 * @return array */ public static function parse_internal_url($url) { static $current_url_parsed; if( !$current_url_parsed ) { $current_url_parsed = parse_url( site_url() ); } $parsed_url = parse_url( $url ); if( !isset( $parsed_url['host'] ) || $parsed_url['host'] === $current_url_parsed['host'] ) { return $parsed_url; } return array(); } /** * * Get attributes of the element * * @param DOMElement $node * * @since 1.1 * * @return array key-value paired attributes */ public static function get_node_attributes($node) { $attributes = array(); foreach ($node->attributes as $attribute) { $attributes[$attribute->nodeName] = $attribute->nodeValue; } return $attributes; } /** * Sanitize element attribute value * * @see https://github.com/ampproject/amphtml/blob/master/spec/amp-html-layout.md * * @param string $layout * @param DOMElement $element * @param array $element_atts * * @since 1.0.0 */ protected function sanitize_layout_attribute($layout, $element, $element_atts) { $atts2remove = array(); $required_atts = array( 'width' => false, 'height' => false, ); switch (strtoupper($layout)) { case 'FIXED-HEIGHT': // The height attribute must be present. The width attribute must not be present or must be equal to auto. $required_atts['height'] = true; break; case 'FIXED': case 'RESPONSIVE': // The width and height attributes must be present $required_atts['width'] = true; $required_atts['height'] = true; break; case 'FILL': case 'CONTAINER': case 'FLEX-ITEM': case 'NODISPLAY': // No validation required! break; } if ( $required_atts['width'] && ( empty($element_atts['width']) || $element_atts['width'] === 'auto' ) ) { $atts2remove[] = 'layout'; } if ( $required_atts['height'] && ( empty($element_atts['height']) || $element_atts['height'] === 'auto' ) ) { $atts2remove[] = 'layout'; } if ($atts2remove) { $this->dom->remove_attributes($element, $atts2remove); // Remove invalid attributes } } /** * Collect Inline element style and print it out in