upskill-event-manager/wordpress-dev/wordpress/wp-content/plugins/breeze/inc/class-breeze-lazy-load.php
bengizmo d1509b3d60 feat(dev-env): implement backup-based development workflow
This commit introduces a more reliable and consistent approach to setting up
the development environment using backups:

- Add setup-from-backup.sh script for environment setup from existing backups
- Standardize script naming and organization
- Move obsolete scripts to bin/obsolete directory
- Update documentation with new workflow instructions
- Create migration guide for transitioning to new workflow
- Update Memory Bank with workflow improvements

The new workflow provides:
- More reliable environment setup
- Faster setup process
- Offline development capability
- Consistent development environments across team members

Breaking changes:
- setup-dev.sh is replaced by setup-from-backup.sh
- sync-and-setup.sh is replaced by separate scripts
- verify-with-wpcli.sh is no longer used

Migration path is documented in MIGRATION_GUIDE.md
2025-03-26 11:26:18 -03:00

443 lines
14 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
header( 'Status: 403 Forbidden' );
header( 'HTTP/1.1 403 Forbidden' );
exit;
}
class Breeze_Lazy_Load {
/**
* Whether Lazy load is active.
* @var false|mixed
* @since 1.2.0
* @access private
*/
private $lazy_load_enabled = false;
/**
* Must use native Lazy Load.
* @var false|mixed
* @since 1.2.0
* @access private
*/
private $lazy_load_native = false;
/**
* The page HTML content.
*
* @var string
* @since 1.2.0
* @access private
*/
private $content = '';
/**
* Exclude images that have these attributes from processing.
*
* @since 1.2.0
* @var string[]
*/
private $exclude_if_atts = array();
/**
* Breeze_Lazy_Load constructor.
*
* @param false $is_enabled If the base Lazy Load is enabled.
* @param string $content the HTML content of the page.
* @param false $is_native Whether to use native lazy load or Javascript based.
*
* @access public
* @since 1.2.0
*/
function __construct( $content = '', $is_enabled = false, $is_native = false ) {
$this->lazy_load_enabled = $is_enabled;
$this->lazy_load_native = $is_native;
$this->content = $content;
$this->exclude_if_atts = apply_filters(
'breeze_excluded_attributes',
array(
'data-src',
'data-no-lazy',
'data-lazy-original',
'data-lazy-src',
'data-lazysrc',
'data-lazyload',
'data-bgposition',
'data-envira-src',
'fullurl',
'lazy-slider-img',
'data-srcset',
'data-spai',
'data-src-webp',
'data-srcset-webp',
'data-src-img',
'data-srcset-img',
)
);
}
/**
* Apply lazy load library option.
*
* @return false|string
* @access public
* @since 1.2.0
*/
public function apply_lazy_load_feature() {
$content = $this->content;
if ( defined( 'REST_REQUEST' ) || is_feed() || is_admin() || is_comment_feed() || is_preview() || is_robots() || is_trackback() ) {
return $content;
}
if ( false === $this->lazy_load_enabled ) {
return $content;
}
if ( '' === trim( $content ) ) {
return $content;
}
// If this option is set to true then loading="lazy" attribute will be use instead.
// The native lazy load is not yet supported by all browsers. ( As of February 2021, 73% of browsers support lazy loading. )
$use_native = apply_filters( 'breeze_use_native_lazy_load', $this->lazy_load_native );
if ( version_compare( PHP_VERSION, '8.2.0', '<' ) ) {
$content = mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' );
} else {
$content = mb_encode_numericentity(
$content,
array( 0x80, 0x10FFFF, 0, ~0 ),
'UTF-8'
);
}
/**
* Fetch all images
*/
preg_match_all( '/<img[^>]+>/i', $content, $img_matches );
// Remove any image tags that have \ inside as it's probably targeted by other scripts
$img_matches[0] = array_filter(
$img_matches[0],
function( $tag ) {
return strpos( $tag, '\\' ) === false;
}
);
// Check if images available
if ( ! empty( $img_matches[0] ) ) {
// Check if required native ll
if ( true === $use_native ) {
foreach ( $img_matches[0] as $img_match ) {
// If we can't find loading attr, we append it
if ( ! preg_match( '/loading=[\'"]\s*lazy\s*[\'"]/i', $img_match ) ) {
// Forming the new string
$img_match_new = preg_replace( '/<img\s/i', '<img loading="lazy" ', $img_match, 1 );
// Rewriting the old string in content
$content = str_replace( $img_match, $img_match_new, $content );
}
}
} else {
// Non-native behavior
foreach ( $img_matches[0] as $img_match ) {
// Get width and height
preg_match( '/width=["\'](.*?)["\']/', $img_match, $width );
preg_match( '/height=["\'](.*?)["\']/', $img_match, $height );
// Skip if image width and height is 1.
if ( isset( $width[1] ) && isset( $height[1] ) && ( (int) $width[1] <= 1 && (int) $height[1] <= 1 ) ) {
continue;
}
// Check if the image is to be ignored.
if ( is_array( $this->exclude_if_atts ) && ! empty( $this->exclude_if_atts ) ) {
$exclude_it = false;
foreach ( $this->exclude_if_atts as $ex_attr ) {
preg_match( '/' . $ex_attr . '=(?:"|\')(.+?)(?:"|\')/', $img_match, $attr_value );
if ( ! empty( $attr_value[1] ) ) {
$exclude_it = true;
}
}
if ( true === $exclude_it ) {
continue;
}
}
// Get the image URL
preg_match( '/src=(?:"|\')(.+?)(?:"|\')/', $img_match, $src_value );
$current_src = ! empty( $src_value[1] ) ? $src_value[1] : '';
if ( true !== $this->excluded_images( $current_src ) ) {
// Add lazy-load data attribute.
$img_match_new = preg_replace( '/(<img\s+)/', '$1data-breeze="' . trim($current_src) . '" ', $img_match );
// Remove the current image source.
$img_match_new = preg_replace( '/(<img.+)(src=(?:"|\').+?(?:"|\'))(.+?>)/', '$1$3', $img_match_new );
preg_match( '/width=(?:"|\')(.+?)(?:"|\')/', $img_match, $width_value );
preg_match( '/height=(?:"|\')(.+?)(?:"|\')/', $img_match, $height_value );
$get_width = ! empty( $width_value[1] ) ? $width_value[1] : '';
$get_height = ! empty( $height_value[1] ) ? $height_value[1] : '';
$placeholder = $this->generate_simple_placeholder( $get_width, $get_height );
// Add placeholder image as source
$img_match_new = preg_replace( '/(<img\s+)/', '$1src="' . $placeholder . '" ', $img_match_new );
// Fetch the current image CSS classes.
preg_match( '/class=(?:"|\')(.+?)(?:"|\')/', $img_match_new, $class_value );
$current_classes = ! empty( $class_value[1] ) ? $class_value[1] : '';
// Append breeze lazy-load CSS class.
if ( empty( trim( $current_classes ) ) ) {
$current_classes = 'br-lazy';
} else {
$current_classes .= ' br-lazy';
}
$img_match_new = preg_replace( '/(<img.+)(class=(?:"|\').+?(?:"|\'))(.+?>)/', '$1$3', $img_match_new );
// Add lazy-load CSS class.
$img_match_new = preg_replace( '/(<img\s+)/', '$1class="' . $current_classes . '" ', $img_match_new );
// handle SRCSET and SIZES attributes.
preg_match( '/srcset=(?:"|\')(.+?)(?:"|\')/', $img_match_new, $srcset_value );
preg_match( '/sizes=(?:"|\')(.+?)(?:"|\')/', $img_match_new, $sizes_value );
$srcset = ! empty( $srcset_value[1] ) ? $srcset_value[1] : '';
$sizes = ! empty( $sizes_value[1] ) ? $sizes_value[1] : '';
if ( ! empty( $srcset ) ) {
$img_match_new = preg_replace( '/srcset=/i', 'data-brsrcset=', $img_match_new );
}
if ( ! empty( $sizes ) ) {
$img_match_new = preg_replace( '/sizes=/i', 'data-brsizes=', $img_match_new );
}
$content = str_replace( $img_match, $img_match_new, $content );
}
}
}
}
$apply_to_iframes = Breeze_Options_Reader::get_option_value( 'breeze-lazy-load-iframes' );
$apply_to_iframes = apply_filters( 'breeze_enable_lazy_load_iframes', $apply_to_iframes );
if ( true === filter_var( $apply_to_iframes, FILTER_VALIDATE_BOOLEAN ) ) {
$allowed_iframes_url = apply_filters(
'breeze_iframe_lazy_load_list',
array(
'youtube.com',
'dailymotion.com/embed/video',
'facebook.com/plugins/video.php',
'player.vimeo.com',
'fast.wistia.net/embed/',
'players.brightcove.net',
's3.amazonaws.com',
'cincopa.com/media',
'twitch.tv',
'bitchute.com',
'media.myspace.com/play/video',
'tiktok.com/embed',
)
);
// Match and process iframes.
preg_match_all( '/<iframe.*<\/iframe>/isU', $content, $iframe_matches );
foreach ( $iframe_matches[0] as $iframe_tag ) {
$src = preg_replace( '/^.*src=\"([^\"]+)\".*$/isU', '$1', $iframe_tag );
$allowed_url = false;
foreach ( $allowed_iframes_url as $iframe_url ) {
if ( false !== strpos( $src, $iframe_url ) ) {
$allowed_url = true;
break;
}
}
if ( true === $allowed_url ) {
// Video Link
$video_link = explode( '/', $src );
$video_id = end( $video_link );
// Get classes
$current_classes = $this->format_tag_ll_classes( $iframe_tag );
// Forming iframe tag
$iframe_tag_new = preg_replace( '/<iframe/isU', '<iframe data-video-id="' . $video_id . '" class="' . $current_classes . '" data-breeze="' . $src . '"', $iframe_tag );
$iframe_tag_new = preg_replace( '/src=\"([^\"]+)\"/isU', '', $iframe_tag_new );
$content = str_replace( $iframe_tag, $iframe_tag_new, $content );
}
}
}
$apply_to_videos = Breeze_Options_Reader::get_option_value( 'breeze-lazy-load-videos' );
$apply_to_videos = apply_filters( 'breeze_enable_lazy_load_videos', $apply_to_videos );
if ( true === filter_var( $apply_to_videos, FILTER_VALIDATE_BOOLEAN ) ) {
preg_match_all( '/<video[^>]*>(.*?)<\/video>/is', $content, $video_matches );
foreach ( $video_matches[0] as $video_tag ) {
// TODO: We need a better placeholder
$placeholder = BREEZE_PLUGIN_URL . 'assets/images/placeholder.mp4';
$placeholder_webp = BREEZE_PLUGIN_URL . 'assets/images/placeholder.webp';
// Lazy loading class
$lazy_class = 'br-lazy';
// Process each <source> element within the <video> tag.
if ( strpos( $video_tag, '<source' ) !== false ) {
continue;
// TODO: implement when finish the library for videos with sources
// $video_tag_new = preg_replace_callback(
// '/<source\s+[^>]*src="([^"]+)"/isU',
// function ( $matches ) use ( $placeholder, $placeholder_webp ) {
// $source_url = $matches[1];
// // Check for .webm and .webp extensions
// if ( preg_match( '/\.(webm|webp)$/i', $source_url ) ) {
// $placeholder_url = $placeholder_webp; // Use WebP placeholder for .webm or .webp files
// } else {
// $placeholder_url = $placeholder; // Default to MP4 placeholder
// }
//
// return str_replace( 'src="' . $source_url . '"', 'src="' . $placeholder_url . '" data-src="' . $source_url . '"', $matches[0] );
// },
// $video_tag_new
// );
}
// Add the lazy loading class to the <video> tag and process its src attribute.
$video_tag_new = preg_replace_callback(
'/<video\s+([^>]*)>/isU',
function ( $matches ) use ( $lazy_class, $placeholder, $placeholder_webp ) {
$video_attrs = $matches[1];
// Determine the correct placeholder for the video src attribute.
if ( preg_match( '/src="([^"]+)"/i', $video_attrs, $src_matches ) ) {
$video_src = $src_matches[1];
$placeholder_url = preg_match( '/\.webp$/i', $video_src ) ? $placeholder_webp : $placeholder;
$video_attrs = str_replace( 'src="' . $video_src . '"', 'src="' . $placeholder_url . '" data-breeze="' . $video_src . '"', $video_attrs );
}
// Add or update the class attribute with lazy loading class.
if ( strpos( $video_attrs, 'class="' ) !== false ) {
$video_attrs = preg_replace( '/class="([^"]*)"/i', 'class="$1 ' . $lazy_class . '"', $video_attrs );
} else {
$video_attrs .= ' class="' . $lazy_class . '"';
}
return '<video ' . $video_attrs . '>';
},
$video_tag
);
// Update the content.
$content = str_replace( $video_tag, $video_tag_new, $content );
}
}
// Buffer decoding.
$content = mb_decode_numericentity( $content, array( 0x80, 0x10FFFF, 0, ~0 ), 'UTF-8' );
return $content;
}
/**
* Extract classes for lazy load and returned clean class string with br-lazy appended
*
* @param $tag
*
* @return string
*/
private function format_tag_ll_classes( $tag ) {
preg_match( '/class=(?:"|\')(.+?)(?:"|\')/', $tag, $class_value );
$current_classes = ! empty( $class_value[1] ) ? $class_value[1] : '';
// Append breeze lazy-load CSS class.
if ( empty( trim( $current_classes ) ) ) {
$current_classes = 'br-lazy';
} else {
$current_classes .= ' br-lazy';
}
return $current_classes;
}
/**
* We need to exclude some images with very specific functionality.
* Example of excluded: Captcha, WooCommerce placeholder image.
*
* @param string $image_url The image full URL path.
*
* @return bool
* @since 1.2.0
* @access private
*/
private function excluded_images( $image_url = '' ) {
$excluded_images_by_url = apply_filters(
'breeze_excluded_images_url',
array(
'wpcf7_captcha/', // Contact Form 7 - Really Simple CAPTCHA.
'woocommerce/assets/images/placeholder.png',
)
);
if ( ! empty( $excluded_images_by_url ) ) {
foreach ( $excluded_images_by_url as $partial_path ) {
if ( false !== strpos( $image_url, $partial_path ) ) {
return true;
}
}
}
return false;
}
/**
* Generate a simple svg placeholder image.
*
* @param int $width Original image width.
* @param int $height Original image height.
*
* @return string
* @access private
* @since 1.2.0
*/
private function generate_simple_placeholder( $width = 0, $height = 0 ) {
if ( ! is_numeric( $width ) ) {
$width = 0;
}
if ( ! is_numeric( $height ) ) {
$height = 0;
}
if ( ! empty( $width ) ) {
$width = absint( $width );
}
if ( ! empty( $height ) ) {
$height = absint( $height );
}
//return "data:image/svg+xml;utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20{$width}%20{$height}'%3E%3C/svg%3E";
// Generate the SVG
$svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {$width} {$height}'></svg>";
// Convert the SVG to a base64 string
$svg_base64 = base64_encode( $svg );
return "data:image/svg+xml;base64,{$svg_base64}";
}
}