From 883f7a6c0b1464f6723e51bf99d06641a612f968 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 13:27:54 -0800 Subject: Avoid marking files as attachments that are not locally uploaded, unless they're really oembedable. HTML-y things now excluded properly. --- classes/File.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'classes') diff --git a/classes/File.php b/classes/File.php index 16e00024a..d71403e64 100644 --- a/classes/File.php +++ b/classes/File.php @@ -352,6 +352,9 @@ class File extends Memcached_DataObject $mimetype = substr($mimetype,0,$semicolon); } if(in_array($mimetype,$notEnclosureMimeTypes)){ + // Never treat HTML as an enclosure type! + return false; + } else { $oembed = File_oembed::staticGet('file_id',$this->id); if($oembed){ $mimetype = strtolower($oembed->mimetype); -- cgit v1.2.3-54-g00ecf From a2994e3aa232106f39a6c36c9176608467b8822e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 15:50:06 -0800 Subject: Testing... using photo info for temp thumbnails --- classes/File.php | 7 +++---- lib/attachmentlist.php | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'classes') diff --git a/classes/File.php b/classes/File.php index d71403e64..e3b922d13 100644 --- a/classes/File.php +++ b/classes/File.php @@ -352,11 +352,10 @@ class File extends Memcached_DataObject $mimetype = substr($mimetype,0,$semicolon); } if(in_array($mimetype,$notEnclosureMimeTypes)){ - // Never treat HTML as an enclosure type! - return false; - } else { + // Never treat generic HTML links as an enclosure type! + // But if we have oEmbed info, we'll consider it golden. $oembed = File_oembed::staticGet('file_id',$this->id); - if($oembed){ + if($oembed && in_array($oembed->type, array('photo', 'video'))){ $mimetype = strtolower($oembed->mimetype); $semicolon = strpos($mimetype,';'); if($semicolon){ diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index f29d32ada..8e6ad038a 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -212,19 +212,30 @@ class AttachmentListItem extends Widget } } + /** + * Pull a thumbnail image reference for the given file. + * In order we check: + * 1) file_thumbnail table (thumbnails found via oEmbed) + * 2) image URL from direct dereference or oEmbed 'photo' type URL + * 3) ??? + * + * @return mixed object with (url, width, height) properties, or false + */ function getThumbInfo() { $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id); if ($thumbnail) { return $thumbnail; - } else { - switch ($this->attachment->mimetype) { + } + $enc = $this->attachment->getEnclosure(); + if ($enc) { + switch ($enc->mimetype) { case 'image/gif': case 'image/png': case 'image/jpg': case 'image/jpeg': $thumb = (object)array(); - $thumb->url = $this->attachment->url; + $thumb->url = $enc->url; $thumb->width = 100; $thumb->height = 75; // @fixme return $thumb; -- cgit v1.2.3-54-g00ecf From c36fecb79431218016615cde45215337d67fee67 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:20:04 -0800 Subject: Save a thumbnail image when uploading an image file into the file attachments system. Currently hardcoded to 100x75, needs aspect-sensitivity etc. --- classes/File_thumbnail.php | 30 ++++++++++++++++++++++++++---- lib/mediafile.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) (limited to 'classes') diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php index edae8ac21..d371b9e8a 100644 --- a/classes/File_thumbnail.php +++ b/classes/File_thumbnail.php @@ -48,12 +48,34 @@ class File_thumbnail extends Memcached_DataObject return array(false, false, false); } - function saveNew($data, $file_id) { + /** + * Save oEmbed-provided thumbnail data + * + * @param object $data + * @param int $file_id + */ + public static function saveNew($data, $file_id) { + self::saveThumbnail($file_id, + $data->thumbnail_url, + $data->thumbnail_width, + $data->thumbnail_height); + } + + /** + * Save a thumbnail record for the referenced file record. + * + * @param int $file_id + * @param string $url + * @param int $width + * @param int $height + */ + static function saveThumbnail($file_id, $url, $width, $height) + { $tn = new File_thumbnail; $tn->file_id = $file_id; - $tn->url = $data->thumbnail_url; - $tn->width = intval($data->thumbnail_width); - $tn->height = intval($data->thumbnail_height); + $tn->url = $url; + $tn->width = intval($width); + $tn->height = intval($height); $tn->insert(); } } diff --git a/lib/mediafile.php b/lib/mediafile.php index aad3575d7..2c04b4650 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -48,11 +48,14 @@ class MediaFile { if ($user == null) { $this->user = common_current_user(); + } else { + $this->user = $user; } $this->filename = $filename; $this->mimetype = $mimetype; $this->fileRecord = $this->storeFile(); + $this->thumbnailRecord = $this->storeThumbnail(); $this->fileurl = common_local_url('attachment', array('attachment' => $this->fileRecord->id)); @@ -102,6 +105,38 @@ class MediaFile return $file; } + /** + * Generate and store a thumbnail image for the uploaded file, if applicable. + * + * @return File_thumbnail or null + */ + function storeThumbnail() + { + if (substr($this->mimetype, 0, strlen('image/')) != 'image/') { + // @fixme video thumbs would be nice! + return null; + } + try { + $image = new ImageFile($this->fileRecord->id, + File::path($this->filename)); + } catch (Exception $e) { + // Unsupported image type. + return null; + } + + $outname = File::filename($this->user->getProfile(), 'thumb-' . $this->filename, $this->mimetype); + $outpath = File::path($outname); + + $width = 100; + $height = 75; + + $image->resizeTo($outpath, $width, $height); + File_thumbnail::saveThumbnail($this->fileRecord->id, + File::url($outname), + $width, + $height); + } + function rememberFile($file, $short) { $this->maybeAddRedir($file->id, $short); -- cgit v1.2.3-54-g00ecf From 6d7f02ff31c6c929223030b051541b1bf103f3a8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:22:01 -0800 Subject: Pass file attachment thumbnails along with oEmbed data. --- actions/oembed.php | 6 ++++++ classes/File.php | 10 ++++++++++ 2 files changed, 16 insertions(+) (limited to 'classes') diff --git a/actions/oembed.php b/actions/oembed.php index da3aa0c71..11d814583 100644 --- a/actions/oembed.php +++ b/actions/oembed.php @@ -112,6 +112,12 @@ class OembedAction extends Action //$oembed['width']= //$oembed['height']= $oembed['url']=$attachment->url; + $thumb = $attachment->getThumbnail(); + if ($thumb) { + $oembed['thumbnail_url'] = $thumb->url; + $oembed['thumbnail_width'] = $thumb->width; + $oembed['thumbnail_height'] = $thumb->height; + } }else{ $oembed['type']='link'; $oembed['url']=common_local_url('attachment', diff --git a/classes/File.php b/classes/File.php index e3b922d13..56bc73ab2 100644 --- a/classes/File.php +++ b/classes/File.php @@ -384,4 +384,14 @@ class File extends Memcached_DataObject $enclosure = $this->getEnclosure(); return !empty($enclosure); } + + /** + * Get the attachment's thumbnail record, if any. + * + * @return File_thumbnail + */ + function getThumbnail() + { + return File_thumbnail::staticGet('file_id', $this->id); + } } -- cgit v1.2.3-54-g00ecf From 694448e0aa81edb7b010f102ee9ee0e6961f6f7c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:36:02 -0800 Subject: Add attachments 'thumb_width' and 'thumb_height' settings for inline thumbs, defaulting to 100x75. This is used as the max thumb width/height for oEmbed requests (replacing the old default of 500x400 which was more suitable for the lightbox). --- classes/File_oembed.php | 6 +++--- lib/default.php | 2 ++ lib/mediafile.php | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'classes') diff --git a/classes/File_oembed.php b/classes/File_oembed.php index 4813d5dda..a5540ecfe 100644 --- a/classes/File_oembed.php +++ b/classes/File_oembed.php @@ -58,11 +58,11 @@ class File_oembed extends Memcached_DataObject return array(false, false, false); } - function _getOembed($url, $maxwidth = 500, $maxheight = 400) { + function _getOembed($url) { require_once INSTALLDIR.'/extlib/Services/oEmbed.php'; $parameters = array( - 'maxwidth'=>$maxwidth, - 'maxheight'=>$maxheight, + 'maxwidth' => common_config('attachments', 'thumb_width'), + 'maxheight' => common_config('attachments', 'thumb_height'), ); try{ $oEmbed = new Services_oEmbed($url); diff --git a/lib/default.php b/lib/default.php index a19453fce..87f4e45c0 100644 --- a/lib/default.php +++ b/lib/default.php @@ -250,6 +250,8 @@ $default = 'monthly_quota' => 15000000, 'uploads' => true, 'filecommand' => '/usr/bin/file', + 'thumb_width' => 100, + 'thumb_height' => 75, ), 'application' => array('desclimit' => null), diff --git a/lib/mediafile.php b/lib/mediafile.php index 2c04b4650..febf4603a 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -127,8 +127,8 @@ class MediaFile $outname = File::filename($this->user->getProfile(), 'thumb-' . $this->filename, $this->mimetype); $outpath = File::path($outname); - $width = 100; - $height = 75; + $width = common_config('attachments', 'thumb_width'); + $height = common_config('attachments', 'thumb_height'); $image->resizeTo($outpath, $width, $height); File_thumbnail::saveThumbnail($this->fileRecord->id, -- cgit v1.2.3-54-g00ecf From dbb95b76a4d384fd62fd24b4d427baefd007cb90 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Nov 2010 12:04:07 -0800 Subject: Allow YouTube-style media links to be counted as enclosures for purposes of listing attachments/thumbs --- classes/File.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'classes') diff --git a/classes/File.php b/classes/File.php index 56bc73ab2..499c8d72c 100644 --- a/classes/File.php +++ b/classes/File.php @@ -361,15 +361,19 @@ class File extends Memcached_DataObject if($semicolon){ $mimetype = substr($mimetype,0,$semicolon); } - if(in_array($mimetype,$notEnclosureMimeTypes)){ - return false; - }else{ + // @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; } -- cgit v1.2.3-54-g00ecf From 2c4313467f07cae059798ac500ec2a1c31953877 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 14:03:08 -0800 Subject: Save oEmbed photo references as thumbnails if there's not a separate thumbnail_url entry in the return data. This fixes thumb saving for Flickr photo references. --- classes/File_oembed.php | 2 +- classes/File_thumbnail.php | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'classes') diff --git a/classes/File_oembed.php b/classes/File_oembed.php index a5540ecfe..bcb2f7bac 100644 --- a/classes/File_oembed.php +++ b/classes/File_oembed.php @@ -120,7 +120,7 @@ class File_oembed extends Memcached_DataObject } } $file_oembed->insert(); - if (!empty($data->thumbnail_url)) { + if (!empty($data->thumbnail_url) || ($data->type == 'photo')) { $ft = File_thumbnail::staticGet('file_id', $file_id); if (!empty($ft)) { common_log(LOG_WARNING, "Strangely, a File_thumbnail object exists for new file $file_id", diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php index d371b9e8a..17bac7f08 100644 --- a/classes/File_thumbnail.php +++ b/classes/File_thumbnail.php @@ -55,10 +55,21 @@ class File_thumbnail extends Memcached_DataObject * @param int $file_id */ public static function saveNew($data, $file_id) { - self::saveThumbnail($file_id, - $data->thumbnail_url, - $data->thumbnail_width, - $data->thumbnail_height); + if (!empty($data->thumbnail_url)) { + // Non-photo types such as video will usually + // show us a thumbnail, though it's not required. + self::saveThumbnail($file_id, + $data->thumbnail_url, + $data->thumbnail_width, + $data->thumbnail_height); + } else if ($data->type == 'photo') { + // The inline photo URL given should also fit within + // our requested thumbnail size, per oEmbed spec. + self::saveThumbnail($file_id, + $data->url, + $data->width, + $data->height); + } } /** -- cgit v1.2.3-54-g00ecf From 4f323efdf7abc5452152a87241e320aca20ce486 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 17:41:35 -0800 Subject: Encapsulate the oEmbed -> oohembed fallback into oEmbedHelper class. Also added a chance to whitelist sites that don't show discovery info but do have oEmbed API endpoints, and to provide alternate APIs for some common services. Newly supported: - TwitPic: added a local function using TwitPic's API, since the oohembed implementation for TwitPic produced invalid output which Services_oEmbed rejects. (bug filed upstream) Tweaked... - Flickr: works, now using whitelist to use their endpoint directly instead of going through oohembed - Youtube: worked around a bug in Services_oEmbed which broke the direct use of API discovery info, so we don't have to use oohembed. Not currently working... - YFrog: whitelisting their endpoint directly as the oohembed output is broken, but this doesn't appear to work currently as I think things are confused by YFrog's servers giving a '204 No Content' response on our HEAD checks on the original link. --- classes/File_oembed.php | 20 +--- lib/oembedhelper.php | 246 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 lib/oembedhelper.php (limited to 'classes') diff --git a/classes/File_oembed.php b/classes/File_oembed.php index bcb2f7bac..b7bf3a5da 100644 --- a/classes/File_oembed.php +++ b/classes/File_oembed.php @@ -59,25 +59,15 @@ class File_oembed extends Memcached_DataObject } function _getOembed($url) { - require_once INSTALLDIR.'/extlib/Services/oEmbed.php'; $parameters = array( 'maxwidth' => common_config('attachments', 'thumb_width'), 'maxheight' => common_config('attachments', 'thumb_height'), ); - try{ - $oEmbed = new Services_oEmbed($url); - $object = $oEmbed->getObject($parameters); - return $object; - }catch(Exception $e){ - try{ - $oEmbed = new Services_oEmbed($url, array( - Services_oEmbed::OPTION_API => common_config('oohembed', 'endpoint') - )); - $object = $oEmbed->getObject($parameters); - return $object; - }catch(Exception $ex){ - return false; - } + try { + return oEmbedHelper::getObject($url, $parameters); + } catch (Exception $e) { + common_log(LOG_ERR, "Error during oembed lookup for $url - " . $e->getMessage()); + return false; } } diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php new file mode 100644 index 000000000..ef2b59214 --- /dev/null +++ b/lib/oembedhelper.php @@ -0,0 +1,246 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} +require_once INSTALLDIR.'/extlib/Services/oEmbed.php'; + + +/** + * Utility class to wrap Services_oEmbed: + * + * 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', + ); + 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 oohembed.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()) + { + common_log(LOG_INFO, 'QQQ: wtf? ' . $url); + $host = parse_url($url, PHP_URL_HOST); + if (substr($host, 0, 4) == 'www.') { + $host = substr($host, 4); + } + + // Blacklist: systems with no oEmbed API of their own, which are + // either missing from or broken on oohembed.com's proxy. + // we know how to look data up in another way... + if (array_key_exists($host, self::$functionMap)) { + $func = self::$functionMap[$host]; + return call_user_func($func, $url, $params); + } + + // 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, 'QQQ: going to: ' . $api); + } else { + $api = false; + common_log(LOG_INFO, 'QQQ: no map for ' . $host); + } + return self::getObjectFrom($api, $url, $params); + } + + /** + * Actually do an oEmbed lookup to a particular API endpoint, + * or to the autodiscovered target, or to oohembed. + * + * @param mixed $api string or false: oEmbed API endpoint URL + * @param string $url target URL to look up info about + * @param array $params + * @return object + */ + static protected function getObjectFrom($api, $url, $params=array()) + { + $options = array(); + if ($api) { + $options[Services_oEmbed::OPTION_API] = $api; + } + + try { + $oEmbed = new Services_oEmbed_Tweaked($url, $options); + } catch (Services_oEmbed_Exception_NoSupport $e) { + // Discovery failed... fall back to oohembed if enabled. + $oohembed = common_config('oohembed', 'endpoint'); + if ($oohembed) { + $options[Services_oEmbed::OPTION_API] = $oohembed; + $oEmbed = new Services_oEmbed_Tweaked($url, $options); + } else { + throw $e; + } + } + + // And.... let's go look it up! + return $oEmbed->getObject($params); + } + + /** + * 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 Services_oEmbed_Tweaked extends Services_oEmbed +{ + protected function discover($url) + { + $api = parent::discover($url); + if (strpos($api, '?') !== false) { + // Services_oEmbed doesn't expect to find existing params + // on its API endpoint, which may surprise you since the + // spec says discovery URLs should include parameters... :) + // + // Appending a '&' on the end keeps the later-appended '?' + // from breaking whatever the first parameters was. + return $api . '&'; + } + return $api; + } + + public function getObject(array $params = array()) + { + $api = $this->options[self::OPTION_API]; + if (strpos($api, '?') !== false) { + // The Services_oEmbed code appends a '?' on the end, which breaks + // the next parameter which may be something important like + // maxwidth. + // + // Sticking this bogus entry into our parameters gets us past it. + $params = array_merge(array('statusnet' => 1), $params); + } + return parent::getObject($params); + } + +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf