` to the src of YouTube embeds the site already has. * Nothing else: every existing parameter and the host stay as they were (a * youtube-nocookie.com embed stays on youtube-nocookie.com), and no YouTube * script is loaded by this plugin. * * Stored as advisorppc_settings['video_tracking'], default off. * * @package AdvisorPPC */ /** * Class AdvisorPPC_Video. */ class AdvisorPPC_Video { /** * Settings key. */ const OPTION = 'video_tracking'; /** * Iframe hosts this touches. Anything else is left alone. * * @var string[] */ const HOSTS = array( 'youtube.com', 'www.youtube.com', 'youtube-nocookie.com', 'www.youtube-nocookie.com', 'youtu.be', ); /** * Attach hooks. Runs on advisorppc_loaded. * * @return void */ public static function init() { /* * Display time only. Not oembed_result: that runs before WP_Embed * stores the markup in the oEmbed post-meta cache, so the parameters * would be cached and served to feeds, REST and admin, and still * served after the switch is turned off. */ add_filter( 'embed_oembed_html', array( __CLASS__, 'filter_html' ), 10, 1 ); if ( is_admin() ) { add_action( 'admin_init', array( __CLASS__, 'register_field' ), 20 ); add_filter( 'advisorppc_sanitize_settings', array( __CLASS__, 'filter_settings' ), 10, 2 ); } } /** * Whether the switch is on. * * @return bool */ public static function enabled() { return class_exists( 'AdvisorPPC_Options' ) && (bool) AdvisorPPC_Options::get( self::OPTION, false ); } /** * Whether embeds should be rewritten on this request. * * Front-end page views only: never in wp-admin, feeds, REST (the block * editor's embed preview), AJAX or cron, and never when a site has * excluded the request through `advisorppc_should_load_scripts`. * * @return bool */ public static function should_filter() { if ( ! self::enabled() ) { return false; } if ( is_admin() || is_feed() ) { return false; } if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) { return false; } if ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) { return false; } /** This filter is documented in includes/class-advisorppc-clickids.php */ return (bool) apply_filters( 'advisorppc_should_load_scripts', true ); } /** * Filter callback for embed_oembed_html. * * @param mixed $html Embed markup. * @return mixed */ public static function filter_html( $html ) { if ( ! is_string( $html ) || false === stripos( $html, ']*>/i', function ( $tag ) use ( $origin ) { return preg_replace_callback( '/(\ssrc\s*=\s*)(["\'])(.*?)\2/is', function ( $src ) use ( $origin ) { return $src[1] . $src[2] . AdvisorPPC_Video::add_params_to_url( $src[3], $origin ) . $src[2]; }, $tag[0], 1 ); }, $html ); return is_string( $result ) ? $result : $html; } /** * Add enablejsapi and origin to one YouTube URL. * * The host, path, fragment and every existing parameter are kept as they * are. A parameter the URL already carries is never added twice. A URL * on any other host comes back unchanged. * * @param string $url URL as it appears in the src attribute. * @param string $origin Site origin. * @return string */ public static function add_params_to_url( $url, $origin ) { // esc_url() writes & as &. Its # is not a fragment, so fold the // numeric forms into & before looking for one. $url = str_replace( array( '&', '&' ), '&', $url ); $decoded = str_replace( '&', '&', $url ); $parts = wp_parse_url( 0 === strpos( $decoded, '//' ) ? 'https:' . $decoded : $decoded ); if ( ! is_array( $parts ) || empty( $parts['host'] ) || ! in_array( strtolower( $parts['host'] ), self::HOSTS, true ) ) { return $url; } $existing = array(); if ( isset( $parts['query'] ) ) { foreach ( explode( '&', $parts['query'] ) as $pair ) { $pair = explode( '=', $pair, 2 ); $name = strtolower( rawurldecode( $pair[0] ) ); if ( '' !== $name ) { $existing[ $name ] = true; } } } $add = array(); if ( ! isset( $existing['enablejsapi'] ) ) { $add[] = 'enablejsapi=1'; } if ( ! isset( $existing['origin'] ) ) { $add[] = 'origin=' . rawurlencode( $origin ); } if ( empty( $add ) ) { return $url; } // Keep the separator style the markup already uses. $amp = false !== strpos( $url, '&' ) ? '&' : '&'; $fragment = ''; $hash = strpos( $url, '#' ); if ( false !== $hash ) { $fragment = substr( $url, $hash ); $url = substr( $url, 0, $hash ); } if ( false === strpos( $url, '?' ) ) { $joiner = '?'; } elseif ( '?' === substr( $url, -1 ) || substr( $url, -strlen( $amp ) ) === $amp ) { $joiner = ''; } else { $joiner = $amp; } return $url . $joiner . implode( $amp, $add ) . $fragment; } /** * Fold the checkbox into a settings form save. * * The field posts a hidden 0 ahead of the checkbox, so the key is present * whenever the form rendered it; a form that did not show it leaves the * stored value alone. * * @param mixed $settings Sanitized settings about to be stored. * @param mixed $input Raw submission. * @return mixed */ public static function filter_settings( $settings, $input ) { if ( is_array( $input ) && method_exists( 'AdvisorPPC_Settings', 'submission_allows' ) && ! AdvisorPPC_Settings::submission_allows( self::OPTION, $input ) ) { return $settings; } if ( is_array( $settings ) && is_array( $input ) && array_key_exists( self::OPTION, $input ) ) { $settings[ self::OPTION ] = '1' === (string) $input[ self::OPTION ]; } return $settings; } /** * Register the checkbox under the tracking tags section. * * @return void */ public static function register_field() { if ( ! class_exists( 'AdvisorPPC_Settings' ) ) { return; } add_settings_field( 'advisorppc_youtube_tracking', __( 'YouTube video tracking', 'advisorppc' ), array( __CLASS__, 'render_field' ), // The Tag IDs section lives on the Tags tab's page. method_exists( 'AdvisorPPC_Settings', 'page_for' ) ? AdvisorPPC_Settings::page_for( 'tags' ) : AdvisorPPC_Settings::PAGE, 'advisorppc_tag_ids', array( 'label_for' => 'advisorppc_youtube_tracking' ) ); } /** * Render the checkbox. * * @return void */ public static function render_field() { $name = AdvisorPPC_Options::OPTION_KEY . '[' . self::OPTION . ']'; ?>