HEX
Server: Apache
System: Linux 162-240-236-42.bluehost.com 3.10.0-1160.114.2.el7.x86_64 #1 SMP Wed Mar 20 15:54:52 UTC 2024 x86_64
User: bt667 (1004)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /home/bt667/public_html/wp-content/plugins/embed-optimizer/hooks.php
<?php
/**
 * Hook callbacks used for Embed Optimizer.
 *
 * @since 0.1.0
 * @package embed-optimizer
 */

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

/**
 * Add hooks.
 *
 * @since 0.2.0
 */
function embed_optimizer_add_hooks(): void {
	add_action( 'wp_head', 'embed_optimizer_render_generator' );

	add_action( 'od_init', 'embed_optimizer_init_optimization_detective' );
	add_action( 'wp_loaded', 'embed_optimizer_add_non_optimization_detective_hooks' );
}
add_action( 'init', 'embed_optimizer_add_hooks' );

/**
 * Adds hooks for when the Optimization Detective logic is not running.
 *
 * @since 0.3.0
 */
function embed_optimizer_add_non_optimization_detective_hooks(): void {
	if ( false === has_action( 'od_register_tag_visitors', 'embed_optimizer_register_tag_visitors' ) ) {
		add_filter( 'embed_oembed_html', 'embed_optimizer_filter_oembed_html_to_lazy_load' );
	}
}

/**
 * Initializes Embed Optimizer when Optimization Detective has loaded.
 *
 * @since 0.3.0
 *
 * @param string $optimization_detective_version Current version of the optimization detective plugin.
 */
function embed_optimizer_init_optimization_detective( string $optimization_detective_version ): void {
	$required_od_version = '0.9.0';
	if ( version_compare( (string) strtok( $optimization_detective_version, '-' ), $required_od_version, '<' ) ) {
		add_action(
			'admin_notices',
			static function (): void {
				global $pagenow;
				if ( ! in_array( $pagenow, array( 'index.php', 'plugins.php' ), true ) ) {
					return;
				}
				wp_admin_notice(
					esc_html__( 'The Embed Optimizer plugin requires a newer version of the Optimization Detective plugin. Please update your plugins.', 'embed-optimizer' ),
					array( 'type' => 'warning' )
				);
			}
		);
		return;
	}

	add_action( 'od_register_tag_visitors', 'embed_optimizer_register_tag_visitors' );
	add_filter( 'embed_oembed_html', 'embed_optimizer_filter_oembed_html_to_detect_embed_presence' );
	add_filter( 'od_url_metric_schema_element_item_additional_properties', 'embed_optimizer_add_element_item_schema_properties' );
}

/**
 * Registers the tag visitor for embeds.
 *
 * @since 0.2.0
 *
 * @param OD_Tag_Visitor_Registry $registry Tag visitor registry.
 */
function embed_optimizer_register_tag_visitors( OD_Tag_Visitor_Registry $registry ): void {
	// Note: This class is loaded on the fly since it is only needed here when Optimization Detective is active.
	require_once __DIR__ . '/class-embed-optimizer-tag-visitor.php';
	$registry->register( 'embeds', new Embed_Optimizer_Tag_Visitor() );
}

/**
 * Filters additional properties for the element item schema for Optimization Detective.
 *
 * @since 0.3.0
 *
 * @param array<string, array{type: string}> $additional_properties Additional properties.
 * @return array<string, array{type: string}> Additional properties.
 */
function embed_optimizer_add_element_item_schema_properties( array $additional_properties ): array {
	$additional_properties['resizedBoundingClientRect'] = array(
		'type'       => 'object',
		'properties' => array_fill_keys(
			array(
				'width',
				'height',
				'x',
				'y',
				'top',
				'right',
				'bottom',
				'left',
			),
			array(
				'type'     => 'number',
				'required' => true,
			)
		),
	);
	return $additional_properties;
}

/**
 * Filters the list of Optimization Detective extension module URLs to include the extension for Embed Optimizer.
 *
 * @since 0.3.0
 *
 * @param string[]|mixed $extension_module_urls Extension module URLs.
 * @return string[] Extension module URLs.
 */
function embed_optimizer_filter_extension_module_urls( $extension_module_urls ): array {
	if ( ! is_array( $extension_module_urls ) ) {
		$extension_module_urls = array();
	}
	$extension_module_urls[] = plugins_url( add_query_arg( 'ver', EMBED_OPTIMIZER_VERSION, embed_optimizer_get_asset_path( 'detect.js' ) ), __FILE__ );
	return $extension_module_urls;
}

