Commit 504b0920 authored by Gregor Anzelj's avatar Gregor Anzelj Committed by Robert Lyon
Browse files

Integrate embed.ly support into the "external media" block (Bug #1441395)



behatnotneeded
Change-Id: Ie966c72484c06ac33ce7cf93c7b76d453443aad5
Signed-off-by: default avatarGregor Anzelj <gregor.anzelj@gmail.com>
parent 18e4d9f2
<?php
interface EmbedBase {
/*
* Returns if this embed service is enabled or not.
*/
public function enabled();
/*
* Function that process the URL and generates HTML
* needed for embedding the URL.
* @param string $input url to be processed
* @param int $width width of the embedded content
* @param int $height height of the embedded content
* @return string html code to embed content within page
*/
public function process_url($input, $width=0, $height=0);
/*
* Function that checks if the URL is valid, meaning that
* embed service is able to generate embed code for this URL.
* @param string $input url to be validated
* @return boolean if url is valid or not
*/
public function validate_url($input);
/*
* Function that process entered embed/iframe code and
* prepares it for embedding into Mahara page.
* @param string $input embed/iframe code to be processed
* @return array values for sanitized embed code
*/
public function process_content($input);
/*
* Function that builds embed/iframe code to be
* embedded into Mahara page.
* @param array $input values for sanitized embed code
* @return string sanitized embed code for Mahara page
*/
public function embed_content($input);
/*
* Function that returns embed service base URL.
* @return string returns embed service base url
*/
public function get_base_url();
}
<?php
require_once(dirname(__FILE__) . '/../Embed_base.php');
class Embed_embedly implements EmbedBase {
private $httpstr;
private static $base_url;
private static $signup_url;
private static $default_width = 640;
private static $default_height = 480;
function __construct() {
$this->httpstr = is_https() ? 'https' : 'http';
self::$base_url = $this->httpstr . '://embed.ly/';
//self::$signup_url = $this->httpstr . '://app.embed.ly/';
}
private static $embed_sources = array(
array(
'match' => '#<a class="embedly-card".*href="(.*?)".*>(.*?)<\/a>.*#s',
'url' => '$1',
'title' => '$2',
'type' => 'link',
),
array(
'match' => '#<blockquote class="embedly-card" (.*?)>.*href="(.*?)".*>(.*?)<\/a>.*<p>(.*?)<\/p><\/blockquote>.*#s',
'data' => '$1',
'url' => '$2',
'title' => '$3',
'desc' => '$4',
'type' => 'card',
),
array(
'match' => '#<div class="embedly-responsive".*style="(.*?)".*src="(.*?)".*style="(.*?)".*<\/div>.*#s',
'style1' => '$1',
'src' => '$2',
'style2' => '$3',
'type' => 'div', // responsive iframe
),
array(
'match' => '#<iframe class="embedly-embed".*src="(.*?)".*<\/iframe>.*#s',
'src' => '$1',
'type' => 'iframe',
),
);
/*
* Returns if this embed service is enabled or not.
*/
public function enabled() {
return true;
}
/*
* Function that process the URL and generates HTML
* needed for embedding the URL.
*/
public function process_url($input, $width=0, $height=0) {
$width = $width ? (int)$width : self::$default_width;
$height = $height ? (int)$height : self::$default_height;
$output = '<a class="embedly-card" href="' . $input . '"></a>';
$result = array(
'videoid' => $output,
'width' => $width,
'height' => $height,
'html' => $output,
);
return $result;
}
/*
* Function that checks if the URL is valid, meaning that
* embed service is able to generate embed code for this URL.
*/
public function validate_url($input) {
if (filter_var($input, FILTER_VALIDATE_URL)) {
return true;
}
/*foreach (self::$embed_sources as $source) {
if (preg_match($source['match'], $input)) {
return true;
}
}*/
return false;
}
/*
* Function that process entered embed/iframe code and
* prepares it for embedding into Mahara page.
*/
public function process_content($input) {
foreach (self::$embed_sources as $source) {
if (preg_match($source['match'], $input)) {
$type = $source['type'];
$result = array();
$result['service'] = 'embedly';
$result['type'] = $type;
if ($type == 'link' || $type == 'card') {
$result['url'] = preg_replace($source['match'], $source['url'], $input);
$result['title'] = preg_replace($source['match'], $source['title'], $input);
}
if ($type == 'card') {
$result['data'] = preg_replace($source['match'], $source['data'], $input);
$result['desc'] = preg_replace($source['match'], $source['desc'], $input);
}
if ($type == 'div' || $type == 'iframe') {
$result['src'] = preg_replace($source['match'], $source['src'], $input);
if (preg_match('#width="(\d+)"#', $input, $m)) {
$result['width'] = $m[1];
}
if (preg_match('#height="(\d+)"#', $input, $m)) {
$result['height'] = $m[1];
}
}
if ($type == 'div') {
$result['style1'] = preg_replace($source['match'], $source['style1'], $input);
$result['style2'] = preg_replace($source['match'], $source['style2'], $input);
}
return $result;
}
}
return false;
}
/*
* Function that builds embed/iframe code to be
* embedded into Mahara page.
*/
public function embed_content($input) {
$width = isset($input['width']) ? (int)$input['width'] : self::$default_width;
$height = isset($input['height']) ? (int)$input['height'] : self::$default_height;
$type = isset($input['type']) ? $input['type'] : 'link';
$url = isset($input['url']) ? $input['url'] : null;
$title = isset($input['title']) ? $input['title'] : null;
$data = isset($input['data']) ? $input['data'] : null;
if ($data) {
// We want to strip out the quotes otherwise they get escaped as
// we can't use '|safe' in template for potentially unsafe input
$data = str_replace('"', '', str_replace("'", "", $data));
}
$desc = isset($input['desc']) ? $input['desc'] : null;
$src = isset($input['src']) ? $input['src'] : null;
$style1 = isset($input['style1']) ? $input['style1'] : null;
$style2 = isset($input['style2']) ? $input['style2'] : null;
$smarty = smarty_core();
$smarty->assign('width', $width);
$smarty->assign('height', $height);
$smarty->assign('type', $type);
$smarty->assign('url', $url);
$smarty->assign('title', $title);
$smarty->assign('data', $data);
$smarty->assign('desc', $desc);
$smarty->assign('src', $src);
$smarty->assign('style1', $style1);
$smarty->assign('style2', $style2);
$smarty->assign('key', get_random_key());
return $smarty->fetch('blocktype:externalvideo:embedly.tpl');
}
/*
* Function that returns embed service base URL.
*/
public function get_base_url() {
return self::$base_url;
}
}
......@@ -4,6 +4,7 @@
* @package mahara
* @subpackage blocktype-externalvideo
* @author Catalyst IT Ltd
* @author Gregor Anzelj
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
......@@ -23,7 +24,7 @@ $string['widthheightdescription'] = 'Width and height fields are only used for U
$string['invalidurl'] = 'Invalid URL';
$string['invalidurlorembed'] = 'Invalid URL or embed code';
//Supported sites language strings
// Supported sites language strings
$string['googlevideo'] = 'Google Video';
$string['scivee'] = 'SciVee';
$string['youtube'] = 'YouTube';
......@@ -35,3 +36,8 @@ $string['vimeo'] = 'Vimeo';
$string['voki'] = 'Voki';
$string['voicethread'] = 'VoiceThread';
$string['wikieducator'] = 'WikiEducator';
// Embed services
$string['validembedservices'] = 'The following <strong>embed services</strong> for embedding content are supported:';
$string['embedservicesdescriptiondetail'] = 'Enable each embed service below by obtaining and entering an API key for that service. Embed services provide additional functionality to the <a href="%sadmin/extensions/iframesites.php">allowed iframe sources</a> by allowing users to simply enter URL address of the resource they want to embed to external media configure block. Embed services then serve embeddable content (such as photos or videos) which can be displayed in pages. Typically these embed services support a wide variety of providers who provide content that can be embedded.';
$string['enableservices'] = 'None, %senable embed services%s';
\ No newline at end of file
......@@ -4,6 +4,7 @@
* @package mahara
* @subpackage blocktype-externalvideo
* @author Catalyst IT Ltd
* @author Gregor Anzelj
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
......@@ -36,6 +37,10 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
'voki',
);
private static $embed_services = array(
'embedly',
);
public static function get_title() {
return get_string('title', 'blocktype.externalvideo');
}
......@@ -71,6 +76,24 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
return $loaded_sources;
}
private static function load_embed_services() {
static $loaded_services = array();
if (!empty($loaded_services)) {
return $loaded_services;
}
foreach (self::$embed_services as $service) {
include_once('embed_services/' . $service . '/embedservice.php');
$servicename = 'Embed_' . $service;
$embedservice = new $servicename;
if ($embedservice->enabled()) {
$loaded_services[$service] = $embedservice;
}
}
return $loaded_services;
}
public static function embed_code($url, $width, $height) {
$width = (int) $width;
$height = (int) $height;
......@@ -123,8 +146,13 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
$instance->commit();
}
if (empty($configdata['html'])) {
return '';
// This is block that contains embed/iframe code from embed_service
if (isset($configdata['embed']) && !empty($configdata['embed'])) {
$service = $configdata['embed']['service'];
include_once('embed_services/' . $service . '/embedservice.php');
$servicename = 'Embed_' . $service;
$embedservice = new $servicename;
return $embedservice->embed_content($configdata['embed']);
}
$smarty = smarty_core();
......@@ -176,7 +204,8 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
'title' => get_string('urlorembedcode', 'blocktype.externalvideo'),
'description' => get_string('videourldescription3', 'blocktype.externalvideo') .
'<br />' . get_string('validiframesites', 'blocktype.externalvideo') . ' ' . self::get_valid_iframe_html() .'<br />'.
get_string('validurlsites', 'blocktype.externalvideo') . ' ' . self::get_valid_url_html(),
get_string('validurlsites', 'blocktype.externalvideo') . ' ' . self::get_valid_url_html() .'<br />'.
get_string('validembedservices', 'blocktype.externalvideo') . ' ' . self::get_valid_services_html(),
'cols' => '60',
'rows' => '3',
'defaultvalue' => isset($configdata['videoid']) ? $configdata['videoid'] : null,
......@@ -232,6 +261,16 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
}
}
// The user entered a valid url, so check whether any of the
// embed_services want to try and generate embed/iframe code.
$services = self::load_embed_services();
foreach ($services as $name => $service) {
if ($service->validate_url($content)) {
return;
}
}
// Nothing recognised this url.
$form->set_error('videoid', get_string('invalidurl', 'blocktype.externalvideo'), false);
}
......@@ -245,6 +284,25 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
$httpstr = is_https() ? 'https' : 'http';
$values['videoid'] = preg_replace('#https?://#', $httpstr . '://', $values['videoid']);
$values['html'] = $values['videoid'];
// Process user entered embed/iframe code from embed_service.
$services = self::load_embed_services();
foreach ($services as $name => $service) {
if ($data = $service->process_content($values['videoid'])) {
// Override width set in embed/iframe code
if ($values['width']) {
$data['width'] = $values['width'];
}
// Override height set in embed/iframe code
if ($values['height']) {
$data['height'] = $values['height'];
}
$values['embed'] = $data;
break;
}
}
return $values;
}
......@@ -279,6 +337,16 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
return $result;
}
}
// Try with embed services
$services = self::load_embed_services();
foreach ($services as $name => $service) {
if ($result = $service->process_url($url, $width, $height)) {
return $result;
}
}
return false;
}
......@@ -331,6 +399,42 @@ class PluginBlocktypeExternalvideo extends MaharaCoreBlocktype {
return $smarty->fetch('blocktype:externalvideo:sitelist.tpl');
}
/**
* Returns a block of HTML that the external video block can use to show the
* embed services (e.g. Embed.ly) which can be used to process URLs.
*/
private static function get_valid_services_html() {
global $USER;
$service_instances = self::load_embed_services();
$wwwroot = get_config('wwwroot');
$data = array();
$nodata = '';
if (empty($service_instances)) {
if ($USER->get('admin')) {
$nodata = get_string('enableservices', 'blocktype.externalvideo', '<a href="' . $wwwroot . 'admin/extensions/pluginconfig.php?plugintype=blocktype&pluginname=externalvideo">', '</a>');
}
else {
$nodata = get_string('none');
}
}
else {
foreach ($service_instances as $name => $service) {
$servicestr = get_string($name, 'blocktype.externalvideo');
$data[$servicestr] = array(
'name' => $servicestr,
'url' => $service->get_base_url(),
'icon' => $wwwroot . 'blocktype/externalvideo/embed_services/' . $name . '/favicon.png',
);
}
}
$smarty = smarty_core();
$smarty->assign('data', $data);
$smarty->assign('nodata', $nodata);
return $smarty->fetch('blocktype:externalvideo:servicelist.tpl');
}
public static function default_copy_type() {
return 'full';
}
......
......@@ -4,6 +4,7 @@
* @package mahara
* @subpackage blocktype-externalvideo
* @author Catalyst IT Ltd
* @author Gregor Anzelj
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
......@@ -12,5 +13,5 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2014030502;
$config->release = '1.0.3';
$config->version = 2015112400;
$config->release = '1.1.0';
......@@ -745,6 +745,9 @@
}
pd['action_moveblockinstance_id_' + instanceId + '_row_' + whereTo['row'] + '_column_' + whereTo['column'] + '_order_' + whereTo['order']] = true;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
if (data.data.html) {
$('#blockinstance_' + instanceId + ' .blockinstance-content').html(data.data.html);
}
hideColumnBackgrounds();
});
}
......
......@@ -2277,6 +2277,15 @@ class View {
$this->dirtycolumns[$values['row']][$bi->get('column')] = 1;
$this->dirtycolumns[$values['row']][$values['column']] = 1;
db_commit();
// Because embedly externalvideo blocks have their original content changed
// by the cdn.embedly.com/widgets/platform.js file to use iframe data the info
// is lost on block move so we need to referesh the block with its original content
$configdata = $bi->get('configdata');
$html = null;
if ($bi->get('blocktype') == 'externalvideo' && $configdata['embed']['service'] == 'embedly') {
$html = PluginBlocktypeExternalvideo::render_instance($bi, true);
}
return array('html' => $html);
}
/**
......
.embedservice_content {
padding: 10px 0px;
}
.embedservice_content table#embedservice_table {
width: 100%;
}
.embedservice_content table#embedservice_table td {
border: 1px solid transparent;
padding: 0px 10px;
font-size: larger;
}
\ No newline at end of file
<div class="mediaplayer-container text-center">
{if $type == 'link'}
<a class="embedly-card" href="{if $url}{$url}{else}{$src}{/if}">{if $title}{$title}{/if}</a>
{/if}
{if $type == 'card'}
<blockquote class="embedly-card" {$data}><h4><a href="{$url}">{if $title}{$title}{/if}</a></h4>{if $desc}<p>{$desc}</p>{/if}</blockquote>
{/if}
{if $type == 'div'}
<div class="embedly-responsive" style="{$style1}"><iframe class="embedly-embed" frameborder="0" scrolling="no" allowfullscreen src="{$src}" width="{$width}" height="{$height}" style="{$style2}"></iframe></div>
{/if}
{if $type == 'iframe'}
<iframe class="embedly-embed" frameborder="0" scrolling="no" allowfullscreen src="{$src}" width="{$width}" height="{$height}"></iframe>
{/if}
</div>
<script type="application/javascript">
function embedLoaded() {
//Embed was loaded.
console.log('...loaded');
var rows = jQuery('.js-col-row'),
i, j,
height,
cols;
for(i = 0; i < rows.length ; i = i + 1) {
height = 0;
cols = jQuery(rows[i]).find('.column .column-content');
cols.height('auto');
for(j = 0; j < cols.length ; j = j + 1) {
height = jQuery(cols[j]).height() > height ? jQuery(cols[j]).height() : height;
}
cols.height(height);
}
}
function checkembedLoaded() {
console.log('loading...');
var expected = jQuery('.mediaplayer-container').length;
var count = 0;
jQuery('.mediaplayer-container').each(function() {
count += (jQuery(this).first().find('iframe').length) ? 1 : 0;
});
if (count == expected) {
clearInterval(twt{$key});
embedLoaded();
}
}
var twt{$key} = setInterval(checkembedLoaded, 1000);
</script>
\ No newline at end of file
{if $data}
<ul class="text-inline unstyled">
{foreach from=$data item=item}
<li class="text-inline"><a href="{$item.url}" target="_blank"><img src="{$item.icon}" alt="{$item.name}" title="{$item.name}"></a></li>
{/foreach}
</ul>
{else}
{$nodata|safe}
{/if}
\ No newline at end of file
......@@ -2,4 +2,4 @@
{foreach from=$data item=item}
<li class="text-inline"><a href="{$item.url}" target="_blank"><img src="{$item.icon}" alt="{$item.name}" title="{$item.name}"></a></li>
{/foreach}
</ul>
</ul>
\ No newline at end of file
......@@ -150,6 +150,9 @@ $javascript = array('views', 'tinymce', 'paginator', 'js/jquery/jquery-ui/js/jqu
'lib/pieforms/static/core/pieforms.js','js/jquery/modernizr.custom.js');
$blocktype_js = $view->get_all_blocktype_javascript();
$javascript = array_merge($javascript, $blocktype_js['jsfiles']);
if (is_plugin_active('externalvideo')) {
$javascript = array_merge($javascript, array((is_https() ? 'https:' : 'http:') . '//cdn.embedly.com/widgets/platform.js'));
}
$inlinejs = "addLoadEvent( function() {\n" . join("\n", $blocktype_js['initjs']) . "\n});";
require_once('pieforms/pieform/elements/select.php');
$inlinejs .= pieform_element_select_get_inlinejs();
......
......@@ -193,6 +193,9 @@ function releaseview_submit() {
$javascript = array('paginator', 'viewmenu', 'js/collection-navigation.js');
$blocktype_js = $view->get_all_blocktype_javascript();
$javascript = array_merge($javascript, $blocktype_js['jsfiles']);
if (is_plugin_active('externalvideo')) {
$javascript = array_merge($javascript, array((is_https() ? 'https:' : 'http:') . '//cdn.embedly.com/widgets/platform.js'));
}
$inlinejs = "addLoadEvent( function() {\n" . join("\n", $blocktype_js['initjs']) . "\n});";
// If the view has comments turned off, tutors can still leave
......