X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=lib%2Fimagefile.php;h=a328df9852bd965f33e0d0107021505f61c56281;hb=f134a423f6a9e7bb61d069c4d6281c05417bbd45;hp=8a0f65232793a962716b49f74dc1fe4f27b3fa94;hpb=79824a362588d480bf668fc957101de9f8fa45e2;p=quix0rs-gnu-social.git diff --git a/lib/imagefile.php b/lib/imagefile.php index 8a0f652327..a328df9852 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -47,17 +47,32 @@ class ImageFile { var $id; var $filepath; - var $barename; + var $filename; var $type; var $height; var $width; var $rotate=0; // degrees to rotate for properly oriented image (extrapolated from EXIF etc.) - var $animated = false; // Animated image? (has more than 2 frames) + var $animated = null; // Animated image? (has more than 1 frame). null means untested + var $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object - function __construct($id=null, $filepath=null, $type=null, $width=null, $height=null) + protected $fileRecord = null; + + function __construct($id, $filepath) { $this->id = $id; + if (!empty($this->id)) { + $this->fileRecord = new File(); + $this->fileRecord->id = $this->id; + if (!$this->fileRecord->find(true)) { + // If we have set an ID, we need that ID to exist! + throw new NoResultException($this->fileRecord); + } + } + + // These do not have to be the same as fileRecord->filename for example, + // since we may have generated an image source file from something else! $this->filepath = $filepath; + $this->filename = basename($filepath); $info = @getimagesize($this->filepath); @@ -73,13 +88,14 @@ class ImageFile throw new UnsupportedMediaException(_('Unsupported image format.'), $this->filepath); } - $this->type = ($info) ? $info[2]:$type; - $this->width = ($info) ? $info[0]:$width; - $this->height = ($info) ? $info[1]:$height; + $this->width = $info[0]; + $this->height = $info[1]; + $this->type = $info[2]; + $this->mimetype = $info['mime']; if ($this->type == IMAGETYPE_JPEG && function_exists('exif_read_data')) { // Orientation value to rotate thumbnails properly - $exif = exif_read_data($this->filepath); + $exif = @exif_read_data($this->filepath); if (is_array($exif) && isset($exif['Orientation'])) { switch ((int)$exif['Orientation']) { case 1: // top is top @@ -97,6 +113,8 @@ class ImageFile } // If we ever write this back, Orientation should be set to '1' } + } elseif ($this->type === IMAGETYPE_GIF) { + $this->animated = $this->isAnimatedGif(); } Event::handle('FillImageFileMetadata', array($this)); @@ -107,6 +125,17 @@ class ImageFile $imgPath = null; $media = common_get_mime_media($file->mimetype); if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) { + if (empty($file->filename) && !file_exists($imgPath)) { + throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.')); + } + + // First some mimetype specific exceptions + switch ($file->mimetype) { + case 'image/svg+xml': + throw new UseFileAsThumbnailException($file->id); + } + + // And we'll only consider it an image if it has such a media type switch ($media) { case 'image': $imgPath = $file->getPath(); @@ -121,11 +150,17 @@ class ImageFile } try { - $image = new ImageFile($file->id, $imgPath); + $image = new ImageFile($file->getID(), $imgPath); } catch (UnsupportedMediaException $e) { // Avoid deleting the original - if ($imgPath != $file->getPath()) { - unlink($imgPath); + try { + if ($imgPath !== $file->getPath()) { + @unlink($imgPath); + } + } catch (FileNotFoundException $e) { + // File reported (via getPath) that the original file + // doesn't exist anyway, so it's safe to delete $imgPath + @unlink($imgPath); } throw $e; } @@ -135,7 +170,7 @@ class ImageFile public function getPath() { if (!file_exists($this->filepath)) { - throw new ServerException('No file in ImageFile filepath'); + throw new FileNotFoundException($this->filepath); } return $this->filepath; @@ -160,6 +195,8 @@ class ImageFile case UPLOAD_ERR_NO_FILE: // No file; probably just a non-AJAX submission. + throw new ClientException(_('No file uploaded.')); + default: common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']); // TRANS: Exception thrown when uploading an image fails for an unknown reason. @@ -177,31 +214,6 @@ 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(); - $outname = Avatar::filename($this->id, - image_type_to_extension($targetType), - $size, - common_timestamp()); - $outpath = Avatar::path($outname); - $this->resizeTo($outpath, array('width'=>$size, 'height'=>$size, - 'x'=>$x, 'y'=>$y, - 'w'=>$w, 'h'=>$h)); - return $outname; - } - /** * Copy the image file to the given destination. * @@ -499,6 +511,122 @@ class ImageFile is_null($cw) ? $width : intval($cw), is_null($ch) ? $height : intval($ch)); } + + /** + * Animated GIF test, courtesy of frank at huddler dot com et al: + * http://php.net/manual/en/function.imagecreatefromgif.php#104473 + * Modified so avoid landing inside of a header (and thus not matching our regexp). + */ + protected function isAnimatedGif() + { + if (!($fh = @fopen($this->filepath, 'rb'))) { + return false; + } + + $count = 0; + //an animated gif contains multiple "frames", with each frame having a + //header made up of: + // * a static 4-byte sequence (\x00\x21\xF9\x04) + // * 4 variable bytes + // * a static 2-byte sequence (\x00\x2C) + // In total the header is maximum 10 bytes. + + // We read through the file til we reach the end of the file, or we've found + // at least 2 frame headers + while(!feof($fh) && $count < 2) { + $chunk = fread($fh, 1024 * 100); //read 100kb at a time + $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches); + // rewind in case we ended up in the middle of the header, but avoid + // infinite loop (i.e. don't rewind if we're already in the end). + if (!feof($fh) && ftell($fh) >= 9) { + fseek($fh, -9, SEEK_CUR); + } + } + + fclose($fh); + return $count > 1; + } + + public function getFileThumbnail($width, $height, $crop, $upscale=false) + { + if (!$this->fileRecord instanceof File) { + throw new ServerException('No File object attached to this ImageFile object.'); + } + + if ($width === null) { + $width = common_config('thumbnail', 'width'); + $height = common_config('thumbnail', 'height'); + $crop = common_config('thumbnail', 'crop'); + } + + if (!$upscale) { + if ($width > $this->width) { + $width = $this->width; + } + if (!is_null($height) && $height > $this->height) { + $height = $this->height; + } + } + + if ($height === null) { + $height = $width; + $crop = true; + } + + // Get proper aspect ratio width and height before lookup + // We have to do it through an ImageFile object because of orientation etc. + // Only other solution would've been to rotate + rewrite uploaded files + // which we don't want to do because we like original, untouched data! + list($width, $height, $x, $y, $w, $h) = $this->scaleToFit($width, $height, $crop); + + $thumb = File_thumbnail::pkeyGet(array( + 'file_id'=> $this->fileRecord->id, + 'width' => $width, + 'height' => $height, + )); + if ($thumb instanceof File_thumbnail) { + return $thumb; + } + + $filename = $this->fileRecord->filehash ?: $this->filename; // Remote files don't have $this->filehash + $extension = File::guessMimeExtension($this->mimetype); + $outname = "thumb-{$this->fileRecord->id}-{$width}x{$height}-{$filename}." . $extension; + $outpath = File_thumbnail::path($outname); + + // The boundary box for our resizing + $box = array('width'=>$width, 'height'=>$height, + 'x'=>$x, 'y'=>$y, + 'w'=>$w, 'h'=>$h); + + // Doublecheck that parameters are sane and integers. + if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize') + || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize') + || $box['w'] < 1 || $box['x'] >= $this->width + || $box['h'] < 1 || $box['y'] >= $this->height) { + // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit + common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box,true)); + throw new ServerException('Bad thumbnail size parameters.'); + } + + common_debug(sprintf('Generating a thumbnail of File id==%u of size %ux%u', $this->fileRecord->id, $width, $height)); + + // Perform resize and store into file + $this->resizeTo($outpath, $box); + + try { + // Avoid deleting the original + if (!in_array($this->getPath(), [File::path($this->filename), File_thumbnail::path($this->filename)])) { + $this->unlink(); + } + } catch (FileNotFoundException $e) { + // $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it! + } + + return File_thumbnail::saveThumbnail($this->fileRecord->getID(), + null, // no url since we generated it ourselves and can dynamically generate the url + $width, $height, + $outname); + } } //PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one