Add upstream

This commit is contained in:
root
2019-10-24 00:12:05 +02:00
parent 85d41e4216
commit ac980f592c
3504 changed files with 1049983 additions and 29971 deletions

View File

@@ -0,0 +1,342 @@
<?php
use Automattic\Jetpack\Assets;
/**
* VideoPress in Jetpack
*/
class Jetpack_VideoPress {
/** @var string */
public $module = 'videopress';
/** @var int */
public $version = 5;
/**
* Singleton
*/
public static function init() {
static $instance = false;
if ( ! $instance ) {
$instance = new Jetpack_VideoPress();
}
return $instance;
}
/**
* Jetpack_VideoPress constructor.
*
* Sets up the initializer and makes sure that videopress activates and deactivates properly.
*/
private function __construct() {
// $this->version = time(); // <s>ghost</s> cache busters!
add_action( 'init', array( $this, 'on_init' ) );
add_action( 'jetpack_deactivate_module_videopress', array( $this, 'jetpack_module_deactivated' ) );
}
/**
* Fires on init
*/
public function on_init() {
add_action( 'wp_enqueue_media', array( $this, 'enqueue_admin_scripts' ) );
add_filter( 'plupload_default_settings', array( $this, 'videopress_pluploder_config' ) );
add_filter( 'wp_get_attachment_url', array( $this, 'update_attachment_url_for_videopress' ), 10, 2 );
if ( Jetpack_Plan::supports( 'videopress' ) ) {
add_filter( 'upload_mimes', array( $this, 'add_video_upload_mimes' ), 999 );
}
add_action( 'admin_print_footer_scripts', array( $this, 'print_in_footer_open_media_add_new' ) );
add_action( 'admin_head', array( $this, 'enqueue_admin_styles' ) );
add_filter( 'wp_mime_type_icon', array( $this, 'wp_mime_type_icon' ), 10, 3 );
add_filter( 'wp_video_extensions', array( $this, 'add_videopress_extenstion' ) );
VideoPress_Scheduler::init();
VideoPress_XMLRPC::init();
}
/**
* Runs when the VideoPress module is deactivated.
*/
public function jetpack_module_deactivated() {
VideoPress_Options::delete_options();
}
/**
* A can of coke
*
* Similar to current_user_can, but internal to VideoPress. Returns
* true if the given VideoPress capability is allowed by the given user.
*/
public function can( $cap, $user_id = false ) {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
// Connection owners are allowed to do all the things.
if ( $this->is_connection_owner( $user_id ) ) {
return true;
}
// Additional and internal caps checks
if ( ! user_can( $user_id, 'upload_files' ) ) {
return false;
}
if ( 'edit_videos' == $cap && ! user_can( $user_id, 'edit_others_posts' ) ) {
return false;
}
if ( 'delete_videos' == $cap && ! user_can( $user_id, 'delete_others_posts' ) ) {
return false;
}
return true;
}
/**
* Returns true if the provided user is the Jetpack connection owner.
*/
public function is_connection_owner( $user_id = false ) {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && $user_id === $user_token->external_user_id;
}
/**
* Register and enqueue VideoPress admin styles.
*/
public function enqueue_admin_styles() {
wp_register_style( 'videopress-admin', plugins_url( 'videopress-admin.css', __FILE__ ), array(), $this->version );
wp_enqueue_style( 'videopress-admin' );
}
/**
* Register VideoPress admin scripts.
*/
public function enqueue_admin_scripts() {
if ( did_action( 'videopress_enqueue_admin_scripts' ) ) {
return;
}
if ( $this->should_override_media_uploader() ) {
wp_enqueue_script(
'videopress-plupload',
Assets::get_file_url_for_environment(
'_inc/build/videopress/js/videopress-plupload.min.js',
'modules/videopress/js/videopress-plupload.js'
),
array(
'jquery',
'wp-plupload',
),
$this->version
);
wp_enqueue_script(
'videopress-uploader',
Assets::get_file_url_for_environment(
'_inc/build/videopress/js/videopress-uploader.min.js',
'modules/videopress/js/videopress-uploader.js'
),
array(
'videopress-plupload',
),
$this->version
);
wp_enqueue_script(
'media-video-widget-extensions',
Assets::get_file_url_for_environment(
'_inc/build/videopress/js/media-video-widget-extensions.min.js',
'modules/videopress/js/media-video-widget-extensions.js'
),
array(),
$this->version,
true
);
}
/**
* Fires after VideoPress scripts are enqueued in the dashboard.
*
* @since 2.5.0
*/
do_action( 'videopress_enqueue_admin_scripts' );
}
/**
* An override for the attachment url, which returns back the WPCOM VideoPress processed url.
*
* This is an action proxy to the videopress_get_attachment_url() utility function.
*
* @param string $url
* @param int $post_id
*
* @return string
*/
public function update_attachment_url_for_videopress( $url, $post_id ) {
if ( $videopress_url = videopress_get_attachment_url( $post_id ) ) {
return $videopress_url;
}
return $url;
}
/**
* Modify the default plupload config to turn on videopress specific filters.
*/
public function videopress_pluploder_config( $config ) {
if ( ! isset( $config['filters']['max_file_size'] ) ) {
$config['filters']['max_file_size'] = wp_max_upload_size() . 'b';
}
$config['filters']['videopress_check_uploads'] = $config['filters']['max_file_size'];
// We're doing our own check in the videopress_check_uploads filter.
unset( $config['filters']['max_file_size'] );
return $config;
}
/**
* Helper function to determine if the media uploader should be overridden.
*
* The rules are simple, only try to load the script when on the edit post or new post pages.
*
* @return bool
*/
protected function should_override_media_uploader() {
global $pagenow;
// Only load in the admin
if ( ! is_admin() ) {
return false;
}
$acceptable_pages = array(
'post-new.php',
'post.php',
'upload.php',
'customize.php',
);
// Only load on the post, new post, or upload pages.
if ( ! in_array( $pagenow, $acceptable_pages ) ) {
return false;
}
$options = VideoPress_Options::get_options();
return $options['shadow_blog_id'] > 0;
}
/**
* A work-around / hack to make it possible to go to the media library with the add new box open.
*
* @return bool
*/
public function print_in_footer_open_media_add_new() {
global $pagenow;
// Only load in the admin
if ( ! is_admin() ) {
return false;
}
if ( $pagenow !== 'upload.php' ) {
return false;
}
if ( ! isset( $_GET['action'] ) || $_GET['action'] !== 'add-new' ) {
return false;
}
?>
<script type="text/javascript">
( function( $ ) {
window.setTimeout( function() {
$('#wp-media-grid .page-title-action').click();
}, 500 );
}( jQuery ) );
</script>
<?php
}
/**
* Makes sure that all video mimes are added in, as multi site installs can remove them.
*
* @param array $existing_mimes
* @return array
*/
public function add_video_upload_mimes( $existing_mimes = array() ) {
$mime_types = wp_get_mime_types();
$video_types = array_filter( $mime_types, array( $this, 'filter_video_mimes' ) );
foreach ( $video_types as $key => $value ) {
$existing_mimes[ $key ] = $value;
}
// Make sure that videopress mimes are considered videos.
$existing_mimes['videopress'] = 'video/videopress';
return $existing_mimes;
}
/**
* Filter designed to get rid of non video mime types.
*
* @param string $value
* @return int
*/
public function filter_video_mimes( $value ) {
return preg_match( '@^video/@', $value );
}
/**
* @param string $icon
* @param string $mime
* @param int $post_id
*
* @return string
*/
public function wp_mime_type_icon( $icon, $mime, $post_id ) {
if ( $mime !== 'video/videopress' ) {
return $icon;
}
$status = get_post_meta( $post_id, 'videopress_status', true );
if ( $status === 'complete' ) {
return $icon;
}
return 'https://wordpress.com/wp-content/mu-plugins/videopress/images/media-video-processing-icon.png';
}
/**
* @param array $extensions
*
* @return array
*/
public function add_videopress_extenstion( $extensions ) {
$extensions[] = 'videopress';
return $extensions;
}
}
// Initialize the module.
Jetpack_VideoPress::init();

View File

@@ -0,0 +1,105 @@
<?php
use Automattic\Jetpack\Connection\Client;
class VideoPress_AJAX {
/**
* @var VideoPress_AJAX
**/
private static $instance = null;
/**
* Private VideoPress_AJAX constructor.
*
* Use the VideoPress_AJAX::init() method to get an instance.
*/
private function __construct() {
add_action( 'wp_ajax_videopress-get-upload-token', array( $this, 'wp_ajax_videopress_get_upload_token' ) );
add_action(
'wp_ajax_videopress-update-transcoding-status',
array(
$this,
'wp_ajax_update_transcoding_status',
),
-1
);
}
/**
* Initialize the VideoPress_AJAX and get back a singleton instance.
*
* @return VideoPress_AJAX
*/
public static function init() {
if ( is_null( self::$instance ) ) {
self::$instance = new VideoPress_AJAX();
}
return self::$instance;
}
/**
* Ajax method that is used by the VideoPress uploader to get a token to upload a file to the wpcom api.
*
* @return void
*/
public function wp_ajax_videopress_get_upload_token() {
$options = VideoPress_Options::get_options();
$args = array(
'method' => 'POST',
// 'sslverify' => false,
);
$endpoint = "sites/{$options['shadow_blog_id']}/media/token";
$result = Client::wpcom_json_api_request_as_blog( $endpoint, Client::WPCOM_JSON_API_VERSION, $args );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack' ) ) );
return;
}
$response = json_decode( $result['body'], true );
if ( empty( $response['upload_token'] ) ) {
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack' ) ) );
return;
}
$response['upload_action_url'] = videopress_make_media_upload_path( $options['shadow_blog_id'] );
wp_send_json_success( $response );
}
/**
* Ajax action to update the video transcoding status from the WPCOM API.
*
* @return void
*/
public function wp_ajax_update_transcoding_status() {
if ( ! isset( $_POST['post_id'] ) ) {
wp_send_json_error( array( 'message' => __( 'A valid post_id is required.', 'jetpack' ) ) );
return;
}
$post_id = (int) $_POST['post_id'];
if ( ! videopress_update_meta_data( $post_id ) ) {
wp_send_json_error( array( 'message' => __( 'That post does not have a VideoPress video associated to it.', 'jetpack' ) ) );
return;
}
wp_send_json_success(
array(
'message' => __( 'Status updated', 'jetpack' ),
'status' => videopress_get_transcoding_status( $post_id ),
)
);
}
}
// Let's start this thing up.
VideoPress_AJAX::init();

View File

@@ -0,0 +1,167 @@
<?php
if ( defined( 'WP_CLI' ) && WP_CLI ) {
/**
* VideoPress command line utilities.
*/
class VideoPress_CLI extends WP_CLI_Command {
/**
* Import a VideoPress Video
*
* ## OPTIONS
*
* <guid>: 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' ) );
}
}
/**
* Manually runs the job to cleanup videos from the media library that failed during the upload process.
*
* ## EXAMPLES
*
* wp videopress cleanup_videos
*/
public function cleanup_videos() {
$num_cleaned = videopress_cleanup_media_library();
WP_CLI::success( sprintf( _n( 'Cleaned up %d video.', 'Cleaned up a total of %d videos.', $num_cleaned, 'jetpack' ), $num_cleaned ) );
}
/**
* List out all of the crons that can be run.
*
* ## EXAMPLES
*
* wp videopress list_crons
*/
public function list_crons() {
$scheduler = VideoPress_Scheduler::init();
$crons = $scheduler->get_crons();
$schedules = wp_get_schedules();
if ( count( $crons ) === 0 ) {
WP_CLI::success( __( 'Found no available cron jobs.', 'jetpack' ) );
} else {
WP_CLI::success( sprintf( _n( 'Found %d available cron job.', 'Found %d available cron jobs.', count( $crons ), 'jetpack' ), count( $crons ) ) );
}
foreach ( $crons as $cron_name => $cron ) {
$interval = isset( $schedules[ $cron['interval'] ]['display'] ) ? $schedules[ $cron['interval'] ]['display'] : $cron['interval'];
$runs_next = $scheduler->check_cron( $cron_name );
$status = $runs_next ? sprintf( 'Scheduled - Runs Next at %s GMT', gmdate( 'Y-m-d H:i:s', $runs_next ) ) : 'Not Scheduled';
WP_CLI::log( 'Name: ' . $cron_name );
WP_CLI::log( 'Method: ' . $cron['method'] );
WP_CLI::log( 'Interval: ' . $interval );
WP_CLI::log( 'Status: ' . $status );
}
}
/**
* Checks for the current status of a cron job.
*
* ## OPTIONS
*
* <cron_name>: The name of the cron job to check
*
* ## EXAMPLES
*
* wp videopress cron_status cleanup
*/
public function cron_status( $args ) {
if ( ! isset( $args[0] ) ) {
return WP_CLI::error( __( 'You need to provide the name of the cronjob to schedule.', 'jetpack' ) );
}
$scheduler = VideoPress_Scheduler::init();
if ( ! $scheduler->is_cron_valid( $args[0] ) ) {
return WP_CLI::error( sprintf( __( 'There is no cron named %s.', 'jetpack' ), $args[0] ) );
}
$time = $scheduler->check_cron( $args[0] );
if ( ! $time ) {
WP_CLI::success( __( 'The cron is not scheduled to run.', 'jetpack' ) );
} else {
WP_CLI::success( sprintf( __( 'Cron will run at: %s GMT', 'jetpack' ), gmdate( 'Y-m-d H:i:s', $time ) ) );
}
}
/**
* Actives the given cron job
*
* ## OPTIONS
*
* <cron_name>: The name of the cron job to check
*
* ## EXAMPLES
*
* wp videopress activate_cron cleanup
*/
public function activate_cron( $args ) {
if ( ! isset( $args[0] ) ) {
WP_CLI::error( __( 'You need to provide the name of the cronjob to schedule.', 'jetpack' ) );
}
$scheduler = VideoPress_Scheduler::init();
if ( ! $scheduler->is_cron_valid( $args[0] ) ) {
return WP_CLI::error( sprintf( __( 'There is no cron named %s.', 'jetpack' ), $args[0] ) );
}
$scheduler->activate_cron( $args[0] );
WP_CLI::success( sprintf( __( 'The cron named `%s` was scheduled.', 'jetpack' ), $args[0] ) );
}
/**
* Actives the given cron job
*
* ## OPTIONS
*
* <cron_name>: The name of the cron job to check
*
* ## EXAMPLES
*
* wp videopress deactivate_cron cleanup
*/
public function deactivate_cron( $args ) {
if ( ! isset( $args[0] ) ) {
WP_CLI::error( __( 'You need to provide the name of the cronjob to schedule.', 'jetpack' ) );
}
$scheduler = VideoPress_Scheduler::init();
if ( ! $scheduler->is_cron_valid( $args[0] ) ) {
return WP_CLI::error( sprintf( __( 'There is no cron named %s.', 'jetpack' ), $args[0] ) );
}
$scheduler->deactivate_cron( $args[0] );
WP_CLI::success( sprintf( __( 'The cron named `%s` was removed from the schedule.', 'jetpack' ), $args[0] ) );
}
}
WP_CLI::add_command( 'videopress', 'VideoPress_CLI' );
}

View File

