first commit
This commit is contained in:
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
/**
|
||||
* MediaEmbed
|
||||
*
|
||||
* This file is part of Grav MediaEmbed plugin.
|
||||
*
|
||||
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
|
||||
* http://benjamin-regler.de/license/
|
||||
*/
|
||||
|
||||
namespace Grav\Plugin\MediaEmbed;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Plugin\MediaEmbed\Service;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* MediaEmbed
|
||||
*
|
||||
* Helper class to embed several media sites (e.g. YouTube, Vimeo,
|
||||
* Soundcloud) by only providing the URL to the medium.
|
||||
*/
|
||||
class MediaEmbed
|
||||
{
|
||||
/**
|
||||
* @var MediaEmbed
|
||||
*/
|
||||
use GravTrait;
|
||||
|
||||
/** ---------------------------
|
||||
* Private/protected properties
|
||||
* ----------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* A unique identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* A key-valued array used for hashing math formulas of a page
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hashes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $assets = [];
|
||||
|
||||
/**
|
||||
* @var Grav\Plugin\MediaEmbed\Service
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/** -------------
|
||||
* Public methods
|
||||
* --------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param [type] $config [description]
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
// Initialize Service class
|
||||
$this->service = new Service();
|
||||
$this->config = $config;
|
||||
$this->hashes = [];
|
||||
|
||||
$services = $this->config->get('plugins.mediaembed.services', []);
|
||||
foreach ($services as $name => $config) {
|
||||
if (!$config['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load providers in directory "services"
|
||||
$class = __NAMESPACE__ . "\\Services\\$name";
|
||||
if (!class_exists($class)) {
|
||||
// Fallback to a more generic one
|
||||
$type = isset($config['type']) ? $config['type'] : '';
|
||||
$class = __NAMESPACE__."\\OEmbed\\OEmbed".ucfirst($type);
|
||||
}
|
||||
|
||||
// Populate config
|
||||
$config['media'] = $this->config->get('plugins.mediaembed.media', []);
|
||||
$config['name'] = $name;
|
||||
|
||||
if (class_exists($class)) {
|
||||
// Load ServiceProvider
|
||||
$provider = new $class($config);
|
||||
|
||||
// Register ServiceProvider
|
||||
$this->service->register($provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the identifier for hashing.
|
||||
*
|
||||
* @param string $var the identifier
|
||||
*
|
||||
* @return string the identifier
|
||||
*/
|
||||
public function id($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->id = $var;
|
||||
}
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function prepare($content, $id = '')
|
||||
{
|
||||
// Set unique identifier based on page content
|
||||
$this->id(md5(time() . $id . md5($content)));
|
||||
|
||||
// Reset class hashes before processing
|
||||
$this->reset();
|
||||
|
||||
$regex = "~
|
||||
( # wrap whole match in $1
|
||||
!\\[
|
||||
(?P<alt>.*?) # alt text = $2
|
||||
\\]
|
||||
\\( # literal paren
|
||||
[ \\t]*
|
||||
<?(?P<src>\S+?)>? # src url = $3
|
||||
[ \\t]*
|
||||
( # $4
|
||||
(['\"]) # quote char = $5
|
||||
(?<title>.*?) # title = $6
|
||||
\\5 # matching quote
|
||||
[ \\t]*
|
||||
)? # title is optional
|
||||
\\)
|
||||
)
|
||||
~xs";
|
||||
|
||||
// Replace all mediaembed links by a (unique) hash
|
||||
$content = preg_replace_callback($regex, function($matches) {
|
||||
// Get the url and parse it
|
||||
$url = parse_url(htmlspecialchars_decode($matches[3]));
|
||||
|
||||
// If there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
if (!isset($matches['title'])) {
|
||||
$matches['title'] = '';
|
||||
}
|
||||
|
||||
return $this->hash($matches[0], $matches);
|
||||
}, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function process($content, $config = [])
|
||||
{
|
||||
/** @var Twig $twig */
|
||||
$twig = self::getGrav()['twig'];
|
||||
|
||||
// Initialize unique per-page counter
|
||||
$uid = 1;
|
||||
|
||||
// '~(<p>)?\s*<a[^>]*href\s*=\s*([\'"])(?P<href>.*?)\2[^>]*>(?P<code>.*?)</a>\s*(?(1)(</p>))~i',
|
||||
|
||||
// Get all <a> tags and extract "href" attribute
|
||||
$content = preg_replace_callback(
|
||||
'~mediaembed::([0-9a-z]+)::([0-9]+)::M~i',
|
||||
function($match) use ($twig, &$uid, $config) {
|
||||
list($embed, $data) = $this->hashes[$match[0]];
|
||||
|
||||
// Check if a service for a specific domain is registered
|
||||
if ($this->service->match($data['src'])) {
|
||||
$mediaembed = [
|
||||
'uid' => $uid++,
|
||||
'service' => null,
|
||||
'config' => $config,
|
||||
|
||||
'raw' => [
|
||||
'alt' => $data['alt'],
|
||||
'title' => $data['title'],
|
||||
'src' => html_entity_decode($data['src']),
|
||||
],
|
||||
|
||||
'success' => true,
|
||||
'message' => '',
|
||||
];
|
||||
|
||||
// Load and get data of OEmbed Media Service
|
||||
try {
|
||||
$provider = $this->service->embed($data['src']);
|
||||
} catch (\Exception $e) {
|
||||
$mediaembed['message'] = $e->getMessage();
|
||||
$mediaembed['success'] = false;
|
||||
}
|
||||
|
||||
// Setup variables for embedding OEmbed Media Service
|
||||
if ($mediaembed['success']) {
|
||||
// Get assets/options of current provider
|
||||
$assets = $provider->onTwigTemplateVariables(
|
||||
new Event(['service' => $this->service, 'mediaembed' => $this])
|
||||
);
|
||||
|
||||
// Assets are passed by value as an array
|
||||
if (is_array($assets)) {
|
||||
$this->addAssets($assets);
|
||||
}
|
||||
|
||||
// Add OEmbed Service to variables
|
||||
$mediaembed['service'] = $provider;
|
||||
|
||||
// TODO: Cache contents from thumbnail and url
|
||||
}
|
||||
|
||||
// Embed OEmbed Media
|
||||
$vars = ['mediaembed' => $mediaembed];
|
||||
$template = 'partials/mediaembed' . TEMPLATE_EXT;
|
||||
$embed = $twig->processTemplate($template, $vars);
|
||||
} else {
|
||||
$text = (strlen($data['alt']) > 0) ? $data['alt'] : $data['src'];
|
||||
|
||||
// If display link or img
|
||||
$link = $config->get('link');
|
||||
if($link == true) {
|
||||
$attributes = [
|
||||
'href' => $data['src'],
|
||||
'title' => $data['title'],
|
||||
];
|
||||
|
||||
$format = '<a%s>%s</a>';
|
||||
|
||||
} else {
|
||||
$attributes = [
|
||||
'src' => $data['src'],
|
||||
'title' => $data['title'],
|
||||
'alt' => $data['alt'],
|
||||
];
|
||||
|
||||
$format = '<img%s>';
|
||||
|
||||
}
|
||||
|
||||
foreach ($attributes as $key => $value) {
|
||||
if (strlen($value) == 0) {
|
||||
unset($attributes[$key]);
|
||||
} else {
|
||||
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$attributes[$key] = $key . '="' . $value . '"';
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = $attributes ? ' ' . implode(' ', $attributes) : '';
|
||||
|
||||
// Transform embed media to link or img for compatibility
|
||||
$embed = sprintf($format, $attributes, $text);
|
||||
|
||||
}
|
||||
|
||||
return $embed;
|
||||
}, $content);
|
||||
|
||||
$this->reset();
|
||||
// Write content back to page
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an event with optional parameters.
|
||||
*
|
||||
* @param string $eventName The name of the event.
|
||||
* @param Event $event Optional parameter to be passed to the
|
||||
* called methods.
|
||||
* @return Event
|
||||
*/
|
||||
public function fireEvent($eventName, Event $event = null)
|
||||
{
|
||||
// Dispatch event; just propagate it to service class
|
||||
return $this->service->call($eventName, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assets of loaded media services.
|
||||
*
|
||||
* @param boolean $reset Toggle whether to reset assets after retrieving
|
||||
* or not.
|
||||
*/
|
||||
public function getAssets($reset = true)
|
||||
{
|
||||
$assets = $this->assets;
|
||||
if ($reset) {
|
||||
$this->assets = [];
|
||||
}
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assets to the queue of MediaEmbed plugin
|
||||
*
|
||||
* @param array $assets An array of assets to add.
|
||||
* @param boolean $append Append assets to array or reset assets.
|
||||
*/
|
||||
public function addAssets($assets, $append = true)
|
||||
{
|
||||
// Append or reset assets
|
||||
if (!$append) {
|
||||
$this->assets = [];
|
||||
}
|
||||
|
||||
// Wrap non-array assets in an array
|
||||
if (!is_array($assets)) {
|
||||
$assets = array($assets);
|
||||
}
|
||||
|
||||
// Merge assets
|
||||
$assets = array_merge($this->assets, $assets);
|
||||
|
||||
// Remove duplicates
|
||||
$this->assets = array_keys(array_flip($assets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assets to the queue of MediaEmbed plugin
|
||||
*
|
||||
* Alias for `addAssets`
|
||||
*
|
||||
* @param array $assets An array of assets to add.
|
||||
* @param boolean $append Append assets to array or reset assets.
|
||||
*/
|
||||
public function add($assets, $append = true)
|
||||
{
|
||||
return $this->addAssets($assets, $append);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assets to the queue of MediaEmbed plugin
|
||||
*
|
||||
* Alias for `addAssets`
|
||||
*
|
||||
* @param array $assets An array of assets to add.
|
||||
* @param boolean $append Append assets to array or reset assets.
|
||||
*/
|
||||
public function addCss($assets, $append = true)
|
||||
{
|
||||
return $this->addAssets($assets, $append);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assets to the queue of MediaEmbed plugin
|
||||
*
|
||||
* Alias for `addAssets`
|
||||
*
|
||||
* @param array $assets An array of assets to add.
|
||||
* @param boolean $append Append assets to array or reset assets.
|
||||
*/
|
||||
public function addJs($assets, $append = true)
|
||||
{
|
||||
return $this->addAssets($assets, $append);
|
||||
}
|
||||
|
||||
/** -------------------------------
|
||||
* Private/protected helper methods
|
||||
* --------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get cached media or media with key.
|
||||
*
|
||||
* @param string $key The key to load from the cache.
|
||||
* @return mixed The media content.
|
||||
*/
|
||||
protected function getCachedMedia($key)
|
||||
{
|
||||
/** @var Cache $cache */
|
||||
$cache = $grav['cache'];
|
||||
|
||||
// Check, if cache should be used or not
|
||||
if ($this->config->get('cache.enabled')) {
|
||||
// Get cache id and try to fetch data
|
||||
$cache_id = md5('mediaembed' . $key . $cache->getKey());
|
||||
$data = $cache->fetch($cache_id);
|
||||
|
||||
if ((false === $data) || (time() > $data['expire'])) {
|
||||
// Pack and provide data with a time stamp.
|
||||
$data = array(
|
||||
'content' => $this->service->embed($key),
|
||||
'expire' => time() + $this->config->get('cache.lifetime'),
|
||||
);
|
||||
$cache->save($cache_id, $data);
|
||||
}
|
||||
|
||||
// Return data contents
|
||||
$content = $data['content'];
|
||||
} else {
|
||||
// Just call callback and return result
|
||||
$content = $this->service->embed($key);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseUrl($url)
|
||||
{
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
$url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');
|
||||
$parts = parse_url($url);
|
||||
$parts['url'] = $url;
|
||||
|
||||
// Get top-level domain from URL
|
||||
$parts['domain'] = isset($parts['host']) ? $parts['host'] : '';
|
||||
if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {
|
||||
$parts['domain'] = $match['domain'];
|
||||
}
|
||||
|
||||
if (isset($parts['query'])) {
|
||||
parse_str(urldecode($parts['query']), $parts['query']);
|
||||
}
|
||||
$parts['query'] = [];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset MathJax class
|
||||
*/
|
||||
protected function reset()
|
||||
{
|
||||
$this->hashes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a given text.
|
||||
*
|
||||
* Called whenever a tag must be hashed when a function insert an
|
||||
* atomic element in the text stream. Passing $text to through this
|
||||
* function gives a unique text-token which will be reverted back when
|
||||
* calling unhash.
|
||||
*
|
||||
* @param string $text The text to be hashed
|
||||
* @param string $type The type (category) the text should be saved
|
||||
*
|
||||
* @return string Return a unique text-token which will be
|
||||
* reverted back when calling unhash.
|
||||
*/
|
||||
protected function hash($text, $data = [])
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
// Swap back any tag hash found in $text so we do not have to `unhash`
|
||||
// multiple times at the end.
|
||||
$text = $this->unhash($text);
|
||||
|
||||
// Then hash the block
|
||||
$key = implode('::', array('mediaembed', $this->id, ++$counter, 'M'));
|
||||
$this->hashes[$key] = [$text, $data];
|
||||
|
||||
// String that will replace the tag
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap back in all the tags hashed by hash.
|
||||
*
|
||||
* @param string $text The text to be un-hashed
|
||||
*
|
||||
* @return string A text containing no hash inside
|
||||
*/
|
||||
protected function unhash($text)
|
||||
{
|
||||
$text = preg_replace_callback(
|
||||
'~mediaembed::([0-9a-z]+)::([0-9]+)::M~i', function($atches) {
|
||||
return $this->hashes[$matches[0]][0];
|
||||
}, $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user