forked from GNUsocial/gnu-social
[MEDIA] ImageFile fromUpload method wasn't ensuring uploaded file was an image
This commit is contained in:
parent
194976135f
commit
2cc2b5b856
@ -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(
|
||||||
|
$this->scoped->getID(),
|
||||||
image_type_to_extension($type),
|
image_type_to_extension($type),
|
||||||
null,
|
null,
|
||||||
'tmp'.common_timestamp());
|
'tmp' . common_timestamp()
|
||||||
|
);
|
||||||
|
|
||||||
$filepath = Avatar::path($filename);
|
$filepath = Avatar::path($filename);
|
||||||
$imagefile = $imagefile->copyTo($filepath);
|
$imagefile = $imagefile->copyTo($filepath);
|
||||||
|
|
||||||
$filedata = array('filename' => $filename,
|
$filedata = [
|
||||||
|
'filename' => $filename,
|
||||||
'filepath' => $filepath,
|
'filepath' => $filepath,
|
||||||
'width' => $imagefile->width,
|
'width' => $imagefile->width,
|
||||||
'height' => $imagefile->height,
|
'height' => $imagefile->height,
|
||||||
'type' => $type);
|
'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>
|
||||||
@ -52,8 +52,8 @@ class ImageFile extends MediaFile
|
|||||||
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)
|
||||||
{
|
{
|
||||||
@ -74,12 +74,7 @@ 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);
|
||||||
@ -99,7 +94,7 @@ 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;
|
||||||
@ -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,
|
||||||
@ -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,7 +365,9 @@ class ImageFile extends MediaFile
|
|||||||
$img = $img->orientate();
|
$img = $img->orientate();
|
||||||
}
|
}
|
||||||
|
|
||||||
$img->fit($box['width'], $box['height'],
|
$img->fit(
|
||||||
|
$box['width'],
|
||||||
|
$box['height'],
|
||||||
function ($constraint) {
|
function ($constraint) {
|
||||||
if (common_config('attachments', 'upscale') !== true) {
|
if (common_config('attachments', 'upscale') !== true) {
|
||||||
$constraint->upsize(); // Prevent upscaling
|
$constraint->upsize(); // Prevent upscaling
|
||||||
@ -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,
|
||||||
@ -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,];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -588,7 +596,8 @@ class ImageFile extends MediaFile
|
|||||||
];
|
];
|
||||||
|
|
||||||
$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')
|
||||||
|
@ -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
|
||||||
@ -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,7 +166,7 @@ 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;
|
||||||
|
|
||||||
@ -178,8 +183,8 @@ class MediaFile
|
|||||||
|
|
||||||
$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');
|
||||||
@ -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');
|
||||||
|
|
||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user