@@ -0,0 +1,391 @@
<?php
use Automattic\Jetpack\Connection\Client;
/**
* VideoPress edit attachment screen
*
* @since 4.1
*/
class VideoPress_Edit_Attachment {
/**
* Singleton method to initialize the object only once.
*
* @return VideoPress_Edit_Attachment
*/
public static function init() {
static $instance = null;
if ( ! $instance ) {
$instance = new VideoPress_Edit_Attachment();
}
return $instance;
}
/**
* VideoPress_Edit_Attachment constructor.
*
* Adds in appropriate actions for attachment fields editor, meta boxes and saving.
*/
public function __construct() {
add_filter( 'attachment_fields_to_edit', array( $this, 'fields_to_edit' ), 10, 2 );
add_filter( 'attachment_fields_to_save', array( $this, 'save_fields' ), 10, 2 );
add_filter( 'wp_ajax_save-attachment', array( $this, 'save_fields' ), -1 );
add_filter( 'wp_ajax_save-attachment-compat', array( $this, 'save_fields' ), -1 );
add_action( 'add_meta_boxes', array( $this, 'configure_meta_boxes' ), 10, 2 );
}
/**
* @param string $post_type
* @param object $post
*/
public function configure_meta_boxes( $post_type = 'unknown', $post = null ) {
if ( null == $post ) {
$post = (object) array( 'ID' => 0 );
}
if ( 'attachment' != $post_type ) {
return;
}
// If this has not been processed by videopress, we can skip the rest.
if ( ! is_videopress_attachment( $post->ID ) ) {
return;
}
add_meta_box( 'videopress-media-info', __( 'VideoPress Information', 'jetpack' ), array( $this, 'videopress_information_box' ), 'attachment', 'side', 'core' );
}
/**
* @param array $post
* @param array|null $attachment
*
* @return array
*/
public function save_fields( $post, $attachment = null ) {
if ( $attachment === null && isset( $_POST['attachment'] ) ) {
$attachment = $_POST['attachment'];
}
if ( ! isset( $attachment['is_videopress_attachment'] ) || $attachment['is_videopress_attachment'] !== 'yes' ) {
return $post;
}
$post_id = absint( $post['ID'] );
$meta = wp_get_attachment_metadata( $post_id );
// If this has not been processed by videopress, we can skip the rest.
if ( ! is_videopress_attachment( $post['ID'] ) ) {
return $post;
}
$values = array();
// Add the video title & description in, so that we save it properly.
if ( isset( $_POST['post_title'] ) ) {
$values['title'] = trim( strip_tags( $_POST['post_title'] ) );
}
if ( isset( $_POST['post_excerpt'] ) ) {
$values['description'] = trim( strip_tags( $_POST['post_excerpt'] ) );
}
if ( isset( $attachment['rating'] ) ) {
$rating = $attachment['rating'];
if ( ! empty( $rating ) && in_array( $rating, array( 'G', 'PG-13', 'R-17', 'X-18' ) ) ) {
$values['rating'] = $rating;
}
}
// We set a default here, as if it isn't selected, then we'll turn it off.
$values['display_embed'] = 0;
if ( isset( $attachment['display_embed'] ) ) {
$display_embed = $attachment['display_embed'];
$values['display_embed'] = 'on' === $display_embed ? 1 : 0;
}
$args = array(
'method' => 'POST',
);
$guid = get_post_meta( $post_id, 'videopress_guid', true );
$endpoint = "videos/{$guid}";
$result = Client::wpcom_json_api_request_as_blog( $endpoint, Client::WPCOM_JSON_API_VERSION, $args, $values );
if ( is_wp_error( $result ) ) {
$post['errors']['videopress']['errors'][] = __( 'There was an issue saving your updates to the VideoPress service. Please try again later.', 'jetpack' );
return $post;
}
if ( isset( $values['display_embed'] ) ) {
$meta['videopress']['display_embed'] = $values['display_embed'];
}
if ( isset( $values['rating'] ) ) {
$meta['videopress']['rating'] = $values['rating'];
}
wp_update_attachment_metadata( $post_id, $meta );
$response = json_decode( $result['body'], true );
if ( 'true' !== $response ) {
return $post;
}
return $post;
}
/**
* Get the upload api path.
*
* @param string $guid
* @return string
*/
public function make_video_api_path( $guid ) {
return sprintf(
'%s://%s/rest/v%s/videos/%s',
'https',
'public-api.wordpress.com', // JETPACK__WPCOM_JSON_API_HOST,
Client::WPCOM_JSON_API_VERSION,
$guid
);
}
/**
* Creates an array of video fields to edit based on transcoded videos.
*
* @param array $fields video fields of interest
* @param stdClass $post post object
* @return array modified version of video fields for administrative interface display
*/
public function fields_to_edit( $fields, $post ) {
$post_id = absint( $post->ID );
$meta = wp_get_attachment_metadata( $post_id );
// If this has not been processed by videopress, we can skip the rest.
if ( ! is_videopress_attachment( $post_id ) || ! isset( $meta['videopress'] ) ) {
return $fields;
}
$info = (object) $meta['videopress'];
$file_statuses = isset( $meta['file_statuses'] ) ? $meta['file_statuses'] : array();
$guid = get_post_meta( $post_id, 'videopress_guid', true );
unset( $fields['url'] );
unset( $fields['post_content'] );
if ( isset( $file_statuses['ogg'] ) && 'done' === $file_statuses['ogg'] ) {
$v_name = preg_replace( '/\.\w+/', '', basename( $info->path ) );
$video_name = $v_name . '_fmt1.ogv';
$ogg_url = videopress_cdn_file_url( $guid, $video_name );
$fields['video-ogg'] = array(
'label' => __( 'Ogg File URL', 'jetpack' ),
'input' => 'html',
'html' => "<input type='text' class='urlfield' readonly='readonly' name='attachments[$post_id][oggurl]' value='" . esc_url( $ogg_url, array( 'http', 'https' ) ) . "' />",
'helps' => __( 'Location of the Ogg video file.', 'jetpack' ),
);
}
$fields['post_title']['helps'] = __( 'Title will appear on the first frame of your video', 'jetpack' );
$fields['post_excerpt']['label'] = _x( 'Description', 'A header for the short description display', 'jetpack' );
$fields['post_excerpt']['input'] = 'textarea';
$fields['post_excerpt']['value'] = $info->description;
$fields['is_videopress_attachment'] = array(
'input' => 'hidden',
'value' => 'yes',
);
$fields['videopress_shortcode'] = array(
'label' => _x( 'Shortcode', 'A header for the shortcode display', 'jetpack' ),
'input' => 'html',
'html' => "<input type=\"text\" name=\"videopress_shortcode\" value=\"[videopress {$guid}]\" readonly=\"readonly\"/>",
'show_in_modal' => true,
'show_in_edit' => false,
);
$fields['display_embed'] = array(
'label' => _x( 'Share', 'A header for the video sharing options area', 'jetpack' ),
'input' => 'html',
'html' => $this->display_embed_choice( $info ),
);
$fields['video-rating'] = array(
'label' => _x( 'Rating', 'A header for the video rating area', 'jetpack' ),
'input' => 'html',
'html' => $this->display_rating( $info ),
);
return $fields;
}
/**
* @param stdClass $post
*/
public function videopress_information_box( $post ) {
$post_id = absint( $post->ID );
$meta = wp_get_attachment_metadata( $post_id );
$guid = get_post_meta( $post_id, 'videopress_guid', true );
// If this has not been processed by videopress, we can skip the rest.
if ( ! is_videopress_attachment( $post_id ) ) {
return;
}
$info = (object) $meta['videopress'];
$status = videopress_get_transcoding_status( $post_id );
$formats = array(
'std_mp4' => 'Standard MP4',
'std_ogg' => 'OGG Vorbis',
'dvd_mp4' => 'DVD',
'hd_mp4' => 'High Definition',
);
$embed = "[videopress {$guid}]";
$shortcode = '<input type="text" id="plugin-embed" readonly="readonly" style="width:180px;" value="' . esc_attr( $embed ) . '" onclick="this.focus();this.select();" />';
$trans_status = '';
$all_trans_done = true;
foreach ( $formats as $status_key => $name ) {
if ( 'DONE' !== $status[ $status_key ] ) {
$all_trans_done = false;
}
$trans_status .= '- <strong>' . $name . ":</strong> <span id=\"status_$status_key\">" . ( 'DONE' === $status[ $status_key ] ? 'Done' : 'Processing' ) . '</span><br>';
}
$nonce = wp_create_nonce( 'videopress-update-transcoding-status' );
$url = 'empty';
if ( ! empty( $guid ) ) {
$url = videopress_build_url( $guid );
$url = "<a href=\"{$url}\">{$url}</a>";
}
$poster = '<em>Still Processing</em>';
if ( ! empty( $info->poster ) ) {
$poster = "<br><img src=\"{$info->poster}\" width=\"175px\">";
}
$status_update = '';
if ( ! $all_trans_done ) {
$status_update = ' (<a href="javascript:;" id="videopress-update-transcoding-status">update</a>)';
}
$html = <<< HTML
<div class="misc-pub-section misc-pub-shortcode">
<strong>Shortcode</strong><br>
{$shortcode}
</div>
<div class="misc-pub-section misc-pub-url">
<strong>Url</strong>
{$url}
</div>
<div class="misc-pub-section misc-pub-poster">
<strong>Poster</strong>
{$poster}
</div>
<div class="misc-pub-section misc-pub-status">
<strong>Transcoding Status$status_update:</strong>
<div id="videopress-transcoding-status">{$trans_status}</div>
</div>
<script>
jQuery( function($) {
$( '#videopress-update-transcoding-status' ).on( "click", function() {
jQuery.ajax( {
type: 'post',
url: 'admin-ajax.php',
data: {
action: 'videopress-update-transcoding-status',
post_id: '{$post_id}',
_ajax_nonce: '{$nonce}'
},
complete: function( response ) {
if ( 200 === response.status ) {
var statuses = response.responseJSON.data.status;
for (var key in statuses) {
$('#status_' + key).text( 'DONE' === statuses[key] ? 'Done' : 'Processing' );
}
}
}
});
} );
} );
</script>
HTML;
echo $html;
}
/**
* Build HTML to display a form checkbox for embedcode display preference
*
* @param object $info database row from the videos table
* @return string input element of type checkbox set to checked state based on stored embed preference
*/
protected function display_embed_choice( $info ) {
$id = "attachments-{$info->post_id}-displayembed";
$out = "<label for='$id'><input type='checkbox' name='attachments[{$info->post_id}][display_embed]' id='$id'";
if ( $info->display_embed ) {
$out .= ' checked="checked"';
}
$out .= ' />' . __( 'Display share menu and allow viewers to embed or download this video', 'jetpack' ) . '</label>';
return $out;
}
/**
* Build HTML to display a form input radio button for video ratings
*
* @param object $info database row from the videos table
* @return string input elements of type radio with existing stored value selected
*/
protected function display_rating( $info ) {
$out = '';
$ratings = array(
'G' => 'G',
'PG-13' => 'PG-13',
'R-17' => 'R',
'X-18' => 'X',
);
foreach ( $ratings as $r => $label ) {
$id = "attachments-{$info->post_id}-rating-$r";
$out .= "<label for=\"$id\"><input type=\"radio\" name=\"attachments[{$info->post_id}][rating]\" id=\"$id\" value=\"$r\"";
if ( $info->rating == $r ) {
$out .= ' checked="checked"';
}
$out .= " />$label</label>";
unset( $id );
}
return $out;
}
}
// Let's start this thing up.
VideoPress_Edit_Attachment::init();

View File

@@ -0,0 +1,169 @@
<?php
/**
* Block Editor functionality for VideoPress users.
*
* @package Jetpack
*/
/**
* Register a VideoPress extension to replace the default Core Video block.
*/
class VideoPress_Gutenberg {
/**
* Singleton
*/
public static function init() {
static $instance = false;
if ( ! $instance ) {
$instance = new VideoPress_Gutenberg();
}
return $instance;
}
/**
* VideoPress_Gutenberg constructor.
*
* Initialize the VideoPress Gutenberg extension
*/
private function __construct() {
add_action( 'init', array( $this, 'register_video_block_with_videopress' ) );
add_action( 'jetpack_register_gutenberg_extensions', array( $this, 'set_extension_availability' ) );
}
/**
* Used to check whether VideoPress is enabled for given site.
*
* @todo Create a global `jetpack_check_module_availability( $module )` helper so we can re-use it on other modules.
* This global helper should be created in a file synced with WordPress.com so we can use it there too.
* @see https://github.com/Automattic/jetpack/pull/11321#discussion_r255477815
*
* @return array Associative array indicating if the module is available (key `available`) and the reason why it is
* unavailable (key `unavailable_reason`)
*/
public function check_videopress_availability() {
// It is available on Simple Sites having the appropriate a plan.
if (
defined( 'IS_WPCOM' ) && IS_WPCOM
&& method_exists( 'Store_Product_List', 'get_site_specific_features_data' )
) {
$features = Store_Product_List::get_site_specific_features_data();
if ( in_array( 'videopress', $features['active'], true ) ) {
return array( 'available' => true );
} else {
return array(
'available' => false,
'unavailable_reason' => 'missing_plan',
);
}
}
// It is available on Jetpack Sites having the module active.
if (
method_exists( 'Jetpack', 'is_active' ) && Jetpack::is_active()
&& method_exists( 'Jetpack', 'is_module_active' )
&& method_exists( 'Jetpack_Plan', 'supports' )
) {
if ( Jetpack::is_module_active( 'videopress' ) ) {
return array( 'available' => true );
} elseif ( ! Jetpack_Plan::supports( 'videopress' ) ) {
return array(
'available' => false,
'unavailable_reason' => 'missing_plan',
);
} else {
return array(
'available' => false,
'unavailable_reason' => 'missing_module',
);
}
}
return array(
'available' => false,
'unavailable_reason' => 'unknown',
);
}
/**
* Set the Jetpack Gutenberg extension availability.
*/
public function set_extension_availability() {
$availability = $this->check_videopress_availability();
if ( $availability['available'] ) {
Jetpack_Gutenberg::set_extension_available( 'jetpack/videopress' );
} else {
Jetpack_Gutenberg::set_extension_unavailable( 'jetpack/videopress', $availability['unavailable_reason'] );
}
}
/**
* Register the core video block as a dynamic block.
*
* It defines a server-side rendering that adds VideoPress support to the core video block.
*/
public function register_video_block_with_videopress() {
jetpack_register_block(
'core/video',
array(
'render_callback' => array( $this, 'render_video_block_with_videopress' ),
)
);
}
/**
* Render the core video block replacing the src attribute with the VideoPress URL
*
* @param array $attributes Array containing the video block attributes.
* @param string $content String containing the video block content.
*
* @return string
*/
public function render_video_block_with_videopress( $attributes, $content ) {
if ( ! isset( $attributes['id'] ) || isset( $attributes['guid'] ) ) {
return $content;
}
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
$blog_id = get_current_blog_id();
} elseif ( method_exists( 'Jetpack', 'is_active' ) && Jetpack::is_active() ) {
/**
* We're intentionally not using `get_current_blog_id` because it was returning unexpected values.
*
* @see https://github.com/Automattic/jetpack/pull/11193#issuecomment-457883886
* @see https://github.com/Automattic/jetpack/pull/11193/commits/215cf789f3d8bd03ff9eb1bbdb693acb8831d273
*/
$blog_id = Jetpack_Options::get_option( 'id' );
}
if ( ! isset( $blog_id ) ) {
return $content;
}
$post_id = absint( $attributes['id'] );
$videopress_id = video_get_info_by_blogpostid( $blog_id, $post_id )->guid;
$videopress_data = videopress_get_video_details( $videopress_id );
if ( empty( $videopress_data->file_url_base->https ) || empty( $videopress_data->files->hd->mp4 ) ) {
return $content;
}
$videopress_url = $videopress_data->file_url_base->https . $videopress_data->files->hd->mp4;
$pattern = '/(\s)src=([\'"])(?:(?!\2).)+?\2/';
return preg_replace(
$pattern,
sprintf(
'\1src="%1$s"',
esc_url_raw( $videopress_url )
),
$content,
1
);
}
}
VideoPress_Gutenberg::init();

View File

@@ -0,0 +1,59 @@
<?php
class VideoPress_Options {
/** @var string */
public static $option_name = 'videopress';
/** @var array */
protected static $options = array();
/**
* Get VideoPress options
*/
public static function get_options() {
// Make sure we only get options from the database and services once per connection.
if ( count( self::$options ) > 0 ) {
return self::$options;
}
$defaults = array(
'meta' => array(
'max_upload_size' => 0,
),
);
self::$options = Jetpack_Options::get_option( self::$option_name, array() );
self::$options = array_merge( $defaults, self::$options );
// Make sure that the shadow blog id never comes from the options, but instead uses the
// associated shadow blog id, if videopress is enabled.
self::$options['shadow_blog_id'] = 0;
// Use the Jetpack ID for the shadow blog ID if we have a plan that supports VideoPress
if ( Jetpack_Plan::supports( 'videopress' ) ) {
self::$options['shadow_blog_id'] = Jetpack_Options::get_option( 'id' );
}
return self::$options;
}
/**
* Update VideoPress options
*/
public static function update_options( $options ) {
Jetpack_Options::update_option( self::$option_name, $options );
self::$options = $options;
}
/**
* Runs when the VideoPress module is deactivated.
*/
public static function delete_options() {
Jetpack_Options::delete_option( self::$option_name );
self::$options = array();
}
}

View File

