ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-16 21:51:52
Editing: class-wpseo-video-sitemap.php
Cancel
Save Changes
<?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; /** * 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'; /** * The date helper. * * @var WPSEO_Date_Helper */ protected $date; /** * 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. WPSEO_Option_Video::register_option(); // 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 ( WPSEO_Options::get( 'video_fitvids' ) === true ) { // Fitvids scripting. add_action( 'wp_head', array( $this, 'fitvids' ) ); } if ( WPSEO_Options::get( 'video_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' ) ); } } $this->date = new WPSEO_Date_Helper(); } /** * 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. $option_content_width = (int) WPSEO_Options::get( 'video_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 int $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; } // Bail if this is a multisite installation and the site has been switched. if ( is_multisite() && ms_is_switched() ) { return; } if ( ! WPSEO_Video_Utils::is_videoseo_active_for_posttype( get_post_type( $post_id ) ) ) { return; } WPSEO_Video_Wrappers::invalidate_sitemap( self::get_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() { $this->max_entries = $this->get_entries_per_page(); $this->add_oembed(); $this->init_beacon(); } /** * Initializes the HelpScout beacon */ private function init_beacon() { if ( ! class_exists( 'WPSEO_HelpScout' ) ) { return; } $helpscout = new WPSEO_HelpScout( '4e7489db-f907-41b3-9e86-93a01b4df9b0', [ 'wpseo_video' ], [ WPSEO_Addon_Manager::VIDEO_SLUG ] ); $helpscout->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 = self::get_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' ); $current_version = null; if ( ! empty( $options['video_dbversion'] ) ) { $current_version = $options['video_dbversion']; } if ( ! $current_version && ! empty( $options['dbversion'] ) ) { $current_version = $options['dbversion']; } // Early bail if dbversion is equal to current version. if ( $current_version && version_compare( $current_version, WPSEO_VIDEO_VERSION, '==' ) ) { return; } // Upgrade to new option & meta classes. if ( ! $current_version || version_compare( $current_version, '1.6', '<' ) ) { WPSEO_Option_Video::get_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 ( ! $current_version || ( version_compare( $current_version, '1.7', '<' ) && version_compare( $current_version, '1.6', '>' ) ) ) { WPSEO_Meta_Video::re_add_durations(); } // Recommend force re-index. if ( $current_version && version_compare( $current_version, '4.0', '<' ) ) { set_transient( 'video_seo_recommend_reindex', 1 ); } // Rename the option values. if ( $current_version && version_compare( $current_version, '12.4-RC1', '<=' ) ) { $fields_to_convert = [ 'dbversion' => 'video_dbversion', 'cloak_sitemap' => 'video_cloak_sitemap', 'disable_rss' => 'video_disable_rss', 'custom_fields' => 'video_custom_fields', 'facebook_embed' => 'video_facebook_embed', 'fitvids' => 'video_fitvids', 'content_width' => 'video_content_width', 'wistia_domain' => 'video_wistia_domain', 'embedly_api_key' => 'video_embedly_api_key', ]; foreach ( $fields_to_convert as $current_field => $new_field ) { if ( ! isset( $options[ $current_field ] ) ) { continue; } $options[ $new_field ] = $options[ $current_field ]; } update_option( 'wpseo_video', $options ); } // Make sure version nr gets updated for any version without specific upgrades. // Re-get to make sure we have the latest version. if ( version_compare( $current_version, WPSEO_VIDEO_VERSION, '<' ) ) { WPSEO_Options::set( 'video_dbversion', WPSEO_VIDEO_VERSION ); } } /** * 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' ) { $total = 0; $sitemap_post_types = (array) WPSEO_Options::get( 'videositemap_posttypes', array() ); foreach ( $sitemap_post_types 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". * * Retrieves the video sitemap basename. * * @since 1.5.3 * * @return string */ public function video_sitemap_basename() { return self::get_video_sitemap_basename(); } /** * Defaults to video, but it's possible to override it by using the YOAST_VIDEO_SITEMAP_BASENAME constant. * * @return string The sitemap basename. */ public static function get_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 = self::get_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 String with the Video XML sitemap additions to the index sitemap in it. */ public function add_to_index( $str ) { $base = $GLOBALS['wp_rewrite']->using_index_permalinks() ? 'index.php/' : ''; $sitemap_post_types = WPSEO_Options::get( 'videositemap_posttypes', array() ); if ( is_array( $sitemap_post_types ) && $sitemap_post_types !== 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' => $sitemap_post_types, '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 = $this->date->format( $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. $wistia_regex = '`(?:http[s]?:)?//[^/]*(wistia\.(com|net)|wi\.st#CUSTOM_URL#)/(medias|embed)/.*`i'; $wistia_domain = WPSEO_Options::get( 'video_wistia_domain', '' ); if ( $wistia_domain !== '' ) { $wistia_regex = str_replace( '#CUSTOM_URL#', '|' . preg_quote( $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 {@link https://wordpress.org/plugins/mrss/} 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 ); } /** * Create an absolute URL for use in the MRSS info. * * @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 ); } } /** * Add attachments to the MRSS gallery lookup array. * * @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; } /** * Print an MRSS item. * * @param mixed $media Media. */ public function mrss_print( $media ) { if ( ! empty( $media ) ) { foreach ( (array) $media as $element ) { $this->mrss_print_element( $element ); } } echo "\n"; } /** * Print an MRSS element. * * @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 ( ! empty( $data['attr'] ) && is_array( $data['attr'] ) ) { foreach ( $data['attr'] as $attr => $value ) { echo ' ' . esc_attr( $attr ) . '="' . esc_attr( ent2ncr( $value ) ) . '"'; } } if ( ! empty( $data['children'] ) && is_array( $data['children'] ) ) { $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 */ 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 bool $echo Whether or not to echo the performed actions. * * @return mixed 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 ) { $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( ! is_array( $sitemap_taxonomies ) || $sitemap_taxonomies === array() ) { return false; } if ( ! in_array( $term->taxonomy, $sitemap_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 ) ) { $default_title = WPSEO_Options::get( 'title-' . $term->taxonomy, '' ); if ( $default_title !== '' ) { $title = wpseo_replace_vars( $default_title, (array) $term ); } } if ( empty( $title ) ) { $title = $term->name; } $vid['title'] = htmlspecialchars( $title, ENT_COMPAT, get_bloginfo( 'charset' ), true ); $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'] = $this->date->format_timestamp( time() ); // 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. * @since 11.x Removed the $update parameter as it was never used. * * @param int $post_id The post ID to check and possibly update the video details for. * @param \WP_Post $post The post object. * * @return mixed 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 ) { global $wp_query; // Bail if this is a multisite installation and the site has been switched. if ( is_multisite() && ms_is_switched() ) { return false; } 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; } $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 === '' ) { $default_title = WPSEO_Options::get( 'title-' . $post->post_type, '' ); if ( $default_title !== '' ) { $title = wpseo_replace_vars( $default_title, (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, ENT_COMPAT, get_bloginfo( 'charset' ), true ); $vid['publication_date'] = $this->date->format( $post->post_date_gmt ); $vid['description'] = WPSEO_Meta::get_value( 'metadesc', $post->ID ); if ( ! is_string( $vid['description'] ) || $vid['description'] === '' ) { $default_description = WPSEO_Options::get( 'metadesc-' . $post->post_type, '' ); if ( $default_description !== '' ) { $vid['description'] = wpseo_replace_vars( $default_description, (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 bool */ 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: ' . $this->date->format_timestamp( ( time() + YEAR_IN_SECONDS ), 'D, d M Y H:i:s' ) . ' 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() { $protocol = $this->get_server_protocol(); // Restrict access to the video sitemap to admins and valid bots. if ( WPSEO_Options::get( 'video_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 ); $sitemap_post_types = WPSEO_Options::get( 'videositemap_posttypes', array() ); if ( is_array( $sitemap_post_types ) && $sitemap_post_types !== array() ) { // Set the initial args array to get videos in chunks. $args = array( 'post_type' => $sitemap_post_types, '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(); $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( is_array( $sitemap_taxonomies ) && $sitemap_taxonomies !== array() ) { // Below is a fix for a nasty bug in WooCommerce: https://github.com/woothemes/woocommerce/issues/3807. $sitemap_taxonomies[0] = ''; $terms = get_terms( $sitemap_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>" . esc_url( $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'] = $this->date->format( $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'] = $this->date->format( 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 int 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() { $sitemap_url = null; $sitemap_post_types = WPSEO_Options::get( 'videositemap_posttypes', array() ); if ( is_array( $sitemap_post_types ) && $sitemap_post_types !== 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' => $sitemap_post_types, '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 ) : ''; $sitemap_url = $this->sitemap_url( $n ); } $admin_page = new WPSEO_Video_Admin_Page(); $admin_page->display( $sitemap_url ); } /** * 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 Value 'video' */ public function opengraph_type( $type ) { if ( WPSEO_Video_Utils::is_videoseo_active_for_posttype( get_post_type() ) === false ) { return $type; } if ( WPSEO_Options::get( 'video_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() ) { $term = get_queried_object(); $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( is_array( $sitemap_taxonomies ) && in_array( $term->taxonomy, $sitemap_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() ) { $term = get_queried_object(); $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( is_array( $sitemap_taxonomies ) && in_array( $term->taxonomy, $sitemap_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() { if ( WPSEO_Options::get( 'video_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(); $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( is_array( $sitemap_taxonomies ) && in_array( $term->taxonomy, $sitemap_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( $this->date->format( $post->post_date_gmt ) ) . '" />' . "\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; } /** * 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'; $sitemap_post_types = WPSEO_Options::get( 'videositemap_posttypes', array() ); if ( is_array( $sitemap_post_types ) && $sitemap_post_types !== array() ) { $args = array( 'post_type' => $sitemap_post_types, '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(); $sitemap_taxonomies = WPSEO_Options::get( 'videositemap_taxonomies', array() ); if ( is_array( $sitemap_taxonomies ) && $sitemap_taxonomies !== array() ) { foreach ( $sitemap_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( self::get_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 * @codeCoverageIgnore * * @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. * * @deprecated 11.1 * @codeCoverageIgnore * * @param int $post_id The post to check for. * @param array $video The video array. * * @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 * @codeCoverageIgnore * * @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 * @codeCoverageIgnore */ 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 * @codeCoverageIgnore * * @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 * @codeCoverageIgnore * * @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. * * @deprecated 11.1 * @codeCoverageIgnore * * @param string $content Content of the post. * * @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 * @codeCoverageIgnore * * @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 ); } }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete