/** * Class Jetpack_Media_Summary * * embed [video] > gallery > image > text */ class Jetpack_Media_Summary { static function get( $post_id, $blog_id = 0, $args = array() ) { $defaults = array( 'max_words' => 16, 'max_chars' => 256, ); $args = wp_parse_args( $args, $defaults ); $switched = false; if ( !empty( $blog_id ) && $blog_id != get_current_blog_id() && function_exists( 'switch_to_blog' ) ) { switch_to_blog( $blog_id ); $switched = true; } else { $blog_id = get_current_blog_id(); } if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) { jetpack_require_lib( 'class.media-extractor' ); } $post = get_post( $post_id ); $permalink = get_permalink( $post_id ); $return = array( 'type' => 'standard', 'permalink' => $permalink, 'image' => '', 'excerpt' => '', 'word_count' => 0, 'secure' => array( 'image' => '', ), 'count' => array( 'image' => 0, 'video' => 0, 'word' => 0, 'link' => 0, ), ); if ( empty( $post->post_password ) ) { $return['excerpt'] = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'] ); $return['count']['word'] = self::get_word_count( $post->post_content ); $return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] ); $return['count']['link'] = self::get_link_count( $post->post_content ); } $extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL ); if ( empty( $extract['has'] ) ) return $return; // Prioritize [some] video embeds if ( !empty( $extract['has']['shortcode'] ) ) { foreach ( $extract['shortcode'] as $type => $data ) { switch ( $type ) { case 'wpvideo': if ( 0 == $return['count']['video'] ) { $return['type'] = 'video'; $return['video'] = esc_url_raw( 'http://s0.videopress.com/player.swf?guid=' . $extract['shortcode']['wpvideo']['id'][0] . '&isDynamicSeeking=true' ); $return['image'] = self::get_video_poster( 'videopress', $extract['shortcode']['wpvideo']['id'][0] ); $return['secure']['video'] = preg_replace( '@http://[^\.]+.videopress.com/@', 'https://v0.wordpress.com/', $return['video'] ); $return['secure']['image'] = str_replace( 'http://videos.videopress.com', 'https://videos.files.wordpress.com', $return['image'] ); } $return['count']['video']++; break; case 'youtube': if ( 0 == $return['count']['video'] ) { $return['type'] = 'video'; $return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] ); $return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] ); $return['secure']['video'] = self::https( $return['video'] ); $return['secure']['image'] = self::https( $return['image'] ); } $return['count']['video']++; break; case 'vimeo': if ( 0 == $return['count']['video'] ) { $return['type'] = 'video'; $return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] ); $return['secure']['video'] = self::https( $return['video'] ); $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true ); if ( !empty( $poster_image ) ) { $return['image'] = $poster_image; $poster_url_parts = parse_url( $poster_image ); $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path']; } } $return['count']['video']++; break; } } } if ( !empty( $extract['has']['embed'] ) ) { foreach( $extract['embed']['url'] as $embed ) { if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) { if ( 0 == $return['count']['video'] ) { $return['type'] = 'video'; $return['video'] = 'http://' . $embed; $return['secure']['video'] = self::https( $return['video'] ); if ( false !== strpos( $embed, 'youtube' ) ) { $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) ); $return['secure']['image'] = self::https( $return['image'] ); } else if ( false !== strpos( $embed, 'youtu.be' ) ) { $youtube_id = jetpack_get_youtube_id( $return['video'] ); $return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be'; $return['secure']['video'] = self::https( $return['video'] ); $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) ); $return['secure']['image'] = self::https( $return['image'] ); } else if ( false !== strpos( $embed, 'vimeo' ) ) { $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true ); if ( !empty( $poster_image ) ) { $return['image'] = $poster_image; $poster_url_parts = parse_url( $poster_image ); $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path']; } } else if ( false !== strpos( $embed, 'dailymotion' ) ) { $return['image'] = str_replace( 'dailymotion.com/video/','dailymotion.com/thumbnail/video/', $embed ); $return['image'] = parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image']; $return['secure']['image'] = self::https( $return['image'] ); } } $return['count']['video']++; } } } // Do we really want to make the video the primary focus of the post? if ( 'video' == $return['type'] ) { $content = wpautop( strip_tags( $post->post_content ) ); $paragraphs = explode( '

', $content ); $number_of_paragraphs = 0; foreach ( $paragraphs as $i => $paragraph ) { // Don't include blank lines as a paragraph if ( '' == trim( $paragraph ) ) { unset( $paragraphs[$i] ); continue; } $number_of_paragraphs++; } $number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos.. // More than 2 paragraph? The video is not the primary focus so we can do some more analysis if ( $number_of_paragraphs > 2 ) $return['type'] = 'standard'; } // If we don't have any prioritized embed... if ( 'standard' == $return['type'] ) { if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) { //... Then we prioritize galleries first (multiple images returned) $return['type'] = 'gallery'; $return['images'] = $extract['image']; foreach ( $return['images'] as $image ) { $return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) ); $return['count']['image']++; } } else if ( ! empty( $extract['has']['image'] ) ) { // ... Or we try and select a single image that would make sense $content = wpautop( strip_tags( $post->post_content ) ); $paragraphs = explode( '

', $content ); $number_of_paragraphs = 0; foreach ( $paragraphs as $i => $paragraph ) { // Don't include 'actual' captions as a paragraph if ( false !== strpos( $paragraph, '[caption' ) ) { unset( $paragraphs[$i] ); continue; } // Don't include blank lines as a paragraph if ( '' == trim( $paragraph ) ) { unset( $paragraphs[$i] ); continue; } $number_of_paragraphs++; } $return['image'] = $extract['image'][0]['url']; $return['secure']['image'] = self::ssl_img( $return['image'] ); $return['count']['image']++; if ( $number_of_paragraphs <= 2 && 1 == count( $extract['image'] ) ) { // If we have lots of text or images, let's not treat it as an image post, but return its first image $return['type'] = 'image'; } } } if ( $switched ) { restore_current_blog(); } return $return; } static function https( $str ) { return str_replace( 'http://', 'https://', $str ); } static function ssl_img( $url ) { if ( false !== strpos( $url, 'files.wordpress.com' ) ) { return self::https( $url ); } else { return self::https( jetpack_photon_url( $url ) ); } } static function get_video_poster( $type, $id ) { if ( 'videopress' == $type ) { if ( function_exists( 'video_get_highest_resolution_image_url' ) ) { return video_get_highest_resolution_image_url( $id ); } else if ( class_exists( 'VideoPress_Video' ) ) { $video = new VideoPress_Video( $id ); return $video->poster_frame_uri; } } else if ( 'youtube' == $type ) { return 'http://img.youtube.com/vi/'.$id.'/0.jpg'; } } static function clean_text( $text ) { return trim( preg_replace( '/[\s]+/', ' ', preg_replace( '@https?://[\S]+@', '', strip_shortcodes( strip_tags( $text ) ) ) ) ); } static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256 ) { if ( function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) { return self::clean_text( wpcom_enhanced_excerpt_extract_excerpt( array( 'text' => $post_content, 'excerpt_only' => true, 'show_read_more' => false, 'max_words' => $max_words, 'max_chars' => $max_chars, 'read_more_threshold' => 25, ) ) ); } else { /** This filter is documented in core/src/wp-includes/post-template.php */ $post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt ); return self::clean_text( $post_excerpt ); } } static function get_word_count( $post_content ) { return str_word_count( self::clean_text( $post_content ) ); } static function get_word_remaining_count( $post_content, $excerpt_content ) { return str_word_count( self::clean_text( $post_content ) ) - str_word_count( self::clean_text( $excerpt_content ) ); } static function get_link_count( $post_content ) { return preg_match_all( '/\ ]/', $post_content, $matches ); } } /** * We won't have any videos less than sixty pixels wide. That would be silly. */ defined( 'VIDEOPRESS_MIN_WIDTH' ) or define( 'VIDEOPRESS_MIN_WIDTH', 60 ); /** * Validate user-supplied guid values against expected inputs * * @since 1.1 * @param string $guid video identifier * @return bool true if passes validation test */ function videopress_is_valid_guid( $guid ) { if ( ! empty( $guid ) && strlen( $guid ) === 8 && ctype_alnum( $guid ) ) { return true; } return false; } /** * Get details about a specific video by GUID: * * @param $guid string * @return object */ function videopress_get_video_details( $guid ) { if ( ! videopress_is_valid_guid( $guid ) ) { return new WP_Error( 'bad-guid-format', __( 'Invalid Video GUID!', 'jetpack' ) ); } $version = '1.1'; $endpoint = sprintf( '/videos/%1$s', $guid ); $response = wp_remote_get( sprintf( 'https://public-api.wordpress.com/rest/v%1$s%2$s', $version, $endpoint ) ); $data = json_decode( wp_remote_retrieve_body( $response ) ); /** * Allow functions to modify fetched video details. * * This filter allows third-party code to modify the return data * about a given video. It may involve swapping some data out or * adding new parameters. * * @since 4.0.0 * * @param object $data The data returned by the WPCOM API. See: https://developer.wordpress.com/docs/api/1.1/get/videos/%24guid/ * @param string $guid The GUID of the VideoPress video in question. */ return apply_filters( 'videopress_get_video_details', $data, $guid ); } /** * Get an attachment ID given a URL. * * Modified from http://wpscholar.com/blog/get-attachment-id-from-wp-image-url/ * * @todo: Add some caching in here. * * @param string $url * * @return int|bool Attachment ID on success, false on failure */ function videopress_get_attachment_id_by_url( $url ) { $wp_upload_dir = wp_upload_dir(); // Strip out protocols, so it doesn't fail because searching for http: in https: dir. $dir = set_url_scheme( trailingslashit( $wp_upload_dir['baseurl'] ), 'relative' ); // Is URL in uploads directory? if ( false !== strpos( $url, $dir ) ) { $file = basename( $url ); $query_args = array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'fields' => 'ids', 'meta_query' => array( array( 'key' => '_wp_attachment_metadata', 'compare' => 'LIKE', 'value' => $file, ), ) ); $query = new WP_Query( $query_args ); if ( $query->have_posts() ) { foreach ( $query->posts as $attachment_id ) { $meta = wp_get_attachment_metadata( $attachment_id ); $original_file = basename( $meta['file'] ); $cropped_files = wp_list_pluck( $meta['sizes'], 'file' ); if ( $original_file === $file || in_array( $file, $cropped_files ) ) { return (int) $attachment_id; } } } } return false; } /** * Similar to `media_sideload_image` -- but returns an ID. * * @param $url * @param $attachment_id * * @return int|mixed|object|WP_Error */ function videopress_download_poster_image( $url, $attachment_id ) { // Set variables for storage, fix file filename for query strings. preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $url, $matches ); if ( ! $matches ) { return new WP_Error( 'image_sideload_failed', __( 'Invalid image URL' ) ); } $file_array = array(); $file_array['name'] = basename( $matches[0] ); $file_array['tmp_name'] = download_url( $url ); // If error storing temporarily, return the error. if ( is_wp_error( $file_array['tmp_name'] ) ) { return $file_array['tmp_name']; } // Do the validation and storage stuff. $thumbnail_id = media_handle_sideload( $file_array, $attachment_id, null ); // Flag it as poster image, so we can exclude it from display. update_post_meta( $thumbnail_id, 'videopress_poster_image', 1 ); return $thumbnail_id; } /** * Creates a local media library item of a remote VideoPress video. * * @param $guid * @param int $parent_id * * @return int|object */ function create_local_media_library_for_videopress_guid( $guid, $parent_id = 0 ) { $vp_data = videopress_get_video_details( $guid ); if ( ! $vp_data || is_wp_error( $vp_data ) ) { return $vp_data; } $args = array( 'post_date' => $vp_data->upload_date, 'post_title' => wp_kses( $vp_data->title, array() ), 'post_content' => wp_kses( $vp_data->description, array() ), 'post_mime_type' => 'video/videopress', 'guid' => sprintf( 'https://videopress.com/v/%s', $guid ), ); $attachment_id = wp_insert_attachment( $args, null, $parent_id ); if ( ! is_wp_error( $attachment_id ) ) { update_post_meta( $attachment_id, 'videopress_guid', $guid ); wp_update_attachment_metadata( $attachment_id, array( 'width' => $vp_data->width, 'height' => $vp_data->height, ) ); $thumbnail_id = videopress_download_poster_image( $vp_data->poster, $attachment_id ); update_post_meta( $attachment_id, '_thumbnail_id', $thumbnail_id ); } return $attachment_id; } if ( defined( 'WP_CLI' ) && WP_CLI ) { /** * Manage and import VideoPress videos. */ class VideoPress_CLI extends WP_CLI_Command { /** * Import a VideoPress Video * * ## OPTIONS * * : Import the video with the specified guid * * ## EXAMPLES * * wp videopress import kUJmAcSf * */ public function import( $args ) { $guid = $args[0]; $attachment_id = create_local_media_library_for_videopress_guid( $guid ); if ( $attachment_id && ! is_wp_error( $attachment_id ) ) { WP_CLI::success( sprintf( __( 'The video has been imported as Attachment ID %d', 'jetpack' ), $attachment_id ) ); } else { WP_CLI::error( __( 'An error has been encountered.', 'jetpack' ) ); } } } WP_CLI::add_command( 'videopress', 'VideoPress_CLI' ); } /** * VideoPress Shortcode Handler * * This file may or may not be included from the Jetpack VideoPress module. */ /** * Translate a 'videopress' or 'wpvideo' shortcode and arguments into a video player display. * * Expected input formats: * * [videopress OcobLTqC] * [wpvideo OcobLTqC] * * @link http://codex.wordpress.org/Shortcode_API Shortcode API * @param array $attr shortcode attributes * @return string HTML markup or blank string on fail */ function videopress_shortcode_callback( $attr ) { global $content_width; /** * We only accept GUIDs as a first unnamed argument. */ $guid = $attr[0]; /** * Make sure the GUID passed in matches how actual GUIDs are formatted. */ if ( ! videopress_is_valid_guid( $guid ) ) { return ''; } /** * Set the defaults */ $defaults = array( 'w' => 0, // Width of the video player, in pixels 'at' => 0, // How many seconds in to initially seek to 'hd' => false, // Whether to display a high definition version 'loop' => false, // Whether to loop the video repeatedly 'freedom' => false, // Whether to use only free/libre codecs 'autoplay' => false, // Whether to autoplay the video on load 'permalink' => true, // Whether to display the permalink to the video 'flashonly' => false, // Whether to support the Flash player exclusively 'defaultlangcode' => false, // Default language code ); $attr = shortcode_atts( $defaults, $attr, 'videopress' ); /** * Cast the attributes, post-input. */ $attr['width'] = absint( $attr['w'] ); $attr['hd'] = (bool) $attr['hd']; $attr['freedom'] = (bool) $attr['freedom']; /** * If the provided width is less than the minimum allowed * width, or greater than `$content_width` ignore. */ if ( $attr['width'] < VIDEOPRESS_MIN_WIDTH ) { $attr['width'] = 0; } elseif ( isset( $content_width ) && $content_width > VIDEOPRESS_MIN_WIDTH && $attr['width'] > $content_width ) { $attr['width'] = 0; } /** * If there was an invalid or unspecified width, set the width equal to the theme's `$content_width`. */ if ( 0 === $attr['width'] && isset( $content_width ) && $content_width >= VIDEOPRESS_MIN_WIDTH ) { $attr['width'] = $content_width; } /** * If the width isn't an even number, reduce it by one (making it even). */ if ( 1 === ( $attr['width'] % 2 ) ) { $attr['width'] --; } /** * Filter the default VideoPress shortcode options. * * @module videopress * * @since 2.5.0 * * @param array $args Array of VideoPress shortcode options. */ $options = apply_filters( 'videopress_shortcode_options', array( 'at' => (int) $attr['at'], 'hd' => $attr['hd'], 'loop' => $attr['autoplay'] || $attr['loop'], 'freedom' => $attr['freedom'], 'autoplay' => $attr['autoplay'], 'permalink' => $attr['permalink'], 'force_flash' => (bool) $attr['flashonly'], 'defaultlangcode' => $attr['defaultlangcode'], 'forcestatic' => false, // This used to be a displayed option, but now is only // accessible via the `videopress_shortcode_options` filter. ) ); // Register VideoPress scripts wp_register_script( 'videopress', 'https://v0.wordpress.com/js/videopress.js', array( 'jquery', 'swfobject' ), '1.09' ); require_once( dirname( __FILE__ ) . '/class.videopress-video.php' ); require_once( dirname( __FILE__ ) . '/class.videopress-player.php' ); $player = new VideoPress_Player( $guid, $attr['width'], $options ); if ( is_feed() ) { return $player->asXML(); } else { return $player->asHTML(); } } add_shortcode( 'videopress', 'videopress_shortcode_callback' ); add_shortcode( 'wpvideo', 'videopress_shortcode_callback' ); /** * By explicitly declaring the provider here, we can speed things up by not relying on oEmbed discovery. */ wp_oembed_add_provider( '#^https?://videopress.com/v/.*#', 'http://public-api.wordpress.com/oembed/1.0/', true ); /** * Adds a `for` query parameter to the oembed provider request URL. * @param String $oembed_provider * @return String $ehnanced_oembed_provider */ function videopress_add_oembed_for_parameter( $oembed_provider ) { if ( false === stripos( $oembed_provider, 'videopress.com' ) ) { return $oembed_provider; } return add_query_arg( 'for', parse_url( home_url(), PHP_URL_HOST ), $oembed_provider ); } add_filter( 'oembed_fetch_url', 'videopress_add_oembed_for_parameter' ); /** * An intermediary shortcode parser for the Core `[video]` shortcode. * * This lets us convert legacy video embeds over to VideoPress embeds, * if the video files have been uploaded and transcoded. * * @param $attr * * @return string|void * / function videopress_shortcode_override_for_core_shortcode( $raw_attr, $contents, $tag ) { $attr = $raw_attr; $videopress_guid = false; if ( isset( $attr['videopress_guid'] ) ) { $videopress_guid = $attr['videopress_guid']; } // If we can find a local media item from the provided url… $media_id = videopress_get_attachment_id_by_url( $attr['src'] ); if ( $media_id ) { // And that local media item has a VideoPress GUID attached to it… $videopress_guid = get_post_meta( $media_id, 'videopress_guid', true ); } if ( $videopress_guid ) { $videopress_attr = array( $videopress_guid ); if ( $attr['width'] ) { $videopress_attr['w'] = (int) $attr['width']; } if ( $attr['autoplay'] ) { $videopress_attr['autoplay'] = $attr['autoplay']; } if ( $attr['loop'] ) { $videopress_attr['loop'] = $attr['loop']; } // Then display the VideoPress version of the stored GUID! return videopress_shortcode_callback( $videopress_attr ); } // Nothing else caught, so fall back to the core shortcode. return call_user_func( $GLOBALS['vp_original_video_shortcode_callback'], $raw_attr, $contents, $tag ); } // The callback should nearly always be `wp_video_shortcode` unless some other plugin // has overridden it similarly to what we're doing here. $GLOBALS['vp_original_video_shortcode_callback'] = $GLOBALS['shortcode_tags']['video']; remove_shortcode( 'video' ); add_shortcode( 'video', 'videopress_shortcode_override_for_core_shortcode' ); /**/