@@ -0,0 +1,880 @@
<?php
/**
* VideoPress playback module markup generator.
*
* @since 1.3
*/
class VideoPress_Player {
/**
* Video data for the requested guid and maximum width
*
* @since 1.3
* @var VideoPress_Video
*/
protected $video;
/**
* DOM identifier of the video container
*
* @var string
* @since 1.3
*/
protected $video_container_id;
/**
* DOM identifier of the video element (video, object, embed)
*
* @var string
* @since 1.3
*/
protected $video_id;
/**
* Array of playback options: force_flash or freedom
*
* @var array
* @since 1.3
*/
protected $options;
/**
* Array of video GUIDs shown and their counts,
* moved from the old VideoPress class.
*/
public static $shown = array();
/**
* Initiate a player object based on shortcode values and possible blog-level option overrides
*
* @since 1.3
* @var string $guid VideoPress unique identifier
* @var int $maxwidth maximum desired width of the video player if specified
* @var array $options player customizations
*/
public function __construct( $guid, $maxwidth = 0, $options = array() ) {
if ( empty( self::$shown[ $guid ] ) ) {
self::$shown[ $guid ] = 0;
}
self::$shown[ $guid ]++;
$this->video_container_id = 'v-' . $guid . '-' . self::$shown[ $guid ];
$this->video_id = $this->video_container_id . '-video';
if ( is_array( $options ) ) {
$this->options = $options;
} else {
$this->options = array();
}
// set up the video
$cache_key = null;
// disable cache in debug mode
if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
$cached_video = null;
} else {
$cache_key_pieces = array( 'video' );
if ( is_multisite() && is_subdomain_install() ) {
$cache_key_pieces[] = get_current_blog_id();
}
$cache_key_pieces[] = $guid;
if ( $maxwidth > 0 ) {
$cache_key_pieces[] = $maxwidth;
}
if ( is_ssl() ) {
$cache_key_pieces[] = 'ssl';
}
$cache_key = implode( '-', $cache_key_pieces );
unset( $cache_key_pieces );
$cached_video = wp_cache_get( $cache_key, 'video' );
}
if ( empty( $cached_video ) ) {
$video = new VideoPress_Video( $guid, $maxwidth );
if ( empty( $video ) ) {
return;
} elseif ( isset( $video->error ) ) {
$this->video = $video->error;
return;
} elseif ( is_wp_error( $video ) ) {
$this->video = $video;
return;
}
$this->video = $video;
unset( $video );
if ( ! defined( 'WP_DEBUG' ) || WP_DEBUG !== true ) {
$expire = 3600;
if ( isset( $video->expires ) && is_int( $video->expires ) ) {
$expires_diff = time() - $video->expires;
if ( $expires_diff > 0 && $expires_diff < 86400 ) { // allowed range: 1 second to 1 day
$expire = $expires_diff;
}
unset( $expires_diff );
}
wp_cache_set( $cache_key, serialize( $this->video ), 'video', $expire );
unset( $expire );
}
} else {
$this->video = unserialize( $cached_video );
}
unset( $cache_key );
unset( $cached_video );
}
/**
* Wrap output in a VideoPress player container
*
* @since 1.3
* @var string $content HTML string
* @return string HTML string or blank string if nothing to wrap
*/
private function html_wrapper( $content ) {
if ( empty( $content ) ) {
return '';
} else {
return '<div id="' . esc_attr( $this->video_container_id ) . '" class="video-player">' . $content . '</div>';
}
}
/**
* Output content suitable for a feed reader displaying RSS or Atom feeds
* We do not display error messages in the feed view due to caching concerns.
* Flash content presented using <embed> markup for feed reader compatibility.
*
* @since 1.3
* @return string HTML string or empty string if error
*/
public function asXML() {
if ( empty( $this->video ) || is_wp_error( $this->video ) ) {
return '';
}
if ( isset( $this->options['force_flash'] ) && true === $this->options['force_flash'] ) {
$content = $this->flash_embed();
} else {
$content = $this->html5_static();
}
return $this->html_wrapper( $content );
}
/**
* Video player markup for best matching the current request and publisher options
*
* @since 1.3
* @return string HTML markup string or empty string if no video property found
*/
public function asHTML() {
if ( empty( $this->video ) ) {
$content = '';
} elseif ( is_wp_error( $this->video ) ) {
$content = $this->error_message( $this->video );
} elseif ( isset( $this->options['force_flash'] ) && true === $this->options['force_flash'] ) {
$content = $this->flash_object();
} elseif ( isset( $this->video->restricted_embed ) && true === $this->video->restricted_embed ) {
if ( $this->options['forcestatic'] ) {
$content = $this->flash_object();
} else {
$content = $this->html5_dynamic();
}
} elseif ( isset( $this->options['freedom'] ) && true === $this->options['freedom'] ) {
$content = $this->html5_static();
} else {
$content = $this->html5_dynamic();
}
return $this->html_wrapper( $content );
}
/**
* Display an error message to users capable of doing something about the error
*
* @since 1.3
* @uses current_user_can() to test if current user has edit_posts capability
* @var WP_Error $error WordPress error
* @return string HTML string
*/
private function error_message( $error ) {
if ( ! current_user_can( 'edit_posts' ) || empty( $error ) ) {
return '';
}
$html = '<div class="videopress-error" style="background-color:rgb(255,0,0);color:rgb(255,255,255);font-family:font-family:\'Helvetica Neue\',Arial,Helvetica,\'Nimbus Sans L\',sans-serif;font-size:140%;min-height:10em;padding-top:1.5em;padding-bottom:1.5em">';
$html .= '<h1 style="font-size:180%;font-style:bold;line-height:130%;text-decoration:underline">' . esc_html( sprintf( __( '%s Error', 'jetpack' ), 'VideoPress' ) ) . '</h1>';
foreach ( $error->get_error_messages() as $message ) {
$html .= $message;
}
$html .= '</div>';
return $html;
}
/**
* Rating agencies and industry associations require a potential viewer verify his or her age before a video or its poster frame are displayed.
* Content rated for audiences 17 years of age or older requires such verification across multiple rating agencies and industry associations
*
* @since 1.3
* @return bool true if video requires the viewer verify he or she is 17 years of age or older
*/
private function age_gate_required() {
if ( isset( $this->video->age_rating ) && $this->video->age_rating >= 17 ) {
return true;
} else {
return false;
}
}
/**
* Select a date of birth using HTML form elements.
*
* @since 1.5
* @return string HTML markup
*/
private function html_age_gate() {
global $wp_locale;
$text_align = 'left';
if ( $this->video->text_direction === 'rtl' ) {
$text_align = 'right';
}
$html = '<div class="videopress-age-gate" style="margin:0 60px">';
$html .= '<p class="instructions" style="color:rgb(255, 255, 255);font-size:21px;padding-top:60px;padding-bottom:20px;text-align:' . $text_align . '">' . esc_html( __( 'This video is intended for mature audiences.', 'jetpack' ) ) . '<br />' . esc_html( __( 'Please verify your birthday.', 'jetpack' ) ) . '</p>';
$html .= '<fieldset id="birthday" style="border:0 none;text-align:' . $text_align . ';padding:0;">';
$inputs_style = 'border:1px solid #444;margin-';
if ( $this->video->text_direction === 'rtl' ) {
$inputs_style .= 'left';
} else {
$inputs_style .= 'right';
}
$inputs_style .= ':10px;background-color:rgb(0, 0, 0);font-size:14px;color:rgb(255,255,255);padding:4px 6px;line-height: 2em;vertical-align: middle';
/**
* Display a list of months in the Gregorian calendar.
* Set values to 0-based to match JavaScript Date.
*
* @link https://developer.mozilla.org/en/JavaScript/Reference/global_objects/date Mozilla JavaScript Reference: Date
*/
$html .= '<select name="month" style="' . $inputs_style . '">';
for ( $i = 0; $i < 12; $i++ ) {
$html .= '<option value="' . esc_attr( $i ) . '">' . esc_html( $wp_locale->get_month( $i + 1 ) ) . '</option>';
}
$html .= '</select>';
/**
* todo: numdays variance by month
*/
$html .= '<select name="day" style="' . $inputs_style . '">';
for ( $i = 1; $i < 32; $i++ ) {
$html .= '<option>' . $i . '</option>';
}
$html .= '</select>';
/**
* Current record for human life is 122. Go back 130 years and no one is left out.
* Don't ask infants younger than 2 for their birthday
* Default to 13
*/
$html .= '<select name="year" style="' . $inputs_style . '">';
$start_year = date( 'Y' ) - 2;
$default_year = $start_year - 11;
$end_year = $start_year - 128;
for ( $year = $start_year; $year > $end_year; $year-- ) {
$html .= '<option';
if ( $year === $default_year ) {
$html .= ' selected="selected"';
}
$html .= '>' . $year . '</option>';
}
unset( $start_year );
unset( $default_year );
unset( $end_year );
$html .= '</select>';
$html .= '<input type="submit" value="' . __( 'Submit', 'jetpack' ) . '" style="cursor:pointer;border-radius: 1em;border:1px solid #333;background-color:#333;background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0, #444), color-stop(1, #111) );background:-moz-linear-gradient(center top, #444 0%, #111 100%);font-size:13px;padding:4px 10px 5px;line-height:1em;vertical-align:top;color:white;text-decoration:none;margin:0" />';
$html .= '</fieldset>';
$html .= '<p style="padding-top:20px;padding-bottom:60px;text-align:' . $text_align . ';"><a rel="nofollow noopener noreferrer" href="http://videopress.com/" target="_blank" style="color:rgb(128,128,128);text-decoration:underline;font-size:15px">' . __( 'More information', 'jetpack' ) . '</a></p>';
$html .= '</div>';
return $html;
}
/**
* Return HTML5 video static markup for the given video parameters.
* Use default browser player controls.
* No Flash fallback.
*
* @since 1.2
* @link http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html HTML5 video
* @return string HTML5 video element and children
*/
private function html5_static() {
wp_enqueue_script( 'videopress' );
$thumbnail = esc_url( $this->video->poster_frame_uri );
$html = "<video id=\"{$this->video_id}\" width=\"{$this->video->calculated_width}\" height=\"{$this->video->calculated_height}\" poster=\"$thumbnail\" controls=\"true\"";
if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
$html .= ' autoplay="true"';
} else {
$html .= ' preload="metadata"';
}
if ( isset( $this->video->text_direction ) ) {
$html .= ' dir="' . esc_attr( $this->video->text_direction ) . '"';
}
if ( isset( $this->video->language ) ) {
$html .= ' lang="' . esc_attr( $this->video->language ) . '"';
}
$html .= '>';
if ( ! isset( $this->options['freedom'] ) || $this->options['freedom'] === false ) {
$mp4 = $this->video->videos->mp4->url;
if ( ! empty( $mp4 ) ) {
$html .= '<source src="' . esc_url( $mp4 ) . '" type="video/mp4; codecs=&quot;' . esc_attr( $this->video->videos->mp4->codecs ) . '&quot;" />';
}
unset( $mp4 );
}
if ( isset( $this->video->videos->ogv ) ) {
$ogg = $this->video->videos->ogv->url;
if ( ! empty( $ogg ) ) {
$html .= '<source src="' . esc_url( $ogg ) . '" type="video/ogg; codecs=&quot;' . esc_attr( $this->video->videos->ogv->codecs ) . '&quot;" />';
}
unset( $ogg );
}
$html .= '<div><img alt="';
if ( isset( $this->video->title ) ) {
$html .= esc_attr( $this->video->title );
}
$html .= '" src="' . $thumbnail . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" /></div>';
if ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) {
$html .= '<p class="robots-nocontent">' . sprintf( __( 'You do not have sufficient <a rel="nofollow noopener noreferrer" href="%s" target="_blank">freedom levels</a> to view this video. Support free software and upgrade.', 'jetpack' ), 'http://www.gnu.org/philosophy/free-sw.html' ) . '</p>';
} elseif ( isset( $this->video->title ) ) {
$html .= '<p>' . esc_html( $this->video->title ) . '</p>';
}
$html .= '</video>';
return $html;
}
/**
* Click to play dynamic HTML5-capable player.
* The player displays a video preview section including poster frame,
* video title, play button and watermark on the original page load
* and calculates the playback capabilities of the browser. The video player
* is loaded when the visitor clicks on the video preview area.
* If Flash Player 10 or above is available the browser will display
* the Flash version of the video. If HTML5 video appears to be supported
* and the browser may be capable of MP4 (H.264, AAC) or OGV (Theora, Vorbis)
* playback the browser will display its native HTML5 player.
*
* @since 1.5
* @return string HTML markup
*/
private function html5_dynamic() {
/**
* Filter the VideoPress legacy player feature
*
* This filter allows you to control whether the legacy VideoPress player should be used
* instead of the improved one.
*
* @module videopress
*
* @since 3.7.0
*
* @param boolean $videopress_use_legacy_player
*/
if ( ! apply_filters( 'jetpack_videopress_use_legacy_player', false ) ) {
return $this->html5_dynamic_next();
}
wp_enqueue_script( 'videopress' );
$video_placeholder_id = $this->video_container_id . '-placeholder';
$age_gate_required = $this->age_gate_required();
$width = absint( $this->video->calculated_width );
$height = absint( $this->video->calculated_height );
$html = '<div id="' . $video_placeholder_id . '" class="videopress-placeholder" style="';
if ( $age_gate_required ) {
$html .= "min-width:{$width}px;min-height:{$height}px";
} else {
$html .= "width:{$width}px;height:{$height}px";
}
$html .= ';display:none;cursor:pointer !important;position:relative;';
if ( isset( $this->video->skin ) && isset( $this->video->skin->background_color ) ) {
$html .= 'background-color:' . esc_attr( $this->video->skin->background_color ) . ';';
}
$html .= 'font-family: \'Helvetica Neue\',Arial,Helvetica,\'Nimbus Sans L\',sans-serif;font-weight:bold;font-size:18px">' . PHP_EOL;
/**
* Do not display a poster frame, title, or any other content hints for mature content.
*/
if ( ! $age_gate_required ) {
if ( ! empty( $this->video->title ) ) {
$html .= '<div class="videopress-title" style="display:inline;position:absolute;margin:20px 20px 0 20px;padding:4px 8px;vertical-align:top;text-align:';
if ( $this->video->text_direction === 'rtl' ) {
$html .= 'right" dir="rtl"';
} else {
$html .= 'left" dir="ltr"';
}
if ( isset( $this->video->language ) ) {
$html .= ' lang="' . esc_attr( $this->video->language ) . '"';
}
$html .= '><span style="padding:3px 0;line-height:1.5em;';
if ( isset( $this->video->skin ) && isset( $this->video->skin->background_color ) ) {
$html .= 'background-color:';
if ( $this->video->skin->background_color === 'rgb(0,0,0)' ) {
$html .= 'rgba(0,0,0,0.8)';
} else {
$html .= esc_attr( $this->video->skin->background_color );
}
$html .= ';';
}
$html .= 'color:rgb(255,255,255)">' . esc_html( $this->video->title ) . '</span></div>';
}
$html .= '<img class="videopress-poster" alt="';
if ( ! empty( $this->video->title ) ) {
$html .= esc_attr( $this->video->title ) . '" title="' . esc_attr( sprintf( _x( 'Watch: %s', 'watch a video title', 'jetpack' ), $this->video->title ) );
}
$html .= '" src="' . esc_url( $this->video->poster_frame_uri, array( 'http', 'https' ) ) . '" width="' . $width . '" height="' . $height . '" />' . PHP_EOL;
// style a play button hovered over the poster frame
$html .= '<div class="play-button"><span style="z-index:2;display:block;position:absolute;top:50%;left:50%;text-align:center;vertical-align:middle;color:rgb(255,255,255);opacity:0.9;margin:0 0 0 -0.45em;padding:0;line-height:0;font-size:500%;text-shadow:0 0 40px rgba(0,0,0,0.5)">&#9654;</span></div>' . PHP_EOL;
// watermark
if ( isset( $this->video->skin ) && isset( $this->video->skin->watermark ) ) {
$html .= '<div style="position:relative;margin-top:-40px;height:25px;margin-bottom:35px;';
if ( $this->video->text_direction === 'rtl' ) {
$html .= 'margin-left:20px;text-align:left;';
} else {
$html .= 'margin-right:20px;text-align:right;';
}
$html .= 'vertical-align:bottom;z-index:3">';
$html .= '<img alt="" src="' . esc_url( $this->video->skin->watermark, array( 'http', 'https' ) ) . '" width="90" height="13" style="background-color:transparent;background-image:none;background-repeat:no-repeat;border:none;margin:0;padding:0"/>';
$html .= '</div>' . PHP_EOL;
}
}
$data = array(
'blog' => absint( $this->video->blog_id ),
'post' => absint( $this->video->post_id ),
'duration' => absint( $this->video->duration ),
'poster' => esc_url_raw( $this->video->poster_frame_uri, array( 'http', 'https' ) ),
'hd' => (bool) $this->options['hd'],
);
if ( isset( $this->video->videos ) ) {
if ( isset( $this->video->videos->mp4 ) && isset( $this->video->videos->mp4->url ) ) {
$data['mp4'] = array(
'size' => $this->video->videos->mp4->format,
'uri' => esc_url_raw( $this->video->videos->mp4->url, array( 'http', 'https' ) ),
);
}
if ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) {
$data['ogv'] = array(
'size' => 'std',
'uri' => esc_url_raw( $this->video->videos->ogv->url, array( 'http', 'https' ) ),
);
}
}
$locale = array( 'dir' => $this->video->text_direction );
if ( isset( $this->video->language ) ) {
$locale['lang'] = $this->video->language;
}
$data['locale'] = $locale;
unset( $locale );
$guid = $this->video->guid;
$guid_js = json_encode( $guid );
$html .= '<script type="text/javascript">' . PHP_EOL;
$html .= 'jQuery(document).ready(function() {';
$html .= 'if ( !jQuery.VideoPress.data[' . json_encode( $guid ) . '] ) { jQuery.VideoPress.data[' . json_encode( $guid ) . '] = new Array(); }' . PHP_EOL;
$html .= 'jQuery.VideoPress.data[' . json_encode( $guid ) . '][' . self::$shown[ $guid ] . ']=' . json_encode( $data ) . ';' . PHP_EOL;
unset( $data );
$jq_container = json_encode( '#' . $this->video_container_id );
$jq_placeholder = json_encode( '#' . $video_placeholder_id );
$player_config = "{width:{$width},height:{$height},";
if ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) {
$player_config .= 'freedom:"true",';
}
$player_config .= 'container:jQuery(' . $jq_container . ')}';
$html .= "jQuery({$jq_placeholder}).show(0,function(){jQuery.VideoPress.analytics.impression({$guid_js})});" . PHP_EOL;
if ( $age_gate_required ) {
$html .= 'if ( jQuery.VideoPress.support.flash() ) {' . PHP_EOL;
/**
* @link http://code.google.com/p/swfobject/wiki/api#swfobject.embedSWF(swfUrlStr,_replaceElemIdStr,_widthStr,_height
*/
$html .= 'swfobject.embedSWF(' . implode(
',',
array(
'jQuery.VideoPress.video.flash.player_uri',
json_encode( $this->video_container_id ),
json_encode( $width ),
json_encode( $height ),
'jQuery.VideoPress.video.flash.min_version',
'jQuery.VideoPress.video.flash.expressinstall', // attempt to upgrade the Flash player if less than min_version. requires a 310x137 container or larger but we will always try to include
'{guid:' . $guid_js . '}', // FlashVars
'jQuery.VideoPress.video.flash.params',
'null', // no attributes
'jQuery.VideoPress.video.flash.embedCallback', // error fallback
)
) . ');';
$html .= '} else {' . PHP_EOL;
$html .= "if ( jQuery.VideoPress.video.prepare({$guid_js},{$player_config}," . self::$shown[ $guid ] . ') ) {' . PHP_EOL;
$html .= 'if ( jQuery(' . $jq_container . ').data( "player" ) === "flash" ){jQuery.VideoPress.video.play(jQuery(' . json_encode( '#' . $this->video_container_id ) . '));}else{';
$html .= 'jQuery(' . $jq_placeholder . ').html(' . json_encode( $this->html_age_date() ) . ');' . PHP_EOL;
$html .= 'jQuery(' . json_encode( '#' . $video_placeholder_id . ' input[type=submit]' ) . ').one("click", function(event){jQuery.VideoPress.requirements.isSufficientAge(jQuery(' . $jq_container . '),' . absint( $this->video->age_rating ) . ')});' . PHP_EOL;
$html .= '}}}' . PHP_EOL;
} else {
$html .= "if ( jQuery.VideoPress.video.prepare({$guid_js}, {$player_config}," . self::$shown[ $guid ] . ') ) {' . PHP_EOL;
if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
$html .= "jQuery.VideoPress.video.play(jQuery({$jq_container}));";
} else {
$html .= 'jQuery(' . $jq_placeholder . ').one("click",function(){jQuery.VideoPress.video.play(jQuery(' . $jq_container . '))});';
}
$html .= '}';
// close the jQuery(document).ready() function
$html .= '});';
}
$html .= '</script>' . PHP_EOL;
$html .= '</div>' . PHP_EOL;
/*
* JavaScript required
*/
$noun = __( 'this video', 'jetpack' );
if ( ! $age_gate_required ) {
$vid_type = '';
if ( ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) && ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) ) {
$vid_type = 'ogv';
} elseif ( isset( $this->video->videos->mp4 ) && isset( $this->video->videos->mp4->url ) ) {
$vid_type = 'mp4';
} elseif ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) {
$vid_type = 'ogv';
}
if ( $vid_type !== '' ) {
$noun = '<a ';
if ( isset( $this->video->language ) ) {
$noun .= 'hreflang="' . esc_attr( $this->video->language ) . '" ';
}
if ( $vid_type === 'mp4' ) {
$noun .= 'type="video/mp4" href="' . esc_url( $this->video->videos->mp4->url, array( 'http', 'https' ) );
} elseif ( $vid_type === 'ogv' ) {
$noun .= 'type="video/ogv" href="' . esc_url( $this->video->videos->ogv->url, array( 'http', 'https' ) );
}
$noun .= '">';
if ( isset( $this->video->title ) ) {
$noun .= esc_html( $this->video->title );
} else {
$noun .= __( 'this video', 'jetpack' );
}
$noun .= '</a>';
} elseif ( ! empty( $this->title ) ) {
$noun = esc_html( $this->title );
}
unset( $vid_type );
}
$html .= '<noscript><p>' . sprintf( _x( 'JavaScript required to play %s.', 'Play as in playback or view a movie', 'jetpack' ), $noun ) . '</p></noscript>';
return $html;
}
function html5_dynamic_next() {
$video_container_id = 'v-' . $this->video->guid;
// Must not use iframes for IE11 due to a fullscreen bug
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && stristr( $_SERVER['HTTP_USER_AGENT'], 'Trident/7.0; rv:11.0' ) ) {
$iframe_embed = false;
} else {
/**
* Filter the VideoPress iframe embed
*
* This filter allows you to control whether the videos will be embedded using an iframe.
* Set this to false in order to use an in-page embed rather than an iframe.
*
* @module videopress
*
* @since 3.7.0
*
* @param boolean $videopress_player_use_iframe
*/
$iframe_embed = apply_filters( 'jetpack_videopress_player_use_iframe', true );
}
if ( ! array_key_exists( 'hd', $this->options ) ) {
$this->options['hd'] = (bool) get_option( 'video_player_high_quality', false );
}
$videopress_options = array(
'width' => absint( $this->video->calculated_width ),
'height' => absint( $this->video->calculated_height ),
);
foreach ( $this->options as $option => $value ) {
switch ( $option ) {
case 'at':
if ( intval( $value ) ) {
$videopress_options[ $option ] = intval( $value );
}
break;
case 'autoplay':
$option = 'autoPlay';
case 'hd':
case 'loop':
case 'permalink':
if ( in_array( $value, array( 1, 'true' ) ) ) {
$videopress_options[ $option ] = true;
} elseif ( in_array( $value, array( 0, 'false' ) ) ) {
$videopress_options[ $option ] = false;
}
break;
case 'defaultlangcode':
$option = 'defaultLangCode';
if ( $value ) {
$videopress_options[ $option ] = $value;
}
break;
}
}
if ( $iframe_embed ) {
$iframe_url = "https://videopress.com/embed/{$this->video->guid}";
foreach ( $videopress_options as $option => $value ) {
if ( ! in_array( $option, array( 'width', 'height' ) ) ) {
// add_query_arg ignores false as a value, so replacing it with 0
$iframe_url = add_query_arg( $option, ( false === $value ) ? 0 : $value, $iframe_url );
}
}
$js_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/next/videopress-iframe.js';
return "<iframe width='" . esc_attr( $videopress_options['width'] )
. "' height='" . esc_attr( $videopress_options['height'] )
. "' src='" . esc_attr( $iframe_url )
. "' frameborder='0' allowfullscreen></iframe>"
. "<script src='" . esc_attr( $js_url ) . "'></script>";
} else {
$videopress_options = json_encode( $videopress_options );
$js_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/next/videopress.js';
return "<div id='{$video_container_id}'></div>
<script src='{$js_url}'></script>
<script>
videopress('{$this->video->guid}', document.querySelector('#{$video_container_id}'), {$videopress_options});
</script>";
}
}
/**
* Only allow legitimate Flash parameters and their values
*
* @since 1.2
* @link http://kb2.adobe.com/cps/127/tn_12701.html Flash object and embed attributes
* @link http://kb2.adobe.com/cps/133/tn_13331.html devicefont
* @link http://kb2.adobe.com/cps/164/tn_16494.html allowscriptaccess
* @link http://www.adobe.com/devnet/flashplayer/articles/full_screen_mode.html full screen mode
* @link http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001079.html allownetworking
* @param array $flash_params Flash parameters expressed in key-value form
* @return array validated Flash parameters
*/
public static function esc_flash_params( $flash_params ) {
$allowed_params = array(
'swliveconnect' => array( 'true', 'false' ),
'play' => array( 'true', 'false' ),
'loop' => array( 'true', 'false' ),
'menu' => array( 'true', 'false' ),
'quality' => array( 'low', 'autolow', 'autohigh', 'medium', 'high', 'best' ),
'scale' => array( 'default', 'noborder', 'exactfit', 'noscale' ),
'align' => array( 'l', 'r', 't' ),
'salign' => array( 'l', 'r', 't', 'tl', 'tr', 'bl', 'br' ),
'wmode' => array( 'window', 'opaque', 'transparent', 'direct', 'gpu' ),
'devicefont' => array( '_sans', '_serif', '_typewriter' ),
'allowscriptaccess' => array( 'always', 'samedomain', 'never' ),
'allownetworking' => array( 'all', 'internal', 'none' ),
'seamlesstabbing' => array( 'true', 'false' ),
'allowfullscreen' => array( 'true', 'false' ),
'fullScreenAspectRatio' => array( 'portrait', 'landscape' ),
'base',
'bgcolor',
'flashvars',
);
$allowed_params_keys = array_keys( $allowed_params );
$filtered_params = array();
foreach ( $flash_params as $param => $value ) {
if ( empty( $param ) || empty( $value ) ) {
continue;
}
$param = strtolower( $param );
if ( in_array( $param, $allowed_params_keys ) ) {
if ( isset( $allowed_params[ $param ] ) && is_array( $allowed_params[ $param ] ) ) {
$value = strtolower( $value );
if ( in_array( $value, $allowed_params[ $param ] ) ) {
$filtered_params[ $param ] = $value;
}
} else {
$filtered_params[ $param ] = $value;
}
}
}
unset( $allowed_params_keys );
/**
* Flash specifies sameDomain, not samedomain. change from lowercase value for preciseness
*/
if ( isset( $filtered_params['allowscriptaccess'] ) && $filtered_params['allowscriptaccess'] === 'samedomain' ) {
$filtered_params['allowscriptaccess'] = 'sameDomain';
}
return $filtered_params;
}
/**
* Filter Flash variables from the response, taking into consideration player options.
*
* @since 1.3
* @return array Flash variable key value pairs
*/
private function get_flash_variables() {
if ( ! isset( $this->video->players->swf->vars ) ) {
return array();
}
$flashvars = (array) $this->video->players->swf->vars;
if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
$flashvars['autoPlay'] = 'true';
}
return $flashvars;
}
/**
* Validate and filter Flash parameters
*
* @since 1.3
* @return array Flash parameters passed through key and value validation
*/
private function get_flash_parameters() {
if ( ! isset( $this->video->players->swf->params ) ) {
return array();
} else {
return self::esc_flash_params(
/**
* Filters the Flash parameters of the VideoPress player.
*
* @module videopress
*
* @since 1.2.0
*
* @param array $this->video->players->swf->params Array of swf parameters for the VideoPress flash player.
*/
apply_filters( 'video_flash_params', (array) $this->video->players->swf->params, 10, 1 )
);
}
}
/**
* Flash player markup in a HTML embed element.
*
* @since 1.1
* @link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#the-embed-element embed element
* @link http://www.google.com/support/reader/bin/answer.py?answer=70664 Google Reader markup support
* @return string HTML markup. Embed element with no children
*/
private function flash_embed() {
wp_enqueue_script( 'videopress' );
if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
return '';
}
$embed = array(
'id' => $this->video_id,
'src' => esc_url_raw( $this->video->players->swf->url . '&' . http_build_query( $this->get_flash_variables(), null, '&' ), array( 'http', 'https' ) ),
'type' => 'application/x-shockwave-flash',
'width' => $this->video->calculated_width,
'height' => $this->video->calculated_height,
);
if ( isset( $this->video->title ) ) {
$embed['title'] = $this->video->title;
}
$embed = array_merge( $embed, $this->get_flash_parameters() );
$html = '<embed';
foreach ( $embed as $attribute => $value ) {
$html .= ' ' . esc_html( $attribute ) . '="' . esc_attr( $value ) . '"';
}
unset( $embed );
$html .= '></embed>';
return $html;
}
/**
* Double-baked Flash object markup for Internet Explorer and more standards-friendly consuming agents.
*
* @since 1.1
* @return HTML markup. Object and children.
*/
private function flash_object() {
wp_enqueue_script( 'videopress' );
if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
return '';
}
$thumbnail_html = '<img alt="';
if ( isset( $this->video->title ) ) {
$thumbnail_html .= esc_attr( $this->video->title );
}
$thumbnail_html .= '" src="' . esc_url( $this->video->poster_frame_uri, array( 'http', 'https' ) ) . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" />';
$flash_vars = esc_attr( http_build_query( $this->get_flash_variables(), null, '&' ) );
$flash_params = '';
foreach ( $this->get_flash_parameters() as $attribute => $value ) {
$flash_params .= '<param name="' . esc_attr( $attribute ) . '" value="' . esc_attr( $value ) . '" />';
}
$flash_help = sprintf( __( 'This video requires <a rel="nofollow noopener noreferrer" href="%s" target="_blank">Adobe Flash</a> for playback.', 'jetpack' ), 'http://www.adobe.com/go/getflashplayer' );
$flash_player_url = esc_url( $this->video->players->swf->url, array( 'http', 'https' ) );
$description = '';
if ( isset( $this->video->title ) ) {
$standby = $this->video->title;
$description = '<p><strong>' . esc_html( $this->video->title ) . '</strong></p>';
} else {
$standby = __( 'Loading video...', 'jetpack' );
}
$standby = ' standby="' . esc_attr( $standby ) . '"';
return <<<OBJECT
<script type="text/javascript">if(typeof swfobject!=="undefined"){swfobject.registerObject("{$this->video_id}", "{$this->video->players->swf->version}");}</script>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}" id="{$this->video_id}"{$standby}>
<param name="movie" value="{$flash_player_url}" />
{$flash_params}
<param name="flashvars" value="{$flash_vars}" />
<!--[if !IE]>-->
<object type="application/x-shockwave-flash" data="{$flash_player_url}" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}"{$standby}>
{$flash_params}
<param name="flashvars" value="{$flash_vars}" />
<!--<![endif]-->
{$thumbnail_html}{$description}<p class="robots-nocontent">{$flash_help}</p>
<!--[if !IE]>-->
</object>
<!--<![endif]-->
</object>
OBJECT;
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* VideoPress playback module markup generator.
*
* @since 1.3
*/
class VideoPress_Scheduler {
/**
* The name of the function used to run the cleanup cron.
*/
const CLEANUP_CRON_METHOD = 'videopress_cleanup_media_library';
/**
* @var VideoPress_Scheduler
**/
private static $instance = null;
/**
* A list of all of the crons that are to be activated, along with their interval timings.
*
* @var array
*/
protected $crons = array(
// 'cleanup' => array(
// 'method' => self::CLEANUP_CRON_METHOD,
// 'interval' => 'minutes_30',
// ),
);
/**
* Private VideoPress_Scheduler constructor.
*
* Use the VideoPress_Scheduler::init() method to get an instance.
*/
private function __construct() {
add_filter( 'cron_schedules', array( $this, 'add_30_minute_cron_interval' ) );
// Activate the cleanup cron if videopress is enabled, jetpack is activated, or jetpack is updated.
add_action( 'jetpack_activate_module_videopress', array( $this, 'activate_all_crons' ) );
add_action( 'updating_jetpack_version', array( $this, 'activate_all_crons' ) );
add_action( 'activated_plugin', array( $this, 'activate_crons_on_jetpack_activation' ) );
// Deactivate the cron if either videopress is disabled or Jetpack is disabled.
add_action( 'jetpack_deactivate_module_videopress', array( $this, 'deactivate_all_crons' ) );
register_deactivation_hook( plugin_basename( JETPACK__PLUGIN_FILE ), array( $this, 'deactivate_all_crons' ) );
}
/**
* Initialize the VideoPress_Scheduler and get back a singleton instance.
*
* @return VideoPress_Scheduler
*/
public static function init() {
if ( is_null( self::$instance ) ) {
self::$instance = new VideoPress_Scheduler();
}
return self::$instance;
}
/**
* Adds 30 minute running interval to the cron schedules.
*
* @param array $current_schedules Currently defined schedules list.
*
* @return array
*/
public function add_30_minute_cron_interval( $current_schedules ) {
// Only add the 30 minute interval if it wasn't already set.
if ( ! isset( $current_schedules['minutes_30'] ) ) {
$current_schedules['minutes_30'] = array(
'interval' => 30 * MINUTE_IN_SECONDS,
'display' => 'Every 30 minutes',
);
}
return $current_schedules;
}
/**
* Activate a single cron
*
* @param string $cron_name
*
* @return bool
*/
public function activate_cron( $cron_name ) {
if ( ! $this->is_cron_valid( $cron_name ) ) {
return false;
}
if ( ! $this->check_cron( $cron_name ) ) {
wp_schedule_event( time(), $this->crons[ $cron_name ]['interval'], $this->crons[ $cron_name ]['method'] );
}
}
/**
* Activates widget update cron task.
*/
public function activate_all_crons() {
if ( ! Jetpack::is_module_active( 'videopress' ) ) {
return false;
}
foreach ( $this->crons as $cron_name => $cron ) {
if ( ! $this->check_cron( $cron_name ) ) {
wp_schedule_event( time(), $cron['interval'], $cron['method'] );
}
}
}
/**
* Only activate the crons if it is Jetpack that was activated.
*
* @param string $plugin_file_name
*/
public function activate_crons_on_jetpack_activation( $plugin_file_name ) {
if ( plugin_basename( JETPACK__PLUGIN_FILE ) === $plugin_file_name ) {
$this->activate_all_crons();
}
}
/**
* Deactivates any crons associated with the VideoPress module.
*
* @return bool
*/
public function deactivate_cron( $cron_name ) {
if ( ! $this->is_cron_valid( $cron_name ) ) {
return false;
}
$next_scheduled_time = $this->check_cron( $cron_name );
wp_unschedule_event( $next_scheduled_time, $this->crons[ $cron_name ]['method'] );
return true;
}
/**
* Deactivates any crons associated with the VideoPress module..
*/
public function deactivate_all_crons() {
foreach ( $this->crons as $cron_name => $cron ) {
$this->deactivate_cron( $cron_name );
}
}
/**
* Is the given cron job currently active?
*
* If so, return when it will next run,
*
* @param string $cron_name
*
* @return int|bool Timestamp of the next run time OR false.
*/
public function check_cron( $cron_name ) {
if ( ! $this->is_cron_valid( $cron_name ) ) {
return false;
}
return wp_next_scheduled( $this->crons[ $cron_name ]['method'] );
}
/**
* Check that the given cron job name is valid.
*
* @param string $cron_name
*
* @return bool
*/
public function is_cron_valid( $cron_name ) {
if ( ! isset( $this->crons[ $cron_name ]['method'] ) || ! isset( $this->crons[ $cron_name ]['interval'] ) ) {
return false;
}
return true;
}
/**
* Get a list of all of the crons that are available.
*
* @return array
*/
public function get_crons() {
return $this->crons;
}
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* VideoPress video object retrieved from VideoPress servers and parsed.
*
* @since 1.3
*/
class VideoPress_Video {
public $version = 3;
/**
* Manifest version returned by remote service.
*
* @var string
* @since 1.3
*/
const manifest_version = '1.5';
/**
* Expiration of the video expressed in Unix time
*
* @var int
* @since 1.3
*/
public $expires;
/**
* VideoPress unique identifier
*
* @var string
* @since 1.3
*/
public $guid;
/**
* WordPress.com blog identifier
*
* @var int
* @since 1.5
*/
public $blog_id;
/**
* Remote blog attachment identifier
*
* @var int
* @since 1.5
*/
public $post_id;
/**
* Maximum desired width.
*
* @var int
* @since 1.3
*/
public $maxwidth;
/**
* Video width calculated based on original video dimensions and the requested maxwidth
*
* @var int
* @since 1.3
*/
public $calculated_width;
/**
* Video height calculated based on original video dimensions and the requested maxwidth
*
* @var int
* @since 1.3
*/
public $calculated_height;
/**
* Video title
*
* @var string
* @since 1.3
*/
public $title;
/**
* Video description
*
* @var string
* @since 4.4
*/
public $description;
/**
* Directionality of title text. ltr or rtl
*
* @var string
* @since 1.3
*/
public $text_direction;
/**
* Text and audio language as ISO 639-2 language code
*
* @var string
* @since 1.3
*/
public $language;
/**
* Video duration in whole seconds
*
* @var int
* @since 1.3
*/
public $duration;
/**
* Recommended minimum age of the viewer.
*
* @var int
* @since 1.3
*/
public $age_rating;
/**
* Video author has restricted video embedding or sharing
*
* @var bool
* @since 1.3
*/
public $restricted_embed;
/**
* Poster frame image URI for the given video guid and calculated dimensions.
*
* @var string
* @since 1.3
*/
public $poster_frame_uri;
/**
* Video files associated with the given guid for the calculated dimensions.
*
* @var stdClass
* @since 1.3
*/
public $videos;
/**
* Video player information
*
* @var stdClass
* @since 1.3
*/
public $players;
/**
* Video player skinning preferences including background color and watermark
*
* @var array
* @since 1.5
*/
public $skin;
/**
* Closed captions if available for the given video. Associative array of ISO 639-2 language code and a WebVTT URI
*
* @var array
* @since 1.5
*/
public $captions;
/**
* Setup the object.
* Request video information from VideoPress servers and process the response.
*
* @since 1.3
* @var string $guid VideoPress unique identifier
* @var int $maxwidth maximum requested video width. final width and height are calculated on VideoPress servers based on the aspect ratio of the original video upload.
*/
public function __construct( $guid, $maxwidth = 640 ) {
$this->guid = $guid;
$maxwidth = absint( $maxwidth );
if ( $maxwidth > 0 ) {
$this->maxwidth = $maxwidth;
}
$data = $this->get_data();
if ( is_wp_error( $data ) || empty( $data ) ) {
/** This filter is documented in modules/videopress/class.videopress-player.php */
if ( ! apply_filters( 'jetpack_videopress_use_legacy_player', false ) ) {
// Unlike the Flash player, the new player does it's own error checking, age gate, etc.
$data = (object) array(
'guid' => $guid,
'width' => $maxwidth,
'height' => $maxwidth / 16 * 9,
);
} else {
$this->error = $data;
return;
}
}
if ( isset( $data->blog_id ) ) {
$this->blog_id = absint( $data->blog_id );
}
if ( isset( $data->post_id ) ) {
$this->post_id = absint( $data->post_id );
}
if ( isset( $data->title ) && $data->title !== '' ) {
$this->title = trim( str_replace( '&nbsp;', ' ', $data->title ) );
}
if ( isset( $data->description ) && $data->description !== '' ) {
$this->description = trim( $data->description );
}
if ( isset( $data->text_direction ) && $data->text_direction === 'rtl' ) {
$this->text_direction = 'rtl';
} else {
$this->text_direction = 'ltr';
}
if ( isset( $data->language ) ) {
$this->language = $data->language;
}
if ( isset( $data->duration ) && $data->duration > 0 ) {
$this->duration = absint( $data->duration );
}
if ( isset( $data->width ) && $data->width > 0 ) {
$this->calculated_width = absint( $data->width );
}
if ( isset( $data->height ) && $data->height > 0 ) {
$this->calculated_height = absint( $data->height );
}
if ( isset( $data->age_rating ) ) {
$this->age_rating = absint( $this->age_rating );
}
if ( isset( $data->restricted_embed ) && $data->restricted_embed === true ) {
$this->restricted_embed = true;
} else {
$this->restricted_embed = false;
}
if ( isset( $data->posterframe ) && $data->posterframe !== '' ) {
$this->poster_frame_uri = esc_url_raw( $data->posterframe, array( 'http', 'https' ) );
}
if ( isset( $data->mp4 ) || isset( $data->ogv ) ) {
$this->videos = new stdClass();
if ( isset( $data->mp4 ) ) {
$this->videos->mp4 = $data->mp4;
}
if ( isset( $data->ogv ) ) {
$this->videos->ogv = $data->ogv;
}
}
if ( isset( $data->swf ) ) {
if ( ! isset( $this->players ) ) {
$this->players = new stdClass();
}
$this->players->swf = $data->swf;
}
if ( isset( $data->skin ) ) {
$this->skin = $data->skin;
}
if ( isset( $data->captions ) ) {
$this->captions = (array) $data->captions;
}
}
/**
* Convert an Expires HTTP header value into Unix time for use in WP Cache
*
* @since 1.3
* @var string $expires_header
* @return int|bool Unix time or false
*/
public static function calculate_expiration( $expires_header ) {
if ( empty( $expires_header ) || ! is_string( $expires_header ) ) {
return false;
}
if (
class_exists( 'DateTimeZone' )
&& method_exists( 'DateTime', 'createFromFormat' )
) {
$expires_date = DateTime::createFromFormat( 'D, d M Y H:i:s T', $expires_header, new DateTimeZone( 'UTC' ) );
if ( $expires_date instanceof DateTime ) {
return date_format( $expires_date, 'U' );
}
} else {
$expires_array = strptime( $expires_header, '%a, %d %b %Y %H:%M:%S %Z' );
if ( is_array( $expires_array ) && isset( $expires_array['tm_hour'] ) && isset( $expires_array['tm_min'] ) && isset( $expires_array['tm_sec'] ) && isset( $expires_array['tm_mon'] ) && isset( $expires_array['tm_mday'] ) && isset( $expires_array['tm_year'] ) ) {
return gmmktime( $expires_array['tm_hour'], $expires_array['tm_min'], $expires_array['tm_sec'], 1 + $expires_array['tm_mon'], $expires_array['tm_mday'], 1900 + $expires_array['tm_year'] );
}
}
return false;
}
/**
* Extract the site's host domain for statistics and comparison against an allowed site list in the case of restricted embeds.
*
* @since 1.2
* @param string $url absolute URL
* @return bool|string host component of the URL, or false if none found
*/
public static function hostname( $url ) {
return parse_url( esc_url_raw( $url ), PHP_URL_HOST );
}
/**
* Request data from WordPress.com for the given guid, maxwidth, and calculated blog hostname.
*
* @since 1.3
* @return stdClass|WP_Error parsed JSON response or WP_Error if request unsuccessful
*/
private function get_data() {
global $wp_version;
$domain = self::hostname( home_url() );
$request_params = array(
'guid' => $this->guid,
'domain' => $domain,
);
if ( isset( $this->maxwidth ) && $this->maxwidth > 0 ) {
$request_params['maxwidth'] = $this->maxwidth;
}
$url = 'http://videopress.com/data/wordpress.json';
if ( is_ssl() ) {
$url = 'https://v.wordpress.com/data/wordpress.json';
}
$response = wp_remote_get(
add_query_arg( $request_params, $url ),
array(
'redirection' => 1,
'user-agent' => 'VideoPress plugin ' . $this->version . '; WordPress ' . $wp_version . ' (' . home_url( '/' ) . ')',
)
);
unset( $request_params );
unset( $url );
$response_body = wp_remote_retrieve_body( $response );
$response_code = absint( wp_remote_retrieve_response_code( $response ) );
if ( is_wp_error( $response ) ) {
return $response;
} elseif ( $response_code === 400 ) {
return new WP_Error( 'bad_config', __( 'The VideoPress plugin could not communicate with the VideoPress servers. This error is most likely caused by a misconfigured plugin. Please reinstall or upgrade.', 'jetpack' ) );
} elseif ( $response_code === 403 ) {
return new WP_Error( 'http_forbidden', '<p>' . sprintf( __( '<strong>%s</strong> is not an allowed embed site.', 'jetpack' ), esc_html( $domain ) ) . '</p><p>' . __( 'Publisher limits playback of video embeds.', 'jetpack' ) . '</p>' );
} elseif ( $response_code === 404 ) {
return new WP_Error( 'http_not_found', '<p>' . sprintf( __( 'No data found for VideoPress identifier: <strong>%s</strong>.', 'jetpack' ), $this->guid ) . '</p>' );
} elseif ( $response_code !== 200 || empty( $response_body ) ) {
return;
} else {
$expires_header = wp_remote_retrieve_header( $response, 'Expires' );
if ( ! empty( $expires_header ) ) {
$expires = self::calculate_expiration( $expires_header );
if ( ! empty( $expires ) ) {
$this->expires = $expires;
}
}
return json_decode( $response_body );
}
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* VideoPress playback module markup generator.
*
* @since 1.3
*/
class VideoPress_XMLRPC {
/**
* @var VideoPress_XMLRPC
**/
private static $instance = null;
/**
* Private VideoPress_XMLRPC constructor.
*
* Use the VideoPress_XMLRPC::init() method to get an instance.
*/
private function __construct() {
add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
}
/**
* Initialize the VideoPress_XMLRPC and get back a singleton instance.
*
* @return VideoPress_XMLRPC
*/
public static function init() {
if ( is_null( self::$instance ) ) {
self::$instance = new VideoPress_XMLRPC();
}
return self::$instance;
}
/**
* Adds additional methods the WordPress xmlrpc API for handling VideoPress specific features
*
* @param array $methods
*
* @return array
*/
public function xmlrpc_methods( $methods ) {
$methods['jetpack.createMediaItem'] = array( $this, 'create_media_item' );
$methods['jetpack.updateVideoPressMediaItem'] = array( $this, 'update_videopress_media_item' );
$methods['jetpack.updateVideoPressPosterImage'] = array( $this, 'update_poster_image' );
return $methods;
}
/**
* This is used by the WPCOM VideoPress uploader in order to create a media item with
* specific meta data about an uploaded file. After this, the transcoding session will
* update the meta information via the update_videopress_media_item() method.
*
* Note: This method technically handles the creation of multiple media objects, though
* in practice this is never done.
*
* @param array $media
* @return array
*/
public function create_media_item( $media ) {
foreach ( $media as & $media_item ) {
$title = sanitize_title( basename( $media_item['url'] ) );
$guid = isset( $media['guid'] ) ? $media['guid'] : null;
$media_id = videopress_create_new_media_item( $title, $guid );
wp_update_attachment_metadata(
$media_id,
array(
'original' => array(
'url' => $media_item['url'],
),
)
);
$media_item['post'] = get_post( $media_id );
}
return array( 'media' => $media );
}
/**
* @param array $request
*
* @return bool
*/
public function update_videopress_media_item( $request ) {
$id = $request['post_id'];
$status = $request['status'];
$format = $request['format'];
$info = $request['info'];
if ( ! $attachment = get_post( $id ) ) {
return false;
}
$attachment->guid = $info['original'];
wp_update_post( $attachment );
// Update the vp guid and set it to a direct meta property.
update_post_meta( $id, 'videopress_guid', $info['guid'] );
$meta = wp_get_attachment_metadata( $id );
$meta['width'] = $info['width'];
$meta['height'] = $info['height'];
$meta['original']['url'] = $info['original'];
$meta['videopress'] = $info;
$meta['videopress']['url'] = 'https://videopress.com/v/' . $info['guid'];
// Update file statuses
$valid_formats = array( 'hd', 'ogg', 'mp4', 'dvd' );
if ( in_array( $format, $valid_formats ) ) {
$meta['file_statuses'][ $format ] = $status;
}
if ( ! get_post_meta( $id, '_thumbnail_id', true ) ) {
// Update the poster in the VideoPress info.
$thumbnail_id = videopress_download_poster_image( $info['poster'], $id );
if ( is_int( $thumbnail_id ) ) {
update_post_meta( $id, '_thumbnail_id', $thumbnail_id );
}
}
wp_update_attachment_metadata( $id, $meta );
videopress_update_meta_data( $id );
// update the meta to tell us that we're processing or complete
update_post_meta( $id, 'videopress_status', videopress_is_finished_processing( $id ) ? 'complete' : 'processing' );
return true;
}
/**
* @param array $request
* @return bool
*/
public function update_poster_image( $request ) {
$post_id = $request['post_id'];
$poster = $request['poster'];
if ( ! $attachment = get_post( $post_id ) ) {
return false;
}
// We add ssl => 1 to make sure that the videos.files.wordpress.com domain is parsed as photon.
$poster = apply_filters( 'jetpack_photon_url', $poster, array( 'ssl' => 1 ), 'https' );
$meta = wp_get_attachment_metadata( $post_id );
$meta['videopress']['poster'] = $poster;
wp_update_attachment_metadata( $post_id, $meta );
// Update the poster in the VideoPress info.
$thumbnail_id = videopress_download_poster_image( $poster, $post_id );
if ( ! is_int( $thumbnail_id ) ) {
return false;
}
update_post_meta( $post_id, '_thumbnail_id', $thumbnail_id );
return true;
}
}

View File

@@ -0,0 +1,60 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
/* VideoPress Settings Modal style overrides */
.mce-videopress-field-guid,
.mce-videopress-field-freedom,
.mce-videopress-field-flashonly {
display: none;
}
.mce-videopress-checkbox .mce-checkbox {
right: 120px !important;
width: 100% !important; /* assigning a full width so the label area is clickable */
}
.mce-videopress-checkbox .mce-label {
right: 150px !important;
}
.mce-videopress-checkbox .mce-label-unit {
position: absolute;
right: 210px;
top: 5px;
}
.mce-videopress-checkbox i.mce-i-checkbox {
background-color: #fff;
color: #1e8cbe;
}
.mce-videopress-checkbox .mce-i-checkbox:before {
display: inline-block;
vertical-align: middle;
width: 16px;
font: 400 21px/1 dashicons;
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: -3px -3px 0 0;
content: "\f147";
}
.mce-videopress-checkbox .mce-i-checkbox.mce-checked:before {
content: "\f147";
}
div[class*=mce-videopress-field] input[type=number] {
width: 70px !important;
right: 120px !important;
}
.mce-videopress-field-w .mce-label,
.mce-videopress-field-at .mce-label {
width: 115px !important;
text-align: left;
}
.mce-videopress-field-unit {
position: absolute;
right: 210px;
top: 5px;
}

View File

@@ -0,0 +1 @@
.mce-videopress-field-flashonly,.mce-videopress-field-freedom,.mce-videopress-field-guid{display:none}.mce-videopress-checkbox .mce-checkbox{right:120px!important;width:100%!important}.mce-videopress-checkbox .mce-label{right:150px!important}.mce-videopress-checkbox .mce-label-unit{position:absolute;right:210px;top:5px}.mce-videopress-checkbox i.mce-i-checkbox{background-color:#fff;color:#1e8cbe}.mce-videopress-checkbox .mce-i-checkbox:before{display:inline-block;vertical-align:middle;width:16px;font:400 21px/1 dashicons;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:-3px -3px 0 0;content:"\f147"}.mce-videopress-checkbox .mce-i-checkbox.mce-checked:before{content:"\f147"}div[class*=mce-videopress-field] input[type=number]{width:70px!important;right:120px!important}.mce-videopress-field-at .mce-label,.mce-videopress-field-w .mce-label{width:115px!important;text-align:left}.mce-videopress-field-unit{position:absolute;right:210px;top:5px}

View File

@@ -0,0 +1,59 @@
/* VideoPress Settings Modal style overrides */
.mce-videopress-field-guid,
.mce-videopress-field-freedom,
.mce-videopress-field-flashonly {
display: none;
}
.mce-videopress-checkbox .mce-checkbox {
left: 120px !important;
width: 100% !important; /* assigning a full width so the label area is clickable */
}
.mce-videopress-checkbox .mce-label {
left: 150px !important;
}
.mce-videopress-checkbox .mce-label-unit {
position: absolute;
left: 210px;
top: 5px;
}
.mce-videopress-checkbox i.mce-i-checkbox {
background-color: #fff;
color: #1e8cbe;
}
.mce-videopress-checkbox .mce-i-checkbox:before {
display: inline-block;
vertical-align: middle;
width: 16px;
font: 400 21px/1 dashicons;
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: -3px 0 0 -3px;
content: "\f147";
}
.mce-videopress-checkbox .mce-i-checkbox.mce-checked:before {
content: "\f147";
}
div[class*=mce-videopress-field] input[type=number] {
width: 70px !important;
left: 120px !important;
}
.mce-videopress-field-w .mce-label,
.mce-videopress-field-at .mce-label {
width: 115px !important;
text-align: right;
}
.mce-videopress-field-unit {
position: absolute;
left: 210px;
top: 5px;
}

View File

@@ -0,0 +1,2 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
.mce-videopress-field-flashonly,.mce-videopress-field-freedom,.mce-videopress-field-guid{display:none}.mce-videopress-checkbox .mce-checkbox{left:120px!important;width:100%!important}.mce-videopress-checkbox .mce-label{left:150px!important}.mce-videopress-checkbox .mce-label-unit{position:absolute;left:210px;top:5px}.mce-videopress-checkbox i.mce-i-checkbox{background-color:#fff;color:#1e8cbe}.mce-videopress-checkbox .mce-i-checkbox:before{display:inline-block;vertical-align:middle;width:16px;font:400 21px/1 dashicons;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:-3px 0 0 -3px;content:"\f147"}.mce-videopress-checkbox .mce-i-checkbox.mce-checked:before{content:"\f147"}div[class*=mce-videopress-field] input[type=number]{width:70px!important;left:120px!important}.mce-videopress-field-at .mce-label,.mce-videopress-field-w .mce-label{width:115px!important;text-align:right}.mce-videopress-field-unit{position:absolute;left:210px;top:5px}

View File

@@ -0,0 +1,22 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
/**
* VideoPress styles for Editor
*/
.videopress-editor-wrapper {
position: relative;
max-width: 100%;
padding: 56.25% 0 0;
height: 0;
overflow: hidden;
}
.tmpl-videopress_iframe_next iframe {
position: absolute;
top: 0;
right: 0;
max-width: 100%;
max-height: 100%;
}
body.rtl .tmpl-videopress_iframe_next iframe {
right: auto;
left: 0;
}

View File

@@ -0,0 +1 @@
.videopress-editor-wrapper{position:relative;max-width:100%;padding:56.25% 0 0;height:0;overflow:hidden}.tmpl-videopress_iframe_next iframe{position:absolute;top:0;right:0;max-width:100%;max-height:100%}body.rtl .tmpl-videopress_iframe_next iframe{right:auto;left:0}

View File

@@ -0,0 +1,21 @@
/**
* VideoPress styles for Editor
*/
.videopress-editor-wrapper {
position: relative;
max-width: 100%;
padding: 56.25% 0 0;
height: 0;
overflow: hidden;
}
.tmpl-videopress_iframe_next iframe {
position: absolute;
top: 0;
left: 0;
max-width: 100%;
max-height: 100%;
}
body.rtl .tmpl-videopress_iframe_next iframe {
left: auto;
right: 0;
}

View File

@@ -0,0 +1,2 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
.videopress-editor-wrapper{position:relative;max-width:100%;padding:56.25% 0 0;height:0;overflow:hidden}.tmpl-videopress_iframe_next iframe{position:absolute;top:0;left:0;max-width:100%;max-height:100%}body.rtl .tmpl-videopress_iframe_next iframe{left:auto;right:0}

View File

@@ -0,0 +1,238 @@
<?php
use Automattic\Jetpack\Assets;
/**
* WordPress Shortcode Editor View JS Code
*/
function videopress_handle_editor_view_js() {
global $content_width;
$current_screen = get_current_screen();
if ( ! isset( $current_screen->id ) || $current_screen->base !== 'post' ) {
return;
}
add_action( 'admin_print_footer_scripts', 'videopress_editor_view_js_templates' );
wp_enqueue_style( 'videopress-editor-ui', plugins_url( 'css/editor.css', __FILE__ ) );
wp_enqueue_script(
'videopress-editor-view',
Assets::get_file_url_for_environment(
'_inc/build/videopress/js/editor-view.min.js',
'modules/videopress/js/editor-view.js'
),
array( 'wp-util', 'jquery' ),
false,
true
);
wp_localize_script(
'videopress-editor-view',
'vpEditorView',
array(
'home_url_host' => parse_url( home_url(), PHP_URL_HOST ),
'min_content_width' => VIDEOPRESS_MIN_WIDTH,
'content_width' => $content_width,
'modal_labels' => array(
'title' => esc_html__( 'VideoPress Shortcode', 'jetpack' ),
'guid' => esc_html__( 'Video ID', 'jetpack' ),
'w' => esc_html__( 'Video Width', 'jetpack' ),
'w_unit' => esc_html__( 'pixels', 'jetpack' ),
/* Translators: example of usage of this is "Start Video After 10 seconds" */
'at' => esc_html__( 'Start Video After', 'jetpack' ),
'at_unit' => esc_html__( 'seconds', 'jetpack' ),
'hd' => esc_html__( 'High definition on by default', 'jetpack' ),
'permalink' => esc_html__( 'Link the video title to its URL on VideoPress.com', 'jetpack' ),
'autoplay' => esc_html__( 'Autoplay video on page load', 'jetpack' ),
'loop' => esc_html__( 'Loop video playback', 'jetpack' ),
'freedom' => esc_html__( 'Use only Open Source codecs (may degrade performance)', 'jetpack' ),
'flashonly' => esc_html__( 'Use legacy Flash Player (not recommended)', 'jetpack' ),
),
)
);
add_editor_style( plugins_url( 'css/videopress-editor-style.css', __FILE__ ) );
}
add_action( 'admin_notices', 'videopress_handle_editor_view_js' );
/**
* WordPress Editor Views
*/
function videopress_editor_view_js_templates() {
/**
* This template uses the following parameters, and displays the video as an iframe:
* - data.guid // The guid of the video.
* - data.width // The width of the iframe.
* - data.height // The height of the iframe.
* - data.urlargs // Arguments serialized into a get string.
*
* In addition, the calling script will need to ensure that the following
* JS file is added to the header of the editor iframe:
* - https://s0.wp.com/wp-content/plugins/video/assets/js/next/videopress-iframe.js
*/
?>
<script type="text/html" id="tmpl-videopress_iframe_vnext">
<div class="tmpl-videopress_iframe_next" style="max-height:{{ data.height }}px;">
<div class="videopress-editor-wrapper" style="padding-top:{{ data.ratio }}%;">
<iframe style="display: block;" width="{{ data.width }}" height="{{ data.height }}" src="https://videopress.com/embed/{{ data.guid }}?{{ data.urlargs }}" frameborder='0' allowfullscreen></iframe>
</div>
</div>
</script>
<?php
}
/*************************************************\
| This is the chunk that handles overriding core |
| media stuff so VideoPress can display natively. |
\*/
/**
* Media Grid:
* Filter out any videopress video posters that we've downloaded,
* so that they don't seem to display twice.
*/
add_filter( 'ajax_query_attachments_args', 'videopress_ajax_query_attachments_args' );
function videopress_ajax_query_attachments_args( $args ) {
$meta_query = array(
array(
'key' => 'videopress_poster_image',
'compare' => 'NOT EXISTS',
),
);
// If there was already a meta query, let's AND it via
// nesting it with our new one. No need to specify the
// relation, as it defaults to AND.
if ( ! empty( $args['meta_query'] ) ) {
$meta_query[] = $args['meta_query'];
}
$args['meta_query'] = $meta_query;
return $args;
}
/**
* Media List:
* Do the same as ^^ but for the list view.
*/
add_action( 'pre_get_posts', 'videopress_media_list_table_query' );
function videopress_media_list_table_query( $query ) {
if (
! function_exists( 'get_current_screen' )
|| is_null( get_current_screen() )
) {
return;
}
if ( is_admin() && $query->is_main_query() && ( 'upload' === get_current_screen()->id ) ) {
$meta_query = array(
array(
'key' => 'videopress_poster_image',
'compare' => 'NOT EXISTS',
),
);
if ( $old_meta_query = $query->get( 'meta_query' ) ) {
$meta_query[] = $old_meta_query;
}
$query->set( 'meta_query', $meta_query );
}
}
/**
* Make sure that any Video that has a VideoPress GUID passes that data back.
*/
add_filter( 'wp_prepare_attachment_for_js', 'videopress_prepare_attachment_for_js' );
function videopress_prepare_attachment_for_js( $post ) {
if ( 'video' === $post['type'] ) {
$guid = get_post_meta( $post['id'], 'videopress_guid' );
if ( $guid ) {
$post['videopress_guid'] = $guid;
}
}
return $post;
}
/**
* Wherever the Media Modal is deployed, also deploy our overrides.
*/
add_action( 'wp_enqueue_media', 'add_videopress_media_overrides' );
function add_videopress_media_overrides() {
add_action( 'admin_print_footer_scripts', 'videopress_override_media_templates', 11 );
}
/**
* Our video overrides!
*
* We have a template for the iframe to get injected.
*/
function videopress_override_media_templates() {
?>
<script type="text/html" id="tmpl-videopress_iframe_vnext">
<iframe style="display: block; max-width: 100%;" width="{{ data.width }}" height="{{ data.height }}" src="https://videopress.com/embed/{{ data.guid }}?{{ data.urlargs }}" frameborder='0' allowfullscreen></iframe>
</script>
<script>
(function( media ){
// This handles the media library modal attachment details display.
if ( 'undefined' !== typeof media.view.Attachment.Details.TwoColumn ) {
var TwoColumn = media.view.Attachment.Details.TwoColumn,
old_render = TwoColumn.prototype.render,
vp_template = wp.template('videopress_iframe_vnext');
TwoColumn.prototype.render = function() {
// Have the original renderer run first.
old_render.apply( this, arguments );
// Now our stuff!
if ( 'video' === this.model.get('type') ) {
if ( this.model.get('videopress_guid') ) {
this.$('.attachment-media-view .thumbnail-video').html( vp_template( {
guid : this.model.get('videopress_guid'),
width : this.model.get('width'),
height : this.model.get('height')
}));
}
}
};
} else { /* console.log( 'media.view.Attachment.Details.TwoColumn undefined' ); */ }
// This handles the recreating of the core video shortcode when editing the mce embed.
if ( 'undefined' !== typeof media.video ) {
media.video.defaults.videopress_guid = '';
// For some reason, even though we're not currently changing anything, the following proxy
// function is necessary to include the above default `videopress_guid` param. ¯\_(ツ)_/¯
var old_video_shortcode = media.video.shortcode;
media.video.shortcode = function( model ) {
// model.videopress_guid = 'FOOBAR';
return old_video_shortcode( model );
};
} else { /* console.log( 'media.video undefined' ); */ }
})( wp.media );
</script>
<?php
}
/**
* Properly inject VideoPress data into Core shortcodes, and
* generate videopress shortcodes for purely remote videos.
*/
add_filter( 'media_send_to_editor', 'videopress_media_send_to_editor', 10, 3 );
function videopress_media_send_to_editor( $html, $id, $attachment ) {
$videopress_guid = get_post_meta( $id, 'videopress_guid', true );
if ( $videopress_guid && videopress_is_valid_guid( $videopress_guid ) ) {
if ( '[video ' === substr( $html, 0, 7 ) ) {
$html = sprintf( '[videopress %1$s]', esc_attr( $videopress_guid ) );
} elseif ( '<a href=' === substr( $html, 0, 8 ) ) {
// We got here because `wp_attachment_is()` returned false for
// video, because there isn't a local copy of the file.
$html = sprintf( '[videopress %1$s]', esc_attr( $videopress_guid ) );
}
} elseif ( videopress_is_attachment_without_guid( $id ) ) {
$html = sprintf( '[videopress postid=%d]', $id );
}
return $html;
}

View File

@@ -0,0 +1,303 @@
/* global tinyMCE, vpEditorView */
( function( $, wp, vpEditorView ) {
wp.mce = wp.mce || {};
if ( 'undefined' === typeof wp.mce.views ) {
return;
}
wp.mce.videopress_wp_view_renderer = {
shortcode_string: 'videopress',
shortcode_data: {},
defaults: {
w: '',
at: '',
permalink: true,
hd: false,
loop: false,
freedom: false,
autoplay: false,
flashonly: false,
},
coerce: wp.media.coerce,
template: wp.template( 'videopress_iframe_vnext' ),
getContent: function() {
var urlargs = 'for=' + encodeURIComponent( vpEditorView.home_url_host ),
named = this.shortcode.attrs.named,
options,
key,
width;
for ( key in named ) {
switch ( key ) {
case 'at':
if ( parseInt( named[ key ], 10 ) ) {
urlargs += '&' + key + '=' + parseInt( named[ key ], 10 );
} // Else omit, as it's the default.
break;
case 'permalink':
if ( 'false' === named[ key ] ) {
urlargs += '&' + key + '=0';
} // Else omit, as it's the default.
break;
case 'hd':
case 'loop':
case 'autoplay':
if ( 'true' === named[ key ] ) {
urlargs += '&' + key + '=1';
} // Else omit, as it's the default.
break;
default:
// Unknown parameters? Ditch it!
break;
}
}
options = {
width: vpEditorView.content_width,
height: vpEditorView.content_width * 0.5625,
guid: this.shortcode.attrs.numeric[ 0 ],
urlargs: urlargs,
};
if ( typeof named.w !== 'undefined' ) {
width = parseInt( named.w, 10 );
if ( width >= vpEditorView.min_content_width && width < vpEditorView.content_width ) {
options.width = width;
options.height = parseInt( width * 0.5625, 10 );
}
}
options.ratio = 100 * ( options.height / options.width );
return this.template( options );
},
edit: function( data ) {
var shortcode_data = wp.shortcode.next( this.shortcode_string, data ),
named = shortcode_data.shortcode.attrs.named,
editor = tinyMCE.activeEditor,
renderer = this,
oldRenderFormItem = tinyMCE.ui.FormItem.prototype.renderHtml;
/**
* Override TextBox renderHtml to support html5 attrs.
* @link https://github.com/tinymce/tinymce/pull/2784
*
* @returns {string}
*/
tinyMCE.ui.TextBox.prototype.renderHtml = function() {
var self = this,
settings = self.settings,
element = document.createElement( settings.multiline ? 'textarea' : 'input' ),
extraAttrs = [
'rows',
'spellcheck',
'maxLength',
'size',
'readonly',
'min',
'max',
'step',
'list',
'pattern',
'placeholder',
'required',
'multiple',
],
i,
key;
for ( i = 0; i < extraAttrs.length; i++ ) {
key = extraAttrs[ i ];
if ( typeof settings[ key ] !== 'undefined' ) {
element.setAttribute( key, settings[ key ] );
}
}
if ( settings.multiline ) {
element.innerText = self.state.get( 'value' );
} else {
element.setAttribute( 'type', settings.subtype ? settings.subtype : 'text' );
element.setAttribute( 'value', self.state.get( 'value' ) );
}
element.id = self._id;
element.className = self.classes;
element.setAttribute( 'hidefocus', 1 );
if ( self.disabled() ) {
element.disabled = true;
}
return element.outerHTML;
};
tinyMCE.ui.FormItem.prototype.renderHtml = function() {
_.each(
vpEditorView.modal_labels,
function( value, key ) {
if ( value === this.settings.items.text ) {
this.classes.add( 'videopress-field-' + key );
}
},
this
);
if (
_.contains(
[
vpEditorView.modal_labels.hd,
vpEditorView.modal_labels.permalink,
vpEditorView.modal_labels.autoplay,
vpEditorView.modal_labels.loop,
vpEditorView.modal_labels.freedom,
vpEditorView.modal_labels.flashonly,
],
this.settings.items.text
)
) {
this.classes.add( 'videopress-checkbox' );
}
return oldRenderFormItem.call( this );
};
/**
* Populate the defaults.
*/
_.each(
this.defaults,
function( value, key ) {
named[ key ] = this.coerce( named, key );
},
this
);
/**
* Declare the fields that will show in the popup when editing the shortcode.
*/
editor.windowManager.open( {
title: vpEditorView.modal_labels.title,
id: 'videopress-shortcode-settings-modal',
width: 520,
height: 240,
body: [
{
type: 'textbox',
disabled: true,
name: 'guid',
label: vpEditorView.modal_labels.guid,
value: shortcode_data.shortcode.attrs.numeric[ 0 ],
},
{
type: 'textbox',
subtype: 'number',
min: vpEditorView.min_content_width, // The `min` may supported be in the future. https://github.com/tinymce/tinymce/pull/2784
name: 'w',
label: vpEditorView.modal_labels.w,
value: named.w,
},
{
type: 'textbox',
subtype: 'number',
min: 0, // The `min` may supported be in the future. https://github.com/tinymce/tinymce/pull/2784
name: 'at',
label: vpEditorView.modal_labels.at,
value: named.at,
},
{
type: 'checkbox',
name: 'hd',
label: vpEditorView.modal_labels.hd,
checked: named.hd,
},
{
type: 'checkbox',
name: 'permalink',
label: vpEditorView.modal_labels.permalink,
checked: named.permalink,
},
{
type: 'checkbox',
name: 'autoplay',
label: vpEditorView.modal_labels.autoplay,
checked: named.autoplay,
},
{
type: 'checkbox',
name: 'loop',
label: vpEditorView.modal_labels.loop,
checked: named.loop,
},
{
type: 'checkbox',
name: 'freedom',
label: vpEditorView.modal_labels.freedom,
checked: named.freedom,
},
{
type: 'checkbox',
name: 'flashonly',
label: vpEditorView.modal_labels.flashonly,
checked: named.flashonly,
},
],
onsubmit: function( e ) {
var args = {
tag: renderer.shortcode_string,
type: 'single',
attrs: {
named: _.pick( e.data, _.keys( renderer.defaults ) ),
numeric: [ e.data.guid ],
},
};
if ( '0' === args.attrs.named.at ) {
args.attrs.named.at = '';
}
_.each(
renderer.defaults,
function( value, key ) {
args.attrs.named[ key ] = this.coerce( args.attrs.named, key );
if ( value === args.attrs.named[ key ] ) {
delete args.attrs.named[ key ];
}
},
renderer
);
editor.insertContent( wp.shortcode.string( args ) );
},
onopen: function( e ) {
var prefix = 'mce-videopress-field-';
_.each( [ 'w', 'at' ], function( value ) {
e.target.$el
.find( '.' + prefix + value + ' .mce-container-body' )
.append(
'<span class="' +
prefix +
'unit ' +
prefix +
'unit-' +
value +
'">' +
vpEditorView.modal_labels[ value + '_unit' ]
);
} );
$( 'body' ).addClass( 'modal-open' );
},
onclose: function() {
$( 'body' ).removeClass( 'modal-open' );
},
} );
// Set it back to its original renderer.
tinyMCE.ui.FormItem.prototype.renderHtml = oldRenderFormItem;
},
};
wp.mce.views.register( 'videopress', wp.mce.videopress_wp_view_renderer );
// Extend the videopress one to also handle `wpvideo` instances.
wp.mce.wpvideo_wp_view_renderer = _.extend( {}, wp.mce.videopress_wp_view_renderer, {
shortcode_string: 'wpvideo',
} );
wp.mce.views.register( 'wpvideo', wp.mce.wpvideo_wp_view_renderer );
} )( jQuery, wp, vpEditorView );

View File

@@ -0,0 +1,52 @@
window.wp = window.wp || {};
( function( wp ) {
if ( wp.mediaWidgets ) {
// Over-ride core media_video#mapMediaToModelProps to set the url based upon videopress_guid if it exists.
wp.mediaWidgets.controlConstructors.media_video.prototype.mapMediaToModelProps = ( function(
originalMapMediaToModelProps
) {
return function( mediaFrameProps ) {
var newProps, originalProps, videoPressGuid;
originalProps = originalMapMediaToModelProps.call( this, mediaFrameProps );
newProps = _.extend( {}, originalProps );
// API response on new media will have the guid at videopress.guid.
if ( mediaFrameProps.videopress && mediaFrameProps.videopress.guid ) {
videoPressGuid = mediaFrameProps.videopress.guid;
}
// Selecting an existing VideoPress file will have the guid at .videopress_guid[ 0 ].
if (
! videoPressGuid &&
mediaFrameProps.videopress_guid &&
mediaFrameProps.videopress_guid.length
) {
videoPressGuid = mediaFrameProps.videopress_guid[ 0 ];
}
if ( videoPressGuid ) {
newProps = _.extend( {}, originalProps, {
url: 'https://videopress.com/v/' + videoPressGuid,
attachment_id: 0,
} );
}
return newProps;
};
} )( wp.mediaWidgets.controlConstructors.media_video.prototype.mapMediaToModelProps );
// Over-ride core media_video#isHostedVideo() to add support for videopress oembed urls.
wp.mediaWidgets.controlConstructors.media_video.prototype.isHostedVideo = ( function(
originalIsHostedVideo
) {
return function( url ) {
var parsedUrl = document.createElement( 'a' );
parsedUrl.href = url;
if ( 'videopress.com' === parsedUrl.hostname ) {
return true;
}
return originalIsHostedVideo.call( this, url );
};
} )( wp.mediaWidgets.controlConstructors.media_video.prototype.isHostedVideo );
}
} )( window.wp );

View File

@@ -0,0 +1,483 @@
/* global pluploadL10n, plupload, _wpPluploadSettings, JSON */
window.wp = window.wp || {};
( function( exports, $ ) {
var Uploader, vp;
if ( typeof _wpPluploadSettings === 'undefined' ) {
return;
}
/**
* A WordPress uploader.
*
* The Plupload library provides cross-browser uploader UI integration.
* This object bridges the Plupload API to integrate uploads into the
* WordPress back end and the WordPress media experience.
*
* @param {object} options The options passed to the new plupload instance.
* @param {object} options.container The id of uploader container.
* @param {object} options.browser The id of button to trigger the file select.
* @param {object} options.dropzone The id of file drop target.
* @param {object} options.plupload An object of parameters to pass to the plupload instance.
* @param {object} options.params An object of parameters to pass to $_POST when uploading the file.
* Extends this.plupload.multipart_params under the hood.
*/
Uploader = function( options ) {
var self = this,
isIE =
navigator.userAgent.indexOf( 'Trident/' ) !== -1 ||
navigator.userAgent.indexOf( 'MSIE ' ) !== -1,
elements = {
container: 'container',
browser: 'browse_button',
dropzone: 'drop_element',
},
key,
error;
this.supports = {
upload: Uploader.browser.supported,
};
this.supported = this.supports.upload;
if ( ! this.supported ) {
return;
}
// Arguments to send to pluplad.Uploader().
// Use deep extend to ensure that multipart_params and other objects are cloned.
this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
this.container = document.body; // Set default container.
// Extend the instance with options.
//
// Use deep extend to allow options.plupload to override individual
// default plupload keys.
$.extend( true, this, options );
// Proxy all methods so this always refers to the current instance.
for ( key in this ) {
if ( $.isFunction( this[ key ] ) ) {
this[ key ] = $.proxy( this[ key ], this );
}
}
// Ensure all elements are jQuery elements and have id attributes,
// then set the proper plupload arguments to the ids.
for ( key in elements ) {
if ( ! this[ key ] ) {
continue;
}
this[ key ] = $( this[ key ] ).first();
if ( ! this[ key ].length ) {
delete this[ key ];
continue;
}
if ( ! this[ key ].prop( 'id' ) ) {
this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
}
this.plupload[ elements[ key ] ] = this[ key ].prop( 'id' );
}
// If the uploader has neither a browse button nor a dropzone, bail.
if (
! ( this.browser && this.browser.length ) &&
! ( this.dropzone && this.dropzone.length )
) {
return;
}
// Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
if (
! isIE &&
'flash' === plupload.predictRuntime( this.plupload ) &&
( ! this.plupload.required_features ||
! this.plupload.required_features.hasOwnProperty( 'send_binary_string' ) )
) {
this.plupload.required_features = this.plupload.required_features || {};
this.plupload.required_features.send_binary_string = true;
}
// Initialize the plupload instance.
this.uploader = new plupload.Uploader( this.plupload );
delete this.plupload;
// Set default params and remove this.params alias.
this.param( this.params || {} );
delete this.params;
// Make sure that the VideoPress object is available
if ( typeof exports.VideoPress !== 'undefined' ) {
vp = exports.VideoPress;
} else {
window.console &&
window.console.error( 'The VideoPress object was not loaded. Errors may occur.' );
}
/**
* Custom error callback.
*
* Add a new error to the errors collection, so other modules can track
* and display errors. @see wp.Uploader.errors.
*
* @param {string} message
* @param {object} data
* @param {plupload.File} file File that was uploaded.
*/
error = function( message, data, file ) {
if ( file.attachment ) {
file.attachment.destroy();
}
Uploader.errors.unshift( {
message: message || pluploadL10n.default_error,
data: data,
file: file,
} );
self.error( message, data, file );
};
/**
* After the Uploader has been initialized, initialize some behaviors for the dropzone.
*
* @param {plupload.Uploader} uploader Uploader instance.
*/
this.uploader.bind( 'init', function( uploader ) {
var timer,
active,
dragdrop,
dropzone = self.dropzone;
dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
// Generate drag/drop helper classes.
if ( ! dropzone ) {
return;
}
dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
if ( ! dragdrop ) {
return dropzone.unbind( '.wp-uploader' );
}
// 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
dropzone.bind( 'dragover.wp-uploader', function() {
if ( timer ) {
clearTimeout( timer );
}
if ( active ) {
return;
}
dropzone.trigger( 'dropzone:enter' ).addClass( 'drag-over' );
active = true;
} );
dropzone.bind( 'dragleave.wp-uploader, drop.wp-uploader', function() {
// Using an instant timer prevents the drag-over class from
// being quickly removed and re-added when elements inside the
// dropzone are repositioned.
//
// @see https://core.trac.wordpress.org/ticket/21705
timer = setTimeout( function() {
active = false;
dropzone.trigger( 'dropzone:leave' ).removeClass( 'drag-over' );
}, 0 );
} );
self.ready = true;
$( self ).trigger( 'uploader:ready' );
} );
this.uploader.bind( 'postinit', function( up ) {
up.refresh();
self.init();
} );
this.uploader.init();
if ( this.browser ) {
this.browser.on( 'mouseenter', this.refresh );
} else {
this.uploader.disableBrowse( true );
// If HTML5 mode, hide the auto-created file container.
$( '#' + this.uploader.id + '_html5_container' ).hide();
}
/**
* After files were filtered and added to the queue, create a model for each.
*
* @event FilesAdded
* @param {plupload.Uploader} uploader Uploader instance.
* @param {Array} files Array of file objects that were added to queue by the user.
*/
this.uploader.bind( 'FilesAdded', function( up, files ) {
_.each( files, function( file ) {
var attributes, image;
// Ignore failed uploads.
if ( plupload.FAILED === file.status ) {
return;
}
// Generate attributes for a new `Attachment` model.
attributes = _.extend(
{
file: file,
uploading: true,
date: new Date(),
filename: file.name,
menuOrder: 0,
uploadedTo: wp.media.model.settings.post.id,
},
_.pick( file, 'loaded', 'size', 'percent' )
);
// Handle early mime type scanning for images.
image = /(?:jpe?g|png|gif)$/i.exec( file.name );
// For images set the model's type and subtype attributes.
if ( image ) {
attributes.type = 'image';
// `jpeg`, `png` and `gif` are valid subtypes.
// `jpg` is not, so map it to `jpeg`.
attributes.subtype = 'jpg' === image[ 0 ] ? 'jpeg' : image[ 0 ];
}
// Create a model for the attachment, and add it to the Upload queue collection
// so listeners to the upload queue can track and display upload progress.
file.attachment = wp.media.model.Attachment.create( attributes );
Uploader.queue.add( file.attachment );
self.added( file.attachment );
} );
up.refresh();
up.start();
} );
this.uploader.bind( 'UploadProgress', function( up, file ) {
file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
self.progress( file.attachment );
} );
/**
* After a file is successfully uploaded, update its model.
*
* @param {plupload.Uploader} uploader Uploader instance.
* @param {plupload.File} file File that was uploaded.
* @param {Object} response Object with response properties.
* @return {mixed}
*/
this.uploader.bind( 'FileUploaded', function( up, file, response ) {
var complete;
try {
response = JSON.parse( response.response );
} catch ( e ) {
return error( pluploadL10n.default_error, e, file );
}
if ( typeof response.media !== 'undefined' ) {
response = vp.handleRestApiResponse( response, file );
} else {
response = vp.handleStandardResponse( response, file );
}
_.each( [ 'file', 'loaded', 'size', 'percent' ], function( key ) {
file.attachment.unset( key );
} );
file.attachment.set( _.extend( response.data, { uploading: false } ) );
wp.media.model.Attachment.get( response.data.id, file.attachment );
complete = Uploader.queue.all( function( attachment ) {
return ! attachment.get( 'uploading' );
} );
if ( complete ) {
vp && vp.resetToOriginalOptions( up );
Uploader.queue.reset();
}
self.success( file.attachment );
} );
/**
* When plupload surfaces an error, send it to the error handler.
*
* @param {plupload.Uploader} uploader Uploader instance.
* @param {Object} error Contains code, message and sometimes file and other details.
*/
this.uploader.bind( 'Error', function( up, pluploadError ) {
var message = pluploadL10n.default_error,
key;
// Check for plupload errors.
for ( key in Uploader.errorMap ) {
if ( pluploadError.code === plupload[ key ] ) {
message = Uploader.errorMap[ key ];
if ( _.isFunction( message ) ) {
message = message( pluploadError.file, pluploadError );
}
break;
}
}
error( message, pluploadError, pluploadError.file );
vp && vp.resetToOriginalOptions( up );
up.refresh();
} );
/**
* Add in a way for the uploader to reset itself when uploads are complete.
*/
this.uploader.bind( 'UploadComplete', function( up ) {
vp && vp.resetToOriginalOptions( up );
} );
/**
* Before we upload, check to see if this file is a videopress upload, if so, set new options and save the old ones.
*/
this.uploader.bind( 'BeforeUpload', function( up, file ) {
if ( typeof file.videopress !== 'undefined' ) {
vp.originalOptions.url = up.getOption( 'url' );
vp.originalOptions.multipart_params = up.getOption( 'multipart_params' );
vp.originalOptions.file_data_name = up.getOption( 'file_data_name' );
up.setOption( 'file_data_name', 'media[]' );
up.setOption( 'url', file.videopress.upload_action_url );
up.setOption( 'headers', {
Authorization:
'X_UPLOAD_TOKEN token="' +
file.videopress.upload_token +
'" blog_id="' +
file.videopress.upload_blog_id +
'"',
} );
}
} );
};
// Adds the 'defaults' and 'browser' properties.
$.extend( Uploader, _wpPluploadSettings );
Uploader.uuid = 0;
// Map Plupload error codes to user friendly error messages.
Uploader.errorMap = {
FAILED: pluploadL10n.upload_failed,
FILE_EXTENSION_ERROR: pluploadL10n.invalid_filetype,
IMAGE_FORMAT_ERROR: pluploadL10n.not_an_image,
IMAGE_MEMORY_ERROR: pluploadL10n.image_memory_exceeded,
IMAGE_DIMENSIONS_ERROR: pluploadL10n.image_dimensions_exceeded,
GENERIC_ERROR: pluploadL10n.upload_failed,
IO_ERROR: pluploadL10n.io_error,
HTTP_ERROR: pluploadL10n.http_error,
SECURITY_ERROR: pluploadL10n.security_error,
FILE_SIZE_ERROR: function( file ) {
return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name );
},
};
$.extend( Uploader.prototype, {
/**
* Acts as a shortcut to extending the uploader's multipart_params object.
*
* param( key )
* Returns the value of the key.
*
* param( key, value )
* Sets the value of a key.
*
* param( map )
* Sets values for a map of data.
*/
param: function( key, value ) {
if ( arguments.length === 1 && typeof key === 'string' ) {
return this.uploader.settings.multipart_params[ key ];
}
if ( arguments.length > 1 ) {
this.uploader.settings.multipart_params[ key ] = value;
} else {
$.extend( this.uploader.settings.multipart_params, key );
}
},
/**
* Make a few internal event callbacks available on the wp.Uploader object
* to change the Uploader internals if absolutely necessary.
*/
init: function() {},
error: function() {},
success: function() {},
added: function() {},
progress: function() {},
complete: function() {},
refresh: function() {
var node, attached, container, id;
if ( this.browser ) {
node = this.browser[ 0 ];
// Check if the browser node is in the DOM.
while ( node ) {
if ( node === document.body ) {
attached = true;
break;
}
node = node.parentNode;
}
// If the browser node is not attached to the DOM, use a
// temporary container to house it, as the browser button
// shims require the button to exist in the DOM at all times.
if ( ! attached ) {
id = 'wp-uploader-browser-' + this.uploader.id;
container = $( '#' + id );
if ( ! container.length ) {
container = $( '<div class="wp-uploader-browser" />' )
.css( {
position: 'fixed',
top: '-1000px',
left: '-1000px',
height: 0,
width: 0,
} )
.attr( 'id', 'wp-uploader-browser-' + this.uploader.id )
.appendTo( 'body' );
}
container.append( this.browser );
}
}
this.uploader.refresh();
},
} );
// Create a collection of attachments in the upload queue,
// so that other modules can track and display upload progress.
Uploader.queue = new wp.media.model.Attachments( [], { query: false } );
// Create a collection to collect errors incurred while attempting upload.
Uploader.errors = new Backbone.Collection();
exports.Uploader = Uploader;
} )( wp, jQuery );

View File

@@ -0,0 +1,157 @@
/* globals plupload, pluploadL10n, error */
window.wp = window.wp || {};
( function( wp ) {
var VideoPress = {
originalOptions: {},
/**
* This is the standard uploader response handler.
*/
handleStandardResponse: function( response, file ) {
if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) {
return error( pluploadL10n.default_error, null, file );
} else if ( ! response.success ) {
return error( response.data && response.data.message, response.data, file );
}
return response;
},
/**
* Handle response from the WPCOM Rest API.
*/
handleRestApiResponse: function( response, file ) {
if ( response.media.length !== 1 ) {
return error( pluploadL10n.default_error, null, file );
}
var media = response.media[ 0 ],
mimeParts = media.mime_type.split( '/' ),
data = {
alt: '',
author: media.author_ID || 0,
authorName: '',
caption: '',
compat: { item: '', meta: '' },
date: media.date || '',
dateFormatted: media.date || '',
description: media.description || '',
editLink: '',
filename: media.file || '',
filesizeHumanReadable: '',
filesizeInBytes: '',
height: media.height,
icon: media.icon || '',
id: media.ID || '',
link: media.URL || '',
menuOrder: 0,
meta: false,
mime: media.mime_type || '',
modified: 0,
name: '',
nonces: { update: '', delete: '', edit: '' },
orientation: '',
sizes: undefined,
status: '',
subtype: mimeParts[ 1 ] || '',
title: media.title || '',
type: mimeParts[ 0 ] || '',
uploadedTo: 1,
uploadedToLink: '',
uploadedToTitle: '',
url: media.URL || '',
width: media.width,
success: '',
videopress: {
guid: media.videopress_guid || null,
processing_done: media.videopress_processing_done || false,
},
};
response.data = data;
return response;
},
/**
* Make sure that all of the original variables have been reset, so the uploader
* doesn't try to go to VideoPress again next time.
*
* @param up
*/
resetToOriginalOptions: function( up ) {
if ( typeof VideoPress.originalOptions.url !== 'undefined' ) {
up.setOption( 'url', VideoPress.originalOptions.url );
delete VideoPress.originalOptions.url;
}
if ( typeof VideoPress.originalOptions.multipart_params !== 'undefined' ) {
up.setOption( 'multipart_params', VideoPress.originalOptions.multipart_params );
delete VideoPress.originalOptions.multipart_params;
}
if ( typeof VideoPress.originalOptions.file_data_name !== 'undefined' ) {
up.setOption( 'file_data_name', VideoPress.originalOptions.file_data_name );
delete VideoPress.originalOptions.file_data_name;
}
},
};
if ( typeof wp.Uploader !== 'undefined' ) {
var media = wp.media;
/**
* A plupload code specifically for videopress failures.
*
* @type {string}
*/
plupload.VIDEOPRESS_TOKEN_FAILURE = 'VP_TOKEN_FAILURE';
/**
* Adds a filter that checks all files to see if they are videopress files and if they are
* it will download extra metadata for them.
*/
plupload.addFileFilter( 'videopress_check_uploads', function( maxSize, file, cb ) {
var mimeParts = file.type.split( '/' );
var self = this;
if ( mimeParts[ 0 ] === 'video' ) {
media
.ajax( 'videopress-get-upload-token', { async: false, data: { filename: file.name } } )
.done( function( response ) {
file.videopress = response;
cb( true );
} )
.fail( function( response ) {
self.trigger( 'Error', {
code: plupload.VIDEOPRESS_TOKEN_FAILURE,
message: plupload.translate(
'Could not get the VideoPress token needed for uploading'
),
file: file,
response: response,
} );
cb( false );
} );
} else {
// Handles the normal max_file_size functionality.
var undef;
// Invalid file size
if ( file.size !== undef && maxSize && file.size > maxSize ) {
this.trigger( 'Error', {
code: plupload.FILE_SIZE_ERROR,
message: plupload.translate( 'File size error.' ),
file: file,
} );
cb( false );
} else {
cb( true );
}
}
} );
}
wp.VideoPress = VideoPress;
} )( window.wp );

View File

@@ -0,0 +1,248 @@
<?php
/**
* VideoPress Shortcode Handler
*
* This file may or may not be included from the Jetpack VideoPress module.
*/
class VideoPress_Shortcode {
/** @var VideoPress_Shortcode */
protected static $instance;
protected function __construct() {
// 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 );
add_shortcode( 'videopress', array( $this, 'shortcode_callback' ) );
add_shortcode( 'wpvideo', array( $this, 'shortcode_callback' ) );
add_filter( 'wp_video_shortcode_override', array( $this, 'video_shortcode_override' ), 10, 4 );
add_filter( 'oembed_fetch_url', array( $this, 'add_oembed_for_parameter' ) );
$this->add_video_embed_hander();
}
/**
* @return VideoPress_Shortcode
*/
public static function initialize() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 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
*/
public function shortcode_callback( $attr ) {
global $content_width;
/**
* We only accept GUIDs as a first unnamed argument.
*/
$guid = isset( $attr[0] ) ? $attr[0] : null;
if ( isset( $attr['postid'] ) ) {
$guid = get_post_meta( $attr['postid'], 'videopress_guid', true );
}
/**
* 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' => true, // 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['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();
}
}
/**
* Override the standard video short tag to also process videopress files as well.
*
* This will, parse the src given, and if it is a videopress file, it will parse as the
* VideoPress shortcode instead.
*
* @param string $html Empty variable to be replaced with shortcode markup.
* @param array $attr Attributes of the video shortcode.
* @param string $content Video shortcode content.
* @param int $instance Unique numeric ID of this video shortcode instance.
*
* @return string
*/
public function video_shortcode_override( $html, $attr, $content, $instance ) {
$videopress_guid = null;
if ( isset( $attr['videopress_guid'] ) ) {
$videopress_guid = $attr['videopress_guid'];
} else {
// Handle the different possible url attributes
$url_keys = array( 'src', 'mp4' );
foreach ( $url_keys as $key ) {
if ( isset( $attr[ $key ] ) ) {
$url = $attr[ $key ];
// phpcs:ignore WordPress.WP.CapitalPDangit
if ( preg_match( '@videos.(videopress\.com|files\.wordpress\.com)/([a-z0-9]{8})/@i', $url, $matches ) ) {
$videopress_guid = $matches[2];
}
// Also test for videopress oembed url, which is used by the Video Media Widget.
if ( ! $videopress_guid && preg_match( '@https://videopress.com/v/([a-z0-9]{8})@i', $url, $matches ) ) {
$videopress_guid = $matches[1];
}
break;
}
}
}
if ( $videopress_guid ) {
$videopress_attr = array( $videopress_guid );
if ( isset( $attr['width'] ) ) {
$videopress_attr['w'] = (int) $attr['width'];
}
if ( isset( $attr['autoplay'] ) ) {
$videopress_attr['autoplay'] = $attr['autoplay'];
}
if ( isset( $attr['loop'] ) ) {
$videopress_attr['loop'] = $attr['loop'];
}
// Then display the VideoPress version of the stored GUID!
return $this->shortcode_callback( $videopress_attr );
}
return '';
}
/**
* Adds a `for` query parameter to the oembed provider request URL.
*
* @param String $oembed_provider
* @return String $ehnanced_oembed_provider
*/
public function 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 );
}
/**
* Register a VideoPress handler for direct links to .mov files (and potential other non-handled types later).
*/
public function add_video_embed_hander() {
// These are the video extensions that VideoPress can transcode and considers video as well (even if core does not).
$extensions = array( 'mov' );
$override_extensions = implode( '|', $extensions );
$regex = "#^https?://videos.(videopress.com|files.wordpress.com)/.+?.($override_extensions)$#i";
/** This filter is already documented in core/wp-includes/embed.php */
$filter = apply_filters( 'wp_video_embed_handler', 'wp_embed_handler_video' );
wp_embed_register_handler( 'video', $regex, $filter, 10 );
}
}
VideoPress_Shortcode::initialize();

View File

@@ -0,0 +1,694 @@
<?php
use Automattic\Jetpack\Connection\Client;
/**
* 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 ) && is_string( $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 );
$query_url = sprintf(
'https://public-api.wordpress.com/rest/v%1$s%2$s',
$version,
$endpoint
);
// Look for data in our transient. If nothing, let's make a new query.
$data_from_cache = get_transient( 'jetpack_videopress_' . $guid );
if ( false === $data_from_cache ) {
$response = wp_remote_get( esc_url_raw( $query_url ) );
$data = json_decode( wp_remote_retrieve_body( $response ) );
// Cache the response for an hour.
set_transient( 'jetpack_videopress_' . $guid, $data, HOUR_IN_SECONDS );
} else {
$data = $data_from_cache;
}
/**
* 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', 'jetpack' ) );
}
$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;
}
/**
* Helper that will look for VideoPress media items that are more than 30 minutes old,
* that have not had anything attached to them by a wpcom upload and deletes the ghost
* attachment.
*
* These happen primarily because of failed upload attempts.
*
* @return int The number of items that were cleaned up.
*/
function videopress_cleanup_media_library() {
// Disable this job for now.
return 0;
$query_args = array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'post_mime_type' => 'video/videopress',
'meta_query' => array(
array(
'key' => 'videopress_status',
'value' => 'new',
),
),
);
$query = new WP_Query( $query_args );
$cleaned = 0;
$now = current_time( 'timestamp' );
if ( $query->have_posts() ) {
foreach ( $query->posts as $post ) {
$post_time = strtotime( $post->post_date_gmt );
// If the post is older than 30 minutes, it is safe to delete it.
if ( $now - $post_time > MINUTE_IN_SECONDS * 30 ) {
// Force delete the attachment, because we don't want it appearing in the trash.
wp_delete_attachment( $post->ID, true );
$cleaned++;
}
}
}
return $cleaned;
}
/**
* Return an absolute URI for a given filename and guid on the CDN.
* No check is performed to ensure the guid exists or the file is present. Simple centralized string builder.
*
* @param string $guid VideoPress identifier
* @param string $filename name of file associated with the guid (video file name or thumbnail file name)
*
* @return string Absolute URL of VideoPress file for the given guid.
*/
function videopress_cdn_file_url( $guid, $filename ) {
return "https://videos.files.wordpress.com/{$guid}/{$filename}";
}
/**
* Get an array of the transcoding status for the given video post.
*
* @since 4.4
* @param int $post_id
* @return array|bool Returns an array of statuses if this is a VideoPress post, otherwise it returns false.
*/
function videopress_get_transcoding_status( $post_id ) {
$meta = wp_get_attachment_metadata( $post_id );
// If this has not been processed by videopress, we can skip the rest.
if ( ! $meta || ! isset( $meta['file_statuses'] ) ) {
return false;
}
$info = (object) $meta['file_statuses'];
$status = array(
'std_mp4' => isset( $info->mp4 ) ? $info->mp4 : null,
'std_ogg' => isset( $info->ogg ) ? $info->ogg : null,
'dvd_mp4' => isset( $info->dvd ) ? $info->dvd : null,
'hd_mp4' => isset( $info->hd ) ? $info->hd : null,
);
return $status;
}
/**
* Get the direct url to the video.
*
* @since 4.4
* @param string $guid
* @return string
*/
function videopress_build_url( $guid ) {
// No guid, no videopress url.
if ( ! $guid ) {
return '';
}
return 'https://videopress.com/v/' . $guid;
}
/**
* Create an empty videopress media item that will be filled out later by an xmlrpc
* callback from the VideoPress servers.
*
* @since 4.4
* @param string $title
* @return int|WP_Error
*/
function videopress_create_new_media_item( $title, $guid = null ) {
$post = array(
'post_type' => 'attachment',
'post_mime_type' => 'video/videopress',
'post_title' => $title,
'post_content' => '',
'guid' => videopress_build_url( $guid ),
);
$media_id = wp_insert_post( $post );
add_post_meta( $media_id, 'videopress_status', 'initiated' );
add_post_meta( $media_id, 'videopress_guid', $guid );
return $media_id;
}
/**
* @param array $current_status
* @param array $new_meta
* @return array
*/
function videopress_merge_file_status( $current_status, $new_meta ) {
$new_statuses = array();
if ( isset( $new_meta['videopress']['files_status']['hd'] ) ) {
$new_statuses['hd'] = $new_meta['videopress']['files_status']['hd'];
}
if ( isset( $new_meta['videopress']['files_status']['dvd'] ) ) {
$new_statuses['dvd'] = $new_meta['videopress']['files_status']['dvd'];
}
if ( isset( $new_meta['videopress']['files_status']['std']['mp4'] ) ) {
$new_statuses['mp4'] = $new_meta['videopress']['files_status']['std']['mp4'];
}
if ( isset( $new_meta['videopress']['files_status']['std']['ogg'] ) ) {
$new_statuses['ogg'] = $new_meta['videopress']['files_status']['std']['ogg'];
}
foreach ( $new_statuses as $format => $status ) {
if ( ! isset( $current_status[ $format ] ) ) {
$current_status[ $format ] = $status;
continue;
}
if ( $current_status[ $format ] !== 'DONE' ) {
$current_status[ $format ] = $status;
}
}
return $current_status;
}
/**
* Check to see if a video has completed processing.
*
* @since 4.4
* @param int $post_id
* @return bool
*/
function videopress_is_finished_processing( $post_id ) {
$post = get_post( $post_id );
if ( is_wp_error( $post ) ) {
return false;
}
$meta = wp_get_attachment_metadata( $post->ID );
if ( ! isset( $meta['file_statuses'] ) || ! is_array( $meta['file_statuses'] ) ) {
return false;
}
$check_statuses = array( 'hd', 'dvd', 'mp4', 'ogg' );
foreach ( $check_statuses as $status ) {
if ( ! isset( $meta['file_statuses'][ $status ] ) || $meta['file_statuses'][ $status ] != 'DONE' ) {
return false;
}
}
return true;
}
/**
* Update the meta information status for the given video post.
*
* @since 4.4
* @param int $post_id
* @return bool
*/
function videopress_update_meta_data( $post_id ) {
$meta = wp_get_attachment_metadata( $post_id );
// If this has not been processed by VideoPress, we can skip the rest.
if ( ! $meta || ! isset( $meta['videopress'] ) ) {
return false;
}
$info = (object) $meta['videopress'];
$args = array(
// 'sslverify' => false,
);
$result = wp_remote_get( videopress_make_video_get_path( $info->guid ), $args );
if ( is_wp_error( $result ) ) {
return false;
}
$response = json_decode( $result['body'], true );
// Update the attachment metadata.
$meta['videopress'] = $response;
wp_update_attachment_metadata( $post_id, $meta );
return true;
}
/**
* Check to see if this is a VideoPress post that hasn't had a guid set yet.
*
* @param int $post_id
* @return bool
*/
function videopress_is_attachment_without_guid( $post_id ) {
$post = get_post( $post_id );
if ( is_wp_error( $post ) ) {
return false;
}
if ( $post->post_mime_type !== 'video/videopress' ) {
return false;
}
$videopress_guid = get_post_meta( $post_id, 'videopress_guid', true );
if ( $videopress_guid ) {
return false;
}
return true;
}
/**
* Check to see if this is a VideoPress attachment.
*
* @param int $post_id
* @return bool
*/
function is_videopress_attachment( $post_id ) {
$post = get_post( $post_id );
if ( is_wp_error( $post ) ) {
return false;
}
if ( $post->post_mime_type !== 'video/videopress' ) {
return false;
}
return true;
}
/**
* Get the video update path
*
* @since 4.4
* @param string $guid
* @return string
*/
function videopress_make_video_get_path( $guid ) {
return sprintf(
'%s://%s/rest/v%s/videos/%s',
'https',
JETPACK__WPCOM_JSON_API_HOST,
Client::WPCOM_JSON_API_VERSION,
$guid
);
}
/**
* Get the upload api path.
*
* @since 4.4
* @param int $blog_id The id of the blog we're uploading to.
* @return string
*/
function videopress_make_media_upload_path( $blog_id ) {
return sprintf(
'https://public-api.wordpress.com/rest/v1.1/sites/%s/media/new',
$blog_id
);
}
/**
* This is a mock of the internal VideoPress method, which is meant to duplicate the functionality
* of the WPCOM API, so that the Jetpack REST API returns the same data with no modifications.
*
* @param int $blog_id Blog ID.
* @param int $post_id Post ID.
* @return bool|stdClass
*/
function video_get_info_by_blogpostid( $blog_id, $post_id ) {
$post = get_post( $post_id );
$video_info = new stdClass();
$video_info->post_id = $post_id;
$video_info->blog_id = $blog_id;
$video_info->guid = null;
$video_info->finish_date_gmt = '0000-00-00 00:00:00';
if ( is_wp_error( $post ) ) {
return $video_info;
}
if ( 'video/videopress' !== $post->post_mime_type ) {
return $video_info;
}
// Since this is a VideoPress post, lt's fill out the rest of the object.
$video_info->guid = get_post_meta( $post_id, 'videopress_guid', true );
if ( videopress_is_finished_processing( $post_id ) ) {
$video_info->finish_date_gmt = date( 'Y-m-d H:i:s' );
}
return $video_info;
}
/**
* Check that a VideoPress video format has finished processing.
*
* This uses the info object, because that is what the WPCOM endpoint
* uses, however we don't have a complete info object in the same way
* WPCOM does, so we pull the meta information out of the post
* options instead.
*
* Note: This mimics the WPCOM function of the same name and helps the media
* API endpoint add all needed VideoPress data.
*
* @param stdClass $info
* @param string $format
* @return bool
*/
function video_format_done( $info, $format ) {
// Avoids notice when a non-videopress item is found.
if ( ! is_object( $info ) ) {
return false;
}
$post_id = $info->post_id;
if ( get_post_mime_type( $post_id ) !== 'video/videopress' ) {
return false;
}
$post = get_post( $post_id );
if ( is_wp_error( $post ) ) {
return false;
}
$meta = wp_get_attachment_metadata( $post->ID );
switch ( $format ) {
case 'fmt_hd':
return isset( $meta['videopress']['files']['hd']['mp4'] );
break;
case 'fmt_dvd':
return isset( $meta['videopress']['files']['dvd']['mp4'] );
break;
case 'fmt_std':
return isset( $meta['videopress']['files']['std']['mp4'] );
break;
case 'fmt_ogg':
return isset( $meta['videopress']['files']['std']['ogg'] );
break;
}
return false;
}
/**
* Get the image URL for the given VideoPress GUID
*
* We look up by GUID, because that is what WPCOM does and this needs to be
* parameter compatible with that.
*
* Note: This mimics the WPCOM function of the same name and helps the media
* API endpoint add all needed VideoPress data.
*
* @param string $guid
* @param string $format
* @return string
*/
function video_image_url_by_guid( $guid, $format ) {
$post = video_get_post_by_guid( $guid );
if ( is_wp_error( $post ) ) {
return null;
}
$meta = wp_get_attachment_metadata( $post->ID );
// We add ssl => 1 to make sure that the videos.files.wordpress.com domain is parsed as photon.
$poster = apply_filters( 'jetpack_photon_url', $meta['videopress']['poster'], array( 'ssl' => 1 ), 'https' );
return $poster;
}
/**
* Using a GUID, find a post.
*
* @param string $guid
* @return WP_Post
*/
function video_get_post_by_guid( $guid ) {
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'video/videopress',
'post_status' => 'inherit',
'meta_query' => array(
array(
'key' => 'videopress_guid',
'value' => $guid,
'compare' => '=',
),
),
);
$query = new WP_Query( $args );
$post = $query->next_post();
return $post;
}
/**
* From the given VideoPress post_id, return back the appropriate attachment URL.
*
* When the MP4 hasn't been processed yet or this is not a VideoPress video, this will return null.
*
* @param int $post_id Post ID of the attachment.
* @return string|null
*/
function videopress_get_attachment_url( $post_id ) {
// We only handle VideoPress attachments.
if ( get_post_mime_type( $post_id ) !== 'video/videopress' ) {
return null;
}
$meta = wp_get_attachment_metadata( $post_id );
if ( ! isset( $meta['videopress']['files']['hd']['mp4'] ) ) {
// Use the original file as the url if it isn't transcoded yet.
if ( isset( $meta['original'] ) ) {
$return = $meta['original'];
} else {
// Otherwise, there isn't much we can do.
return null;
}
} else {
$return = $meta['videopress']['file_url_base']['https'] . $meta['videopress']['files']['hd']['mp4'];
}
// If the URL is a string, return it. Otherwise, we shouldn't to avoid errors downstream, so null.
return ( is_string( $return ) ) ? $return : null;
}

