|
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/src/Optimizer/Transformer/ |
Upload File : |
<?php
/**
* Class DetermineHeroImages.
*
* @package AmpProject\AmpWP
*/
namespace AmpProject\AmpWP\Optimizer\Transformer;
use AmpProject\Dom\Document;
use AmpProject\Dom\Element;
use AmpProject\Html\Attribute;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\ImageDimensions;
use AmpProject\Optimizer\Transformer;
/**
* Determine the images to flag with data-hero-candidate so the Optimizer can prerender them.
*
* This transformer checks for the following images in the given order:
* 1. Header images (including Custom Logo and Custom Header)
* 2. Image which is descendant of first child of first entry content
*
* @package AmpProject\AmpWP
* @since 2.1
* @internal
*/
final class DetermineHeroImages implements Transformer {
/**
* XPath query to find preceding images which are not lazy-loaded.
*
* @var string
*/
const PRECEDING_NON_LAZY_IMAGE_XPATH_QUERY = "preceding::amp-img[ not( @data-hero ) ][ not( noscript/img/@loading ) or noscript/img/@loading != 'lazy' ]";
/**
* XPath query to find the first entry-content.
*
* Note that the 'entry-content' class name is the classic form for what the h-entry spec now has as 'e-content'.
* The 'amp-wp-article-content' class name is used in legacy Reader templates. Note that 'entry-content' isn't
* simply just added to templates/single.php and templates/page.php because these templates are frequently forked.
*
* @link https://microformats.org/wiki/h-entry
* @var string
*/
const FIRST_ENTRY_CONTENT_XPATH_QUERY = "
.//*[ @class ][
contains( concat( ' ', normalize-space( @class ), ' ' ), ' entry-content ' )
or
contains( concat( ' ', normalize-space( @class ), ' ' ), ' e-content ' )
or
contains( concat( ' ', normalize-space( @class ), ' ' ), ' amp-wp-article-content ' )
]
";
/**
* XPath query to find an image at the beginning of entry content (including nested inside of another block).
*
* @var string
*/
const INITIAL_CONTENT_IMAGE_XPATH_QUERY = './*[1]//amp-img[ not( @data-hero ) ]';
/**
* Apply transformations to the provided DOM document.
*
* @param Document $document DOM document to apply the
* transformations to.
* @param ErrorCollection $errors Collection of errors that are collected
* during transformation.
* @return void
*/
public function transform( Document $document, ErrorCollection $errors ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$hero_image_elements = [];
foreach ( [ 'header_images', 'initial_content_image' ] as $hero_image_source ) {
$candidate = null;
switch ( $hero_image_source ) {
case 'header_images':
$candidate = $this->get_header_images( $document );
break;
case 'initial_content_image':
$candidate = $this->get_initial_content_image( $document );
break;
}
if ( $candidate instanceof Element ) {
$hero_image_elements[ spl_object_hash( $candidate ) ] = $candidate;
} elseif ( is_array( $candidate ) ) {
foreach ( $candidate as $hero_image_element ) {
$hero_image_elements[ spl_object_hash( $hero_image_element ) ] = $hero_image_element;
}
}
}
$this->add_data_hero_candidate_attribute(
array_values( $hero_image_elements )
);
}
/**
* Retrieve the images in the header.
*
* This returns all non-tiny images which occur before the main content, or else a tiny image that has `logo` in the
* class name.
*
* Note that the `HeroCandidateFiltering` service will have already identified the Header Image and Custom Logo for
* any theme using the standard `the_header_image_tag()` and `the_custom_logo()` template tags, respectively. This
* remains for here as a fallback to identify Header Images and Custom Logos being rendered via non-standard
* template tags.
*
* @see \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::add_custom_logo_data_hero_candidate_attribute()
* @see \AmpProject\AmpWP\Optimizer\HeroCandidateFiltering::filter_header_image_tag()
*
* @param Document $document Document to retrieve the header images from.
* @return Element[] Header images.
*/
private function get_header_images( Document $document ) {
// Note that 3,508 out of 3,923 themes on WP.org (89%) use the <main> element.
$after_header_element = $document->getElementsByTagName( 'main' )->item( 0 );
// If a theme happens to not use the <main> element, then fall back to using the first entry-content.
if ( ! $after_header_element instanceof Element ) {
$after_header_element = $this->get_first_entry_content( $document );
}
if ( ! $after_header_element instanceof Element ) {
return [];
}
$query = $document->xpath->query(
self::PRECEDING_NON_LAZY_IMAGE_XPATH_QUERY,
$after_header_element
);
return array_filter(
iterator_to_array( $query ),
static function ( Element $element ) {
// A custom logo may in fact be tiny and yet since it is in the header it should be prerendered.
// Note that a theme may not be using `the_custom_logo()` template tag and that is why the `custom-logo`
// class is not being checked for specifically.
if (
$element->hasAttribute( Attribute::CLASS_ )
&&
false !== strpos( $element->getAttribute( Attribute::CLASS_ ), 'logo' )
) {
return true;
}
return ! ( new ImageDimensions( $element ) )->isTiny();
}
);
}
/**
* Retrieve the first entry content.
*
* @param Document $document Document to retrieve the first entry content from.
* @return Element|null First entry content element.
*/
private function get_first_entry_content( Document $document ) {
$query = $document->xpath->query(
self::FIRST_ENTRY_CONTENT_XPATH_QUERY,
$document->body
);
$entry_content = $query->item( 0 );
return $entry_content instanceof Element ? $entry_content : null;
}
/**
* Retrieve the first image that is in the first position in content.
*
* @param Document $document Document to retrieve the image from.
* @return Element|null Image at the beginning of the first entry content.
*/
private function get_initial_content_image( Document $document ) {
$entry_content = $this->get_first_entry_content( $document );
if ( ! $entry_content instanceof Element ) {
return null;
}
$query = $document->xpath->query(
self::INITIAL_CONTENT_IMAGE_XPATH_QUERY,
$entry_content
);
$image = $query->item( 0 );
if ( $image instanceof Element && ! ( new ImageDimensions( $image ) )->isTiny() ) {
return $image;
}
return null;
}
/**
* Add the data-hero attribute to viable hero images.
*
* @param Element[] $hero_image_elements Elements that are viable hero images.
*/
private function add_data_hero_candidate_attribute( $hero_image_elements ) {
foreach ( $hero_image_elements as $hero_image_element ) {
$hero_image_element->setAttribute( Attribute::DATA_HERO_CANDIDATE, null );
}
}
}