<?php
/**
* This file implements the Video Plug plugin for b2evolution
*
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package plugins
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* Replaces Video Plug markup in HTML (not XML).
*
* @todo dh> Hook into AdminBeforeItemEditUpdate and validate provided video IDs
*
* @package plugins
*/
class videoplug_plugin extends Plugin
{
var $code = 'evo_videoplug';
var $name = 'Video Plug';
var $priority = 65;
var $group = 'rendering';
var $short_desc;
var $long_desc;
var $version = '7.2.3';
var $number_of_installs = 1;
/**
* Init
*/
function PluginInit( & $params )
{
$this->short_desc = T_('Video plug for a few popular video sites.');
$this->long_desc = T_('This plugin allows to quickly embed (plug) a video from a video hosting site such as YouTube, Vimeo, DailyMotion and Facebook. Use it through the toolbar or directly by entering a shortcode like [video:youtube:123xyz] or [video:vimeo:123xyz] into your post, where 123xyz is the ID of the video.');
}
/**
* Define the GLOBAL settings of the plugin here. These can then be edited in the backoffice in System > Plugins.
*
* @param array Associative array of parameters (since v1.9).
* 'for_editing': true, if the settings get queried for editing;
* false, if they get queried for instantiating {@link Plugin::$Settings}.
* @return array see {@link Plugin::GetDefaultSettings()}.
* The array to be returned should define the names of the settings as keys (max length is 30 chars)
* and assign an array with the following keys to them (only 'label' is required):
*/
function GetDefaultSettings( & $params )
{
return array(
'youtube_load' => array(
'label' => 'YouTube',
'type' => 'radio',
'options' => array(
array( 'standard', T_('Standard').' <code><iframe></code>' ),
array( 'lazyload', T_('Lazy-Load') ),
),
'field_lines' => true,
'defaultvalue' => 'standard',
),
);
}
/**
* Define here default custom settings that are to be made available
* in the backoffice for collections, private messages and newsletters.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::GetDefaultSettings()}.
*/
function get_custom_setting_definitions( & $params )
{
return array(
'width' => array(
'label' => T_('Video width (px or %)'),
'note' => T_('100% width if left empty or 0'),
'valid_pattern' => '/^(\d+(\.\d+)?%?)?$/',
'defaultvalue' => '100%',
),
'height' => array(
'label' => T_('Video height (px or %)'),
'defaultvalue' => '',
'allow_empty' => true,
'valid_pattern' => '/^(\d+(\.\d+)?%?)?$/',
'note' => T_('Leave empty for a 16/9 aspect ratio').' (16/9=56.25%)',
'defaultvalue' => '56.25%',
),
);
}
/**
* Define here default collection/blog settings that are to be made available in the backoffice.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::GetDefaultSettings()}.
*/
function get_coll_setting_definitions( & $params )
{
return array_merge( parent::get_coll_setting_definitions( $params ),
array(
'replace_url_post' => array(
'label' => T_('Replace full video URLs found in posts' ),
'type' => 'checkbox',
'defaultvalue' => 1,
),
'replace_url_comment' => array(
'label' => T_('Replace full video URLs found in comments' ),
'type' => 'checkbox',
'defaultvalue' => 1,
),
)
);
}
/**
* Event handler: Called at the beginning of the skin's HTML HEAD section.
*
* Use this to add any HTML HEAD lines (like CSS styles or links to resource files (CSS, JavaScript, ..)).
*
* @param array Associative array of parameters
*/
function SkinBeginHtmlHead( & $params )
{
$this->require_css( 'css/videoplug.min.css' );
// Initialize config for video plugin:
expose_var_to_js( 'evo_plugin_videoplug_config', array(
'youtube_lazyload_selector' => '.evo_youtube[data-embed]',
) );
}
/**
* Perform rendering
*
* @todo add more video sites, anyone...
*
* @see Plugin::RenderItemAsHtml()
*/
function RenderItemAsHtml( & $params )
{
$content = & $params['data'];
if( $setting_Blog = & $this->get_Blog_from_params( $params ) )
{ // We are rendering Item, Comment or Widget now, Get the settings depending on Collection:
$this->video_width = $this->get_coll_setting( 'width', $setting_Blog );
$this->video_height = $this->get_coll_setting( 'height', $setting_Blog );
}
elseif( ! empty( $params['Message'] ) )
{ // We are rendering Message now:
$this->video_width = $this->get_msg_setting( 'width' );
$this->video_height = $this->get_msg_setting( 'height' );
}
elseif( ! empty( $params['EmailCampaign'] ) )
{ // We are rendering EmailCampaign now:
$this->video_width = $this->get_email_setting( 'width' );
$this->video_height = $this->get_email_setting( 'height' );
}
else
{ // Unknown call, Don't render this case:
return;
}
if( ! empty( $setting_Blog ) && (
( $this->get_coll_setting( 'replace_url_comment', $setting_Blog ) && ! empty( $params['Comment'] ) && $params['Comment'] instanceof Comment ) ||
( $this->get_coll_setting( 'replace_url_post', $setting_Blog ) && ! empty( $params['Item'] ) && $params['Item'] instanceof Item )
) )
{ // Render full video URLs in post or comment content:
$content = replace_outside_code_and_short_tags( '#(<a[^>]+href=")?(https?://(.+\.)?(youtube.com|youtu.be|dailymotion.com|vimeo.com|facebook.com|wistia.com)([^"\s\n\r<]+))("[^>]*>(.+?)</a>)?#i',
array( $this, 'parse_video_url_callback' ), $content, 'replace_content', 'preg_callback' );
}
// Move short tag outside of paragraph:
$content = move_short_tags( $content, '/\[video:(youtube|dailymotion|vimeo|facebook|wistia):?[^\[\]]*\]/i' );
// Replace video tags with html code:
$content = replace_outside_code_tags( '#\[video:(youtube|dailymotion|vimeo|facebook|wistia|google|livevideo|ifilm):([^:\[\]\\\/]*|https?:\/\/.*\.facebook\.com\/[^:]*):?(\d+%?)?:?(\d+%?)?:?([^:\[\]\\\/]*)\]#',
array( $this, 'parse_video_tag_callback' ), $content, 'replace_content', 'preg_callback' );
return true;
}
/**
* Callback function to build HTML video code from video URL or link tag <a> with video URL
*
* @param array Matches:
* 0 - Full video URL or full link tag <a>
* 1 - Start part of <a> tag, or empty string when simple URL without <a> tag
* 2 - Full URL
* 3 - domain prefix like "www." or "www.subdomain." or empty
* 4 - Domain: youtube.com, youtu.be, dailymotion.com, vimeo.com, facebook.com, wistia.com
* 5 - Part of video URL after domain
* 6 - End part of <a> tag, or not defined when simple URL without <a> tag
* 7 - Text of <a> tag, or not defined when simple URL without <a> tag
* @return string HTML video code
*/
function parse_video_url_callback( $m )
{
if( isset( $m[7] ) && $m[7] != $m[2] )
{ // Skip if link text is different than URL(e.g. <a href="https://youtu.be/abc123">Click to see Video!</a>):
return $m[0];
}
// Try to extract video code from URL depending on video server:
switch( $m[4] )
{
case 'youtube.com':
case 'youtu.be':
if( preg_match( '#(^/(embed/)?|[\?&]v=)([^&=\?]+)(&|$)#', $m[5], $code ) )
{
if( $this->get_setting( 'youtube_load' ) == 'standard' )
{ // Use standard loading with iframe:
$video_block = '<iframe id="ytplayer" type="text/html" src="//www.youtube.com/embed/'.$code[3].'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
}
else
{ // Use Lazy-loading:
$video_block = '<div class="evo_youtube" data-embed="'.$code[3].'"><div class="evo_youtube__play_button"></div></div>';
}
}
break;
case 'dailymotion.com':
if( preg_match( '#^/video/(.+)$#', $m[5], $code ) )
{
$video_block = '<iframe src="//www.dailymotion.com/embed/video/'.$code[1].'" frameborder="0" allowfullscreen></iframe>';
}
break;
case 'vimeo.com':
if( preg_match( '#^/(.+)$#', $m[5], $code ) )
{
$video_block = '<iframe src="//player.vimeo.com/video/'.$code[1].'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
}
break;
case 'facebook.com':
if( preg_match( '#(/videos/|[\?&]v=)(\d+)(/|$)#', $m[5], $code ) )
{
$video_block = '<iframe src="https://www.facebook.com/plugins/video.php?href='.urlencode( $m[2] ).'" scrolling="no" frameborder="0" allowTransparency="true" allowFullScreen="true"></iframe>';
}
break;
case 'wistia.com':
if( preg_match( '#/medias/([a-z0-9]+)$#', $m[5], $code ) )
{
$video_block = '<script src="https://fast.wistia.com/embed/medias/'.$code[1].'.jsonp" async></script>'
.'<script src="https://fast.wistia.com/assets/external/E-v1.js" async></script>'
.'<div class="wistia_responsive_padding">'
.'<div class="wistia_responsive_wrapper" style="height:100%;left:0;position:absolute;top:0;width:100%;">'
.'<span class="wistia_embed wistia_async_'.$code[1].' popover=true popoverAnimateThumbnail=true videoFoam=true" style="display:inline-block;height:100%;position:relative;width:100%"> </span>'
.'</div>'
.'</div>';
}
break;
}
if( ! isset( $video_block ) )
{ // No found correct video code in the URL:
return $m[0];
}
$style = '';
if( ! empty( $this->video_width ) )
{ // Set width depending on what units are used:
$style .= 'width:'.( strpos( $this->video_width, '%' ) === false ? $this->video_width.'px' : $this->video_width ).';';
}
if( ! empty( $this->video_height ) )
{ // Set height depending on what units are used:
$style .= 'padding-bottom:'.( strpos( $this->video_height, '%' ) === false ? '0;height:'.$this->video_height.'px' : $this->video_height );
}
return '<div class="videoblock"'.( $style == '' ? '' : ' style="'.$style.'"' ).'>'.$video_block.'</div>';
}
/**
* Callback function to build HTML video code from video tag
*
* @param array Matches:
* 0 - Full video tag
* 1 - Video type: youtube, dailymotion, vimeo, facebook, wistia, google, livevideo, ifilm
* 2 - Video code/key
* 3 - Width
* 4 - Height
* 5 - Extra params
* @return string HTML video code
*/
function parse_video_tag_callback( $m )
{
switch( $m[1] )
{
case 'youtube':
$video_block = '<iframe id="ytplayer" type="text/html" src="//www.youtube.com/embed/'.$m[2].( ! empty( $m[5] )? '?'.$m[5] : '' ).'" allowfullscreen="allowfullscreen" frameborder="0"></iframe>';
break;
case 'dailymotion':
$video_block = '<iframe src="//www.dailymotion.com/embed/video/'.$m[2].'" frameborder="0" allowfullscreen></iframe>';
break;
case 'vimeo':
$video_block = '<iframe src="//player.vimeo.com/video/'.$m[2].'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
break;
case 'facebook':
$video_block = '<iframe src="https://www.facebook.com/plugins/video.php?href='.urlencode( $m[2] ).'" scrolling="no" frameborder="0" allowTransparency="true" allowFullScreen="true"></iframe>';
break;
case 'wistia':
$video_block = '<script src="https://fast.wistia.com/embed/medias/'.$m[2].'.jsonp" async></script>'
.'<script src="https://fast.wistia.com/assets/external/E-v1.js" async></script>'
.'<div class="wistia_responsive_padding">'
.'<div class="wistia_responsive_wrapper" style="height:100%;left:0;position:absolute;top:0;width:100%;">'
.'<span class="wistia_embed wistia_async_'.$m[2].' popover=true popoverAnimateThumbnail=true videoFoam=true" style="display:inline-block;height:100%;position:relative;width:100%"> </span>'
.'</div>'
.'</div>';
break;
default: // google, livevideo, ifilm:
// Unavailable services. Keep them for backwards compatibility:
$video_block = 'The '.$m[1].' video service is not available anymore.';
break;
}
$style = '';
// Get width from video tag or from settings:
$width = empty( $m[3] ) ? $this->video_width : $m[3];
if( ! empty( $width ) )
{ // Set width depending on what units are used:
$style .= 'width:'.( strpos( $width, '%' ) === false ? $width.'px' : $width ).';';
}
// Get height from video tag or from settings:
$height = empty( $m[4] ) ? $this->video_height : $m[4];
if( ! empty( $height ) )
{ // Set height depending on what units are used:
$style .= 'padding-bottom:'.( strpos( $height, '%' ) === false ? '0;height:'.$height.'px' : $height );
}
return '<div class="videoblock"'.( $style == '' ? '' : ' style="'.$style.'"' ).'>'.$video_block.'</div>';
}
/**
* Perform rendering for XML feeds
*
* @see Plugin::RenderItemAsXml()
*/
function RenderItemAsXml( & $params )
{
return $this->RenderItemAsHtml( $params );
}
/**
* Event handler: Called when displaying editor toolbars on post/item form.
*
* This is for post/item edit forms only. Comments, PMs and emails use different events.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function AdminDisplayToolbar( & $params )
{
if( !empty( $params['Item'] ) )
{ // Item is set, get Blog from post:
$edited_Item = & $params['Item'];
$Collection = $Blog = & $edited_Item->get_Blog();
}
if( empty( $Blog ) )
{ // Item is not set, try global Blog:
global $Collection, $Blog;
if( empty( $Blog ) )
{ // We can't get a Blog, this way "apply_rendering" plugin collection setting is not available:
return false;
}
}
$apply_rendering = $this->get_coll_setting( 'coll_apply_rendering', $Blog );
if( empty( $apply_rendering ) || $apply_rendering == 'never' )
{ // Plugin is not enabled for current case, so don't display a toolbar:
return false;
}
return $this->DisplayCodeToolbar( $params );
}
/**
* Event handler: Called when displaying editor toolbars for message.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayMessageToolbar( & $params )
{
$apply_rendering = $this->get_msg_setting( 'msg_apply_rendering' );
if( ! empty( $apply_rendering ) && $apply_rendering != 'never' )
{
return $this->DisplayCodeToolbar( $params );
}
return false;
}
/**
* Event handler: Called when displaying editor toolbars for email.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayEmailToolbar( & $params )
{
$apply_rendering = $this->get_email_setting( 'email_apply_rendering' );
if( ! empty( $apply_rendering ) && $apply_rendering != 'never' )
{
return $this->DisplayCodeToolbar( $params );
}
return false;
}
/**
* Event handler: Called when displaying editor toolbars on comment form.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayCommentToolbar( & $params )
{
$Comment = & $params['Comment'];
if( $Comment )
{ // Get a post of the comment:
if( $comment_Item = & $Comment->get_Item() )
{
$Collection = $Blog = & $comment_Item->get_Blog();
}
}
if( empty( $Blog ) )
{ // Item is not set, try global Blog
global $Collection, $Blog;
if( empty( $Blog ) )
{ // We can't get a Blog, this way "apply_rendering" plugin collection setting is not available
return false;
}
}
$apply_rendering = $this->get_coll_setting( 'coll_apply_comment_rendering', $Blog );
if( empty( $apply_rendering ) || $apply_rendering == 'never' )
{ // Plugin is not enabled for current case, so don't display a toolbar:
return false;
}
return $this->DisplayCodeToolbar( $params );
}
/**
* Display a code toolbar
*
* @param array Params
* @return boolean did we display a toolbar?
*/
function DisplayCodeToolbar( $params = array() )
{
$params = array_merge( array(
'js_prefix' => '', // Use different prefix if you use several toolbars on one page
), $params );
echo $this->get_template( 'toolbar_before', array( '$toolbar_class$' => $params['js_prefix'].$this->code.'_toolbar' ) );
echo $this->get_template( 'toolbar_title_before' ).T_('Video').': '.$this->get_template( 'toolbar_title_after' );
echo $this->get_template( 'toolbar_group_before' );
echo '<input type="button" id="video_youtube" title="'.T_('Insert Youtube video').'" class="'.$this->get_template( 'toolbar_button_class' ).'" data-func="videotag|youtube|'.$params['js_prefix'].'" value="YouTube" />';
echo '<input type="button" id="video_vimeo" title="'.T_('Insert vimeo video').'" class="'.$this->get_template( 'toolbar_button_class' ).'" data-func="videotag|vimeo|'.$params['js_prefix'].'" value="Vimeo" />';
echo '<input type="button" id="video_dailymotion" title="'.T_('Insert DailyMotion video').'" class="'.$this->get_template( 'toolbar_button_class' ).'" data-func="videotag|dailymotion|'.$params['js_prefix'].'" value="DailyMotion" />';
echo '<input type="button" id="video_facebook" title="'.T_('Insert Facebook video').'" class="'.$this->get_template( 'toolbar_button_class' ).'" data-func="videotag|facebook|'.$params['js_prefix'].'" value="Facebook" />';
echo '<input type="button" id="video_wistia" title="'.T_('Insert Wistia video').'" class="'.$this->get_template( 'toolbar_button_class' ).'" data-func="videotag|wistia|'.$params['js_prefix'].'" value="Wistia" />';
echo $this->get_template( 'toolbar_group_after' );
echo $this->get_template( 'toolbar_after' );
// Load js to work with textarea
require_js_defer( 'functions.js', 'blog', true );
?><script>
//<![CDATA[
function videotag( tag, prefix )
{
while( 1 )
{
var p = '<?php echo TS_('Copy/paste the URL or the ID of your video from %s:') ?>';
var video_ID = prompt( p.replace( /%s/, tag ), '' );
if( ! video_ID )
{
return;
}
// Validate Video ID or URL:
var regexp_ID = false;
var regexp_URL = false;
switch( tag )
{
case 'youtube':
// Allow HD video code with ?hd=1 at the end
regexp_ID = /^[a-z0-9_-]+$/i;
regexp_URL = /^(.+youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|.+youtu\.be\/)([a-z0-9_-]{11})((?:\?|&).*)?/i;
break;
case 'dailymotion':
regexp_ID = /^[a-z0-9]+$/i;
regexp_URL = /^(.+\/video\/)?([a-z0-9]+)(_[a-z0-9_-]+)?(\?.+)?$/i;
break;
case 'vimeo':
regexp_ID = /^\d+$/;
regexp_URL = /^(.+\/)?(\d+)(\?.+)?$/;
break;
case 'facebook':
regexp_ID = /^https:\/\/.+\.facebook\.com\/.+/i;
regexp_URL = /^((https:\/\/.+\.facebook\.com\/.+))$/i;
break;
case 'wistia':
regexp_ID = /^[a-z0-9]+$/i;
regexp_URL = /^(.+\/medias\/)?([a-z0-9]+)$/i;
break;
default:
// Don't allow unknown video:
break;
}
if( regexp_ID && regexp_URL )
{ // Check the entered data by regexp:
if( video_ID.match( regexp_ID ) )
{ // Valid video ID
break;
}
else if( video_ID.match( /^https?:\/\// ) )
{ // If this is URL, Check to correct format:
if( video_ID.match( regexp_URL ) )
{ // Valid video URL
// Extract ID from URL:
if( tag == 'youtube' )
{
var params = video_ID.replace( regexp_URL, '$3' ).trim();
params = params.replace( /^[&\?]/, '' );
params = params.replace( /&$/, '' );
params = params.replace( /rel=\d+&?/, '' ); // remove rel=
video_ID = video_ID.replace( regexp_URL, '$2' );
if( params.length )
{
video_ID = video_ID + ':::' + params;
}
}
else
{
video_ID = video_ID.replace( regexp_URL, '$2' );
}
break;
}
else
{ // Display error when URL doesn't match:
alert( '<?php echo TS_('The URL you provided could not be recognized.'); ?>' );
continue;
}
}
}
// Display error of wrong entered data:
alert( '<?php echo TS_('The URL or video ID is invalid.'); ?>' );
}
tag = '[video:'+tag+':'+video_ID+']';
textarea_wrap_selection( window[ ( prefix ? prefix : '' ) + 'b2evoCanvas' ], tag, '', 1 );
}
//]]>
</script><?php
return true;
}
}
?>