<?php

use OptimoleWP\PageProfiler\Profile;
use OptimoleWP\Preload\Links;

/**
 * The class handles the img tag replacements for lazyload.
 *
 * @package    \Optml\Inc
 * @author     Optimole <friends@optimole.com>
 */
final class Optml_Lazyload_Replacer extends Optml_App_Replacer {
	use Optml_Normalizer;
	use Optml_Validator;
	use Optml_Dam_Offload_Utils;

	const IFRAME_PLACEHOLDER_CLASS = '
			iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
				background-color: #ffffff;
				background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22-webkit-transform-origin%3A50%25%2050%25%3B-webkit-animation%3Aspin%201.5s%20linear%20infinite%3B-webkit-backface-visibility%3Ahidden%3Banimation%3Aspin%201.5s%20linear%20infinite%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cdefs%3E%3Cstyle%3E%3C%21%5BCDATA%5B%40-webkit-keyframes%20spin%7Bfrom%7B-webkit-transform%3Arotate%280deg%29%7Dto%7B-webkit-transform%3Arotate%28-359deg%29%7D%7D%40keyframes%20spin%7Bfrom%7Btransform%3Arotate%280deg%29%7Dto%7Btransform%3Arotate%28-359deg%29%7D%7D%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22outer%22%3E%3Cpath%20d%3D%22M20%200a3.994%203.994%200%20110%207.988A3.994%203.994%200%200120%200z%22%2F%3E%3Cpath%20d%3D%22M5.858%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23d2d2d2%22%2F%3E%3Cpath%20d%3D%22M20%2032.012A3.994%203.994%200%201120%2040a3.994%203.994%200%20010-7.988z%22%20fill%3D%22%23828282%22%2F%3E%3Cpath%20d%3D%22M28.494%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23656565%22%2F%3E%3Cpath%20d%3D%22M3.994%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%23bbb%22%2F%3E%3Cpath%20d%3D%22M5.858%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23a4a4a4%22%2F%3E%3Cpath%20d%3D%22M36.006%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%234a4a4a%22%2F%3E%3Cpath%20d%3D%22M28.494%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23323232%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
				background-repeat: no-repeat;
				background-position: 50% 50%;
			}
			video[data-opt-src]:not([data-opt-lazy-loaded]) {
				background-color: #ffffff;
				background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22-webkit-transform-origin%3A50%25%2050%25%3B-webkit-animation%3Aspin%201.5s%20linear%20infinite%3B-webkit-backface-visibility%3Ahidden%3Banimation%3Aspin%201.5s%20linear%20infinite%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cdefs%3E%3Cstyle%3E%3C%21%5BCDATA%5B%40-webkit-keyframes%20spin%7Bfrom%7B-webkit-transform%3Arotate%280deg%29%7Dto%7B-webkit-transform%3Arotate%28-359deg%29%7D%7D%40keyframes%20spin%7Bfrom%7Btransform%3Arotate%280deg%29%7Dto%7Btransform%3Arotate%28-359deg%29%7D%7D%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22outer%22%3E%3Cpath%20d%3D%22M20%200a3.994%203.994%200%20110%207.988A3.994%203.994%200%200120%200z%22%2F%3E%3Cpath%20d%3D%22M5.858%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23d2d2d2%22%2F%3E%3Cpath%20d%3D%22M20%2032.012A3.994%203.994%200%201120%2040a3.994%203.994%200%20010-7.988z%22%20fill%3D%22%23828282%22%2F%3E%3Cpath%20d%3D%22M28.494%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23656565%22%2F%3E%3Cpath%20d%3D%22M3.994%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%23bbb%22%2F%3E%3Cpath%20d%3D%22M5.858%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23a4a4a4%22%2F%3E%3Cpath%20d%3D%22M36.006%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%234a4a4a%22%2F%3E%3Cpath%20d%3D%22M28.494%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23323232%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
				background-repeat: no-repeat;
				background-position: 50% 50%;
			}';
	const IFRAME_PLACEHOLDER_STYLE = '<style type="text/css">' . self::IFRAME_PLACEHOLDER_CLASS . '</style>';

	const IFRAME_TEMP_COMMENT = '/** optmliframelazyloadplaceholder */';

	const SVG_PLACEHOLDER = 'data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20#width#%20#height#%22%20width%3D%22#width#%22%20height%3D%22#height#%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22#width#%22%20height%3D%22#height#%22%20fill%3D%22#fill#%22%2F%3E%3C%2Fsvg%3E';
	/**
	 * If frame lazyload is present on page.
	 *
	 * @var bool Whether or not at least one iframe has been lazyloaded.
	 */
	private static $found_iframe = false;
	/**
	 * Cached object instance.
	 *
	 * @var Optml_Lazyload_Replacer
	 */
	protected static $instance = null;
	/**
	 * Holds the number of images to skip from lazyload.
	 *
	 * @var integer The number image tags to skip.
	 */
	private static $skip_lazyload_images = null;
	/**
	 * Holds classes for listening to lazyload on background.
	 *
	 * @var array Lazyload background classes.
	 */
	private static $lazyload_background_classes = null;
	/**
	 * Selectors used for background lazyload.
	 *
	 * @var array Lazyload background CSS selectors.
	 */
	private static $background_lazyload_selectors = null;
	/**
	 * Holds flags which remove noscript tag bundle causing issues on render, i.e slider plugins.
	 *
	 * @var array Noscript flags.
	 */
	private static $ignore_no_script_flags = null;
	/**
	 * Holds possible iframe lazyload flags where we should ignore our lazyload.
	 *
	 * @var array
	 */
	protected static $iframe_lazyload_flags = null;
	/**
	 * Holds classes responsabile for watching lazyload behaviour.
	 *
	 * @var array Lazyload classes.
	 */
	private static $lazyload_watcher_classes = null;
	/**
	 * Should we use the generic placeholder?
	 *
	 * @var bool Lazyload placeholder flag.
	 */
	private static $is_lazyload_placeholder = false;

	/**
	 * Class instance method.
	 *
	 * @codeCoverageIgnore
	 * @static
	 * @return Optml_Lazyload_Replacer
	 * @since  1.0.0
	 * @access public
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
			add_action( 'optml_replacer_setup', [ self::$instance, 'init' ] );
		}

		return self::$instance;
	}

	/**
	 * Return lazyload selectors for background images.
	 *
	 * @return array Lazyload selectors.
	 */
	public static function get_background_lazyload_selectors() {
		if ( null !== self::$background_lazyload_selectors ) {
			return self::$background_lazyload_selectors;
		}

		if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
			self::$background_lazyload_selectors = [];
			return self::$background_lazyload_selectors;
		}
		$default_watchers = [
			'.elementor-section[data-settings*="background_background"]',
			'.elementor-column[data-settings*="background_background"] > .elementor-widget-wrap',
			'.elementor-section > .elementor-background-overlay',
			'[class*="wp-block-cover"][style*="background-image"]',
			'[style*="background-image:url("]', '[style*="background-image: url("]',
			'[style*="background:url("]', '[style*="background: url("]',
			'[class*="wp-block-group"][style*="background-image"]',
		];

		$saved_watchers = self::instance()->settings->get_watchers();

		$saved_watchers = str_replace( [ "\n", "\r" ], ',', $saved_watchers );
		$saved_watchers = explode( ',', $saved_watchers );
		$all_watchers   = array_merge( $default_watchers, $saved_watchers );
		$all_watchers   = apply_filters( 'optml_lazyload_bg_selectors', $all_watchers );
		$all_watchers   = array_filter(
			$all_watchers,
			function ( $value ) {
				return ! empty( $value ) && strlen( $value ) >= 2;
			}
		);

		self::$background_lazyload_selectors = $all_watchers;

		return self::$background_lazyload_selectors;
	}

	/**
	 * Returns background classes for lazyload.
	 *
	 * @return array
	 */
	public static function get_lazyload_bg_classes() {
		if ( null !== self::$lazyload_background_classes ) {
			return self::$lazyload_background_classes;
		}

		self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );

		return self::$lazyload_background_classes;
	}
	/**
	 * Returns the number of images to skip from lazyload.
	 *
	 * @return integer
	 */
	public static function get_skip_lazyload_limit() {

		if ( self::$skip_lazyload_images !== null ) {
			return self::$skip_lazyload_images;
		}

		self::$skip_lazyload_images = apply_filters( 'optml_lazyload_images_skip', self::instance()->settings->get( 'skip_lazyload_images' ) );
		return self::$skip_lazyload_images;
	}
	/**
	 * Returns classes for lazyload additional watch.
	 *
	 * @return array
	 */
	public static function get_watcher_lz_classes() {
		if ( null !== self::$lazyload_watcher_classes ) {
			return self::$lazyload_watcher_classes;
		}

		self::$lazyload_watcher_classes = apply_filters( 'optml_watcher_lz_classes', [] );

		return self::$lazyload_watcher_classes;
	}

	/**
	 * The initialize method.
	 */
	public function init() {
		parent::init();

		if ( ! $this->settings->use_lazyload() ) {
			return;
		}
		$filters = $this->settings->get_filters();
		if ( ! Optml_Filters::should_do_page( $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL ], $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL_MATCH ] ) ) {
			return;
		}

		self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';

		add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );

		add_filter( 'optml_video_replace', [ $this, 'lazyload_video_replace' ], 2, 1 );
	}
	/**
	 * Check if there are lazyloaded iframes.
	 *
	 * @return bool Whether an iframe was lazyloaded on the page or not.
	 */
	public static function found_iframe() {
		return self::$found_iframe;
	}

	/**
	 * Check if the img tag should be lazyloaded early.
	 *
	 * @param string $image_tag The image tag.
	 * @return bool Whether the image tag should be lazyloaded early or not.
	 */
	public function should_lazyload_early( $image_tag ) {
		$flags = apply_filters( 'optml_lazyload_early_flags', [] );

		foreach ( $flags as $flag ) {
			if ( strpos( $image_tag, $flag ) !== false ) {
				return true;
			}
		}

		return false;
	}
	/**
	 * Replaces the tags with lazyload tags.
	 *
	 * @param string $new_tag The new tag.
	 * @param string $original_url The original URL.
	 * @param string $new_url The optimized URL.
	 * @param array  $optml_args Options passed for URL optimization.
	 * @param bool   $is_slashed If the url needs slashes.
	 * @param string $full_tag Full tag, wrapper included.
	 *
	 * @return string
	 */
	public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {

		if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {

			return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
		}
		// Remove fetchpriority high attribute when the image is lazyloaded
		if ( strpos( $new_tag, 'fetchpriority' ) !== false ) {
			$new_tag = str_ireplace(
				[
					'fetchpriority=high',
					'fetchpriority="high"',
					"fetchpriority='high'",
					'fetchpriority=\"high\"',
					"fetchpriority=\'high\'",
				],
				'',
				$new_tag
			);
		}

		if ( self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
			// First check if loading attribute already exists
			if ( strpos( $new_tag, 'loading=' ) !== false ) {
				// Replace existing loading attribute with lazy
				$loading_attr = $is_slashed ? 'loading=\"lazy\"' : 'loading="lazy"';
				$new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', $loading_attr, $new_tag );
			} else {
				// No loading attribute exists, add it after <img
				$loading_attr = $is_slashed ? ' loading=\"lazy\"' : ' loading="lazy"';
				$new_tag = preg_replace( '/<img/i', '<img' . $loading_attr, $new_tag, 1 );
			}
			return $new_tag;
		}

		// we remove the loading attribute if it exists with strpos
		$new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', '', $new_tag );

		$should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );

		if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
			$optml_args['quality'] = 'eco';
			$optml_args['resize']  = [];
			$low_url               = apply_filters( 'optml_content_url', $this->get_unoptimized_url( $original_url ), $optml_args );
			$low_url               = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
		} else {
			$low_url = $this->get_svg_for(
				isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
				isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
				( $should_ignore_rescale ? null : $original_url )
			);
		}
		$opt_format = '';

		if ( $this->should_add_data_tag( $full_tag ) ) {
			$opt_format = ' data-opt-src="%s" ';
			$custom_class = '';

			if ( $should_ignore_rescale ) {
				$custom_class .= 'optimole-lazy-only ';
			}
			if ( $this->should_lazyload_early( $full_tag ) ) {
				$custom_class .= 'optimole-load-early';
			}

			if ( ! empty( $custom_class ) ) {
				if ( strpos( $new_tag, 'class=' ) === false ) {
					$opt_format .= ' class="' . $custom_class . '" ';
				} else {
					$new_tag = str_replace(
						( $is_slashed ? 'class=\"' : 'class="' ),
						( $is_slashed ? 'class=\"' . $custom_class . ' ' : 'class="' . $custom_class . ' ' ),
						$new_tag
					);
				}
			}
			$opt_format = $is_slashed ? addslashes( $opt_format ) : $opt_format;
		}
		$new_url = $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;

		$opt_src = sprintf( $opt_format, $new_url );

		$no_script_tag = str_replace(
			$original_url,
			$new_url,
			$new_tag
		);
		$new_tag       = preg_replace(
			[
				'/((?:\s|\'|"){1,}src(?>=|"|\'|\s|\\\\)*)' . preg_quote( $original_url, '/' ) . '/m',
				'/<img/im',
			],
			[
				"$1$low_url",
				'<img' . $opt_src,
			],
			$new_tag,
			1
		);

		// We remove srcset and sizes attributes since our lazyload does not need them.
		$pattern = '/\s+(?:srcset|sizes)=("[^"]*"|\'[^\']*\'|\\\\"[^\\\\"]*\\\\")/i';
		$new_tag = preg_replace( $pattern, '', $new_tag );

		// We keep this for srcset lazyload compatibility that might break our mechanism.
		$new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );

		if ( ! $this->should_add_noscript( $new_tag ) ) {
			return $new_tag;
		}

		return $new_tag . '<noscript>' . $no_script_tag . '</noscript>';
	}
	/**
	 * Replaces video embeds with lazyload embeds.
	 *
	 * @param string $content Html page content.
	 *
	 * @return string
	 */
	public function lazyload_video_replace( $content ) {
		$video_tags = [];
		$iframes = [];
		$videos = [];
		preg_match_all( '#(?:<noscript\s*>\s*)?<iframe(.*?)></iframe>(?:\s*</noscript\s*>)?#is', $content, $iframes );
		preg_match_all( '#(?:<noscript\s*>\s*)?<video(.*?)>.*?</video>(?:\s*</noscript\s*>)?#is', $content, $videos );
		$video_tags = array_merge( $iframes[0], $videos[0] );

		$search = [];
		$replace = [];

		foreach ( $video_tags as $video_tag ) {

			if ( ! $this->should_lazyload_iframe( $video_tag ) ) {
				continue;
			}
			if ( preg_match( "/ data-opt-src=['\"]/is", $video_tag ) ) {
				continue;
			}
			$should_add_noscript = true;
			$original_tag = $video_tag;
			// replace the src and add the data-opt-src attribute
			if ( strpos( $video_tag, 'iframe' ) !== false ) {
				$video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
			} elseif ( strpos( $video_tag, 'video' ) !== false ) {
				if ( strpos( $video_tag, 'source' ) !== false ) {
					if ( strpos( $video_tag, 'preload' ) === false ) {
						$video_tag = preg_replace( '/video(.*?)>/is', 'video$1 preload="metadata">', $video_tag, 1 );
						$should_add_noscript = false;
					} else {
						continue;
					}
				} else {
					$video_tag = preg_replace( '/video(.*?)src=/is', 'video$1 data-opt-src=', $video_tag );
				}
			}
			if ( $this->should_add_noscript( $video_tag ) && $should_add_noscript ) {
				$video_tag .= '<noscript>' . $original_tag . '</noscript>';
			}
			array_push( $search, $original_tag );
			array_push( $replace, $video_tag );
			self::$found_iframe = true;
		}
		$search = array_unique( $search );
		$replace = array_unique( $replace );
		$content = str_replace( $search, $replace, $content );
		return $content;
	}

	/**
	 * Check if the lazyload is allowed for this url.
	 *
	 * @param string $url Url.
	 * @param string $tag Html tag.
	 *
	 * @return bool We can lazyload?
	 */
	public function can_lazyload_for( $url, $tag = '' ) {
		if ( OPTML_DEBUG ) {
			do_action( 'optml_log', 'can_lazyload_for: ' . $url . ' ' . $tag );
		}
		foreach ( self::possible_lazyload_flags() as $banned_string ) {
			if ( strpos( $tag, $banned_string ) !== false ) {
				return false;
			}
		}
		if ( false === Optml_Filters::should_do_image( $url, self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_FILENAME ] ) ) {
			return false;
		}
		$url = strtok( $url, '?' );

		$type = wp_check_filetype(
			basename( $url ),
			Optml_Config::$image_extensions
		);

		if ( empty( $type['ext'] ) ) {
			return false;
		}

		if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_EXT ], $type['ext'] ) ) {
			return false;
		}

		if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
			return $type['ext'] !== 'png';
		}

		$no_viewport_data_available = true;

		if ( $this->settings->is_lazyload_type_viewport() ) {
			$image_id                   = $this->get_id_by_url( $url );
			$is_in_all_viewports        = Optml_Manager::instance()->page_profiler->is_in_all_viewports( $image_id );
			$is_lcp_image               = Optml_Manager::instance()->page_profiler->is_lcp_image_in_all_viewports( $image_id );
			$no_viewport_data_available = ! Optml_Manager::instance()->page_profiler->is_data_available();

			if ( OPTML_DEBUG ) {
				if ( $is_in_all_viewports ) {
					do_action( 'optml_log', 'Lazyload skipped image is in all viewports ' . $url . '|' . $image_id );
				} elseif ( $is_lcp_image ) {
					do_action( 'optml_log', 'Lazyload skipped image is LCP ' . $url . '|' . $image_id );
				}
			}

			if ( $is_in_all_viewports || $is_lcp_image ) {
				return false;
			}
		}

		if (
			$no_viewport_data_available &&
			$this->settings->is_lazyload_type_fixed() &&
			Optml_Tag_Replacer::$lazyload_skipped_images < self::get_skip_lazyload_limit()
		) {
			return false;
		}
		return true;
	}

	/**
	 * Check if we should add the data-opt-tag.
	 *
	 * @param string $tag Html tag.
	 *
	 * @return bool Should add?
	 */
	public function should_add_data_tag( $tag ) {
		foreach ( self::possible_data_ignore_flags() as $banned_string ) {
			if ( strpos( $tag, $banned_string ) !== false ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Get SVG markup with specific width/height.
	 *
	 * @param int|string  $width Markup Width.
	 * @param int|string  $height Markup Height.
	 * @param string|null $url Original URL.
	 *
	 * @return string SVG code.
	 */
	public function get_svg_for( $width, $height, $url = null ) {

		if ( $url !== null && ! is_numeric( $width ) ) {
			$url   = strtok( $url, '?' );
			$key   = crc32( $url );
			$sizes = wp_cache_get( $key, 'optml_sources' );
			if ( $sizes === false ) {
				$filepath = substr( $url, strpos( $url, $this->upload_resource['content_folder'] ) + $this->upload_resource['content_folder_length'] );
				$filepath = WP_CONTENT_DIR . $filepath;
				if ( is_file( $filepath ) ) {
					$sizes = getimagesize( $filepath );
					wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
				}
			}
			list( $width, $height ) = $sizes;
		}
		// If the width is not found the url might be an offloaded attachment so we can get the width and height from the metadata.
		if ( ! is_numeric( $width ) && ! empty( $url ) ) {
			$attachment_id = $this->attachment_url_to_post_id( $url );
			$meta = wp_get_attachment_metadata( $attachment_id );
			$width = $meta['width'] ?? false;
			$height = $meta['height'] ?? false;
		}

		$width  = ! is_numeric( $width ) ? '100%' : $width;
		$height = ! is_numeric( $height ) ? '100%' : $height;
		$fill = $this->settings->get( 'placeholder_color' );

		if ( empty( $fill ) ) {
			$fill = 'transparent';
		}

		return str_replace(
			[ '#width#', '#height#', '#fill#' ],
			[
				$width,
				$height,
				urlencode( $fill ),
			],
			self::SVG_PLACEHOLDER
		);
	}

	/**
	 * Check if we should add the noscript tag.
	 *
	 * @param string $tag Html tag.
	 *
	 * @return bool Should add?
	 */
	public function should_add_noscript( $tag ) {
		if ( $this->settings->get( 'no_script' ) === 'disabled' ) {
			return false;
		}
		foreach ( self::get_ignore_noscript_flags() as $banned_string ) {
			if ( strpos( $tag, $banned_string ) !== false ) {
				return false;
			}
		}

		return true;
	}
	/**
	 * Check if we should lazyload iframe.
	 *
	 * @param string $tag Html tag.
	 *
	 * @return bool Should add?
	 */
	public function should_lazyload_iframe( $tag ) {

		foreach ( self::get_iframe_lazyload_flags() as $banned_string ) {
			if ( strpos( $tag, $banned_string ) !== false ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Returns flags for ignoring noscript tag additional watch.
	 *
	 * @return array
	 */
	public static function get_ignore_noscript_flags() {
		if ( null !== self::$ignore_no_script_flags ) {
			return self::$ignore_no_script_flags;
		}

		self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [ '<noscript' ] );

		return self::$ignore_no_script_flags;
	}
	/**
	 * Returns possible lazyload flags for iframes.
	 *
	 * @return array
	 */
	public static function get_iframe_lazyload_flags() {
		if ( null !== self::$iframe_lazyload_flags ) {
			return self::$iframe_lazyload_flags;
		}

		self::$iframe_lazyload_flags = self::possible_lazyload_flags();
		self::$iframe_lazyload_flags = array_merge( self::$iframe_lazyload_flags, apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript', 'recaptcha', '-src' ] ) );

		return self::$iframe_lazyload_flags;
	}
	/**
	 * Throw error on object clone
	 *
	 * The whole idea of the singleton design pattern is that there is a single
	 * object therefore, we don't want the object to be cloned.
	 *
	 * @codeCoverageIgnore
	 * @access public
	 * @return void
	 * @since  1.0.0
	 */
	public function __clone() {
		// Cloning instances of the class is forbidden.
		_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
	}

	/**
	 * Disable unserializing of the class
	 *
	 * @codeCoverageIgnore
	 * @access public
	 * @return void
	 * @since  1.0.0
	 */
	public function __wakeup() {
		// Unserializing instances of the class is forbidden.
		_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
	}
}
