diff --git a/_config.php b/_config.php
index e41ce9d2d..cc2163fe0 100644
--- a/_config.php
+++ b/_config.php
@@ -19,7 +19,7 @@
ShortcodeParser::get('default')
->register('file_link', array('File', 'handle_shortcode'))
- ->register('embed', array('Oembed', 'handle_shortcode'))
+ ->register('embed', array('SilverStripe\Forms\HtmlEditor\EmbedShortcodeProvider', 'handle_shortcode'))
->register('image', array('Image', 'handle_shortcode'));
// Shortcode parser which only regenerates shortcodes
diff --git a/_config/Oembed.yml b/_config/Oembed.yml
deleted file mode 100644
index ee54de4ff..000000000
--- a/_config/Oembed.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Oembed
----
-Oembed:
- providers:
- 'http://*.youtube.com/watch*':
- http: 'http://www.youtube.com/oembed/'
- https: 'https://www.youtube.com/oembed/?scheme=https'
- 'https://*.youtube.com/watch*':
- http: 'http://www.youtube.com/oembed/'
- https: 'https://www.youtube.com/oembed/?scheme=https'
- 'http://*.youtu.be/*':
- http: 'http://www.youtube.com/oembed/'
- https: 'https://www.youtube.com/oembed/?scheme=https'
- 'https://youtu.be/*':
- http: 'http://www.youtube.com/oembed/'
- https: 'https://www.youtube.com/oembed/?scheme=https'
- 'http://*.flickr.com/*':
- 'http://www.flickr.com/services/oembed/'
- 'http://*.viddler.com/*':
- 'http://lab.viddler.com/services/oembed/'
- 'http://*.revision3.com/*':
- 'http://revision3.com/api/oembed/'
- 'http://*.hulu.com/watch/*':
- 'http://www.hulu.com/api/oembed.json'
- 'http://*.vimeo.com/*':
- http: 'http://vimeo.com/api/oembed.json'
- https: 'https://vimeo.com/api/oembed.json'
- 'https://*.vimeo.com/*':
- http: 'http://vimeo.com/api/oembed.json'
- https: 'https://vimeo.com/api/oembed.json'
- 'http://twitter.com/*':
- http: 'https://api.twitter.com/1/statuses/oembed.json'
- https: 'https://api.twitter.com/1/statuses/oembed.json'
- 'https://twitter.com/*':
- http: 'https://api.twitter.com/1/statuses/oembed.json'
- https: 'https://api.twitter.com/1/statuses/oembed.json'
- autodiscover:
- true
- enabled:
- true
diff --git a/client/src/legacy/HtmlEditorField.js b/client/src/legacy/HtmlEditorField.js
index 3c9c0748e..445739a45 100644
--- a/client/src/legacy/HtmlEditorField.js
+++ b/client/src/legacy/HtmlEditorField.js
@@ -1397,7 +1397,7 @@ $.entwine('ss', function($) {
/**
- * Insert an oembed object tag into the content.
+ * Insert an Embed object tag into the content.
* Requires the 'media' plugin for serialization of tags into placeholders.
*/
$('form.htmleditorfield-mediaform .ss-htmleditorfield-file.embed').entwine({
diff --git a/composer.json b/composer.json
index c3315a7c7..ae51e78f2 100644
--- a/composer.json
+++ b/composer.json
@@ -20,8 +20,9 @@
"composer/installers": "~1.0",
"monolog/monolog": "~1.11",
"league/flysystem": "~1.0.12",
- "symfony/yaml": "~2.7"
- },
+ "symfony/yaml": "~2.7",
+ "embed/embed": "^2.6"
+ },
"require-dev": {
"phpunit/PHPUnit": "~4.8"
},
diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md
index f374becd0..4f4d148dd 100644
--- a/docs/en/04_Changelogs/4.0.0.md
+++ b/docs/en/04_Changelogs/4.0.0.md
@@ -281,6 +281,14 @@ E.g.
'mysite/js/src/main.js',
'mysite/js/src/functions.js'
]]);
+
+### RestfulService
+
+* `RestfulService` has been removed. Use Guzzle instead. See Upgrading notes.
+
+### Oembed
+
+* Our self-maintained `Oembed` implementation has been removed, in favour of introducing [oscarotero/Embed](https://github.com/oscarotero/Embed) as a dependency.
## Upgrading
@@ -822,3 +830,18 @@ Will become:
}
Note that string references to SS_Datetime passed to injector, or used in config values, will still work, and will refer to the updated class names.
+
+### Upgrading from deprecated RestfulService
+
+Install Guzzle to get an API consuming library.
+`composer require guzzlehttp/guzzle` or add `guzzlehttp/guzzle: "^6.0"` to your composer.json.
+
+For information on how to use Guzzle, please see the extensive [Guzzle documentation](http://docs.guzzlephp.org/en/latest/)
+
+In case you want to keep using RestfulService, you can use `Firesphere/silverstripe-restfulservice`, but it is unmaintained and deprecated.
+
+### Upgrading from deprecated Oembed
+
+Instead of Oembed, the framework now relies on [oscarotero/Embed](https://github.com/oscarotero/Embed) to handle getting the shortcode-data for embedding.
+
+If you have custom embedding-code relying on Oembed, please refer to the documentation provided by oscarotero.
diff --git a/forms/htmleditor/EmbedShortcodeProvider.php b/forms/htmleditor/EmbedShortcodeProvider.php
new file mode 100644
index 000000000..f4a2bc0f6
--- /dev/null
+++ b/forms/htmleditor/EmbedShortcodeProvider.php
@@ -0,0 +1,77 @@
+' . $content . '';
+ }
+ }
+
+ /**
+ * @param Adapter $embed
+ *
+ * @return string
+ */
+ public static function embedForTemplate($embed)
+ {
+ switch ($embed->type) {
+ case 'video':
+ case 'rich':
+ if ($embed->extraClass) {
+ return "
";
+ } else {
+ return "$embed->code
";
+ }
+ break;
+ case 'link':
+ return '';
+ break;
+ case 'photo':
+ return "";
+ break;
+ }
+ }
+}
diff --git a/forms/htmleditor/HTMLEditorField.php b/forms/htmleditor/HTMLEditorField.php
index 516f94ecb..f5c07524a 100644
--- a/forms/htmleditor/HTMLEditorField.php
+++ b/forms/htmleditor/HTMLEditorField.php
@@ -1,4 +1,7 @@
tags, which are then converted with JavaScript.
@@ -523,8 +526,8 @@ class HTMLEditorField_Toolbar extends RequestHandler {
));
}
- // Instanciate file wrapper and get fields based on its type
- // Check if appCategory is an image and exists on the local system, otherwise use oEmbed to refference a
+ // Instantiate file wrapper and get fields based on its type
+ // Check if appCategory is an image and exists on the local system, otherwise use Embed to reference a
// remote image
$fileCategory = $this->getFileCategory($url, $file);
switch($fileCategory) {
@@ -541,11 +544,11 @@ class HTMLEditorField_Toolbar extends RequestHandler {
if($file) {
throw $this->getErrorFor(_t(
"HTMLEditorField_Toolbar.ERROR_OEMBED_REMOTE",
- "Oembed is only compatible with remote files"
+ "Embed is only compatible with remote files"
));
}
- // Other files should fallback to oembed
+ // Other files should fallback to embed
$fileWrapper = new HTMLEditorField_Embed($url, $file);
break;
}
@@ -1031,9 +1034,9 @@ abstract class HTMLEditorField_File extends ViewableData {
}
/**
- * Encapsulation of an oembed tag, linking to an external media source.
+ * Encapsulation of an embed tag, linking to an external media source.
*
- * @see Oembed
+ * @see Embed
* @package forms
* @subpackage fields-formattedinput
*/
@@ -1045,16 +1048,16 @@ class HTMLEditorField_Embed extends HTMLEditorField_File {
);
/**
- * Oembed result
+ * Embed result
*
- * @var Oembed_Result
+ * @var Embed
*/
- protected $oembed;
+ protected $embed;
public function __construct($url, File $file = null) {
parent::__construct($url, $file);
- $this->oembed = Oembed::get_oembed_from_url($url);
- if(!$this->oembed) {
+ $this->embed = Embed::create($url);
+ if(!$this->embed) {
$controller = Controller::curr();
$response = $controller->getResponse();
$response->addHeader('X-Status',
@@ -1093,32 +1096,32 @@ class HTMLEditorField_Embed extends HTMLEditorField_File {
}
/**
- * Get width of this oembed
+ * Get width of this Embed
*
* @return int
*/
public function getWidth() {
- return $this->oembed->Width ?: 100;
+ return $this->embed->width ?: 100;
}
/**
- * Get height of this oembed
+ * Get height of this Embed
*
* @return int
*/
public function getHeight() {
- return $this->oembed->Height ?: 100;
+ return $this->embed->height ?: 100;
}
public function getPreviewURL() {
// Use thumbnail url
- if(!empty($this->oembed->thumbnail_url)) {
- return $this->oembed->thumbnail_url;
+ if($this->embed->image) {
+ return $this->embed->image;
}
// Use direct image type
- if($this->getType() == 'photo' && !empty($this->Oembed->url)) {
- return $this->Oembed->url;
+ if($this->getType() == 'photo' && !empty($this->embed->url)) {
+ return $this->embed->url;
}
// Default media
@@ -1126,32 +1129,37 @@ class HTMLEditorField_Embed extends HTMLEditorField_File {
}
public function getName() {
- if(isset($this->oembed->title)) {
- return $this->oembed->title;
+ if($this->embed->title) {
+ return $this->embed->title;
} else {
return parent::getName();
}
}
/**
- * Get OEmbed type
+ * Get Embed type
*
* @return string
*/
public function getType() {
- return $this->oembed->type;
+ return $this->embed->type;
}
+ /**
+ * Get filetype
+ *
+ * @return string
+ */
public function getFileType() {
return $this->getType()
?: parent::getFileType();
}
/**
- * @return Oembed_Result
+ * @return AdapterInterface
*/
- public function getOembed() {
- return $this->oembed;
+ public function getEmbed() {
+ return $this->embed;
}
public function appCategory() {
@@ -1159,12 +1167,12 @@ class HTMLEditorField_Embed extends HTMLEditorField_File {
}
/**
- * Info for this oembed
+ * Info for this Embed
*
* @return string
*/
public function getInfo() {
- return $this->oembed->info;
+ return $this->embed->info;
}
}
diff --git a/oembed/Oembed.php b/oembed/Oembed.php
deleted file mode 100644
index f2b7f1a6c..000000000
--- a/oembed/Oembed.php
+++ /dev/null
@@ -1,428 +0,0 @@
-
- *
- * name: Oembed
- * ---
- * Oembed:
- * providers:
- * 'http://*.youtube.com/watch*':
- * 'http://www.youtube.com/oembed/'
- * autodiscover:
- * true
- *
- *
- * @package framework
- * @subpackage oembed
- */
-
-class Oembed implements ShortcodeHandler {
-
- 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)) {
- $protocol = Director::is_https() ? 'https' : 'http';
-
- if (is_array($endpoint)) {
- if (array_key_exists($protocol, $endpoint)) $endpoint = $endpoint[$protocol];
- else $endpoint = reset($endpoint);
- }
-
- 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)
- {
- $timeout = 5;
- $sapphireInfo = new SapphireInfo();
- $useragent = 'SilverStripe/' . $sapphireInfo->Version();
- $curlRequest = curl_init();
- curl_setopt_array(
- $curlRequest,
- array(
- CURLOPT_URL => $url,
- CURLOPT_RETURNTRANSFER => 1,
- CURLOPT_USERAGENT => $useragent,
- CURLOPT_CONNECTTIMEOUT => $timeout,
- CURLOPT_FOLLOWLOCATION => 1,
-
- )
- );
-
- $response = curl_exec($curlRequest);
- $headers = curl_getinfo($curlRequest);
- if(!$response || $headers['http_code'] !== 200) {
- return false;
- }
- $body = $response;
- return static::autodiscover_from_body($body);
- }
-
- /**
- * Given a response body, determine if there is an autodiscover url
- *
- * @param string $body
- * @return bool|string
- */
- public static function autodiscover_from_body($body) {
- // Look within the body for an oembed link.
- $pcreOmbed = '#]+?(?:href=[\'"](?[^\'"]+?)[\'"][^>]+?)'
- . '?type=["\']application/json\+oembed["\']'
- . '(?:[^>]+?href=[\'"](?[^\'"]+?)[\'"])?#';
-
- if(preg_match_all($pcreOmbed, $body, $matches, PREG_SET_ORDER)) {
- $match = $matches[0];
- if(!empty($match['second'])) {
- return html_entity_decode($match['second']);
- }
- if(!empty($match['first'])) {
- return html_entity_decode($match['first']);
- }
- }
- 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 get_shortcodes() {
- return 'embed';
- }
-
- public static function handle_shortcode($arguments, $content, $parser, $shortcode, $extra = array()) {
- if(isset($arguments['type'])) {
- $type = $arguments['type'];
- unset($arguments['type']);
- } else {
- $type = false;
- }
- $oembed = self::get_oembed_from_url($content, $type, $arguments);
- if($oembed && $oembed->exists()) {
- return $oembed->forTemplate();
- } else {
- return '' . $content . '';
- }
- }
-}
-
-/**
- * @property string $Type Oembed type
- * @property string $Title Title
- * @property string $URL URL to asset
- * @property string $Provider_URL Url for provider
- * @property int $Width
- * @property int $Height
- * @property string $Info Descriptive text for this oembed
- *
- * @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'];
- }
- if($options) {
- if(isset($options['width'])) {
- $this->Width = $options['width'];
- }
- if(isset($options['height'])) {
- $this->Height = $options['height'];
- }
- }
- 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;
- }
- $timeout = 5;
- $sapphireInfo = new SapphireInfo();
- $useragent = 'SilverStripe/' . $sapphireInfo->Version();
- $curlRequest = curl_init();
- curl_setopt_array(
- $curlRequest,
- array(
- CURLOPT_URL => $this->url,
- CURLOPT_RETURNTRANSFER => 1,
- CURLOPT_USERAGENT => $useragent,
- CURLOPT_CONNECTTIMEOUT => $timeout,
- CURLOPT_FOLLOWLOCATION => 1,
-
- )
- );
-
- $response = curl_exec($curlRequest);
- $headers = curl_getinfo($curlRequest);
- if(!$response || $headers['http_code'] !== 200) {
- $this->data = array();
- return;
- }
- $body = $response;
- $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();
- if (!function_exists('imagecreatefromstring')) {
- throw new LogicException('imagecreatefromstring function does not exist - Please make sure GD is installed');
- }
- $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);
-
- // Check if we can guess thumbnail
- if(empty($data['thumbnail_url']) && $thumbnail = $this->findThumbnail($data)) {
- $data['thumbnail_url'] = $thumbnail;
- }
-
- // Purge everything if the type does not match.
- if($this->type && $this->type != $data['type']) {
- $data = array();
- }
-
- $this->data = $data;
- }
-
- /**
- * Find thumbnail if omitted from data
- *
- * @param array $data
- * @return string
- */
- public function findThumbnail($data) {
- if(!empty($data['thumbnail_url'])) {
- return $data['thumbnail_url'];
- }
-
- // Hack in facebook graph thumbnail
- if(!empty($data['provider_name']) && $data['provider_name'] === 'Facebook') {
- $id = preg_replace("/.*\\/(\\d+?)\\/?($|\\?.*)/", "$1", $data["url"]);
- return "https://graph.facebook.com/{$id}/picture";
- }
-
- // no thumbnail found
- return null;
- }
-
- /**
- * 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 "";
- } else {
- return "$this->HTML
";
- }
- break;
- case 'link':
- return '';
- break;
- case 'photo':
- return "";
- break;
- }
- }
-
- public function exists() {
- $this->loadData();
- return count($this->data) > 0;
- }
-}
-
diff --git a/parsers/ShortcodeParser.php b/parsers/ShortcodeParser.php
index a1bb961f1..354d34c36 100644
--- a/parsers/ShortcodeParser.php
+++ b/parsers/ShortcodeParser.php
@@ -1,4 +1,5 @@
callShortcode($tag['open'], $tag['attrs'], $tag['content'], $extra);
// Missing tag
@@ -157,7 +161,8 @@ class ShortcodeParser extends Object {
}
protected function insertAfter($new, $after) {
- $parent = $after->parentNode; $next = $after->nextSibling;
+ $parent = $after->parentNode;
+ $next = $after->nextSibling;
if ($next) {
$parent->insertBefore($new, $next);
@@ -182,6 +187,9 @@ class ShortcodeParser extends Object {
}
}
+ /**
+ * @var string
+ */
protected static $marker_class = '--ss-shortcode-marker';
protected static $block_level_elements = array(
@@ -612,4 +620,5 @@ class ShortcodeParser extends Object {
return $content;
}
+
}
diff --git a/tests/forms/EmbedShortcodeProviderTest.php b/tests/forms/EmbedShortcodeProviderTest.php
new file mode 100644
index 000000000..54b996ac5
--- /dev/null
+++ b/tests/forms/EmbedShortcodeProviderTest.php
@@ -0,0 +1,59 @@
+providerName, 'YouTube');
+ $embedded = EmbedShortcodeProvider::embedForTemplate($result);
+ self::assertContains("