270 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace W3TC;
 | 
						|
 | 
						|
class UserExperience_LazyLoad_Mutator {
 | 
						|
	private $config;
 | 
						|
	private $modified = false;
 | 
						|
	private $excludes;
 | 
						|
	private $posts_by_url;
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function __construct( $config, $posts_by_url ) {
 | 
						|
		$this->config = $config;
 | 
						|
		$this->posts_by_url = $posts_by_url;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function run( $buffer ) {
 | 
						|
		$this->excludes = apply_filters( 'w3tc_lazyload_excludes',
 | 
						|
			$this->config->get_array( 'lazyload.exclude' ) );
 | 
						|
 | 
						|
		$r = apply_filters( 'w3tc_lazyload_mutator_before', array(
 | 
						|
			'buffer' => $buffer,
 | 
						|
			'modified' => $this->modified
 | 
						|
		) );
 | 
						|
		$buffer = $r['buffer'];
 | 
						|
		$this->modified = $r['modified'];
 | 
						|
 | 
						|
		$unmutable = new UserExperience_LazyLoad_Mutator_Unmutable();
 | 
						|
		$buffer = $unmutable->remove_unmutable( $buffer );
 | 
						|
 | 
						|
		if ( $this->config->get_boolean( 'lazyload.process_img' ) ) {
 | 
						|
			$buffer = preg_replace_callback(
 | 
						|
				'~<picture(\s[^>]+)*>(.*?)</picture>~is',
 | 
						|
				array( $this, 'tag_picture' ), $buffer
 | 
						|
			);
 | 
						|
			$buffer = preg_replace_callback(
 | 
						|
				'~<img\s[^>]+>~is',
 | 
						|
				array( $this, 'tag_img' ), $buffer
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $this->config->get_boolean( 'lazyload.process_background' ) ) {
 | 
						|
			$buffer = preg_replace_callback(
 | 
						|
				'~<[^>]+background(-image)?:\s*url[^>]+>~is',
 | 
						|
				array( $this, 'tag_with_background' ), $buffer
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		$buffer = $unmutable->restore_unmutable( $buffer );
 | 
						|
 | 
						|
		return $buffer;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function content_modified() {
 | 
						|
		return $this->modified;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function tag_picture( $matches ) {
 | 
						|
		$content = $matches[0];
 | 
						|
 | 
						|
		if ( $this->is_content_excluded( $content ) ) {
 | 
						|
			return $content;
 | 
						|
		}
 | 
						|
 | 
						|
		$m = new UserExperience_LazyLoad_Mutator_Picture( $this );
 | 
						|
		return $m->run( $content );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function tag_img( $matches ) {
 | 
						|
		$content = $matches[0];
 | 
						|
 | 
						|
		if ( $this->is_content_excluded( $content ) ) {
 | 
						|
			return $content;
 | 
						|
		}
 | 
						|
 | 
						|
		// get image dimensions
 | 
						|
		$dim = $this->tag_get_dimensions( $content );
 | 
						|
		return $this->tag_img_content_replace( $content, $dim );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Common replace code for picture and img tags
 | 
						|
	 */
 | 
						|
	public function tag_img_content_replace( $content, $dim ) {
 | 
						|
		// do replace
 | 
						|
		$count = 0;
 | 
						|
		$content = preg_replace( '~(\s)src=~is',
 | 
						|
			'$1src="' . $this->placeholder( $dim['w'], $dim['h'] ) .
 | 
						|
			'" data-src=', $content, -1, $count );
 | 
						|
 | 
						|
		if ( $count > 0 ) {
 | 
						|
			$content = preg_replace( '~(\s)(srcset|sizes)=~is',
 | 
						|
				'$1data-$2=', $content );
 | 
						|
 | 
						|
			$content = $this->add_class_lazy( $content );
 | 
						|
			$content = $this->remove_native_lazy( $content );
 | 
						|
			$this->modified = true;
 | 
						|
		}
 | 
						|
 | 
						|
		return $content;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Common get dimensions of image
 | 
						|
	 */
 | 
						|
	public function tag_get_dimensions( $content ) {
 | 
						|
		$dim = array( 'w' => 1, 'h' => 1 );
 | 
						|
		$m = null;
 | 
						|
		if ( preg_match( '~\swidth=[\s\'"]*([0-9]+)~is', $content, $m ) ) {
 | 
						|
			$dim['h'] = $dim['w'] = (int)$m[1];
 | 
						|
 | 
						|
			if ( preg_match( '~\sheight=[\s\'"]*([0-9]+)~is', $content, $m ) ) {
 | 
						|
				$dim['h'] = (int)$m[1];
 | 
						|
				return $dim;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// if not in attributes - try to find via url
 | 
						|
		if ( !preg_match( '~\ssrc=(\'([^\']*)\'|"([^"]*)"|([^\'"][^\\s]*))~is',
 | 
						|
				$content, $m ) ) {
 | 
						|
			return $dim;
 | 
						|
		}
 | 
						|
 | 
						|
		$url = ( !empty( $m[4] ) ? $m[4] : ( ( !empty( $m[3] ) ? $m[3] : $m[2] ) ) );
 | 
						|
 | 
						|
		// full url found
 | 
						|
		if ( isset( $this->posts_by_url[$url] ) ) {
 | 
						|
			$post_id = $this->posts_by_url[$url];
 | 
						|
 | 
						|
			$image = wp_get_attachment_image_src( $post_id, 'full' );
 | 
						|
			if ( $image ) {
 | 
						|
				$dim['w'] = $image[1];
 | 
						|
				$dim['h'] = $image[2];
 | 
						|
			}
 | 
						|
 | 
						|
			return $dim;
 | 
						|
		}
 | 
						|
 | 
						|
		// try resized url by format
 | 
						|
		static $base_url = null;
 | 
						|
		if ( is_null( $base_url ) ) {
 | 
						|
			$base_url = wp_get_upload_dir()['baseurl'];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( substr( $url, 0, strlen( $base_url ) ) == $base_url &&
 | 
						|
				 preg_match( '~(.+)-(\\d+)x(\\d+)(\\.[a-z0-9]+)$~is', $url, $m ) ) {
 | 
						|
			$dim['w'] = (int)$m[2];
 | 
						|
			$dim['h'] = (int)$m[3];
 | 
						|
		}
 | 
						|
 | 
						|
		return $dim;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function tag_with_background( $matches ) {
 | 
						|
		$content = $matches[0];
 | 
						|
 | 
						|
		if ( $this->is_content_excluded( $content ) ) {
 | 
						|
			return $content;
 | 
						|
		}
 | 
						|
 | 
						|
		$quote_match = null;
 | 
						|
		if ( !preg_match( '~\s+style\s*=\s*([\"\'])~is', $content, $quote_match ) ) {
 | 
						|
			return $content;
 | 
						|
		}
 | 
						|
		$quote = $quote_match[1];
 | 
						|
 | 
						|
		$count = 0;
 | 
						|
		$content = preg_replace_callback(
 | 
						|
			'~(\s+)(style\s*=\s*[' . $quote . '])(.*?)([' . $quote . '])~is',
 | 
						|
			array( $this, 'style_offload_background' ), $content, -1, $count
 | 
						|
		);
 | 
						|
 | 
						|
		if ( $count > 0 ) {
 | 
						|
			$content = $this->add_class_lazy( $content );
 | 
						|
			$this->modified = true;
 | 
						|
		}
 | 
						|
 | 
						|
		return $content;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function style_offload_background( $matches ) {
 | 
						|
		list( $match, $v1, $v2, $v, $quote ) = $matches;
 | 
						|
		$url_match = null;
 | 
						|
		preg_match( '~background(-image)?:\s*(url\([^>]+\))~is', $v, $url_match );
 | 
						|
		$v = preg_replace( '~background(-image)?:\s*url\([^>]+\)[;]?\s*~is', '', $v );
 | 
						|
 | 
						|
		return $v1 . $v2 . $v . $quote . ' data-bg=' . $quote . $url_match[2] . $quote;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	private function add_class_lazy( $content ) {
 | 
						|
		$count = 0;
 | 
						|
		$content = preg_replace_callback(
 | 
						|
			'~(\s+)(class=)([\"\'])(.*?)([\"\'])~is',
 | 
						|
			array( $this, 'class_process' ), $content, -1, $count
 | 
						|
		);
 | 
						|
 | 
						|
		if ( $count <= 0) {
 | 
						|
			$content = preg_replace(
 | 
						|
				'~<(\S+)(\s+)~is', '<$1$2class="lazy" ', $content
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		return $content;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * In safari javascript lazy-loaded image with loading="lazy"
 | 
						|
	 * dont fire events, i.e. image not loaded
 | 
						|
	 */
 | 
						|
	public function remove_native_lazy( $content ) {
 | 
						|
		return preg_replace(
 | 
						|
			'~(\s+)loading=[\'"]lazy[\'"]~is', '', $content
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function class_process( $matches ) {
 | 
						|
		list( $match, $v1, $v2, $quote, $v ) = $matches;
 | 
						|
		if ( preg_match( '~(^|\\s)lazy(\\s|$)~is', $v ) ) {
 | 
						|
			return $match;
 | 
						|
		}
 | 
						|
 | 
						|
		$v .= ' lazy';
 | 
						|
 | 
						|
		return $v1 . $v2 . $quote . $v . $quote;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	private function is_content_excluded( $content ) {
 | 
						|
		foreach ( $this->excludes as $w ) {
 | 
						|
			if ( !empty($w) ) {
 | 
						|
				if ( strpos( $content, $w ) !== FALSE ) {
 | 
						|
					return true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	public function placeholder( $w, $h ) {
 | 
						|
		return 'data:image/svg+xml,%3Csvg%20xmlns=\'http://www.w3.org/2000/svg\'%20viewBox=\'0%200%20' .
 | 
						|
			$w . '%20'. $h . '\'%3E%3C/svg%3E';
 | 
						|
	}
 | 
						|
}
 |