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';
 | |
| 	}
 | |
| }
 |