silverstripe-framework/oembed/Oembed.php
Will Rossiter ddcfcf7bed Update @package, @subpackage labels
Cleanup of framework's use of @package and @subpackage labels and additional of labels for classes missing packages.

Moved all GridField related components to the one name.

Countless spelling fixes, grammar for other comments.

Link ClassName references in file headers.
2013-05-21 22:24:41 +12:00

334 lines
8.4 KiB
PHP

<?php
/**
* Format of the Oembed config. Autodiscover allows discovery of all URLs.
*
* Endpoint set to true means autodiscovery for this specific provider is
* allowed (even if autodiscovery in general has been disabled).
*
* <code>
*
* name: Oembed
* ---
* Oembed:
* providers:
* 'http://*.youtube.com/watch*':
* 'http://www.youtube.com/oembed/'
* autodiscover:
* true
* </code>
*
* @package framework
* @subpackage oembed
*/
class Oembed {
public static function is_enabled() {
return Config::inst()->get('Oembed', 'enabled');
}
/**
* Gets the autodiscover setting from the config.
*/
public static function get_autodiscover() {
return Config::inst()->get('Oembed', 'autodiscover');
}
/**
* Gets providers from config.
*/
public static function get_providers() {
return Config::inst()->get('Oembed', 'providers');
}
/**
* Returns an endpoint (a base Oembed URL) from first matching provider.
*
* @param $url Human-readable URL.
* @returns string/bool URL of an endpoint, or false if no matching provider exists.
*/
protected static function find_endpoint($url) {
foreach(self::get_providers() as $scheme=>$endpoint) {
if(self::matches_scheme($url, $scheme)) {
return $endpoint;
}
}
return false;
}
/**
* Checks the URL if it matches against the scheme (pattern).
*
* @param $url Human-readable URL to be checked.
* @param $scheme Pattern to be matched against.
* @returns bool Whether the pattern matches or not.
*/
protected static function matches_scheme($url, $scheme) {
$urlInfo = parse_url($url);
$schemeInfo = parse_url($scheme);
foreach($schemeInfo as $k=>$v) {
if(!array_key_exists($k, $urlInfo)) {
return false;
}
if(strpos($v, '*') !== false) {
$v = preg_quote($v, '/');
$v = str_replace('\*', '.*', $v);
if($k == 'host') {
$v = str_replace('*\.', '*', $v);
}
if(!preg_match('/' . $v . '/', $urlInfo[$k])) {
return false;
}
} elseif(strcasecmp($urlInfo[$k], $v)) {
return false;
}
}
return true;
}
/**
* Performs a HTTP request to the URL and scans the response for resource links
* that mention oembed in their type.
*
* @param $url Human readable URL.
* @returns string/bool Oembed URL, or false.
*/
protected static function autodiscover_from_url($url) {
// Fetch the URL (cache for a week by default)
$service = new RestfulService($url, 60*60*24*7);
$body = $service->request();
if(!$body || $body->isError()) {
return false;
}
$body = $body->getBody();
// Look within the body for an oembed link.
$pcreOmbed = '#<link[^>]+?(?:href=[\'"](.+?)[\'"][^>]+?)'
. '?type=["\']application/json\+oembed["\']'
. '(?:[^>]+?href=[\'"](.+?)[\'"])?#';
if(preg_match_all($pcreOmbed, $body, $matches, PREG_SET_ORDER)) {
$match = $matches[0];
if(!empty($match[1])) {
return html_entity_decode($match[1]);
}
if(!empty($match[2])) {
return html_entity_decode($match[2]);
}
}
return false;
}
/**
* Takes the human-readable URL of an embeddable resource and converts it into an
* Oembed_Result descriptor (which contains a full Oembed resource URL).
*
* @param $url Human-readable URL
* @param $type ?
* @param $options array Options to be used for constructing the resulting descriptor.
* @returns Oembed_Result/bool An Oembed descriptor, or false
*/
public static function get_oembed_from_url($url, $type = false, array $options = array()) {
if(!self::is_enabled()) return false;
// Find or build the Oembed URL.
$endpoint = self::find_endpoint($url);
$oembedUrl = false;
if(!$endpoint) {
if(self::get_autodiscover()) {
$oembedUrl = self::autodiscover_from_url($url);
}
} elseif($endpoint === true) {
$oembedUrl = self::autodiscover_from_url($url);
} else {
// Build the url manually - we gave all needed information.
$oembedUrl = Controller::join_links($endpoint, '?format=json&url=' . rawurlencode($url));
}
// If autodescovery failed the resource might be a direct link to a file
if(!$oembedUrl) {
if(File::get_app_category(File::get_file_extension($url)) == "image") {
return new Oembed_Result($url, $url, $type, $options);
}
}
if($oembedUrl) {
// Inject the options into the Oembed URL.
if($options) {
if(isset($options['width']) && !isset($options['maxwidth'])) {
$options['maxwidth'] = $options['width'];
}
if(isset($options['height']) && !isset($options['maxheight'])) {
$options['maxheight'] = $options['height'];
}
$oembedUrl = Controller::join_links($oembedUrl, '?' . http_build_query($options, '', '&'));
}
return new Oembed_Result($oembedUrl, $url, $type, $options);
}
// No matching Oembed resource found.
return false;
}
public static function handle_shortcode($arguments, $url, $parser, $shortcode) {
if(isset($arguments['type'])) {
$type = $arguments['type'];
unset($arguments['type']);
} else {
$type = false;
}
$oembed = self::get_oembed_from_url($url, $type, $arguments);
if($oembed && $oembed->exists()) {
return $oembed->forTemplate();
} else {
return '<a href="' . $url . '">' . $url . '</a>';
}
}
}
/**
* @package framework
* @subpackage oembed
*/
class Oembed_Result extends ViewableData {
/**
* JSON data fetched from the Oembed URL.
* This data is accessed dynamically by getField and hasField.
*/
protected $data = false;
/**
* Human readable URL
*/
protected $origin = false;
/**
* ?
*/
protected $type = false;
/**
* Oembed URL
*/
protected $url;
/**
* Class to be injected into the resulting HTML element.
*/
protected $extraClass;
private static $casting = array(
'html' => 'HTMLText',
);
public function __construct($url, $origin = false, $type = false, array $options = array()) {
$this->url = $url;
$this->origin = $origin;
$this->type = $type;
if(isset($options['class'])) {
$this->extraClass = $options['class'];
}
parent::__construct();
}
public function getOembedURL() {
return $this->url;
}
/**
* Fetches the JSON data from the Oembed URL (cached).
* Only sets the internal variable.
*/
protected function loadData() {
if($this->data !== false) {
return;
}
// Fetch from Oembed URL (cache for a week by default)
$service = new RestfulService($this->url, 60*60*24*7);
$body = $service->request();
if(!$body || $body->isError()) {
$this->data = array();
return;
}
$body = $body->getBody();
$data = json_decode($body, true);
if(!$data) {
// if the response is no valid JSON we might have received a binary stream to an image
$data = array();
$image = @imagecreatefromstring($body);
if($image !== FALSE) {
preg_match("/^(http:\/\/)?([^\/]+)/i", $this->url, $matches);
$protocoll = $matches[1];
$host = $matches[2];
$data['type'] = "photo";
$data['title'] = basename($this->url) . " ($host)";
$data['url'] = $this->url;
$data['provider_url'] = $protocoll.$host;
$data['width'] = imagesx($image);
$data['height'] = imagesy($image);
$data['info'] = _t('UploadField.HOTLINKINFO',
'Info: This image will be hotlinked. Please ensure you have permissions from the'
. ' original site creator to do so.');
}
}
// Convert all keys to lowercase
$data = array_change_key_case($data, CASE_LOWER);
// Purge everything if the type does not match.
if($this->type && $this->type != $data['type']) {
$data = array();
}
$this->data = $data;
}
/**
* Wrap the check for looking into Oembed JSON within $this->data.
*/
public function hasField($field) {
$this->loadData();
return array_key_exists(strtolower($field), $this->data);
}
/**
* Wrap the field calls to fetch data from Oembed JSON (within $this->data)
*/
public function getField($field) {
$field = strtolower($field);
if($this->hasField($field)) {
return $this->data[$field];
}
}
public function forTemplate() {
$this->loadData();
switch($this->Type) {
case 'video':
case 'rich':
if($this->extraClass) {
return "<div class='$this->extraClass'>$this->HTML</div>";
} else {
return $this->HTML;
}
break;
case 'link':
return '<a class="' . $this->extraClass . '" href="' . $this->origin . '">' . $this->Title . '</a>';
break;
case 'photo':
return "<img src='$this->URL' width='$this->Width' height='$this->Height' class='$this->extraClass' />";
break;
}
}
public function exists() {
$this->loadData();
return count($this->data) > 0;
}
}