晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。   林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。   见渔人,乃大惊,问所从来。具答之。便要还家,设酒杀鸡作食。村中闻有此人,咸来问讯。自云先世避秦时乱,率妻子邑人来此绝境,不复出焉,遂与外人间隔。问今是何世,乃不知有汉,无论魏晋。此人一一为具言所闻,皆叹惋。余人各复延至其家,皆出酒食。停数日,辞去。此中人语云:“不足为外人道也。”(间隔 一作:隔绝)   既出,得其船,便扶向路,处处志之。及郡下,诣太守,说如此。太守即遣人随其往,寻向所志,遂迷,不复得路。   南阳刘子骥,高尚士也,闻之,欣然规往。未果,寻病终。后遂无问津者。 .
Prv8 Shell
Server : Apache
System : Linux srv.rainic.com 4.18.0-553.47.1.el8_10.x86_64 #1 SMP Wed Apr 2 05:45:37 EDT 2025 x86_64
User : rainic ( 1014)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/stando/www/wp-content/plugins/wpseo-video/classes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //home/stando/www/wp-content/plugins/wpseo-video/classes/class-wpseo-video-sitemap.php
<?php
/**
 * All functionality for fetching video data and creating an XML video sitemap with it.
 *
 * @link https://codex.wordpress.org/oEmbed OEmbed Codex Article.
 * @link http://oembed.com/                 OEmbed Homepage.
 *
 * @package    WordPress SEO
 * @subpackage WordPress SEO Video
 */

/**
 * Wpseo_video_Video_Sitemap class.
 *
 * @package WordPress SEO Video
 * @since   0.1
 */
class WPSEO_Video_Sitemap {

	/**
	 * The maximum number of entries per sitemap page.
	 *
	 * @var int
	 */
	private $max_entries = 5;

	/**
	 * Name of the metabox tab.
	 *
	 * @var string
	 */
	private $metabox_tab;

	/**
	 * Option object.
	 *
	 * @var object
	 */
	protected $option_instance;

	/**
	 * Youtube video ID regex pattern.
	 *
	 * @var string
	 */
	public static $youtube_id_pattern = '[0-9a-zA-Z_-]+';

	/**
	 * Video extension list for use in regex pattern.
	 *
	 * @var string
	 *
	 * @todo - shouldn't this be a class constant ?
	 */
	public static $video_ext_pattern = 'mpg|mpeg|mp4|m4v|mov|ogv|wmv|asf|avi|ra|ram|rm|flv|swf';

	/**
	 * Image extension list for use in regex pattern.
	 *
	 * @var string
	 *
	 * @todo - shouldn't this be a class constant ?
	 */
	public static $image_ext_pattern = 'jpg|jpeg|jpe|gif|png';

	/**
	 * Constructor for the WPSEO_Video_Sitemap class.
	 *
	 * @todo  Deal with upgrade from license constant WPSEO_VIDEO_LICENSE
	 * @since 0.1
	 */
	public function __construct() {
		// Initialize the options.
		$this->option_instance = WPSEO_Option_Video::get_instance();

		$options = get_option( 'wpseo_video' );

		// Run upgrade routine.
		$this->upgrade();

		add_filter( 'wpseo_tax_meta_special_term_id_validation__video', array( $this, 'validate_video_tax_meta' ) );

		// Set content_width based on theme content_width or our option value if either is available.
		$content_width = $this->get_content_width();
		if ( $content_width !== false ) {
			$GLOBALS['content_width'] = $content_width;
		}
		unset( $content_width );

		add_action( 'setup_theme', array( $this, 'init' ) );
		add_action( 'admin_init', array( $this, 'init' ) );
		add_action( 'init', array( $this, 'register_sitemap' ), 20 ); // Register sitemap after cpts have been added.
		add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_item' ), 97 );
		add_filter( 'oembed_providers', array( $this, 'sync_oembed_providers' ) );

		if ( is_admin() ) {

			add_filter( 'wpseo_submenu_pages', array( $this, 'add_submenu_pages' ) );

			add_action( 'wp_insert_post', array( $this, 'update_video_post_meta' ), 12, 3 );

			$valid_pages = array(
				'edit.php',
				'post.php',
				'post-new.php',
			);
			if ( in_array( $GLOBALS['pagenow'], $valid_pages, true )
			     || apply_filters( 'wpseo_always_register_metaboxes_on_admin', false )
			) {
				$this->metabox_tab = new WPSEO_Video_Metabox();
			}

			add_action( 'admin_enqueue_scripts', array( $this, 'admin_video_enqueue_scripts' ) );

			add_action( 'admin_init', array( $this, 'admin_video_enqueue_styles' ) );

			add_action( 'wp_ajax_index_posts', array( $this, 'index_posts_callback' ) );

			add_action( 'wp_insert_post', array( $this, 'invalidate_sitemap' ) );

			// Maybe show 'Recommend re-index' admin notice.
			if ( get_transient( 'video_seo_recommend_reindex' ) === '1' ) {
				add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_ignore' ) );
				add_action( 'all_admin_notices', array( $this, 'recommend_force_index' ) );
				add_action( 'wp_ajax_videoseo_set_ignore', array( $this, 'set_ignore' ) );
			}
		}
		else {

			// OpenGraph.
			add_action( 'wpseo_opengraph', array( $this, 'opengraph' ) );
			add_filter( 'wpseo_opengraph_type', array( $this, 'opengraph_type' ), 10, 1 );
			add_action( 'wpseo_add_opengraph_additional_images', array( $this, 'opengraph_image' ), 15, 1 );
			add_filter( 'wpseo_html_namespaces', array( $this, 'add_video_namespaces' ) );

			// XML Sitemap Index addition.
			add_filter( 'wpseo_sitemap_index', array( $this, 'add_to_index' ) );

			if ( $options['fitvids'] === true ) {
				// Fitvids scripting.
				add_action( 'wp_head', array( $this, 'fitvids' ) );
			}

			if ( $options['disable_rss'] !== true ) {
				// MRSS.
				add_action( 'rss2_ns', array( $this, 'mrss_namespace' ) );
				add_action( 'rss2_item', array( $this, 'mrss_item' ), 10, 1 );
				add_filter( 'mrss_media', array( $this, 'mrss_add_video' ) );
			}
		}
	}

	/**
	 * Retrieve a value to use for content_width.
	 *
	 * @since 3.8.0
	 *
	 * @param int $default (Optional) Default value to use if value could not be determined.
	 *
	 * @return int|false Integer content width value or false if it could not be determined
	 *                   and no default was provided.
	 */
	public function get_content_width( $default = 0 ) {
		// If the theme or WP has set it, use what's already available.
		if ( ! empty( $GLOBALS['content_width'] ) ) {
			return (int) $GLOBALS['content_width'];
		}

		// If the user has set it in options, use that.
		$options              = get_option( 'wpseo_video' );
		$option_content_width = (int) $options['content_width'];
		if ( $option_content_width > 0 ) {
			return $option_content_width;
		}

		// Otherwise fall back to an arbitrary default if provided.
		// WP itself uses 500 for embeds, 640 for playlists and video shortcodes.
		if ( $default > 0 ) {
			return $default;
		}

		return false;
	}

	/**
	 * Method to invalidate the sitemap
	 *
	 * @param integer $post_id Post ID.
	 */
	public function invalidate_sitemap( $post_id ) {
		// If this is just a revision, don't invalidate the sitemap cache yet.
		if ( wp_is_post_revision( $post_id ) ) {
			return;
		}

		if ( ! WPSEO_Video_Utils::is_videoseo_active_for_posttype( get_post_type( $post_id ) ) ) {
			return;
		}

		WPSEO_Video_Wrappers::invalidate_sitemap( $this->video_sitemap_basename() );
	}

	/**
	 * When sitemap is coming out of the cache there is no stylesheet. Normally it will take the default stylesheet.
	 *
	 * This method is called by a filter that will set the video stylesheet.
	 *
	 * @param object $target_object Target object.
	 *
	 * @return object
	 */
	public function set_stylesheet_cache( $target_object ) {
		if ( property_exists( $target_object, 'renderer' ) ) {
			$target_object->renderer->set_stylesheet( $this->get_stylesheet_line() );
		}

		return $target_object;
	}

	/**
	 * Getter for stylesheet url
	 *
	 * @return string
	 */
	public function get_stylesheet_line() {
		$stylesheet_url = "\n" . '<?xml-stylesheet type="text/xsl" href="' . esc_url( $this->get_xsl_url() ) . '"?>';

		return $stylesheet_url;
	}

	/**
	 * Adds the fitvids JavaScript to the output if there's a video on the page that's supported by this script.
	 * Prevents fitvids being added when the JWPlayer plugin is active as they are incompatible.
	 *
	 * @todo  - check if we can remove the JW6. The JWP plugin does some checking and deactivating
	 * themselves, so if we can rely on that, all the better.
	 *
	 * @since 1.5.4
	 */
	public function fitvids() {
		if ( ! is_singular() || defined( 'JWP6' ) ) {
			return;
		}

		global $post;

		if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
			return;
		}

		$video = WPSEO_Meta::get_value( 'video_meta', $post->ID );

		if ( ! is_array( $video ) || $video === array() ) {
			return;
		}

		// Check if the current post contains a YouTube, Vimeo, Blip.tv or Viddler video, if it does, add the fitvids code.
		if ( in_array( $video['type'], array( 'youtube', 'vimeo', 'blip.tv', 'viddler', 'wistia' ), true ) ) {
			$file = 'js/jquery.fitvids.min.js';
			if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
				$file = 'js/jquery.fitvids.js';
			}

