PATH:
home
/
letacommog
/
slution01
/
wp-content
/
plugins
/
tiny-compress-images
/
src
<?php /* * Tiny Compress Images - WordPress plugin. * Copyright (C) 2015-2018 Tinify B.V. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * Class responsible for parsing and modifying html to insert picture elements. * * 1) searches for <picture> elements or <img> elements * 2) checks wether existing source has a modern alternative * 3) augments or creates a picture element * 4) replaces the original source with the source which includes the modern format */ class Tiny_Picture extends Tiny_WP_Base { /** @var string */ private $base_dir; /** @var array */ private $allowed_domains = array(); /** * Initialize the plugin. * * @param string $base_dir Absolute path (e.g. ABSPATH) * @param array $domains List of allowed domain URLs */ function __construct( $base_dir = ABSPATH, $domains = array() ) { $this->base_dir = $base_dir; $this->allowed_domains = $domains; if ( is_admin() || is_customize_preview() ) { return; } if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return; } if ( defined( 'DOING_CRON' ) && DOING_CRON ) { return; } if ( Tiny_Helpers::is_pagebuilder_request() ) { return; } add_action('template_redirect', function () { ob_start( array( $this, 'replace_sources' ), 1000 ); }); } public function replace_sources( $content ) { $content = $this->replace_picture_sources( $content ); $content = $this->replace_img_sources( $content ); return $content; } /** * Will extend existing picture elements with additional sourcesets * * @param string $content * @return string the new source html */ private function replace_picture_sources( $content ) { $picture_sources = $this->filter_pictures( $content ); foreach ( $picture_sources as $picture_source ) { $content = $this->replace_picture( $content, $picture_source ); } return $content; } private function replace_img_sources( $content ) { $image_sources = $this->filter_images( $content ); foreach ( $image_sources as $image_source ) { $content = Tiny_Picture::replace_image( $content, $image_source ); } return $content; } /** * Will search for all picture elements within the given source html * * @param string $content * @return array<Tiny_Picture_Source> an array of picture element sources */ private function filter_pictures( $content ) { $matches = array(); if ( ! preg_match_all( '#<picture\b[^>]*>.*?<\/picture>#is', $content, $matches ) ) { return array(); } $pictures = array(); foreach ( $matches[0] as $raw_picture ) { $pictures[] = new Tiny_Picture_Source( $raw_picture, $this->base_dir, $this->allowed_domains ); } return $pictures; } /** * Will add additional sourcesets to picture elements. * * @param string $content the full page content * @param Tiny_Picture_Source $source the picture element * * @return string the updated content including augmented picture elements */ private function replace_picture( $content, $source ) { $content = str_replace( $source->raw_html, $source->augment_picture_element(), $content ); return $content; } /** * Will replace img elements with picture elements that (possibly) have additional formats. * * @param string $content the full page content * @param Tiny_Image_Source $source the picture element * * @return string the updated content including augmented picture elements */ private static function replace_image( $content, $source ) { $content = str_replace( $source->raw_html, $source->create_picture_elements(), $content ); return $content; } /** * Filters out all images from the content and returns them as an array. * * @return Tiny_Image[] */ private function filter_images( $content ) { // Extract only the <body>...</body> section. if ( preg_match( '/(?=<body).*<\/body>/is', $content, $body ) ) { $content = $body[0]; } // strip HTML comments. $content = preg_replace( '/<!--(.*)-->/Uis', '', $content ); // strip existing <picture> blocks to avoid double-processing. $content = preg_replace( '/<picture\b.*?>.*?<\/picture>/is', '', $content ); // Strip <noscript> blocks to avoid altering their contents. $content = preg_replace( '/<noscript\b.*?>.*?<\/noscript>/is', '', $content ); // Find all <img> tags with any attributes. if ( ! preg_match_all( '/<img\b[^>]*>/is', $content, $matches ) ) { return array(); } $images = array(); foreach ( $matches[0] as $img ) { $images[] = new Tiny_Image_Source( $img, $this->base_dir, $this->allowed_domains ); } return $images; } } abstract class Tiny_Source_Base { public $raw_html; protected $base_dir; protected $allowed_domains; protected $valid_mimetypes; public function __construct( $html, $base_dir, $domains ) { $this->raw_html = $html; $this->base_dir = $base_dir; $this->allowed_domains = $domains; $this->valid_mimetypes = array( 'image/avif', 'image/webp' ); } protected static function get_attribute_value( $element, $name ) { // Match the exact attribute name (not part of data-media, mediaType, etc.) // and capture a single- or double-quoted value. $delim = '~'; $attr = preg_quote( $name, $delim ); $regex = $delim . '(?<![\w:-])' . $attr . '\s*=\s*(["\'])(.*?)\1' . $delim . 'is'; if ( preg_match( $regex, $element, $m ) ) { return $m[2]; } return null; } /** * Extract elements by tag name from an HTML string (regex-based). * * @param string $html The HTML string to search in. * @param string $tagname The tag name (e.g., 'div', 'source', 'img'). * @return array Array of matched elements as strings. */ protected function get_element_by_tag( $html, $tagname ) { $results = []; // Self-closing / void tag (e.g. <source />, <img />, <br />) if ( preg_match_all( '~<' . preg_quote( $tagname, '~' ) . '\b(?:[^>"\']+|"[^"]*"|\'[^\']*\')*/?>~i', $html, $matches ) ) { $results = array_merge( $results, $matches[0] ); } // Normal paired tags (e.g. <div>…</div>) $regex_tag = preg_quote( $tagname, '~' ); if ( preg_match_all( '~<' . $regex_tag . '\b(?:[^>"\']+|"[^"]*"|\'[^\']*\')*>.*?</' . $regex_tag . '>~is', $html, $matches ) ) { $results = array_merge( $results, $matches[0] ); } return $results; } protected function get_local_path( $url ) { if ( strpos( $url, 'http' ) === 0 ) { $matched_domain = null; foreach ( $this->allowed_domains as $domain ) { if ( strpos( $url, $domain ) === 0 ) { $matched_domain = $domain; break; } } if ( null === $matched_domain ) { return ''; } $url = substr( $url, strlen( $matched_domain ) ); } $url = $this->base_dir . $url; return $url; } protected function get_formatted_source( $image_source_data, $mimetype ) { $format_url = Tiny_Helpers::replace_file_extension( $mimetype, $image_source_data['path'] ); $local_path = $this->get_local_path( $format_url ); if ( empty( $local_path ) ) { return null; } $exists_local = file_exists( $local_path ); if ( $exists_local ) { return array( 'src' => $format_url, 'size' => $image_source_data['size'], 'type' => $mimetype, ); } return null; } /** * Retrieves the sources from the <img> or <source> element * * @return array{path: string, size: string}[] The image sources */ protected function get_image_srcsets( $html ) { $result = array(); $srcset = $this::get_attribute_value( $html, 'srcset' ); if ( $srcset ) { // Split the srcset to get individual entries $srcset_entries = explode( ',', $srcset ); foreach ( $srcset_entries as $entry ) { // Trim whitespace $entry = trim( $entry ); // Split by whitespace to separate path and size/density descriptor $parts = preg_split( '/\s+/', $entry, 2 ); if ( count( $parts ) === 2 ) { // We have both path and size $result[] = array( 'path' => $parts[0], 'size' => $parts[1], ); } elseif ( count( $parts ) === 1 ) { // We only have a path, will be interpreted as pixel // density 1x (unusual in srcset) $result[] = array( 'path' => $parts[0], 'size' => '', ); } } } return $result; } /** * Retrieves the sources from the <img> or <source> element * * @return array{path: string, size: string}[] The image sources */ private function get_image_src( $html ) { $source = $this::get_attribute_value( $html, 'src' ); if ( ! empty( $source ) ) { // No srcset, but we have a src attribute return array( 'path' => $source, 'size' => '', ); } return array(); } /** * Creates one or more <source> elements if alternative formats * are available. * * @param string $original_source_html, either <source> or <img> * @return array{string} array of <source> html */ protected function create_alternative_sources( $original_source_html ) { $srcsets = $this->get_image_srcsets( $original_source_html ); if ( empty( $srcsets ) ) { // no srcset, try src attribute $srcsets[] = $this->get_image_src( $original_source_html ); } if ( empty( $srcsets ) ) { return array(); } $is_source_tag = (bool) preg_match( '#<source\b#i', $original_source_html ); $sources = array(); $width_descriptor = $this->get_largest_width_descriptor( $srcsets ); foreach ( $this->valid_mimetypes as $mimetype ) { $srcset_parts = array(); foreach ( $srcsets as $srcset ) { $alt_source = $this->get_formatted_source( $srcset, $mimetype ); if ( $alt_source ) { $srcset_parts[] = trim( $alt_source['src'] . ' ' . $alt_source['size'] ); } } if ( $width_descriptor && ! self::srcset_contains_width_descriptor( $srcset_parts, $width_descriptor ) ) { continue; } if ( empty( $srcset_parts ) ) { continue; } $source_attr_parts = array(); $srcset_attr = implode( ', ', $srcset_parts ); $source_attr_parts['srcset'] = $srcset_attr; if ( $is_source_tag ) { foreach ( array( 'sizes', 'media', 'width', 'height' ) as $attr ) { $attr_value = $this->get_attribute_value( $original_source_html, $attr ); if ( $attr_value ) { $source_attr_parts[ $attr ] = $attr_value; } } } $source_attr_parts['type'] = $mimetype; $source_parts = array( '<source' ); foreach ( $source_attr_parts as $source_attr_name => $source_attr_val ) { $source_parts[] = $source_attr_name . '="' . $source_attr_val . '"'; } $source_parts[] = '/>'; $sources[] = implode( ' ', $source_parts ); } // End foreach(). return $sources; } /** * Returns the largest numeric width descriptor * (e.g. 2000 from "2000w") found in the srcset data. * * @param array<array{path: string, size: string}> $srcsets * @return int */ public static function get_largest_width_descriptor( $srcsets ) { $largest = 0; foreach ( $srcsets as $srcset ) { if ( empty( $srcset['size'] ) ) { continue; } if ( preg_match( '/(\d+)w/', $srcset['size'], $matches ) ) { $width = (int) $matches[1]; if ( $width > $largest ) { $largest = $width; } } } return $largest; } /** * Determines whether a srcset list contains the provided width descriptor. * * @param string[] $srcset_parts * @param int $width_descriptor * @return bool true if width is in srcset */ public static function srcset_contains_width_descriptor( $srcset_parts, $width_descriptor ) { if ( empty( $srcset_parts ) || $width_descriptor <= 0 ) { return false; } $suffix = ' ' . $width_descriptor . 'w'; $suffix_length = strlen( $suffix ); foreach ( $srcset_parts as $srcset_part ) { if ( substr( $srcset_part, -$suffix_length ) === $suffix ) { return true; } } return false; } } class Tiny_Picture_Source extends Tiny_Source_Base { /** * Adds alternative format sources (e.g., image/webp, image/avif) to an existing * <picture> element based on locally available converted files. * * @return string The augmented <picture> HTML or the original if no additions. */ public function augment_picture_element() { $modified_sources = array(); foreach ( $this->get_element_by_tag( $this->raw_html, 'source' ) as $source_tag_html ) { $type_attr = self::get_attribute_value( $source_tag_html, 'type' ); $type_attr = null !== $type_attr ? strtolower( trim( $type_attr ) ) : ''; // Skip if already optimized. if ( '' !== $type_attr && in_array( $type_attr, $this->valid_mimetypes, true ) ) { continue; } $alternative_sources = $this->create_alternative_sources( $source_tag_html ); if ( is_array( $alternative_sources ) && $alternative_sources ) { foreach ( $alternative_sources as $alt ) { $modified_sources[] = $alt; // no array_merge in the loop } } } // handle inner image foreach ( $this->get_element_by_tag( $this->raw_html, 'img' ) as $img_tag_html ) { $alt_image_source = $this->create_alternative_sources( $img_tag_html ); $modified_sources = array_merge( $modified_sources, $alt_image_source ); } $modified_source = implode( '', $modified_sources ); // Insert newly built <source> elements immediately before the first <img> return preg_replace( '#(<img\b)#i', $modified_source . '$1', $this->raw_html, 1 ); } } class Tiny_Image_Source extends Tiny_Source_Base { /** * Generates a formatted image source array if the corresponding local file exists. * * Attempts to replace the file extension of the provided image path with the * specified MIME type, resolves the local path of the resulting file, and returns * the `srcset` and `type` if the file exists. * * @return string a <picture> element contain additional sources */ public function create_picture_elements() { $sources = $this->create_alternative_sources( $this->raw_html ); if ( empty( $sources ) ) { return $this->raw_html; } $picture_element = array( '<picture>' ); $picture_element[] = implode( '', $sources ); $picture_element[] = $this->raw_html; $picture_element[] = '</picture>'; return implode( '', $picture_element ); } }
[+]
..
[+]
images
[+]
config
[-] class-tiny-wp-base.php
[edit]
[-] class-tiny-image-size.php
[edit]
[-] class-tiny-picture.php
[edit]
[+]
views
[+]
js
[+]
vendor
[-] class-tiny-settings.php
[edit]
[-] class-tiny-compress.php
[edit]
[-] class-tiny-compress-fopen.php
[edit]
[-] class-tiny-cli.php
[edit]
[-] class-tiny-notices.php
[edit]
[+]
css
[+]
compatibility
[-] class-tiny-image.php
[edit]
[+]
data
[-] class-tiny-php.php
[edit]
[-] class-tiny-compress-client.php
[edit]
[-] class-tiny-exception.php
[edit]
[-] class-tiny-plugin.php
[edit]
[-] class-tiny-helpers.php
[edit]
[-] class-tiny-bulk-optimization.php
[edit]