File Manager
<?php
declare(strict_types=1);
namespace Imagify\Picture;
use Imagify\EventManagement\SubscriberInterface;
use Imagify_Filesystem;
/**
* Display Next-gen images on the site with <picture> tags.
*
* @since 1.9
*/
class Display implements SubscriberInterface {
/**
* Option value.
*
* @var string
*/
const OPTION_VALUE = 'picture';
/**
* Filesystem object.
*
* @var Imagify_Filesystem
*/
protected $filesystem;
/**
* Constructor.
*
* @param Imagify_Filesystem $filesystem Filesystem instance.
*/
public function __construct( Imagify_Filesystem $filesystem ) {
$this->filesystem = $filesystem;
}
/**
* Array of events this subscriber listens to
*
* @return array
*/
public static function get_subscribed_events() {
return [
'template_redirect' => 'start_content_process',
'imagify_process_webp_content' => 'process_content',
];
}
/** ----------------------------------------------------------------------------------------- */
/** ADD <PICTURE> TAGS TO THE PAGE ========================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Start buffering the page content.
*
* @since 1.9
*
* @return void
*/
public function start_content_process() {
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
$allow = apply_filters_deprecated( 'imagify_allow_picture_tags_for_webp', [ true ], '2.2', 'imagify_allow_picture_tags_for_nextgen' );
/**
* Prevent the replacement of <img> tags into <picture> tags.
*
* @since 1.9
*
* @param bool $allow True to allow the use of <picture> tags (default). False to prevent their use.
*/
$allow = apply_filters( 'imagify_allow_picture_tags_for_nextgen', true );
if ( ! $allow ) {
return;
}
ob_start( [ $this, 'maybe_process_buffer' ] );
}
/**
* Maybe process the page content.
*
* @since 1.9
*
* @param string $buffer The buffer content.
*
* @return string
*/
public function maybe_process_buffer( $buffer ) {
if ( ! $this->is_html( $buffer ) ) {
return $buffer;
}
if ( strlen( $buffer ) <= 255 ) {
// Buffer length must be > 255 (IE does not read pages under 255 c).
return $buffer;
}
$buffer = $this->process_content( $buffer );
/**
* Filter the page content after Imagify.
*
* @since 1.9
*
* @param string $buffer The page content.
*/
$buffer = (string) apply_filters( 'imagify_buffer', $buffer );
return $buffer;
}
/**
* Process the content.
*
* @since 1.9
*
* @param string $content The content.
*
* @return string
*/
public function process_content( $content ) {
$html_no_picture_tags = $this->remove_picture_tags( $content );
$images = $this->get_images( $html_no_picture_tags );
if ( ! $images ) {
return $content;
}
foreach ( $images as $image ) {
$tag = $this->build_picture_tag( $image );
$content = str_replace( $image['tag'], $tag, $content );
}
return $content;
}
/**
* Remove pre-existing <picture> tags.
*
* We shouldn't replace images already nested inside picture tags
* that are already in the page.
*
* @since 1.10.0
*
* @param string $html Content of the page.
*
* @return string HTML content without pre-existing <picture> tags.
*/
private function remove_picture_tags( $html ) {
$replace = preg_replace( '#<picture[^>]*>.*?<\/picture\s*>#mis', '', $html );
if ( null === $replace ) {
return $html;
}
return $replace;
}
/** ----------------------------------------------------------------------------------------- */
/** BUILD HTML TAGS AND ATTRIBUTES ========================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Build the <picture> tag to insert.
*
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
*
* @return string A <picture> tag.
*/
protected function build_picture_tag( $image ) {
$to_remove = [
'alt' => '',
'height' => '',
'width' => '',
'data-lazy-src' => '',
'data-src' => '',
'src' => '',
'data-lazy-srcset' => '',
'data-srcset' => '',
'srcset' => '',
'data-lazy-sizes' => '',
'data-sizes' => '',
'sizes' => '',
];
$attributes = array_diff_key( $image['attributes'], $to_remove );
/**
* Filter the attributes to be added to the <picture> tag.
*
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <picture> tag.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
*/
$attributes = apply_filters( 'imagify_picture_attributes', $attributes, $image );
/**
* Remove Gutenberg specific attributes from picture tag, leave them on img tag.
* Optional: $attributes['class'] = 'imagify-webp-cover-wrapper'; for website admin styling ease.
*/
if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) {
unset( $attributes['style'] );
unset( $attributes['class'] );
unset( $attributes['data-object-fit'] );
unset( $attributes['data-object-position'] );
}
$output = '<picture' . $this->build_attributes( $attributes ) . ">\n";
/**
* Allow to add more <source> tags to the <picture> tag.
*
* @since 1.9
*
* @param string $more_source_tags Additional <source> tags.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
*/
$output .= apply_filters( 'imagify_additional_source_tags', '', $image );
$output .= $this->build_source_tag( $image );
$output .= $this->build_img_tag( $image );
$output .= "</picture>\n";
return $output;
}
/**
* Build the <source> tag to insert in the <picture>.
*
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
*
* @return string A <source> tag.
*/
protected function build_source_tag( $image ) {
$source = '';
foreach ( [ 'avif', 'webp' ] as $image_type ) {
$attributes = $this->build_source_attributes( $image, $image_type );
if ( empty( $attributes ) ) {
continue;
}
$source .= '<source' . $this->build_attributes( $attributes ) . "/>\n";
}
return $source;
}
/**
* Build the attribute for the source tag.
*
* @param array $image An array of data.
* @param string $image_type Type of image.
*
* @return array
*/
protected function build_source_attributes( array $image, string $image_type ): array {
$mime_type = '';
$url = '';
switch ( $image_type ) {
case 'webp':
$mime_type = 'image/webp';
$url = 'webp_url';
break;
case 'avif':
$mime_type = 'image/avif';
$url = 'avif_url';
break;
}
$srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set';
$attributes = [
'type' => $mime_type,
$srcset_source => [],
];
if ( ! empty( $image['srcset'] ) ) {
foreach ( $image['srcset'] as $srcset ) {
if ( empty( $srcset[ $url ] ) ) {
continue;
}
$attributes[ $srcset_source ][] = $srcset[ $url ] . ' ' . $srcset['descriptor'];
}
}
if ( empty( $attributes[ $srcset_source ] ) && empty( $image['src'][ $url ] ) ) {
return [];
}
if ( empty( $attributes[ $srcset_source ] ) ) {
$attributes[ $srcset_source ][] = $image['src'][ $url ];
}
$attributes[ $srcset_source ] = implode( ', ', $attributes[ $srcset_source ] );
foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
if ( ! empty( $image['attributes'][ $srcset_attr ] ) && $srcset_attr !== $srcset_source ) {
$attributes[ $srcset_attr ] = $image['attributes'][ $srcset_attr ];
}
}
if ( 'srcset' !== $srcset_source && empty( $attributes['srcset'] ) && ! empty( $image['attributes']['src'] ) ) {
// Lazyload: the "src" attr should contain a placeholder (a data image or a blank.gif ).
$attributes['srcset'] = $image['attributes']['src'];
}
foreach ( [ 'data-lazy-sizes', 'data-sizes', 'sizes' ] as $sizes_attr ) {
if ( ! empty( $image['attributes'][ $sizes_attr ] ) ) {
$attributes[ $sizes_attr ] = $image['attributes'][ $sizes_attr ];
}
}
/**
* Filter the attributes to be added to the <source> tag.
*
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <source> tag.
* @param array $data Data built from the original <img> tag. See $this->process_image().
*/
$attributes = apply_filters( 'imagify_picture_source_attributes', $attributes, $image );
return $attributes;
}
/**
* Build the <img> tag to insert in the <picture>.
*
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
*
* @return string A <img> tag.
*/
protected function build_img_tag( $image ) {
/**
* Gutenberg fix.
* Check for the 'wp-block-cover__image-background' class on the original image, and leave that class and style attributes if found.
*/
if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) {
$to_remove = [
'id' => '',
'title' => '',
];
$attributes = array_diff_key( $image['attributes'], $to_remove );
} else {
$to_remove = [
'class' => '',
'id' => '',
'style' => '',
'title' => '',
];
$attributes = array_diff_key( $image['attributes'], $to_remove );
}
/**
* Filter the attributes to be added to the <img> tag.
*
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <img> tag.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
*/
$attributes = apply_filters( 'imagify_picture_img_attributes', $attributes, $image );
return '<img' . $this->build_attributes( $attributes ) . "/>\n";
}
/**
* Create HTML attributes from an array.
*
* @since 1.9
*
* @param array $attributes A list of attribute pairs.
*
* @return string HTML attributes.
*/
protected function build_attributes( $attributes ) {
if ( ! $attributes || ! is_array( $attributes ) ) {
return '';
}
$out = '';
foreach ( $attributes as $attribute => $value ) {
$out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
}
return $out;
}
/** ----------------------------------------------------------------------------------------- */
/** VARIOUS TOOLS =========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get a list of images in a content.
*
* @since 1.9
*
* @param string $content The content.
*
* @return array
*/
protected function get_images( $content ) {
// Remove comments.
$content = preg_replace( '/<!--(.*)-->/Uis', '', $content );
if ( ! preg_match_all( '/<img\s.*>/isU', $content, $matches ) ) {
return [];
}
$images = array_map( [ $this, 'process_image' ], $matches[0] );
$images = array_filter( $images );
/**
* Filter the images to display with a <picture> tag.
*
* @since 1.9
* @see $this->process_image()
*
* @param array $images A list of arrays.
* @param string $content The page content.
*/
$images = apply_filters( 'imagify_webp_picture_images_to_display', $images, $content );
if ( ! $images || ! is_array( $images ) ) {
return [];
}
foreach ( $images as $i => $image ) {
if ( ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) &&
( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) ) {
unset( $images[ $i ] );
continue;
}
if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) {
unset( $images[ $i ]['src']['webp_url'] );
}
if ( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) {
unset( $images[ $i ]['src']['avif_url'] );
}
unset( $images[ $i ]['src']['webp_path'], $images[ $i ]['src']['webp_exists'] );
unset( $images[ $i ]['src']['avif_path'], $images[ $i ]['src']['avif_exists'] );
if ( empty( $image['srcset'] ) || ! is_array( $image['srcset'] ) ) {
unset( $images[ $i ]['srcset'] );
continue;
}
foreach ( $image['srcset'] as $j => $srcset ) {
if ( ! is_array( $srcset ) ) {
continue;
}
if ( ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) &&
( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) ) {
unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
unset( $images[ $i ]['srcset'][ $j ]['avif_url'] );
}
if ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) {
unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
}
if ( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) {
unset( $images[ $i ]['srcset'][ $j ]['avif_url'] );
}
unset( $images[ $i ]['srcset'][ $j ]['webp_path'], $images[ $i ]['srcset'][ $j ]['webp_exists'] );
unset( $images[ $i ]['srcset'][ $j ]['avif_path'], $images[ $i ]['srcset'][ $j ]['avif_exists'] );
}
}
return $images;
}
/**
* Process an image tag and get an array containing some data.
*
* @since 1.9
*
* @param string $image An image html tag.
* @return array|false {
* An array of data if the image has a WebP version. False otherwise.
*
* @type string $tag The image tag.
* @type array $attributes The image attributes (minus src and srcset).
* @type array $src {
* @type string $url URL to the original image.
* @type string $webp_url URL to the WebP version.
* }
* @type array $srcset {
* An array or arrays. Not set if not applicable.
*
* @type string $url URL to the original image.
* @type string $webp_url URL to the WebP version. Not set if not applicable.
* @type string $descriptor A src descriptor.
* }
* }
*/
protected function process_image( $image ) {
static $extensions;
$atts_pattern = '/(?<name>[^\s"\']+)\s*=\s*(["\'])\s*(?<value>.*?)\s*\2/s';
if ( ! preg_match_all( $atts_pattern, $image, $tmp_attributes, PREG_SET_ORDER ) ) {
// No attributes?
return false;
}
$attributes = [];
foreach ( $tmp_attributes as $attribute ) {
$attributes[ $attribute['name'] ] = $attribute['value'];
}
if ( ! empty( $attributes['class'] ) && strpos( $attributes['class'], 'imagify-no-webp' ) !== false ) {
// Has the 'imagify-no-webp' class.
return false;
}
// Deal with the src attribute.
$src_source = false;
foreach ( [ 'data-lazy-src', 'data-src', 'src' ] as $src_attr ) {
if ( ! empty( $attributes[ $src_attr ] ) ) {
$src_source = $src_attr;
break;
}
}
if ( ! $src_source ) {
// No src attribute.
return false;
}
if ( ! isset( $extensions ) ) {
$extensions = imagify_get_mime_types( 'image' );
$extensions = array_keys( $extensions );
$extensions = implode( '|', $extensions );
}
if ( ! preg_match( '@^(?<src>(?:(?:https?:)?//|/).+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $attributes[ $src_source ], $src ) ) {
// Not a supported image format.
return false;
}
$data = [
'tag' => $image,
'attributes' => $attributes,
'src_attribute' => $src_source,
'src' => [
'url' => $attributes[ $src_source ],
],
'srcset_attribute' => false,
'srcset' => [],
];
foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) {
$data['src'][ $key ] = $value;
}
// Deal with the srcset attribute.
$srcset_source = false;
foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
if ( ! empty( $attributes[ $srcset_attr ] ) ) {
$srcset_source = $srcset_attr;
break;
}
}
if ( $srcset_source ) {
$srcset_data = [];
$data['srcset_attribute'] = $srcset_source;
$srcset = explode( ',', $attributes[ $srcset_source ] );
foreach ( $srcset as $srcs ) {
$srcs = preg_split( '/\s+/', trim( $srcs ) );
if ( count( $srcs ) > 2 ) {
// Not a good idea to have space characters in file name.
$descriptor = array_pop( $srcs );
$srcs = [ implode( ' ', $srcs ), $descriptor ];
}
if ( empty( $srcs[1] ) ) {
$srcs[1] = '1x';
}
if ( ! preg_match( '@^(?<src>(?:https?:)?//.+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $srcs[0], $src ) ) {
// Not a supported image format.
$data['srcset'][] = [
'url' => $srcs[0],
'descriptor' => $srcs[1],
];
continue;
}
$srcset_data = [
'url' => $srcs[0],
'descriptor' => $srcs[1],
];
foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) {
$srcset_data[ $key ] = $value;
}
$data['srcset'][] = $srcset_data;
}
}
/**
* Filter a processed image tag.
*
* @since 1.9
*
* @param array $data An array of data for this image.
* @param string $image An image html tag.
*/
$data = apply_filters( 'imagify_webp_picture_process_image', $data, $image );
if ( ! $data || ! is_array( $data ) ) {
return false;
}
if ( ! isset( $data['tag'], $data['attributes'], $data['src_attribute'], $data['src'], $data['srcset_attribute'], $data['srcset'] ) ) {
return false;
}
return $data;
}
/**
* Get the next-gen image(webp & avif) data set.
*
* @param array $src Array of url/path segments.
*
* @return array
*/
protected function get_nextgen_image_data_set( array $src ): array {
$webp_url = imagify_path_to_nextgen( $src['src'], 'webp' );
$webp_path = $this->url_to_path( $webp_url );
$avif_url = imagify_path_to_nextgen( $src['src'], 'avif' );
$avif_path = $this->url_to_path( $avif_url );
$query_string = ! empty( $src['query'] ) ? $src['query'] : '';
return [
// WebP data set.
'webp_url' => $webp_url . $query_string,
'webp_path' => $webp_path,
'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
// Avif data set.
'avif_url' => $avif_url . $query_string,
'avif_path' => $avif_path,
'avif_exists' => $avif_path && $this->filesystem->exists( $avif_path ),
];
}
/**
* Tell if a content is HTML.
*
* @since 1.9
*
* @param string $content The content.
*
* @return bool
*/
protected function is_html( $content ) {
return preg_match( '/<\/html>/i', $content );
}
/**
* Convert a file URL to an absolute path.
*
* @since 1.9
*
* @param string $url A file URL.
*
* @return string|bool The file path. False on failure.
*/
protected function url_to_path( $url ) {
static $uploads_url;
static $uploads_dir;
static $root_url;
static $root_dir;
static $cdn_url;
static $domain_url;
/**
* $url, $uploads_url, $root_url, and $cdn_url are passed through `set_url_scheme()` only to make sure `stripos()` doesn't fail over a stupid http/https difference.
*/
if ( ! isset( $uploads_url ) ) {
$uploads_url = set_url_scheme( $this->filesystem->get_upload_baseurl() );
$uploads_dir = $this->filesystem->get_upload_basedir( true );
$root_url = set_url_scheme( $this->filesystem->get_site_root_url() );
$root_dir = $this->filesystem->get_site_root();
$cdn_url = apply_filters( 'imagify_cdn_source_url', '' );
$cdn_url = $cdn_url['url'] ? set_url_scheme( $cdn_url['url'] ) : false;
$domain_url = wp_parse_url( $root_url );
if ( ! empty( $domain_url['scheme'] ) && ! empty( $domain_url['host'] ) ) {
$domain_url = $domain_url['scheme'] . '://' . $domain_url['host'] . '/';
} else {
$domain_url = false;
}
}
// Get the right URL format.
if ( $domain_url && strpos( $url, '/' ) === 0 ) {
// URL like `/path/to/image.jpg.webp`.
$url = $domain_url . ltrim( $url, '/' );
}
$url = set_url_scheme( $url );
if ( $cdn_url && $domain_url && stripos( $url, $cdn_url ) === 0 ) {
// CDN.
$url = str_ireplace( $cdn_url, $domain_url, $url );
}
// Return the path.
if ( stripos( $url, $uploads_url ) === 0 ) {
return str_ireplace( $uploads_url, $uploads_dir, $url );
}
if ( stripos( $url, $root_url ) === 0 ) {
return str_ireplace( $root_url, $root_dir, $url );
}
return false;
}
}
File Manager Version 1.0, Coded By Lucas
Email: hehe@yahoo.com