[Media] Fix issues with database file storage
Fixed file quota as well. There can be more than one file for the same filehash IF the url are different. Possible states: - A file with no url and with filename is a local file. - A file with an url but no filename is a remote file that wasn't fetched, not even the thumbnail. - A file with an url and filename is a fetched remote file (maybe just a thumbnail of it). - A file with no filename nor url is a redirect. Routes: Given these states, updated routes so that an attachment can only be retrieved by id and a file by filehash. Major API changes: File::getByHash now returns a yield of files Major UI changes: - Now remote non stored files are presented. - /view became preferred - Redirects to remote originals are preferred. Many other minor bug fixes...
This commit is contained in:
parent
e9cd437668
commit
6028175bfc
@ -19,11 +19,11 @@ defined('GNUSOCIAL') || die();
|
|||||||
/**
|
/**
|
||||||
* Show notice attachments
|
* Show notice attachments
|
||||||
*
|
*
|
||||||
* @category Personal
|
* @category Personal
|
||||||
* @package StatusNet
|
* @package GNUsocial
|
||||||
* @author Evan Prodromou <evan@status.net>
|
* @author Evan Prodromou <evan@status.net>
|
||||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @copyright 2008-2009 StatusNet, Inc.
|
||||||
* @link http://status.net/
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
class AttachmentAction extends ManagedAction
|
class AttachmentAction extends ManagedAction
|
||||||
{
|
{
|
||||||
@ -58,9 +58,11 @@ class AttachmentAction extends ManagedAction
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!empty($id = $this->trimmed('attachment'))) {
|
if (!empty($id = $this->trimmed('attachment'))) {
|
||||||
$this->attachment = File::getByID($id);
|
$this->attachment = File::getByID((int) $id);
|
||||||
} elseif (!empty($this->filehash = $this->trimmed('filehash'))) {
|
} elseif (!empty($this->filehash = $this->trimmed('filehash'))) {
|
||||||
$this->attachment = File::getByHash($this->filehash);
|
$file = File::getByHash($this->filehash);
|
||||||
|
$file->fetch();
|
||||||
|
$this->attachment = $file;
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// Not found
|
// Not found
|
||||||
@ -70,13 +72,22 @@ class AttachmentAction extends ManagedAction
|
|||||||
$this->clientError(_m('No such attachment.'), 404);
|
$this->clientError(_m('No such attachment.'), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->filepath = $this->attachment->getFileOrThumbnailPath();
|
$this->filesize = $this->attachment->size;
|
||||||
if (empty($this->filepath)) {
|
$this->mimetype = $this->attachment->mimetype;
|
||||||
$this->clientError(_m('Requested local URL for a file that is not stored locally.'), 404);
|
$this->filename = $this->attachment->filename;
|
||||||
|
|
||||||
|
if ($this->attachment->isLocal()) {
|
||||||
|
$this->filepath = $this->attachment->getFileOrThumbnailPath();
|
||||||
|
if (empty($this->filepath)) {
|
||||||
|
$this->clientError(
|
||||||
|
_m('Requested local URL for a file that is not stored locally.'),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->filesize = $this->attachment->getFileOrThumbnailSize();
|
||||||
|
$this->mimetype = $this->attachment->getFileOrThumbnailMimetype();
|
||||||
|
$this->filename = MediaFile::getDisplayName($this->attachment);
|
||||||
}
|
}
|
||||||
$this->filesize = $this->attachment->getFileOrThumbnailSize();
|
|
||||||
$this->mimetype = $this->attachment->getFileOrThumbnailMimetype();
|
|
||||||
$this->filename = MediaFile::getDisplayName($this->attachment);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -104,8 +115,12 @@ class AttachmentAction extends ManagedAction
|
|||||||
|
|
||||||
public function showPage(): void
|
public function showPage(): void
|
||||||
{
|
{
|
||||||
if (empty($this->filepath)) {
|
if (
|
||||||
// if it's not a local file, gtfo
|
!$this->attachment->isLocal()
|
||||||
|
|| empty($this->filepath)
|
||||||
|
|| !file_exists($this->filepath)
|
||||||
|
) {
|
||||||
|
// If it's not a locally stored file, get lost
|
||||||
common_redirect($this->attachment->getUrl(), 303);
|
common_redirect($this->attachment->getUrl(), 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,29 @@
|
|||||||
<?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/>.
|
||||||
|
|
||||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
defined('GNUSOCIAL') || die();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download notice attachment
|
* Download notice attachment
|
||||||
*
|
*
|
||||||
* @category Personal
|
* @category Personal
|
||||||
* @package GNUsocial
|
* @package GNUsocial
|
||||||
* @author Mikael Nordfeldth <mmn@hethane.se>
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @copyright 2016 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @link https:/gnu.io/social
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or late
|
||||||
*/
|
*/
|
||||||
class Attachment_downloadAction extends AttachmentAction
|
class Attachment_downloadAction extends AttachmentAction
|
||||||
{
|
{
|
||||||
@ -20,6 +34,15 @@ class Attachment_downloadAction extends AttachmentAction
|
|||||||
// script execution, and we don't want to have any more errors until then, so don't reset it
|
// script execution, and we don't want to have any more errors until then, so don't reset it
|
||||||
@ini_set('display_errors', 0);
|
@ini_set('display_errors', 0);
|
||||||
|
|
||||||
common_send_file($this->filepath, $this->mimetype, $this->filename, 'attachment');
|
if ($this->attachment->isLocal()) {
|
||||||
|
common_send_file(
|
||||||
|
$this->filepath,
|
||||||
|
$this->mimetype,
|
||||||
|
$this->filename,
|
||||||
|
'attachment'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
common_redirect($this->attachment->getUrl(), 303);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@ defined('GNUSOCIAL') || die();
|
|||||||
/**
|
/**
|
||||||
* Show notice attachments
|
* Show notice attachments
|
||||||
*
|
*
|
||||||
* @category Personal
|
* @category Personal
|
||||||
* @package StatusNet
|
* @package GNUsocial
|
||||||
* @author Evan Prodromou <evan@status.net>
|
* @author Evan Prodromou <evan@status.net>
|
||||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @copyright 2008-2009 StatusNet, Inc.
|
||||||
* @link http://status.net/
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
class Attachment_thumbnailAction extends AttachmentAction
|
class Attachment_thumbnailAction extends Attachment_viewAction
|
||||||
{
|
{
|
||||||
protected $thumb_w = null; // max width
|
protected $thumb_w = null; // max width
|
||||||
protected $thumb_h = null; // max height
|
protected $thumb_h = null; // max height
|
||||||
@ -52,14 +52,21 @@ class Attachment_thumbnailAction extends AttachmentAction
|
|||||||
public function showPage(): void
|
public function showPage(): void
|
||||||
{
|
{
|
||||||
// Returns a File_thumbnail object or throws exception if not available
|
// Returns a File_thumbnail object or throws exception if not available
|
||||||
|
$filename = $this->filename;
|
||||||
|
$filepath = $this->filepath;
|
||||||
try {
|
try {
|
||||||
$thumbnail = $this->attachment->getThumbnail($this->thumb_w, $this->thumb_h, $this->thumb_c);
|
$thumbnail = $this->attachment->getThumbnail($this->thumb_w, $this->thumb_h, $this->thumb_c);
|
||||||
$file = $thumbnail->getFile();
|
$filename = $thumbnail->getFilename();
|
||||||
|
$filepath = $thumbnail->getPath();
|
||||||
} catch (UseFileAsThumbnailException $e) {
|
} catch (UseFileAsThumbnailException $e) {
|
||||||
// With this exception, the file exists locally
|
// With this exception, the file exists locally $e->file;
|
||||||
$file = $e->file;
|
|
||||||
} catch (FileNotFoundException $e) {
|
} catch (FileNotFoundException $e) {
|
||||||
$this->clientError(_m('No such attachment'), 404);
|
$this->clientError(_m('No such attachment'), 404);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if (is_null($filepath)) {
|
||||||
|
$this->clientError(_m('No such thumbnail'), 404);
|
||||||
|
}
|
||||||
|
// Remote file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable errors, to not mess with the file contents (suppress errors in case access to this
|
// Disable errors, to not mess with the file contents (suppress errors in case access to this
|
||||||
@ -67,6 +74,6 @@ class Attachment_thumbnailAction extends AttachmentAction
|
|||||||
// script execution, and we don't want to have any more errors until then, so don't reset it
|
// script execution, and we don't want to have any more errors until then, so don't reset it
|
||||||
@ini_set('display_errors', 0);
|
@ini_set('display_errors', 0);
|
||||||
|
|
||||||
common_send_file($this->filepath, $this->mimetype, $this->filename, 'inline');
|
common_send_file($filepath, $this->mimetype, $filename, 'inline');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ defined('GNUSOCIAL') || die();
|
|||||||
/**
|
/**
|
||||||
* View notice attachment
|
* View notice attachment
|
||||||
*
|
*
|
||||||
* @package GNUsocial
|
* @package GNUsocial
|
||||||
* @author Miguel Dantas <biodantasgs@gmail.com>
|
* @author Miguel Dantas <biodantasgs@gmail.com>
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
|
||||||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
class Attachment_viewAction extends AttachmentAction
|
class Attachment_viewAction extends AttachmentAction
|
||||||
{
|
{
|
||||||
@ -32,7 +33,15 @@ class Attachment_viewAction extends AttachmentAction
|
|||||||
// script execution, and we don't want to have any more errors until then, so don't reset it
|
// script execution, and we don't want to have any more errors until then, so don't reset it
|
||||||
@ini_set('display_errors', 0);
|
@ini_set('display_errors', 0);
|
||||||
|
|
||||||
$disposition = in_array(common_get_mime_media($this->mimetype), ['image', 'video']) ? 'inline' : 'attachment';
|
if ($this->attachment->isLocal()) {
|
||||||
common_send_file($this->filepath, $this->mimetype, $this->filename, $disposition);
|
$disposition = 'attachment';
|
||||||
|
if (in_array(common_get_mime_media($this->mimetype), ['image', 'video'])) {
|
||||||
|
$disposition = 'inline';
|
||||||
|
}
|
||||||
|
common_send_file($this->filepath, $this->mimetype,
|
||||||
|
$this->filename, $disposition);
|
||||||
|
} else {
|
||||||
|
common_redirect($this->attachment->getUrl(), 303);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,43 @@
|
|||||||
<?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
|
|
||||||
*
|
|
||||||
* Handler for posting new notices
|
* Handler for posting new notices
|
||||||
*
|
*
|
||||||
* 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 Personal
|
* @category Personal
|
||||||
* @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>
|
||||||
* @author Sarven Capadisli <csarven@status.net>
|
* @author Sarven Capadisli <csarven@status.net>
|
||||||
* @copyright 2008-2009 StatusNet, Inc.
|
* @copyright 2008-2009 StatusNet, Inc.
|
||||||
* @copyright 2013 Free Software Foundation, Inc.
|
* @copyright 2013 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
* @link http://status.net/
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
defined('GNUSOCIAL') || die();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action for posting new notices
|
* Action for posting new notices
|
||||||
*
|
*
|
||||||
* @category Personal
|
* @category Personal
|
||||||
* @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>
|
||||||
* @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 https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
* @link http://status.net/
|
|
||||||
*/
|
*/
|
||||||
class NewnoticeAction extends FormAction
|
class NewnoticeAction extends FormAction
|
||||||
{
|
{
|
||||||
@ -56,7 +52,7 @@ class NewnoticeAction extends FormAction
|
|||||||
*
|
*
|
||||||
* @return string page title
|
* @return string page title
|
||||||
*/
|
*/
|
||||||
function title()
|
public function title()
|
||||||
{
|
{
|
||||||
if ($this->getInfo() && $this->stored instanceof Notice) {
|
if ($this->getInfo() && $this->stored instanceof Notice) {
|
||||||
// TRANS: Page title after sending a notice.
|
// TRANS: Page title after sending a notice.
|
||||||
@ -66,12 +62,12 @@ class NewnoticeAction extends FormAction
|
|||||||
return _m('TITLE', 'New reply');
|
return _m('TITLE', 'New reply');
|
||||||
}
|
}
|
||||||
// TRANS: Page title for sending a new notice.
|
// TRANS: Page title for sending a new notice.
|
||||||
return _m('TITLE','New notice');
|
return _m('TITLE', 'New notice');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function doPreparation()
|
protected function doPreparation()
|
||||||
{
|
{
|
||||||
foreach(array('inreplyto') as $opt) {
|
foreach (['inreplyto'] as $opt) {
|
||||||
if ($this->trimmed($opt)) {
|
if ($this->trimmed($opt)) {
|
||||||
$this->formOpts[$opt] = $this->trimmed($opt);
|
$this->formOpts[$opt] = $this->trimmed($opt);
|
||||||
}
|
}
|
||||||
@ -106,26 +102,6 @@ class NewnoticeAction extends FormAction
|
|||||||
$options = array('source' => 'web');
|
$options = array('source' => 'web');
|
||||||
Event::handle('StartSaveNewNoticeWeb', array($this, $user, &$content, &$options));
|
Event::handle('StartSaveNewNoticeWeb', array($this, $user, &$content, &$options));
|
||||||
|
|
||||||
$upload = null;
|
|
||||||
try {
|
|
||||||
// throws exception on failure
|
|
||||||
$upload = MediaFile::fromUpload('attach', $this->scoped);
|
|
||||||
if (Event::handle('StartSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options))) {
|
|
||||||
$content .= ($content==='' ? '' : ' ') . $upload->shortUrl();
|
|
||||||
}
|
|
||||||
Event::handle('EndSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options));
|
|
||||||
|
|
||||||
// We could check content length here if the URL was added, but I'll just let it slide for now...
|
|
||||||
|
|
||||||
$act->enclosures[] = $upload->getEnclosure();
|
|
||||||
} catch (NoUploadedMediaException $e) {
|
|
||||||
// simply no attached media to the new notice
|
|
||||||
if (empty($content)) {
|
|
||||||
// TRANS: Client error displayed trying to send a notice without content.
|
|
||||||
throw new ClientException(_('No content!'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$inter = new CommandInterpreter();
|
$inter = new CommandInterpreter();
|
||||||
|
|
||||||
$cmd = $inter->handle_command($user, $content);
|
$cmd = $inter->handle_command($user, $content);
|
||||||
@ -144,15 +120,37 @@ class NewnoticeAction extends FormAction
|
|||||||
$act->time = time();
|
$act->time = time();
|
||||||
$act->actor = $this->scoped->asActivityObject();
|
$act->actor = $this->scoped->asActivityObject();
|
||||||
|
|
||||||
|
$upload = null;
|
||||||
|
try {
|
||||||
|
// throws exception on failure
|
||||||
|
$upload = MediaFile::fromUpload('attach', $this->scoped);
|
||||||
|
if (Event::handle('StartSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options))) {
|
||||||
|
$content .= ($content==='' ? '' : ' ') . $upload->shortUrl();
|
||||||
|
}
|
||||||
|
Event::handle('EndSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options));
|
||||||
|
|
||||||
|
// We could check content length here if the URL was added, but I'll just let it slide for now...
|
||||||
|
|
||||||
|
$act->enclosures[] = $upload->getEnclosure();
|
||||||
|
} catch (NoUploadedMediaException $e) {
|
||||||
|
// simply no attached media to the new notice
|
||||||
|
if (empty($content)) {
|
||||||
|
// TRANS: Client error displayed trying to send a notice without content.
|
||||||
|
throw new ClientException(_m('No content!'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reject notice if it is too long (without the HTML)
|
// Reject notice if it is too long (without the HTML)
|
||||||
// This is done after MediaFile::fromUpload etc. just to act the same as the ApiStatusesUpdateAction
|
// This is done after MediaFile::fromUpload etc. just to act the same as the ApiStatusesUpdateAction
|
||||||
if (Notice::contentTooLong($content)) {
|
if (Notice::contentTooLong($content)) {
|
||||||
// TRANS: Client error displayed when the parameter "status" is missing.
|
// TRANS: Client error displayed when the parameter "status" is missing.
|
||||||
// TRANS: %d is the maximum number of character for a notice.
|
// TRANS: %d is the maximum number of character for a notice.
|
||||||
throw new ClientException(sprintf(_m('That\'s too long. Maximum notice size is %d character.',
|
throw new ClientException(sprintf(
|
||||||
'That\'s too long. Maximum notice size is %d characters.',
|
_m('That\'s too long. Maximum notice size is %d character.',
|
||||||
Notice::maxContent()),
|
'That\'s too long. Maximum notice size is %d characters.',
|
||||||
Notice::maxContent()));
|
Notice::maxContent()),
|
||||||
|
Notice::maxContent()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$act->context = new ActivityContext();
|
$act->context = new ActivityContext();
|
||||||
@ -165,17 +163,21 @@ class NewnoticeAction extends FormAction
|
|||||||
if ($this->scoped->shareLocation()) {
|
if ($this->scoped->shareLocation()) {
|
||||||
// use browser data if checked; otherwise profile data
|
// use browser data if checked; otherwise profile data
|
||||||
if ($this->arg('notice_data-geo')) {
|
if ($this->arg('notice_data-geo')) {
|
||||||
$locOptions = Notice::locationOptions($this->trimmed('lat'),
|
$locOptions = Notice::locationOptions(
|
||||||
$this->trimmed('lon'),
|
$this->trimmed('lat'),
|
||||||
$this->trimmed('location_id'),
|
$this->trimmed('lon'),
|
||||||
$this->trimmed('location_ns'),
|
$this->trimmed('location_id'),
|
||||||
$this->scoped);
|
$this->trimmed('location_ns'),
|
||||||
|
$this->scoped
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$locOptions = Notice::locationOptions(null,
|
$locOptions = Notice::locationOptions(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
$this->scoped);
|
null,
|
||||||
|
$this->scoped
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$act->context->location = Location::fromOptions($locOptions);
|
$act->context->location = Location::fromOptions($locOptions);
|
||||||
@ -202,9 +204,7 @@ class NewnoticeAction extends FormAction
|
|||||||
|
|
||||||
$this->stored = Notice::saveActivity($act, $this->scoped, $options);
|
$this->stored = Notice::saveActivity($act, $this->scoped, $options);
|
||||||
|
|
||||||
if ($upload instanceof MediaFile) {
|
$upload->attachToNotice($this->stored);
|
||||||
$upload->attachToNotice($this->stored);
|
|
||||||
}
|
|
||||||
|
|
||||||
Event::handle('EndNoticeSaveWeb', array($this, $this->stored));
|
Event::handle('EndNoticeSaveWeb', array($this, $this->stored));
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ class NewnoticeAction extends FormAction
|
|||||||
common_redirect($url, 303);
|
common_redirect($url, 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _('Saved the notice!');
|
return _m('Saved the notice!');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function showContent()
|
protected function showContent()
|
||||||
@ -240,7 +240,7 @@ class NewnoticeAction extends FormAction
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function showNotice(Notice $notice)
|
public function showNotice(Notice $notice)
|
||||||
{
|
{
|
||||||
$nli = new NoticeListItem($notice, $this);
|
$nli = new NoticeListItem($notice, $this);
|
||||||
$nli->show();
|
$nli->show();
|
||||||
|
@ -56,7 +56,7 @@ class RedirecturlAction extends ManagedAction
|
|||||||
|
|
||||||
public function showPage()
|
public function showPage()
|
||||||
{
|
{
|
||||||
common_redirect($this->file->getUrl(false), 301);
|
common_redirect($this->file->getUrl(true), 301);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isReadOnly($args)
|
function isReadOnly($args)
|
||||||
|
131
classes/File.php
131
classes/File.php
@ -53,7 +53,7 @@ class File extends Managed_DataObject
|
|||||||
return array(
|
return array(
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'id' => array('type' => 'serial', 'not null' => true),
|
'id' => array('type' => 'serial', 'not null' => true),
|
||||||
'urlhash' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 of destination URL (url field)'),
|
'urlhash' => array('type' => 'varchar', 'length' => 64, 'description' => 'sha256 of destination URL (url field)'),
|
||||||
'url' => array('type' => 'text', 'description' => 'destination URL after following possible redirections'),
|
'url' => array('type' => 'text', 'description' => 'destination URL after following possible redirections'),
|
||||||
'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'),
|
'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'),
|
||||||
'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'),
|
'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'),
|
||||||
@ -112,21 +112,26 @@ class File extends Managed_DataObject
|
|||||||
// We don't have the file's URL since before, so let's continue.
|
// We don't have the file's URL since before, so let's continue.
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the given url is an local attachment url and the id already exists, don't
|
// If the given url is a local attachment url, don't save a new file record.
|
||||||
// save a new file record. This should never happen, but let's make it foolproof
|
$uh = parse_url($given_url, PHP_URL_HOST);
|
||||||
// FIXME: how about attachments servers?
|
$up = parse_url($given_url, PHP_URL_PATH);
|
||||||
$u = parse_url($given_url);
|
if ($uh == common_config('site', 'server') || $uh == common_config('attachments', 'server')) {
|
||||||
if (isset($u['host']) && $u['host'] === common_config('site', 'server')) {
|
unset($uh);
|
||||||
$r = Router::get();
|
$r = Router::get();
|
||||||
// Skip the / in the beginning or $r->map won't match
|
// Skip the / in the beginning or $r->map won't match
|
||||||
try {
|
try {
|
||||||
$args = $r->map(mb_substr($u['path'], 1));
|
$args = $r->map(mb_substr($up, 1));
|
||||||
if ($args['action'] === 'attachment') {
|
if ($args['action'] === 'attachment' ||
|
||||||
|
$args['action'] === 'attachment_view' ||
|
||||||
|
$args['action'] === 'attachment_download' ||
|
||||||
|
$args['action'] === 'attachment_thumbnail' ) {
|
||||||
try {
|
try {
|
||||||
if (!empty($args['attachment'])) {
|
if (array_key_exists('attachment', $args)) {
|
||||||
return File::getByID($args['attachment']);
|
return File::getByID((int)$args['attachment']);
|
||||||
} elseif ($args['filehash']) {
|
} elseif (array_key_exists('filehash', $args)) {
|
||||||
return File::getByHash($args['filehash']);
|
$file = File::getByHash($args['filehash']);
|
||||||
|
$file->fetch();
|
||||||
|
return $file;
|
||||||
}
|
}
|
||||||
} catch (NoResultException $e) {
|
} catch (NoResultException $e) {
|
||||||
// apparently this link goes to us, but is _not_ an existing attachment (File) ID?
|
// apparently this link goes to us, but is _not_ an existing attachment (File) ID?
|
||||||
@ -158,10 +163,10 @@ class File extends Managed_DataObject
|
|||||||
$file->mimetype = $redir_data['type'];
|
$file->mimetype = $redir_data['type'];
|
||||||
}
|
}
|
||||||
if (!empty($redir_data['size'])) {
|
if (!empty($redir_data['size'])) {
|
||||||
$file->size = intval($redir_data['size']);
|
$file->size = (int)$redir_data['size'];
|
||||||
}
|
}
|
||||||
if (isset($redir_data['time']) && $redir_data['time'] > 0) {
|
if (isset($redir_data['time']) && $redir_data['time'] > 0) {
|
||||||
$file->date = intval($redir_data['time']);
|
$file->date = (int)$redir_data['time'];
|
||||||
}
|
}
|
||||||
$file->saveFile();
|
$file->saveFile();
|
||||||
return $file;
|
return $file;
|
||||||
@ -169,7 +174,7 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
public function saveFile()
|
public function saveFile()
|
||||||
{
|
{
|
||||||
$this->urlhash = self::hashurl($this->url);
|
$this->urlhash = is_null($this->url) ? null : self::hashurl($this->url);
|
||||||
|
|
||||||
if (!Event::handle('StartFileSaveNew', array(&$this))) {
|
if (!Event::handle('StartFileSaveNew', array(&$this))) {
|
||||||
throw new ServerException('File not saved due to an aborted StartFileSaveNew event.');
|
throw new ServerException('File not saved due to an aborted StartFileSaveNew event.');
|
||||||
@ -193,14 +198,14 @@ class File extends Managed_DataObject
|
|||||||
* - return the File object with the full reference
|
* - return the File object with the full reference
|
||||||
*
|
*
|
||||||
* @param string $given_url the URL we're looking at
|
* @param string $given_url the URL we're looking at
|
||||||
* @param Notice $notice (optional)
|
* @param Notice|null $notice (optional)
|
||||||
* @param bool $followRedirects defaults to true
|
* @param bool $followRedirects defaults to true
|
||||||
*
|
*
|
||||||
* @return mixed File on success, -1 on some errors
|
* @return mixed File on success, -1 on some errors
|
||||||
*
|
*
|
||||||
* @throws ServerException on failure
|
* @throws ServerException on failure
|
||||||
*/
|
*/
|
||||||
public static function processNew($given_url, Notice $notice=null, $followRedirects=true)
|
public static function processNew($given_url, ?Notice $notice=null, bool $followRedirects=true)
|
||||||
{
|
{
|
||||||
if (empty($given_url)) {
|
if (empty($given_url)) {
|
||||||
throw new ServerException('No given URL to process');
|
throw new ServerException('No given URL to process');
|
||||||
@ -265,7 +270,8 @@ class File extends Managed_DataObject
|
|||||||
INNER JOIN notice
|
INNER JOIN notice
|
||||||
ON file_to_post.post_id = notice.id
|
ON file_to_post.post_id = notice.id
|
||||||
WHERE profile_id = {$scoped->id} AND
|
WHERE profile_id = {$scoped->id} AND
|
||||||
file.url LIKE '%/notice/%/file'";
|
filename IS NULL AND
|
||||||
|
file.url IS NOT NULL";
|
||||||
$file->query($query);
|
$file->query($query);
|
||||||
$file->fetch();
|
$file->fetch();
|
||||||
$total = $file->total + $fileSize;
|
$total = $file->total + $fileSize;
|
||||||
@ -460,6 +466,14 @@ class File extends Managed_DataObject
|
|||||||
return $dir . $filename;
|
return $dir . $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't use for attachments, only for assets.
|
||||||
|
*
|
||||||
|
* @param $filename
|
||||||
|
* @return mixed|string
|
||||||
|
* @throws InvalidFilenameException
|
||||||
|
* @throws ServerException
|
||||||
|
*/
|
||||||
public static function url($filename)
|
public static function url($filename)
|
||||||
{
|
{
|
||||||
self::tryFilename($filename);
|
self::tryFilename($filename);
|
||||||
@ -534,7 +548,7 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
$needMoreMetadataMimetypes = array(null, 'application/xhtml+xml', 'text/html');
|
$needMoreMetadataMimetypes = array(null, 'application/xhtml+xml', 'text/html');
|
||||||
|
|
||||||
if (!isset($this->filename) && in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) {
|
if (isset($enclosure->url) && in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) {
|
||||||
// This fetches enclosure metadata for non-local links with unset/HTML mimetypes,
|
// This fetches enclosure metadata for non-local links with unset/HTML mimetypes,
|
||||||
// which may be enriched through oEmbed or similar (implemented as plugins)
|
// which may be enriched through oEmbed or similar (implemented as plugins)
|
||||||
Event::handle('FileEnclosureMetadata', array($this, &$enclosure));
|
Event::handle('FileEnclosureMetadata', array($this, &$enclosure));
|
||||||
@ -561,45 +575,28 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the attachment's thumbnail record, if any.
|
* Get the attachment's thumbnail record, if any or generate one.
|
||||||
* Make sure you supply proper 'int' typed variables (or null).
|
|
||||||
*
|
*
|
||||||
* @param $width int Max width of thumbnail in pixels. (if null, use common_config values)
|
* @param int|null $width Max width of thumbnail in pixels. (if null, use common_config values)
|
||||||
* @param $height int Max height of thumbnail in pixels. (if null, square-crop to $width)
|
* @param int|null $height Max height of thumbnail in pixels. (if null, square-crop to $width)
|
||||||
* @param $crop bool Crop to the max-values' aspect ratio
|
* @param bool $crop Crop to the max-values' aspect ratio
|
||||||
* @param $force_still bool Don't allow fallback to showing original (such as animated GIF)
|
* @param bool $force_still Don't allow fallback to showing original (such as animated GIF)
|
||||||
* @param $upscale mixed Whether or not to scale smaller images up to larger thumbnail sizes. (null = site default)
|
* @param bool|null $upscale Whether or not to scale smaller images up to larger thumbnail sizes. (null = site default)
|
||||||
*
|
*
|
||||||
* @return File_thumbnail
|
* @return File_thumbnail
|
||||||
*
|
*
|
||||||
* @throws UseFileAsThumbnailException if the file is considered an image itself and should be itself as thumbnail
|
* @throws ClientException
|
||||||
* @throws UnsupportedMediaException if, despite trying, we can't understand how to make a thumbnail for this format
|
* @throws FileNotFoundException
|
||||||
* @throws ServerException on various other errors
|
* @throws FileNotStoredLocallyException
|
||||||
|
* @throws InvalidFilenameException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException on various other errors
|
||||||
|
* @throws UnsupportedMediaException if, despite trying, we can't understand how to make a thumbnail for this format
|
||||||
|
* @throws UseFileAsThumbnailException if the file is considered an image itself and should be itself as thumbnail
|
||||||
*/
|
*/
|
||||||
public function getThumbnail($width = null, $height = null, $crop = false, $force_still = true, $upscale = null): File_thumbnail
|
public function getThumbnail (?int $width = null, ?int $height = null, bool $crop = false, bool $force_still = true, ?bool $upscale = null): File_thumbnail
|
||||||
{
|
{
|
||||||
// Get some more information about this file through our ImageFile class
|
return File_thumbnail::fromFileObject($this, $width, $height, $crop, $force_still, $upscale);
|
||||||
$image = ImageFile::fromFileObject($this);
|
|
||||||
if ($image->animated && !common_config('thumbnail', 'animated')) {
|
|
||||||
// null means "always use file as thumbnail"
|
|
||||||
// false means you get choice between frozen frame or original when calling getThumbnail
|
|
||||||
if (is_null(common_config('thumbnail', 'animated')) || !$force_still) {
|
|
||||||
try {
|
|
||||||
// remote files with animated GIFs as thumbnails will match this
|
|
||||||
return File_thumbnail::byFile($this);
|
|
||||||
} catch (NoResultException $e) {
|
|
||||||
// and if it's not a remote file, it'll be safe to use the locally stored File
|
|
||||||
throw new UseFileAsThumbnailException($this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image->getFileThumbnail(
|
|
||||||
$width,
|
|
||||||
$height,
|
|
||||||
$crop,
|
|
||||||
!is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath()
|
public function getPath()
|
||||||
@ -698,34 +695,33 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
public function getAttachmentDownloadUrl()
|
public function getAttachmentDownloadUrl()
|
||||||
{
|
{
|
||||||
return common_local_url('attachment_download', array('attachment'=>$this->getID()));
|
return common_local_url('attachment_download', ['filehash' => $this->filehash]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAttachmentViewUrl()
|
public function getAttachmentViewUrl()
|
||||||
{
|
{
|
||||||
return common_local_url('attachment_view', array('attachment'=>$this->getID()));
|
return common_local_url('attachment_view', ['filehash' => $this->filehash]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $use_local true means require local, null means prefer local, false means use whatever is stored
|
* @param bool|null $use_local true means require local, null means prefer original, false means use whatever is stored
|
||||||
* @return string
|
* @return string
|
||||||
* @throws FileNotStoredLocallyException
|
* @throws FileNotStoredLocallyException
|
||||||
*/
|
*/
|
||||||
public function getUrl($use_local=null)
|
public function getUrl(?bool $use_local=null): ?string
|
||||||
{
|
{
|
||||||
if ($use_local !== false) {
|
if ($use_local !== false) {
|
||||||
if (is_string($this->filename) || !empty($this->filename)) {
|
if (empty($this->url)) {
|
||||||
// A locally stored file, so let's generate a URL for our instance.
|
// A locally stored file, so let's generate a URL for our instance.
|
||||||
return $this->getAttachmentViewUrl();
|
return $this->getAttachmentViewUrl();
|
||||||
}
|
}
|
||||||
if ($use_local) {
|
if ($use_local) {
|
||||||
// if the file wasn't stored locally (has filename) and we require a local URL
|
// if the file isn't ours but and we require a local URL anyway
|
||||||
throw new FileNotStoredLocallyException($this);
|
throw new FileNotStoredLocallyException($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The original file's URL
|
||||||
// No local filename available, return the URL we have stored
|
|
||||||
return $this->url;
|
return $this->url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,7 +744,7 @@ class File extends Managed_DataObject
|
|||||||
{
|
{
|
||||||
$file = new File();
|
$file = new File();
|
||||||
$file->filehash = strtolower($hashstr);
|
$file->filehash = strtolower($hashstr);
|
||||||
if (!$file->find(true)) {
|
if (!$file->find()) {
|
||||||
throw new NoResultException($file);
|
throw new NoResultException($file);
|
||||||
}
|
}
|
||||||
return $file;
|
return $file;
|
||||||
@ -836,11 +832,10 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
public function isLocal()
|
public function isLocal()
|
||||||
{
|
{
|
||||||
return !empty($this->filename);
|
return empty($this->url) && !empty($this->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete($useWhere=false)
|
public function unlink() {
|
||||||
{
|
|
||||||
// Delete the file, if it exists locally
|
// Delete the file, if it exists locally
|
||||||
if (!empty($this->filename) && file_exists(self::path($this->filename))) {
|
if (!empty($this->filename) && file_exists(self::path($this->filename))) {
|
||||||
$deleted = @unlink(self::path($this->filename));
|
$deleted = @unlink(self::path($this->filename));
|
||||||
@ -848,12 +843,18 @@ class File extends Managed_DataObject
|
|||||||
common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename)));
|
common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($useWhere=false)
|
||||||
|
{
|
||||||
|
// Delete the file, if it exists locally
|
||||||
|
$this->unlink();
|
||||||
|
|
||||||
// Clear out related things in the database and filesystem, such as thumbnails
|
// Clear out related things in the database and filesystem, such as thumbnails
|
||||||
$related = [
|
$related = [
|
||||||
'File_redirection',
|
'File_redirection',
|
||||||
'File_thumbnail',
|
'File_thumbnail',
|
||||||
'File_to_post',
|
'File_to_post'
|
||||||
];
|
];
|
||||||
Event::handle('FileDeleteRelated', [$this, &$related]);
|
Event::handle('FileDeleteRelated', [$this, &$related]);
|
||||||
|
|
||||||
|
@ -59,6 +59,69 @@ class File_thumbnail extends Managed_DataObject
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachment's thumbnail record, if any or generate one.
|
||||||
|
*
|
||||||
|
* @param File $file
|
||||||
|
* @param int|null $width Max width of thumbnail in pixels. (if null, use common_config values)
|
||||||
|
* @param int|null $height Max height of thumbnail in pixels. (if null, square-crop to $width)
|
||||||
|
* @param bool $crop Crop to the max-values' aspect ratio
|
||||||
|
* @param bool $force_still Don't allow fallback to showing original (such as animated GIF)
|
||||||
|
* @param bool|null $upscale Whether or not to scale smaller images up to larger thumbnail sizes. (null = site default)
|
||||||
|
*
|
||||||
|
* @return File_thumbnail
|
||||||
|
*
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
* @throws FileNotStoredLocallyException
|
||||||
|
* @throws InvalidFilenameException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException on various other errors
|
||||||
|
* @throws UnsupportedMediaException if, despite trying, we can't understand how to make a thumbnail for this format
|
||||||
|
* @throws UseFileAsThumbnailException if the file is considered an image itself and should be itself as thumbnail
|
||||||
|
*/
|
||||||
|
public static function fromFileObject (File $file, ?int $width = null, ?int $height = null, bool $crop = false, bool $force_still = true, ?bool $upscale = null): File_thumbnail
|
||||||
|
{
|
||||||
|
if (is_null($file->filename)) {
|
||||||
|
throw new FileNotFoundException("This remote file has no local thumbnail.");
|
||||||
|
}
|
||||||
|
$image = ImageFile::fromFileObject($file);
|
||||||
|
$imgPath = $image->getPath();
|
||||||
|
$media = common_get_mime_media($file->mimetype);
|
||||||
|
if (Event::handle('CreateFileImageThumbnailSource', [$file, &$imgPath, $media])) {
|
||||||
|
if (!file_exists($imgPath)) {
|
||||||
|
throw new FileNotFoundException($imgPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First some mimetype specific exceptions
|
||||||
|
switch ($file->mimetype) {
|
||||||
|
case 'image/svg+xml':
|
||||||
|
throw new UseFileAsThumbnailException($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($image->animated && !common_config('thumbnail', 'animated')) {
|
||||||
|
// null means "always use file as thumbnail"
|
||||||
|
// false means you get choice between frozen frame or original when calling getThumbnail
|
||||||
|
if (is_null(common_config('thumbnail', 'animated')) || !$force_still) {
|
||||||
|
try {
|
||||||
|
// remote files with animated GIFs as thumbnails will match this
|
||||||
|
return File_thumbnail::byFile($file);
|
||||||
|
} catch (NoResultException $e) {
|
||||||
|
// and if it's not a remote file, it'll be safe to use the locally stored File
|
||||||
|
throw new UseFileAsThumbnailException($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image->getFileThumbnail(
|
||||||
|
$width,
|
||||||
|
$height,
|
||||||
|
$crop,
|
||||||
|
!is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save oEmbed-provided thumbnail data
|
* Save oEmbed-provided thumbnail data
|
||||||
*
|
*
|
||||||
@ -128,8 +191,8 @@ class File_thumbnail extends Managed_DataObject
|
|||||||
$tn->file_id = $file_id;
|
$tn->file_id = $file_id;
|
||||||
$tn->url = $url;
|
$tn->url = $url;
|
||||||
$tn->filename = $filename;
|
$tn->filename = $filename;
|
||||||
$tn->width = intval($width);
|
$tn->width = (int)$width;
|
||||||
$tn->height = intval($height);
|
$tn->height = (int)$height;
|
||||||
$tn->insert();
|
$tn->insert();
|
||||||
return $tn;
|
return $tn;
|
||||||
}
|
}
|
||||||
@ -148,30 +211,6 @@ class File_thumbnail extends Managed_DataObject
|
|||||||
return $dir . $filename;
|
return $dir . $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function url($filename)
|
|
||||||
{
|
|
||||||
File::tryFilename($filename);
|
|
||||||
|
|
||||||
// FIXME: private site thumbnails?
|
|
||||||
|
|
||||||
$path = common_config('thumbnail', 'path');
|
|
||||||
if (empty($path)) {
|
|
||||||
return File::url('thumb')."/{$filename}";
|
|
||||||
}
|
|
||||||
|
|
||||||
$protocol = (GNUsocial::useHTTPS() ? 'https' : 'http');
|
|
||||||
$server = common_config('thumbnail', 'server') ?: common_config('site', 'server');
|
|
||||||
|
|
||||||
if ($path[mb_strlen($path)-1] != '/') {
|
|
||||||
$path .= '/';
|
|
||||||
}
|
|
||||||
if ($path[0] != '/') {
|
|
||||||
$path = '/'.$path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $protocol.'://'.$server.$path.$filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilename()
|
public function getFilename()
|
||||||
{
|
{
|
||||||
return File::tryFilename($this->filename);
|
return File::tryFilename($this->filename);
|
||||||
@ -222,17 +261,11 @@ class File_thumbnail extends Managed_DataObject
|
|||||||
|
|
||||||
public function getUrl()
|
public function getUrl()
|
||||||
{
|
{
|
||||||
if (!empty($this->filename) || $this->getFile()->isLocal()) {
|
$url = common_local_url('attachment_thumbnail', ['filehash' => $this->getFile()->filehash]);
|
||||||
// A locally stored File, so we can dynamically generate a URL.
|
if (strpos($url, '?') === false) {
|
||||||
$url = common_local_url('attachment_thumbnail', array('attachment'=>$this->file_id));
|
$url .= '?';
|
||||||
if (strpos($url, '?') === false) {
|
|
||||||
$url .= '?';
|
|
||||||
}
|
|
||||||
return $url . http_build_query(array('w'=>$this->width, 'h'=>$this->height));
|
|
||||||
}
|
}
|
||||||
|
return $url . http_build_query(['w'=>$this->width, 'h'=>$this->height]);
|
||||||
// No local filename available, return the remote URL we have stored
|
|
||||||
return $this->url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHeight()
|
public function getHeight()
|
||||||
@ -272,7 +305,7 @@ class File_thumbnail extends Managed_DataObject
|
|||||||
return parent::delete($useWhere);
|
return parent::delete($useWhere);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFile()
|
public function getFile(): File
|
||||||
{
|
{
|
||||||
return File::getByID($this->file_id);
|
return File::getByID($this->file_id);
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,6 @@ class Attachment extends AttachmentListItem
|
|||||||
|
|
||||||
public function linkAttr()
|
public function linkAttr()
|
||||||
{
|
{
|
||||||
return array('rel' => 'external', 'href' => $this->attachment->getAttachmentDownloadUrl());
|
return ['rel' => 'external', 'href' => $this->attachment->getUrl(null)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,12 +75,6 @@ class AttachmentList extends Widget
|
|||||||
function show()
|
function show()
|
||||||
{
|
{
|
||||||
$attachments = $this->notice->attachments();
|
$attachments = $this->notice->attachments();
|
||||||
foreach ($attachments as $key=>$att) {
|
|
||||||
// Remove attachments which are not representable with neither a title nor thumbnail
|
|
||||||
if ($att->getTitle() === _('Untitled attachment') && !$att->hasThumbnail()) {
|
|
||||||
unset($attachments[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!count($attachments)) {
|
if (!count($attachments)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,11 @@ class AttachmentListItem extends Widget
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkAttr() {
|
function linkAttr() {
|
||||||
return array(
|
return [
|
||||||
'class' => 'u-url',
|
'class' => 'u-url',
|
||||||
'href' => $this->attachment->getAttachmentUrl(),
|
'href' => $this->attachment->getAttachmentDownloadUrl(),
|
||||||
'title' => $this->linkTitle());
|
'title' => $this->linkTitle()
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNoticeAttachment()
|
function showNoticeAttachment()
|
||||||
@ -105,101 +106,110 @@ class AttachmentListItem extends Widget
|
|||||||
function showRepresentation() {
|
function showRepresentation() {
|
||||||
$enclosure = $this->attachment->getEnclosure();
|
$enclosure = $this->attachment->getEnclosure();
|
||||||
|
|
||||||
if (Event::handle('StartShowAttachmentRepresentation', array($this->out, $this->attachment))) {
|
if (Event::handle('StartShowAttachmentRepresentation', [$this->out, $this->attachment])) {
|
||||||
|
|
||||||
$this->out->elementStart('label');
|
$this->out->elementStart('label');
|
||||||
$this->out->element('a', $this->linkAttr(), $this->title());
|
$this->out->element('a', ['rel' => 'external', 'href' => $this->attachment->getAttachmentUrl()], $this->title());
|
||||||
$this->out->elementEnd('label');
|
$this->out->elementEnd('label');
|
||||||
|
|
||||||
$this->out->element('br');
|
$this->out->element('br');
|
||||||
|
|
||||||
if (!empty($enclosure->mimetype)) {
|
try {
|
||||||
// First, prepare a thumbnail if it exists.
|
if (!empty($enclosure->mimetype)) {
|
||||||
$thumb = null;
|
// First, prepare a thumbnail if it exists.
|
||||||
try {
|
|
||||||
// Tell getThumbnail that we can show an animated image if it has one (4th arg, "force_still")
|
|
||||||
$thumb = $this->attachment->getThumbnail(null, null, false, false);
|
|
||||||
} catch (UseFileAsThumbnailException $e) {
|
|
||||||
$thumb = null;
|
$thumb = null;
|
||||||
} catch (UnsupportedMediaException $e) {
|
try {
|
||||||
// FIXME: Show a good representation of unsupported/unshowable images
|
// Tell getThumbnail that we can show an animated image if it has one (4th arg, "force_still")
|
||||||
$thumb = null;
|
$thumb = File_thumbnail::fromFileObject($this->attachment, null, null, false, false);
|
||||||
}
|
} catch (UseFileAsThumbnailException $e) {
|
||||||
|
$thumb = null;
|
||||||
// Then get the kind of mediatype we're dealing with
|
} catch (UnsupportedMediaException $e) {
|
||||||
$mediatype = common_get_mime_media($enclosure->mimetype);
|
// FIXME: Show a good representation of unsupported/unshowable images
|
||||||
|
$thumb = null;
|
||||||
// FIXME: Get proper mime recognition of Ogg files! If system has 'mediainfo', this should do it:
|
} catch (FileNotFoundException $e) {
|
||||||
// $ mediainfo --inform='General;%InternetMediaType%'
|
// Remote file
|
||||||
if ($this->attachment->mimetype === 'application/ogg') {
|
$thumb = null;
|
||||||
$mediatype = 'video'; // because this element can handle Ogg/Vorbis etc. on its own
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ugly hack to show text/html links which have a thumbnail (such as from oEmbed/OpenGraph image URLs)
|
|
||||||
if (!in_array($mediatype, ['image','audio','video']) && $thumb instanceof File_thumbnail) {
|
|
||||||
$mediatype = 'image';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($mediatype) {
|
|
||||||
// Anything we understand as an image, if we need special treatment, do it in StartShowAttachmentRepresentation
|
|
||||||
case 'image':
|
|
||||||
if ($thumb instanceof File_thumbnail) {
|
|
||||||
$this->out->element('img', $thumb->getHtmlAttrs(['class'=>'u-photo', 'alt' => '']));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// getUrl(true) because we don't want to hotlink, could be made configurable
|
|
||||||
$this->out->element('img', ['class'=>'u-photo',
|
|
||||||
'src'=>$this->attachment->getUrl(true),
|
|
||||||
'alt' => $this->attachment->getTitle()]);
|
|
||||||
} catch (FileNotStoredLocallyException $e) {
|
|
||||||
$url = $e->file->getUrl(false);
|
|
||||||
$this->out->element('a', ['href'=>$url, 'rel'=>'external'], $url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unset($thumb); // there's no need carrying this along after this
|
|
||||||
break;
|
|
||||||
|
|
||||||
// HTML5 media elements
|
|
||||||
case 'audio':
|
|
||||||
case 'video':
|
|
||||||
if ($thumb instanceof File_thumbnail) {
|
|
||||||
$poster = $thumb->getUrl();
|
|
||||||
unset($thumb); // there's no need carrying this along after this
|
|
||||||
} else {
|
|
||||||
$poster = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->out->elementStart($mediatype,
|
// Then get the kind of mediatype we're dealing with
|
||||||
array('class'=>"attachment_player u-{$mediatype}",
|
$mediatype = common_get_mime_media($enclosure->mimetype);
|
||||||
'poster'=>$poster,
|
|
||||||
'controls'=>'controls'));
|
|
||||||
$this->out->element('source',
|
|
||||||
array('src'=>$this->attachment->getUrl(),
|
|
||||||
'type'=>$this->attachment->mimetype));
|
|
||||||
$this->out->elementEnd($mediatype);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
// FIXME: Get proper mime recognition of Ogg files! If system has 'mediainfo', this should do it:
|
||||||
unset($thumb); // there's no need carrying this along
|
// $ mediainfo --inform='General;%InternetMediaType%'
|
||||||
switch (common_bare_mime($this->attachment->mimetype)) {
|
if ($this->attachment->mimetype === 'application/ogg') {
|
||||||
case 'text/plain':
|
$mediatype = 'video'; // because this element can handle Ogg/Vorbis etc. on its own
|
||||||
$this->element('div', ['class'=>'e-content plaintext'],
|
}
|
||||||
file_get_contents($this->attachment->getPath()));
|
|
||||||
break;
|
// Ugly hack to show text/html links which have a thumbnail (such as from oEmbed/OpenGraph image URLs)
|
||||||
case 'text/html':
|
if (!in_array($mediatype, ['image', 'audio', 'video']) && $thumb instanceof File_thumbnail) {
|
||||||
if (!empty($this->attachment->filename)
|
$mediatype = 'image';
|
||||||
&& (GNUsocial::isAjax() || common_config('attachments', 'show_html'))) {
|
}
|
||||||
// Locally-uploaded HTML. Scrub and display inline.
|
|
||||||
$this->showHtmlFile($this->attachment);
|
switch ($mediatype) {
|
||||||
|
// Anything we understand as an image, if we need special treatment, do it in StartShowAttachmentRepresentation
|
||||||
|
case 'image':
|
||||||
|
if ($thumb instanceof File_thumbnail) {
|
||||||
|
$this->out->element('img', $thumb->getHtmlAttrs(['class' => 'u-photo', 'alt' => '']));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// getUrl(true) because we don't want to hotlink, could be made configurable
|
||||||
|
$this->out->element('img', ['class' => 'u-photo',
|
||||||
|
'src' => $this->attachment->getUrl(true),
|
||||||
|
'alt' => $this->attachment->getTitle()]);
|
||||||
|
} catch (FileNotStoredLocallyException $e) {
|
||||||
|
$url = $e->file->getUrl(false);
|
||||||
|
$this->out->element('a', ['href' => $url, 'rel' => 'external'], $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($thumb); // there's no need carrying this along after this
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
// Fall through to default if it wasn't a _local_ text/html File object
|
// HTML5 media elements
|
||||||
default:
|
case 'audio':
|
||||||
Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
|
case 'video':
|
||||||
|
if ($thumb instanceof File_thumbnail) {
|
||||||
|
$poster = $thumb->getUrl();
|
||||||
|
unset($thumb); // there's no need carrying this along after this
|
||||||
|
} else {
|
||||||
|
$poster = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->out->elementStart($mediatype,
|
||||||
|
array('class' => "attachment_player u-{$mediatype}",
|
||||||
|
'poster' => $poster,
|
||||||
|
'controls' => 'controls'));
|
||||||
|
$this->out->element('source',
|
||||||
|
array('src' => $this->attachment->getUrl(),
|
||||||
|
'type' => $this->attachment->mimetype));
|
||||||
|
$this->out->elementEnd($mediatype);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
unset($thumb); // there's no need carrying this along
|
||||||
|
switch (common_bare_mime($this->attachment->mimetype)) {
|
||||||
|
case 'text/plain':
|
||||||
|
$this->element('div', ['class' => 'e-content plaintext'],
|
||||||
|
file_get_contents($this->attachment->getPath()));
|
||||||
|
break;
|
||||||
|
case 'text/html':
|
||||||
|
if (!empty($this->attachment->filename)
|
||||||
|
&& (GNUsocial::isAjax() || common_config('attachments', 'show_html'))) {
|
||||||
|
// Locally-uploaded HTML. Scrub and display inline.
|
||||||
|
$this->showHtmlFile($this->attachment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Fall through to default if it wasn't a _local_ text/html File object
|
||||||
|
default:
|
||||||
|
Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException $e) {
|
||||||
|
if (!$this->attachment->isLocal()) {
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::handle('EndShowAttachmentRepresentation', array($this->out, $this->attachment));
|
Event::handle('EndShowAttachmentRepresentation', array($this->out, $this->attachment));
|
||||||
|
@ -63,13 +63,13 @@ class ImageFile extends MediaFile
|
|||||||
* interactions (useful for temporary objects)
|
* interactions (useful for temporary objects)
|
||||||
* @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|null $filehash The hash of the file, if known. Optional
|
* @param string|null $filehash The hash of the file, if known. Optional
|
||||||
*
|
* @param string|null $fileurl
|
||||||
* @throws ClientException
|
* @throws ClientException
|
||||||
* @throws NoResultException
|
* @throws NoResultException
|
||||||
* @throws ServerException
|
* @throws ServerException
|
||||||
* @throws UnsupportedMediaException
|
* @throws UnsupportedMediaException
|
||||||
*/
|
*/
|
||||||
public function __construct(?int $id = null, string $filepath, ?string $filehash = null)
|
public function __construct(?int $id = null, string $filepath, ?string $filehash = null, ?string $fileurl = null)
|
||||||
{
|
{
|
||||||
$old_limit = ini_set('memory_limit', common_config('attachments', 'memory_limit'));
|
$old_limit = ini_set('memory_limit', common_config('attachments', 'memory_limit'));
|
||||||
|
|
||||||
@ -109,7 +109,8 @@ class ImageFile extends MediaFile
|
|||||||
$filepath,
|
$filepath,
|
||||||
$this->mimetype,
|
$this->mimetype,
|
||||||
$filehash,
|
$filehash,
|
||||||
$id
|
$id,
|
||||||
|
$fileurl
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->type === IMAGETYPE_JPEG) {
|
if ($this->type === IMAGETYPE_JPEG) {
|
||||||
@ -143,69 +144,28 @@ class ImageFile extends MediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a thumbnail from a file object
|
* Shortcut method to get an ImageFile from a File
|
||||||
*
|
*
|
||||||
* @param File $file
|
* @param File $file
|
||||||
* @return ImageFile
|
* @return ImageFile
|
||||||
|
* @throws ClientException
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException
|
||||||
* @throws UnsupportedMediaException
|
* @throws UnsupportedMediaException
|
||||||
* @throws UseFileAsThumbnailException
|
|
||||||
*/
|
*/
|
||||||
public static function fromFileObject(File $file)
|
public static function fromFileObject(File $file)
|
||||||
{
|
{
|
||||||
$imgPath = null;
|
|
||||||
$media = common_get_mime_media($file->mimetype);
|
$media = common_get_mime_media($file->mimetype);
|
||||||
if (Event::handle('CreateFileImageThumbnailSource', [$file, &$imgPath, $media])) {
|
|
||||||
if (empty($file->filename) && !file_exists($imgPath)) {
|
|
||||||
throw new FileNotFoundException($imgPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First some mimetype specific exceptions
|
// And we'll only consider it an image if it has such a media type
|
||||||
switch ($file->mimetype) {
|
if ($media !== 'image') {
|
||||||
case 'image/svg+xml':
|
throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath());
|
||||||
throw new UseFileAsThumbnailException($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// And we'll only consider it an image if it has such a media type
|
|
||||||
if ($media !== 'image') {
|
|
||||||
throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($file->filename)) {
|
|
||||||
$imgPath = $file->getPath();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists($imgPath)) {
|
$filepath = $file->getPath();
|
||||||
throw new FileNotFoundException($imgPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return new self($file->getID(), $filepath, $file->filehash);
|
||||||
$image = new self($file->getID(), $imgPath);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// Avoid deleting the original
|
|
||||||
try {
|
|
||||||
if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) {
|
|
||||||
common_debug(__METHOD__ . ': Deleting temporary file that was created as image file' .
|
|
||||||
'thumbnail source: ' . _ve($imgPath));
|
|
||||||
@unlink($imgPath);
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException $e) {
|
|
||||||
// File reported (via getPath) that the original file
|
|
||||||
// doesn't exist anyway, so it's safe to delete $imgPath
|
|
||||||
@unlink($imgPath);
|
|
||||||
}
|
|
||||||
common_debug(sprintf(
|
|
||||||
'Exception %s caught when creating ImageFile for File id==%s ' .
|
|
||||||
'and imgPath==%s: %s',
|
|
||||||
get_class($e),
|
|
||||||
_ve($file->id),
|
|
||||||
_ve($imgPath),
|
|
||||||
_ve($e->getMessage())
|
|
||||||
));
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
return $image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath()
|
public function getPath()
|
||||||
@ -251,9 +211,9 @@ class ImageFile extends MediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a file upload
|
* Create a new ImageFile object from an url
|
||||||
*
|
*
|
||||||
* Uses MediaFile's `fromURL` to do the majority of the work
|
* Uses MediaFile's `fromUrl` to do the majority of the work
|
||||||
* and ensures the uploaded file is in fact an image.
|
* and ensures the uploaded file is in fact an image.
|
||||||
*
|
*
|
||||||
* @param string $url Remote image URL
|
* @param string $url Remote image URL
|
||||||
@ -453,11 +413,6 @@ class ImageFile extends MediaFile
|
|||||||
return $outpath;
|
return $outpath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unlink()
|
|
||||||
{
|
|
||||||
@unlink($this->filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function scaleToFit($maxWidth = null, $maxHeight = null, $crop = null)
|
public function scaleToFit($maxWidth = null, $maxHeight = null, $crop = null)
|
||||||
{
|
{
|
||||||
return self::getScalingValues(
|
return self::getScalingValues(
|
||||||
@ -587,7 +542,21 @@ class ImageFile extends MediaFile
|
|||||||
return $count >= 1; // number of animated frames apart from the original image
|
return $count >= 1; // number of animated frames apart from the original image
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFileThumbnail($width, $height, $crop, $upscale = false)
|
/**
|
||||||
|
* @param $width
|
||||||
|
* @param $height
|
||||||
|
* @param $crop
|
||||||
|
* @param false $upscale
|
||||||
|
* @return File_thumbnail
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
* @throws FileNotStoredLocallyException
|
||||||
|
* @throws InvalidFilenameException
|
||||||
|
* @throws ServerException
|
||||||
|
* @throws UnsupportedMediaException
|
||||||
|
* @throws UseFileAsThumbnailException
|
||||||
|
*/
|
||||||
|
public function getFileThumbnail($width = null, $height = null, $crop = null, $upscale = false)
|
||||||
{
|
{
|
||||||
if (!$this->fileRecord instanceof File) {
|
if (!$this->fileRecord instanceof File) {
|
||||||
throw new ServerException('No File object attached to this ImageFile object.');
|
throw new ServerException('No File object attached to this ImageFile object.');
|
||||||
@ -679,9 +648,7 @@ class ImageFile extends MediaFile
|
|||||||
|
|
||||||
return File_thumbnail::saveThumbnail(
|
return File_thumbnail::saveThumbnail(
|
||||||
$this->fileRecord->getID(),
|
$this->fileRecord->getID(),
|
||||||
// no url since we generated it ourselves and can dynamically
|
$this->fileRecord->getUrl(false),
|
||||||
// generate the url
|
|
||||||
null,
|
|
||||||
$width,
|
$width,
|
||||||
$height,
|
$height,
|
||||||
$outname
|
$outname
|
||||||
|
@ -41,9 +41,11 @@ class InlineAttachmentListItem extends AttachmentListItem
|
|||||||
// XXX: RDFa
|
// XXX: RDFa
|
||||||
// TODO: add notice_type class e.g., notice_video, notice_image
|
// TODO: add notice_type class e.g., notice_video, notice_image
|
||||||
$this->out->elementStart('li',
|
$this->out->elementStart('li',
|
||||||
array('class' => 'inline-attachment',
|
[
|
||||||
'id' => 'attachment-' . $this->attachment->getID(),
|
'class' => 'inline-attachment',
|
||||||
));
|
'id' => 'attachment-' . $this->attachment->getID(),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,24 +48,25 @@ class MediaFile
|
|||||||
/**
|
/**
|
||||||
* MediaFile constructor.
|
* MediaFile constructor.
|
||||||
*
|
*
|
||||||
* @param string $filepath The path of the file this media refers to. Required
|
* @param string|null $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 string|null $filehash The hash of the file, if known. Optional
|
* @param string|null $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 int|null $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)
|
||||||
*
|
* @param string|null $fileurl Provide if remote
|
||||||
* @throws ClientException
|
* @throws ClientException
|
||||||
* @throws NoResultException
|
* @throws NoResultException
|
||||||
* @throws ServerException
|
* @throws ServerException
|
||||||
*/
|
*/
|
||||||
public function __construct(string $filepath, string $mimetype, ?string $filehash = null, ?int $id = null)
|
public function __construct(?string $filepath = null, string $mimetype, ?string $filehash = null, ?int $id = null, ?string $fileurl = null)
|
||||||
{
|
{
|
||||||
$this->filepath = $filepath;
|
$this->filepath = $filepath;
|
||||||
$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 = is_null($filepath) ? null : self::getHashOfFile($this->filepath, $filehash);
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
|
$this->fileurl = $fileurl;
|
||||||
|
|
||||||
// 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
|
||||||
@ -82,16 +83,28 @@ class MediaFile
|
|||||||
// Otherwise, store it
|
// Otherwise, store it
|
||||||
$this->fileRecord = $this->storeFile();
|
$this->fileRecord = $this->storeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->fileurl = common_local_url(
|
|
||||||
'attachment',
|
|
||||||
['attachment' => $this->fileRecord->id]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->short_fileurl = common_shorten_url($this->fileurl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method to get a MediaFile from a File
|
||||||
|
*
|
||||||
|
* @param File $file
|
||||||
|
* @return MediaFile|ImageFile
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException
|
||||||
|
*/
|
||||||
|
public static function fromFileObject(File $file)
|
||||||
|
{
|
||||||
|
$filepath = null;
|
||||||
|
try {
|
||||||
|
$filepath = $file->getPath();
|
||||||
|
} catch (Exception $e) {}
|
||||||
|
return new self($filepath, common_get_mime_media($file->mimetype), $file->filehash, $file->getID());
|
||||||
|
}
|
||||||
|
|
||||||
public function attachToNotice(Notice $notice)
|
public function attachToNotice(Notice $notice)
|
||||||
{
|
{
|
||||||
File_to_post::processNew($this->fileRecord, $notice);
|
File_to_post::processNew($this->fileRecord, $notice);
|
||||||
@ -102,9 +115,26 @@ class MediaFile
|
|||||||
return File::path($this->filename);
|
return File::path($this->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool|null $use_local true means require local, null means prefer original, false means use whatever is stored
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUrl(?bool $use_local=null): ?string
|
||||||
|
{
|
||||||
|
if ($use_local !== false) {
|
||||||
|
if (empty($this->fileurl)) {
|
||||||
|
// A locally stored file, so let's generate a URL for our instance.
|
||||||
|
return common_local_url('attachment_view', ['filehash' => $this->filehash]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original file's URL
|
||||||
|
return $this->fileurl;
|
||||||
|
}
|
||||||
|
|
||||||
public function shortUrl()
|
public function shortUrl()
|
||||||
{
|
{
|
||||||
return $this->short_fileurl;
|
return common_shorten_url($this->getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEnclosure()
|
public function getEnclosure()
|
||||||
@ -112,11 +142,27 @@ class MediaFile
|
|||||||
return $this->getFile()->getEnclosure();
|
return $this->getFile()->getEnclosure();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete($useWhere=false)
|
||||||
{
|
{
|
||||||
|
if (!is_null($this->fileRecord)) {
|
||||||
|
$this->fileRecord->delete($useWhere);
|
||||||
|
}
|
||||||
@unlink($this->filepath);
|
@unlink($this->filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function unlink()
|
||||||
|
{
|
||||||
|
$this->filename = null;
|
||||||
|
// Delete the file, if it exists locally
|
||||||
|
if (!empty($this->filepath) && file_exists($this->filepath)) {
|
||||||
|
$deleted = @unlink($this->filepath);
|
||||||
|
if (!$deleted) {
|
||||||
|
common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', $this->filepath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->fileRecord->unlink();
|
||||||
|
}
|
||||||
|
|
||||||
public function getFile()
|
public function getFile()
|
||||||
{
|
{
|
||||||
if (!$this->fileRecord instanceof File) {
|
if (!$this->fileRecord instanceof File) {
|
||||||
@ -164,19 +210,19 @@ class MediaFile
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$file = File::getByHash($this->filehash);
|
$file = File::getByHash($this->filehash);
|
||||||
// We're done here. Yes. Already. We assume sha256 won't collide on us anytime soon.
|
if (is_null($this->fileurl) && is_null($file->getUrl(false))) {
|
||||||
return $file;
|
// An already existing local file is being re-added, return it
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
} catch (NoResultException $e) {
|
} catch (NoResultException $e) {
|
||||||
// Well, let's just continue below.
|
// Well, let's just continue below.
|
||||||
}
|
}
|
||||||
|
|
||||||
$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->url = $this->fileurl;
|
||||||
$file->url = $fileurl;
|
$file->urlhash = is_null($file->url) ? null : File::hashurl($file->url);
|
||||||
$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) {
|
||||||
@ -196,7 +242,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);
|
||||||
@ -205,6 +251,8 @@ class MediaFile
|
|||||||
// may have generated a temporary file from a
|
// may have generated a temporary file from a
|
||||||
// video support plugin or something.
|
// video support plugin or something.
|
||||||
// FIXME: Do this more automagically.
|
// FIXME: Do this more automagically.
|
||||||
|
// Honestly, I think this is unlikely these days,
|
||||||
|
// but better be safe than sure, I guess
|
||||||
if ($image->getPath() != $file->getPath()) {
|
if ($image->getPath() != $file->getPath()) {
|
||||||
$image->unlink();
|
$image->unlink();
|
||||||
}
|
}
|
||||||
@ -390,6 +438,16 @@ class MediaFile
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$file = File::getByHash($filehash);
|
$file = File::getByHash($filehash);
|
||||||
|
while ($file->fetch()) {
|
||||||
|
if ($file->getUrl(false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ImageFile::fromFileObject($file);
|
||||||
|
} catch (UnsupportedMediaException $e) {
|
||||||
|
return MediaFile::fromFileObject($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
// If no exception is thrown the file exists locally, so we'll use that and just add redirections.
|
// If no exception is thrown the file exists locally, so we'll use that and just add redirections.
|
||||||
// but if the _actual_ locally stored file doesn't exist, getPath will throw FileNotFoundException
|
// but if the _actual_ locally stored file doesn't exist, getPath will throw FileNotFoundException
|
||||||
$filepath = $file->getPath();
|
$filepath = $file->getPath();
|
||||||
@ -432,10 +490,10 @@ class MediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($media == 'image') {
|
if ($media == 'image') {
|
||||||
return new ImageFile(null, $filepath);
|
return new ImageFile(null, $filepath, $filehash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new self($filepath, $mimetype, $filehash);
|
return new self($filepath, $mimetype, $filehash, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -464,6 +522,48 @@ class MediaFile
|
|||||||
throw new ServerException(sprintf('Invalid remote media URL %s.', $url));
|
throw new ServerException(sprintf('Invalid remote media URL %s.', $url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$http = new HTTPClient();
|
||||||
|
common_debug(sprintf('Performing HEAD request for incoming activity to avoid ' .
|
||||||
|
'unnecessarily downloading too large files. URL: %s',
|
||||||
|
$url));
|
||||||
|
$head = $http->head($url);
|
||||||
|
$url = $head->getEffectiveUrl(); // to avoid going through redirects again
|
||||||
|
if (empty($url)) {
|
||||||
|
throw new ServerException(sprintf('URL after redirects is somehow empty, for URL %s.', $url));
|
||||||
|
}
|
||||||
|
$headers = $head->getHeader();
|
||||||
|
$headers = array_change_key_case($headers, CASE_LOWER);
|
||||||
|
if (array_key_exists('content-length', $headers)) {
|
||||||
|
$fileQuota = common_config('attachments', 'file_quota');
|
||||||
|
$fileSize = $headers['content-length'];
|
||||||
|
if ($fileSize > $fileQuota) {
|
||||||
|
// TRANS: Message used to be inserted as %2$s in the text "No file may
|
||||||
|
// TRANS: be larger than %1$d byte and the file you sent was %2$s.".
|
||||||
|
// TRANS: %1$d is the number of bytes of an uploaded file.
|
||||||
|
$fileSizeText = sprintf(_m('%1$d byte', '%1$d bytes', $fileSize), $fileSize);
|
||||||
|
|
||||||
|
// TRANS: Message given if an upload is larger than the configured maximum.
|
||||||
|
// TRANS: %1$d (used for plural) is the byte limit for uploads,
|
||||||
|
// TRANS: %2$s is the proper form of "n bytes". This is the only ways to have
|
||||||
|
// TRANS: gettext support multiple plurals in the same message, unfortunately...
|
||||||
|
throw new ClientException(
|
||||||
|
sprintf(
|
||||||
|
_m(
|
||||||
|
'No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
|
||||||
|
'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
|
||||||
|
$fileQuota
|
||||||
|
),
|
||||||
|
$fileQuota,
|
||||||
|
$fileSizeText
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ServerException(sprintf('Invalid remote media URL headers %s.', $url));
|
||||||
|
}
|
||||||
|
unset($head);
|
||||||
|
unset($headers);
|
||||||
|
|
||||||
$tempfile = new TemporaryFile('gs-mediafile');
|
$tempfile = new TemporaryFile('gs-mediafile');
|
||||||
fwrite($tempfile->getResource(), HTTPClient::quickGet($url));
|
fwrite($tempfile->getResource(), HTTPClient::quickGet($url));
|
||||||
fflush($tempfile->getResource());
|
fflush($tempfile->getResource());
|
||||||
@ -471,7 +571,7 @@ class MediaFile
|
|||||||
$filehash = strtolower(self::getHashOfFile($tempfile->getRealPath()));
|
$filehash = strtolower(self::getHashOfFile($tempfile->getRealPath()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$file = File::getByHash($filehash);
|
$file = File::getByUrl($url);
|
||||||
/*
|
/*
|
||||||
* If no exception is thrown the file exists locally, so we'll use
|
* If no exception is thrown the file exists locally, so we'll use
|
||||||
* that and just add redirections.
|
* that and just add redirections.
|
||||||
@ -531,10 +631,10 @@ class MediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($media === 'image') {
|
if ($media === 'image') {
|
||||||
return new ImageFile(null, $filepath);
|
return new ImageFile(null, $filepath, $filehash, $url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new self($filepath, $mimetype, $filehash);
|
return new self($filepath, $mimetype, $filehash, null, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromFileInfo(SplFileInfo $finfo, Profile $scoped = null)
|
public static function fromFileInfo(SplFileInfo $finfo, Profile $scoped = null)
|
||||||
@ -543,6 +643,7 @@ class MediaFile
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$file = File::getByHash($filehash);
|
$file = File::getByHash($filehash);
|
||||||
|
$file->fetch();
|
||||||
// Already have it, so let's reuse the locally stored File
|
// Already have it, so let's reuse the locally stored File
|
||||||
// by using getPath we also check whether the file exists
|
// by using getPath we also check whether the file exists
|
||||||
// and throw a FileNotFoundException with the path if it doesn't.
|
// and throw a FileNotFoundException with the path if it doesn't.
|
||||||
|
@ -223,16 +223,18 @@ class Router
|
|||||||
['q' => '.+']);
|
['q' => '.+']);
|
||||||
$m->connect('search/notice/rss', ['action' => 'noticesearchrss']);
|
$m->connect('search/notice/rss', ['action' => 'noticesearchrss']);
|
||||||
|
|
||||||
foreach (['' => 'attachment',
|
// Attachment page for file
|
||||||
'/view' => 'attachment_view',
|
$m->connect("attachment/:attachment",
|
||||||
'/download' => 'attachment_download',
|
['action' => 'attachment'],
|
||||||
|
['attachment' => '[0-9]+']);
|
||||||
|
|
||||||
|
// Retrieve local file
|
||||||
|
foreach (['/view' => 'attachment_view',
|
||||||
|
'/download' => 'attachment_download',
|
||||||
'/thumbnail' => 'attachment_thumbnail'] as $postfix => $action) {
|
'/thumbnail' => 'attachment_thumbnail'] as $postfix => $action) {
|
||||||
foreach (['filehash' => '[A-Za-z0-9._-]{64}',
|
$m->connect("attachment/:filehash{$postfix}",
|
||||||
'attachment' => '[0-9]+'] as $type => $match) {
|
|
||||||
$m->connect("attachment/:{$type}{$postfix}",
|
|
||||||
['action' => $action],
|
['action' => $action],
|
||||||
[$type => $match]);
|
['filehash' => '[A-Za-z0-9._-]{64}']);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
|
$m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
|
||||||
|
@ -189,14 +189,12 @@ class EmbedPlugin extends Plugin
|
|||||||
foreach (['xml', 'json'] as $format) {
|
foreach (['xml', 'json'] as $format) {
|
||||||
$action->element(
|
$action->element(
|
||||||
'link',
|
'link',
|
||||||
['rel' =>'alternate',
|
[
|
||||||
'type' => "application/{$format}+oembed",
|
'rel' =>'alternate',
|
||||||
'href' => common_local_url(
|
'type' => "application/{$format}+oembed",
|
||||||
'oembed',
|
'href' => common_local_url('oembed', [], ['format' => $format, 'url' => $url]),
|
||||||
[],
|
'title' => 'oEmbed'
|
||||||
['format' => $format, 'url' => $url]
|
]
|
||||||
),
|
|
||||||
'title' => 'oEmbed']
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class OEmbedAction extends Action
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$thumb = $attachment->getThumbnail();
|
$thumb = $attachment->getThumbnail();
|
||||||
$thumb_url = File_thumbnail::url($thumb->filename);
|
$thumb_url = $thumb->getUrl();
|
||||||
$oembed['thumbnail_url'] = $thumb_url;
|
$oembed['thumbnail_url'] = $thumb_url;
|
||||||
break; // only first one
|
break; // only first one
|
||||||
} catch (UseFileAsThumbnailException $e) {
|
} catch (UseFileAsThumbnailException $e) {
|
||||||
|
@ -917,7 +917,7 @@ class Ostatus_profile extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ImageFile throws exception if something goes wrong, which we'll let go on its merry way
|
// ImageFile throws exception if something goes wrong, which we'll let go on its merry way
|
||||||
$imagefile = ImageFile::fromURL($url);
|
$imagefile = ImageFile::fromUrl($url);
|
||||||
|
|
||||||
$self = $this->localProfile();
|
$self = $this->localProfile();
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ class StoreRemoteMediaPlugin extends Plugin
|
|||||||
try {
|
try {
|
||||||
// Exception will be thrown before $file is set to anything, so old $file value will be kept
|
// Exception will be thrown before $file is set to anything, so old $file value will be kept
|
||||||
$file = File::getByHash($filehash);
|
$file = File::getByHash($filehash);
|
||||||
|
$file->fetch();
|
||||||
|
|
||||||
//FIXME: Add some code so we don't have to store duplicate File rows for same hash files.
|
//FIXME: Add some code so we don't have to store duplicate File rows for same hash files.
|
||||||
} catch (NoResultException $e) {
|
} catch (NoResultException $e) {
|
||||||
|
Loading…
Reference in New Issue
Block a user