forked from GNUsocial/gnu-social
		
	[MEDIA] ImageFile fromUpload method wasn't ensuring uploaded file was an image
This commit is contained in:
		| @@ -1,34 +1,32 @@ | |||||||
| <?php | <?php | ||||||
|  | // This file is part of GNU social - https://www.gnu.org/software/social | ||||||
|  | // | ||||||
|  | // GNU social 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. | ||||||
|  | // | ||||||
|  | // GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * StatusNet, the distributed open-source microblogging tool |  | ||||||
|  * |  | ||||||
|  * Upload an avatar |  * Upload an avatar | ||||||
|  * |  * | ||||||
|  * 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  Settings |  * @category  Settings | ||||||
|  * @package   StatusNet |  * @package   GNUsocial | ||||||
|  |  * | ||||||
|  * @author    Evan Prodromou <evan@status.net> |  * @author    Evan Prodromou <evan@status.net> | ||||||
|  * @author    Zach Copley <zach@status.net> |  * @author    Zach Copley <zach@status.net> | ||||||
|  * @copyright 2008-2009 StatusNet, Inc. |  * @author    Diogo Cordeiro <diogo@fc.up.pt> | ||||||
|  |  * @copyright 2008-2009, 2020 Free Software Foundation http://fsf.org | ||||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * @link      http://status.net/ |  | ||||||
|  */ |  */ | ||||||
|  | defined('GNUSOCIAL') || die; | ||||||
| if (!defined('GNUSOCIAL')) { exit(1); } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Upload an avatar |  * Upload an avatar | ||||||
| @@ -37,24 +35,26 @@ if (!defined('GNUSOCIAL')) { exit(1); } | |||||||
|  * |  * | ||||||
|  * @category Settings |  * @category Settings | ||||||
|  * @package  StatusNet |  * @package  StatusNet | ||||||
|  |  * | ||||||
|  * @author   Evan Prodromou <evan@status.net> |  * @author   Evan Prodromou <evan@status.net> | ||||||
|  * @author   Zach Copley <zach@status.net> |  * @author   Zach Copley <zach@status.net> | ||||||
|  * @author   Sarven Capadisli <csarven@status.net> |  * @author   Sarven Capadisli <csarven@status.net> | ||||||
|  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * @link     http://status.net/ |  * | ||||||
|  |  * @see     http://status.net/ | ||||||
|  */ |  */ | ||||||
| class AvatarsettingsAction extends SettingsAction | class AvatarsettingsAction extends SettingsAction | ||||||
| { | { | ||||||
|     var $mode = null; |     public $mode; | ||||||
|     var $imagefile = null; |     public $imagefile; | ||||||
|     var $filename = null; |     public $filename; | ||||||
|  |  | ||||||
|     function prepare(array $args=array()) |     public function prepare(array $args = []) | ||||||
|     { |     { | ||||||
|         $avatarpath = Avatar::path(''); |         $avatarpath = Avatar::path(''); | ||||||
|  |  | ||||||
|         if (!is_writable($avatarpath)) { |         if (!is_writable($avatarpath)) { | ||||||
|             throw new Exception(_("The administrator of your site needs to |             throw new Exception(_m("The administrator of your site needs to | ||||||
|                 add write permissions on the avatar upload folder before |                 add write permissions on the avatar upload folder before | ||||||
|                 you're able to set one.")); |                 you're able to set one.")); | ||||||
|         } |         } | ||||||
| @@ -67,24 +67,30 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|      * Title of the page |      * Title of the page | ||||||
|      * |      * | ||||||
|      * @return string Title of the page |      * @return string Title of the page | ||||||
|  |      * @throws Exception | ||||||
|  |      * | ||||||
|      */ |      */ | ||||||
|     function title() |     public function title() | ||||||
|     { |     { | ||||||
|         // TRANS: Title for avatar upload page. |         // TRANS: Title for avatar upload page. | ||||||
|         return _('Avatar'); |         return _m('Avatar'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Instructions for use |      * Instructions for use | ||||||
|      * |      * | ||||||
|      * @return instructions for use |      * @return string instructions for use | ||||||
|  |      * @throws Exception | ||||||
|  |      * | ||||||
|      */ |      */ | ||||||
|     function getInstructions() |     public function getInstructions() | ||||||
|     { |     { | ||||||
|         // TRANS: Instruction for avatar upload page. |         // TRANS: Instruction for avatar upload page. | ||||||
|         // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB". |         // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB". | ||||||
|         return sprintf(_('You can upload your personal avatar. The maximum file size is %s.'), |         return sprintf( | ||||||
|                        ImageFile::maxFileSize()); |             _m('You can upload your personal avatar. The maximum file size is %s.'), | ||||||
|  |             ImageFile::maxFileSize() | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -95,7 +101,7 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|      * |      * | ||||||
|      * @return void |      * @return void | ||||||
|      */ |      */ | ||||||
|     function showContent() |     public function showContent() | ||||||
|     { |     { | ||||||
|         if ($this->mode == 'crop') { |         if ($this->mode == 'crop') { | ||||||
|             $this->showCropForm(); |             $this->showCropForm(); | ||||||
| @@ -104,33 +110,32 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function showUploadForm() |     public function showUploadForm() | ||||||
|     { |     { | ||||||
|         $this->elementStart('form', array('enctype' => 'multipart/form-data', |         $this->elementStart('form', ['enctype' => 'multipart/form-data', | ||||||
|                                           'method' => 'post', |             'method' => 'post', | ||||||
|                                           'id' => 'form_settings_avatar', |             'id' => 'form_settings_avatar', | ||||||
|                                           'class' => 'form_settings', |             'class' => 'form_settings', | ||||||
|                                           'action' => |             'action' => common_local_url('avatarsettings'),]); | ||||||
|                                           common_local_url('avatarsettings'))); |  | ||||||
|         $this->elementStart('fieldset'); |         $this->elementStart('fieldset'); | ||||||
|         // TRANS: Avatar upload page form legend. |         // TRANS: Avatar upload page form legend. | ||||||
|         $this->element('legend', null, _('Avatar settings')); |         $this->element('legend', null, _m('Avatar settings')); | ||||||
|         $this->hidden('token', common_session_token()); |         $this->hidden('token', common_session_token()); | ||||||
|  |  | ||||||
|         if (Event::handle('StartAvatarFormData', array($this))) { |         if (Event::handle('StartAvatarFormData', [$this])) { | ||||||
|             $this->elementStart('ul', 'form_data'); |             $this->elementStart('ul', 'form_data'); | ||||||
|             try { |             try { | ||||||
|                 $original = Avatar::getUploaded($this->scoped); |                 $original = Avatar::getUploaded($this->scoped); | ||||||
|  |  | ||||||
|                 $this->elementStart('li', array('id' => 'avatar_original', |                 $this->elementStart('li', ['id' => 'avatar_original', | ||||||
|                                                 'class' => 'avatar_view')); |                     'class' => 'avatar_view',]); | ||||||
|                 // TRANS: Header on avatar upload page for thumbnail of originally uploaded avatar (h2). |                 // TRANS: Header on avatar upload page for thumbnail of originally uploaded avatar (h2). | ||||||
|                 $this->element('h2', null, _("Original")); |                 $this->element('h2', null, _m('Original')); | ||||||
|                 $this->elementStart('div', array('id'=>'avatar_original_view')); |                 $this->elementStart('div', ['id' => 'avatar_original_view']); | ||||||
|                 $this->element('img', array('src' => $original->displayUrl(), |                 $this->element('img', ['src' => $original->displayUrl(), | ||||||
|                                             'width' => $original->width, |                     'width' => $original->width, | ||||||
|                                             'height' => $original->height, |                     'height' => $original->height, | ||||||
|                                             'alt' => $this->scoped->getNickname())); |                     'alt' => $this->scoped->getNickname(),]); | ||||||
|                 $this->elementEnd('div'); |                 $this->elementEnd('div'); | ||||||
|                 $this->elementEnd('li'); |                 $this->elementEnd('li'); | ||||||
|             } catch (NoAvatarException $e) { |             } catch (NoAvatarException $e) { | ||||||
| @@ -139,97 +144,100 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 $avatar = $this->scoped->getAvatar(AVATAR_PROFILE_SIZE); |                 $avatar = $this->scoped->getAvatar(AVATAR_PROFILE_SIZE); | ||||||
|                 $this->elementStart('li', array('id' => 'avatar_preview', |                 $this->elementStart('li', ['id' => 'avatar_preview', | ||||||
|                                                 'class' => 'avatar_view')); |                     'class' => 'avatar_view',]); | ||||||
|                 // TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2). |                 // TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2). | ||||||
|                 $this->element('h2', null, _("Preview")); |                 $this->element('h2', null, _m('Preview')); | ||||||
|                 $this->elementStart('div', array('id'=>'avatar_preview_view')); |                 $this->elementStart('div', ['id' => 'avatar_preview_view']); | ||||||
|                 $this->element('img', array('src' => $avatar->displayUrl(), |                 $this->element('img', ['src' => $avatar->displayUrl(), | ||||||
|                                             'width' => AVATAR_PROFILE_SIZE, |                     'width' => AVATAR_PROFILE_SIZE, | ||||||
|                                             'height' => AVATAR_PROFILE_SIZE, |                     'height' => AVATAR_PROFILE_SIZE, | ||||||
|                                             'alt' => $this->scoped->getNickname())); |                     'alt' => $this->scoped->getNickname(),]); | ||||||
|                 $this->elementEnd('div'); |                 $this->elementEnd('div'); | ||||||
|                 if (!empty($avatar->filename)) { |                 if (!empty($avatar->filename)) { | ||||||
|                     // TRANS: Button on avatar upload page to delete current avatar. |                     // TRANS: Button on avatar upload page to delete current avatar. | ||||||
|                     $this->submit('delete', _m('BUTTON','Delete')); |                     $this->submit('delete', _m('BUTTON', 'Delete')); | ||||||
|                 } |                 } | ||||||
|                 $this->elementEnd('li'); |                 $this->elementEnd('li'); | ||||||
|             } catch (NoAvatarException $e) { |             } catch (NoAvatarException $e) { | ||||||
|                 // No previously uploaded avatar to preview. |                 // No previously uploaded avatar to preview. | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             $this->elementStart('li', array ('id' => 'settings_attach')); |             $this->elementStart('li', ['id' => 'settings_attach']); | ||||||
|             $this->element('input', array('name' => 'MAX_FILE_SIZE', |             $this->element('input', ['name' => 'MAX_FILE_SIZE', | ||||||
|                                           'type' => 'hidden', |                 'type' => 'hidden', | ||||||
|                                           'id' => 'MAX_FILE_SIZE', |                 'id' => 'MAX_FILE_SIZE', | ||||||
|                                           'value' => ImageFile::maxFileSizeInt())); |                 'value' => ImageFile::maxFileSizeInt(),]); | ||||||
|             $this->element('input', array('name' => 'avatarfile', |             $this->element('input', ['name' => 'avatarfile', | ||||||
|                                           'type' => 'file', |                 'type' => 'file', | ||||||
|                                           'id' => 'avatarfile')); |                 'id' => 'avatarfile',]); | ||||||
|             $this->elementEnd('li'); |             $this->elementEnd('li'); | ||||||
|             $this->elementEnd('ul'); |             $this->elementEnd('ul'); | ||||||
|  |  | ||||||
|             $this->elementStart('ul', 'form_actions'); |             $this->elementStart('ul', 'form_actions'); | ||||||
|             $this->elementStart('li'); |             $this->elementStart('li'); | ||||||
|                 // TRANS: Button on avatar upload page to upload an avatar. |             // TRANS: Button on avatar upload page to upload an avatar. | ||||||
|             $this->submit('upload', _m('BUTTON','Upload')); |             $this->submit('upload', _m('BUTTON', 'Upload')); | ||||||
|             $this->elementEnd('li'); |             $this->elementEnd('li'); | ||||||
|             $this->elementEnd('ul'); |             $this->elementEnd('ul'); | ||||||
|         } |         } | ||||||
|         Event::handle('EndAvatarFormData', array($this)); |         Event::handle('EndAvatarFormData', [$this]); | ||||||
|  |  | ||||||
|         $this->elementEnd('fieldset'); |         $this->elementEnd('fieldset'); | ||||||
|         $this->elementEnd('form'); |         $this->elementEnd('form'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function showCropForm() |     public function showCropForm() | ||||||
|     { |     { | ||||||
|         $this->elementStart('form', array('method' => 'post', |         $this->elementStart('form', ['method' => 'post', | ||||||
|                                           'id' => 'form_settings_avatar', |             'id' => 'form_settings_avatar', | ||||||
|                                           'class' => 'form_settings', |             'class' => 'form_settings', | ||||||
|                                           'action' => |             'action' => common_local_url('avatarsettings'),]); | ||||||
|                                           common_local_url('avatarsettings'))); |  | ||||||
|         $this->elementStart('fieldset'); |         $this->elementStart('fieldset'); | ||||||
|         // TRANS: Avatar upload page crop form legend. |         // TRANS: Avatar upload page crop form legend. | ||||||
|         $this->element('legend', null, _('Avatar settings')); |         $this->element('legend', null, _m('Avatar settings')); | ||||||
|         $this->hidden('token', common_session_token()); |         $this->hidden('token', common_session_token()); | ||||||
|  |  | ||||||
|         $this->elementStart('ul', 'form_data'); |         $this->elementStart('ul', 'form_data'); | ||||||
|  |  | ||||||
|         $this->elementStart('li', |         $this->elementStart( | ||||||
|                             array('id' => 'avatar_original', |             'li', | ||||||
|                                   'class' => 'avatar_view')); |             ['id' => 'avatar_original', | ||||||
|  |                 'class' => 'avatar_view',] | ||||||
|  |         ); | ||||||
|         // TRANS: Header on avatar upload crop form for thumbnail of originally uploaded avatar (h2). |         // TRANS: Header on avatar upload crop form for thumbnail of originally uploaded avatar (h2). | ||||||
|         $this->element('h2', null, _('Original')); |         $this->element('h2', null, _m('Original')); | ||||||
|         $this->elementStart('div', array('id'=>'avatar_original_view')); |         $this->elementStart('div', ['id' => 'avatar_original_view']); | ||||||
|         $this->element('img', array('src' => Avatar::url($this->filedata['filename']), |         $this->element('img', ['src' => Avatar::url($this->filedata['filename']), | ||||||
|                                     'width' => $this->filedata['width'], |             'width' => $this->filedata['width'], | ||||||
|                                     'height' => $this->filedata['height'], |             'height' => $this->filedata['height'], | ||||||
|                                     'alt' => $this->scoped->getNickname())); |             'alt' => $this->scoped->getNickname(),]); | ||||||
|         $this->elementEnd('div'); |         $this->elementEnd('div'); | ||||||
|         $this->elementEnd('li'); |         $this->elementEnd('li'); | ||||||
|  |  | ||||||
|         $this->elementStart('li', |         $this->elementStart( | ||||||
|                             array('id' => 'avatar_preview', |             'li', | ||||||
|                                   'class' => 'avatar_view')); |             ['id' => 'avatar_preview', | ||||||
|  |                 'class' => 'avatar_view',] | ||||||
|  |         ); | ||||||
|         // TRANS: Header on avatar upload crop form for thumbnail of to be used rendition of uploaded avatar (h2). |         // TRANS: Header on avatar upload crop form for thumbnail of to be used rendition of uploaded avatar (h2). | ||||||
|         $this->element('h2', null, _('Preview')); |         $this->element('h2', null, _m('Preview')); | ||||||
|         $this->elementStart('div', array('id'=>'avatar_preview_view')); |         $this->elementStart('div', ['id' => 'avatar_preview_view']); | ||||||
|         $this->element('img', array('src' => Avatar::url($this->filedata['filename']), |         $this->element('img', ['src' => Avatar::url($this->filedata['filename']), | ||||||
|                                     'width' => AVATAR_PROFILE_SIZE, |             'width' => AVATAR_PROFILE_SIZE, | ||||||
|                                     'height' => AVATAR_PROFILE_SIZE, |             'height' => AVATAR_PROFILE_SIZE, | ||||||
|                                     'alt' => $this->scoped->getNickname())); |             'alt' => $this->scoped->getNickname(),]); | ||||||
|         $this->elementEnd('div'); |         $this->elementEnd('div'); | ||||||
|  |  | ||||||
|         foreach (array('avatar_crop_x', 'avatar_crop_y', |         foreach (['avatar_crop_x', 'avatar_crop_y', | ||||||
|                        'avatar_crop_w', 'avatar_crop_h') as $crop_info) { |                      'avatar_crop_w', 'avatar_crop_h',] as $crop_info) { | ||||||
|             $this->element('input', array('name' => $crop_info, |             $this->element('input', ['name' => $crop_info, | ||||||
|                                           'type' => 'hidden', |                 'type' => 'hidden', | ||||||
|                                           'id' => $crop_info)); |                 'id' => $crop_info,]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TRANS: Button on avatar upload crop form to confirm a selected crop as avatar. |         // TRANS: Button on avatar upload crop form to confirm a selected crop as avatar. | ||||||
|         $this->submit('crop', _m('BUTTON','Crop')); |         $this->submit('crop', _m('BUTTON', 'Crop')); | ||||||
|  |  | ||||||
|         $this->elementEnd('li'); |         $this->elementEnd('li'); | ||||||
|         $this->elementEnd('ul'); |         $this->elementEnd('ul'); | ||||||
| @@ -237,20 +245,31 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|         $this->elementEnd('form'); |         $this->elementEnd('form'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return string | ||||||
|  |      * @throws NoResultException | ||||||
|  |      * @throws NoUploadedMediaException | ||||||
|  |      * @throws ServerException | ||||||
|  |      * @throws UnsupportedMediaException | ||||||
|  |      * @throws UseFileAsThumbnailException | ||||||
|  |      * @throws Exception | ||||||
|  |      * | ||||||
|  |      * @throws ClientException | ||||||
|  |      */ | ||||||
|     protected function doPost() |     protected function doPost() | ||||||
|     { |     { | ||||||
|         if (Event::handle('StartAvatarSaveForm', array($this))) { |         if (Event::handle('StartAvatarSaveForm', [$this])) { | ||||||
|             if ($this->trimmed('upload')) { |             if ($this->trimmed('upload')) { | ||||||
|                 return $this->uploadAvatar(); |                 return $this->uploadAvatar(); | ||||||
|             } else if ($this->trimmed('crop')) { |             } elseif ($this->trimmed('crop')) { | ||||||
|                 return $this->cropAvatar(); |                 return $this->cropAvatar(); | ||||||
|             } else if ($this->trimmed('delete')) { |             } elseif ($this->trimmed('delete')) { | ||||||
|                 return $this->deleteAvatar(); |                 return $this->deleteAvatar(); | ||||||
|             } else { |             } else { | ||||||
|                 // TRANS: Unexpected validation error on avatar upload form. |                 // TRANS: Unexpected validation error on avatar upload form. | ||||||
|                 throw new ClientException(_('Unexpected form submission.')); |                 throw new ClientException(_m('Unexpected form submission.')); | ||||||
|             } |             } | ||||||
|             Event::handle('EndAvatarSaveForm', array($this)); |             Event::handle('EndAvatarSaveForm', [$this]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -260,28 +279,39 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|      * Does all the magic for handling an image upload, and crops the |      * Does all the magic for handling an image upload, and crops the | ||||||
|      * image by default. |      * image by default. | ||||||
|      * |      * | ||||||
|      * @return void |      * @return string | ||||||
|  |      * @throws NoResultException | ||||||
|  |      * @throws NoUploadedMediaException | ||||||
|  |      * @throws ServerException | ||||||
|  |      * @throws UnsupportedMediaException | ||||||
|  |      * @throws UseFileAsThumbnailException | ||||||
|  |      * | ||||||
|  |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     function uploadAvatar() |     public function uploadAvatar(): string | ||||||
|     { |     { | ||||||
|         // ImageFile throws exception if something goes wrong, which we'll |         // ImageFile throws exception if something goes wrong, which we'll | ||||||
|         // pick up and show as an error message above the form. |         // pick up and show as an error message above the form. | ||||||
|         $imagefile = ImageFile::fromUpload('avatarfile'); |         $imagefile = ImageFile::fromUpload('avatarfile'); | ||||||
|  |  | ||||||
|         $type = $imagefile->preferredType(); |         $type = $imagefile->preferredType(); | ||||||
|         $filename = Avatar::filename($this->scoped->getID(), |         $filename = Avatar::filename( | ||||||
|                                      image_type_to_extension($type), |             $this->scoped->getID(), | ||||||
|                                      null, |             image_type_to_extension($type), | ||||||
|                                      'tmp'.common_timestamp()); |             null, | ||||||
|  |             'tmp' . common_timestamp() | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         $filepath = Avatar::path($filename); |         $filepath = Avatar::path($filename); | ||||||
|         $imagefile = $imagefile->copyTo($filepath); |         $imagefile = $imagefile->copyTo($filepath); | ||||||
|  |  | ||||||
|         $filedata = array('filename' => $filename, |         $filedata = [ | ||||||
|                           'filepath' => $filepath, |             'filename' => $filename, | ||||||
|                           'width' => $imagefile->width, |             'filepath' => $filepath, | ||||||
|                           'height' => $imagefile->height, |             'width' => $imagefile->width, | ||||||
|                           'type' => $type); |             'height' => $imagefile->height, | ||||||
|  |             'type' => $type, | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|         $_SESSION['FILEDATA'] = $filedata; |         $_SESSION['FILEDATA'] = $filedata; | ||||||
|  |  | ||||||
| @@ -290,13 +320,18 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|         $this->mode = 'crop'; |         $this->mode = 'crop'; | ||||||
|  |  | ||||||
|         // TRANS: Avatar upload form instruction after uploading a file. |         // TRANS: Avatar upload form instruction after uploading a file. | ||||||
|         return _('Pick a square area of the image to be your avatar.'); |         return _m('Pick a square area of the image to be your avatar.'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Handle the results of jcrop. |      * Handle the results of jcrop. | ||||||
|      * |      * | ||||||
|      * @return void |      * @return string | ||||||
|  |      * @throws NoResultException | ||||||
|  |      * @throws ServerException | ||||||
|  |      * @throws UnsupportedMediaException | ||||||
|  |      * | ||||||
|  |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     public function cropAvatar() |     public function cropAvatar() | ||||||
|     { |     { | ||||||
| @@ -304,30 +339,34 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|  |  | ||||||
|         if (empty($filedata)) { |         if (empty($filedata)) { | ||||||
|             // TRANS: Server error displayed if an avatar upload went wrong somehow server side. |             // TRANS: Server error displayed if an avatar upload went wrong somehow server side. | ||||||
|             throw new ServerException(_('Lost our file data.')); |             throw new ServerException(_m('Lost our file data.')); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $file_d = min($filedata['width'],  $filedata['height']); |         $file_d = min($filedata['width'], $filedata['height']); | ||||||
|  |  | ||||||
|         $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0; |         $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x') : 0; | ||||||
|         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; |         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y') : 0; | ||||||
|         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d; |         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w') : $file_d; | ||||||
|         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$file_d; |         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h') : $file_d; | ||||||
|         $size = intval(min($dest_w, $dest_h, common_config('avatar', 'maxsize'))); |         $size = (int)(min($dest_w, $dest_h, common_config('avatar', 'maxsize'))); | ||||||
|  |  | ||||||
|         $box = array('width' => $size, 'height' => $size, |         $box = ['width' => $size, 'height' => $size, | ||||||
|                      'x' => $dest_x,   'y' => $dest_y, |             'x' => $dest_x, 'y' => $dest_y, | ||||||
|                      'w' => $dest_w,   'h' => $dest_h); |             'w' => $dest_w, 'h' => $dest_h,]; | ||||||
|  |  | ||||||
|         $imagefile = new ImageFile(null, $filedata['filepath']); |         $imagefile = new ImageFile(null, $filedata['filepath']); | ||||||
|         $filename = Avatar::filename($this->scoped->getID(), image_type_to_extension($imagefile->preferredType()), |         $filename = Avatar::filename( | ||||||
|                                      $size, common_timestamp()); |             $this->scoped->getID(), | ||||||
|  |             image_type_to_extension($imagefile->preferredType()), | ||||||
|  |             $size, | ||||||
|  |             common_timestamp() | ||||||
|  |         ); | ||||||
|         try { |         try { | ||||||
|             $imagefile->resizeTo(Avatar::path($filename), $box); |             $imagefile->resizeTo(Avatar::path($filename), $box); | ||||||
|         } catch (UseFileAsThumbnailException $e) { |         } catch (UseFileAsThumbnailException $e) { | ||||||
|             common_debug('Using uploaded avatar directly without resizing, copying it to: '.$filename); |             common_debug('Using uploaded avatar directly without resizing, copying it to: ' . $filename); | ||||||
|             if (!copy($filedata['filepath'], Avatar::path($filename))) { |             if (!copy($filedata['filepath'], Avatar::path($filename))) { | ||||||
|                 common_debug('Tried to copy image file '.$filedata['filepath'].' to destination '.Avatar::path($filename)); |                 common_debug('Tried to copy image file ' . $filedata['filepath'] . ' to destination ' . Avatar::path($filename)); | ||||||
|                 throw new ServerException('Could not copy file to destination.'); |                 throw new ServerException('Could not copy file to destination.'); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -337,24 +376,26 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|             unset($_SESSION['FILEDATA']); |             unset($_SESSION['FILEDATA']); | ||||||
|             $this->mode = 'upload'; |             $this->mode = 'upload'; | ||||||
|             // TRANS: Success message for having updated a user avatar. |             // TRANS: Success message for having updated a user avatar. | ||||||
|             return _('Avatar updated.'); |             return _m('Avatar updated.'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason. |         // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason. | ||||||
|         throw new ServerException(_('Failed updating avatar.')); |         throw new ServerException(_m('Failed updating avatar.')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Get rid of the current avatar. |      * Get rid of the current avatar. | ||||||
|      * |      * | ||||||
|      * @return void |      * @return string | ||||||
|  |      * @throws Exception | ||||||
|  |      * | ||||||
|      */ |      */ | ||||||
|     function deleteAvatar() |     public function deleteAvatar() | ||||||
|     { |     { | ||||||
|         Avatar::deleteFromProfile($this->scoped); |         Avatar::deleteFromProfile($this->scoped); | ||||||
|  |  | ||||||
|         // TRANS: Success message for deleting a user avatar. |         // TRANS: Success message for deleting a user avatar. | ||||||
|         return _('Avatar deleted.'); |         return _m('Avatar deleted.'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -362,11 +403,10 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|      * |      * | ||||||
|      * @return void |      * @return void | ||||||
|      */ |      */ | ||||||
|  |     public function showStylesheets() | ||||||
|     function showStylesheets() |  | ||||||
|     { |     { | ||||||
|         parent::showStylesheets(); |         parent::showStylesheets(); | ||||||
|         $this->cssLink('js/extlib/jquery-jcrop/css/jcrop.css','base','screen, projection, tv'); |         $this->cssLink('js/extlib/jquery-jcrop/css/jcrop.css', 'base', 'screen, projection, tv'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -374,7 +414,7 @@ class AvatarsettingsAction extends SettingsAction | |||||||
|      * |      * | ||||||
|      * @return void |      * @return void | ||||||
|      */ |      */ | ||||||
|     function showScripts() |     public function showScripts() | ||||||
|     { |     { | ||||||
|         parent::showScripts(); |         parent::showScripts(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,10 +24,9 @@ | |||||||
|  * @author    Zach Copley <zach@status.net> |  * @author    Zach Copley <zach@status.net> | ||||||
|  * @author    Mikael Nordfeldth <mmn@hethane.se> |  * @author    Mikael Nordfeldth <mmn@hethane.se> | ||||||
|  * @author    Miguel Dantas <biodantasgs@gmail.com> |  * @author    Miguel Dantas <biodantasgs@gmail.com> | ||||||
|  * @copyright 2008, 2019 Free Software Foundation http://fsf.org |  * @author    Diogo Cordeiro <diogo@fc.up.pt> | ||||||
|  |  * @copyright 2008, 2019-2020 Free Software Foundation http://fsf.org | ||||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * |  | ||||||
|  * @see       https://www.gnu.org/software/social/ |  | ||||||
|  */ |  */ | ||||||
| defined('GNUSOCIAL') || die(); | defined('GNUSOCIAL') || die(); | ||||||
|  |  | ||||||
| @@ -40,6 +39,7 @@ use Intervention\Image\ImageManagerStatic as Image; | |||||||
|  * |  * | ||||||
|  * @category Image |  * @category Image | ||||||
|  * @package  GNUsocial |  * @package  GNUsocial | ||||||
|  |  * | ||||||
|  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * @author   Evan Prodromou <evan@status.net> |  * @author   Evan Prodromou <evan@status.net> | ||||||
|  * @author   Zach Copley <zach@status.net> |  * @author   Zach Copley <zach@status.net> | ||||||
| @@ -51,9 +51,9 @@ class ImageFile extends MediaFile | |||||||
|     public $type; |     public $type; | ||||||
|     public $height; |     public $height; | ||||||
|     public $width; |     public $width; | ||||||
|     public $rotate   = 0;    // degrees to rotate for properly oriented image (extrapolated from EXIF etc.) |     public $rotate = 0;    // degrees to rotate for properly oriented image (extrapolated from EXIF etc.) | ||||||
|     public $animated = null; // Animated image? (has more than 1 frame). null means untested |     public $animated; // Animated image? (has more than 1 frame). null means untested | ||||||
|     public $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object |     public $mimetype; // The _ImageFile_ mimetype, _not_ the originating File object | ||||||
|  |  | ||||||
|     public function __construct($id, string $filepath) |     public function __construct($id, string $filepath) | ||||||
|     { |     { | ||||||
| @@ -64,7 +64,7 @@ class ImageFile extends MediaFile | |||||||
|         $this->filepath = $filepath; |         $this->filepath = $filepath; | ||||||
|         $this->filename = basename($filepath); |         $this->filename = basename($filepath); | ||||||
|  |  | ||||||
|         $img            = Image::make($this->filepath); |         $img = Image::make($this->filepath); | ||||||
|         $this->mimetype = $img->mime(); |         $this->mimetype = $img->mime(); | ||||||
|  |  | ||||||
|         $cmp = function ($obj, $type) { |         $cmp = function ($obj, $type) { | ||||||
| @@ -74,18 +74,13 @@ class ImageFile extends MediaFile | |||||||
|             } |             } | ||||||
|             return false; |             return false; | ||||||
|         }; |         }; | ||||||
|         if (!(($cmp($this, IMAGETYPE_GIF)  && function_exists('imagecreatefromgif'))  || |         if (!(($cmp($this, IMAGETYPE_GIF) && function_exists('imagecreatefromgif')) || ($cmp($this, IMAGETYPE_JPEG) && function_exists('imagecreatefromjpeg')) || ($cmp($this, IMAGETYPE_BMP) && function_exists('imagecreatefrombmp')) || ($cmp($this, IMAGETYPE_WBMP) && function_exists('imagecreatefromwbmp')) || ($cmp($this, IMAGETYPE_XBM) && function_exists('imagecreatefromxbm')) || ($cmp($this, IMAGETYPE_PNG) && function_exists('imagecreatefrompng')))) { | ||||||
|               ($cmp($this, IMAGETYPE_JPEG) && function_exists('imagecreatefromjpeg')) || |  | ||||||
|               ($cmp($this, IMAGETYPE_BMP)  && function_exists('imagecreatefrombmp'))  || |  | ||||||
|               ($cmp($this, IMAGETYPE_WBMP) && function_exists('imagecreatefromwbmp')) || |  | ||||||
|               ($cmp($this, IMAGETYPE_XBM)  && function_exists('imagecreatefromxbm'))  || |  | ||||||
|               ($cmp($this, IMAGETYPE_PNG)  && function_exists('imagecreatefrompng')))) { |  | ||||||
|             common_debug("Mimetype '{$this->mimetype}' was not recognized as a supported format"); |             common_debug("Mimetype '{$this->mimetype}' was not recognized as a supported format"); | ||||||
|             // TRANS: Exception thrown when trying to upload an unsupported image file format. |             // TRANS: Exception thrown when trying to upload an unsupported image file format. | ||||||
|             throw new UnsupportedMediaException(_m('Unsupported image format.'), $this->filepath); |             throw new UnsupportedMediaException(_m('Unsupported image format.'), $this->filepath); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $this->width  = $img->width(); |         $this->width = $img->width(); | ||||||
|         $this->height = $img->height(); |         $this->height = $img->height(); | ||||||
|  |  | ||||||
|         parent::__construct( |         parent::__construct( | ||||||
| @@ -99,19 +94,19 @@ class ImageFile extends MediaFile | |||||||
|             // Orientation value to rotate thumbnails properly |             // Orientation value to rotate thumbnails properly | ||||||
|             $exif = @$img->exif(); |             $exif = @$img->exif(); | ||||||
|             if (is_array($exif) && isset($exif['Orientation'])) { |             if (is_array($exif) && isset($exif['Orientation'])) { | ||||||
|                 switch ((int) ($exif['Orientation'])) { |                 switch ((int)($exif['Orientation'])) { | ||||||
|                 case 1: // top is top |                     case 1: // top is top | ||||||
|                     $this->rotate = 0; |                         $this->rotate = 0; | ||||||
|                     break; |                         break; | ||||||
|                 case 3: // top is bottom |                     case 3: // top is bottom | ||||||
|                     $this->rotate = 180; |                         $this->rotate = 180; | ||||||
|                     break; |                         break; | ||||||
|                 case 6: // top is right |                     case 6: // top is right | ||||||
|                     $this->rotate = -90; |                         $this->rotate = -90; | ||||||
|                     break; |                         break; | ||||||
|                 case 8: // top is left |                     case 8: // top is left | ||||||
|                     $this->rotate = 90; |                         $this->rotate = 90; | ||||||
|                     break; |                         break; | ||||||
|                 } |                 } | ||||||
|                 // If we ever write this back, Orientation should be set to '1' |                 // If we ever write this back, Orientation should be set to '1' | ||||||
|             } |             } | ||||||
| @@ -133,7 +128,7 @@ class ImageFile extends MediaFile | |||||||
|     public static function fromFileObject(File $file) |     public static function fromFileObject(File $file) | ||||||
|     { |     { | ||||||
|         $imgPath = null; |         $imgPath = null; | ||||||
|         $media   = common_get_mime_media($file->mimetype); |         $media = common_get_mime_media($file->mimetype); | ||||||
|         if (Event::handle('CreateFileImageThumbnailSource', [$file, &$imgPath, $media])) { |         if (Event::handle('CreateFileImageThumbnailSource', [$file, &$imgPath, $media])) { | ||||||
|             if (empty($file->filename) && !file_exists($imgPath)) { |             if (empty($file->filename) && !file_exists($imgPath)) { | ||||||
|                 throw new FileNotFoundException($imgPath); |                 throw new FileNotFoundException($imgPath); | ||||||
| @@ -141,8 +136,8 @@ class ImageFile extends MediaFile | |||||||
|  |  | ||||||
|             // First some mimetype specific exceptions |             // First some mimetype specific exceptions | ||||||
|             switch ($file->mimetype) { |             switch ($file->mimetype) { | ||||||
|             case 'image/svg+xml': |                 case 'image/svg+xml': | ||||||
|                 throw new UseFileAsThumbnailException($file); |                     throw new UseFileAsThumbnailException($file); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // And we'll only consider it an image if it has such a media type |             // And we'll only consider it an image if it has such a media type | ||||||
| @@ -165,7 +160,7 @@ class ImageFile extends MediaFile | |||||||
|             try { |             try { | ||||||
|                 if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) { |                 if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) { | ||||||
|                     common_debug(__METHOD__ . ': Deleting temporary file that was created as image file' . |                     common_debug(__METHOD__ . ': Deleting temporary file that was created as image file' . | ||||||
|                                  'thumbnail source: ' . _ve($imgPath)); |                         'thumbnail source: ' . _ve($imgPath)); | ||||||
|                     @unlink($imgPath); |                     @unlink($imgPath); | ||||||
|                 } |                 } | ||||||
|             } catch (FileNotFoundException $e) { |             } catch (FileNotFoundException $e) { | ||||||
| @@ -175,7 +170,7 @@ class ImageFile extends MediaFile | |||||||
|             } |             } | ||||||
|             common_debug(sprintf( |             common_debug(sprintf( | ||||||
|                 'Exception %s caught when creating ImageFile for File id==%s ' . |                 'Exception %s caught when creating ImageFile for File id==%s ' . | ||||||
|                                  'and imgPath==%s: %s', |                 'and imgPath==%s: %s', | ||||||
|                 get_class($e), |                 get_class($e), | ||||||
|                 _ve($file->id), |                 _ve($file->id), | ||||||
|                 _ve($imgPath), |                 _ve($imgPath), | ||||||
| @@ -198,24 +193,34 @@ class ImageFile extends MediaFile | |||||||
|     /** |     /** | ||||||
|      * Process a file upload |      * Process a file upload | ||||||
|      * |      * | ||||||
|      * Uses MediaFile's `fromUpload` to do the majority of the work and reencodes the image, |      * Uses MediaFile's `fromUpload` to do the majority of the work | ||||||
|      * to mitigate injection attacks. |      * and ensures the uploaded file is in fact an image. | ||||||
|      * |      * | ||||||
|      * @param string       $param |      * @param string $param | ||||||
|      * @param null|Profile $scoped |      * @param null|Profile $scoped | ||||||
|      * |      * | ||||||
|      * @throws ClientException |      * @return ImageFile | ||||||
|      * @throws NoResultException |      * @throws NoResultException | ||||||
|      * @throws NoUploadedMediaException |      * @throws NoUploadedMediaException | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|      * @throws UnsupportedMediaException |      * @throws UnsupportedMediaException | ||||||
|      * @throws UseFileAsThumbnailException |      * @throws UseFileAsThumbnailException | ||||||
|      * |      * | ||||||
|      * @return ImageFile|MediaFile |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     public static function fromUpload(string $param = 'upload', Profile $scoped = null) |     public static function fromUpload(string $param = 'upload', ?Profile $scoped = null): self | ||||||
|     { |     { | ||||||
|         return parent::fromUpload($param, $scoped); |         $mediafile = parent::fromUpload($param, $scoped); | ||||||
|  |         if ($mediafile instanceof self) { | ||||||
|  |             return $mediafile; | ||||||
|  |         } else { | ||||||
|  |             // We can conclude that we have failed to get the MIME type | ||||||
|  |             // TRANS: Client exception thrown trying to upload an invalid image type. | ||||||
|  |             // TRANS: %s is the file type that was denied | ||||||
|  |             $hint = sprintf(_m('"%s" is not a supported file type on this server. ' . | ||||||
|  |                 'Try using another image format.'), $mediafile->mimetype); | ||||||
|  |             throw new ClientException($hint); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -227,7 +232,7 @@ class ImageFile extends MediaFile | |||||||
|      */ |      */ | ||||||
|     public function preferredType() |     public function preferredType() | ||||||
|     { |     { | ||||||
|         // Keep only JPEG and GIF in their orignal format |         // Keep only JPEG and GIF in their original format | ||||||
|         if ($this->type === IMAGETYPE_JPEG || $this->type === IMAGETYPE_GIF) { |         if ($this->type === IMAGETYPE_JPEG || $this->type === IMAGETYPE_GIF) { | ||||||
|             return $this->type; |             return $this->type; | ||||||
|         } |         } | ||||||
| @@ -245,13 +250,13 @@ class ImageFile extends MediaFile | |||||||
|      * |      * | ||||||
|      * @param string $outpath |      * @param string $outpath | ||||||
|      * |      * | ||||||
|      * @throws ClientException |      * @return ImageFile the image stored at target path | ||||||
|      * @throws NoResultException |      * @throws NoResultException | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|      * @throws UnsupportedMediaException |      * @throws UnsupportedMediaException | ||||||
|      * @throws UseFileAsThumbnailException |      * @throws UseFileAsThumbnailException | ||||||
|      * |      * | ||||||
|      * @return ImageFile the image stored at target path |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     public function copyTo($outpath) |     public function copyTo($outpath) | ||||||
|     { |     { | ||||||
| @@ -263,20 +268,21 @@ class ImageFile extends MediaFile | |||||||
|      * |      * | ||||||
|      * @param string $outpath |      * @param string $outpath | ||||||
|      * @param array $box width, height, boundary box (x,y,w,h) defaults to full image |      * @param array $box width, height, boundary box (x,y,w,h) defaults to full image | ||||||
|  |      * | ||||||
|  |      * @return string full local filesystem filename | ||||||
|      * @return string full local filesystem filename |      * @return string full local filesystem filename | ||||||
|      * @throws UnsupportedMediaException |      * @throws UnsupportedMediaException | ||||||
|      * @throws UseFileAsThumbnailException |      * @throws UseFileAsThumbnailException | ||||||
|      * |      * | ||||||
|      * @return string full local filesystem filename |  | ||||||
|      */ |      */ | ||||||
|     public function resizeTo($outpath, array $box = []) |     public function resizeTo($outpath, array $box = []) | ||||||
|     { |     { | ||||||
|         $box['width']  = isset($box['width'])  ? intval($box['width'])  : $this->width; |         $box['width'] = isset($box['width']) ? (int)($box['width']) : $this->width; | ||||||
|         $box['height'] = isset($box['height']) ? intval($box['height']) : $this->height; |         $box['height'] = isset($box['height']) ? (int)($box['height']) : $this->height; | ||||||
|         $box['x']      = isset($box['x'])      ? intval($box['x'])      : 0; |         $box['x'] = isset($box['x']) ? (int)($box['x']) : 0; | ||||||
|         $box['y']      = isset($box['y'])      ? intval($box['y'])      : 0; |         $box['y'] = isset($box['y']) ? (int)($box['y']) : 0; | ||||||
|         $box['w']      = isset($box['w'])      ? intval($box['w'])      : $this->width; |         $box['w'] = isset($box['w']) ? (int)($box['w']) : $this->width; | ||||||
|         $box['h']      = isset($box['h'])      ? intval($box['h'])      : $this->height; |         $box['h'] = isset($box['h']) ? (int)($box['h']) : $this->height; | ||||||
|  |  | ||||||
|         if (!file_exists($this->filepath)) { |         if (!file_exists($this->filepath)) { | ||||||
|             // TRANS: Exception thrown during resize when image has been registered as present, |             // TRANS: Exception thrown during resize when image has been registered as present, | ||||||
| @@ -285,13 +291,13 @@ class ImageFile extends MediaFile | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Don't rotate/crop/scale if it isn't necessary |         // Don't rotate/crop/scale if it isn't necessary | ||||||
|         if ($box['width']     === $this->width |         if ($box['width'] === $this->width | ||||||
|             && $box['height'] === $this->height |             && $box['height'] === $this->height | ||||||
|             && $box['x']      === 0 |             && $box['x'] === 0 | ||||||
|             && $box['y']      === 0 |             && $box['y'] === 0 | ||||||
|             && $box['w']      === $this->width |             && $box['w'] === $this->width | ||||||
|             && $box['h']      === $this->height |             && $box['h'] === $this->height | ||||||
|             && $this->type    === $this->preferredType()) { |             && $this->type === $this->preferredType()) { | ||||||
|             if (abs($this->rotate) == 90) { |             if (abs($this->rotate) == 90) { | ||||||
|                 // Box is rotated 90 degrees in either direction, |                 // Box is rotated 90 degrees in either direction, | ||||||
|                 // so we have to redefine x to y and vice versa. |                 // so we have to redefine x to y and vice versa. | ||||||
| @@ -308,7 +314,7 @@ class ImageFile extends MediaFile | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         $this->height = $box['h']; |         $this->height = $box['h']; | ||||||
|         $this->width  = $box['w']; |         $this->width = $box['w']; | ||||||
|  |  | ||||||
|         if (Event::handle('StartResizeImageFile', [$this, $outpath, $box])) { |         if (Event::handle('StartResizeImageFile', [$this, $outpath, $box])) { | ||||||
|             $outpath = $this->resizeToFile($outpath, $box); |             $outpath = $this->resizeToFile($outpath, $box); | ||||||
| @@ -346,7 +352,7 @@ class ImageFile extends MediaFile | |||||||
|         try { |         try { | ||||||
|             $img = Image::make($this->filepath); |             $img = Image::make($this->filepath); | ||||||
|         } catch (Exception $e) { |         } catch (Exception $e) { | ||||||
|             common_log(LOG_ERR, __METHOD__ . ' ecountered exception: ' . print_r($e, true)); |             common_log(LOG_ERR, __METHOD__ . ' encountered exception: ' . print_r($e, true)); | ||||||
|             // TRANS: Exception thrown when trying to resize an unknown file type. |             // TRANS: Exception thrown when trying to resize an unknown file type. | ||||||
|             throw new Exception(_m('Unknown file type')); |             throw new Exception(_m('Unknown file type')); | ||||||
|         } |         } | ||||||
| @@ -359,29 +365,31 @@ class ImageFile extends MediaFile | |||||||
|             $img = $img->orientate(); |             $img = $img->orientate(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $img->fit($box['width'], $box['height'], |         $img->fit( | ||||||
|                   function ($constraint) { |             $box['width'], | ||||||
|                       if (common_config('attachments', 'upscale') !== true) { |             $box['height'], | ||||||
|                           $constraint->upsize(); // Prevent upscaling |             function ($constraint) { | ||||||
|                       } |                 if (common_config('attachments', 'upscale') !== true) { | ||||||
|                   } |                     $constraint->upsize(); // Prevent upscaling | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         // Ensure we save in the correct format and allow customization based on type |         // Ensure we save in the correct format and allow customization based on type | ||||||
|         $type = $this->preferredType(); |         $type = $this->preferredType(); | ||||||
|         switch ($type) { |         switch ($type) { | ||||||
|         case IMAGETYPE_GIF: |             case IMAGETYPE_GIF: | ||||||
|             $img->save($outpath, 100, 'gif'); |                 $img->save($outpath, 100, 'gif'); | ||||||
|             break; |                 break; | ||||||
|         case IMAGETYPE_PNG: |             case IMAGETYPE_PNG: | ||||||
|             $img->save($outpath, 100, 'png'); |                 $img->save($outpath, 100, 'png'); | ||||||
|             break; |                 break; | ||||||
|          case IMAGETYPE_JPEG: |             case IMAGETYPE_JPEG: | ||||||
|              $img->save($outpath, common_config('image', 'jpegquality'), 'jpg'); |                 $img->save($outpath, common_config('image', 'jpegquality'), 'jpg'); | ||||||
|             break; |                 break; | ||||||
|          default: |             default: | ||||||
|             // TRANS: Exception thrown when trying resize an unknown file type. |                 // TRANS: Exception thrown when trying resize an unknown file type. | ||||||
|             throw new Exception(_m('Unknown file type')); |                 throw new Exception(_m('Unknown file type')); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $img->destroy(); |         $img->destroy(); | ||||||
| @@ -421,9 +429,9 @@ class ImageFile extends MediaFile | |||||||
|      * @param $crop     int Crop to the size (not preserving aspect ratio) |      * @param $crop     int Crop to the size (not preserving aspect ratio) | ||||||
|      * @param int $rotate |      * @param int $rotate | ||||||
|      * |      * | ||||||
|  |      * @return array | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|      * |      * | ||||||
|      * @return array |  | ||||||
|      */ |      */ | ||||||
|     public static function getScalingValues( |     public static function getScalingValues( | ||||||
|         $width, |         $width, | ||||||
| @@ -447,8 +455,8 @@ class ImageFile extends MediaFile | |||||||
|  |  | ||||||
|         // Because GD doesn't understand EXIF orientation etc. |         // Because GD doesn't understand EXIF orientation etc. | ||||||
|         if (abs($rotate) == 90) { |         if (abs($rotate) == 90) { | ||||||
|             $tmp    = $width; |             $tmp = $width; | ||||||
|             $width  = $height; |             $width = $height; | ||||||
|             $height = $tmp; |             $height = $tmp; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -461,7 +469,7 @@ class ImageFile extends MediaFile | |||||||
|  |  | ||||||
|         if ($crop) { |         if ($crop) { | ||||||
|             $s_ar = $width / $height; |             $s_ar = $width / $height; | ||||||
|             $t_ar = $maxW  / $maxH; |             $t_ar = $maxW / $maxH; | ||||||
|  |  | ||||||
|             $rw = $maxW; |             $rw = $maxW; | ||||||
|             $rh = $maxH; |             $rh = $maxH; | ||||||
| @@ -484,10 +492,10 @@ class ImageFile extends MediaFile | |||||||
|                 $rw = ceil($width * $rh / $height); |                 $rw = ceil($width * $rh / $height); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return array(intval($rw), intval($rh), |         return [(int)$rw, (int)$rh, | ||||||
|                      intval($cx), intval($cy), |             (int)$cx, (int)$cy, | ||||||
|                      is_null($cw) ? $width : intval($cw), |             is_null($cw) ? $width : (int)$cw, | ||||||
|                      is_null($ch) ? $height : intval($ch)); |             is_null($ch) ? $height : (int)$ch,]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -536,9 +544,9 @@ class ImageFile extends MediaFile | |||||||
|         $filename = basename($this->filepath); |         $filename = basename($this->filepath); | ||||||
|  |  | ||||||
|         if ($width === null) { |         if ($width === null) { | ||||||
|             $width  = common_config('thumbnail', 'width'); |             $width = common_config('thumbnail', 'width'); | ||||||
|             $height = common_config('thumbnail', 'height'); |             $height = common_config('thumbnail', 'height'); | ||||||
|             $crop   = common_config('thumbnail', 'crop'); |             $crop = common_config('thumbnail', 'crop'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!$upscale) { |         if (!$upscale) { | ||||||
| @@ -552,7 +560,7 @@ class ImageFile extends MediaFile | |||||||
|  |  | ||||||
|         if ($height === null) { |         if ($height === null) { | ||||||
|             $height = $width; |             $height = $width; | ||||||
|             $crop   = true; |             $crop = true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Get proper aspect ratio width and height before lookup |         // Get proper aspect ratio width and height before lookup | ||||||
| @@ -563,18 +571,18 @@ class ImageFile extends MediaFile | |||||||
|  |  | ||||||
|         $thumb = File_thumbnail::pkeyGet([ |         $thumb = File_thumbnail::pkeyGet([ | ||||||
|             'file_id' => $this->fileRecord->getID(), |             'file_id' => $this->fileRecord->getID(), | ||||||
|             'width'   => $width, |             'width' => $width, | ||||||
|             'height'  => $height, |             'height' => $height, | ||||||
|         ]); |         ]); | ||||||
|  |  | ||||||
|         if ($thumb instanceof File_thumbnail) { |         if ($thumb instanceof File_thumbnail) { | ||||||
|             $this->height = $height; |             $this->height = $height; | ||||||
|             $this->width  = $width; |             $this->width = $width; | ||||||
|             return $thumb; |             return $thumb; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $type = $this->preferredType(); |         $type = $this->preferredType(); | ||||||
|         $ext  = image_type_to_extension($type, true); |         $ext = image_type_to_extension($type, true); | ||||||
|         // Decoding returns null if the file is in the old format |         // Decoding returns null if the file is in the old format | ||||||
|         $filename = MediaFile::decodeFilename(basename($this->filepath)); |         $filename = MediaFile::decodeFilename(basename($this->filepath)); | ||||||
|         // Encoding null makes the file use 'untitled', and also replaces the extension |         // Encoding null makes the file use 'untitled', and also replaces the extension | ||||||
| @@ -583,18 +591,19 @@ class ImageFile extends MediaFile | |||||||
|         // The boundary box for our resizing |         // The boundary box for our resizing | ||||||
|         $box = [ |         $box = [ | ||||||
|             'width' => $width, 'height' => $height, |             'width' => $width, 'height' => $height, | ||||||
|             'x'     => $x,         'y'  => $y, |             'x' => $x, 'y' => $y, | ||||||
|             'w'     => $w,         'h'  => $h, |             'w' => $w, 'h' => $h, | ||||||
|         ]; |         ]; | ||||||
|  |  | ||||||
|         $outpath = File_thumbnail::path( |         $outpath = File_thumbnail::path( | ||||||
|             "thumb-{$this->fileRecord->id}-{$box['width']}x{$box['height']}-{$outfilename}"); |             "thumb-{$this->fileRecord->id}-{$box['width']}x{$box['height']}-{$outfilename}" | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         // Doublecheck that parameters are sane and integers. |         // Doublecheck that parameters are sane and integers. | ||||||
|         if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize') |         if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize') | ||||||
|                 || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize') |             || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize') | ||||||
|                 || $box['w'] < 1 || $box['x'] >= $this->width |             || $box['w'] < 1 || $box['x'] >= $this->width | ||||||
|                 || $box['h'] < 1 || $box['y'] >= $this->height) { |             || $box['h'] < 1 || $box['y'] >= $this->height) { | ||||||
|             // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit |             // 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)); |             common_debug("Boundary box parameters for resize of {$this->filepath} : " . var_export($box, true)); | ||||||
|             throw new ServerException('Bad thumbnail size parameters.'); |             throw new ServerException('Bad thumbnail size parameters.'); | ||||||
| @@ -608,7 +617,7 @@ class ImageFile extends MediaFile | |||||||
|         )); |         )); | ||||||
|  |  | ||||||
|         $this->height = $box['height']; |         $this->height = $box['height']; | ||||||
|         $this->width  = $box['width']; |         $this->width = $box['width']; | ||||||
|  |  | ||||||
|         // Perform resize and store into file |         // Perform resize and store into file | ||||||
|         $outpath = $this->resizeTo($outpath, $box); |         $outpath = $this->resizeTo($outpath, $box); | ||||||
|   | |||||||
| @@ -1,33 +1,33 @@ | |||||||
| <?php | <?php | ||||||
|  | // This file is part of GNU social - https://www.gnu.org/software/social | ||||||
|  | // | ||||||
|  | // GNU social 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. | ||||||
|  | // | ||||||
|  | // GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * GNU social - a federating social network |  | ||||||
|  * |  | ||||||
|  * Abstraction for media files |  * Abstraction for media files | ||||||
|  * |  * | ||||||
|  * 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  Media |  * @category  Media | ||||||
|  * @package   GNUsocial |  * @package   GNUsocial | ||||||
|  |  * | ||||||
|  * @author    Robin Millette <robin@millette.info> |  * @author    Robin Millette <robin@millette.info> | ||||||
|  * @author    Miguel Dantas <biodantas@gmail.com> |  * @author    Miguel Dantas <biodantas@gmail.com> | ||||||
|  * @author    Zach Copley <zach@status.net> |  * @author    Zach Copley <zach@status.net> | ||||||
|  * @author    Mikael Nordfeldth <mmn@hethane.se> |  * @author    Mikael Nordfeldth <mmn@hethane.se> | ||||||
|  * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org |  * @author    Diogo Cordeiro <diogo@fc.up.pt> | ||||||
|  |  * @copyright 2008-2009, 2019-2020 Free Software Foundation http://fsf.org | ||||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * @link      https://www.gnu.org/software/social/ |  | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| defined('GNUSOCIAL') || die(); | defined('GNUSOCIAL') || die(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -35,21 +35,22 @@ defined('GNUSOCIAL') || die(); | |||||||
|  */ |  */ | ||||||
| class MediaFile | class MediaFile | ||||||
| { | { | ||||||
|     public $id            = null; |     public $id; | ||||||
|     public $filepath      = null; |     public $filepath; | ||||||
|     public $filename      = null; |     public $filename; | ||||||
|     public $fileRecord    = null; |     public $fileRecord; | ||||||
|     public $fileurl       = null; |     public $fileurl; | ||||||
|     public $short_fileurl = null; |     public $short_fileurl; | ||||||
|     public $mimetype      = null; |     public $mimetype; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param string $filepath The path of the file this media refers to. Required |      * @param string $filepath The path of the file this media refers to. Required | ||||||
|      * @param string $mimetype The mimetype of the file. Required |      * @param string $mimetype The mimetype of the file. Required | ||||||
|      * @param $filehash        The hash of the file, if known. Optional |      * @param string $filehash The hash of the file, if known. Optional | ||||||
|      * @param int|null $id     The DB id of the file. Int if known, null if not. |      * @param null|int $id The DB id of the file. Int if known, null if not. | ||||||
|      *                         If null, it searches for it. If -1, it skips all DB |      *                     If null, it searches for it. If -1, it skips all DB | ||||||
|      *                         interactions (useful for temporary objects) |      *                     interactions (useful for temporary objects) | ||||||
|  |      * | ||||||
|      * @throws ClientException |      * @throws ClientException | ||||||
|      * @throws NoResultException |      * @throws NoResultException | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
| @@ -60,7 +61,7 @@ class MediaFile | |||||||
|         $this->filename = basename($this->filepath); |         $this->filename = basename($this->filepath); | ||||||
|         $this->mimetype = $mimetype; |         $this->mimetype = $mimetype; | ||||||
|         $this->filehash = self::getHashOfFile($this->filepath, $filehash); |         $this->filehash = self::getHashOfFile($this->filepath, $filehash); | ||||||
|         $this->id       = $id; |         $this->id = $id; | ||||||
|  |  | ||||||
|         // If id is -1, it means we're dealing with a temporary object and don't want to store it in the DB, |         // If id is -1, it means we're dealing with a temporary object and don't want to store it in the DB, | ||||||
|         // or add redirects |         // or add redirects | ||||||
| @@ -80,7 +81,7 @@ class MediaFile | |||||||
|  |  | ||||||
|             $this->fileurl = common_local_url( |             $this->fileurl = common_local_url( | ||||||
|                 'attachment', |                 'attachment', | ||||||
|                 array('attachment' => $this->fileRecord->id) |                 ['attachment' => $this->fileRecord->id] | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|             $this->short_fileurl = common_shorten_url($this->fileurl); |             $this->short_fileurl = common_shorten_url($this->fileurl); | ||||||
| @@ -125,14 +126,17 @@ class MediaFile | |||||||
|      * Calculate the hash of a file. |      * Calculate the hash of a file. | ||||||
|      * |      * | ||||||
|      * This won't work for files >2GiB because PHP uses only 32bit. |      * This won't work for files >2GiB because PHP uses only 32bit. | ||||||
|  |      * | ||||||
|      * @param string $filepath |      * @param string $filepath | ||||||
|      * @param string|null $filehash |      * @param null|string $filehash | ||||||
|  |      * | ||||||
|      * @return string |      * @return string | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|  |      * | ||||||
|      */ |      */ | ||||||
|     public static function getHashOfFile(string $filepath, $filehash = null) |     public static function getHashOfFile(string $filepath, $filehash = null) | ||||||
|     { |     { | ||||||
|         assert(!empty($filepath), __METHOD__ . ": filepath cannot be null"); |         assert(!empty($filepath), __METHOD__ . ': filepath cannot be null'); | ||||||
|         if ($filehash === null) { |         if ($filehash === null) { | ||||||
|             // Calculate if we have an older upload method somewhere (Qvitter) that |             // Calculate if we have an older upload method somewhere (Qvitter) that | ||||||
|             // doesn't do this before calling new MediaFile on its local files... |             // doesn't do this before calling new MediaFile on its local files... | ||||||
| @@ -148,8 +152,9 @@ class MediaFile | |||||||
|      * Retrieve or insert as a file in the DB |      * Retrieve or insert as a file in the DB | ||||||
|      * |      * | ||||||
|      * @return object File |      * @return object File | ||||||
|      * @throws ClientException |  | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|  |      * | ||||||
|  |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     protected function storeFile() |     protected function storeFile() | ||||||
|     { |     { | ||||||
| @@ -161,25 +166,25 @@ class MediaFile | |||||||
|             // Well, let's just continue below. |             // Well, let's just continue below. | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $fileurl = common_local_url('attachment_view', array('filehash' => $this->filehash)); |         $fileurl = common_local_url('attachment_view', ['filehash' => $this->filehash]); | ||||||
|  |  | ||||||
|         $file = new File; |         $file = new File; | ||||||
|  |  | ||||||
|         $file->filename = $this->filename; |         $file->filename = $this->filename; | ||||||
|         $file->urlhash  = File::hashurl($fileurl); |         $file->urlhash = File::hashurl($fileurl); | ||||||
|         $file->url      = $fileurl; |         $file->url = $fileurl; | ||||||
|         $file->filehash = $this->filehash; |         $file->filehash = $this->filehash; | ||||||
|         $file->size     = filesize($this->filepath); |         $file->size = filesize($this->filepath); | ||||||
|         if ($file->size === false) { |         if ($file->size === false) { | ||||||
|             throw new ServerException('Could not read file to get its size'); |             throw new ServerException('Could not read file to get its size'); | ||||||
|         } |         } | ||||||
|         $file->date     = time(); |         $file->date = time(); | ||||||
|         $file->mimetype = $this->mimetype; |         $file->mimetype = $this->mimetype; | ||||||
|  |  | ||||||
|         $file_id = $file->insert(); |         $file_id = $file->insert(); | ||||||
|  |  | ||||||
|         if ($file_id===false) { |         if ($file_id === false) { | ||||||
|             common_log_db_error($file, "INSERT", __FILE__); |             common_log_db_error($file, 'INSERT', __FILE__); | ||||||
|             // TRANS: Client exception thrown when a database error was thrown during a file upload operation. |             // TRANS: Client exception thrown when a database error was thrown during a file upload operation. | ||||||
|             throw new ClientException(_m('There was a database error while saving your file. Please try again.')); |             throw new ClientException(_m('There was a database error while saving your file. Please try again.')); | ||||||
|         } |         } | ||||||
| @@ -187,7 +192,7 @@ class MediaFile | |||||||
|         // Set file geometrical properties if available |         // Set file geometrical properties if available | ||||||
|         try { |         try { | ||||||
|             $image = ImageFile::fromFileObject($file); |             $image = ImageFile::fromFileObject($file); | ||||||
|             $orig = clone($file); |             $orig = clone $file; | ||||||
|             $file->width = $image->width; |             $file->width = $image->width; | ||||||
|             $file->height = $image->height; |             $file->height = $image->height; | ||||||
|             $file->update($orig); |             $file->update($orig); | ||||||
| @@ -215,11 +220,11 @@ class MediaFile | |||||||
|     { |     { | ||||||
|         $value = self::maxFileSizeInt(); |         $value = self::maxFileSizeInt(); | ||||||
|         if ($value > 1024 * 1024) { |         if ($value > 1024 * 1024) { | ||||||
|             $value = $value/(1024*1024); |             $value = $value / (1024 * 1024); | ||||||
|             // TRANS: Number of megabytes. %d is the number. |             // TRANS: Number of megabytes. %d is the number. | ||||||
|             return sprintf(_m('%dMB', '%dMB', $value), $value); |             return sprintf(_m('%dMB', '%dMB', $value), $value); | ||||||
|         } elseif ($value > 1024) { |         } elseif ($value > 1024) { | ||||||
|             $value = $value/1024; |             $value = $value / 1024; | ||||||
|             // TRANS: Number of kilobytes. %d is the number. |             // TRANS: Number of kilobytes. %d is the number. | ||||||
|             return sprintf(_m('%dkB', '%dkB', $value), $value); |             return sprintf(_m('%dkB', '%dkB', $value), $value); | ||||||
|         } else { |         } else { | ||||||
| @@ -231,7 +236,7 @@ class MediaFile | |||||||
|     /** |     /** | ||||||
|      * The maximum allowed file size, as an int |      * The maximum allowed file size, as an int | ||||||
|      */ |      */ | ||||||
|     public static function maxFileSizeInt() : int |     public static function maxFileSizeInt(): int | ||||||
|     { |     { | ||||||
|         return common_config('attachments', 'file_quota'); |         return common_config('attachments', 'file_quota'); | ||||||
|     } |     } | ||||||
| @@ -239,9 +244,13 @@ class MediaFile | |||||||
|     /** |     /** | ||||||
|      * Encodes a file name and a file hash in the new file format, which is used to avoid |      * Encodes a file name and a file hash in the new file format, which is used to avoid | ||||||
|      * having an extension in the file, removing trust in extensions, while keeping the original name |      * having an extension in the file, removing trust in extensions, while keeping the original name | ||||||
|  |      * | ||||||
|  |      * @param mixed $original_name | ||||||
|  |      * @param null|mixed $ext | ||||||
|  |      * | ||||||
|      * @throws ClientException |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     public static function encodeFilename($original_name, string $filehash, $ext = null) : string |     public static function encodeFilename($original_name, string $filehash, $ext = null): string | ||||||
|     { |     { | ||||||
|         if (empty($original_name)) { |         if (empty($original_name)) { | ||||||
|             $original_name = _m('Untitled attachment'); |             $original_name = _m('Untitled attachment'); | ||||||
| @@ -249,9 +258,9 @@ class MediaFile | |||||||
|  |  | ||||||
|         // If we're given an extension explicitly, use it, otherwise... |         // If we're given an extension explicitly, use it, otherwise... | ||||||
|         $ext = $ext ?: |         $ext = $ext ?: | ||||||
|              // get a replacement extension if configured, returns false if it's blocked, |             // get a replacement extension if configured, returns false if it's blocked, | ||||||
|              // null if no extension |             // null if no extension | ||||||
|              File::getSafeExtension($original_name); |             File::getSafeExtension($original_name); | ||||||
|         if ($ext === false) { |         if ($ext === false) { | ||||||
|             throw new ClientException(_m('Blacklisted file extension.')); |             throw new ClientException(_m('Blacklisted file extension.')); | ||||||
|         } |         } | ||||||
| @@ -268,6 +277,7 @@ class MediaFile | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Decode the new filename format |      * Decode the new filename format | ||||||
|  |      * | ||||||
|      * @return false | null | string on failure, no match (old format) or original file name, respectively |      * @return false | null | string on failure, no match (old format) or original file name, respectively | ||||||
|      */ |      */ | ||||||
|     public static function decodeFilename(string $encoded_filename) |     public static function decodeFilename(string $encoded_filename) | ||||||
| @@ -319,19 +329,21 @@ class MediaFile | |||||||
|      * format ("{$hash}.{$ext}") |      * format ("{$hash}.{$ext}") | ||||||
|      * |      * | ||||||
|      * @param string $param Form name |      * @param string $param Form name | ||||||
|      * @param Profile|null $scoped |      * @param null|Profile $scoped | ||||||
|  |      * | ||||||
|      * @return ImageFile|MediaFile |      * @return ImageFile|MediaFile | ||||||
|      * @throws ClientException |  | ||||||
|      * @throws NoResultException |      * @throws NoResultException | ||||||
|      * @throws NoUploadedMediaException |      * @throws NoUploadedMediaException | ||||||
|      * @throws ServerException |      * @throws ServerException | ||||||
|      * @throws UnsupportedMediaException |      * @throws UnsupportedMediaException | ||||||
|      * @throws UseFileAsThumbnailException |      * @throws UseFileAsThumbnailException | ||||||
|  |      * | ||||||
|  |      * @throws ClientException | ||||||
|      */ |      */ | ||||||
|     public static function fromUpload(string $param='media', Profile $scoped=null) |     public static function fromUpload(string $param = 'media', Profile $scoped = null) | ||||||
|     { |     { | ||||||
|         // The existence of the "error" element means PHP has processed it properly even if it was ok. |         // The existence of the "error" element means PHP has processed it properly even if it was ok. | ||||||
|         if (!(isset($_FILES[$param]) && isset($_FILES[$param]['error']))) { |         if (!(isset($_FILES[$param], $_FILES[$param]['error']))) { | ||||||
|             throw new NoUploadedMediaException($param); |             throw new NoUploadedMediaException($param); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -363,7 +375,7 @@ class MediaFile | |||||||
|                 // TRANS: Client exception thrown when a file upload operation has been stopped by an extension. |                 // TRANS: Client exception thrown when a file upload operation has been stopped by an extension. | ||||||
|                 throw new ClientException(_m('File upload stopped by extension.')); |                 throw new ClientException(_m('File upload stopped by extension.')); | ||||||
|             default: |             default: | ||||||
|                 common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']); |                 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. |                 // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason. | ||||||
|                 throw new ClientException(_m('System error uploading file.')); |                 throw new ClientException(_m('System error uploading file.')); | ||||||
|         } |         } | ||||||
| @@ -417,10 +429,10 @@ class MediaFile | |||||||
|                 return new ImageFile(null, $filepath); |                 return new ImageFile(null, $filepath); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return new MediaFile($filepath, $mimetype, $filehash); |         return new self($filepath, $mimetype, $filehash); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static function fromFilehandle($fh, Profile $scoped=null) |     public static function fromFilehandle($fh, Profile $scoped = null) | ||||||
|     { |     { | ||||||
|         $stream = stream_get_meta_data($fh); |         $stream = stream_get_meta_data($fh); | ||||||
|         // So far we're only handling filehandles originating from tmpfile(), |         // So far we're only handling filehandles originating from tmpfile(), | ||||||
| @@ -449,7 +461,7 @@ class MediaFile | |||||||
|                 throw new ClientException(_m('File could not be moved to destination directory.')); |                 throw new ClientException(_m('File could not be moved to destination directory.')); | ||||||
|             } |             } | ||||||
|             if (!chmod($e->path, 0664)) { |             if (!chmod($e->path, 0664)) { | ||||||
|                 common_log(LOG_ERR, 'Could not chmod uploaded file: '._ve($e->path)); |                 common_log(LOG_ERR, 'Could not chmod uploaded file: ' . _ve($e->path)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             $filename = basename($file->getPath()); |             $filename = basename($file->getPath()); | ||||||
| @@ -467,14 +479,14 @@ class MediaFile | |||||||
|             $result = copy($stream['uri'], $filepath) && chmod($filepath, 0664); |             $result = copy($stream['uri'], $filepath) && chmod($filepath, 0664); | ||||||
|  |  | ||||||
|             if (!$result) { |             if (!$result) { | ||||||
|                 common_log(LOG_ERR, 'File could not be moved (or chmodded) from '._ve($stream['uri']) . ' to ' . _ve($filepath)); |                 common_log(LOG_ERR, 'File could not be moved (or chmodded) from ' . _ve($stream['uri']) . ' to ' . _ve($filepath)); | ||||||
|                 // TRANS: Client exception thrown when a file upload operation fails because the file could |                 // 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. |                 // TRANS: not be moved from the temporary folder to the permanent file location. | ||||||
|                 throw new ClientException(_m('File could not be moved to destination directory.')); |                 throw new ClientException(_m('File could not be moved to destination directory.')); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new MediaFile($filename, $mimetype, $filehash); |         return new self($filename, $mimetype, $filehash); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -482,14 +494,16 @@ class MediaFile | |||||||
|      * |      * | ||||||
|      * @param string $filepath filesystem path as string (file must exist) |      * @param string $filepath filesystem path as string (file must exist) | ||||||
|      * @param bool $originalFilename (optional) for extension-based detection |      * @param bool $originalFilename (optional) for extension-based detection | ||||||
|  |      * | ||||||
|      * @return string |      * @return string | ||||||
|      * |      * | ||||||
|      * @throws ClientException if type is known, but not supported for local uploads |  | ||||||
|      * @throws ServerException |  | ||||||
|      * @fixme this seems to tie a front-end error message in, kinda confusing |      * @fixme this seems to tie a front-end error message in, kinda confusing | ||||||
|      * |      * | ||||||
|  |      * @throws ServerException | ||||||
|  |      * | ||||||
|  |      * @throws ClientException if type is known, but not supported for local uploads | ||||||
|      */ |      */ | ||||||
|     public static function getUploadedMimeType(string $filepath, $originalFilename=false) |     public static function getUploadedMimeType(string $filepath, $originalFilename = false) | ||||||
|     { |     { | ||||||
|         // We only accept filenames to existing files |         // We only accept filenames to existing files | ||||||
|  |  | ||||||
| @@ -534,7 +548,7 @@ class MediaFile | |||||||
|          *    due to security concerns, hence the function_usable() checks |          *    due to security concerns, hence the function_usable() checks | ||||||
|          */ |          */ | ||||||
|         if (DIRECTORY_SEPARATOR !== '\\') { |         if (DIRECTORY_SEPARATOR !== '\\') { | ||||||
|             $cmd = 'file --brief --mime '.escapeshellarg($filepath).' 2>&1'; |             $cmd = 'file --brief --mime ' . escapeshellarg($filepath) . ' 2>&1'; | ||||||
|             if (empty($mimetype) && function_exists('exec')) { |             if (empty($mimetype) && function_exists('exec')) { | ||||||
|                 /* This might look confusing, as $mime is being populated with all of the output |                 /* This might look confusing, as $mime is being populated with all of the output | ||||||
|                  * when set in the second parameter. However, we only need the last line, which is |                  * when set in the second parameter. However, we only need the last line, which is | ||||||
| @@ -581,13 +595,13 @@ class MediaFile | |||||||
|  |  | ||||||
|         // Unclear types are such that we can't really tell by the auto |         // Unclear types are such that we can't really tell by the auto | ||||||
|         // detect what they are (.bin, .exe etc. are just "octet-stream") |         // detect what they are (.bin, .exe etc. are just "octet-stream") | ||||||
|         $unclearTypes = array('application/octet-stream', |         $unclearTypes = ['application/octet-stream', | ||||||
|                               'application/vnd.ms-office', |             'application/vnd.ms-office', | ||||||
|                               'application/zip', |             'application/zip', | ||||||
|                               'text/plain', |             'text/plain', | ||||||
|                               'text/html',  // Ironically, Wikimedia Commons' SVG_logo.svg is identified as text/html |             'text/html',  // Ironically, Wikimedia Commons' SVG_logo.svg is identified as text/html | ||||||
|                               // TODO: for XML we could do better content-based sniffing too |             // TODO: for XML we could do better content-based sniffing too | ||||||
|                               'text/xml'); |             'text/xml',]; | ||||||
|  |  | ||||||
|         $supported = common_config('attachments', 'supported'); |         $supported = common_config('attachments', 'supported'); | ||||||
|  |  | ||||||
| @@ -618,7 +632,7 @@ class MediaFile | |||||||
|             // TRANS: %1$s is the file type that was denied, %2$s is the application part of |             // TRANS: %1$s is the file type that was denied, %2$s is the application part of | ||||||
|             // TRANS: the MIME type that was denied. |             // TRANS: the MIME type that was denied. | ||||||
|             $hint = sprintf(_m('"%1$s" is not a supported file type on this server. ' . |             $hint = sprintf(_m('"%1$s" is not a supported file type on this server. ' . | ||||||
|             'Try using another %2$s format.'), $mimetype, $media); |                 'Try using another %2$s format.'), $mimetype, $media); | ||||||
|         } else { |         } else { | ||||||
|             // TRANS: Client exception thrown trying to upload a forbidden MIME type. |             // TRANS: Client exception thrown trying to upload a forbidden MIME type. | ||||||
|             // TRANS: %s is the file type that was denied. |             // TRANS: %s is the file type that was denied. | ||||||
| @@ -630,10 +644,12 @@ class MediaFile | |||||||
|     /** |     /** | ||||||
|      * Title for a file, to display in the interface (if there's no better title) and |      * Title for a file, to display in the interface (if there's no better title) and | ||||||
|      * for download filenames |      * for download filenames | ||||||
|  |      * | ||||||
|      * @param $file File object |      * @param $file File object | ||||||
|      * @returns string |      * @returns string | ||||||
|      */ |      */ | ||||||
|     public static function getDisplayName(File $file) : string { |     public static function getDisplayName(File $file): string | ||||||
|  |     { | ||||||
|         if (empty($file->filename)) { |         if (empty($file->filename)) { | ||||||
|             return _m('Untitled attachment'); |             return _m('Untitled attachment'); | ||||||
|         } |         } | ||||||
| @@ -644,7 +660,7 @@ class MediaFile | |||||||
|         // If there was an error in the match, something's wrong with some piece |         // If there was an error in the match, something's wrong with some piece | ||||||
|         // of code (could be a file with utf8 chars in the name) |         // of code (could be a file with utf8 chars in the name) | ||||||
|         $log_error_msg = "Invalid file name for File with id={$file->id} " . |         $log_error_msg = "Invalid file name for File with id={$file->id} " . | ||||||
|                        "({$file->filename}). Some plugin probably did something wrong."; |             "({$file->filename}). Some plugin probably did something wrong."; | ||||||
|         if ($filename === false) { |         if ($filename === false) { | ||||||
|             common_log(LOG_ERR, $log_error_msg); |             common_log(LOG_ERR, $log_error_msg); | ||||||
|         } elseif ($filename === null) { |         } elseif ($filename === null) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user