			wp_enqueue_script(
				'fitvids',
				plugins_url( $file, WPSEO_VIDEO_FILE ),
				array( 'jquery' ),
				WPSEO_VIDEO_VERSION,
				true // Load in footer.
			);
		}

		add_action( 'wp_footer', array( $this, 'fitvids_footer' ) );
	}

	/**
	 * The fitvids instantiation code.
	 *
	 * @since 1.5.4
	 */
	public function fitvids_footer() {
		global $post;

		// Try and use the post class to determine the container.
		$classes = get_post_class( '', $post->ID );
		$class   = 'post';
		if ( is_array( $classes ) && $classes !== array() ) {
			$class = $classes[0];
		}
		?>
		<script type="text/javascript">
			jQuery( document ).ready( function ( $ ) {
				$( ".<?php echo esc_attr( $class ); ?>" ).fitVids( {customSelector: "iframe.wistia_embed"} );
			} );
		</script>
		<?php
	}

	/**
	 * Registers the Video SEO submenu.
	 *
	 * @param array $submenu_pages Currently registered submenu pages.
	 *
	 * @return array Submenu pages with our submenu added.
	 */
	public function add_submenu_pages( $submenu_pages ) {
		$submenu_pages[] = array(
			'wpseo_dashboard',
			'Yoast SEO: Video SEO',
			'Video SEO',
			'wpseo_manage_options',
			'wpseo_video',
			array( $this, 'admin_panel' ),
		);

		return $submenu_pages;
	}

	/**
	 * Adds the rewrite for the video XML sitemap
	 *
	 * @since 0.1
	 */
	public function init() {
		// Get options to set the entries per page.
		$this->max_entries = $this->get_entries_per_page();

		// Add oEmbed providers.
		$this->add_oembed();

		// Only load the beacon when the License Manager is present.
		if ( class_exists( 'Yoast_Plugin_License_Manager' ) ) {
			$this->init_beacon();
		}
	}

	/**
	 * Initializes the HelpScout beacon
	 */
	private function init_beacon() {
		$page      = filter_input( INPUT_GET, 'page' );
		$query_var = ( $page ) ? $page : '';

		// Only add the helpscout beacon on Yoast SEO pages.
		if ( $query_var === 'wpseo_video' ) {
			$beacon = yoast_get_helpscout_beacon( $query_var );
			$beacon->add_setting( new WPSEO_Video_Beacon_Setting() );
			$beacon->register_hooks();
		}
	}

	/**
	 * Add VideoSeo Admin bar menu item
	 *
	 * @param object $wp_admin_bar Current admin bar.
	 */
	public function add_admin_bar_item( $wp_admin_bar ) {
		if ( $this->can_manage_options() === true ) {
			$wp_admin_bar->add_menu(
				array(
					'parent' => 'wpseo-settings',
					'id'     => 'wpseo-video',
					'title'  => __( 'Video SEO', 'yoast-video-seo' ),
					'href'   => admin_url( 'admin.php?page=wpseo_video' ),
				)
			);
		}
	}

	/**
	 * Register the video sitemap in the WPSEO sitemap class
	 *
	 * @since 1.7
	 */
	public function register_sitemap() {
		$basename = $this->video_sitemap_basename();

		// Register the sitemap.
		WPSEO_Video_Wrappers::register_sitemap( $basename, array( $this, 'build_video_sitemap' ) );
		WPSEO_Video_Wrappers::register_xsl( 'video', array( $this, 'build_video_sitemap_xsl' ) );

		if ( is_admin() ) {
			// Setting action for removing the transient on update options.
			WPSEO_Video_Wrappers::register_cache_clear_option( 'wpseo_video', $basename );
		}
		else {
			// Setting stylesheet for cached sitemap.
			add_action( 'wpseo_sitemap_stylesheet_cache_' . $basename, array( $this, 'set_stylesheet_cache' ) );
		}
	}

	/**
	 * Execute upgrade actions when needed
	 */
	public function upgrade() {

		$options = get_option( 'wpseo_video' );

		// Early bail if dbversion is equal to current version.
		if ( isset( $options['dbversion'] ) && version_compare( $options['dbversion'], WPSEO_VIDEO_VERSION, '==' ) ) {
			return;
		}

		// Upgrade to new option & meta classes.
		if ( ! isset( $options['dbversion'] ) || version_compare( $options['dbversion'], '1.6', '<' ) ) {
			$this->option_instance->clean();
			// Make sure our meta values are cleaned up even if WP SEO would have been upgraded already.
			WPSEO_Meta::clean_up();
		}

		// Re-add missing durations.
		if ( ! isset( $options['dbversion'] ) || ( version_compare( $options['dbversion'], '1.7', '<' ) && version_compare( $options['dbversion'], '1.6', '>' ) ) ) {
			WPSEO_Meta_Video::re_add_durations();
		}

		// Recommend force re-index.
		if ( isset( $options['dbversion'] ) && version_compare( $options['dbversion'], '4.0', '<' ) ) {
			set_transient( 'video_seo_recommend_reindex', 1 );
		}

		// Make sure version nr gets updated for any version without specific upgrades.
		// Re-get to make sure we have the latest version.
		$options = get_option( 'wpseo_video' );
		if ( version_compare( $options['dbversion'], WPSEO_VIDEO_VERSION, '<' ) ) {
			$options['dbversion'] = WPSEO_VIDEO_VERSION;
			update_option( 'wpseo_video', $options );
		}
	}

	/**
	 * Recommend re-index with force index checked
	 *
	 * @since 1.8.0
	 */
	public function recommend_force_index() {
		if ( ! $this->can_manage_options() ) {
			return;
		}

		printf(
			'
	<div class="error" id="videoseo-reindex">
		<p style="float: right;"><a href="javascript:videoseo_setIgnore(\'recommend_reindex\',\'videoseo-reindex\',\'%1$s\');" class="button fixit">%2$s</a></p>
		<p>%3$s</p>
	</div>',
			esc_js( wp_create_nonce( 'videoseo-ignore' ) ), // #1.
			esc_html__( 'Ignore.', 'yoast-video-seo' ), // #2.
			sprintf(
				/* translators: 1: link open tag, 2: link close tag. */
				esc_html__( 'The VideoSEO upgrade which was just applied contains a lot of improvements. It is strongly recommended that you %1$sre-index the video content on your website%2$s with the \'force reindex\' option checked.', 'yoast-video-seo' ),
				'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_video' ) ) . '">',
				'</a>'
			) // #3.
		);
	}

	/**
	 * Function used to remove the temporary admin notices for several purposes, dies on exit.
	 */
	public function set_ignore() {
		if ( ! $this->can_manage_options() ) {
			die( '-1' );
		}

		check_ajax_referer( 'videoseo-ignore' );
		delete_transient( 'video_seo_' . sanitize_text_field( $_POST['option'] ) );
		die( '1' );
	}

	/**
	 * Load other scripts for the admin in the Video SEO plugin
	 */
	public function admin_video_enqueue_scripts() {
		if ( isset( $_POST['reindex'] ) ) {
			wp_enqueue_script(
				'videoseo-admin-progress-bar',
				plugins_url( 'js/videoseo-admin-progressbar' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_VIDEO_FILE ),
				array( 'jquery' ),
				WPSEO_VIDEO_VERSION,
				true
			);
		}
	}

	/**
	 * Load styles for the admin in Video SEO
	 */
	public function admin_video_enqueue_styles() {
		if ( isset( $_POST['reindex'] ) ) {
			wp_enqueue_style(
				'videoseo-admin-progress-bar-css',
				plugins_url( 'css/videoseo-admin-progressbar' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_VIDEO_FILE ),
				array(),
				WPSEO_VIDEO_VERSION
			);
		}
	}

	/**
	 * Load a small js file to facilitate ignoring admin messages
	 */
	public function admin_enqueue_scripts_ignore() {
		if ( ! $this->can_manage_options() ) {
			return;
		}

		wp_enqueue_script( 'videoseo-admin-global-script', plugins_url( 'js/videoseo-admin-global' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_VIDEO_FILE ), array( 'jquery' ), WPSEO_VIDEO_VERSION, true );
	}

	/**
	 * AJAX request handler for reindex posts
	 */
	public function index_posts_callback() {
		if ( wp_verify_nonce( $_POST['nonce'], 'videoseo-ajax-nonce-for-reindex' ) ) {
			if ( isset( $_POST['type'] ) && $_POST['type'] === 'total_posts' ) {
				$options = get_option( 'wpseo_video' );
				$total   = 0;
				foreach ( $options['videositemap_posttypes'] as $post_type ) {
					$total += wp_count_posts( $post_type )->publish;
				}
				echo (int) $total;
			}
			elseif ( isset( $_POST['type'] ) && $_POST['type'] === 'index' ) {
				$start_time = time();

				$post_defaults = array(
					'portion' => 5,
					'start'   => 0,
					'total'   => 0,
				);

				foreach ( $post_defaults as $key => $default ) {
					if ( isset( $_POST[ $key ] ) && is_numeric( $_POST[ $key ] ) ) {
						${$key} = (int) $_POST[ $key ];
					}
					else {
						${$key} = $default;
					}
				}

				$this->reindex( $portion, $start, $total );

				$end_time = time();

				// Return time in seconds that we've needed to index.
				echo (int) ( ( $end_time - $start_time ) + 1 );
			}
		}

		exit;
	}


	/**
	 * Returns the basename of the video-sitemap, the first portion of the name of the sitemap "file".
	 *
	 * Defaults to video, but it's possible to override it by using the YOAST_VIDEO_SITEMAP_BASENAME constant.
	 *
	 * @since 1.5.3
	 *
	 * @return string $basename
	 */
	public function video_sitemap_basename() {
		$basename = 'video';

		if ( post_type_exists( 'video' ) ) {
			$basename = 'yoast-video';
		}

		if ( defined( 'YOAST_VIDEO_SITEMAP_BASENAME' ) ) {
			$basename = YOAST_VIDEO_SITEMAP_BASENAME;
		}

		return $basename;
	}

	/**
	 * Return the Video Sitemap URL
	 *
	 * @since 1.2.1
	 * @since 3.8.0 The $extra parameter was added.
	 *
	 * @param string $extra Optionally suffix to add to the filename part of the sitemap url.
	 *
	 * @return string The URL to the video Sitemap.
	 */
	public function sitemap_url( $extra = '' ) {
		$sitemap = $this->video_sitemap_basename() . '-sitemap' . $extra . '.xml';

		return WPSEO_Video_Wrappers::xml_sitemaps_base_url( $sitemap );
	}

	/**
	 * Adds the video XML sitemap to the Index Sitemap.
	 *
	 * @since  0.1
	 *
	 * @param string $str String with the filtered additions to the index sitemap in it.
	 *
	 * @return string $str String with the Video XML sitemap additions to the index sitemap in it.
	 */
	public function add_to_index( $str ) {
		$options = get_option( 'wpseo_video' );

		$base = $GLOBALS['wp_rewrite']->using_index_permalinks() ? 'index.php/' : '';

		if ( is_array( $options['videositemap_posttypes'] ) && $options['videositemap_posttypes'] !== array() ) {
			// Use fields => ids to limit the overhead of fetching entire post objects, fetch only an array of ids instead to count.
			$args = array(
				'post_type'      => $options['videositemap_posttypes'],
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'meta_key'       => '_yoast_wpseo_video_meta',
				'meta_compare'   => '!=',
				'meta_value'     => 'none',
				'fields'         => 'ids',
			);
			// Copy these args to be used and modify later.
			$date_args = $args;

			$video_ids = get_posts( $args );
			$count     = count( $video_ids );

			if ( $count > 0 ) {
				$n = ( $count > $this->max_entries ) ? (int) ceil( $count / $this->max_entries ) : 1;
				for ( $i = 0; $i < $n; $i ++ ) {
					$count = ( $n > 1 ) ? ( $i + 1 ) : '';

					if ( empty( $count ) || $count === $n ) {
						$date_args['fields']         = 'all';
						$date_args['posts_per_page'] = 1;
						$date_args['offset']         = 0;
						$date_args['order']          = 'DESC';
						$date_args['orderby']        = 'modified';
					}
					else {
						$date_args['fields']         = 'all';
						$date_args['posts_per_page'] = 1;
						$date_args['offset']         = ( ( $this->max_entries * ( $i + 1 ) ) - 1 );
						$date_args['order']          = 'ASC';
						$date_args['orderby']        = 'modified';
					}
					$posts = get_posts( $date_args );
					$date  = date( 'c', strtotime( $posts[0]->post_modified_gmt ) );

					$text = ( $count > 1 ) ? $count : '';
					$str  .= '<sitemap>' . "\n";
					$str  .= '<loc>' . $this->sitemap_url( $text ) . '</loc>' . "\n";
					$str  .= '<lastmod>' . $date . '</lastmod>' . "\n";
					$str  .= '</sitemap>' . "\n";
				}
			}
		}

		return $str;
	}

	/**
	 * Adds oembed endpoints for supported video platforms that are not supported by core.
	 *
	 * @since 1.3.5
	 */
	public function add_oembed() {
		// @todo - check with official plugin.
		// Wistia.
		$options      = get_option( 'wpseo_video' );
		$wistia_regex = '`(?:http[s]?:)?//[^/]*(wistia\.(com|net)|wi\.st#CUSTOM_URL#)/(medias|embed)/.*`i';
		if ( $options['wistia_domain'] !== '' ) {
			$wistia_regex = str_replace( '#CUSTOM_URL#', '|' . preg_quote( $options['wistia_domain'], '`' ), $wistia_regex );
		}
		else {
			$wistia_regex = str_replace( '#CUSTOM_URL#', '', $wistia_regex );
		}
		wp_oembed_add_provider( $wistia_regex, 'http://fast.wistia.com/oembed', true );

		// Viddler - WP native support removed in WP 4.0.
		wp_oembed_add_provider( '`http[s]?://(?:www\.)?viddler\.com/.*`i', 'http://lab.viddler.com/services/oembed/', true );

		// Screenr.
		wp_oembed_add_provider( '`http[s]?://(?:www\.)?screenr\.com/.*`i', 'http://www.screenr.com/api/oembed.{format}', true );

		// EVS.
		$evs_location = get_option( 'evs_location' );
		if ( $evs_location && ! empty( $evs_location ) ) {
			wp_oembed_add_provider( $evs_location . '/*', $evs_location . '/oembed.php', false );
		}
	}

	/**
	 * Synchronize the WP native oembed providers list for various WP versions.
	 *
	 * If VideoSEO users choose to stay on a lower WP version, they will still get the benefit of improved
	 * oembed regexes and provider compatibility this way.
	 *
	 * @param  array $providers Providers.
	 *
	 * @return array
	 */
	public function sync_oembed_providers( $providers ) {

		// Support SSL urls for flick shortdomain (natively added in WP4.0).
		if ( isset( $providers['http://flic.kr/*'] ) ) {
			unset( $providers['http://flic.kr/*'] );
			$providers['#https?://flic\.kr/.*#i'] = array( 'https://www.flickr.com/services/oembed/', true );
		}

		// Change to SSL for oembed provider domain (natively changed in WP4.0).
		if ( isset( $providers['#https?://(www\.)?flickr\.com/.*#i'] ) && strpos( $providers['#https?://(www\.)?flickr\.com/.*#i'][0], 'https' ) !== 0 ) {
			$providers['#https?://(www\.)?flickr\.com/.*#i'] = array( 'https://www.flickr.com/services/oembed/', true );
		}

		// Allow any vimeo subdomain (natively changed in WP3.9).
		if ( isset( $providers['#https?://(www\.)?vimeo\.com/.*#i'] ) ) {
			unset( $providers['#https?://(www\.)?vimeo\.com/.*#i'] );
			$providers['#https?://(.+\.)?vimeo\.com/.*#i'] = array( 'http://vimeo.com/api/oembed.{format}', true );
		}

		// Support SSL urls for wordpress.tv (natively added in WP4.0).
		if ( isset( $providers['http://wordpress.tv/*'] ) ) {
			unset( $providers['http://wordpress.tv/*'] );
			$providers['#https?://wordpress.tv/.*#i'] = array( 'http://wordpress.tv/oembed/', true );
		}

		return $providers;
	}

	/**
	 * Add the MRSS namespace to the RSS feed.
	 *
	 * @since 0.1
	 */
	public function mrss_namespace() {
		echo ' xmlns:media="http://search.yahoo.com/mrss/" ';
	}

	/**
	 * Add the MRSS info to the feed
	 *
	 * Based upon the MRSS plugin developed by Andy Skelton
	 *
	 * @since     0.1
	 * @copyright Andy Skelton
	 */
	public function mrss_item() {
		global $mrss_gallery_lookup;
		$media  = array();
		$lookup = array();

		// Honor the feed settings. Don't include any media that isn't in the feed.
		if ( get_option( 'rss_use_excerpt' ) || ! strlen( get_the_content() ) ) {
			ob_start();
			the_excerpt_rss();
			$content = ob_get_clean();
		}
		else {
			// If any galleries are processed, we need to capture the attachment IDs.
			add_filter( 'wp_get_attachment_link', array( $this, 'mrss_gallery_lookup' ), 10, 5 );
			$content = apply_filters( 'the_content', get_the_content() );
			remove_filter( 'wp_get_attachment_link', array( $this, 'mrss_gallery_lookup' ), 10, 5 );
			$lookup = $mrss_gallery_lookup;
			unset( $mrss_gallery_lookup );
		}

		$images = 0;
		if ( preg_match_all( '`<img ([^>]+)>`', $content, $matches ) ) {
			foreach ( $matches[1] as $attrs ) {
				$item = array();
				$img  = array();
				// Construct $img array from <img> attributes.
				$attributes = wp_kses_hair( $attrs, array( 'http' ) );
				foreach ( $attributes as $attr ) {
					$img[ $attr['name'] ] = $attr['value'];
				}
				unset( $attributes );

				// Skip emoticons and images without source attribute.
				if ( ! isset( $img['src'] ) || ( isset( $img['class'] ) && false !== strpos( $img['class'], 'wp-smiley' ) ) ) {
					continue;
				}

				$img['src'] = $this->mrss_url( $img['src'] );

				$id = false;
				if ( isset( $lookup[ $img['src'] ] ) ) {
					$id = $lookup[ $img['src'] ];
				}
				elseif ( isset( $img['class'] ) && preg_match( '`wp-image-(\d+)`', $img['class'], $match ) ) {
					$id = $match[1];
				}
				if ( $id ) {
					// It's an attachment, so we will get the URLs, title, and description from functions.
					$attachment =& get_post( $id );
					$src        = wp_get_attachment_image_src( $id, 'full' );
					if ( ! empty( $src[0] ) ) {
						$img['src'] = $src[0];
					}
					$thumbnail = wp_get_attachment_image_src( $id, 'thumbnail' );
					if ( ! empty( $thumbnail[0] ) && $thumbnail[0] !== $img['src'] ) {
						$img['thumbnail'] = $thumbnail[0];
					}
					$title = get_the_title( $id );
					if ( ! empty( $title ) ) {
						$img['title'] = trim( $title );
					}
					if ( ! empty( $attachment->post_excerpt ) ) {
						$img['description'] = trim( $attachment->post_excerpt );
					}
				}
				// If this is the first image in the markup, make it the post thumbnail.
				if ( ++ $images === 1 ) {
					if ( isset( $img['thumbnail'] ) ) {
						$media[]['thumbnail']['attr']['url'] = $img['thumbnail'];
					}
					else {
						$media[]['thumbnail']['attr']['url'] = $img['src'];
					}
				}

				$item['content']['attr']['url']    = $img['src'];
				$item['content']['attr']['medium'] = 'image';
				if ( ! empty( $img['title'] ) ) {
					$item['content']['children']['title']['attr']['type'] = 'html';
					$item['content']['children']['title']['children'][]   = $img['title'];
				}
				elseif ( ! empty( $img['alt'] ) ) {
					$item['content']['children']['title']['attr']['type'] = 'html';
					$item['content']['children']['title']['children'][]   = $img['alt'];
				}
				if ( ! empty( $img['description'] ) ) {
					$item['content']['children']['description']['attr']['type'] = 'html';
					$item['content']['children']['description']['children'][]   = $img['description'];
				}
				if ( ! empty( $img['thumbnail'] ) ) {
					$item['content']['children']['thumbnail']['attr']['url'] = $img['thumbnail'];
				}
				$media[] = $item;
			}
		}

		$media = apply_filters( 'mrss_media', $media );
		$this->mrss_print( $media );
	}

	/**
	 * @todo Properly document
	 *
	 * @param string $url Variable to evaluate for URL.
	 *
	 * @return string
	 */
	public function mrss_url( $url ) {
		if ( preg_match( '`^(?:http[s]?:)//`', $url ) ) {
			return $url;
		}
		else {
			return home_url( $url );
		}
	}

	/**
	 * @todo Properly document
	 *
	 * @param string $link Link tag.
	 * @param mixed  $id   ID to lookup.
	 *
	 * @return mixed
	 */
	public function mrss_gallery_lookup( $link, $id ) {
		if ( preg_match( '` src="([^"]+)"`', $link, $matches ) ) {
			$GLOBALS['mrss_gallery_lookup'][ $matches[1] ] = $id;
		}

		return $link;
	}

	/**
	 * @todo Properly document
	 *
	 * @param mixed $media Media.
	 */
	public function mrss_print( $media ) {
		if ( ! empty( $media ) ) {
			foreach ( (array) $media as $element ) {
				$this->mrss_print_element( $element );
			}
		}
		echo "\n";
	}

	/**
	 * @todo Properly document
	 *
	 * @param array $element Element.
	 * @param int   $indent  Ident.
	 */
	public function mrss_print_element( $element, $indent = 2 ) {
		echo "\n";
		foreach ( (array) $element as $name => $data ) {
			echo str_repeat( "\t", $indent ) . '<media:' . esc_attr( $name );

			if ( is_array( $data['attr'] ) && $data['attr'] !== array() ) {
				foreach ( $data['attr'] as $attr => $value ) {
					echo ' ' . esc_attr( $attr ) . '="' . esc_attr( ent2ncr( $value ) ) . '"';
				}
			}
			if ( is_array( $data['children'] ) && $data['children'] !== array() ) {
				$nl = false;
				echo '>';
				foreach ( $data['children'] as $_name => $_data ) {
					if ( is_int( $_name ) ) {
						echo ent2ncr( esc_html( $_data ) );
					}
					else {
						$nl = true;
						$this->mrss_print_element( array( $_name => $_data ), ( $indent + 1 ) );
					}
				}
				if ( $nl ) {
					echo "\n" . str_repeat( "\t", $indent );
				}
				echo '</media:' . esc_attr( $name ) . '>';
			}
			else {
				echo ' />';
			}
		}
	}

	/**
	 * Add the video output to the MRSS feed.
	 *
	 * @since 0.1
	 *
	 * @param array $media Media.
	 *
	 * @return array
	 */
	public function mrss_add_video( $media ) {
		global $post;

		if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
			return $media;
		}

		$video = WPSEO_Meta::get_value( 'video_meta', $post->ID );

		if ( ! is_array( $video ) || $video === array() ) {
			return $media;
		}

		$video_duration = WPSEO_Meta::get_value( 'videositemap-duration', $post->ID );
		if ( $video_duration == 0 && isset( $video['duration'] ) ) {
			$video_duration = $video['duration'];
		}

		$item['content']['attr']['url']                             = $video['player_loc'];
		$item['content']['attr']['duration']                        = $video_duration;
		$item['content']['children']['player']['attr']['url']       = $video['player_loc'];
		$item['content']['children']['title']['attr']['type']       = 'html';
		$item['content']['children']['title']['children'][]         = esc_html( $video['title'] );
		$item['content']['children']['description']['attr']['type'] = 'html';
		$item['content']['children']['description']['children'][]   = esc_html( $video['description'] );
		$item['content']['children']['thumbnail']['attr']['url']    = $video['thumbnail_loc'];
		$item['content']['children']['keywords']['children'][]      = implode( ',', $video['tag'] );
		array_unshift( $media, $item );

		return $media;
	}

	/**
	 * Parse the content of a post or term description.
	 *
	 * @since      1.3
	 * @see        WPSEO_Video_Analyse_Post
	 *
	 * @param string $content The content to parse for videos.
	 * @param array  $vid     The video array to update.
	 * @param array  $old_vid The former video array.
	 * @param mixed  $post    The post object or the post id of the post to analyse.
	 *
	 * @return array $vid
	 */
	public function index_content( $content, $vid, $old_vid = array(), $post = null ) {
		$index = new WPSEO_Video_Analyse_Post( $content, $vid, $old_vid, $post );

		return $index->get_vid_info();
	}

	/**
	 * Check and, if applicable, update video details for a term description
	 *
	 * @since 1.3
	 *
	 * @param object  $term The term to check the description and possibly update the video details for.
	 * @param boolean $echo Whether or not to echo the performed actions.
	 *
	 * @return mixed $vid The video array that was just stored, or "none" if nothing was stored
	 *                    or false if not applicable.
	 */
	public function update_video_term_meta( $term, $echo = false ) {
		$options = array_merge( WPSEO_Options::get_all(), get_option( 'wpseo_video' ) );

		if ( ! is_array( $options['videositemap_taxonomies'] ) || $options['videositemap_taxonomies'] === array() ) {
			return false;
		}

		if ( ! in_array( $term->taxonomy, $options['videositemap_taxonomies'], true ) ) {
			return false;
		}

		$tax_meta = get_option( 'wpseo_taxonomy_meta' );
		$old_vid  = array();
		if ( ! isset( $_POST['force'] ) ) {
			if ( isset( $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] ) ) {
				$old_vid = $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ];
			}
		}

		$vid = array();

		$title = WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'wpseo_title' );
		if ( empty( $title ) && isset( $options[ 'title-' . $term->taxonomy ] ) && $options[ 'title-' . $term->taxonomy ] !== '' ) {
			$title = wpseo_replace_vars( $options[ 'title-' . $term->taxonomy ], (array) $term );
		}
		if ( empty( $title ) ) {
			$title = $term->name;
		}
		$vid['title'] = htmlspecialchars( $title );

		$vid['description'] = WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'wpseo_metadesc' );
		if ( ! $vid['description'] ) {
			$vid['description'] = esc_attr( preg_replace( '`\s+`', ' ', wp_html_excerpt( strip_shortcodes( get_term_field( 'description', $term->term_id, $term->taxonomy ) ), 300 ) ) );
		}

		$vid['publication_date'] = date( 'Y-m-d\TH:i:s+00:00' );

		// Concatenate genesis intro text and term description to index the videos for both.
		$genesis_term_meta = get_option( 'genesis-term-meta' );

		$content = '';
		if ( isset( $genesis_term_meta[ $term->term_id ]['intro_text'] ) && $genesis_term_meta[ $term->term_id ]['intro_text'] ) {
			$content .= $genesis_term_meta[ $term->term_id ]['intro_text'];
		}

		$content .= "\n" . $term->description;
		$content = stripslashes( $content );

		$vid = $this->index_content( $content, $vid, $old_vid, null );

		if ( $vid !== 'none' ) {
			$tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] = $vid;
			// Don't bother with the complete tax meta validation.
			$tax_meta['wpseo_already_validated'] = true;
			update_option( 'wpseo_taxonomy_meta', $tax_meta );

			if ( $echo ) {
				$link = get_term_link( $term );
				if ( ! is_wp_error( $link ) ) {
					echo 'Updated <a href="' . esc_url( $link ) . '">' . esc_html( $vid['title'] ) . '</a> - ' . esc_html( $vid['type'] ) . '<br/>';
				}
			}
		}

		return $vid;
	}

	/**
	 * (Don't) validate the _video taxonomy metadata array
	 * Doesn't actually validate it atm, but having this function hooked in *does* make sure that the
	 * _video taxonomy metadata is not removed as it otherwise would be (by the normal taxonomy meta validation).
	 *
	 * @since 1.6
	 *
	 * @param  array $tax_meta_data Received _video tax metadata.
	 *
	 * @return array  Validated _video tax metadata
	 */
	public function validate_video_tax_meta( $tax_meta_data ) {
		return $tax_meta_data;
	}

	/**
	 * Check and, if applicable, update video details for a post
	 *
	 * @since 0.1
	 * @since 3.8 The $echo parameter was removed and the $post and $update parameters
	 *            added to be in line with the parameters received from the hook this
	 *            method is tied to.
	 *
	 * @param int      $post_id The post ID to check and possibly update the video details for.
	 * @param \WP_Post $post    The post object.
	 * @param boolean  $update  Whether this is an existing post being updated or not.
	 *
	 * @return mixed $vid The video array that was just stored, string "none" if nothing was stored
	 *                    or false if not applicable.
	 */
	public function update_video_post_meta( $post_id, $post = null, $update = null ) {
		global $wp_query;

		if ( ( ! isset( $post ) || ! ( $post instanceof WP_Post ) ) && is_numeric( $post_id ) ) {
			$post = get_post( $post_id );
		}

		if ( isset( $post ) && ( ! ( $post instanceof WP_Post ) || ! isset( $post->ID ) ) ) {
			return false;
		}

		if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
			return false;
		}

		$options = array_merge( WPSEO_Options::get_all(), get_option( 'wpseo_video' ) );

		$old_vid = array();
		if ( ! isset( $_POST['force'] ) ) {
			$old_vid = WPSEO_Meta::get_value( 'video_meta', $post->ID );
		}

		$title = WPSEO_Meta::get_value( 'title', $post->ID );
		if ( ! is_string( $title ) || $title === '' ) {
			if ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) {
				$title = wpseo_replace_vars( $options[ 'title-' . $post->post_type ], (array) $post );
			}
			else {
				$title = wpseo_replace_vars( '%%title%% - %%sitename%%', (array) $post );
			}
		}

		if ( ! is_string( $title ) || $title === '' ) {
			$title = $post->post_title;
		}

		$vid = array();

		// @todo [JRF->Yoast] Verify if this is really what we want. What about non-hierarchical custom post types ? and are we adjusting the main query output now ? could this cause bugs for others ?
		if ( $post->post_type === 'post' ) {
			$wp_query->is_single = true;
			$wp_query->is_page   = false;
		}
		else {
			$wp_query->is_single = false;
			$wp_query->is_page   = true;
		}

		$vid['post_id'] = $post->ID;

		$vid['title']            = htmlspecialchars( $title );
		$vid['publication_date'] = mysql2date( 'Y-m-d\TH:i:s+00:00', $post->post_date_gmt );

		$vid['description'] = WPSEO_Meta::get_value( 'metadesc', $post->ID );
		if ( ! is_string( $vid['description'] ) || $vid['description'] === '' ) {
			if ( isset( $options[ 'metadesc-' . $post->post_type ] ) && $options[ 'metadesc-' . $post->post_type ] !== '' ) {
				$vid['description'] = wpseo_replace_vars( $options[ 'metadesc-' . $post->post_type ], (array) $post );
			}
			else {
				$vid['description'] = esc_attr( preg_replace( '`\s+`', ' ', wp_html_excerpt( strip_shortcodes( $post->post_content ), 300 ) ) );
			}
		}

		$vid = $this->index_content( $post->post_content, $vid, $old_vid, $post );

		if ( 'none' !== $vid ) {
			// Shouldn't be needed, but just in case.
			if ( isset( $vid['__add_to_content'] ) ) {
				unset( $vid['__add_to_content'] );
			}

			if ( ! isset( $vid['thumbnail_loc'] ) || empty( $vid['thumbnail_loc'] ) ) {
				$img = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' );
				if ( strpos( $img[0], 'http' ) !== 0 ) {
					$vid['thumbnail_loc'] = get_site_url( null, $img[0] );
				}
				else {
					$vid['thumbnail_loc'] = $img[0];
				}
			}

			// Grab the metadata from the post.
			if ( isset( $_POST['yoast_wpseo_videositemap-category'] ) && ! empty( $_POST['yoast_wpseo_videositemap-category'] ) ) {
				$vid['category'] = sanitize_text_field( $_POST['yoast_wpseo_videositemap-category'] );
			}
			else {
				$cats = wp_get_object_terms( $post->ID, 'category', array( 'fields' => 'names' ) );
				if ( isset( $cats[0] ) ) {
					$vid['category'] = $cats[0];
				}
				unset( $cats );
			}

			$tags = wp_get_object_terms( $post->ID, 'post_tag', array( 'fields' => 'names' ) );

			if ( isset( $_POST['yoast_wpseo_videositemap-tags'] ) && ! empty( $_POST['yoast_wpseo_videositemap-tags'] ) ) {
				$extra_tags = explode( ',', sanitize_text_field( $_POST['yoast_wpseo_videositemap-tags'] ) );
				$tags       = array_merge( $extra_tags, $tags );
			}

			$tag = array();
			if ( is_array( $tags ) ) {
				foreach ( $tags as $t ) {
					$tag[] = $t;
				}
			}
			elseif ( isset( $cats[0] ) ) {
				$tag[] = $cats[0]->name;
			}

			$focuskw = WPSEO_Meta::get_value( 'focuskw', $post->ID );
			if ( ! empty( $focuskw ) ) {
				$tag[] = $focuskw;
			}
			$vid['tag'] = $tag;

			if ( WPSEO_Video_Wrappers::is_development_mode() ) {
				error_log( 'Updated [' . esc_html( $post->post_title ) . '](' . esc_url( add_query_arg( array( 'p' => $post->ID ), home_url() ) ) . ') - ' . esc_html( $vid['type'] ) );
			}
		}

		WPSEO_Meta::set_value( 'video_meta', $vid, $post->ID );

		return $vid;
	}

	/**
	 * Check whether the current visitor is really Google or Bing's bot by doing a reverse DNS lookup
	 *
	 * @since 1.2.2
	 *
	 * @return boolean
	 */
	public function is_valid_bot() {
		if ( preg_match( '`(Google|bing)bot`', sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ), $match ) ) {
			$hostname = gethostbyaddr( sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) );

			if (
				( $match[1] === 'Google' && preg_match( '`googlebot\.com$`', $hostname ) && gethostbyname( $hostname ) === $_SERVER['REMOTE_ADDR'] ) ||
				( $match[1] === 'bing' && preg_match( '`search\.msn\.com$`', $hostname ) && gethostbyname( $hostname ) === $_SERVER['REMOTE_ADDR'] )
			) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get the server protocol.
	 *
	 * @since 4.1.0
	 *
	 * @return string
	 */
	protected function get_server_protocol() {
		$protocol = 'HTTP/1.1';
		if ( isset( $_SERVER['SERVER_PROTOCOL'] ) && $_SERVER['SERVER_PROTOCOL'] !== '' ) {
			$protocol = sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) );
		}

		return $protocol;
	}

	/**
	 * Outputs the XSL file
	 */
	public function build_video_sitemap_xsl() {
		$protocol = $this->get_server_protocol();

		// Force a 200 header and replace other status codes.
		header( $protocol . ' 200 OK', true, 200 );

		// Set the right content / mime type.
		header( 'Content-Type: text/xml' );

		// Prevent the search engines from indexing the XML Sitemap.
		header( 'X-Robots-Tag: noindex, follow', true );

		// Make the browser cache this file properly.
		header( 'Pragma: public' );
		header( 'Cache-Control: maxage=' . YEAR_IN_SECONDS );
		header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', ( time() + YEAR_IN_SECONDS ) ) . ' GMT' );

		readfile( plugin_dir_path( WPSEO_VIDEO_FILE ) . 'xml-video-sitemap.xsl' );

		die();
	}

	/**
	 * The main function of this class: it generates the XML sitemap's contents.
	 *
	 * @since 0.1
	 */
	public function build_video_sitemap() {
		$options  = get_option( 'wpseo_video' );
		$protocol = $this->get_server_protocol();

		// Restrict access to the video sitemap to admins and valid bots.
		if ( $options['cloak_sitemap'] === true && ( ! $this->can_manage_options() && ! $this->is_valid_bot() ) ) {
			header( $protocol . ' 403 Forbidden', true, 403 );
			wp_die( "We're sorry, access to our video sitemap is restricted to site admins and valid Google & Bing bots." );
		}

		// Force a 200 header and replace other status codes.
		header( $protocol . ' 200 OK', true, 200 );

		$output = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">' . "\n";

		$printed_post_ids = array();

		$steps  = $this->max_entries;
		$n      = (int) get_query_var( 'sitemap_n' );
		$offset = ( $n > 1 ) ? ( ( $n - 1 ) * $this->max_entries ) : 0;
		$total  = ( $offset + $this->max_entries );

		if ( is_array( $options['videositemap_posttypes'] ) && $options['videositemap_posttypes'] !== array() ) {
			// Set the initial args array to get videos in chunks.
			$args = array(
				'post_type'      => $options['videositemap_posttypes'],
				'post_status'    => 'publish',
				'posts_per_page' => $steps,
				'offset'         => $offset,
				'meta_key'       => '_yoast_wpseo_video_meta',
				'meta_compare'   => '!=',
				'meta_value'     => 'none',
				'order'          => 'ASC',
				'orderby'        => 'post_modified',
			);

			/*
				@TODO: add support to tax video to honor pages
				add a bool to the while loop to see if tax has been processed
				if $items is empty the posts are done so move on to tax

				do some math between $printed_post_ids and $this-max_entries to figure out how many from tax to add to this pagination
			*/

			// Add entries to the sitemap until the total is hit (rounded up by nearest $steps).
			$items = get_posts( $args );
			while ( ( $total > $offset ) && $items ) {

				if ( is_array( $items ) && $items !== array() ) {
					foreach ( $items as $item ) {
						if ( ! is_object( $item ) || in_array( $item->ID, $printed_post_ids, true ) ) {
							continue;
						}
						else {
							$printed_post_ids[] = $item->ID;
						}

						if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $item->ID ) === '1' ) {
							continue;
						}

						$disable = WPSEO_Meta::get_value( 'videositemap-disable', $item->ID );
						if ( $disable === 'on' ) {
							continue;
						}

						$video = WPSEO_Meta::get_value( 'video_meta', $item->ID );

						$video = WPSEO_Video_Utils::get_video_image( $item->ID, $video );

						// When we don't have a thumbnail and either a player_loc or a content_loc, skip this video.
						if ( ! isset( $video['thumbnail_loc'] )
						     || ( ! isset( $video['player_loc'] ) && ! isset( $video['content_loc'] ) )
						) {
							continue;
						}

						$video_duration = WPSEO_Meta::get_value( 'videositemap-duration', $item->ID );
						if ( $video_duration > 0 ) {
							$video['duration'] = $video_duration;
						}

						$video['permalink'] = get_permalink( $item );

						$rating = apply_filters( 'wpseo_video_rating', WPSEO_Meta::get_value( 'videositemap-rating', $item->ID ) );
						if ( $rating && WPSEO_Meta_Video::sanitize_rating( null, $rating, WPSEO_Meta_Video::$meta_fields['video']['videositemap-rating'] ) ) {
							$video['rating'] = number_format( $rating, 1 );
						}

						$video['family_friendly'] = 'yes';
						if ( WPSEO_Video_Utils::is_video_family_friendly( $item->ID ) === false ) {
							$video['family_friendly'] = 'no';
						}

						$video['author'] = $item->post_author;

						$output .= $this->print_sitemap_line( $video, $item );
					}
				}

				// Update these args for the next iteration.
				$offset         = ( $offset + $steps );
				$args['offset'] += $steps;
				$items          = get_posts( $args );
			}
		}

		$tax_meta = get_option( 'wpseo_taxonomy_meta' );
		$terms    = array();
		if ( is_array( $options['videositemap_taxonomies'] ) && $options['videositemap_taxonomies'] !== array() ) {
			// Below is a fix for a nasty bug in WooCommerce: https://github.com/woothemes/woocommerce/issues/3807.
			$options['videositemap_taxonomies'][0] = '';
			$terms                                 = get_terms( $options['videositemap_taxonomies'] );
		}

		if ( is_array( $terms ) && $terms !== array() ) {
			foreach ( $terms as $term ) {
				if ( is_object( $term ) && isset( $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] ) ) {
					$video = $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ];
					if ( is_array( $video ) ) {
						$video['permalink'] = get_term_link( $term, $term->taxonomy );
						$video['category']  = $term->name;
						$output             .= $this->print_sitemap_line( $video, $term );
					}
				}
			}
		}

		$output .= '</urlset>';

		WPSEO_Video_Wrappers::set_sitemap( $output );
		WPSEO_Video_Wrappers::set_stylesheet( $this->get_stylesheet_line() );
	}

	/**
	 * Print a full <url> line in the sitemap.
	 *
	 * @since 1.3
	 *
	 * @param array  $video  The video object to print out.
	 * @param object $object The post/tax object this video relates to.
	 *
	 * @return string The output generated
	 */
	public function print_sitemap_line( $video, $object ) {
		if ( ! is_array( $video ) || $video === array() ) {
			return '';
		}

		$output = "\t<url>\n";
		$output .= "\t\t<loc>" . htmlspecialchars( $video['permalink'] ) . '</loc>' . "\n";
		$output .= "\t\t<video:video>\n";


		if ( empty( $video['publication_date'] ) || WPSEO_Video_Wrappers::is_valid_datetime( $video['publication_date'] ) === false ) {
			$post = $object;
			if ( is_object( $post ) && $post->post_date_gmt !== '0000-00-00 00:00:00' && WPSEO_Video_Wrappers::is_valid_datetime( $post->post_date_gmt ) ) {
				$video['publication_date'] = mysql2date( 'Y-m-d\TH:i:s+00:00', $post->post_date_gmt );
			}
			elseif ( is_object( $post ) && $post->post_date !== '0000-00-00 00:00:00' && WPSEO_Video_Wrappers::is_valid_datetime( $post->post_date ) ) {
				$video['publication_date'] = date( 'Y-m-d\TH:i:s+00:00', get_gmt_from_date( $post->post_date ) );
			}
			else {
				return '<!-- Post with ID ' . $video['post_id'] . 'skipped, because there\'s no valid date in the DB for it. -->';
			} // If we have no valid date for the post, skip the video and don't print it in the XML Video Sitemap.
		}

		// @todo - We should really switch to whitelist format, rather than blacklist
		$video_keys_to_skip = array(
			'id',
			'url',
			'type',
			'permalink',
			'post_id',
			'hd',
			'maybe_local',
			'attachment_id',
			'file_path',
			'file_url',
		);

		foreach ( $video as $key => $val ) {
			if ( in_array( $key, $video_keys_to_skip, true ) ) {
				continue;
			}

			if ( $key === 'author' ) {
				$output .= "\t\t\t<video:uploader info='" . get_author_posts_url( $val ) . "'>" . ent2ncr( esc_html( get_the_author_meta( 'display_name', $val ) ) ) . "</video:uploader>\n";
				continue;
			}

			$xtra = '';
			if ( $key === 'player_loc' ) {
				$xtra = ' allow_embed="yes"';
			}

			if ( $key === 'description' && empty( $val ) ) {
				$val = $video['title'];
			}

			if ( is_scalar( $val ) && ! empty( $val ) ) {
				$prepare_sitemap_line = $this->get_single_sitemap_line( $val, $key, $xtra, $object );

				if ( ! is_null( $prepare_sitemap_line ) ) {
					$output .= $prepare_sitemap_line;
				}
			}
			elseif ( is_array( $val ) && $val !== array() ) {
				$i = 1;
				foreach ( $val as $v ) {
					// Only 32 tags are allowed.
					if ( $key === 'tag' && $i > 32 ) {
						break;
					}
					$prepare_sitemap_line = $this->get_single_sitemap_line( $v, $key, $xtra, $object );

					if ( ! is_null( $prepare_sitemap_line ) ) {
						$output .= $prepare_sitemap_line;
					}
					$i ++;
				}
			}
		}

		// Allow custom implementations with extra tags here.
		$output .= apply_filters( 'wpseo_video_item', '', isset( $video['post_id'] ) ? $video['post_id'] : 0 );

		$output .= "\t\t</video:video>\n";

		$output .= "\t</url>\n";

		return $output;
	}

	/**
	 * Cleans a string for XML display purposes.
	 *
	 * @since 1.2.1
	 *
	 * @link http://php.net/html-entity-decode#98697 Modified for WP from here.
	 *
	 * @param string $in     The string to clean.
	 * @param int    $offset Offset of the string to start the cleaning at.
	 *
	 * @return string Cleaned string.
	 */
	public function clean_string( $in, $offset = null ) {
		$out = trim( $in );
		$out = strip_shortcodes( $out );
		$out = html_entity_decode( $out, ENT_QUOTES, 'ISO-8859-15' );
		$out = html_entity_decode( $out, ENT_QUOTES, get_bloginfo( 'charset' ) );
		if ( ! empty( $out ) ) {
			$entity_start = strpos( $out, '&', $offset );
			if ( $entity_start === false ) {
				return _wp_specialchars( $out );
			}
			else {
				$entity_end = strpos( $out, ';', $entity_start );
				if ( $entity_end === false ) {
					return _wp_specialchars( $out );
				}
				elseif ( $entity_end > ( $entity_start + 7 ) ) {
					$out = $this->clean_string( $out, ( $entity_start + 1 ) );
				}
				else {
					$clean = substr( $out, 0, $entity_start );
					$subst = substr( $out, ( $entity_start + 1 ), 1 );
					$clean .= ( $subst !== '#' ) ? $subst : '_';
					$clean .= substr( $out, ( $entity_end + 1 ) );
					$out   = $this->clean_string( $clean, ( $entity_start + 1 ) );
				}
			}
		}

		return _wp_specialchars( $out );
	}

	/**
	 * Roughly calculate the length of an FLV video.
	 *
	 * @since 1.3.1
	 *
	 * @param string $file The path to the video file to calculate the length for.
	 *
	 * @return integer Duration of the video
	 */
	public function get_flv_duration( $file ) {
		if ( is_file( $file ) && is_readable( $file ) ) {
			$flv = fopen( $file, 'rb' );
			if ( is_resource( $flv ) ) {
				fseek( $flv, - 4, SEEK_END );
				$arr             = unpack( 'N', fread( $flv, 4 ) );
				$last_tag_offset = $arr[1];
				fseek( $flv, - ( $last_tag_offset + 4 ), SEEK_END );
				fseek( $flv, 4, SEEK_CUR );
				$t0                    = fread( $flv, 3 );
				$t1                    = fread( $flv, 1 );
				$arr                   = unpack( 'N', $t1 . $t0 );
				$milliseconds_duration = $arr[1];

				return $milliseconds_duration;
			}
		}

		return 0;
	}

	/**
	 * Outputs the admin panel for the Video Sitemaps on the XML Sitemaps page with the WP SEO admin
	 *
	 * @since 0.1
	 */
	public function admin_panel() {
		$options = get_option( 'wpseo_video' );
		$xmlopt  = WPSEO_Options::get( 'enable_xml_sitemap', false );

		if ( $this->is_video_page( filter_input( INPUT_GET, 'page' ) ) ) {
			$this->register_i18n_promo_class();
		}

		WPSEO_Video_Wrappers::admin_header( true, $this->option_instance->group_name, $this->option_instance->option_name, false );

		if ( isset( $_POST['reindex'] ) ) {
			/**
			 * Load the reindex page, shows a progressbar and sents ajax calls to the server with
			 * small amounts of posts to reindex.
			 */
			require plugin_dir_path( WPSEO_VIDEO_FILE ) . 'views/reindex-page.php';
		}
		else {
			if ( $xmlopt !== true ) {
				printf(
					'<p>%s</p>',
					sprintf(
						/* translators: 1: link open tag; 2: link close tag. */
						esc_html__( 'Please enable the XML sitemap under the SEO -> %1$sFeatures%2$s settings', 'yoast-video-seo' ),
						'<a href="' . esc_url( add_query_arg( array( 'page' => 'wpseo_dashboard' ), admin_url( 'admin.php' ) ) . '#top#features' ) . '">',
						'</a>'
					)
				);
			}
			else {

				echo '<h2>' . esc_html__( 'General Settings', 'yoast-video-seo' ) . '</h2>';

				if ( is_array( $options['videositemap_posttypes'] ) && $options['videositemap_posttypes'] !== array() ) {
					// Use fields => ids to limit the overhead of fetching entire post objects, fetch only an array of ids instead to count.
					$args       = array(
						'post_type'      => $options['videositemap_posttypes'],
						'post_status'    => 'publish',
						'posts_per_page' => -1,
						'meta_key'       => '_yoast_wpseo_video_meta',
						'meta_compare'   => '!=',
						'meta_value'     => 'none',
						'fields'         => 'ids',
					);
					$video_ids  = get_posts( $args );
					$count      = count( $video_ids );
					$n          = ( $count > $this->max_entries ) ? (int) ceil( $count / $this->max_entries ) : '';
					$video_last = $this->sitemap_url( $n );

					echo '<p>' . esc_html__( 'Please find your video sitemap here:', 'yoast-video-seo' ) . ' <a target="_blank" href="' . esc_url( $video_last ) . '">' . esc_html__( 'XML Video Sitemap', 'yoast-video-seo' ) . '</a></p>';
				}
				else {
					echo '<div class="notice notice-warning"><p>' . esc_html__( 'Select at least one post type to enable the video sitemap.', 'yoast-video-seo' ) . '</p></div>';

				}


				echo WPSEO_Video_Wrappers::checkbox( 'cloak_sitemap', esc_html__( 'Hide the sitemap from normal visitors?', 'yoast-video-seo' ) );
				echo WPSEO_Video_Wrappers::checkbox( 'disable_rss', esc_html__( 'Disable Media RSS Enhancement', 'yoast-video-seo' ) );
				echo '<br class="clear"/>';

				echo WPSEO_Video_Wrappers::textinput( 'custom_fields', esc_html__( 'Custom fields', 'yoast-video-seo' ) );
				echo '<p class="clear description desc label">' . esc_html__( 'Custom fields the plugin should check for video content (comma separated)', 'yoast-video-seo' ) . '</p>';
				echo WPSEO_Video_Wrappers::textinput( 'embedly_api_key', esc_html__( '(Optional) Embedly API Key', 'yoast-video-seo' ) );
				/* translators: 1,3: link open tag; 2: link close tag. */
				echo '<p class="clear description desc label">' . sprintf( esc_html__( 'The video SEO plugin provides where possible enriched information about your videos. A lot of %1$svideo services%2$s are supported by default. For those services which aren\'t supported, we can try to retrieve enriched video information using %3$sEmbedly%2$s. If you want to use this option, you\'ll need to sign up for a (free) %3$sEmbedly%2$s account and provide the API key you receive.', 'yoast-video-seo' ), '<a href="http://kb.yoast.com/article/95-supported-video-hosting-platforms-for-video-seo-plugin">', '</a>', '<a href="http://embed.ly/">' ) . '</p>';


				echo '<h2>' . esc_html__( 'Embed Settings', 'yoast-video-seo' ) . '</h2>';

				echo WPSEO_Video_Wrappers::checkbox( 'facebook_embed', esc_html__( 'Allow videos to be played directly on other websites, such as Facebook or Twitter?', 'yoast-video-seo' ) );
				/* translators: 1: link open tag, 2: link close tag. */
				echo WPSEO_Video_Wrappers::checkbox( 'fitvids', sprintf( esc_html__( 'Try to make videos responsive using %1$sFitVids.js%2$s?', 'yoast-video-seo' ), '<a href="http://fitvidsjs.com/">', '</a>' ) );
				echo '<br class="clear"/>';

				echo WPSEO_Video_Wrappers::textinput( 'content_width', esc_html__( 'Content width', 'yoast-video-seo' ) );
				echo '<p class="clear description desc label">' . esc_html__( 'This defaults to your themes content width, but if it\'s empty, setting a value here will make sure videos are embedded in the right width.', 'yoast-video-seo' ) . '</p>';

				echo WPSEO_Video_Wrappers::textinput( 'wistia_domain', esc_html__( 'Wistia domain', 'yoast-video-seo' ) );
				echo '<p class="clear description desc label">' . esc_html__( 'If you use Wistia in combination with a custom domain, set this to the domain name you use for your Wistia videos, no http: or slashes needed.', 'yoast-video-seo' ) . '</p>';


				echo '<h2>' . esc_html__( 'Post Types for which to enable the Video SEO plugin', 'yoast-video-seo' ) . '</h2>';
				echo '<p>' . esc_html__( 'Determine which post types on your site might contain video.', 'yoast-video-seo' ) . '</p>';

				$post_types = get_post_types( array( 'public' => true ), 'objects' );
				if ( is_array( $post_types ) && $post_types !== array() ) {
					foreach ( $post_types as $posttype ) {
						printf(
							'<input class="checkbox double" id="%1$s" type="checkbox" name="wpseo_video[videositemap_posttypes][%2$s]" %3$s value="%2$s"/><label class="checkbox" for="%1$s">%4$s</label><br class="clear">',
							esc_attr( 'include-' . $posttype->name ), // #1.
							esc_attr( $posttype->name ), // #2.
							checked( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $posttype->name ), true, false ), // #3.
							esc_html( $posttype->labels->name ) // #4.
						);
					}
				}
				unset( $post_types );


				echo '<h2>' . esc_html__( 'Taxonomies to include in XML Video Sitemap', 'yoast-video-seo' ) . '</h2>';
				echo '<p>' . esc_html__( 'You can also include your taxonomy archives, for instance, if you have videos on a category page.', 'yoast-video-seo' ) . '</p>';

				$taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
				if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
					foreach ( $taxonomies as $tax ) {
						$sel = false;
						if ( is_array( $options['videositemap_taxonomies'] ) && in_array( $tax->name, $options['videositemap_taxonomies'], true ) ) {
							$sel = true;
						}
						printf(
							'<input class="checkbox double" id="%1$s" type="checkbox" name="wpseo_video[videositemap_taxonomies][%2$s]" %3$s value="%2$s"/><label class="checkbox" for="%1$s">%4$s</label><br class="clear">',
							esc_attr( 'include-' . $tax->name ), // #1.
							esc_attr( $tax->name ), // #2.
							checked( $sel, true, false ), // #3.
							esc_html( $tax->labels->name ) // #4.
						);
					}
				}
				unset( $taxonomies );
			}
			echo '<br class="clear"/>';
			?>

			<div class="submit">
				<input type="submit" class="button button-primary" name="submit"
					   value="<?php esc_attr_e( 'Save Settings', 'yoast-video-seo' ); ?>"/>
			</div>
			</form>

			<h2><?php esc_html_e( 'Indexation of videos in your content', 'yoast-video-seo' ); ?></h2>

			<p style="max-width: 600px;"><?php esc_html_e( 'This process goes through all the post types specified by you, as well as the terms of each taxonomy, to check for videos in the content. If the plugin finds a video, it updates the metadata for that piece of content, so it can add that metadata and content to the XML Video Sitemap.', 'yoast-video-seo' ); ?></p>

			<p style="max-width: 600px;"><?php esc_html_e( 'By default the plugin only checks content that hasn\'t been checked yet. However, if you check \'Force Re-Index\', it will re-check all content. This is particularly interesting if you want to check for a video embed code that wasn\'t supported before, or if you want to update thumbnail images en masse.', 'yoast-video-seo' ); ?></p>

			<form method="post" action="">

				<input class="checkbox double" type="checkbox" name="force" id="force">
				<label class="checkbox"
					   for="force"><?php esc_html_e( 'Force reindex of already indexed videos.', 'yoast-video-seo' ); ?></label><br/>
				<p class="submit">
					<input type="submit" class="button" name="reindex"
						   value="<?php esc_html_e( 'Re-Index Videos', 'yoast-video-seo' ); ?>"/>
				</p>
			</form>
			<?php

		}
		// Add debug info.
		WPSEO_Video_Wrappers::admin_footer( false, false );
	}

	/**
	 * A better strip tags that leaves spaces intact (and rips out more code)
	 *
	 * @since 1.3.4
	 *
	 * @link http://php.net/strip-tags#110280
	 *
	 * @param string $string string to strip tags from.
	 *
	 * @return string
	 */
	public function strip_tags( $string ) {

		// ----- remove HTML TAGs -----
		$string = preg_replace( '/<[^>]*>/', ' ', $string );

		// ----- remove control characters -----
		$string = str_replace( "\r", '', $string ); // --- replace with empty space
		$string = str_replace( "\n", ' ', $string ); // --- replace with space
		$string = str_replace( "\t", ' ', $string ); // --- replace with space

		// ----- remove multiple spaces -----
		$string = trim( preg_replace( '/ {2,}/', ' ', $string ) );

		return $string;
	}

	/**
	 * Add the video and yandex namespaces to the namespaces in the html prefix attribute.
	 *
	 * @since 4.1.0
	 *
	 * @link  http://ogp.me/#type_video
	 * @link  https://yandex.com/support/webmaster/video/open-graph.xml
	 *
	 * @param array $namespaces Currently registered namespaces.
	 *
	 * @return array
	 */
	public function add_video_namespaces( $namespaces ) {
		$namespaces[] = 'video: http://ogp.me/ns/video#';

		/**
		 * Allow for turning off Yandex support.
		 *
		 * @since 4.1.0
		 *
		 * @param bool Whether or not to support (add) Yandex specific video SEO
		 *             meta tags. Defaults to `true`.
		 *             Return `false` to disable Yandex support.
		 */
		if ( apply_filters( 'wpseo_video_yandex_support', true ) === true ) {
			$namespaces[] = 'ya: http://webmaster.yandex.ru/vocabularies/';
		}

		return $namespaces;
	}

	/**
	 * Filter the OpenGraph type for the post and sets it to 'video'
	 *
	 * @since 0.1
	 *
	 * @param string $type The type, normally "article".
	 *
	 * @return string $type Value 'video'
	 */
	public function opengraph_type( $type ) {
		if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( get_post_type() ) === false ) {
			return $type;
		}

		$options = get_option( 'wpseo_video' );

		if ( $options['facebook_embed'] !== true ) {
			return $type;
		}

		return $this->type_filter( $type, 'video.other' );
	}

	/**
	 * Switch the Twitter card type to player if needed.
	 *
	 * {@internal [JRF] This method does not seem to be hooked in anywhere.}}
	 *
	 * @param string $type The Twitter card type.
	 *
	 * @return string
	 */
	public function card_type( $type ) {
		return $this->type_filter( $type, 'player' );
	}

	/**
	 * Helper function for Twitter and OpenGraph card types
	 *
	 * @param  string $type         The card type.
	 * @param  string $video_output Output.
	 *
	 * @return string
	 */
	public function type_filter( $type, $video_output ) {
		global $post;

		if ( is_singular() ) {
			if ( is_object( $post ) ) {
				if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
					return $type;
				}

				$video = WPSEO_Meta::get_value( 'video_meta', $post->ID );
				if ( ! is_array( $video ) || $video === array() ) {
					return $type;
				}

				$disable = WPSEO_Meta::get_value( 'videositemap-disable', $post->ID );
				if ( $disable === 'on' ) {
					return $type;
				}

				return $video_output;
			}
		}
		else {
			if ( is_tax() || is_category() || is_tag() ) {
				$options = get_option( 'wpseo_video' );
				$term    = get_queried_object();

				if ( is_array( $options['videositemap_taxonomies'] ) && in_array( $term->taxonomy, $options['videositemap_taxonomies'], true ) ) {
					$tax_meta = get_option( 'wpseo_taxonomy_meta' );
					if ( isset( $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] ) ) {
						return $video_output;
					}
				}
			}
		}

		return $type;
	}

	/**
	 * Filter the OpenGraph image for the post and sets it to the video thumbnail
	 *
	 * @since 0.1
	 *
	 * @param WPSEO_OpenGraph_Image $wpseo_opengraph_image The WPSEO OpenGraph image object.
	 *
	 * @return void
	 */
	public function opengraph_image( $wpseo_opengraph_image ) {
		if ( is_singular() ) {
			$post = get_queried_object();

			if ( is_object( $post ) ) {
				if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
					return;
				}

				$disable = WPSEO_Meta::get_value( 'videositemap-disable', $post->ID );
				if ( $disable === 'on' ) {
					return;
				}

				$video = WPSEO_Meta::get_value( 'video_meta', $post->ID );
				if ( ! is_array( $video ) || $video === array() ) {
					return;
				}

				$wpseo_opengraph_image->add_image_by_url( $video['thumbnail_loc'] );

				return;
			}

			return;
		}

		if ( is_tax() || is_category() || is_tag() ) {
			$options = get_option( 'wpseo_video' );
			$term    = get_queried_object();

			if ( is_array( $options['videositemap_taxonomies'] ) && in_array( $term->taxonomy, $options['videositemap_taxonomies'], true ) ) {
				$tax_meta = get_option( 'wpseo_taxonomy_meta' );
				if ( isset( $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] ) ) {
					$video = $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ];
					$wpseo_opengraph_image->add_image_by_url( $video['thumbnail_loc'] );
				}
			}
		}
	}

	/**
	 * Add OpenGraph video info if present
	 *
	 * @since 0.1
	 */
	public function opengraph() {
		$options = get_option( 'wpseo_video' );

		if ( $options['facebook_embed'] !== true ) {
			return false;
		}

		$video_duration = 0;
		if ( is_singular() ) {
			global $post;

			if ( is_object( $post ) ) {
				if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post->post_type ) === false ) {
					return false;
				}

				$disable = WPSEO_Meta::get_value( 'videositemap-disable', $post->ID );
				if ( $disable === 'on' ) {
					return false;
				}

				$video = WPSEO_Meta::get_value( 'video_meta', $post->ID );

				if ( is_array( $video ) && $video !== array() ) {
					$video = WPSEO_Video_Utils::get_video_image( $post->ID, $video );
				}

				$video_duration = WPSEO_Video_Utils::get_video_duration( $video, $post->ID );
			}
		}
		else {
			if ( is_tax() || is_category() || is_tag() ) {

				$term = get_queried_object();

				if ( is_array( $options['videositemap_taxonomies'] ) && in_array( $term->taxonomy, $options['videositemap_taxonomies'], true ) ) {
					$tax_meta = get_option( 'wpseo_taxonomy_meta' );
					if ( isset( $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ] ) ) {
						$video = $tax_meta[ $term->taxonomy ]['_video'][ $term->term_id ];
					}

					if ( isset( $video ) ) {
						$video_duration = WPSEO_Video_Utils::get_video_duration( $video );
					}
					else {
						$video_duration = WPSEO_Video_Utils::get_video_duration( array() );
					}
				}
			}
		}

		if ( ! isset( $video ) || ! is_array( $video ) || ! isset( $video['player_loc'] ) ) {
			return false;
		}

		echo '<meta property="og:video" content="' . esc_url( $video['player_loc'] ) . '" />' . "\n";

		if ( strpos( $video['player_loc'], 'https://' ) === 0 ) {
			echo '<meta property="og:video:secure_url" content="' . esc_url( $video['player_loc'] ) . '" />' . "\n";
		}

		echo '<meta property="og:video:type" content="text/html" />' . "\n";
		if ( isset( $video['width'] ) && isset( $video['height'] ) ) {
			echo '<meta property="og:video:width" content="' . esc_attr( $video['width'] ) . '" />' . "\n";
			echo '<meta property="og:video:height" content="' . esc_attr( $video['height'] ) . '" />' . "\n";
		}

		if ( $video_duration !== 0 ) {
			echo '<meta property="video:duration" content="' . intval( $video_duration ) . '" />' . "\n";
		}

		// This filter is documented in the `add_yandex_namespace()` method above.
		if ( apply_filters( 'wpseo_video_yandex_support', true ) === true ) {
			if ( isset( $post, $post->post_date, $post->ID ) ) {
				echo '<meta property="ya:ovs:upload_date" content="' . esc_attr( date( 'c', strtotime( $post->post_date ) ) ) . '" />' . "\n";

				$not_family_friendly = 'false';
				if ( WPSEO_Video_Utils::is_video_family_friendly( $post->ID ) === false ) {
					$not_family_friendly = 'true';
				}
				echo '<meta property="ya:ovs:adult" content="', esc_attr( $not_family_friendly ), '" />', "\n";
			}

			echo '<meta property="ya:ovs:allow_embed" content="true" />' . "\n";
		}
	}

	/**
	 * Make the get_terms query only return terms with a non-empty description.
	 *
	 * @since 1.3
	 *
	 * @param  array $pieces The separate pieces of the terms query to filter.
	 *
	 * @return mixed
	 */
	public function filter_terms_clauses( $pieces ) {
		$pieces['where'] .= " AND tt.description != ''";

		return $pieces;
	}

	/**
	 * Register the promotion class for our GlotPress instance
	 *
	 * @link https://github.com/Yoast/i18n-module
	 */
	private function register_i18n_promo_class() {
		new Yoast_I18n_v3(
			array(
				'textdomain'     => 'yoast-video-seo',
				'project_slug'   => 'yoast-video-seo',
				'plugin_name'    => 'Video SEO for WordPress SEO by Yoast',
				'hook'           => 'wpseo_admin_promo_footer',
				'glotpress_url'  => 'http://translate.yoast.com/gp/',
				'glotpress_name' => 'Yoast Translate',
				'glotpress_logo' => 'http://translate.yoast.com/gp-templates/images/Yoast_Translate.svg',
				'register_url'   => 'http://translate.yoast.com/gp/projects#utm_source=plugin&utm_medium=promo-box&utm_campaign=wpseo-video-i18n-promo',
			)
		);
	}

	/**
	 * Checks if the current page is a video seo plugin page.
	 *
	 * @param string $page The page to check for.
	 *
	 * @return bool
	 */
	private function is_video_page( $page ) {
		$video_pages = array( 'wpseo_video' );

		return in_array( $page, $video_pages, true );
	}

	/**
	 * Get a single sitemap line to output in the xml sitemap
	 *
	 * @param string $val    Value.
	 * @param string $key    Key.
	 * @param string $xtra   Extra.
	 * @param object $object The post/tax object this value relates to.
	 *
	 * @return null|string
	 */
	private function get_single_sitemap_line( $val, $key, $xtra, $object ) {
		$val = $this->clean_string( $val );
		if ( in_array( $key, array( 'description', 'category', 'tag', 'title' ), true ) ) {
			$val = ent2ncr( esc_html( $val ) );
		}
		if ( ! empty( $val ) ) {
			$val = wpseo_replace_vars( $val, $object );
			$val = _wp_specialchars( html_entity_decode( $val, ENT_QUOTES, 'UTF-8' ) );

			return "\t\t\t<video:" . $key . $xtra . '>' . $val . '</video:' . $key . ">\n";
		}

		return null;
	}

	/**
	 * Reindex the video info from posts
	 *
	 * @since 0.1
	 * @since 3.8 $total parameter was added.
	 *
	 * @param int $portion Number of posts.
	 * @param int $start   Offset.
	 * @param int $total   Total number of posts which will be re-indexed.
	 */
	private function reindex( $portion, $start, $total ) {
		require_once ABSPATH . 'wp-admin/includes/media.php';

		$options = get_option( 'wpseo_video' );

		if ( is_array( $options['videositemap_posttypes'] ) && $options['videositemap_posttypes'] !== array() ) {
			$args = array(
				'post_type'   => $options['videositemap_posttypes'],
				'post_status' => 'publish',
				'numberposts' => $portion,
				'offset'      => $start,
			);

			if ( ! isset( $_POST['force'] ) ) {
				$args['meta_query'] = array(
					'key'     => '_yoast_wpseo_video_meta',
					'compare' => 'NOT EXISTS',
				);
			}


			$results      = get_posts( $args );
			$result_count = count( $results );

			if ( is_array( $results ) && $result_count > 0 ) {
				foreach ( $results as $post ) {
					if ( $post instanceof WP_Post ) {
						$this->update_video_post_meta( $post->ID, $post );
					}
					elseif ( is_numeric( $post ) ) {
						$this->update_video_post_meta( $post );
					}
					flush(); // Clear system output buffer if any exist.
				}
			}
		}

		if ( ( $start + $portion ) >= $total ) {
			// Get all the non-empty terms.
			add_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ) );
			$terms = array();
			if ( is_array( $options['videositemap_taxonomies'] ) && $options['videositemap_taxonomies'] !== array() ) {
				foreach ( $options['videositemap_taxonomies'] as $val ) {
					$new_terms = get_terms( $val );
					if ( is_array( $new_terms ) ) {
						$terms = array_merge( $terms, $new_terms );
					}
				}
			}
			remove_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ) );

			if ( count( $terms ) > 0 ) {

				foreach ( $terms as $term ) {
					$this->update_video_term_meta( $term, false );
					flush();
				}
			}

			// As this is used from within an AJAX call, we don't queue the cache clearing,
			// but do a hard reset.
			WPSEO_Video_Wrappers::invalidate_cache_storage( $this->video_sitemap_basename() );

			// Ping the search engines with our updated XML sitemap, we ping with the index sitemap because
			// we don't know which video sitemap, or sitemaps, have been updated / added.
			WPSEO_Video_Wrappers::ping_search_engines();

			// Remove the admin notice.
			delete_transient( 'video_seo_recommend_reindex' );
		}
	}

	/**
	 * Retrieves the XSL URL that should be used in the current environment
	 *
	 * When home_url and site_url are not the same, the home_url should be used.
	 * This is because the XSL needs to be served from the same domain, protocol and port
	 * as the XML file that is loading it.
	 *
	 * @return string The XSL URL that needs to be used.
	 */
	protected function get_xsl_url() {
		if ( home_url() !== site_url() ) {
			return home_url( 'video-sitemap.xsl' );
		}

		return plugin_dir_url( WPSEO_VIDEO_FILE ) . 'xml-video-sitemap.xsl';
	}

	/**
	 * Checks if the user can manage options.
	 *
	 * @since 5.6.0
	 *
	 * @return bool True if the user can manage options.
	 */
	protected function can_manage_options() {
		if ( class_exists( 'WPSEO_Capability_Utils' ) ) {
			return WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
		}

		return false;
	}

	/**
	 * Retrieves the maximum number of entries per XML sitemap.
	 *
	 * @return int The maximum number of entries.
	 */
	protected function get_entries_per_page() {
		/**
		 * Filter the maximum number of entries per XML sitemap.
		 *
		 * @param int $entries The maximum number of entries per XML sitemap.
		 */
		return (int) apply_filters( 'wpseo_sitemap_entries_per_page', 1000 );
	}

	/* ********************* DEPRECATED METHODS ********************* */

	/**
	 * Call WPSEO_OpenGraph::image() if the method exists.
	 *
	 * @since      4.1
	 * @deprecated 8.2
	 * @codeCoverageIgnore
	 *
	 * @param string $image Image URL.
	 */
	public static function og_image_output( $image ) {
		_deprecated_function( __METHOD__, 'Video SEO 8.2', __CLASS__ . '::opengraph_image()' );
	}

	/**
	 * Return the plugin file
	 *
	 * @deprecated 11.1
	 *
	 * @return string
	 */
	public static function get_plugin_file() {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::get_plugin_file()' );

		return WPSEO_Video_Utils::get_plugin_file();
	}

	/**
	 * Check to see if the video thumbnail was manually set, if so, update the $video array.
	 *
	 * @param int   $post_id The post to check for.
	 * @param array $video   The video array.
	 *
	 * @deprecated 11.1
	 *
	 * @return array
	 */
	public static function get_video_image( $post_id, $video ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::get_video_image()' );

		return WPSEO_Video_Utils::get_video_image( $post_id, $video );
	}

	/**
	 * Check whether VideoSEO is active for a specific post type.
	 *
	 * @since      4.1
	 *
	 * @deprecated 11.1
	 *
	 * @param string $post_type The post type to check for.
	 *
	 * @return bool True if active, false if inactive.
	 */
	public static function is_videoseo_active_for_posttype( $post_type ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::is_videoseo_active_for_posttype()' );

		return WPSEO_Video_Utils::is_videoseo_active_for_posttype( $post_type );
	}

	/**
	 * Load translations.
	 *
	 * @deprecated 11.1
	 */
	public static function load_textdomain() {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::load_textdomain()' );

		WPSEO_Video_Utils::load_textdomain();
	}


	/**
	 * Translate a duration to ISO 8601.
	 *
	 * @deprecated 11.1
	 *
	 * @param int $duration The duration in seconds.
	 *
	 * @return string $out ISO 8601 compatible output.
	 */
	public function iso_8601_duration( $duration ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::iso_8601_duration()' );

		return WPSEO_Video_Utils::iso_8601_duration( $duration );
	}

	/**
	 * Retrieve the duration of a video.
	 *
	 * Use a user provided duration if available, fall back to the available video data
	 * as previously retrieved through an API call.
	 *
	 * @since      4.1.0
	 *
	 * @deprecated 11.1
	 *
	 * @param array $video   Data about the video being evaluated.
	 * @param int   $post_id Optional. Post ID.
	 *
	 * @return int Duration in seconds or 0 if no duration could be determined.
	 */
	public static function get_video_duration( $video, $post_id = null ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::get_video_duration()' );

		return WPSEO_Video_Utils::get_video_duration( $video, $post_id );
	}

	/**
	 * We once used this to output schema. We no longer do.
	 *
	 * @param string $content Content of the post.
	 *
	 * @since 11.1
	 *
	 * @return string Content of the post.
	 */
	public function content_filter( $content ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1' );

		return $content;
	}

	/**
	 * Determine whether a video is family friendly or not.
	 *
	 * @since 4.1.0
	 *
	 * @deprecated 11.1
	 *
	 * @param int $post_id Post ID.
	 *
	 * @return bool True if family friendly, false if not.
	 */
	public static function is_video_family_friendly( $post_id ) {
		_deprecated_function( __METHOD__, 'Video SEO 11.1', 'WPSEO_Video_Utils::is_video_family_friendly()' );

		return WPSEO_Video_Utils::is_video_family_friendly( $post_id );
	}
}

haha - 2025