/**
 * Filter the oEmbed HTML to detect when an embed is present so that the Optimization Detective extension module can be enqueued.
 *
 * This ensures that the module for handling embeds is only loaded when there is an embed on the page.
 *
 * @since 0.3.0
 *
 * @param string|mixed $html The oEmbed HTML.
 * @return string Unchanged oEmbed HTML.
 */
function embed_optimizer_filter_oembed_html_to_detect_embed_presence( $html ): string {
	if ( ! is_string( $html ) ) {
		$html = '';
	}
	add_filter( 'od_extension_module_urls', 'embed_optimizer_filter_extension_module_urls' );
	return $html;
}

/**
 * Filter the oEmbed HTML to lazy load the embed.
 *
 * Add loading="lazy" to any iframe tags.
 * Lazy load any script tags.
 *
 * @since 0.1.0
 *
 * @param string|mixed $html The oEmbed HTML.
 * @return string Filtered oEmbed HTML.
 */
function embed_optimizer_filter_oembed_html_to_lazy_load( $html ): string {
	if ( ! is_string( $html ) ) {
		$html = '';
	}
	$html_processor = new WP_HTML_Tag_Processor( $html );
	if ( embed_optimizer_update_markup( $html_processor, true ) ) {
		add_action( 'wp_footer', 'embed_optimizer_lazy_load_scripts' );
	}
	return $html_processor->get_updated_html();
}

/**
 * Applies changes to HTML in the supplied tag processor to lazy-load the embed.
 *
 * @since 0.2.0
 *
 * phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- The exception is caught.
 *
 * @param WP_HTML_Tag_Processor|OD_HTML_Tag_Processor $html_processor HTML Processor.
 * @param bool                                        $is_isolated    Whether processing an isolated embed fragment or the entire document.
 * @return bool Whether the lazy-loading script is required.
 */
