From: Mikael Nordfeldth Date: Tue, 6 May 2014 21:00:30 +0000 (+0200) Subject: Moved oEmbed stuff out to a plugin (Oembed). X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=1776c90cb98d5ca738db143703fb998612ada31e;p=quix0rs-gnu-social.git Moved oEmbed stuff out to a plugin (Oembed). --- diff --git a/CONFIGURE b/CONFIGURE index 141c475563..bec9e90a6f 100644 --- a/CONFIGURE +++ b/CONFIGURE @@ -592,23 +592,6 @@ desclimit: maximum number of characters to allow in group descriptions. addtag: Whether to add a tag for the group nickname for every group post (pre-1.0.x behaviour). Defaults to false. -oembed --------- - -oEmbed endpoint for multimedia attachments (links in posts). Will also -work as 'oohembed' for backwards compatibility. - -endpoint: oohembed endpoint using http://oohembed.com/ software. Defaults to - 'http://oohembed.com/oohembed/'. -order: Array of methods to check for OEmbed data. Methods include 'built-in' - (use a built-in function to simulate oEmbed for some sites), - 'well-known' (use well-known public oEmbed endpoints), - 'discovery' (discover using headers in HTML), 'service' (use - a third-party service, like oohembed or embed.ly. Default is - array('built-in', 'well-known', 'service', 'discovery'). Note that very - few sites implement oEmbed; 'discovery' is going to fail 99% of the - time. - search ------ diff --git a/actions/attachment.php b/actions/attachment.php index 30499c15f4..d9cacb1193 100644 --- a/actions/attachment.php +++ b/actions/attachment.php @@ -96,28 +96,6 @@ class AttachmentAction extends Action return $a->title(); } - function extraHead() - { - $this->element('link',array('rel'=>'alternate', - 'type'=>'application/json+oembed', - 'href'=>common_local_url( - 'oembed', - array(), - array('format'=>'json', 'url'=> - common_local_url('attachment', - array('attachment' => $this->attachment->id)))), - 'title'=>'oEmbed'),null); - $this->element('link',array('rel'=>'alternate', - 'type'=>'text/xml+oembed', - 'href'=>common_local_url( - 'oembed', - array(), - array('format'=>'xml','url'=> - common_local_url('attachment', - array('attachment' => $this->attachment->id)))), - 'title'=>'oEmbed'),null); - } - /** * Handle input * diff --git a/actions/oembed.php b/actions/oembed.php deleted file mode 100644 index da0352edf8..0000000000 --- a/actions/oembed.php +++ /dev/null @@ -1,244 +0,0 @@ -. - * - * @category Twitter - * @package StatusNet - * @author Evan Prodromou - * @copyright 2008 StatusNet, Inc. - * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Oembed provider implementation - * - * This class handles all /main/oembed(.xml|.json)/ requests. - * - * @category oEmbed - * @package StatusNet - * @author Craig Andrews - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class OembedAction extends Action -{ - - function handle($args) - { - common_debug("in oembed api action"); - - $url = $args['url']; - if( substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url()) ){ - $path = substr($url,strlen(common_root_url())); - - $r = Router::get(); - - $proxy_args = $r->map($path); - - if (!$proxy_args) { - // TRANS: Server error displayed in oEmbed action when path not found. - // TRANS: %s is a path. - $this->serverError(sprintf(_('"%s" not found.'),$path), 404); - } - $oembed=array(); - $oembed['version']='1.0'; - $oembed['provider_name']=common_config('site', 'name'); - $oembed['provider_url']=common_root_url(); - switch($proxy_args['action']){ - case 'shownotice': - $oembed['type']='link'; - $id = $proxy_args['notice']; - $notice = Notice::getKV($id); - if(empty($notice)){ - // TRANS: Server error displayed in oEmbed action when notice not found. - // TRANS: %s is a notice. - $this->serverError(sprintf(_("Notice %s not found."),$id), 404); - } - $profile = $notice->getProfile(); - if (empty($profile)) { - // TRANS: Server error displayed in oEmbed action when notice has not profile. - $this->serverError(_('Notice has no profile.'), 500); - } - $authorname = $profile->getFancyName(); - // TRANS: oEmbed title. %1$s is the author name, %2$s is the creation date. - $oembed['title'] = sprintf(_('%1$s\'s status on %2$s'), - $authorname, - common_exact_date($notice->created)); - $oembed['author_name']=$authorname; - $oembed['author_url']=$profile->profileurl; - $oembed['url']=$notice->getUrl(); - $oembed['html']=$notice->rendered; - break; - case 'attachment': - $id = $proxy_args['attachment']; - $attachment = File::getKV($id); - if(empty($attachment)){ - // TRANS: Server error displayed in oEmbed action when attachment not found. - // TRANS: %d is an attachment ID. - $this->serverError(sprintf(_('Attachment %s not found.'),$id), 404); - } - if(empty($attachment->filename) && $file_oembed = File_oembed::getKV('file_id', $attachment->id)){ - // Proxy the existing oembed information - $oembed['type']=$file_oembed->type; - $oembed['provider']=$file_oembed->provider; - $oembed['provider_url']=$file_oembed->provider_url; - $oembed['width']=$file_oembed->width; - $oembed['height']=$file_oembed->height; - $oembed['html']=$file_oembed->html; - $oembed['title']=$file_oembed->title; - $oembed['author_name']=$file_oembed->author_name; - $oembed['author_url']=$file_oembed->author_url; - $oembed['url']=$file_oembed->getUrl(); - }else if(substr($attachment->mimetype,0,strlen('image/'))=='image/'){ - $oembed['type']='photo'; - if ($attachment->filename) { - $filepath = File::path($attachment->filename); - $gis = @getimagesize($filepath); - if ($gis) { - $oembed['width'] = $gis[0]; - $oembed['height'] = $gis[1]; - } else { - // TODO Either throw an error or find a fallback? - } - } - $oembed['url']=$attachment->getUrl(); - try { - $thumb = $attachment->getThumbnail(); - $oembed['thumbnail_url'] = $thumb->getUrl(); - $oembed['thumbnail_width'] = $thumb->width; - $oembed['thumbnail_height'] = $thumb->height; - unset($thumb); - } catch (UnsupportedMediaException $e) { - // No thumbnail data available - } - }else{ - $oembed['type']='link'; - $oembed['url']=common_local_url('attachment', - array('attachment' => $attachment->id)); - } - if($attachment->title) $oembed['title']=$attachment->title; - break; - default: - // TRANS: Server error displayed in oEmbed request when a path is not supported. - // TRANS: %s is a path. - $this->serverError(sprintf(_('"%s" not supported for oembed requests.'),$path), 501); - } - switch($args['format']){ - case 'xml': - $this->init_document('xml'); - $this->elementStart('oembed'); - $this->element('version',null,$oembed['version']); - $this->element('type',null,$oembed['type']); - if($oembed['provider_name']) $this->element('provider_name',null,$oembed['provider_name']); - if($oembed['provider_url']) $this->element('provider_url',null,$oembed['provider_url']); - if($oembed['title']) $this->element('title',null,$oembed['title']); - if($oembed['author_name']) $this->element('author_name',null,$oembed['author_name']); - if($oembed['author_url']) $this->element('author_url',null,$oembed['author_url']); - if($oembed['url']) $this->element('url',null,$oembed['url']); - if($oembed['html']) $this->element('html',null,$oembed['html']); - if($oembed['width']) $this->element('width',null,$oembed['width']); - if($oembed['height']) $this->element('height',null,$oembed['height']); - if($oembed['cache_age']) $this->element('cache_age',null,$oembed['cache_age']); - if($oembed['thumbnail_url']) $this->element('thumbnail_url',null,$oembed['thumbnail_url']); - if($oembed['thumbnail_width']) $this->element('thumbnail_width',null,$oembed['thumbnail_width']); - if($oembed['thumbnail_height']) $this->element('thumbnail_height',null,$oembed['thumbnail_height']); - - $this->elementEnd('oembed'); - $this->end_document('xml'); - break; - case 'json': case '': - $this->init_document('json'); - print(json_encode($oembed)); - $this->end_document('json'); - break; - default: - // TRANS: Error message displaying attachments. %s is a raw MIME type (eg 'image/png') - $this->serverError(sprintf(_('Content type %s not supported.'), $apidata['content-type']), 501); - } - }else{ - // TRANS: Error message displaying attachments. %s is the site's base URL. - $this->serverError(sprintf(_('Only %s URLs over plain HTTP please.'), common_root_url()), 404); - } - } - - function init_document($type) - { - switch ($type) { - case 'xml': - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - break; - case 'json': - header('Content-Type: application/json; charset=utf-8'); - - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print $callback . '('; - } - break; - default: - // TRANS: Server error displayed in oEmbed action when request specifies an unsupported data format. - $this->serverError(_('Not a supported data format.'), 501); - break; - } - } - - function end_document($type='xml') - { - switch ($type) { - case 'xml': - $this->endXML(); - break; - case 'json': - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print ')'; - } - break; - default: - // TRANS: Server error displayed in oEmbed action when request specifies an unsupported data format. - $this->serverError(_('Not a supported data format.'), 501); - break; - } - return; - } - - /** - * Is this action read-only? - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ - function isReadOnly($args) - { - return true; - } -} diff --git a/actions/shownotice.php b/actions/shownotice.php index ff39080eeb..28cb68c121 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -282,21 +282,6 @@ class ShownoticeAction extends Action 'content' => $id->toString())); } - $this->element('link',array('rel'=>'alternate', - 'type'=>'application/json+oembed', - 'href'=>common_local_url( - 'oembed', - array(), - array('format'=>'json','url'=>$this->notice->getUrl())), - 'title'=>'oEmbed'),null); - $this->element('link',array('rel'=>'alternate', - 'type'=>'text/xml+oembed', - 'href'=>common_local_url( - 'oembed', - array(), - array('format'=>'xml','url'=>$this->notice->getUrl())), - 'title'=>'oEmbed'),null); - // Extras to aid in sharing notices to Facebook $avatarUrl = $this->profile->avatarUrl(AVATAR_PROFILE_SIZE); $this->element('meta', array('property' => 'og:image', diff --git a/classes/File.php b/classes/File.php index 29dc7b5492..69aee7fcee 100644 --- a/classes/File.php +++ b/classes/File.php @@ -75,62 +75,26 @@ class File extends Managed_DataObject // I don't know why we have to keep doing this but I'm adding this last check to avoid // uniqueness bugs. - $x = File::getKV('url', $given_url); + $file = File::getKV('url', $given_url); - if (!$x instanceof File) { - $x = new File; - $x->url = $given_url; - if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected']; - if (!empty($redir_data['title'])) $x->title = $redir_data['title']; - if (!empty($redir_data['type'])) $x->mimetype = $redir_data['type']; - if (!empty($redir_data['size'])) $x->size = intval($redir_data['size']); - if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']); - $file_id = $x->insert(); + if (!$file instanceof File) { + $file = new File; + $file->url = $given_url; + if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected']; + if (!empty($redir_data['title'])) $file->title = $redir_data['title']; + if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type']; + if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']); + if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']); + $file_id = $file->insert(); } - $x->saveOembed($redir_data, $given_url); - return $x; - } - - /** - * Save embedding information for this file, if applicable. - * - * Normally this won't need to be called manually, as File::saveNew() - * takes care of it. - * - * @param array $redir_data lookup data eg from File_redirection::where() - * @param string $given_url - * @return boolean success - */ - public function saveOembed(array $redir_data, $given_url) - { - if (isset($redir_data['type']) - && (('text/html' === substr($redir_data['type'], 0, 9) - || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21)))) { - try { - $oembed_data = File_oembed::_getOembed($given_url); - } catch (Exception $e) { - return false; - } - if ($oembed_data === false) { - return false; - } - $fo = File_oembed::getKV('file_id', $this->id); - - if ($fo instanceof File_oembed) { - common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__); - } else { - File_oembed::saveNew($oembed_data, $this->id); - return true; - } - } - return false; + Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url)); + return $file; } /** * Go look at a URL and possibly save data about it if it's new: * - follow redirect chains and store them in file_redirection - * - look up oEmbed data and save it in file_oembed * - if a thumbnail is available, save it in file_thumbnail * - save file record with basic info * - optionally save a file_to_post record @@ -384,7 +348,7 @@ class File extends Managed_DataObject $enclosure->size=$this->size; $enclosure->mimetype=$this->mimetype; - if(! isset($this->filename)){ + if (!isset($this->filename)) { $notEnclosureMimeTypes = array(null,'text/html','application/xhtml+xml'); $mimetype = $this->mimetype; if($mimetype != null){ @@ -394,32 +358,8 @@ class File extends Managed_DataObject if($semicolon){ $mimetype = substr($mimetype,0,$semicolon); } - if(in_array($mimetype,$notEnclosureMimeTypes)){ - // Never treat generic HTML links as an enclosure type! - // But if we have oEmbed info, we'll consider it golden. - $oembed = File_oembed::getKV('file_id',$this->id); - if($oembed && in_array($oembed->type, array('photo', 'video'))){ - $mimetype = strtolower($oembed->mimetype); - $semicolon = strpos($mimetype,';'); - if($semicolon){ - $mimetype = substr($mimetype,0,$semicolon); - } - // @fixme uncertain if this is right. - // we want to expose things like YouTube videos as - // viewable attachments, but don't expose them as - // downloadable enclosures.....? - //if (in_array($mimetype, $notEnclosureMimeTypes)) { - // return false; - //} else { - if($oembed->mimetype) $enclosure->mimetype=$oembed->mimetype; - if($oembed->url) $enclosure->url=$oembed->url; - if($oembed->title) $enclosure->title=$oembed->title; - if($oembed->modified) $enclosure->modified=$oembed->modified; - unset($oembed->size); - //} - } else { - return false; - } + if (in_array($mimetype, $notEnclosureMimeTypes)) { + Event::handle('FileEnclosureMetadata', array($file, &$enclosure)); } } return $enclosure; diff --git a/classes/File_oembed.php b/classes/File_oembed.php deleted file mode 100644 index cb8420e352..0000000000 --- a/classes/File_oembed.php +++ /dev/null @@ -1,140 +0,0 @@ -. - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Table Definition for file_oembed - */ - -class File_oembed extends Managed_DataObject -{ - public $__table = 'file_oembed'; // table name - public $file_id; // int(4) primary_key not_null - public $version; // varchar(20) - public $type; // varchar(20) - public $mimetype; // varchar(50) - public $provider; // varchar(50) - public $provider_url; // varchar(255) - public $width; // int(4) - public $height; // int(4) - public $html; // text() - public $title; // varchar(255) - public $author_name; // varchar(50) - public $author_url; // varchar(255) - public $url; // varchar(255) - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'oEmbed for that URL/file'), - 'version' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed spec. version'), - 'type' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed type: photo, video, link, rich'), - 'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'), - 'provider' => array('type' => 'varchar', 'length' => 50, 'description' => 'name of this oEmbed provider'), - 'provider_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of this oEmbed provider'), - 'width' => array('type' => 'int', 'description' => 'width of oEmbed resource when available'), - 'height' => array('type' => 'int', 'description' => 'height of oEmbed resource when available'), - 'html' => array('type' => 'text', 'description' => 'html representation of this oEmbed resource when applicable'), - 'title' => array('type' => 'varchar', 'length' => 255, 'description' => 'title of oEmbed resource when available'), - 'author_name' => array('type' => 'varchar', 'length' => 50, 'description' => 'author name for this oEmbed resource'), - 'author_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'author URL for this oEmbed resource'), - 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL for this oEmbed resource when applicable (photo, link)'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('file_id'), - 'foreign keys' => array( - 'file_oembed_file_id_fkey' => array('file', array('file_id' => 'id')), - ), - ); - } - - function _getOembed($url) { - $parameters = array( - 'maxwidth' => common_config('thumbnail', 'width'), - 'maxheight' => common_config('thumbnail', 'height'), - ); - try { - return oEmbedHelper::getObject($url, $parameters); - } catch (Exception $e) { - common_log(LOG_INFO, "Error during oembed lookup for $url - " . $e->getMessage()); - return false; - } - } - - public function getUrl() - { - return $this->url; - } - - /** - * Save embedding info for a new file. - * - * @param object $data Services_oEmbed_Object_* - * @param int $file_id - */ - public static function saveNew($data, $file_id) { - $file_oembed = new File_oembed; - $file_oembed->file_id = $file_id; - if (!isset($data->version)) { - common_debug('DEBUGGING oEmbed: data->version undefined in variable $data: '.var_export($data, true)); - } - $file_oembed->version = $data->version; - $file_oembed->type = $data->type; - if (!empty($data->provider_name)) $file_oembed->provider = $data->provider_name; - if (!empty($data->provider)) $file_oembed->provider = $data->provider; - if (!empty($data->provider_url)) $file_oembed->provider_url = $data->provider_url; - if (!empty($data->width)) $file_oembed->width = intval($data->width); - if (!empty($data->height)) $file_oembed->height = intval($data->height); - if (!empty($data->html)) $file_oembed->html = $data->html; - if (!empty($data->title)) $file_oembed->title = $data->title; - if (!empty($data->author_name)) $file_oembed->author_name = $data->author_name; - if (!empty($data->author_url)) $file_oembed->author_url = $data->author_url; - if (!empty($data->url)){ - $file_oembed->url = $data->url; - $given_url = File_redirection::_canonUrl($file_oembed->url); - if (! empty($given_url)){ - $file = File::getKV('url', $given_url); - if ($file instanceof File) { - $file_oembed->mimetype = $file->mimetype; - } else { - $file_redir = File_redirection::getKV('url', $given_url); - if (empty($file_redir)) { - $redir_data = File_redirection::where($given_url); - $file_oembed->mimetype = $redir_data['type']; - } else { - $file_id = $file_redir->file_id; - } - } - } - } - $file_oembed->insert(); - if (!empty($data->thumbnail_url) || ($data->type == 'photo')) { - $ft = File_thumbnail::getKV('file_id', $file_id); - if ($ft instanceof File_thumbnail) { - common_log(LOG_WARNING, "Strangely, a File_thumbnail object exists for new file $file_id", - __FILE__); - } else { - File_thumbnail::saveNew($data, $file_id); - } - } - } -} diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 42021668d9..0bcccc6cff 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -257,9 +257,6 @@ class File_redirection extends Managed_DataObject // Save file and embedding data about it! $file = File::saveNew($redir_data, $long_url); $file_id = $file->id; - if (!empty($redir_data['oembed']['json'])) { - File_oembed::saveNew($redir_data['oembed']['json'], $file_id); - } } else if (is_string($redir_data)) { // The file is a known redirect target. $file = File::getKV('url', $redir_data); diff --git a/db/core.php b/db/core.php index d435c03cd6..61f8228cb5 100644 --- a/db/core.php +++ b/db/core.php @@ -67,7 +67,6 @@ $classes = array('Schema_version', 'Group_inbox', 'Group_member', 'File', - 'File_oembed', 'File_redirection', 'File_thumbnail', 'File_to_post', diff --git a/lib/action.php b/lib/action.php index e219847c70..4d917dc69e 100644 --- a/lib/action.php +++ b/lib/action.php @@ -166,6 +166,11 @@ class Action extends HTMLOutputter // lawsuit return $this->scoped; } + public function getActionName() + { + return $this->action; + } + /** * Show page, a template method. * diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 7f356f9527..66b03182b3 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -134,8 +134,6 @@ class AttachmentListItem extends Widget var $attachment = null; - var $oembed = null; - /** * @param File $attachment the attachment we will display */ @@ -143,21 +141,10 @@ class AttachmentListItem extends Widget { parent::__construct($out); $this->attachment = $attachment; - $this->oembed = File_oembed::getKV('file_id', $this->attachment->id); } function title() { - if (empty($this->attachment->title)) { - if (empty($this->oembed->title)) { - $title = $this->attachment->filename; - } else { - $title = $this->oembed->title; - } - } else { - $title = $this->attachment->title; - } - - return $title; + return $this->attachment->title ?: $this->attachment->filename; } function linkTitle() { @@ -238,40 +225,19 @@ class AttachmentListItem extends Widget class Attachment extends AttachmentListItem { function showLink() { - $this->out->elementStart('div', array('id' => 'attachment_view', - 'class' => 'hentry')); - $this->out->elementStart('div', 'entry-title'); - $this->out->element('a', $this->linkAttr(), $this->linkTitle()); - $this->out->elementEnd('div'); + if (Event::handle('StartShowAttachmentLink', array($this->out, $this->attachment))) { + $this->out->elementStart('div', array('id' => 'attachment_view', + 'class' => 'hentry')); + $this->out->elementStart('div', 'entry-title'); + $this->out->element('a', $this->linkAttr(), $this->linkTitle()); + $this->out->elementEnd('div'); - $this->out->elementStart('div', 'entry-content'); - $this->showRepresentation(); - $this->out->elementEnd('div'); - - if (!empty($this->oembed->author_name) || !empty($this->oembed->provider)) { - $this->out->elementStart('div', array('id' => 'oembed_info', - 'class' => 'entry-content')); - if (!empty($this->oembed->author_name)) { - $this->out->elementStart('div', 'fn vcard author'); - if (empty($this->oembed->author_url)) { - $this->out->text($this->oembed->author_name); - } else { - $this->out->element('a', array('href' => $this->oembed->author_url, - 'class' => 'url'), $this->oembed->author_name); - } - } - if (!empty($this->oembed->provider)) { - $this->out->elementStart('div', 'fn vcard'); - if (empty($this->oembed->provider_url)) { - $this->out->text($this->oembed->provider); - } else { - $this->out->element('a', array('href' => $this->oembed->provider_url, - 'class' => 'url'), $this->oembed->provider); - } - } + $this->out->elementStart('div', 'entry-content'); + $this->showRepresentation(); + $this->out->elementEnd('div'); + Event::handle('EndShowAttachmentLink', array($this->out, $this->attachment)); $this->out->elementEnd('div'); } - $this->out->elementEnd('div'); } function show() { @@ -288,28 +254,7 @@ class Attachment extends AttachmentListItem function showRepresentation() { if (Event::handle('StartShowAttachmentRepresentation', array($this->out, $this->attachment))) { - if (!empty($this->oembed->type)) { - switch ($this->oembed->type) { - case 'rich': - case 'video': - case 'link': - if (!empty($this->oembed->html)) { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - $config = array( - 'safe'=>1, - 'elements'=>'*+object+embed'); - $this->out->raw(htmLawed($this->oembed->html,$config)); - } - break; - - case 'photo': - $this->out->element('img', array('src' => $this->oembed->url, 'width' => $this->oembed->width, 'height' => $this->oembed->height, 'alt' => 'alt')); - break; - - default: - Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment)); - } - } elseif (!empty($this->attachment->mimetype)) { + if (!empty($this->attachment->mimetype)) { switch ($this->attachment->mimetype) { case 'image/gif': case 'image/png': diff --git a/lib/default.php b/lib/default.php index d2270fa6a5..65301bbb45 100644 --- a/lib/default.php +++ b/lib/default.php @@ -303,6 +303,7 @@ $default = 'ClientSideShorten' => array(), 'EmailAuthentication' => array(), 'Event' => array(), + 'Oembed' => array(), 'OpenID' => array(), 'Poll' => array(), 'QnA' => array(), diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php deleted file mode 100644 index fab1131648..0000000000 --- a/lib/oembedhelper.php +++ /dev/null @@ -1,363 +0,0 @@ -. - */ - -if (!defined('STATUSNET')) { - exit(1); -} - - -/** - * Utility class to wrap basic oEmbed lookups. - * - * Blacklisted hosts will use an alternate lookup method: - * - Twitpic - * - * Whitelisted hosts will use known oEmbed API endpoints: - * - Flickr, YFrog - * - * Sites that provide discovery links will use them directly; a bug - * in use of discovery links with query strings is worked around. - * - * Others will fall back to oohembed (unless disabled). - * The API endpoint can be configured or disabled through config - * as 'oohembed'/'endpoint'. - */ -class oEmbedHelper -{ - protected static $apiMap = array( - 'flickr.com' => 'http://www.flickr.com/services/oembed/', - 'yfrog.com' => 'http://www.yfrog.com/api/oembed', - 'youtube.com' => 'http://www.youtube.com/oembed', - 'viddler.com' => 'http://lab.viddler.com/services/oembed/', - 'qik.com' => 'http://qik.com/api/oembed.json', - 'revision3.com' => 'http://revision3.com/api/oembed/', - 'hulu.com' => 'http://www.hulu.com/api/oembed.json', - 'vimeo.com' => 'http://www.vimeo.com/api/oembed.json', - 'my.opera.com' => 'http://my.opera.com/service/oembed', - ); - protected static $functionMap = array( - 'twitpic.com' => 'oEmbedHelper::twitPic', - ); - - /** - * Perform or fake an oEmbed lookup for the given resource. - * - * Some known hosts are whitelisted with API endpoints where we - * know they exist but autodiscovery data isn't available. - * If autodiscovery links are missing and we don't recognize the - * host, we'll pass it to noembed.com's public service which - * will either proxy or fake info on a lot of sites. - * - * A few hosts are blacklisted due to known problems with oohembed, - * in which case we'll look up the info another way and return - * equivalent data. - * - * Throws exceptions on failure. - * - * @param string $url - * @param array $params - * @return object - */ - public static function getObject($url, $params=array()) - { - $host = parse_url($url, PHP_URL_HOST); - if (substr($host, 0, 4) == 'www.') { - $host = substr($host, 4); - } - - common_log(LOG_INFO, 'Checking for oEmbed data for ' . $url); - - // You can fiddle with the order of discovery -- either skipping - // some types or re-ordering them. - - $order = common_config('oembed', 'order'); - - foreach ($order as $method) { - - switch ($method) { - case 'built-in': - common_log(LOG_INFO, 'Considering built-in oEmbed methods...'); - // Blacklist: systems with no oEmbed API of their own, which are - // either missing from or broken on noembed.com's proxy. - // we know how to look data up in another way... - if (array_key_exists($host, self::$functionMap)) { - common_log(LOG_INFO, 'We have a built-in method for ' . $host); - $func = self::$functionMap[$host]; - return call_user_func($func, $url, $params); - } - break; - case 'well-known': - common_log(LOG_INFO, 'Considering well-known oEmbed endpoints...'); - // Whitelist: known API endpoints for sites that don't provide discovery... - if (array_key_exists($host, self::$apiMap)) { - $api = self::$apiMap[$host]; - common_log(LOG_INFO, 'Using well-known endpoint "' . $api . '" for "' . $host . '"'); - break 2; - } - break; - case 'discovery': - try { - common_log(LOG_INFO, 'Trying to discover an oEmbed endpoint using link headers.'); - $api = self::discover($url); - common_log(LOG_INFO, 'Found API endpoint ' . $api . ' for URL ' . $url); - break 2; - } catch (Exception $e) { - common_log(LOG_INFO, 'Could not find an oEmbed endpoint using link headers.'); - // Just ignore it! - } - break; - case 'service': - $api = common_config('oembed', 'endpoint'); - common_log(LOG_INFO, 'Using service API endpoint ' . $api); - break 2; - break; - } - } - - if (empty($api)) { - // TRANS: Server exception thrown in oEmbed action if no API endpoint is available. - throw new ServerException(_('No oEmbed API endpoint available.')); - } - - return self::getObjectFrom($api, $url, $params); - } - - /** - * Perform basic discovery. - * @return string - */ - static function discover($url) - { - // @fixme ideally skip this for non-HTML stuff! - $body = self::http($url); - return self::discoverFromHTML($url, $body); - } - - /** - * Partially ripped from OStatus' FeedDiscovery class. - * - * @param string $url source URL, used to resolve relative links - * @param string $body HTML body text - * @return mixed string with URL or false if no target found - */ - static function discoverFromHTML($url, $body) - { - // DOMDocument::loadHTML may throw warnings on unrecognized elements, - // and notices on unrecognized namespaces. - $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE)); - $dom = new DOMDocument(); - $ok = $dom->loadHTML($body); - error_reporting($old); - - if (!$ok) { - throw new oEmbedHelper_BadHtmlException(); - } - - // Ok... now on to the links! - $feeds = array( - 'application/json+oembed' => false, - ); - - $nodes = $dom->getElementsByTagName('link'); - for ($i = 0; $i < $nodes->length; $i++) { - $node = $nodes->item($i); - if ($node->hasAttributes()) { - $rel = $node->attributes->getNamedItem('rel'); - $type = $node->attributes->getNamedItem('type'); - $href = $node->attributes->getNamedItem('href'); - if ($rel && $type && $href) { - $rel = array_filter(explode(" ", $rel->value)); - $type = trim($type->value); - $href = trim($href->value); - - if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) { - // Save the first feed found of each type... - $feeds[$type] = $href; - } - } - } - } - - // Return the highest-priority feed found - foreach ($feeds as $type => $url) { - if ($url) { - return $url; - } - } - - throw new oEmbedHelper_DiscoveryException(); - } - - /** - * Actually do an oEmbed lookup to a particular API endpoint. - * - * @param string $api oEmbed API endpoint URL - * @param string $url target URL to look up info about - * @param array $params - * @return object - */ - static function getObjectFrom($api, $url, $params=array()) - { - $params['url'] = $url; - $params['format'] = 'json'; - $key=common_config('oembed','apikey'); - if(isset($key)) { - $params['key'] = common_config('oembed','apikey'); - } - $data = self::json($api, $params); - return self::normalize($data); - } - - /** - * Normalize oEmbed format. - * - * @param object $orig - * @return object - */ - static function normalize($orig) - { - $data = clone($orig); - - if (empty($data->type)) { - throw new Exception('Invalid oEmbed data: no type field.'); - } - - if ($data->type == 'image') { - // YFrog does this. - $data->type = 'photo'; - } - - if (isset($data->thumbnail_url)) { - if (!isset($data->thumbnail_width)) { - // !?!?! - $data->thumbnail_width = common_config('thumbnail', 'width'); - $data->thumbnail_height = common_config('thumbnail', 'height'); - } - } - - return $data; - } - - /** - * Using a local function for twitpic lookups, as oohembed's adapter - * doesn't return a valid result: - * http://code.google.com/p/oohembed/issues/detail?id=19 - * - * This code fetches metadata from Twitpic's own API, and attempts - * to guess proper thumbnail size from the original's size. - * - * @todo respect maxwidth and maxheight params - * - * @param string $url - * @param array $params - * @return object - */ - static function twitPic($url, $params=array()) - { - $matches = array(); - if (preg_match('!twitpic\.com/(\w+)!', $url, $matches)) { - $id = $matches[1]; - } else { - throw new Exception("Invalid twitpic URL"); - } - - // Grab metadata from twitpic's API... - // http://dev.twitpic.com/docs/2/media_show - $data = self::json('http://api.twitpic.com/2/media/show.json', - array('id' => $id)); - $oembed = (object)array('type' => 'photo', - 'url' => 'http://twitpic.com/show/full/' . $data->short_id, - 'width' => $data->width, - 'height' => $data->height); - if (!empty($data->message)) { - $oembed->title = $data->message; - } - - // Thumbnail is cropped and scaled to 150x150 box: - // http://dev.twitpic.com/docs/thumbnails/ - $thumbSize = 150; - $oembed->thumbnail_url = 'http://twitpic.com/show/thumb/' . $data->short_id; - $oembed->thumbnail_width = $thumbSize; - $oembed->thumbnail_height = $thumbSize; - - return $oembed; - } - - /** - * Fetch some URL and return JSON data. - * - * @param string $url - * @param array $params query-string params - * @return object - */ - static protected function json($url, $params=array()) - { - $data = self::http($url, $params); - return json_decode($data); - } - - /** - * Hit some web API and return data on success. - * @param string $url - * @param array $params - * @return string - */ - static protected function http($url, $params=array()) - { - $client = HTTPClient::start(); - if ($params) { - $query = http_build_query($params, null, '&'); - if (strpos($url, '?') === false) { - $url .= '?' . $query; - } else { - $url .= '&' . $query; - } - } - $response = $client->get($url); - if ($response->isOk()) { - return $response->getBody(); - } else { - throw new Exception('Bad HTTP response code: ' . $response->getStatus()); - } - } -} - -class oEmbedHelper_Exception extends Exception -{ - public function __construct($message = "", $code = 0, $previous = null) - { - parent::__construct($message, $code); - } -} - -class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception -{ - function __construct($previous=null) - { - return parent::__construct('Bad HTML in discovery data.', 0, $previous); - } -} - -class oEmbedHelper_DiscoveryException extends oEmbedHelper_Exception -{ - function __construct($previous=null) - { - return parent::__construct('No oEmbed discovery data.', 0, $previous); - } -} diff --git a/lib/router.php b/lib/router.php index 71bf4e8e82..3febbbeff4 100644 --- a/lib/router.php +++ b/lib/router.php @@ -168,9 +168,6 @@ class Router $m->connect('main/tagprofile', array('action' => 'tagprofile')); - $m->connect('main/oembed', - array('action' => 'oembed')); - $m->connect('main/xrds', array('action' => 'publicxrds')); diff --git a/plugins/Bookmark/README b/plugins/Bookmark/README new file mode 100644 index 0000000000..e853760544 --- /dev/null +++ b/plugins/Bookmark/README @@ -0,0 +1 @@ +Depends on the oEmbed plugin (Oembed). diff --git a/plugins/LinkPreview/README b/plugins/LinkPreview/README new file mode 100644 index 0000000000..a1b1a87303 --- /dev/null +++ b/plugins/LinkPreview/README @@ -0,0 +1 @@ +Depends on the oEmbed plugin (Oembed) diff --git a/plugins/Oembed/CONFIGURE b/plugins/Oembed/CONFIGURE new file mode 100644 index 0000000000..b8d24f909f --- /dev/null +++ b/plugins/Oembed/CONFIGURE @@ -0,0 +1,18 @@ +So far this is still in $config['site']['oembed']. + +oembed +-------- + +oEmbed endpoint for multimedia attachments (links in posts). Will also +work as 'oohembed' for backwards compatibility. + +endpoint: oohembed endpoint using http://oohembed.com/ software. Defaults to + 'http://oohembed.com/oohembed/'. +order: Array of methods to check for OEmbed data. Methods include 'built-in' + (use a built-in function to simulate oEmbed for some sites), + 'well-known' (use well-known public oEmbed endpoints), + 'discovery' (discover using headers in HTML), 'service' (use + a third-party service, like oohembed or embed.ly. Default is + array('built-in', 'well-known', 'service', 'discovery'). Note that very + few sites implement oEmbed; 'discovery' is going to fail 99% of the + time. diff --git a/plugins/Oembed/OembedPlugin.php b/plugins/Oembed/OembedPlugin.php new file mode 100644 index 0000000000..177fd6ed3e --- /dev/null +++ b/plugins/Oembed/OembedPlugin.php @@ -0,0 +1,187 @@ +ensureTable('file_oembed', File_oembed::schemaDef()); + return true; + } + + public function onRouterInitialized(URLMapper $m) + { + $m->connect('main/oembed', array('action' => 'oembed')); + } + + public function onEndShowHeadElements(Action $action) + { + switch ($action->getActionName()) { + case 'attachment': + $action->element('link',array('rel'=>'alternate', + 'type'=>'application/json+oembed', + 'href'=>common_local_url( + 'oembed', + array(), + array('format'=>'json', 'url'=> + common_local_url('attachment', + array('attachment' => $action->attachment->id)))), + 'title'=>'oEmbed'),null); + $action->element('link',array('rel'=>'alternate', + 'type'=>'text/xml+oembed', + 'href'=>common_local_url( + 'oembed', + array(), + array('format'=>'xml','url'=> + common_local_url('attachment', + array('attachment' => $action->attachment->id)))), + 'title'=>'oEmbed'),null); + break; + case 'shownotice': + $this->element('link',array('rel'=>'alternate', + 'type'=>'application/json+oembed', + 'href'=>common_local_url( + 'oembed', + array(), + array('format'=>'json','url'=>$this->notice->getUrl())), + 'title'=>'oEmbed'),null); + $this->element('link',array('rel'=>'alternate', + 'type'=>'text/xml+oembed', + 'href'=>common_local_url( + 'oembed', + array(), + array('format'=>'xml','url'=>$this->notice->getUrl())), + 'title'=>'oEmbed'),null); + break; + } + + return true; + } + + /** + * Save embedding information for a File, if applicable. + * + * Normally this event is called through File::saveNew() + * + * @param File $file The newly inserted File object. + * @param array $redir_data lookup data eg from File_redirection::where() + * @param string $given_url + * + * @return boolean success + */ + public function onEndFileSaveNew(File $file, array $redir_data, $given_url) + { + $fo = File_oembed::getKV('file_id', $file->id); + if ($fo instanceof File_oembed) { + common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__); + return true; + } + + if (isset($redir_data['oembed']['json']) + && !empty($redir_data['oembed']['json'])) { + File_oembed::saveNew($redir_data['oembed']['json'], $file->id); + } elseif (isset($redir_data['type']) + && (('text/html' === substr($redir_data['type'], 0, 9) + || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21)))) { + + try { + $oembed_data = File_oembed::_getOembed($given_url); + if ($oembed_data === false) { + throw new Exception('Did not get oEmbed data from URL'); + } + } catch (Exception $e) { + return true; + } + + File_oembed::saveNew($oembed_data, $file->id); + } + return true; + } + + public function onEndShowAttachmentLink(HTMLOutputter $out, File $file) + { + $oembed = File_oembed::getKV('file_id', $file->id); + if (empty($oembed->author_name) && empty($oembed->provider)) { + return true; + } + $out->elementStart('div', array('id'=>'oembed_info', 'class'=>'entry-content')); + if (!empty($oembed->author_name)) { + $out->elementStart('div', 'fn vcard author'); + if (empty($oembed->author_url)) { + $out->text($oembed->author_name); + } else { + $out->element('a', array('href' => $oembed->author_url, + 'class' => 'url'), + $oembed->author_name); + } + } + if (!empty($oembed->provider)) { + $out->elementStart('div', 'fn vcard'); + if (empty($oembed->provider_url)) { + $out->text($oembed->provider); + } else { + $out->element('a', array('href' => $oembed->provider_url, + 'class' => 'url'), + $oembed->provider); + } + } + $out->elementEnd('div'); + } + + public function onFileEnclosureMetadata(File $file, &$enclosure) + { + // Never treat generic HTML links as an enclosure type! + // But if we have oEmbed info, we'll consider it golden. + $oembed = File_oembed::getKV('file_id', $file->id); + if (!$oembed instanceof File_oembed || !in_array($oembed->type, array('photo', 'video'))) { + return true; + } + + foreach (array('mimetype', 'url', 'title', 'modified') as $key) { + if (!empty($oembed->{$key})) { + $enclosure->{$key} = $oembed->{$key}; + } + } + return true; + } + + public function onStartShowAttachmentRepresentation(HTMLOutputter $out, File $file) + { + $oembed = File_oembed::getKV('file_id', $file->id); + if (empty($oembed->type)) { + return true; + } + switch ($oembed->type) { + case 'rich': + case 'video': + case 'link': + if (!empty($oembed->html)) { + require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; + $config = array( + 'safe'=>1, + 'elements'=>'*+object+embed'); + $out->raw(htmLawed($oembed->html,$config)); + } + break; + + case 'photo': + $out->element('img', array('src' => $oembed->url, 'width' => $oembed->width, 'height' => $oembed->height, 'alt' => 'alt')); + break; + + default: + Event::handle('ShowUnsupportedAttachmentRepresentation', array($out, $file)); + } + } + + public function onPluginVersion(array &$versions) + { + $versions[] = array('name' => 'Oembed', + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => 'http://gnu.io/', + 'description' => + // TRANS: Plugin description. + _m('Plugin for using and representing Oembed data.')); + return true; + } +} diff --git a/plugins/Oembed/README b/plugins/Oembed/README new file mode 100644 index 0000000000..be8c09f7a6 --- /dev/null +++ b/plugins/Oembed/README @@ -0,0 +1 @@ +It's really called oEmbed. diff --git a/plugins/Oembed/actions/oembed.php b/plugins/Oembed/actions/oembed.php new file mode 100644 index 0000000000..509c3a73ee --- /dev/null +++ b/plugins/Oembed/actions/oembed.php @@ -0,0 +1,236 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Oembed provider implementation + * + * This class handles all /main/oembed(.xml|.json)/ requests. + * + * @category oEmbed + * @package GNUsocial + * @author Craig Andrews + * @author Mikael Nordfeldth + * @copyright 2008 StatusNet, Inc. + * @copyright 2014 Free Software Foundation, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class OembedAction extends Action +{ + protected function handle() + { + parent::handle(); + + $url = $this->trimmed('url'); + if (substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url())) { + $path = substr($url,strlen(common_root_url())); + + $r = Router::get(); + + $proxy_args = $r->map($path); + if (!$proxy_args) { + // TRANS: Server error displayed in oEmbed action when path not found. + // TRANS: %s is a path. + $this->serverError(sprintf(_('"%s" not found.'),$path), 404); + } + + $oembed=array(); + $oembed['version']='1.0'; + $oembed['provider_name']=common_config('site', 'name'); + $oembed['provider_url']=common_root_url(); + + switch ($proxy_args['action']) { + case 'shownotice': + $oembed['type']='link'; + $id = $proxy_args['notice']; + $notice = Notice::getKV($id); + if(empty($notice)){ + // TRANS: Server error displayed in oEmbed action when notice not found. + // TRANS: %s is a notice. + $this->serverError(sprintf(_("Notice %s not found."),$id), 404); + } + $profile = $notice->getProfile(); + if (empty($profile)) { + // TRANS: Server error displayed in oEmbed action when notice has not profile. + $this->serverError(_('Notice has no profile.'), 500); + } + $authorname = $profile->getFancyName(); + // TRANS: oEmbed title. %1$s is the author name, %2$s is the creation date. + $oembed['title'] = sprintf(_('%1$s\'s status on %2$s'), + $authorname, + common_exact_date($notice->created)); + $oembed['author_name']=$authorname; + $oembed['author_url']=$profile->profileurl; + $oembed['url']=$notice->getUrl(); + $oembed['html']=$notice->rendered; + break; + + case 'attachment': + $id = $proxy_args['attachment']; + $attachment = File::getKV($id); + if(empty($attachment)){ + // TRANS: Server error displayed in oEmbed action when attachment not found. + // TRANS: %d is an attachment ID. + $this->serverError(sprintf(_('Attachment %s not found.'),$id), 404); + } + if (empty($attachment->filename) && $file_oembed = File_oembed::getKV('file_id', $attachment->id)) { + // Proxy the existing oembed information + $oembed['type']=$file_oembed->type; + $oembed['provider']=$file_oembed->provider; + $oembed['provider_url']=$file_oembed->provider_url; + $oembed['width']=$file_oembed->width; + $oembed['height']=$file_oembed->height; + $oembed['html']=$file_oembed->html; + $oembed['title']=$file_oembed->title; + $oembed['author_name']=$file_oembed->author_name; + $oembed['author_url']=$file_oembed->author_url; + $oembed['url']=$file_oembed->getUrl(); + } elseif (substr($attachment->mimetype,0,strlen('image/'))==='image/') { + $oembed['type']='photo'; + if ($attachment->filename) { + $filepath = File::path($attachment->filename); + $gis = @getimagesize($filepath); + if ($gis) { + $oembed['width'] = $gis[0]; + $oembed['height'] = $gis[1]; + } else { + // TODO Either throw an error or find a fallback? + } + } + $oembed['url']=$attachment->getUrl(); + try { + $thumb = $attachment->getThumbnail(); + $oembed['thumbnail_url'] = $thumb->getUrl(); + $oembed['thumbnail_width'] = $thumb->width; + $oembed['thumbnail_height'] = $thumb->height; + unset($thumb); + } catch (UnsupportedMediaException $e) { + // No thumbnail data available + } + } else { + $oembed['type']='link'; + $oembed['url']=common_local_url('attachment', + array('attachment' => $attachment->id)); + } + if ($attachment->title) { + $oembed['title']=$attachment->title; + } + break; + default: + // TRANS: Server error displayed in oEmbed request when a path is not supported. + // TRANS: %s is a path. + $this->serverError(sprintf(_('"%s" not supported for oembed requests.'),$path), 501); + } + switch ($this->trimmed('format')) { + case 'xml': + $this->init_document('xml'); + $this->elementStart('oembed'); + foreach(array( + 'version', 'type', 'provider_name', + 'provider_url', 'title', 'author_name', + 'author_url', 'url', 'html', 'width', + 'height', 'cache_age', 'thumbnail_url', + 'thumbnail_width', 'thumbnail_height', + ) as $key) { + if (isset($oembed[$key]) && $oembed[$key]!='') { + $this->element($key, null, $oembed[$key]); + } + } + $this->elementEnd('oembed'); + $this->end_document('xml'); + break; + + case 'json': + case null: + $this->init_document('json'); + $this->raw(json_encode($oembed)); + $this->end_document('json'); + break; + default: + // TRANS: Error message displaying attachments. %s is a raw MIME type (eg 'image/png') + $this->serverError(sprintf(_('Content type %s not supported.'), $apidata['content-type']), 501); + } + } else { + // TRANS: Error message displaying attachments. %s is the site's base URL. + $this->serverError(sprintf(_('Only %s URLs over plain HTTP please.'), common_root_url()), 404); + } + } + + public function init_document($type) + { + switch ($type) { + case 'xml': + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + break; + case 'json': + header('Content-Type: application/json; charset=utf-8'); + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print $callback . '('; + } + break; + default: + // TRANS: Server error displayed in oEmbed action when request specifies an unsupported data format. + $this->serverError(_('Not a supported data format.'), 501); + break; + } + } + + public function end_document($type) + { + switch ($type) { + case 'xml': + $this->endXML(); + break; + case 'json': + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print ')'; + } + break; + default: + // TRANS: Server error displayed in oEmbed action when request specifies an unsupported data format. + $this->serverError(_('Not a supported data format.'), 501); + break; + } + return; + } + + /** + * Is this action read-only? + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + function isReadOnly($args) + { + return true; + } +} diff --git a/plugins/Oembed/classes/File_oembed.php b/plugins/Oembed/classes/File_oembed.php new file mode 100644 index 0000000000..cb8420e352 --- /dev/null +++ b/plugins/Oembed/classes/File_oembed.php @@ -0,0 +1,140 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Table Definition for file_oembed + */ + +class File_oembed extends Managed_DataObject +{ + public $__table = 'file_oembed'; // table name + public $file_id; // int(4) primary_key not_null + public $version; // varchar(20) + public $type; // varchar(20) + public $mimetype; // varchar(50) + public $provider; // varchar(50) + public $provider_url; // varchar(255) + public $width; // int(4) + public $height; // int(4) + public $html; // text() + public $title; // varchar(255) + public $author_name; // varchar(50) + public $author_url; // varchar(255) + public $url; // varchar(255) + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + + public static function schemaDef() + { + return array( + 'fields' => array( + 'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'oEmbed for that URL/file'), + 'version' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed spec. version'), + 'type' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed type: photo, video, link, rich'), + 'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'), + 'provider' => array('type' => 'varchar', 'length' => 50, 'description' => 'name of this oEmbed provider'), + 'provider_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of this oEmbed provider'), + 'width' => array('type' => 'int', 'description' => 'width of oEmbed resource when available'), + 'height' => array('type' => 'int', 'description' => 'height of oEmbed resource when available'), + 'html' => array('type' => 'text', 'description' => 'html representation of this oEmbed resource when applicable'), + 'title' => array('type' => 'varchar', 'length' => 255, 'description' => 'title of oEmbed resource when available'), + 'author_name' => array('type' => 'varchar', 'length' => 50, 'description' => 'author name for this oEmbed resource'), + 'author_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'author URL for this oEmbed resource'), + 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL for this oEmbed resource when applicable (photo, link)'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('file_id'), + 'foreign keys' => array( + 'file_oembed_file_id_fkey' => array('file', array('file_id' => 'id')), + ), + ); + } + + function _getOembed($url) { + $parameters = array( + 'maxwidth' => common_config('thumbnail', 'width'), + 'maxheight' => common_config('thumbnail', 'height'), + ); + try { + return oEmbedHelper::getObject($url, $parameters); + } catch (Exception $e) { + common_log(LOG_INFO, "Error during oembed lookup for $url - " . $e->getMessage()); + return false; + } + } + + public function getUrl() + { + return $this->url; + } + + /** + * Save embedding info for a new file. + * + * @param object $data Services_oEmbed_Object_* + * @param int $file_id + */ + public static function saveNew($data, $file_id) { + $file_oembed = new File_oembed; + $file_oembed->file_id = $file_id; + if (!isset($data->version)) { + common_debug('DEBUGGING oEmbed: data->version undefined in variable $data: '.var_export($data, true)); + } + $file_oembed->version = $data->version; + $file_oembed->type = $data->type; + if (!empty($data->provider_name)) $file_oembed->provider = $data->provider_name; + if (!empty($data->provider)) $file_oembed->provider = $data->provider; + if (!empty($data->provider_url)) $file_oembed->provider_url = $data->provider_url; + if (!empty($data->width)) $file_oembed->width = intval($data->width); + if (!empty($data->height)) $file_oembed->height = intval($data->height); + if (!empty($data->html)) $file_oembed->html = $data->html; + if (!empty($data->title)) $file_oembed->title = $data->title; + if (!empty($data->author_name)) $file_oembed->author_name = $data->author_name; + if (!empty($data->author_url)) $file_oembed->author_url = $data->author_url; + if (!empty($data->url)){ + $file_oembed->url = $data->url; + $given_url = File_redirection::_canonUrl($file_oembed->url); + if (! empty($given_url)){ + $file = File::getKV('url', $given_url); + if ($file instanceof File) { + $file_oembed->mimetype = $file->mimetype; + } else { + $file_redir = File_redirection::getKV('url', $given_url); + if (empty($file_redir)) { + $redir_data = File_redirection::where($given_url); + $file_oembed->mimetype = $redir_data['type']; + } else { + $file_id = $file_redir->file_id; + } + } + } + } + $file_oembed->insert(); + if (!empty($data->thumbnail_url) || ($data->type == 'photo')) { + $ft = File_thumbnail::getKV('file_id', $file_id); + if ($ft instanceof File_thumbnail) { + common_log(LOG_WARNING, "Strangely, a File_thumbnail object exists for new file $file_id", + __FILE__); + } else { + File_thumbnail::saveNew($data, $file_id); + } + } + } +} diff --git a/plugins/Oembed/lib/oembedhelper.php b/plugins/Oembed/lib/oembedhelper.php new file mode 100644 index 0000000000..fab1131648 --- /dev/null +++ b/plugins/Oembed/lib/oembedhelper.php @@ -0,0 +1,363 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + + +/** + * Utility class to wrap basic oEmbed lookups. + * + * Blacklisted hosts will use an alternate lookup method: + * - Twitpic + * + * Whitelisted hosts will use known oEmbed API endpoints: + * - Flickr, YFrog + * + * Sites that provide discovery links will use them directly; a bug + * in use of discovery links with query strings is worked around. + * + * Others will fall back to oohembed (unless disabled). + * The API endpoint can be configured or disabled through config + * as 'oohembed'/'endpoint'. + */ +class oEmbedHelper +{ + protected static $apiMap = array( + 'flickr.com' => 'http://www.flickr.com/services/oembed/', + 'yfrog.com' => 'http://www.yfrog.com/api/oembed', + 'youtube.com' => 'http://www.youtube.com/oembed', + 'viddler.com' => 'http://lab.viddler.com/services/oembed/', + 'qik.com' => 'http://qik.com/api/oembed.json', + 'revision3.com' => 'http://revision3.com/api/oembed/', + 'hulu.com' => 'http://www.hulu.com/api/oembed.json', + 'vimeo.com' => 'http://www.vimeo.com/api/oembed.json', + 'my.opera.com' => 'http://my.opera.com/service/oembed', + ); + protected static $functionMap = array( + 'twitpic.com' => 'oEmbedHelper::twitPic', + ); + + /** + * Perform or fake an oEmbed lookup for the given resource. + * + * Some known hosts are whitelisted with API endpoints where we + * know they exist but autodiscovery data isn't available. + * If autodiscovery links are missing and we don't recognize the + * host, we'll pass it to noembed.com's public service which + * will either proxy or fake info on a lot of sites. + * + * A few hosts are blacklisted due to known problems with oohembed, + * in which case we'll look up the info another way and return + * equivalent data. + * + * Throws exceptions on failure. + * + * @param string $url + * @param array $params + * @return object + */ + public static function getObject($url, $params=array()) + { + $host = parse_url($url, PHP_URL_HOST); + if (substr($host, 0, 4) == 'www.') { + $host = substr($host, 4); + } + + common_log(LOG_INFO, 'Checking for oEmbed data for ' . $url); + + // You can fiddle with the order of discovery -- either skipping + // some types or re-ordering them. + + $order = common_config('oembed', 'order'); + + foreach ($order as $method) { + + switch ($method) { + case 'built-in': + common_log(LOG_INFO, 'Considering built-in oEmbed methods...'); + // Blacklist: systems with no oEmbed API of their own, which are + // either missing from or broken on noembed.com's proxy. + // we know how to look data up in another way... + if (array_key_exists($host, self::$functionMap)) { + common_log(LOG_INFO, 'We have a built-in method for ' . $host); + $func = self::$functionMap[$host]; + return call_user_func($func, $url, $params); + } + break; + case 'well-known': + common_log(LOG_INFO, 'Considering well-known oEmbed endpoints...'); + // Whitelist: known API endpoints for sites that don't provide discovery... + if (array_key_exists($host, self::$apiMap)) { + $api = self::$apiMap[$host]; + common_log(LOG_INFO, 'Using well-known endpoint "' . $api . '" for "' . $host . '"'); + break 2; + } + break; + case 'discovery': + try { + common_log(LOG_INFO, 'Trying to discover an oEmbed endpoint using link headers.'); + $api = self::discover($url); + common_log(LOG_INFO, 'Found API endpoint ' . $api . ' for URL ' . $url); + break 2; + } catch (Exception $e) { + common_log(LOG_INFO, 'Could not find an oEmbed endpoint using link headers.'); + // Just ignore it! + } + break; + case 'service': + $api = common_config('oembed', 'endpoint'); + common_log(LOG_INFO, 'Using service API endpoint ' . $api); + break 2; + break; + } + } + + if (empty($api)) { + // TRANS: Server exception thrown in oEmbed action if no API endpoint is available. + throw new ServerException(_('No oEmbed API endpoint available.')); + } + + return self::getObjectFrom($api, $url, $params); + } + + /** + * Perform basic discovery. + * @return string + */ + static function discover($url) + { + // @fixme ideally skip this for non-HTML stuff! + $body = self::http($url); + return self::discoverFromHTML($url, $body); + } + + /** + * Partially ripped from OStatus' FeedDiscovery class. + * + * @param string $url source URL, used to resolve relative links + * @param string $body HTML body text + * @return mixed string with URL or false if no target found + */ + static function discoverFromHTML($url, $body) + { + // DOMDocument::loadHTML may throw warnings on unrecognized elements, + // and notices on unrecognized namespaces. + $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE)); + $dom = new DOMDocument(); + $ok = $dom->loadHTML($body); + error_reporting($old); + + if (!$ok) { + throw new oEmbedHelper_BadHtmlException(); + } + + // Ok... now on to the links! + $feeds = array( + 'application/json+oembed' => false, + ); + + $nodes = $dom->getElementsByTagName('link'); + for ($i = 0; $i < $nodes->length; $i++) { + $node = $nodes->item($i); + if ($node->hasAttributes()) { + $rel = $node->attributes->getNamedItem('rel'); + $type = $node->attributes->getNamedItem('type'); + $href = $node->attributes->getNamedItem('href'); + if ($rel && $type && $href) { + $rel = array_filter(explode(" ", $rel->value)); + $type = trim($type->value); + $href = trim($href->value); + + if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) { + // Save the first feed found of each type... + $feeds[$type] = $href; + } + } + } + } + + // Return the highest-priority feed found + foreach ($feeds as $type => $url) { + if ($url) { + return $url; + } + } + + throw new oEmbedHelper_DiscoveryException(); + } + + /** + * Actually do an oEmbed lookup to a particular API endpoint. + * + * @param string $api oEmbed API endpoint URL + * @param string $url target URL to look up info about + * @param array $params + * @return object + */ + static function getObjectFrom($api, $url, $params=array()) + { + $params['url'] = $url; + $params['format'] = 'json'; + $key=common_config('oembed','apikey'); + if(isset($key)) { + $params['key'] = common_config('oembed','apikey'); + } + $data = self::json($api, $params); + return self::normalize($data); + } + + /** + * Normalize oEmbed format. + * + * @param object $orig + * @return object + */ + static function normalize($orig) + { + $data = clone($orig); + + if (empty($data->type)) { + throw new Exception('Invalid oEmbed data: no type field.'); + } + + if ($data->type == 'image') { + // YFrog does this. + $data->type = 'photo'; + } + + if (isset($data->thumbnail_url)) { + if (!isset($data->thumbnail_width)) { + // !?!?! + $data->thumbnail_width = common_config('thumbnail', 'width'); + $data->thumbnail_height = common_config('thumbnail', 'height'); + } + } + + return $data; + } + + /** + * Using a local function for twitpic lookups, as oohembed's adapter + * doesn't return a valid result: + * http://code.google.com/p/oohembed/issues/detail?id=19 + * + * This code fetches metadata from Twitpic's own API, and attempts + * to guess proper thumbnail size from the original's size. + * + * @todo respect maxwidth and maxheight params + * + * @param string $url + * @param array $params + * @return object + */ + static function twitPic($url, $params=array()) + { + $matches = array(); + if (preg_match('!twitpic\.com/(\w+)!', $url, $matches)) { + $id = $matches[1]; + } else { + throw new Exception("Invalid twitpic URL"); + } + + // Grab metadata from twitpic's API... + // http://dev.twitpic.com/docs/2/media_show + $data = self::json('http://api.twitpic.com/2/media/show.json', + array('id' => $id)); + $oembed = (object)array('type' => 'photo', + 'url' => 'http://twitpic.com/show/full/' . $data->short_id, + 'width' => $data->width, + 'height' => $data->height); + if (!empty($data->message)) { + $oembed->title = $data->message; + } + + // Thumbnail is cropped and scaled to 150x150 box: + // http://dev.twitpic.com/docs/thumbnails/ + $thumbSize = 150; + $oembed->thumbnail_url = 'http://twitpic.com/show/thumb/' . $data->short_id; + $oembed->thumbnail_width = $thumbSize; + $oembed->thumbnail_height = $thumbSize; + + return $oembed; + } + + /** + * Fetch some URL and return JSON data. + * + * @param string $url + * @param array $params query-string params + * @return object + */ + static protected function json($url, $params=array()) + { + $data = self::http($url, $params); + return json_decode($data); + } + + /** + * Hit some web API and return data on success. + * @param string $url + * @param array $params + * @return string + */ + static protected function http($url, $params=array()) + { + $client = HTTPClient::start(); + if ($params) { + $query = http_build_query($params, null, '&'); + if (strpos($url, '?') === false) { + $url .= '?' . $query; + } else { + $url .= '&' . $query; + } + } + $response = $client->get($url); + if ($response->isOk()) { + return $response->getBody(); + } else { + throw new Exception('Bad HTTP response code: ' . $response->getStatus()); + } + } +} + +class oEmbedHelper_Exception extends Exception +{ + public function __construct($message = "", $code = 0, $previous = null) + { + parent::__construct($message, $code); + } +} + +class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception +{ + function __construct($previous=null) + { + return parent::__construct('Bad HTML in discovery data.', 0, $previous); + } +} + +class oEmbedHelper_DiscoveryException extends oEmbedHelper_Exception +{ + function __construct($previous=null) + { + return parent::__construct('No oEmbed discovery data.', 0, $previous); + } +} diff --git a/plugins/Oembed/scripts/fixup_files.php b/plugins/Oembed/scripts/fixup_files.php new file mode 100755 index 0000000000..50ddc8a994 --- /dev/null +++ b/plugins/Oembed/scripts/fixup_files.php @@ -0,0 +1,74 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('dry-run'); + +$helptext = <<title = 'h'; +$f->mimetype = 'h'; +$f->size = 0; +$f->protected = 0; +$f->find(); +echo "Found $f->N bad items:\n"; + +while ($f->fetch()) { + echo "$f->id $f->url"; + + $data = File_redirection::lookupWhere($f->url); + if ($dry) { + if (is_array($data)) { + echo " (unchanged)\n"; + } else { + echo " (unchanged, but embedding lookup failed)\n"; + } + } else { + // NULL out the mime/title/size/protected fields + $sql = sprintf("UPDATE file " . + "SET mimetype=null,title=null,size=null,protected=null " . + "WHERE id=%d", + $f->id); + $f->query($sql); + $f->decache(); + + if (is_array($data)) { + Event::handle('EndFileSaveNew', array($f, $data, $f->url)); + echo " (ok)\n"; + } else { + echo " (ok, but embedding lookup failed)\n"; + } + } +} + +echo "done.\n"; + diff --git a/plugins/Oembed/tests/oEmbedTest.php b/plugins/Oembed/tests/oEmbedTest.php new file mode 100644 index 0000000000..49e070cb6d --- /dev/null +++ b/plugins/Oembed/tests/oEmbedTest.php @@ -0,0 +1,144 @@ +old_ohembed = common_config('ohembed', 'endpoint'); + } + + public function tearDown() + { + $GLOBALS['config']['oembed']['endpoint'] = $this->old_ohembed; + } + + /** + * Test with ohembed DISABLED. + * + * @dataProvider discoverableSources + */ + public function testoEmbed($url, $expectedType) + { + $GLOBALS['config']['oembed']['endpoint'] = false; + $this->_doTest($url, $expectedType); + } + + /** + * Test with oohembed ENABLED. + * + * @dataProvider fallbackSources + */ + public function testnoEmbed($url, $expectedType) + { + $GLOBALS['config']['oembed']['endpoint'] = $this->_endpoint(); + $this->_doTest($url, $expectedType); + } + + /** + * Get default oembed endpoint. + * + * @return string + */ + function _endpoint() + { + $default = array(); + $_server = 'localhost'; $_path = ''; + require INSTALLDIR . '/lib/default.php'; + return $default['oembed']['endpoint']; + } + + /** + * Actually run an individual test. + * + * @param string $url + * @param string $expectedType + */ + function _doTest($url, $expectedType) + { + try { + $data = oEmbedHelper::getObject($url); + $this->assertEquals($expectedType, $data->type); + if ($data->type == 'photo') { + $this->assertTrue(!empty($data->url), 'Photo must have a URL.'); + $this->assertTrue(!empty($data->width), 'Photo must have a width.'); + $this->assertTrue(!empty($data->height), 'Photo must have a height.'); + } else if ($data->type == 'video') { + $this->assertTrue(!empty($data->html), 'Video must have embedding HTML.'); + $this->assertTrue(!empty($data->thumbnail_url), 'Video should have a thumbnail.'); + } + if (!empty($data->thumbnail_url)) { + $this->assertTrue(!empty($data->thumbnail_width), 'Thumbnail must list a width.'); + $this->assertTrue(!empty($data->thumbnail_height), 'Thumbnail must list a height.'); + } + } catch (Exception $e) { + if ($expectedType == 'none') { + $this->assertEquals($expectedType, 'none', 'Should not have data for this URL.'); + } else { + throw $e; + } + } + } + + /** + * Sample oEmbed targets for sites we know ourselves... + * @return array + */ + static public function knownSources() + { + $sources = array( + array('http://www.flickr.com/photos/brionv/5172500179/', 'photo'), + array('http://yfrog.com/fy42747177j', 'photo'), + array('http://twitpic.com/36adw6', 'photo'), + ); + return $sources; + } + + /** + * Sample oEmbed targets that can be found via discovery. + * Includes also knownSources() output. + * + * @return array + */ + static public function discoverableSources() + { + $sources = array( + + array('http://www.youtube.com/watch?v=eUgLR232Cnw', 'video'), + array('http://vimeo.com/9283184', 'video'), + + // Will fail discovery: + array('http://leuksman.com/log/2010/10/29/statusnet-0-9-6-release/', 'none'), + ); + return array_merge(self::knownSources(), $sources); + } + + /** + * Sample oEmbed targets that can be found via noembed.com. + * Includes also discoverableSources() output. + * + * @return array + */ + static public function fallbackSources() + { + + $sources = array( + array('https://github.com/git/git/commit/85e9c7e1d42849c5c3084a9da748608468310c0e', 'Github Commit'), // @fixme in future there may be a native provider -- will change to 'photo' + ); + + $sources = array(); + + return array_merge(self::discoverableSources(), $sources); + } +} diff --git a/scripts/fixup_files.php b/scripts/fixup_files.php deleted file mode 100755 index 18feaf2218..0000000000 --- a/scripts/fixup_files.php +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$longoptions = array('dry-run'); - -$helptext = <<title = 'h'; -$f->mimetype = 'h'; -$f->size = 0; -$f->protected = 0; -$f->find(); -echo "Found $f->N bad items:\n"; - -while ($f->fetch()) { - echo "$f->id $f->url"; - - $data = File_redirection::lookupWhere($f->url); - if ($dry) { - if (is_array($data)) { - echo " (unchanged)\n"; - } else { - echo " (unchanged, but embedding lookup failed)\n"; - } - } else { - // NULL out the mime/title/size/protected fields - $sql = sprintf("UPDATE file " . - "SET mimetype=null,title=null,size=null,protected=null " . - "WHERE id=%d", - $f->id); - $f->query($sql); - $f->decache(); - - if (is_array($data)) { - if ($f->saveOembed($data, $f->url)) { - echo " (ok)\n"; - } else { - echo " (ok, no embedding data)\n"; - } - } else { - echo " (ok, but embedding lookup failed)\n"; - } - } -} - -echo "done.\n"; - diff --git a/tests/oEmbedTest.php b/tests/oEmbedTest.php deleted file mode 100644 index 50f3fd2919..0000000000 --- a/tests/oEmbedTest.php +++ /dev/null @@ -1,144 +0,0 @@ -old_ohembed = common_config('ohembed', 'endpoint'); - } - - public function tearDown() - { - $GLOBALS['config']['oembed']['endpoint'] = $this->old_ohembed; - } - - /** - * Test with ohembed DISABLED. - * - * @dataProvider discoverableSources - */ - public function testoEmbed($url, $expectedType) - { - $GLOBALS['config']['oembed']['endpoint'] = false; - $this->_doTest($url, $expectedType); - } - - /** - * Test with oohembed ENABLED. - * - * @dataProvider fallbackSources - */ - public function testnoEmbed($url, $expectedType) - { - $GLOBALS['config']['oembed']['endpoint'] = $this->_endpoint(); - $this->_doTest($url, $expectedType); - } - - /** - * Get default oembed endpoint. - * - * @return string - */ - function _endpoint() - { - $default = array(); - $_server = 'localhost'; $_path = ''; - require INSTALLDIR . '/lib/default.php'; - return $default['oembed']['endpoint']; - } - - /** - * Actually run an individual test. - * - * @param string $url - * @param string $expectedType - */ - function _doTest($url, $expectedType) - { - try { - $data = oEmbedHelper::getObject($url); - $this->assertEquals($expectedType, $data->type); - if ($data->type == 'photo') { - $this->assertTrue(!empty($data->url), 'Photo must have a URL.'); - $this->assertTrue(!empty($data->width), 'Photo must have a width.'); - $this->assertTrue(!empty($data->height), 'Photo must have a height.'); - } else if ($data->type == 'video') { - $this->assertTrue(!empty($data->html), 'Video must have embedding HTML.'); - $this->assertTrue(!empty($data->thumbnail_url), 'Video should have a thumbnail.'); - } - if (!empty($data->thumbnail_url)) { - $this->assertTrue(!empty($data->thumbnail_width), 'Thumbnail must list a width.'); - $this->assertTrue(!empty($data->thumbnail_height), 'Thumbnail must list a height.'); - } - } catch (Exception $e) { - if ($expectedType == 'none') { - $this->assertEquals($expectedType, 'none', 'Should not have data for this URL.'); - } else { - throw $e; - } - } - } - - /** - * Sample oEmbed targets for sites we know ourselves... - * @return array - */ - static public function knownSources() - { - $sources = array( - array('http://www.flickr.com/photos/brionv/5172500179/', 'photo'), - array('http://yfrog.com/fy42747177j', 'photo'), - array('http://twitpic.com/36adw6', 'photo'), - ); - return $sources; - } - - /** - * Sample oEmbed targets that can be found via discovery. - * Includes also knownSources() output. - * - * @return array - */ - static public function discoverableSources() - { - $sources = array( - - array('http://www.youtube.com/watch?v=eUgLR232Cnw', 'video'), - array('http://vimeo.com/9283184', 'video'), - - // Will fail discovery: - array('http://leuksman.com/log/2010/10/29/statusnet-0-9-6-release/', 'none'), - ); - return array_merge(self::knownSources(), $sources); - } - - /** - * Sample oEmbed targets that can be found via noembed.com. - * Includes also discoverableSources() output. - * - * @return array - */ - static public function fallbackSources() - { - - $sources = array( - array('https://github.com/git/git/commit/85e9c7e1d42849c5c3084a9da748608468310c0e', 'Github Commit'), // @fixme in future there may be a native provider -- will change to 'photo' - ); - - $sources = array(); - - return array_merge(self::discoverableSources(), $sources); - } -}