diff options
author | Brion Vibber <brion@status.net> | 2010-11-12 12:28:44 -0800 |
---|---|---|
committer | Brion Vibber <brion@status.net> | 2010-11-12 12:28:44 -0800 |
commit | 5d12ec0532fa9bcc71644186f6ff80f2b527703c (patch) | |
tree | 5a62c9af56efa0cdf47a06bf0fe26c8efbf8f7b7 /lib | |
parent | fdf3a23da7769586a818ca2219ec6bc1b46587de (diff) | |
parent | cb124fe831a3c77dfca89590ebb8d691685bb573 (diff) |
Merge branch 'oembed-thumbnails' into 0.9.x
Diffstat (limited to 'lib')
-rw-r--r-- | lib/attachmentlist.php | 97 | ||||
-rw-r--r-- | lib/default.php | 3 | ||||
-rw-r--r-- | lib/imagefile.php | 103 | ||||
-rw-r--r-- | lib/inlineattachmentlist.php | 108 | ||||
-rw-r--r-- | lib/mediafile.php | 49 | ||||
-rw-r--r-- | lib/noticelist.php | 8 | ||||
-rw-r--r-- | lib/util.php | 2 |
7 files changed, 311 insertions, 59 deletions
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index f6b09fb49..6e127af86 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->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')); + } - foreach ($att as $n=>$attachment) { - $item = $this->newListItem($attachment); - $item->show(); - } - + function showListEnd() + { $this->out->elementEnd('dd'); $this->out->elementEnd('ol'); $this->out->elementEnd('dl'); - - return count($att); } /** @@ -187,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() { @@ -203,9 +216,56 @@ 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) ??? + * + * @return mixed object with (url, width, height) properties, or false + */ + 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; + } + $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; + } + } + 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 +294,9 @@ class AttachmentListItem extends Widget } } +/** + * used for one-off attachment action + */ class Attachment extends AttachmentListItem { function showLink() { @@ -414,18 +477,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('<script>window.location = ' . json_encode($this->attachment->url) . ';</script>'); - } } diff --git a/lib/default.php b/lib/default.php index a19453fce..ece01f2a8 100644 --- a/lib/default.php +++ b/lib/default.php @@ -250,6 +250,9 @@ $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, ), 'application' => array('desclimit' => null), diff --git a/lib/imagefile.php b/lib/imagefile.php index b70fd248e..159deead6 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); - - 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); + imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $width, $height, $w, $h); - 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() diff --git a/lib/inlineattachmentlist.php b/lib/inlineattachmentlist.php new file mode 100644 index 000000000..de5008e87 --- /dev/null +++ b/lib/inlineattachmentlist.php @@ -0,0 +1,108 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * widget for displaying notice attachments thumbnails + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category UI + * @package StatusNet + * @author Brion Vibber <brion@status.net> + * @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'); + } + + /** + * Build HTML attributes for the link + * @return array + */ + function linkAttr() + { + $attr = parent::linkAttr(); + $attr['class'] = 'attachment-thumbnail'; + return $attr; + } + + /** + * 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/mediafile.php b/lib/mediafile.php index aad3575d7..a41d7c76b 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,52 @@ 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); + + $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, + File::url($outname), + $width, + $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); diff --git a/lib/noticelist.php b/lib/noticelist.php index 6f82c9269..c6f964662 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,13 @@ class NoticeListItem extends Widget $this->out->elementEnd('p'); } + function showNoticeAttachments() { + if (common_config('attachments', 'show_thumbs')) { + $al = new InlineAttachmentList($this->notice, $this->out); + $al->show(); + } + } + /** * show the link to the main page for the notice * diff --git a/lib/util.php b/lib/util.php index 8f2a9f173..e6b62f750 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; |