function embed_optimizer_update_markup( WP_HTML_Tag_Processor $html_processor, bool $is_isolated ): bool {
	$bookmark_names = array(
		'script' => 'embed_optimizer_script',
		'iframe' => 'embed_optimizer_iframe',
	);
	$function_name  = __FUNCTION__;
	$trigger_error  = static function ( string $message ) use ( $function_name ): void {
		wp_trigger_error( $function_name, esc_html( $message ) );
	};

	// As of 1.0.0-beta3, next_tag() allows $query and is beginning to migrate to skip tag closers by default.
	// In versions prior to this, the method always visited closers and passing a $query actually threw an exception.
	$tag_query = ! defined( 'OPTIMIZATION_DETECTIVE_VERSION' ) || version_compare( OPTIMIZATION_DETECTIVE_VERSION, '1.0.0-beta3', '>=' )
		? array( 'tag_closers' => 'visit' ) : null;
	try {
		/*
		 * Determine how to lazy load the embed.
		 *
		 * - If there is only one iframe, set loading="lazy".
		 * - Prevent making scripts lazy if there is an inline script.
		 * - Only make script lazy if there is a single external script (since if there are
		 *   multiple they may not get loaded in the right order).
		 * - Ensure that both the iframe and the script are made lazy if both occur in the same embed.
		 */
		$iframe_count      = 0;
		$script_count      = 0;
		$needs_lazy_script = false;
		$has_inline_script = false;
		$figure_depth      = 0;
		// Locate the iframes and scripts.
		do {
			// This condition ensures that when iterating over an embed inside a larger document that we stop once we reach
			// closing </figure> tag. The $processor is an OD_HTML_Tag_Processor when Optimization Detective is iterating
			// over all tags in the document, and this embed_optimizer_update_markup() is used as part of the tag visitor
			// from Embed Optimizer. On the other hand, if $html_processor is not an OD_HTML_Tag_Processor then this is
			// iterating over the tags of the embed markup alone as is passed into the embed_oembed_html filter.
			if ( ! $is_isolated ) {
				if ( 'FIGURE' === $html_processor->get_tag() ) {
					if ( $html_processor->is_tag_closer() ) {
						--$figure_depth;
						if ( $figure_depth <= 0 ) {
							// We reached the end of the embed.
							break;
						}
					} else {
						++$figure_depth;
						// Move to next element to start looking for IFRAME or SCRIPT tag.
						continue;
					}
				}
				if ( 0 === $figure_depth ) {
					continue;
				}
			}

			if ( 'IFRAME' === $html_processor->get_tag() ) {
				$loading_value = $html_processor->get_attribute( 'loading' );
				// Per the HTML spec: "The attribute's missing value default and invalid value default are both the Eager state".
				if ( 'lazy' !== $loading_value ) {
					++$iframe_count;
					if ( ! $html_processor->set_bookmark( $bookmark_names['iframe'] ) ) {
						throw new Exception(
							/* translators: %s is bookmark name */
							sprintf( __( 'Embed Optimizer unable to set %s bookmark.', 'embed-optimizer' ), $bookmark_names['iframe'] )
						);
					}
				}
			} elseif ( 'SCRIPT' === $html_processor->get_tag() ) {
				if ( null === $html_processor->get_attribute( 'src' ) ) {
					$has_inline_script = true;
				} else {
					++$script_count;
					if ( ! $html_processor->set_bookmark( $bookmark_names['script'] ) ) {
						throw new Exception(
							/* translators: %s is bookmark name */
							sprintf( __( 'Embed Optimizer unable to set %s bookmark.', 'embed-optimizer' ), $bookmark_names['script'] )
						);
					}
				}
			}
		} while ( $html_processor->next_tag( $tag_query ) );
		// If there was only one non-inline script, make it lazy.
		if ( 1 === $script_count && ! $has_inline_script && $html_processor->has_bookmark( $bookmark_names['script'] ) ) {
			$needs_lazy_script = true;
			if ( $html_processor->seek( $bookmark_names['script'] ) ) {
				if ( is_string( $html_processor->get_attribute( 'type' ) ) ) {
					$html_processor->set_attribute( 'data-original-type', $html_processor->get_attribute( 'type' ) );
				}
				$html_processor->set_attribute( 'type', 'application/vnd.embed-optimizer.javascript' );
			} else {
				$trigger_error(
					/* translators: %s is bookmark name */
					sprintf( __( 'Embed Optimizer unable to seek to %s bookmark.', 'embed-optimizer' ), $bookmark_names['script'] )
				);
			}
		}
		// If there was only one iframe, make it lazy.
		if ( 1 === $iframe_count && $html_processor->has_bookmark( $bookmark_names['iframe'] ) ) {
			if ( $html_processor->seek( $bookmark_names['iframe'] ) ) {
				$html_processor->set_attribute( 'loading', 'lazy' );

				// For post embeds, use visibility:hidden instead of clip since browsers will consistently load the
				// lazy-loaded iframe (where Chromium is unreliably with clip) while at the same time improve accessibility
				// by preventing links in the hidden iframe from receiving focus.
				if ( true === $html_processor->has_class( 'wp-embedded-content' ) ) {
					$style = $html_processor->get_attribute( 'style' );
					if ( is_string( $style ) ) {
						// WordPress core injects this clip CSS property:
						// <https://github.com/WordPress/wordpress-develop/blob/6974b994de5/src/wp-includes/embed.php#L968>.
						$style = str_replace( 'clip: rect(1px, 1px, 1px, 1px);', 'visibility: hidden;', $style );

						// Note: wp-embed.js removes the style attribute entirely when the iframe is loaded:
						// <https://github.com/WordPress/wordpress-develop/blob/6974b994d/src/js/_enqueues/wp/embed.js#L60>.
						$html_processor->set_attribute( 'style', $style );
					}
				}
			} else {
				$trigger_error(
					/* translators: %s is bookmark name */
					sprintf( __( 'Embed Optimizer unable to seek to %s bookmark.', 'embed-optimizer' ), $bookmark_names['iframe'] )
				);
			}
		}
	} catch ( Exception $exception ) {
		$trigger_error( $exception->getMessage() );
		$needs_lazy_script = false;
	}

	// Since there is a limit to the number of bookmarks we can add, make sure any new ones we add get removed.
	foreach ( $bookmark_names as $bookmark_name ) {
		$html_processor->release_bookmark( $bookmark_name );
	}

	return $needs_lazy_script;
}

/**
 * Prints the script to lazy-load embeds.
 *
 * Load an embed's scripts when it approaches the viewport using an IntersectionObserver.
 *
 * @since 0.1.0
 */
function embed_optimizer_lazy_load_scripts(): void {
	wp_print_inline_script_tag( embed_optimizer_get_lazy_load_script(), array( 'type' => 'module' ) );
}

/**
 * Gets the script to lazy-load embeds.
 *
 * Load an embed's scripts when it approaches the viewport using an IntersectionObserver.
 *
 * @since 0.2.0
 */
