From 883f7a6c0b1464f6723e51bf99d06641a612f968 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 13:27:54 -0800 Subject: [PATCH 01/27] 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 +++ lib/util.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/classes/File.php b/classes/File.php index 16e00024a5..d71403e649 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); diff --git a/lib/util.php b/lib/util.php index 8f2a9f1738..e6b62f750f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -877,7 +877,7 @@ function common_linkify($url) { } if (!empty($f)) { - if ($f->getEnclosure() || File_oembed::staticGet('file_id',$f->id)) { + if ($f->getEnclosure()) { $is_attachment = true; $attachment_id = $f->id; From 32321de5e048ee50417a6d91b651180c28a801b1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 14:20:23 -0800 Subject: [PATCH 02/27] Some initial testing w/ thumb gen --- js/util.js | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/js/util.js b/js/util.js index 1be3f30535..a4eb0fc284 100644 --- a/js/util.js +++ b/js/util.js @@ -428,30 +428,15 @@ var SN = { // StatusNet }).attr('title', SN.msg('showmore_tooltip')); } else { - $.fn.jOverlay.options = { - method : 'GET', - data : '', - url : '', - color : '#000', - opacity : '0.6', - zIndex : 9999, - center : false, - imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', - bgClickToClose : true, - success : function() { - $('#jOverlayContent').append(''); - $('#jOverlayContent button').click($.closeOverlay); - }, - timeout : 0, - autoHide : true, - css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} - }; + //imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', - notice.find('a.attachment').click(function() { + notice.find('a.attachment').each(function() { var attachId = ($(this).attr('id').substring('attachment'.length + 1)); if (attachId) { - $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); - return false; + var thumbUrl = $('address .url')[0].href+'attachment/' + attachId + '/thumb'; + var thumb = $('
Thumb:
'); + thumb.find('img').attr('src', thumbUrl).last(); + notice.append(thumb); } }); From 551b196a3572ac9dabcda47abc92db201fb0e6c9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 15:32:41 -0800 Subject: [PATCH 03/27] doomy doom doom --- actions/shownotice.php | 5 --- js/util.js | 8 +++-- lib/attachmentlist.php | 64 +++++++++++++++++++++++++++----------- lib/noticelist.php | 6 ++++ theme/base/css/display.css | 20 +++++++++--- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index b7e61a1375..7a11787b66 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -356,9 +356,4 @@ class SingleNoticeItem extends DoFollowListItem $this->profile->fullname : $this->profile->nickname)); } - - function showNoticeAttachments() { - $al = new AttachmentList($this->notice, $this->out); - $al->show(); - } } diff --git a/js/util.js b/js/util.js index a4eb0fc284..15fb163103 100644 --- a/js/util.js +++ b/js/util.js @@ -431,16 +431,19 @@ var SN = { // StatusNet //imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', notice.find('a.attachment').each(function() { + /* var attachId = ($(this).attr('id').substring('attachment'.length + 1)); if (attachId) { var thumbUrl = $('address .url')[0].href+'attachment/' + attachId + '/thumb'; - var thumb = $('
Thumb:
'); + var thumb = $('
Thumb:
'); thumb.find('img').attr('src', thumbUrl).last(); - notice.append(thumb); + notice.find('.entry-title .entry-content').append(thumb); } + */ }); if ($('#shownotice').length == 0) { + /* var t; notice.find('a.thumbnail').hover( function() { @@ -465,6 +468,7 @@ var SN = { // StatusNet $(this).closest('.entry-title').removeClass('ov'); } ); + */ } } }, diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index f6b09fb491..f29d32ada3 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -181,9 +181,11 @@ class AttachmentListItem extends Widget */ function show() { - $this->showStart(); - $this->showNoticeAttachment(); - $this->showEnd(); + if ($this->attachment->isEnclosure()) { + $this->showStart(); + $this->showNoticeAttachment(); + $this->showEnd(); + } } function linkAttr() { @@ -203,9 +205,44 @@ class AttachmentListItem extends Widget } function showRepresentation() { + $thumb = $this->getThumbInfo(); + if ($thumb) { + $thumb = $this->sizeThumb($thumb); + $this->out->element('img', array('alt' => '', 'src' => $thumb->url, 'width' => $thumb->width, 'height' => $thumb->height)); + } + } + + function getThumbInfo() + { $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id); - if (!empty($thumbnail)) { - $this->out->element('img', array('alt' => '', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height)); + if ($thumbnail) { + return $thumbnail; + } else { + switch ($this->attachment->mimetype) { + case 'image/gif': + case 'image/png': + case 'image/jpg': + case 'image/jpeg': + $thumb = (object)array(); + $thumb->url = $this->attachment->url; + $thumb->width = 100; + $thumb->height = 75; // @fixme + return $thumb; + } + } + return false; + } + + function sizeThumb($thumbnail) { + $maxWidth = 100; + $maxHeight = 75; + if ($thumbnail->width > $maxWidth) { + $thumb = clone($thumbnail); + $thumb->width = $maxWidth; + $thumb->height = intval($thumbnail->height * $maxWidth / $thumbnail->width); + return $thumb; + } else { + return $thumbnail; } } @@ -234,6 +271,9 @@ class AttachmentListItem extends Widget } } +/** + * used for one-off attachment action + */ class Attachment extends AttachmentListItem { function showLink() { @@ -414,18 +454,4 @@ class Attachment extends AttachmentListItem return $scrubbed; } - - function showFallback() - { - // If we don't know how to display an attachment inline, we probably - // shouldn't have gotten to this point. - // - // But, here we are... displaying details on a file or remote URL - // either on the main view or in an ajax-loaded lightbox. As a lesser - // of several evils, we'll try redirecting to the actual target via - // client-side JS. - - common_log(LOG_ERR, "Empty or unknown type for file id {$this->attachment->id}; falling back to client-side redirect."); - $this->out->raw(''); - } } diff --git a/lib/noticelist.php b/lib/noticelist.php index 6f82c9269b..fb5db2374c 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -208,6 +208,7 @@ class NoticeListItem extends Widget $this->showStart(); if (Event::handle('StartShowNoticeItem', array($this))) { $this->showNotice(); + $this->showNoticeAttachments(); $this->showNoticeInfo(); $this->showNoticeOptions(); Event::handle('EndShowNoticeItem', array($this)); @@ -383,6 +384,11 @@ class NoticeListItem extends Widget $this->out->elementEnd('p'); } + function showNoticeAttachments() { + $al = new AttachmentList($this->notice, $this->out); + $al->show(); + } + /** * show the link to the main page for the notice * diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 7ac66095a8..29f7d0ae0d 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1150,7 +1150,8 @@ border-radius:4px; -webkit-border-radius:4px; } -.notice div.entry-content { +.notice div.entry-content, +.notice dl.entry-content { clear:left; float:left; font-size:0.95em; @@ -1325,6 +1326,7 @@ margin-left:4px; .notice .attachment.more { padding-left:0; } +/* .notice .attachment img { position:absolute; top:18px; @@ -1334,20 +1336,30 @@ z-index:99; #shownotice .notice .attachment img { position:static; } +*/ -#attachments { + +/* Small inline attachment list */ +#attachments ol li { + list-style-type: none; +} +#attachments dt { + display: none; +} + +#shownotice #attachments { clear:both; float:left; width:100%; margin-top:18px; } -#attachments dt { +#shownotice #attachments dt { font-weight:bold; font-size:1.3em; margin-bottom:4px; } -#attachments ol li { +#shownotice #attachments ol li { margin-bottom:18px; list-style-type:decimal; float:left; From a2994e3aa232106f39a6c36c9176608467b8822e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 15:50:06 -0800 Subject: [PATCH 04/27] Testing... using photo info for temp thumbnails --- classes/File.php | 7 +++---- lib/attachmentlist.php | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/classes/File.php b/classes/File.php index d71403e649..e3b922d13b 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 f29d32ada3..8e6ad038a3 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; From dc497ed090d94561db195983a4346a2f66503422 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 16:51:31 -0800 Subject: [PATCH 05/27] Break out ImageFile->resizeTo() from ImageFile->resize(); allows resizing images to non-square sizes and to arbitrary destinations. Will be used for creating thumbnails as well as the originala use of cropping/sizing avatars. --- lib/imagefile.php | 103 +++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/lib/imagefile.php b/lib/imagefile.php index b70fd248e1..159deead61 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -115,10 +115,46 @@ class ImageFile return new ImageFile(null, $_FILES[$param]['tmp_name']); } + /** + * Compat interface for old code generating avatar thumbnails... + * Saves the scaled file directly into the avatar area. + * + * @param int $size target width & height -- must be square + * @param int $x (default 0) upper-left corner to crop from + * @param int $y (default 0) upper-left corner to crop from + * @param int $w (default full) width of image area to crop + * @param int $h (default full) height of image area to crop + * @return string filename + */ function resize($size, $x = 0, $y = 0, $w = null, $h = null) + { + $targetType = $this->preferredType($this->type); + $outname = Avatar::filename($this->id, + image_type_to_extension($targetType), + $size, + common_timestamp()); + $outpath = Avatar::path($outname); + $this->resizeTo($outpath, $size, $size, $x, $y, $w, $h); + return $outname; + } + + /** + * Create and save a thumbnail image. + * + * @param string $outpath + * @param int $width target width + * @param int $height target height + * @param int $x (default 0) upper-left corner to crop from + * @param int $y (default 0) upper-left corner to crop from + * @param int $w (default full) width of image area to crop + * @param int $h (default full) height of image area to crop + * @return string full local filesystem filename + */ + function resizeTo($outpath, $width, $height, $x=0, $y=0, $w=null, $h=null) { $w = ($w === null) ? $this->width:$w; $h = ($h === null) ? $this->height:$h; + $targetType = $this->preferredType($this->type); if (!file_exists($this->filepath)) { throw new Exception(_('Lost our file.')); @@ -126,20 +162,16 @@ class ImageFile } // Don't crop/scale if it isn't necessary - if ($size === $this->width - && $size === $this->height + if ($width === $this->width + && $height === $this->height && $x === 0 && $y === 0 && $w === $this->width - && $h === $this->height) { + && $h === $this->height + && $this->type == $targetType) { - $outname = Avatar::filename($this->id, - image_type_to_extension($this->type), - $size, - common_timestamp()); - $outpath = Avatar::path($outname); @copy($this->filepath, $outpath); - return $outname; + return $outpath; } switch ($this->type) { @@ -166,7 +198,7 @@ class ImageFile return; } - $image_dest = imagecreatetruecolor($size, $size); + $image_dest = imagecreatetruecolor($width, $height); if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) { @@ -189,30 +221,9 @@ class ImageFile } } - imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $w, $h); + imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $width, $height, $w, $h); - if($this->type == IMAGETYPE_BMP) { - //we don't want to save BMP... it's an inefficient, rare, antiquated format - //save png instead - $this->type = IMAGETYPE_PNG; - } else if($this->type == IMAGETYPE_WBMP) { - //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support - //save png instead - $this->type = IMAGETYPE_PNG; - } else if($this->type == IMAGETYPE_XBM) { - //we don't want to save XBM... it's a rare format that we can't guarantee clients will support - //save png instead - $this->type = IMAGETYPE_PNG; - } - - $outname = Avatar::filename($this->id, - image_type_to_extension($this->type), - $size, - common_timestamp()); - - $outpath = Avatar::path($outname); - - switch ($this->type) { + switch ($targetType) { case IMAGETYPE_GIF: imagegif($image_dest, $outpath); break; @@ -230,7 +241,31 @@ class ImageFile imagedestroy($image_src); imagedestroy($image_dest); - return $outname; + return $outpath; + } + + /** + * Several obscure file types should be normalized to PNG on resize. + * + * @param int $type + * @return int + */ + function preferredType($type) + { + if($type == IMAGETYPE_BMP) { + //we don't want to save BMP... it's an inefficient, rare, antiquated format + //save png instead + return IMAGETYPE_PNG; + } else if($type == IMAGETYPE_WBMP) { + //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support + //save png instead + return IMAGETYPE_PNG; + } else if($type == IMAGETYPE_XBM) { + //we don't want to save XBM... it's a rare format that we can't guarantee clients will support + //save png instead + return IMAGETYPE_PNG; + } + return $type; } function unlink() From c36fecb79431218016615cde45215337d67fee67 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:20:04 -0800 Subject: [PATCH 06/27] 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(-) diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php index edae8ac21a..d371b9e8aa 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 aad3575d72..2c04b46501 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); From 6d7f02ff31c6c929223030b051541b1bf103f3a8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:22:01 -0800 Subject: [PATCH 07/27] Pass file attachment thumbnails along with oEmbed data. --- actions/oembed.php | 6 ++++++ classes/File.php | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/actions/oembed.php b/actions/oembed.php index da3aa0c716..11d8145837 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 e3b922d13b..56bc73ab25 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); + } } From 694448e0aa81edb7b010f102ee9ee0e6961f6f7c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:36:02 -0800 Subject: [PATCH 08/27] 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(-) diff --git a/classes/File_oembed.php b/classes/File_oembed.php index 4813d5dda5..a5540ecfee 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 a19453fce4..87f4e45c0e 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 2c04b46501..febf4603a7 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, From 504529e8cd8fbaf5e8e1b980260d1d87d9e880ac Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Nov 2010 17:51:53 -0800 Subject: [PATCH 09/27] Keep aspect ratio when generating local thumbnails --- lib/mediafile.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/mediafile.php b/lib/mediafile.php index febf4603a7..a41d7c76b5 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -127,8 +127,9 @@ class MediaFile $outname = File::filename($this->user->getProfile(), 'thumb-' . $this->filename, $this->mimetype); $outpath = File::path($outname); - $width = common_config('attachments', 'thumb_width'); - $height = common_config('attachments', 'thumb_height'); + $maxWidth = common_config('attachments', 'thumb_width'); + $maxHeight = common_config('attachments', 'thumb_height'); + list($width, $height) = $this->scaleToFit($image->width, $image->height, $maxWidth, $maxHeight); $image->resizeTo($outpath, $width, $height); File_thumbnail::saveThumbnail($this->fileRecord->id, @@ -137,6 +138,19 @@ class MediaFile $height); } + function scaleToFit($width, $height, $maxWidth, $maxHeight) + { + $aspect = $maxWidth / $maxHeight; + $w1 = $maxWidth; + $h1 = intval($height * $maxWidth / $width); + if ($h1 > $maxHeight) { + $w2 = intval($width * $maxHeight / $height); + $h2 = $maxHeight; + return array($w2, $h2); + } + return array($w1, $h1); + } + function rememberFile($file, $short) { $this->maybeAddRedir($file->id, $short); From f25accc43ea1e66f290c8bc1d284ae04b4bf004f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Nov 2010 10:45:19 -0800 Subject: [PATCH 10/27] split out InlineAttachmentList from AttachmentList --- actions/shownotice.php | 9 ++++ lib/attachmentlist.php | 34 ++++++++----- lib/inlineattachmentlist.php | 97 ++++++++++++++++++++++++++++++++++++ lib/noticelist.php | 2 +- theme/base/css/display.css | 6 +++ 5 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 lib/inlineattachmentlist.php diff --git a/actions/shownotice.php b/actions/shownotice.php index 7a11787b66..b4af7dbaa2 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -331,6 +331,15 @@ class SingleNoticeItem extends DoFollowListItem $this->showEnd(); } + /** + * For our zoomed-in special case we'll use a fuller list + * for the attachment info. + */ + function showNoticeAttachments() { + $al = new AttachmentList($this->notice, $this->out); + $al->show(); + } + /** * show the avatar of the notice's author * diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 8e6ad038a3..f9ef7499e1 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -79,23 +79,33 @@ class AttachmentList extends Widget $atts = new File; $att = $atts->getAttachments($this->notice->id); if (empty($att)) return 0; - $this->out->elementStart('dl', array('id' =>'attachments', - 'class' => 'entry-content')); - // TRANS: DT element label in attachment list. - $this->out->element('dt', null, _('Attachments')); - $this->out->elementStart('dd'); - $this->out->elementStart('ol', array('class' => 'attachments')); + $this->showListStart(); foreach ($att as $n=>$attachment) { $item = $this->newListItem($attachment); $item->show(); } + $this->showListEnd(); + + return count($att); + } + + function showListStart() + { + $this->out->elementStart('dl', array('id' =>'attachments', + 'class' => 'entry-content')); + // TRANS: DT element label in attachment list. + $this->out->element('dt', null, _('Attachments')); + $this->out->elementStart('dd'); + $this->out->elementStart('ol', array('class' => 'attachments')); + } + + function showListEnd() + { $this->out->elementEnd('dd'); $this->out->elementEnd('ol'); $this->out->elementEnd('dl'); - - return count($att); } /** @@ -181,11 +191,9 @@ class AttachmentListItem extends Widget */ function show() { - if ($this->attachment->isEnclosure()) { - $this->showStart(); - $this->showNoticeAttachment(); - $this->showEnd(); - } + $this->showStart(); + $this->showNoticeAttachment(); + $this->showEnd(); } function linkAttr() { diff --git a/lib/inlineattachmentlist.php b/lib/inlineattachmentlist.php new file mode 100644 index 0000000000..8b1a1cd9bb --- /dev/null +++ b/lib/inlineattachmentlist.php @@ -0,0 +1,97 @@ +. + * + * @category UI + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 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/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class InlineAttachmentList extends AttachmentList +{ + function showListStart() + { + $this->out->elementStart('div', array('class' => 'entry-content thumbnails')); + } + + function showListEnd() + { + $this->out->elementEnd('div'); + } + + /** + * returns a new list item for the current attachment + * + * @param File $notice the current attachment + * + * @return ListItem a list item for displaying the attachment + */ + function newListItem($attachment) + { + return new InlineAttachmentListItem($attachment, $this->out); + } +} + +class InlineAttachmentListItem extends AttachmentListItem +{ + function show() + { + if ($this->attachment->isEnclosure()) { + parent::show(); + } + } + + function showLink() { + $this->out->elementStart('a', $this->linkAttr()); + $this->showRepresentation(); + $this->out->elementEnd('a'); + } + + /** + * start a single notice. + * + * @return void + */ + function showStart() + { + // XXX: RDFa + // TODO: add notice_type class e.g., notice_video, notice_image + $this->out->elementStart('span', array('class' => 'inline-attachment')); + } + + /** + * finish the notice + * + * Close the last elements in the notice list item + * + * @return void + */ + function showEnd() + { + $this->out->elementEnd('span'); + } +} diff --git a/lib/noticelist.php b/lib/noticelist.php index fb5db2374c..d2ac7ed84a 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -385,7 +385,7 @@ class NoticeListItem extends Widget } function showNoticeAttachments() { - $al = new AttachmentList($this->notice, $this->out); + $al = new InlineAttachmentList($this->notice, $this->out); $al->show(); } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 29f7d0ae0d..6615e13eb4 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1728,6 +1728,12 @@ width:auto; min-width:0; } +.inline-attachment img { + /* Why on earth is this changed to block at the top? */ + display: inline; + border: solid 1px #aaa; + padding: 1px; +} }/*end of @media screen, projection, tv*/ From dbb95b76a4d384fd62fd24b4d427baefd007cb90 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Nov 2010 12:04:07 -0800 Subject: [PATCH 11/27] 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(-) diff --git a/classes/File.php b/classes/File.php index 56bc73ab25..499c8d72c3 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; } From a4654bfe9f950fa3ca76d109f21030a03507c3da Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 9 Nov 2010 12:53:57 -0500 Subject: [PATCH 12/27] session table was missing from upgrade scripts --- db/074to080.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/db/074to080.sql b/db/074to080.sql index ff08191596..e3631e214a 100644 --- a/db/074to080.sql +++ b/db/074to080.sql @@ -107,3 +107,15 @@ create table group_alias ( index group_alias_group_id_idx (group_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table session ( + + id varchar(32) primary key comment 'session ID', + session_data text comment 'session data', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + index session_modified_idx (modified) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + From 3afb031d9270a29db7f1ac4a964bb4b796759827 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 9 Nov 2010 17:08:11 -0500 Subject: [PATCH 13/27] Missing one close-paren in newgroup.php --- actions/newgroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/newgroup.php b/actions/newgroup.php index 2951920362..371e508379 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -147,7 +147,7 @@ class NewgroupAction extends Action $this->showForm(sprintf(_m('Description is too long (maximum %d character).', 'Description is too long (maximum %d characters).', User_group::maxDescription(), - User_group::maxDescription())); + User_group::maxDescription()))); return; } else if (!is_null($location) && mb_strlen($location) > 255) { $this->showForm(_('Location is too long (maximum 255 characters).')); From 5a3d01423d378272218072136f2b3e46b5aa5269 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Nov 2010 16:28:33 -0800 Subject: [PATCH 14/27] Cleanup on the CSS for inline attachments; removed some unneeded changes since the split of the inline and regular attachment lists. Removing commented-out code that seems to be for some old thumbnailing system where the thumbnails were hidden popups within the core text (wtf!) --- theme/base/css/display.css | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 6615e13eb4..8c364febce 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1150,8 +1150,7 @@ border-radius:4px; -webkit-border-radius:4px; } -.notice div.entry-content, -.notice dl.entry-content { +.notice div.entry-content { clear:left; float:left; font-size:0.95em; @@ -1326,40 +1325,20 @@ margin-left:4px; .notice .attachment.more { padding-left:0; } -/* -.notice .attachment img { -position:absolute; -top:18px; -left:0; -z-index:99; -} -#shownotice .notice .attachment img { -position:static; -} -*/ - -/* Small inline attachment list */ -#attachments ol li { - list-style-type: none; -} -#attachments dt { - display: none; -} - -#shownotice #attachments { +#attachments { clear:both; float:left; width:100%; margin-top:18px; } -#shownotice #attachments dt { +#attachments dt { font-weight:bold; font-size:1.3em; margin-bottom:4px; } -#shownotice #attachments ol li { +#attachments ol li { margin-bottom:18px; list-style-type:decimal; float:left; From 592e0bc505c52a38952469bae0a081c224180bd8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Nov 2010 16:43:37 -0800 Subject: [PATCH 15/27] add title attribute on attachment list items --- lib/attachmentlist.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index f9ef7499e1..6e127af864 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -197,7 +197,10 @@ class AttachmentListItem extends Widget } function linkAttr() { - return array('class' => 'attachment', 'href' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id); + return array('class' => 'attachment', + 'href' => $this->attachment->url, + 'id' => 'attachment-' . $this->attachment->id, + 'title' => $this->title()); } function showLink() { @@ -244,8 +247,9 @@ class AttachmentListItem extends Widget case 'image/jpeg': $thumb = (object)array(); $thumb->url = $enc->url; - $thumb->width = 100; - $thumb->height = 75; // @fixme + // @fixme use the given width/height aspect + $thumb->width = common_config('attachments', 'thumb_width'); + $thumb->height = common_config('attachments', 'thumb_height'); return $thumb; } } From 46223da59433e602343169a948bc895977ea253f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Nov 2010 14:31:55 -0800 Subject: [PATCH 16/27] CSS class tweak for inline attachment thumbnails to avoid things thinking they're content links --- lib/inlineattachmentlist.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/inlineattachmentlist.php b/lib/inlineattachmentlist.php index 8b1a1cd9bb..de5008e87b 100644 --- a/lib/inlineattachmentlist.php +++ b/lib/inlineattachmentlist.php @@ -71,6 +71,17 @@ class InlineAttachmentListItem extends AttachmentListItem $this->out->elementEnd('a'); } + /** + * Build HTML attributes for the link + * @return array + */ + function linkAttr() + { + $attr = parent::linkAttr(); + $attr['class'] = 'attachment-thumbnail'; + return $attr; + } + /** * start a single notice. * From fbd8052d05fda7a967d8440574d2b5013d4e7be1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Nov 2010 15:26:18 -0800 Subject: [PATCH 17/27] Add error logging for a couple send-fail cases in XMPP out --- lib/xmppmanager.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index 7acd7663a2..238696664a 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -198,10 +198,12 @@ class XmppManager extends IoManager $this->conn->processTime(0); return true; } else { + common_log(LOG_ERR, __METHOD__ . ' failed: 0 bytes sent'); return false; } } else { // Can't send right now... + common_log(LOG_ERR, __METHOD__ . ' failed: XMPP server connection currently down'); return false; } } From 09aaf21e8d9be5fc0ed82df0028125c9fd96e48d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 11 Nov 2010 10:33:26 -0800 Subject: [PATCH 18/27] Fix missing close of comment block --- actions/allrss.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/allrss.php b/actions/allrss.php index d398c8a6ad..573bb4eb2f 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -56,6 +56,8 @@ class AllrssAction extends Rss10Action * @param array $args Web and URL arguments * * @return boolean false if user doesn't exist + * + */ function prepare($args) { parent::prepare($args); From b6af5a25ba61ef3c86a3772abea7ac95778689f7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 12 Nov 2010 11:46:45 -0500 Subject: [PATCH 19/27] don't try to initialize the mapstraction canvas if it doesn't exist --- plugins/Mapstraction/MapstractionPlugin.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php index c4ba6464ea..d5261d8bc7 100644 --- a/plugins/Mapstraction/MapstractionPlugin.php +++ b/plugins/Mapstraction/MapstractionPlugin.php @@ -156,7 +156,8 @@ class MapstractionPlugin extends Plugin ' var user = null; '. (($actionName == 'showstream') ? ' user = scrapeUser(); ' : '') . ' var notices = scrapeNotices(user); ' . - ' showMapstraction($("#map_canvas"), notices); '. + ' var canvas = $("#map_canvas")[0]; ' . + ' if (typeof(canvas) != "undefined") { showMapstraction(canvas, notices); } '. '});'); } From 62467f51e520439d3ec44ceb6a66a91ad54d77b6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 12:10:29 -0800 Subject: [PATCH 20/27] Drop commented-out code from old lightbox & thumbnail popup stuff --- js/util.js | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/js/util.js b/js/util.js index 15fb163103..bf3c43fd8a 100644 --- a/js/util.js +++ b/js/util.js @@ -427,50 +427,6 @@ var SN = { // StatusNet return false; }).attr('title', SN.msg('showmore_tooltip')); } - else { - //imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', - - notice.find('a.attachment').each(function() { - /* - var attachId = ($(this).attr('id').substring('attachment'.length + 1)); - if (attachId) { - var thumbUrl = $('address .url')[0].href+'attachment/' + attachId + '/thumb'; - var thumb = $('
Thumb:
'); - thumb.find('img').attr('src', thumbUrl).last(); - notice.find('.entry-title .entry-content').append(thumb); - } - */ - }); - - if ($('#shownotice').length == 0) { - /* - var t; - notice.find('a.thumbnail').hover( - function() { - var anchor = $(this); - $('a.thumbnail').children('img').hide(); - anchor.closest(".entry-title").addClass('ov'); - - if (anchor.children('img').length === 0) { - t = setTimeout(function() { - $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 500); - } - else { - anchor.children('img').show(); - } - }, - function() { - clearTimeout(t); - $('a.thumbnail').children('img').hide(); - $(this).closest('.entry-title').removeClass('ov'); - } - ); - */ - } - } }, NoticeDataAttach: function() { From cda59dc1771c2911491c9271662c32f56103347f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 12:10:51 -0800 Subject: [PATCH 21/27] drop a comma which isn't actually an error but keeps throwing annoying warnings in netbeans --- js/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index bf3c43fd8a..74eef4df17 100644 --- a/js/util.js +++ b/js/util.js @@ -56,7 +56,7 @@ var SN = { // StatusNet NoticeDataGeoCookie: 'NoticeDataGeo', NoticeDataGeoSelected: 'notice_data-geo_selected', StatusNetInstance:'StatusNetInstance' - }, + } }, messages: {}, From cb124fe831a3c77dfca89590ebb8d691685bb573 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 12:24:55 -0800 Subject: [PATCH 22/27] Add a quick config setting to disable/enable display of thumbnails in regular notice lists (attachments/show_thumbs) - disabling gives the same display as before this feature was added (but changes to oembed handling are still there, and the lightbox popup is gone) --- lib/default.php | 1 + lib/noticelist.php | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/default.php b/lib/default.php index 87f4e45c0e..ece01f2a8b 100644 --- a/lib/default.php +++ b/lib/default.php @@ -250,6 +250,7 @@ $default = 'monthly_quota' => 15000000, 'uploads' => true, 'filecommand' => '/usr/bin/file', + 'show_thumbs' => true, // show thumbnails in notice lists for uploaded images, and photos and videos linked remotely that provide oEmbed info 'thumb_width' => 100, 'thumb_height' => 75, ), diff --git a/lib/noticelist.php b/lib/noticelist.php index d2ac7ed84a..c6f964662f 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -385,8 +385,10 @@ class NoticeListItem extends Widget } function showNoticeAttachments() { - $al = new InlineAttachmentList($this->notice, $this->out); - $al->show(); + if (common_config('attachments', 'show_thumbs')) { + $al = new InlineAttachmentList($this->notice, $this->out); + $al->show(); + } } /** From 6291e8201f90ed50687f7670edc505645ea55bfb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 13:06:41 -0800 Subject: [PATCH 23/27] Fix for failure edge case in TwitterBridge outgoing repeat/retweets. When the retweet failed with a 403 error (say due to it being a private tweet, which can't be retweeted) we would end up mishandling the return value from our internal error handling. Instead of correctly discarding the message and closing out the queue item, we ended up trying to save a bogus twitter<->local ID mapping, which threw another exception and lead the queue system to re-run it. - Fixed the logic check and return values for the retweet case in broadcast_twitter(). - Added doc comments explaining the return values on some functions in twitter.php - Added check on Notice_to_status::saveNew() for empty input -- throw an exception before we try to actually insert into db. :) --- plugins/TwitterBridge/Notice_to_status.php | 7 +++++ plugins/TwitterBridge/twitter.php | 32 +++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/TwitterBridge/Notice_to_status.php b/plugins/TwitterBridge/Notice_to_status.php index 2e32ba963c..3b8f816cfc 100644 --- a/plugins/TwitterBridge/Notice_to_status.php +++ b/plugins/TwitterBridge/Notice_to_status.php @@ -144,6 +144,7 @@ class Notice_to_status extends Memcached_DataObject /** * Save a mapping between a notice and a status + * Warning: status_id values may not fit in 32-bit integers. * * @param integer $notice_id ID of the notice in StatusNet * @param integer $status_id ID of the status in Twitter @@ -153,12 +154,18 @@ class Notice_to_status extends Memcached_DataObject static function saveNew($notice_id, $status_id) { + if (empty($notice_id)) { + throw new Exception("Invalid notice_id $notice_id"); + } $n2s = Notice_to_status::staticGet('notice_id', $notice_id); if (!empty($n2s)) { return $n2s; } + if (empty($status_id)) { + throw new Exception("Invalid status_id $status_id"); + } $n2s = Notice_to_status::staticGet('status_id', $status_id); if (!empty($n2s)) { diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index cd1ad70b9b..b34488069a 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -128,6 +128,16 @@ function is_twitter_notice($id) return (!empty($n2s)); } +/** + * Check if we need to broadcast a notice over the Twitter bridge, and + * do so if necessary. Will determine whether to do a straight post or + * a repeat/retweet + * + * This function is meant to be called directly from TwitterQueueHandler. + * + * @param Notice $notice + * @return boolean true if complete or successful, false if we should retry + */ function broadcast_twitter($notice) { $flink = Foreign_link::getByUserID($notice->profile_id, @@ -137,8 +147,13 @@ function broadcast_twitter($notice) if (!empty($flink) && TwitterOAuthClient::isPackedToken($flink->credentials)) { if (!empty($notice->repeat_of) && is_twitter_notice($notice->repeat_of)) { $retweet = retweet_notice($flink, Notice::staticGet('id', $notice->repeat_of)); - if (!empty($retweet)) { + if (is_object($retweet)) { Notice_to_status::saveNew($notice->id, $retweet->id); + return true; + } else { + // Our error processing will have decided if we need to requeue + // this or can discard safely. + return $retweet; } } else if (is_twitter_bound($notice, $flink)) { return broadcast_oauth($notice, $flink); @@ -148,6 +163,21 @@ function broadcast_twitter($notice) return true; } +/** + * Send a retweet to Twitter for a notice that has been previously bridged + * in or out. + * + * Warning: the return value is not guaranteed to be an object; some error + * conditions will return a 'true' which should be passed on to a calling + * queue handler. + * + * No local information about the resulting retweet is saved: it's up to + * caller to save new mappings etc if appropriate. + * + * @param Foreign_link $flink + * @param Notice $notice + * @return mixed object with resulting Twitter status data on success, or true/false/null on error conditions. + */ function retweet_notice($flink, $notice) { $token = TwitterOAuthClient::unpackToken($flink->credentials); From 2c4313467f07cae059798ac500ec2a1c31953877 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 14:03:08 -0800 Subject: [PATCH 24/27] 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(-) diff --git a/classes/File_oembed.php b/classes/File_oembed.php index a5540ecfee..bcb2f7bacc 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 d371b9e8aa..17bac7f08c 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); + } } /** From 2c33fdd2fb98c37798a80a8600798caa9dabcb0e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 14:03:57 -0800 Subject: [PATCH 25/27] Only use saved thumbnails for notice list attachment thumbs -- don't attempt to search enclosures for photo types. We now save thumbs directly for oEmbed photos that don't list a separate thumb entry (like Flickr), so it's not needed. Keeps things cleaner :D --- lib/attachmentlist.php | 47 ++++++++++-------------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 6e127af864..0d56791d70 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -218,55 +218,30 @@ class AttachmentListItem extends Widget function showRepresentation() { $thumb = $this->getThumbInfo(); if ($thumb) { - $thumb = $this->sizeThumb($thumb); $this->out->element('img', array('alt' => '', 'src' => $thumb->url, 'width' => $thumb->width, 'height' => $thumb->height)); } } /** - * 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) ??? + * Pull a thumbnail image reference for the given file, and if necessary + * resize it to match currently thumbnail size settings. * - * @return mixed object with (url, width, height) properties, or false + * @return File_Thumbnail or false/null */ function getThumbInfo() { $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id); if ($thumbnail) { - return $thumbnail; - } - $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 = $enc->url; - // @fixme use the given width/height aspect - $thumb->width = common_config('attachments', 'thumb_width'); - $thumb->height = common_config('attachments', 'thumb_height'); - return $thumb; + $maxWidth = common_config('attachments', 'thumb_width'); + $maxHeight = common_config('attachments', 'thumb_height'); + if ($thumbnail->width > $maxWidth) { + $thumb = clone($thumbnail); + $thumb->width = $maxWidth; + $thumb->height = intval($thumbnail->height * $maxWidth / $thumbnail->width); + return $thumb; } } - return false; - } - - function sizeThumb($thumbnail) { - $maxWidth = 100; - $maxHeight = 75; - if ($thumbnail->width > $maxWidth) { - $thumb = clone($thumbnail); - $thumb->width = $maxWidth; - $thumb->height = intval($thumbnail->height * $maxWidth / $thumbnail->width); - return $thumb; - } else { - return $thumbnail; - } + return $thumbnail; } /** From 398e622fecdb2b2b6bf6cde975e3978284db62b4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 17:40:34 -0800 Subject: [PATCH 26/27] Save attached URLs when importing a Twitter status: this lets our thumbnail detection handle photos and videos linked to by Twitter posters. --- plugins/TwitterBridge/twitterimport.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 07a9cf95f6..498e9b1fc5 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -189,6 +189,7 @@ class TwitterImport Notice_to_status::saveNew($notice->id, $status->id); $this->saveStatusMentions($notice, $status); + $this->saveStatusAttachments($notice, $status); $notice->blowOnInsert(); @@ -648,4 +649,20 @@ class TwitterImport } } } + + /** + * Record URL links from the notice. Needed to get thumbnail records + * for referenced photo and video posts, etc. + * + * @param Notice $notice + * @param object $status + */ + function saveStatusAttachments($notice, $status) + { + if (!empty($status->entities) && !empty($status->entities->urls)) { + foreach ($status->entities->urls as $url) { + File::processNew($url->url, $notice->id); + } + } + } } \ No newline at end of file From 4f323efdf7abc5452152a87241e320aca20ce486 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Nov 2010 17:41:35 -0800 Subject: [PATCH 27/27] 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 diff --git a/classes/File_oembed.php b/classes/File_oembed.php index bcb2f7bacc..b7bf3a5dae 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 0000000000..ef2b59214c --- /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