|
Server : LiteSpeed System : Linux host 5.15.0-161-generic #171-Ubuntu SMP Sat Oct 11 08:17:01 UTC 2025 x86_64 User : idnco5810 ( 1093) PHP Version : 8.2.29 Disable Function : NONE Directory : /home/idn98.co/public_html/wp-content/plugins/amp/includes/sanitizers/ |
Upload File : |
<?php
/**
* Class AMP_Bento_Sanitizer.
*
* @package AMP
*/
use AmpProject\AmpWP\ValidationExemption;
use AmpProject\CssLength;
use AmpProject\Dom\Element;
use AmpProject\Html\Attribute;
use AmpProject\Html\LengthUnit;
use AmpProject\Layout;
/**
* Convert all bento-prefixed components into amp-prefixed components, or else mark them as PX-verified if they have no
* AMP versions. Remove Bento stylesheets and scripts if they aren't needed.
*
* @since 2.2
* @deprecated 2.5.0 Bento support have been removed from amphtml.
* @internal
*/
class AMP_Bento_Sanitizer extends AMP_Base_Sanitizer {
/** @var string */
const XPATH_BENTO_ELEMENTS_QUERY = './/*[ starts-with( name(), "bento-" ) ]';
/**
* Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
*
* @return array Mapping.
*/
public function get_selector_conversion_mapping() {
$mapping = [];
foreach ( AMP_Allowed_Tags_Generated::get_extension_specs() as $amp_extension_name => $extension_spec ) {
if ( empty( $extension_spec['bento'] ) ) {
continue;
}
$bento_extension_name = str_replace( 'amp-', 'bento-', $amp_extension_name );
if ( $bento_extension_name !== $amp_extension_name ) {
$mapping[ $bento_extension_name ] = [ $amp_extension_name ];
}
}
return $mapping;
}
/**
* Indicate that the selector conversion mappings do not involve light shadow DOM.
*
* For example, with `bento-base-carousel`, the descendant `h2` elements will be present in the document initially.
* So a selector like `bento-base-carousel h2` will not have issues with tree shaking when it is converted into
* `amp-base-carousel h2`. Additionally, Bento components by definition use the _actual_ real shadow DOM, so if
* there were a selector like `bento-foo div` then the `div` would never match an element since is beyond the
* shadow boundary, and tree shaking should be free to remove such a selector. Selectors that are targeting
* slotted elements are not inside the shadow DOM, so for example `bento-base-carousel img` will target an actual
* element in the initial DOM, even though `bento-base-carousel` has other elements that are beyond the shadow
* DOM boundary.
*
* @return false
*/
public function has_light_shadow_dom() {
return false;
}
/**
* Sanitize.
*/
public function sanitize() {
$bento_elements = $this->dom->xpath->query( self::XPATH_BENTO_ELEMENTS_QUERY, $this->dom->body );
$bento_elements_discovered = [];
$bento_elements_converted = [];
$extension_specs = AMP_Allowed_Tags_Generated::get_extension_specs();
foreach ( $bento_elements as $bento_element ) {
/** @var Element $bento_element */
$bento_name = $bento_element->tagName;
$amp_name = str_replace( 'bento-', 'amp-', $bento_name );
$bento_elements_discovered[ $bento_name ] = true;
// Skip Bento components which aren't valid (yet).
if ( ! array_key_exists( $amp_name, $extension_specs ) ) {
ValidationExemption::mark_node_as_px_verified( $bento_element );
continue;
}
$amp_element = $this->dom->createElement( $amp_name );
while ( $bento_element->attributes->length ) {
/** @var DOMAttr $attribute */
$attribute = $bento_element->attributes->item( 0 );
// Essential for unique attributes like ID, or else PHP DOM will keep it referencing the old element.
$bento_element->removeAttributeNode( $attribute );
$amp_element->setAttributeNode( $attribute );
}
while ( $bento_element->firstChild instanceof DOMNode ) {
$amp_element->appendChild( $bento_element->removeChild( $bento_element->firstChild ) );
}
$this->adapt_layout_styles( $amp_element );
$bento_element->parentNode->replaceChild( $amp_element, $bento_element );
$bento_elements_converted[ $bento_name ] = true;
}
// Remove the Bento external stylesheets which are no longer necessary. For the others, mark as PX-verified.
$links = $this->dom->xpath->query(
'//link[ @rel = "stylesheet" and starts-with( @href, "https://cdn.ampproject.org/v0/bento-" ) ]'
);
foreach ( $links as $link ) {
/** @var Element $link */
$bento_name = $this->get_bento_component_name_from_url( $link->getAttribute( Attribute::HREF ) );
if ( ! $bento_name ) {
continue;
}
if (
// If the Bento element doesn't exist in the page, remove the extraneous stylesheet.
! array_key_exists( $bento_name, $bento_elements_discovered )
||
// If the Bento element was converted to AMP, then remove the now-unnecessary stylesheet.
array_key_exists( $bento_name, $bento_elements_converted )
) {
$link->parentNode->removeChild( $link );
} else {
ValidationExemption::mark_node_as_px_verified( $link );
ValidationExemption::mark_node_as_px_verified( $link->getAttributeNode( Attribute::HREF ) );
}
}
// Keep track of the number of Bento scripts we kept, as then we'll need to make sure we keep the Bento runtime script.
$non_amp_scripts_retained = 0;
// Handle Bento scripts.
$scripts = $this->dom->xpath->query(
'//script[ starts-with( @src, "https://cdn.ampproject.org/v0/bento" ) ]'
);
foreach ( $scripts as $script ) {
/** @var Element $script */
$bento_name = $this->get_bento_component_name_from_url( $script->getAttribute( Attribute::SRC ) );
if ( ! $bento_name ) {
continue;
}
if (
// If the Bento element doesn't exist in the page, remove the extraneous script.
! array_key_exists( $bento_name, $bento_elements_discovered )
||
// If the Bento element was converted to AMP, then remove the now-unnecessary script.
array_key_exists( $bento_name, $bento_elements_converted )
) {
$script->parentNode->removeChild( $script );
} else {
ValidationExemption::mark_node_as_px_verified( $script );
$non_amp_scripts_retained++;
}
}
// Remove the Bento runtime script if it is not needed, or else mark it as PX-verified.
$bento_runtime_scripts = $this->dom->xpath->query(
'//script[ @src = "https://cdn.ampproject.org/bento.mjs" or @src = "https://cdn.ampproject.org/bento.js" ]'
);
if ( 0 === $non_amp_scripts_retained ) {
foreach ( $bento_runtime_scripts as $bento_runtime_script ) {
$bento_runtime_script->parentNode->removeChild( $bento_runtime_script );
}
} else {
foreach ( $bento_runtime_scripts as $bento_runtime_script ) {
ValidationExemption::mark_node_as_px_verified( $bento_runtime_script );
}
}
}
/**
* Parse Bento component name from a Bento CDN URL.
*
* @param string $url URL for script or stylesheet.
* @return string|null Bento component name or null if no match was made.
*/
private function get_bento_component_name_from_url( $url ) {
$path = wp_parse_url( $url, PHP_URL_PATH );
if ( $path && preg_match( '#^(bento-.*?)-\d+\.\d+\.(m?js|css)#', basename( $path ), $matches ) ) {
return $matches[1];
} else {
return null;
}
}
/**
* Adapt inline styles from Bento element to AMP layout attributes.
*
* This will try its best to convert `width`, `height`, and `aspect-ratio` inline styles over to their corresponding
* AMP layout attributes. In order for a Bento component to be AMP-compatible, it needs to utilize inline styles
* for its dimensions rather than rely on a stylesheet rule.
*
* @param Element $amp_element AMP element (converted from Bento).
*/
private function adapt_layout_styles( Element $amp_element ) {
$supported_layouts = [];
foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( $amp_element->tagName ) as $rule_spec ) {
if ( isset( $rule_spec['tag_spec']['amp_layout']['supported_layouts'] ) ) {
$supported_layouts = array_merge( $supported_layouts, $rule_spec['tag_spec']['amp_layout']['supported_layouts'] );
}
}
$supported_layouts = array_unique( $supported_layouts );
// If no layouts are supported, then there is nothing to do.
if ( count( $supported_layouts ) === 0 ) {
return;
}
// If nodisplay is the only supported layout or nodisplay is supported and
// the element is hidden, then give it the nodisplay layout.
if (
[ Layout::TO_SPEC[ Layout::NODISPLAY ] ] === $supported_layouts
||
(
$amp_element->hasAttribute( Attribute::HIDDEN )
&&
in_array( Layout::TO_SPEC[ Layout::NODISPLAY ], $supported_layouts, true )
)
) {
$amp_element->setAttribute( Attribute::LAYOUT, Layout::NODISPLAY );
$amp_element->removeAttribute( Attribute::HIDDEN );
return;
}
// Since Bento elements don't support width/height attributes (currently),
// obtain the inline styles or else abort if none are provided.
$style_string = $amp_element->getAttribute( Attribute::STYLE );
if ( ! $style_string ) {
return;
}
// Re-use set_layout method to detect fill layout (only).
if ( in_array( Layout::TO_SPEC[ Layout::FILL ], $supported_layouts, true ) ) {
$attributes = $this->set_layout( [ Attribute::STYLE => $style_string ] );
if ( isset( $attributes[ Attribute::LAYOUT ] ) && Layout::FILL === $attributes[ Attribute::LAYOUT ] ) {
$amp_element->setAttributes( $attributes );
if ( ! array_key_exists( Attribute::STYLE, $attributes ) ) {
$amp_element->removeAttribute( Attribute::STYLE );
}
return;
}
}
// If there are no layouts that support width and height, then abort.
if (
0 === count(
array_intersect(
$supported_layouts,
[
Layout::TO_SPEC[ Layout::FIXED ],
Layout::TO_SPEC[ Layout::FIXED_HEIGHT ],
Layout::TO_SPEC[ Layout::INTRINSIC ],
Layout::TO_SPEC[ Layout::RESPONSIVE ],
]
)
)
) {
return;
}
$styles = $this->parse_style_string( $style_string );
$layout_attributes = [];
// Obtain the height.
if ( isset( $styles[ Attribute::HEIGHT ] ) ) {
$height = new CssLength( $styles[ Attribute::HEIGHT ] );
$height->validate( false, false );
if ( $height->isValid() ) {
$layout_attributes[ Attribute::HEIGHT ] = $height->getNumeral() . ( $height->getUnit() !== LengthUnit::PX ? $height->getUnit() : '' );
unset( $styles[ Attribute::HEIGHT ] );
}
}
// Obtain the width.
if (
in_array( Layout::TO_SPEC[ Layout::FIXED_HEIGHT ], $supported_layouts, true )
&&
( ! isset( $styles[ Attribute::WIDTH ] ) || '100%' === $styles[ Attribute::WIDTH ] )
) {
$layout_attributes[ Attribute::WIDTH ] = CssLength::AUTO;
$layout_attributes[ Attribute::LAYOUT ] = Layout::FIXED_HEIGHT;
unset( $styles[ Attribute::WIDTH ] );
} elseif ( isset( $styles[ Attribute::WIDTH ] ) ) {
$width = new CssLength( $styles[ Attribute::WIDTH ] );
$width->validate( false, false );
if ( $width->isValid() ) {
$layout_attributes[ Attribute::WIDTH ] = $width->getNumeral() . ( $width->getUnit() !== LengthUnit::PX ? $width->getUnit() : '' );
unset( $styles[ Attribute::WIDTH ] );
}
}
$supports_responsive = in_array( Layout::TO_SPEC[ Layout::RESPONSIVE ], $supported_layouts, true );
$supports_intrinsic = in_array( Layout::TO_SPEC[ Layout::INTRINSIC ], $supported_layouts, true );
if (
isset( $styles[ Attribute::ASPECT_RATIO ] )
&&
( $supports_responsive || $supports_intrinsic )
&&
preg_match( '#(?P<width>\d+(?:.\d+)?)(?:\s*/\s*(?P<height>\d+(?:.\d+)?))?#', $styles[ Attribute::ASPECT_RATIO ], $matches )
) {
$height = isset( $matches[ Attribute::HEIGHT ] ) ? (float) $matches[ Attribute::HEIGHT ] : 1.0;
$width = (float) $matches[ Attribute::WIDTH ];
// Derive intrinsic width and height when max-width is supplied.
$intrinsic_width = null;
if ( isset( $styles[ Attribute::MAX_WIDTH ] ) ) {
$intrinsic_width = new CssLength( $styles[ Attribute::MAX_WIDTH ] );
$intrinsic_width->validate( false, false );
if ( $intrinsic_width->isValid() && $intrinsic_width->getUnit() === LengthUnit::PX ) {
unset( $styles[ Attribute::MAX_WIDTH ] );
} else {
$intrinsic_width = null;
}
}
if ( $intrinsic_width ) {
$height = ( $height / $width ) * $intrinsic_width->getNumeral();
$width = $intrinsic_width->getNumeral();
} else {
$supports_intrinsic = false;
}
$layout_attributes[ Attribute::LAYOUT ] = $supports_intrinsic ? Layout::INTRINSIC : Layout::RESPONSIVE;
$layout_attributes[ Attribute::HEIGHT ] = (string) $height;
$layout_attributes[ Attribute::WIDTH ] = (string) $width;
unset( $styles[ Attribute::ASPECT_RATIO ] );
}
if ( $layout_attributes ) {
$amp_element->setAttribute( Attribute::STYLE, $this->reassemble_style_string( $styles ) );
$amp_element->setAttributes( $layout_attributes );
}
}
}