function embed_optimizer_get_lazy_load_script(): string {
	$script = file_get_contents( __DIR__ . '/' . embed_optimizer_get_asset_path( 'lazy-load.js' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- It's a local filesystem path not a remote request.

	if ( false === $script ) {
		return '';
	}

	return $script;
}

/**
 * Prints the Optimization Detective installation notices.
 *
 * @since 0.2.0
 *
 * @param string $plugin_file Plugin file.
 */
function embed_optimizer_print_row_meta_install_notice( string $plugin_file ): void {
	$od_plugin_slug = 'optimization-detective';
	$od_plugin_file = "{$od_plugin_slug}/load.php";
	$od_plugin_name = 'Optimization Detective';
	if ( 'embed-optimizer/load.php' === $plugin_file && ! is_plugin_active( $od_plugin_file ) ) {
		if ( current_user_can( 'install_plugins' ) ) {
			$details_url = esc_url_raw(
				add_query_arg(
					array(
						'tab'       => 'plugin-information',
						'plugin'    => $od_plugin_slug,
						'TB_iframe' => 'true',
						'width'     => 600,
						'height'    => 550,
					),
					admin_url( 'plugin-install.php' )
				)
			);

			$link_start_tag = sprintf(
				'<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s">',
				esc_url( $details_url ),
				/* translators: %s: Plugin name and version. */
				esc_attr( sprintf( __( 'More information about %s', 'default' ), $od_plugin_name ) )
			);
		} else {
			/* translators: %s: Plugin name. */
			$aria_label  = sprintf( __( 'Visit plugin site for %s', 'default' ), $od_plugin_name );
			$details_url = __( 'https://wordpress.org/plugins/', 'default' ) . $od_plugin_slug . '/';

			$link_start_tag = sprintf(
				'<a href="%s" aria-label="%s" target="_blank">',
				esc_url( $details_url ),
				esc_attr( $aria_label )
			);
		}

		$message = str_replace(
			'<a>',
			$link_start_tag,
			__( 'This plugin performs best when <a>Optimization Detective</a> is also installed and active.', 'embed-optimizer' )
		);

		wp_admin_notice(
			'<p>' . $message . '</p>',
			array(
				'type'               => 'warning',
				'additional_classes' => array( 'inline', 'notice' ),
			)
		);
	} elseif ( $od_plugin_file === $plugin_file ) {
		if ( is_plugin_active( $od_plugin_file ) ) {
			printf(
				'<p><strong>%s</strong> %s</p>',
				esc_html__( 'Recommended by:', 'embed-optimizer' ),
				'Embed Optimizer'
			);
		} else {
			$message = __( 'This plugin is strongly recommended to be active by <strong>Embed Optimizer</strong>.', 'embed-optimizer' );
			wp_admin_notice(
				'<p>' . wp_kses( $message, array( 'strong' => array() ) ) . '</p>',
				array(
					'type'               => 'warning',
					'additional_classes' => array( 'inline', 'notice-alt' ),
				)
			);
		}
	}
}
add_action( 'after_plugin_row_meta', 'embed_optimizer_print_row_meta_install_notice', 20 );

/**
 * Displays the HTML generator tag for the Embed Optimizer plugin.
 *
 * See {@see 'wp_head'}.
 *
 * @since 0.1.0
 */
function embed_optimizer_render_generator(): void {
	// Use the plugin slug as it is immutable.
	echo '<meta name="generator" content="embed-optimizer ' . esc_attr( EMBED_OPTIMIZER_VERSION ) . '">' . "\n";
}

/**
 * Gets the path to a script or stylesheet.
 *
 * @since 0.4.0
 *
 * @param string      $src_path Source path, relative to plugin root.
 * @param string|null $min_path Minified path. If not supplied, then '.min' is injected before the file extension in the source path.
 * @return string URL to script or stylesheet.
 */
function embed_optimizer_get_asset_path( string $src_path, ?string $min_path = null ): string {
	if ( null === $min_path ) {
		// Note: wp_scripts_get_suffix() is not used here because we need access to both the source and minified paths.
		$min_path = (string) preg_replace( '/(?=\.\w+$)/', '.min', $src_path );
	}

	$force_src = false;
	if ( WP_DEBUG && ! file_exists( trailingslashit( __DIR__ ) . $min_path ) ) {
		$force_src = true;
		wp_trigger_error(
			__FUNCTION__,
			sprintf(
				/* translators: %s is the minified asset path */
				__( 'Minified asset has not been built: %s', 'embed-optimizer' ),
				$min_path
			),
			E_USER_WARNING
		);
	}

	if ( SCRIPT_DEBUG || $force_src ) {
		return $src_path;
	}

	return $min_path;
}