X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fmediafile.php;h=caa902de5dfeb13c18413372769198b9af209241;hb=df4afee86ce847e26f76a075c8f676ca56c736fa;hp=be101cdcabcadab1a259952558a2ff2bc9f06670;hpb=93a6e83d5ddb48ccae80ba9f5c62c63888724083;p=quix0rs-gnu-social.git diff --git a/lib/mediafile.php b/lib/mediafile.php index be101cdcab..caa902de5d 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -1,8 +1,10 @@ * @author Zach Copley * @copyright 2008-2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 @@ -31,9 +34,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class MediaFile +class MediaFile { - + var $filename = null; var $fileRecord = null; var $user = null; @@ -45,46 +48,31 @@ class MediaFile { if ($user == null) { $this->user = common_current_user(); + } else { + $this->user = $user; } - - common_debug('in MediaFile constructor'); - - $this->filename = $filename; - $this->mimetype = $mimetype; - - common_debug('storing file'); + + $this->filename = $filename; + $this->mimetype = $mimetype; $this->fileRecord = $this->storeFile(); - common_debug('finished storing file'); - + $this->thumbnailRecord = $this->storeThumbnail(); + $this->fileurl = common_local_url('attachment', array('attachment' => $this->fileRecord->id)); - common_debug('$this->fileurl() = ' . $this->fileurl); - - // not sure this is necessary -- Zach $this->maybeAddRedir($this->fileRecord->id, $this->fileurl); - - common_debug('shortening file url'); $this->short_fileurl = common_shorten_url($this->fileurl); - common_debug('shortened file url = ' . $short_fileurl); - - // Also, not sure this is necessary -- Zach $this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl); - - common_debug("MediaFile: end of constructor"); } - + function attachToNotice($notice) { - common_debug('MediaFile::attachToNotice() -- doing File_to_post'); File_to_post::processNew($this->fileRecord->id, $notice->id); - common_debug('MediaFile done doing File_to_post'); - $this->maybeAddRedir($this->fileRecord->id, - common_local_url('file', array('notice' => $notice->id))); + common_local_url('file', array('notice' => $notice->id))); } - - function shortUrl() + + function shortUrl() { return $this->short_fileurl; } @@ -98,34 +86,71 @@ class MediaFile function storeFile() { $file = new File; - $file->filename = $this->filename; - - common_debug('storing ' . $this->filename); - - $file->url = File::url($this->filename); - common_debug('file->url = ' . $file->url); - - $filepath = File::path($this->filename); - common_debug('filepath = ' . $filepath); - $file->size = filesize($filepath); - $file->date = time(); + $file->filename = $this->filename; + $file->url = File::url($this->filename); + $filepath = File::path($this->filename); + $file->size = filesize($filepath); + $file->date = time(); $file->mimetype = $this->mimetype; $file_id = $file->insert(); if (!$file_id) { - - common_debug("storeFile: problem inserting new file"); common_log_db_error($file, "INSERT", __FILE__); + // TRANS: Client exception thrown when a database error was thrown during a file upload operation. throw new ClientException(_('There was a database error while saving your file. Please try again.')); } - - common_debug('finished storing file'); 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); @@ -133,15 +158,10 @@ class MediaFile function maybeAddRedir($file_id, $url) { - - common_debug("maybeAddRedir: looking up url: $url for file id $file_id"); - $file_redir = File_redirection::staticGet('url', $url); if (empty($file_redir)) { - - common_debug("maybeAddRedir: $url is not in the db"); - + $file_redir = new File_redirection; $file_redir->url = $url; $file_redir->file_id = $file_id; @@ -150,165 +170,242 @@ class MediaFile if (!$result) { common_log_db_error($file_redir, "INSERT", __FILE__); + // TRANS: Client exception thrown when a database error was thrown during a file upload operation. throw new ClientException(_('There was a database error while saving your file. Please try again.')); } - } else { - - common_debug("maybeAddRedir: no need to add $url, it's already in the db"); } } - static function fromUpload($param = 'media') + static function fromUpload($param = 'media', $user = null) { - common_debug("fromUpload: param = $param"); - + if (empty($user)) { + $user = common_current_user(); + } + if (!isset($_FILES[$param]['error'])){ - common_debug('no file found'); return; } - + switch ($_FILES[$param]['error']) { case UPLOAD_ERR_OK: // success, jump out break; case UPLOAD_ERR_INI_SIZE: + // TRANS: Client exception thrown when an uploaded file is larger than set in php.ini. throw new ClientException(_('The uploaded file exceeds the ' . 'upload_max_filesize directive in php.ini.')); return; case UPLOAD_ERR_FORM_SIZE: throw new ClientException( + // TRANS: Client exception. _('The uploaded file exceeds the MAX_FILE_SIZE directive' . ' that was specified in the HTML form.')); return; case UPLOAD_ERR_PARTIAL: @unlink($_FILES[$param]['tmp_name']); + // TRANS: Client exception. throw new ClientException(_('The uploaded file was only' . ' partially uploaded.')); - return; + return; + case UPLOAD_ERR_NO_FILE: + // No file; probably just a non-AJAX submission. + return; case UPLOAD_ERR_NO_TMP_DIR: + // TRANS: Client exception thrown when a temporary folder is not present to store a file upload. throw new ClientException(_('Missing a temporary folder.')); return; case UPLOAD_ERR_CANT_WRITE: + // TRANS: Client exception thrown when writing to disk is not possible during a file upload operation. throw new ClientException(_('Failed to write file to disk.')); return; case UPLOAD_ERR_EXTENSION: + // TRANS: Client exception thrown when a file upload operation has been stopped by an extension. throw new ClientException(_('File upload stopped by extension.')); return; default: + common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . + $_FILES[$param]['error']); + // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason. throw new ClientException(_('System error uploading file.')); return; } - - $user = common_current_user(); - - if (!MediaFile::respectsQuota($user, $_FILES['attach']['size'])) { - + + if (!MediaFile::respectsQuota($user, $_FILES[$param]['size'])) { + // Should never actually get here - + @unlink($_FILES[$param]['tmp_name']); - throw new ClientException(_('File exceeds user\'s quota!')); + // TRANS: Client exception thrown when a file upload operation would cause a user to exceed a set quota. + throw new ClientException(_('File exceeds user\'s quota.')); return; } - $mimetype = MediaFile::getUploadedFileType($_FILES[$param]['tmp_name']); - + $mimetype = MediaFile::getUploadedFileType($_FILES[$param]['tmp_name'], + $_FILES[$param]['name']); + $filename = null; - + if (isset($mimetype)) { - + $basename = basename($_FILES[$param]['name']); $filename = File::filename($user->getProfile(), $basename, $mimetype); $filepath = File::path($filename); - common_debug("filepath = " . $filepath); - $result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath); - - if (!$result) { + + if (!$result) { + // TRANS: Client exception thrown when a file upload operation fails because the file could + // TRANS: not be moved from the temporary folder to the permanent file location. throw new ClientException(_('File could not be moved to destination directory.')); return; } - + } else { - throw new ClientException(_('Could not determine file\'s mime-type!')); + // TRANS: Client exception thrown when a file upload operation has been stopped because the MIME + // TRANS: type of the uploaded file could not be determined. + throw new ClientException(_('Could not determine file\'s MIME type.')); return; } - + return new MediaFile($user, $filename, $mimetype); } - static function fromFilehandle($user, $fh) { + static function fromFilehandle($fh, $user) { $stream = stream_get_meta_data($fh); - if (MediaFile::respectsQuota($user, filesize($stream['uri']))) { - + if (!MediaFile::respectsQuota($user, filesize($stream['uri']))) { + // Should never actually get here - - throw new ClientException(_('File exceeds user\'s quota!')); + + // TRANS: Client exception thrown when a file upload operation would cause a user to exceed a set quota. + throw new ClientException(_('File exceeds user\'s quota.')); return; } $mimetype = MediaFile::getUploadedFileType($fh); - + $filename = null; - + if (isset($mimetype)) { $filename = File::filename($user->getProfile(), "email", $mimetype); $filepath = File::path($filename); - + $result = copy($stream['uri'], $filepath) && chmod($filepath, 0664); - + if (!$result) { + // TRANS: Client exception thrown when a file upload operation fails because the file could + // TRANS: not be moved from the temporary folder to the permanent file location. throw new ClientException(_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath)); } } else { - throw new ClientException(_('Could not determine file\'s mime-type!')); + // TRANS: Client exception thrown when a file upload operation has been stopped because the MIME + // TRANS: type of the uploaded file could not be determined. + throw new ClientException(_('Could not determine file\'s MIME type.')); return; } - + return new MediaFile($user, $filename, $mimetype); } - static function getUploadedFileType($f) { + /** + * Attempt to identify the content type of a given file. + * + * @param mixed $f file handle resource, or filesystem path as string + * @param string $originalFilename (optional) for extension-based detection + * @return string + * + * @fixme is this an internal or public method? It's called from GetFileAction + * @fixme this seems to tie a front-end error message in, kinda confusing + * @fixme this looks like it could return a PEAR_Error in some cases, if + * type can't be identified and $config['attachments']['supported'] is true + * + * @throws ClientException if type is known, but not supported for local uploads + */ + static function getUploadedFileType($f, $originalFilename=false) { require_once 'MIME/Type.php'; - - common_debug("in getUploadedFileType"); + require_once 'MIME/Type/Extension.php'; + + // We have to disable auto handling of PEAR errors + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $mte = new MIME_Type_Extension(); $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd'); $cmd = common_config('attachments', 'filecommand'); - + $filetype = null; - + + // If we couldn't get a clear type from the file extension, + // we'll go ahead and try checking the content. Content checks + // are unambiguous for most image files, but nearly useless + // for office document formats. + if (is_string($f)) { - + // assuming a filename - + $filetype = MIME_Type::autoDetect($f); + } else { - + // assuming a filehandle - + $stream = stream_get_meta_data($f); $filetype = MIME_Type::autoDetect($stream['uri']); } - - if (in_array($filetype, common_config('attachments', 'supported'))) { + + // The content-based sources for MIME_Type::autoDetect() + // are wildly unreliable for office-type documents. If we've + // gotten an unclear reponse back or just couldn't identify it, + // we'll try detecting a type from its extension... + $unclearTypes = array('application/octet-stream', + 'application/vnd.ms-office', + 'application/zip', + // TODO: for XML we could do better content-based sniffing too + 'text/xml'); + + if ($originalFilename && (!$filetype || in_array($filetype, $unclearTypes))) { + $type = $mte->getMIMEType($originalFilename); + if (is_string($type)) { + $filetype = $type; + } + } + + $supported = common_config('attachments', 'supported'); + if (is_array($supported)) { + // Normalize extensions to mime types + foreach ($supported as $i => $entry) { + if (strpos($entry, '/') === false) { + common_log(LOG_INFO, "sample.$entry"); + $supported[$i] = $mte->getMIMEType("sample.$entry"); + } + } + } + if ($supported === true || in_array($filetype, $supported)) { + // Restore PEAR error handlers for our DB code... + PEAR::staticPopErrorHandling(); return $filetype; } $media = MIME_Type::getMedia($filetype); if ('application' !== $media) { - $hint = sprintf(_(' Try using another %s format.'), $media); + // TRANS: Client exception thrown trying to upload a forbidden MIME type. + // TRANS: %1$s is the file type that was denied, %2$s is the application part of + // TRANS: the MIME type that was denied. + $hint = sprintf(_('"%1$s" is not a supported file type on this server. ' . + 'Try using another %2$s format.'), $filetype, $media); } else { - $hint = ''; + // TRANS: Client exception thrown trying to upload a forbidden MIME type. + // TRANS: %s is the file type that was denied. + $hint = sprintf(_('"%s" is not a supported file type on this server.'), $filetype); } - throw new ClientException(sprintf( - _('%s is not a supported filetype on this server.'), $filetype) . $hint); + // Restore PEAR error handlers for our DB code... + PEAR::staticPopErrorHandling(); + throw new ClientException($hint); } - static function respectsQuota($user, $filesize) + static function respectsQuota($user, $filesize) { $file = new File; $result = $file->isRespectsQuota($user, $filesize); @@ -319,4 +416,4 @@ class MediaFile } } -} \ No newline at end of file +}