View File

@@ -0,0 +1,106 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
/**
* VideoPress admin media styles
*/
.videopress-modal-backdrop {
background: #000;
opacity: 0.7;
position: absolute;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 100;
}
.videopress-modal {
padding: 10px 20px;
background: white;
position: absolute;
top: 0;
width: 440px;
overflow: hidden;
right: 50%;
margin-right: -220px;
z-index: 101;
box-shadow: -2px 2px 5px 2px rgba( 0, 0, 0, 0.5 );
-webkit-border-bottom-left-radius: 2px;
-webkit-border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
.videopress-modal .submit {
text-align: left;
padding: 10px 0 5px;
}
.videopress-preview {
display: block;
float: left;
width: 65%;
margin-top: 18px;
background: black;
min-height: 97px;
text-decoration: none;
}
.vp-preview span.videopress-preview-unavailable {
width: 65%;
float: left;
text-align: right;
margin-left: 0;
}
.videopress-preview img {
float: right;
width: 100%;
}
.videopress-preview span {
display: block;
padding-top: 40px;
color: white !important;
text-align: center;
}
.vp-setting .help {
margin: 0 35% 4px 0;
}
.media-sidebar .vp-setting input[type="checkbox"] {
float: right;
margin-top: 10px;
}
.vp-setting label {
float: right;
margin: 8px 5px 0 8px;
max-width: 135px;
}
.vp-setting input[type='radio'] {
float: right;
margin-top: 9px;
width: auto;
}
.vp-preview span {
margin-top: 18px;
}
.uploader-videopress {
margin: 16px;
}
.uploader-videopress .videopress-errors div {
margin: 16px 0;
}
.compat-field-video-rating input[type="radio"],
.compat-field-display_embed input[type="checkbox"]{
margin-top: -1px !important;
margin-left: 5px !important;
margin-right: 5px !important;
vertical-align: middle;
}

View File

@@ -0,0 +1 @@
.videopress-modal-backdrop{background:#000;opacity:.7;position:absolute;top:0;width:100%;height:100%;overflow:hidden;z-index:100}.videopress-modal{padding:10px 20px;background:#fff;position:absolute;top:0;width:440px;overflow:hidden;right:50%;margin-right:-220px;z-index:101;box-shadow:-2px 2px 5px 2px rgba(0,0,0,.5);-webkit-border-bottom-left-radius:2px;-webkit-border-bottom-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px}.videopress-modal .submit{text-align:left;padding:10px 0 5px}.videopress-preview{display:block;float:left;width:65%;margin-top:18px;background:#000;min-height:97px;text-decoration:none}.vp-preview span.videopress-preview-unavailable{width:65%;float:left;text-align:right;margin-left:0}.videopress-preview img{float:right;width:100%}.videopress-preview span{display:block;padding-top:40px;color:#fff!important;text-align:center}.vp-setting .help{margin:0 35% 4px 0}.media-sidebar .vp-setting input[type=checkbox]{float:right;margin-top:10px}.vp-setting label{float:right;margin:8px 5px 0 8px;max-width:135px}.vp-setting input[type=radio]{float:right;margin-top:9px;width:auto}.vp-preview span{margin-top:18px}.uploader-videopress{margin:16px}.uploader-videopress .videopress-errors div{margin:16px 0}.compat-field-display_embed input[type=checkbox],.compat-field-video-rating input[type=radio]{margin-top:-1px!important;margin-left:5px!important;margin-right:5px!important;vertical-align:middle}

View File

@@ -0,0 +1,105 @@
/**
* VideoPress admin media styles
*/
.videopress-modal-backdrop {
background: #000;
opacity: 0.7;
position: absolute;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 100;
}
.videopress-modal {
padding: 10px 20px;
background: white;
position: absolute;
top: 0;
width: 440px;
overflow: hidden;
left: 50%;
margin-left: -220px;
z-index: 101;
box-shadow: 2px 2px 5px 2px rgba( 0, 0, 0, 0.5 );
-webkit-border-bottom-right-radius: 2px;
-webkit-border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
.videopress-modal .submit {
text-align: right;
padding: 10px 0 5px;
}
.videopress-preview {
display: block;
float: right;
width: 65%;
margin-top: 18px;
background: black;
min-height: 97px;
text-decoration: none;
}
.vp-preview span.videopress-preview-unavailable {
width: 65%;
float: right;
text-align: left;
margin-right: 0;
}
.videopress-preview img {
float: left;
width: 100%;
}
.videopress-preview span {
display: block;
padding-top: 40px;
color: white !important;
text-align: center;
}
.vp-setting .help {
margin: 0 0 4px 35%;
}
.media-sidebar .vp-setting input[type="checkbox"] {
float: left;
margin-top: 10px;
}
.vp-setting label {
float: left;
margin: 8px 8px 0 5px;
max-width: 135px;
}
.vp-setting input[type='radio'] {
float: left;
margin-top: 9px;
width: auto;
}
.vp-preview span {
margin-top: 18px;
}
.uploader-videopress {
margin: 16px;
}
.uploader-videopress .videopress-errors div {
margin: 16px 0;
}
.compat-field-video-rating input[type="radio"],
.compat-field-display_embed input[type="checkbox"]{
margin-top: -1px !important;
margin-right: 5px !important;
margin-left: 5px !important;
vertical-align: middle;
}

View File

@@ -0,0 +1,2 @@
/* Do not modify this file directly. It is concatenated from individual module CSS files. */
.videopress-modal-backdrop{background:#000;opacity:.7;position:absolute;top:0;width:100%;height:100%;overflow:hidden;z-index:100}.videopress-modal{padding:10px 20px;background:#fff;position:absolute;top:0;width:440px;overflow:hidden;left:50%;margin-left:-220px;z-index:101;box-shadow:2px 2px 5px 2px rgba(0,0,0,.5);-webkit-border-bottom-right-radius:2px;-webkit-border-bottom-left-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.videopress-modal .submit{text-align:right;padding:10px 0 5px}.videopress-preview{display:block;float:right;width:65%;margin-top:18px;background:#000;min-height:97px;text-decoration:none}.vp-preview span.videopress-preview-unavailable{width:65%;float:right;text-align:left;margin-right:0}.videopress-preview img{float:left;width:100%}.videopress-preview span{display:block;padding-top:40px;color:#fff!important;text-align:center}.vp-setting .help{margin:0 0 4px 35%}.media-sidebar .vp-setting input[type=checkbox]{float:left;margin-top:10px}.vp-setting label{float:left;margin:8px 8px 0 5px;max-width:135px}.vp-setting input[type=radio]{float:left;margin-top:9px;width:auto}.vp-preview span{margin-top:18px}.uploader-videopress{margin:16px}.uploader-videopress .videopress-errors div{margin:16px 0}.compat-field-display_embed input[type=checkbox],.compat-field-video-rating input[type=radio]{margin-top:-1px!important;margin-right:5px!important;margin-left:5px!important;vertical-align:middle}