forked from GNUsocial/gnu-social
Merge branch 'nightly' of biodantas/gnu-social into nightly
This commit is contained in:
commit
abfd691fda
@ -649,7 +649,16 @@ detection.
|
|||||||
* `supported`: an array of mime types you accept to store and distribute,
|
* `supported`: an array of mime types you accept to store and distribute,
|
||||||
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
|
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
|
||||||
setup your server to properly recognize the types you want to
|
setup your server to properly recognize the types you want to
|
||||||
support.
|
support. It's important to use the result of calling `image_type_to_extension`
|
||||||
|
for the appropriate image type, in the case of images. This is so all parts of
|
||||||
|
the code see the same extension for each image type (jpg vs jpeg).
|
||||||
|
For example, to enable BMP uploads, add this to the config.php file:
|
||||||
|
$config['attachments']['supported'][image_type_to_mime_type(IMAGETYPE_GIF)]
|
||||||
|
= image_type_to_extension(IMAGETYPE_GIF);
|
||||||
|
See https://www.php.net/manual/en/function.image-type-to-mime-type.php for a
|
||||||
|
list of such constants. If a filetype is not listed there, it's possible to add
|
||||||
|
the mimetype and the extension by hand, but they need to match those returned by
|
||||||
|
the file command.
|
||||||
|
|
||||||
* `uploads`: false to disable uploading files with notices (true by default).
|
* `uploads`: false to disable uploading files with notices (true by default).
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# GNU social 1.19.x
|
# GNU social 1.20.x
|
||||||
(c) 2010-2019 Free Software Foundation, Inc
|
(c) 2010-2019 Free Software Foundation, Inc
|
||||||
|
|
||||||
This is the README file for GNU social, the free
|
This is the README file for GNU social, the free
|
||||||
|
224
classes/File.php
224
classes/File.php
@ -1,23 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/**
|
||||||
* StatusNet - the distributed open-source microblogging tool
|
* GNU social - a federating social network
|
||||||
* Copyright (C) 2008, 2009, StatusNet, Inc.
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Abstraction for 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
|
* 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
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* @category Files
|
||||||
|
* @package GNUsocial
|
||||||
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
|
* @author Miguel Dantas <biodantas@gmail.com>
|
||||||
|
* @copyright 2008-2009, 2019 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
|
||||||
|
* @link https://www.gnu.org/software/social/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
defined('GNUSOCIAL') || die();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table Definition for file
|
* Table Definition for file
|
||||||
@ -71,20 +80,20 @@ class File extends Managed_DataObject
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isProtected($url) {
|
public static function isProtected($url)
|
||||||
|
{
|
||||||
|
$protected_urls_exps = array(
|
||||||
|
'https://www.facebook.com/login.php',
|
||||||
|
common_path('main/login')
|
||||||
|
);
|
||||||
|
|
||||||
$protected_urls_exps = array(
|
foreach ($protected_urls_exps as $protected_url_exp) {
|
||||||
'https://www.facebook.com/login.php',
|
if (preg_match('!^'.preg_quote($protected_url_exp).'(.*)$!i', $url) === 1) {
|
||||||
common_path('main/login')
|
return true;
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($protected_urls_exps as $protected_url_exp) {
|
return false;
|
||||||
if (preg_match('!^'.preg_quote($protected_url_exp).'(.*)$!i', $url) === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,6 +102,7 @@ class File extends Managed_DataObject
|
|||||||
* @param array $redir_data lookup data eg from File_redirection::where()
|
* @param array $redir_data lookup data eg from File_redirection::where()
|
||||||
* @param string $given_url
|
* @param string $given_url
|
||||||
* @return File
|
* @return File
|
||||||
|
* @throws ServerException
|
||||||
*/
|
*/
|
||||||
public static function saveNew(array $redir_data, $given_url)
|
public static function saveNew(array $redir_data, $given_url)
|
||||||
{
|
{
|
||||||
@ -142,16 +152,27 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
$file = new File;
|
$file = new File;
|
||||||
$file->url = $given_url;
|
$file->url = $given_url;
|
||||||
if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
|
if (!empty($redir_data['protected'])) {
|
||||||
if (!empty($redir_data['title'])) $file->title = $redir_data['title'];
|
$file->protected = $redir_data['protected'];
|
||||||
if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type'];
|
}
|
||||||
if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']);
|
if (!empty($redir_data['title'])) {
|
||||||
if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']);
|
$file->title = $redir_data['title'];
|
||||||
|
}
|
||||||
|
if (!empty($redir_data['type'])) {
|
||||||
|
$file->mimetype = $redir_data['type'];
|
||||||
|
}
|
||||||
|
if (!empty($redir_data['size'])) {
|
||||||
|
$file->size = intval($redir_data['size']);
|
||||||
|
}
|
||||||
|
if (isset($redir_data['time']) && $redir_data['time'] > 0) {
|
||||||
|
$file->date = intval($redir_data['time']);
|
||||||
|
}
|
||||||
$file->saveFile();
|
$file->saveFile();
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveFile() {
|
public function saveFile()
|
||||||
|
{
|
||||||
$this->urlhash = self::hashurl($this->url);
|
$this->urlhash = self::hashurl($this->url);
|
||||||
|
|
||||||
if (!Event::handle('StartFileSaveNew', array(&$this))) {
|
if (!Event::handle('StartFileSaveNew', array(&$this))) {
|
||||||
@ -183,7 +204,8 @@ class File extends Managed_DataObject
|
|||||||
*
|
*
|
||||||
* @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, $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');
|
||||||
}
|
}
|
||||||
@ -212,12 +234,13 @@ class File extends Managed_DataObject
|
|||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function respectsQuota(Profile $scoped, $fileSize) {
|
public static function respectsQuota(Profile $scoped, $fileSize)
|
||||||
|
{
|
||||||
if ($fileSize > common_config('attachments', 'file_quota')) {
|
if ($fileSize > common_config('attachments', 'file_quota')) {
|
||||||
// TRANS: Message used to be inserted as %2$s in the text "No file may
|
// 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: 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.
|
// TRANS: %1$d is the number of bytes of an uploaded file.
|
||||||
$fileSizeText = sprintf(_m('%1$d byte','%1$d bytes',$fileSize),$fileSize);
|
$fileSizeText = sprintf(_m('%1$d byte', '%1$d bytes', $fileSize), $fileSize);
|
||||||
|
|
||||||
$fileQuota = common_config('attachments', 'file_quota');
|
$fileQuota = common_config('attachments', 'file_quota');
|
||||||
// TRANS: Message given if an upload is larger than the configured maximum.
|
// TRANS: Message given if an upload is larger than the configured maximum.
|
||||||
@ -225,10 +248,16 @@ class File extends Managed_DataObject
|
|||||||
// TRANS: %2$s is the proper form of "n bytes". This is the only ways to have
|
// 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...
|
// TRANS: gettext support multiple plurals in the same message, unfortunately...
|
||||||
throw new ClientException(
|
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.',
|
sprintf(
|
||||||
'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
|
_m(
|
||||||
$fileQuota),
|
'No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
|
||||||
$fileQuota, $fileSizeText));
|
'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
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = new File;
|
$file = new File;
|
||||||
@ -241,10 +270,15 @@ class File extends Managed_DataObject
|
|||||||
// TRANS: Message given if an upload would exceed user quota.
|
// TRANS: Message given if an upload would exceed user quota.
|
||||||
// TRANS: %d (number) is the user quota in bytes and is used for plural.
|
// TRANS: %d (number) is the user quota in bytes and is used for plural.
|
||||||
throw new ClientException(
|
throw new ClientException(
|
||||||
sprintf(_m('A file this large would exceed your user quota of %d byte.',
|
sprintf(
|
||||||
'A file this large would exceed your user quota of %d bytes.',
|
_m(
|
||||||
common_config('attachments', 'user_quota')),
|
'A file this large would exceed your user quota of %d byte.',
|
||||||
common_config('attachments', 'user_quota')));
|
'A file this large would exceed your user quota of %d bytes.',
|
||||||
|
common_config('attachments', 'user_quota')
|
||||||
|
),
|
||||||
|
common_config('attachments', 'user_quota')
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
|
$query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
|
||||||
$file->query($query);
|
$file->query($query);
|
||||||
@ -254,10 +288,15 @@ class File extends Managed_DataObject
|
|||||||
// TRANS: Message given id an upload would exceed a user's monthly quota.
|
// TRANS: Message given id an upload would exceed a user's monthly quota.
|
||||||
// TRANS: $d (number) is the monthly user quota in bytes and is used for plural.
|
// TRANS: $d (number) is the monthly user quota in bytes and is used for plural.
|
||||||
throw new ClientException(
|
throw new ClientException(
|
||||||
sprintf(_m('A file this large would exceed your monthly quota of %d byte.',
|
sprintf(
|
||||||
'A file this large would exceed your monthly quota of %d bytes.',
|
_m(
|
||||||
common_config('attachments', 'monthly_quota')),
|
'A file this large would exceed your monthly quota of %d byte.',
|
||||||
common_config('attachments', 'monthly_quota')));
|
'A file this large would exceed your monthly quota of %d bytes.',
|
||||||
|
common_config('attachments', 'monthly_quota')
|
||||||
|
),
|
||||||
|
common_config('attachments', 'monthly_quota')
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -274,7 +313,7 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
// where should the file go?
|
// where should the file go?
|
||||||
|
|
||||||
static function filename(Profile $profile, $origname, $mimetype)
|
public static function filename(Profile $profile, $origname, $mimetype)
|
||||||
{
|
{
|
||||||
$ext = self::guessMimeExtension($mimetype, $origname);
|
$ext = self::guessMimeExtension($mimetype, $origname);
|
||||||
|
|
||||||
@ -298,10 +337,12 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $mimetype The mimetype we've discovered for this file.
|
* @param $mimetype string The mimetype we've discovered for this file.
|
||||||
* @param $filename An optional filename which we can use on failure.
|
* @param $filename string An optional filename which we can use on failure.
|
||||||
|
* @return mixed|string
|
||||||
|
* @throws ClientException
|
||||||
*/
|
*/
|
||||||
static function guessMimeExtension($mimetype, $filename=null)
|
public static function guessMimeExtension($mimetype, $filename=null)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// first see if we know the extension for our mimetype
|
// first see if we know the extension for our mimetype
|
||||||
@ -349,16 +390,17 @@ class File extends Managed_DataObject
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation for as-saved base filenames
|
* Validation for as-saved base filenames
|
||||||
|
* @param $filename
|
||||||
|
* @return false|int
|
||||||
*/
|
*/
|
||||||
static function validFilename($filename)
|
public static function validFilename($filename)
|
||||||
{
|
{
|
||||||
return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
|
return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function tryFilename($filename)
|
public static function tryFilename($filename)
|
||||||
{
|
{
|
||||||
if (!self::validFilename($filename))
|
if (!self::validFilename($filename)) {
|
||||||
{
|
|
||||||
throw new InvalidFilenameException($filename);
|
throw new InvalidFilenameException($filename);
|
||||||
}
|
}
|
||||||
// if successful, return the filename for easy if-statementing
|
// if successful, return the filename for easy if-statementing
|
||||||
@ -366,9 +408,11 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ClientException on invalid filename
|
* @param $filename
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidFilenameException
|
||||||
*/
|
*/
|
||||||
static function path($filename)
|
public static function path($filename)
|
||||||
{
|
{
|
||||||
self::tryFilename($filename);
|
self::tryFilename($filename);
|
||||||
|
|
||||||
@ -381,19 +425,18 @@ class File extends Managed_DataObject
|
|||||||
return $dir . $filename;
|
return $dir . $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function url($filename)
|
public static function url($filename)
|
||||||
{
|
{
|
||||||
self::tryFilename($filename);
|
self::tryFilename($filename);
|
||||||
|
|
||||||
if (common_config('site','private')) {
|
if (common_config('site', 'private')) {
|
||||||
|
return common_local_url(
|
||||||
return common_local_url('getfile',
|
'getfile',
|
||||||
array('filename' => $filename));
|
array('filename' => $filename)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GNUsocial::useHTTPS()) {
|
if (GNUsocial::useHTTPS()) {
|
||||||
|
|
||||||
$sslserver = common_config('attachments', 'sslserver');
|
$sslserver = common_config('attachments', 'sslserver');
|
||||||
|
|
||||||
if (empty($sslserver)) {
|
if (empty($sslserver)) {
|
||||||
@ -402,7 +445,7 @@ class File extends Managed_DataObject
|
|||||||
if (is_string(common_config('site', 'sslserver')) &&
|
if (is_string(common_config('site', 'sslserver')) &&
|
||||||
mb_strlen(common_config('site', 'sslserver')) > 0) {
|
mb_strlen(common_config('site', 'sslserver')) > 0) {
|
||||||
$server = common_config('site', 'sslserver');
|
$server = common_config('site', 'sslserver');
|
||||||
} else if (common_config('site', 'server')) {
|
} elseif (common_config('site', 'server')) {
|
||||||
$server = common_config('site', 'server');
|
$server = common_config('site', 'server');
|
||||||
}
|
}
|
||||||
$path = common_config('site', 'path') . '/file/';
|
$path = common_config('site', 'path') . '/file/';
|
||||||
@ -439,9 +482,10 @@ class File extends Managed_DataObject
|
|||||||
return $protocol.'://'.$server.$path.$filename;
|
return $protocol.'://'.$server.$path.$filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
static $_enclosures = array();
|
public static $_enclosures = array();
|
||||||
|
|
||||||
function getEnclosure(){
|
public function getEnclosure()
|
||||||
|
{
|
||||||
if (isset(self::$_enclosures[$this->getID()])) {
|
if (isset(self::$_enclosures[$this->getID()])) {
|
||||||
return self::$_enclosures[$this->getID()];
|
return self::$_enclosures[$this->getID()];
|
||||||
}
|
}
|
||||||
@ -515,8 +559,12 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $image->getFileThumbnail($width, $height, $crop,
|
return $image->getFileThumbnail(
|
||||||
!is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale'));
|
$width,
|
||||||
|
$height,
|
||||||
|
$crop,
|
||||||
|
!is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath()
|
public function getPath()
|
||||||
@ -534,7 +582,9 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $use_local true means require local, null means prefer local, false means use whatever is stored
|
* @param mixed $use_local true means require local, null means prefer local, false means use whatever is stored
|
||||||
|
* @return string
|
||||||
|
* @throws FileNotStoredLocallyException
|
||||||
*/
|
*/
|
||||||
public function getUrl($use_local=null)
|
public function getUrl($use_local=null)
|
||||||
{
|
{
|
||||||
@ -554,7 +604,7 @@ class File extends Managed_DataObject
|
|||||||
return $this->url;
|
return $this->url;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function getByUrl($url)
|
public static function getByUrl($url)
|
||||||
{
|
{
|
||||||
$file = new File();
|
$file = new File();
|
||||||
$file->urlhash = self::hashurl($url);
|
$file->urlhash = self::hashurl($url);
|
||||||
@ -565,9 +615,11 @@ class File extends Managed_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $hashstr String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
|
* @param string $hashstr String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
|
||||||
|
* @return File
|
||||||
|
* @throws NoResultException
|
||||||
*/
|
*/
|
||||||
static public function getByHash($hashstr)
|
public static function getByHash($hashstr)
|
||||||
{
|
{
|
||||||
$file = new File();
|
$file = new File();
|
||||||
$file->filehash = strtolower($hashstr);
|
$file->filehash = strtolower($hashstr);
|
||||||
@ -584,10 +636,13 @@ class File extends Managed_DataObject
|
|||||||
throw new ServerException('URL already exists in DB');
|
throw new ServerException('URL already exists in DB');
|
||||||
}
|
}
|
||||||
$sql = 'UPDATE %1$s SET urlhash=%2$s, url=%3$s WHERE urlhash=%4$s;';
|
$sql = 'UPDATE %1$s SET urlhash=%2$s, url=%3$s WHERE urlhash=%4$s;';
|
||||||
$result = $this->query(sprintf($sql, $this->tableName(),
|
$result = $this->query(sprintf(
|
||||||
$this->_quote((string)self::hashurl($url)),
|
$sql,
|
||||||
$this->_quote((string)$url),
|
$this->tableName(),
|
||||||
$this->_quote((string)$this->urlhash)));
|
$this->_quote((string)self::hashurl($url)),
|
||||||
|
$this->_quote((string)$url),
|
||||||
|
$this->_quote((string)$this->urlhash)
|
||||||
|
));
|
||||||
if ($result === false) {
|
if ($result === false) {
|
||||||
common_log_db_error($this, 'UPDATE', __FILE__);
|
common_log_db_error($this, 'UPDATE', __FILE__);
|
||||||
throw new ServerException("Could not UPDATE {$this->tableName()}.url");
|
throw new ServerException("Could not UPDATE {$this->tableName()}.url");
|
||||||
@ -604,7 +659,7 @@ class File extends Managed_DataObject
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function blowCache($last=false)
|
public function blowCache($last=false)
|
||||||
{
|
{
|
||||||
self::blow('file:notice-ids:%s', $this->id);
|
self::blow('file:notice-ids:%s', $this->id);
|
||||||
if ($last) {
|
if ($last) {
|
||||||
@ -624,7 +679,7 @@ class File extends Managed_DataObject
|
|||||||
* @return array ids of notices that link to this file
|
* @return array ids of notices that link to this file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
|
public function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
|
||||||
{
|
{
|
||||||
// FIXME: Try to get the Profile::current() here in some other way to avoid mixing
|
// FIXME: Try to get the Profile::current() here in some other way to avoid mixing
|
||||||
// the current session user with possibly background/queue processing.
|
// the current session user with possibly background/queue processing.
|
||||||
@ -632,14 +687,13 @@ class File extends Managed_DataObject
|
|||||||
return $stream->getNotices($offset, $limit, $since_id, $max_id);
|
return $stream->getNotices($offset, $limit, $since_id, $max_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function noticeCount()
|
public function noticeCount()
|
||||||
{
|
{
|
||||||
$cacheKey = sprintf('file:notice-count:%d', $this->id);
|
$cacheKey = sprintf('file:notice-count:%d', $this->id);
|
||||||
|
|
||||||
$count = self::cacheGet($cacheKey);
|
$count = self::cacheGet($cacheKey);
|
||||||
|
|
||||||
if ($count === false) {
|
if ($count === false) {
|
||||||
|
|
||||||
$f2p = new File_to_post();
|
$f2p = new File_to_post();
|
||||||
|
|
||||||
$f2p->file_id = $this->id;
|
$f2p->file_id = $this->id;
|
||||||
@ -647,7 +701,7 @@ class File extends Managed_DataObject
|
|||||||
$count = $f2p->count();
|
$count = $f2p->count();
|
||||||
|
|
||||||
self::cacheSet($cacheKey, $count);
|
self::cacheSet($cacheKey, $count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
@ -704,7 +758,7 @@ class File extends Managed_DataObject
|
|||||||
return $this->update($orig);
|
return $this->update($orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function hashurl($url)
|
public static function hashurl($url)
|
||||||
{
|
{
|
||||||
if (empty($url)) {
|
if (empty($url)) {
|
||||||
throw new Exception('No URL provided to hash algorithm.');
|
throw new Exception('No URL provided to hash algorithm.');
|
||||||
@ -712,7 +766,7 @@ class File extends Managed_DataObject
|
|||||||
return hash(self::URLHASH_ALG, $url);
|
return hash(self::URLHASH_ALG, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function beforeSchemaUpdate()
|
public static function beforeSchemaUpdate()
|
||||||
{
|
{
|
||||||
$table = strtolower(get_called_class());
|
$table = strtolower(get_called_class());
|
||||||
$schema = Schema::get();
|
$schema = Schema::get();
|
||||||
@ -745,7 +799,7 @@ class File extends Managed_DataObject
|
|||||||
$dupfile->update($orig);
|
$dupfile->update($orig);
|
||||||
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
|
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
|
||||||
// only start deleting with this fetch.
|
// only start deleting with this fetch.
|
||||||
while($dupfile->fetch()) {
|
while ($dupfile->fetch()) {
|
||||||
common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
|
common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
|
||||||
print ".";
|
print ".";
|
||||||
$dupfile->delete();
|
$dupfile->delete();
|
||||||
@ -761,13 +815,13 @@ class File extends Managed_DataObject
|
|||||||
echo "\n...now running hacky pre-schemaupdate change for $table:";
|
echo "\n...now running hacky pre-schemaupdate change for $table:";
|
||||||
// We have to create a urlhash that is _not_ the primary key,
|
// We have to create a urlhash that is _not_ the primary key,
|
||||||
// transfer data and THEN run checkSchema
|
// transfer data and THEN run checkSchema
|
||||||
$schemadef['fields']['urlhash'] = array (
|
$schemadef['fields']['urlhash'] = array(
|
||||||
'type' => 'varchar',
|
'type' => 'varchar',
|
||||||
'length' => 64,
|
'length' => 64,
|
||||||
'not null' => false, // this is because when adding column, all entries will _be_ NULL!
|
'not null' => false, // this is because when adding column, all entries will _be_ NULL!
|
||||||
'description' => 'sha256 of destination URL (url field)',
|
'description' => 'sha256 of destination URL (url field)',
|
||||||
);
|
);
|
||||||
$schemadef['fields']['url'] = array (
|
$schemadef['fields']['url'] = array(
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'description' => 'destination URL after following possible redirections',
|
'description' => 'destination URL after following possible redirections',
|
||||||
);
|
);
|
||||||
@ -780,11 +834,13 @@ class File extends Managed_DataObject
|
|||||||
// urlhash is hash('sha256', $url) in the File table
|
// urlhash is hash('sha256', $url) in the File table
|
||||||
echo "Updating urlhash fields in $table table...";
|
echo "Updating urlhash fields in $table table...";
|
||||||
// Maybe very MySQL specific :(
|
// Maybe very MySQL specific :(
|
||||||
$tablefix->query(sprintf('UPDATE %1$s SET %2$s=%3$s;',
|
$tablefix->query(sprintf(
|
||||||
$schema->quoteIdentifier($table),
|
'UPDATE %1$s SET %2$s=%3$s;',
|
||||||
'urlhash',
|
$schema->quoteIdentifier($table),
|
||||||
|
'urlhash',
|
||||||
// The line below is "result of sha256 on column `url`"
|
// The line below is "result of sha256 on column `url`"
|
||||||
'SHA2(url, 256)'));
|
'SHA2(url, 256)'
|
||||||
|
));
|
||||||
echo "DONE.\n";
|
echo "DONE.\n";
|
||||||
echo "Resuming core schema upgrade...";
|
echo "Resuming core schema upgrade...";
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* StatusNet, the distributed open-source microblogging tool
|
* GNU social - a federating social network
|
||||||
*
|
*
|
||||||
* Default settings for core configuration
|
|
||||||
*
|
|
||||||
* PHP version 5
|
|
||||||
*
|
*
|
||||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -22,9 +19,9 @@
|
|||||||
* @category Config
|
* @category Config
|
||||||
* @package GNUsocial
|
* @package GNUsocial
|
||||||
* @author Evan Prodromou <evan@status.net>
|
* @author Evan Prodromou <evan@status.net>
|
||||||
* @copyright 2008-9 StatusNet, Inc.
|
* @copyright 2008-2009, 2019 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://www.gnu.org/software/social/
|
* @link https://www.gnu.org/software/social/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$default =
|
$default =
|
||||||
@ -253,11 +250,11 @@ $default =
|
|||||||
'application/x-go-sgf' => 'sgf',
|
'application/x-go-sgf' => 'sgf',
|
||||||
'application/xml' => 'xml',
|
'application/xml' => 'xml',
|
||||||
'application/gpx+xml' => 'gpx',
|
'application/gpx+xml' => 'gpx',
|
||||||
'image/png' => 'png',
|
image_type_to_mime_type(IMAGETYPE_PNG) => image_type_to_extension(IMAGETYPE_PNG),
|
||||||
'image/jpeg' => 'jpg',
|
image_type_to_mime_type(IMAGETYPE_JPEG) => image_type_to_extension(IMAGETYPE_JPEG),
|
||||||
'image/gif' => 'gif',
|
image_type_to_mime_type(IMAGETYPE_GIF) => image_type_to_extension(IMAGETYPE_GIF),
|
||||||
'image/svg+xml' => 'svg',
|
'image/svg+xml' => 'svg', // No built-in constant
|
||||||
'image/vnd.microsoft.icon' => 'ico',
|
image_type_to_mime_type(IMAGETYPE_ICO) => image_type_to_extension(IMAGETYPE_ICO),
|
||||||
'audio/ogg' => 'ogg',
|
'audio/ogg' => 'ogg',
|
||||||
'audio/mpeg' => 'mpg',
|
'audio/mpeg' => 'mpg',
|
||||||
'audio/x-speex' => 'spx',
|
'audio/x-speex' => 'spx',
|
||||||
@ -280,6 +277,7 @@ $default =
|
|||||||
'php' => 'phps', // this turns .php into .phps
|
'php' => 'phps', // this turns .php into .phps
|
||||||
'exe' => false, // this would deny any uploads to keep the "exe" file extension
|
'exe' => false, // this would deny any uploads to keep the "exe" file extension
|
||||||
],
|
],
|
||||||
|
'memory_limit' => '1024M' // PHP's memory limit to use temporarily when handling images
|
||||||
),
|
),
|
||||||
'thumbnail' => [
|
'thumbnail' => [
|
||||||
'dir' => null, // falls back to File::path('thumb') (equivalent to ['attachments']['dir'] . '/thumb/')
|
'dir' => null, // falls back to File::path('thumb') (equivalent to ['attachments']['dir'] . '/thumb/')
|
||||||
|
@ -22,7 +22,7 @@ if (!defined('GNUSOCIAL')) { exit(1); }
|
|||||||
define('GNUSOCIAL_ENGINE', 'GNU social');
|
define('GNUSOCIAL_ENGINE', 'GNU social');
|
||||||
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
|
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
|
||||||
|
|
||||||
define('GNUSOCIAL_BASE_VERSION', '1.19.4');
|
define('GNUSOCIAL_BASE_VERSION', '1.20.0');
|
||||||
define('GNUSOCIAL_LIFECYCLE', 'rc0'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
define('GNUSOCIAL_LIFECYCLE', 'rc0'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
||||||
|
|
||||||
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
|
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* StatusNet, the distributed open-source microblogging tool
|
* GNU social - a federating social network
|
||||||
*
|
*
|
||||||
* Abstraction for an image file
|
* Abstraction for an image file
|
||||||
*
|
*
|
||||||
* PHP version 5
|
|
||||||
*
|
|
||||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
@ -20,55 +18,41 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* @category Image
|
* @category Image
|
||||||
* @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 Mikael Nordfeldth <mmn@hethane.se>
|
||||||
|
* @author Miguel Dantas <biodantasgs@gmail.com>
|
||||||
|
* @copyright 2008, 2019 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/
|
* @link https://www.gnu.org/software/social/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
defined('GNUSOCIAL') || die();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper on uploaded files
|
* A wrapper on uploaded images
|
||||||
*
|
*
|
||||||
* Makes it slightly easier to accept an image file from upload.
|
* Makes it slightly easier to accept an image file from upload.
|
||||||
*
|
*
|
||||||
* @category Image
|
* @category Image
|
||||||
* @package StatusNet
|
* @package GNUsocial
|
||||||
|
* @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>
|
||||||
* @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/
|
||||||
* @link http://status.net/
|
|
||||||
*/
|
*/
|
||||||
|
class ImageFile extends MediaFile
|
||||||
class ImageFile
|
|
||||||
{
|
{
|
||||||
var $id;
|
public $type;
|
||||||
var $filepath;
|
public $height;
|
||||||
var $filename;
|
public $width;
|
||||||
var $type;
|
public $rotate = 0; // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
|
||||||
var $height;
|
public $animated = null; // Animated image? (has more than 1 frame). null means untested
|
||||||
var $width;
|
public $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object
|
||||||
var $rotate=0; // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
|
|
||||||
var $animated = null; // Animated image? (has more than 1 frame). null means untested
|
|
||||||
var $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object
|
|
||||||
|
|
||||||
protected $fileRecord = null;
|
public function __construct($id, string $filepath)
|
||||||
|
|
||||||
function __construct($id, $filepath)
|
|
||||||
{
|
{
|
||||||
$this->id = $id;
|
|
||||||
if (!empty($this->id)) {
|
|
||||||
$this->fileRecord = new File();
|
|
||||||
$this->fileRecord->id = $this->id;
|
|
||||||
if (!$this->fileRecord->find(true)) {
|
|
||||||
// If we have set an ID, we need that ID to exist!
|
|
||||||
throw new NoResultException($this->fileRecord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These do not have to be the same as fileRecord->filename for example,
|
// These do not have to be the same as fileRecord->filename for example,
|
||||||
// since we may have generated an image source file from something else!
|
// since we may have generated an image source file from something else!
|
||||||
$this->filepath = $filepath;
|
$this->filepath = $filepath;
|
||||||
@ -76,16 +60,14 @@ class ImageFile
|
|||||||
|
|
||||||
$info = @getimagesize($this->filepath);
|
$info = @getimagesize($this->filepath);
|
||||||
|
|
||||||
if (!(
|
if (!(($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
|
||||||
($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
|
($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
|
||||||
($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
|
($info[2] == IMAGETYPE_BMP && function_exists('imagecreatefrombmp')) ||
|
||||||
$info[2] == IMAGETYPE_BMP ||
|
($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
|
||||||
($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
|
($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
|
||||||
($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
|
($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
|
||||||
($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
|
|
||||||
|
|
||||||
// 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(_('Unsupported image format.'), $this->filepath);
|
throw new UnsupportedMediaException(_m('Unsupported image format.'), $this->filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->width = $info[0];
|
$this->width = $info[0];
|
||||||
@ -93,11 +75,18 @@ class ImageFile
|
|||||||
$this->type = $info[2];
|
$this->type = $info[2];
|
||||||
$this->mimetype = $info['mime'];
|
$this->mimetype = $info['mime'];
|
||||||
|
|
||||||
|
parent::__construct(
|
||||||
|
$filepath,
|
||||||
|
$this->mimetype,
|
||||||
|
null /* filehash, MediaFile will calculate it */,
|
||||||
|
$id
|
||||||
|
);
|
||||||
|
|
||||||
if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
|
if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
|
||||||
// Orientation value to rotate thumbnails properly
|
// Orientation value to rotate thumbnails properly
|
||||||
$exif = @exif_read_data($this->filepath);
|
$exif = @exif_read_data($this->filepath);
|
||||||
if (is_array($exif) && isset($exif['Orientation'])) {
|
if (is_array($exif) && isset($exif['Orientation'])) {
|
||||||
switch ((int)$exif['Orientation']) {
|
switch (intval($exif['Orientation'])) {
|
||||||
case 1: // top is top
|
case 1: // top is top
|
||||||
$this->rotate = 0;
|
$this->rotate = 0;
|
||||||
break;
|
break;
|
||||||
@ -126,7 +115,7 @@ class ImageFile
|
|||||||
$media = common_get_mime_media($file->mimetype);
|
$media = common_get_mime_media($file->mimetype);
|
||||||
if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
|
if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
|
||||||
if (empty($file->filename) && !file_exists($imgPath)) {
|
if (empty($file->filename) && !file_exists($imgPath)) {
|
||||||
throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.'));
|
throw new UnsupportedMediaException(_m('File without filename could not get a thumbnail source.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// First some mimetype specific exceptions
|
// First some mimetype specific exceptions
|
||||||
@ -141,7 +130,7 @@ class ImageFile
|
|||||||
$imgPath = $file->getPath();
|
$imgPath = $file->getPath();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedMediaException(_('Unsupported media format.'), $file->getPath());
|
throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +144,8 @@ class ImageFile
|
|||||||
// Avoid deleting the original
|
// Avoid deleting the original
|
||||||
try {
|
try {
|
||||||
if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) {
|
if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) {
|
||||||
common_debug(__METHOD__.': Deleting temporary file that was created as image file thumbnail source: '._ve($imgPath));
|
common_debug(__METHOD__.': Deleting temporary file that was created as image file' .
|
||||||
|
'thumbnail source: '._ve($imgPath));
|
||||||
@unlink($imgPath);
|
@unlink($imgPath);
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException $e) {
|
} catch (FileNotFoundException $e) {
|
||||||
@ -163,7 +153,14 @@ class ImageFile
|
|||||||
// doesn't exist anyway, so it's safe to delete $imgPath
|
// doesn't exist anyway, so it's safe to delete $imgPath
|
||||||
@unlink($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())));
|
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;
|
throw $e;
|
||||||
}
|
}
|
||||||
return $image;
|
return $image;
|
||||||
@ -178,42 +175,43 @@ class ImageFile
|
|||||||
return $this->filepath;
|
return $this->filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function fromUpload($param='upload')
|
/**
|
||||||
|
* Process a file upload
|
||||||
|
*
|
||||||
|
* Uses MediaFile's `fromUpload` to do the majority of the work and reencodes the image,
|
||||||
|
* to mitigate injection attacks.
|
||||||
|
* @param string $param
|
||||||
|
* @param Profile|null $scoped
|
||||||
|
* @return ImageFile|MediaFile
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws NoUploadedMediaException
|
||||||
|
* @throws ServerException
|
||||||
|
* @throws UnsupportedMediaException
|
||||||
|
* @throws UseFileAsThumbnailException
|
||||||
|
*/
|
||||||
|
public static function fromUpload(string $param='upload', Profile $scoped = null)
|
||||||
{
|
{
|
||||||
switch ($_FILES[$param]['error']) {
|
return parent::fromUpload($param, $scoped);
|
||||||
case UPLOAD_ERR_OK: // success, jump out
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
/**
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
* Several obscure file types should be normalized to PNG on resize.
|
||||||
// TRANS: Exception thrown when too large a file is uploaded.
|
*
|
||||||
// TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
|
* Keeps only PNG, JPEG and GIF
|
||||||
throw new Exception(sprintf(_('That file is too big. The maximum file size is %s.'), ImageFile::maxFileSize()));
|
*
|
||||||
|
* @return int
|
||||||
case UPLOAD_ERR_PARTIAL:
|
*/
|
||||||
@unlink($_FILES[$param]['tmp_name']);
|
public function preferredType()
|
||||||
// TRANS: Exception thrown when uploading an image and that action could not be completed.
|
{
|
||||||
throw new Exception(_('Partial upload.'));
|
// Keep only JPEG and GIF in their orignal format
|
||||||
|
if ($this->type === IMAGETYPE_JPEG || $this->type === IMAGETYPE_GIF) {
|
||||||
case UPLOAD_ERR_NO_FILE:
|
return $this->type;
|
||||||
// No file; probably just a non-AJAX submission.
|
|
||||||
throw new ClientException(_('No file uploaded.'));
|
|
||||||
|
|
||||||
default:
|
|
||||||
common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']);
|
|
||||||
// TRANS: Exception thrown when uploading an image fails for an unknown reason.
|
|
||||||
throw new Exception(_('System error uploading file.'));
|
|
||||||
}
|
}
|
||||||
|
// We don't want to save some formats as they are rare, inefficient and antiquated
|
||||||
$info = @getimagesize($_FILES[$param]['tmp_name']);
|
// thus we can't guarantee clients will support
|
||||||
|
// So just save it as PNG
|
||||||
if (!$info) {
|
return IMAGETYPE_PNG;
|
||||||
@unlink($_FILES[$param]['tmp_name']);
|
|
||||||
// TRANS: Exception thrown when uploading a file as image that is not an image or is a corrupt file.
|
|
||||||
throw new UnsupportedMediaException(_('Not an image or corrupt file.'), '[deleted]');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ImageFile(null, $_FILES[$param]['tmp_name']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,8 +222,13 @@ class ImageFile
|
|||||||
*
|
*
|
||||||
* @param string $outpath
|
* @param string $outpath
|
||||||
* @return ImageFile the image stored at target path
|
* @return ImageFile the image stored at target path
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException
|
||||||
|
* @throws UnsupportedMediaException
|
||||||
|
* @throws UseFileAsThumbnailException
|
||||||
*/
|
*/
|
||||||
function copyTo($outpath)
|
public function copyTo($outpath)
|
||||||
{
|
{
|
||||||
return new ImageFile(null, $this->resizeTo($outpath));
|
return new ImageFile(null, $this->resizeTo($outpath));
|
||||||
}
|
}
|
||||||
@ -234,36 +237,34 @@ class ImageFile
|
|||||||
* Create and save a thumbnail image.
|
* Create and save a thumbnail image.
|
||||||
*
|
*
|
||||||
* @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
|
||||||
|
* @throws UnsupportedMediaException
|
||||||
|
* @throws UseFileAsThumbnailException
|
||||||
*/
|
*/
|
||||||
function resizeTo($outpath, array $box=array())
|
public function resizeTo($outpath, array $box=array())
|
||||||
{
|
{
|
||||||
$box['width'] = isset($box['width']) ? intval($box['width']) : $this->width;
|
$box['width'] = isset($box['width']) ? intval($box['width']) : $this->width;
|
||||||
$box['height'] = isset($box['height']) ? intval($box['height']) : $this->height;
|
$box['height'] = isset($box['height']) ? intval($box['height']) : $this->height;
|
||||||
$box['x'] = isset($box['x']) ? intval($box['x']) : 0;
|
$box['x'] = isset($box['x']) ? intval($box['x']) : 0;
|
||||||
$box['y'] = isset($box['y']) ? intval($box['y']) : 0;
|
$box['y'] = isset($box['y']) ? intval($box['y']) : 0;
|
||||||
$box['w'] = isset($box['w']) ? intval($box['w']) : $this->width;
|
$box['w'] = isset($box['w']) ? intval($box['w']) : $this->width;
|
||||||
$box['h'] = isset($box['h']) ? intval($box['h']) : $this->height;
|
$box['h'] = isset($box['h']) ? intval($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, but is no longer there.
|
// TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
|
||||||
throw new Exception(_('Lost our file.'));
|
throw new Exception(_m('Lost our file.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't rotate/crop/scale if it isn't necessary
|
// Don't rotate/crop/scale if it isn't necessary
|
||||||
if ($box['width'] === $this->width
|
if ($box['width'] === $this->width
|
||||||
&& $box['height'] === $this->height
|
&& $box['height'] === $this->height
|
||||||
&& $box['x'] === 0
|
&& $box['x'] === 0
|
||||||
&& $box['y'] === 0
|
&& $box['y'] === 0
|
||||||
&& $box['w'] === $this->width
|
&& $box['w'] === $this->width
|
||||||
&& $box['h'] === $this->height
|
&& $box['h'] === $this->height
|
||||||
&& $this->type == $this->preferredType()) {
|
&& $this->type === $this->preferredType()) {
|
||||||
if ($this->rotate == 0) {
|
if (abs($this->rotate) == 90) {
|
||||||
// No rotational difference, just copy it as-is
|
|
||||||
@copy($this->filepath, $outpath);
|
|
||||||
return $outpath;
|
|
||||||
} elseif (abs($this->rotate) == 90) {
|
|
||||||
// Box is rotated 90 degrees in either direction,
|
// Box is rotated 90 degrees in either direction,
|
||||||
// so we have to redefine x to y and vice versa.
|
// so we have to redefine x to y and vice versa.
|
||||||
$tmp = $box['width'];
|
$tmp = $box['width'];
|
||||||
@ -278,7 +279,6 @@ class ImageFile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
|
if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
|
||||||
$this->resizeToFile($outpath, $box);
|
$this->resizeToFile($outpath, $box);
|
||||||
}
|
}
|
||||||
@ -294,8 +294,22 @@ class ImageFile
|
|||||||
return $outpath;
|
return $outpath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes a file. If $box is omitted, the size is not changed, but this is still useful,
|
||||||
|
* because it will reencode the image in the `self::prefferedType()` format. This only
|
||||||
|
* applies henceforward, not retroactively
|
||||||
|
*
|
||||||
|
* Increases the 'memory_limit' to the one in the 'attachments' section in the config, to
|
||||||
|
* enable the handling of bigger images, which can cause a peak of memory consumption, while
|
||||||
|
* encoding
|
||||||
|
* @param $outpath
|
||||||
|
* @param array $box
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
protected function resizeToFile($outpath, array $box)
|
protected function resizeToFile($outpath, array $box)
|
||||||
{
|
{
|
||||||
|
$old_limit = ini_set('memory_limit', common_config('attachments', 'memory_limit'));
|
||||||
|
$image_src = null;
|
||||||
switch ($this->type) {
|
switch ($this->type) {
|
||||||
case IMAGETYPE_GIF:
|
case IMAGETYPE_GIF:
|
||||||
$image_src = imagecreatefromgif($this->filepath);
|
$image_src = imagecreatefromgif($this->filepath);
|
||||||
@ -317,7 +331,7 @@ class ImageFile
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// 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(_('Unknown file type'));
|
throw new Exception(_m('Unknown file type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->rotate != 0) {
|
if ($this->rotate != 0) {
|
||||||
@ -326,30 +340,45 @@ class ImageFile
|
|||||||
|
|
||||||
$image_dest = imagecreatetruecolor($box['width'], $box['height']);
|
$image_dest = imagecreatetruecolor($box['width'], $box['height']);
|
||||||
|
|
||||||
if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
|
if ($this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
|
||||||
|
|
||||||
$transparent_idx = imagecolortransparent($image_src);
|
$transparent_idx = imagecolortransparent($image_src);
|
||||||
|
|
||||||
if ($transparent_idx >= 0) {
|
if ($transparent_idx >= 0 && $transparent_idx < 255) {
|
||||||
|
|
||||||
$transparent_color = imagecolorsforindex($image_src, $transparent_idx);
|
$transparent_color = imagecolorsforindex($image_src, $transparent_idx);
|
||||||
$transparent_idx = imagecolorallocate($image_dest, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
|
$transparent_idx = imagecolorallocate(
|
||||||
|
$image_dest,
|
||||||
|
$transparent_color['red'],
|
||||||
|
$transparent_color['green'],
|
||||||
|
$transparent_color['blue']
|
||||||
|
);
|
||||||
imagefill($image_dest, 0, 0, $transparent_idx);
|
imagefill($image_dest, 0, 0, $transparent_idx);
|
||||||
imagecolortransparent($image_dest, $transparent_idx);
|
imagecolortransparent($image_dest, $transparent_idx);
|
||||||
|
|
||||||
} elseif ($this->type == IMAGETYPE_PNG) {
|
} elseif ($this->type == IMAGETYPE_PNG) {
|
||||||
|
|
||||||
imagealphablending($image_dest, false);
|
imagealphablending($image_dest, false);
|
||||||
$transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
|
$transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
|
||||||
imagefill($image_dest, 0, 0, $transparent);
|
imagefill($image_dest, 0, 0, $transparent);
|
||||||
imagesavealpha($image_dest, true);
|
imagesavealpha($image_dest, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imagecopyresampled($image_dest, $image_src, 0, 0, $box['x'], $box['y'], $box['width'], $box['height'], $box['w'], $box['h']);
|
imagecopyresampled(
|
||||||
|
$image_dest,
|
||||||
|
$image_src,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$box['x'],
|
||||||
|
$box['y'],
|
||||||
|
$box['width'],
|
||||||
|
$box['height'],
|
||||||
|
$box['w'],
|
||||||
|
$box['h']
|
||||||
|
);
|
||||||
|
|
||||||
switch ($this->preferredType()) {
|
$type = $this->preferredType();
|
||||||
|
$ext = image_type_to_extension($type, true);
|
||||||
|
$outpath = preg_replace("/\.[^\.]+$/", $ext, $outpath);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
case IMAGETYPE_GIF:
|
case IMAGETYPE_GIF:
|
||||||
imagegif($image_dest, $outpath);
|
imagegif($image_dest, $outpath);
|
||||||
break;
|
break;
|
||||||
@ -361,92 +390,32 @@ class ImageFile
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// TRANS: Exception thrown when trying resize an unknown file type.
|
// TRANS: Exception thrown when trying resize an unknown file type.
|
||||||
throw new Exception(_('Unknown file type'));
|
throw new Exception(_m('Unknown file type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
imagedestroy($image_src);
|
imagedestroy($image_src);
|
||||||
imagedestroy($image_dest);
|
imagedestroy($image_dest);
|
||||||
|
ini_set('memory_limit', $old_limit); // Restore the old memory limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function unlink()
|
||||||
/**
|
|
||||||
* Several obscure file types should be normalized to PNG on resize.
|
|
||||||
*
|
|
||||||
* @fixme consider flattening anything not GIF or JPEG to PNG
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
function preferredType()
|
|
||||||
{
|
|
||||||
if($this->type == IMAGETYPE_BMP) {
|
|
||||||
//we don't want to save BMP... it's an inefficient, rare, antiquated format
|
|
||||||
//save png instead
|
|
||||||
return IMAGETYPE_PNG;
|
|
||||||
} else if($this->type == IMAGETYPE_WBMP) {
|
|
||||||
//we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
|
|
||||||
//save png instead
|
|
||||||
return IMAGETYPE_PNG;
|
|
||||||
} else if($this->type == IMAGETYPE_XBM) {
|
|
||||||
//we don't want to save XBM... it's a rare format that we can't guarantee clients will support
|
|
||||||
//save png instead
|
|
||||||
return IMAGETYPE_PNG;
|
|
||||||
}
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
function unlink()
|
|
||||||
{
|
{
|
||||||
@unlink($this->filepath);
|
@unlink($this->filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function maxFileSize()
|
|
||||||
{
|
|
||||||
$value = ImageFile::maxFileSizeInt();
|
|
||||||
|
|
||||||
if ($value > 1024 * 1024) {
|
|
||||||
$value = $value/(1024*1024);
|
|
||||||
// TRANS: Number of megabytes. %d is the number.
|
|
||||||
return sprintf(_m('%dMB','%dMB',$value),$value);
|
|
||||||
} else if ($value > 1024) {
|
|
||||||
$value = $value/1024;
|
|
||||||
// TRANS: Number of kilobytes. %d is the number.
|
|
||||||
return sprintf(_m('%dkB','%dkB',$value),$value);
|
|
||||||
} else {
|
|
||||||
// TRANS: Number of bytes. %d is the number.
|
|
||||||
return sprintf(_m('%dB','%dB',$value),$value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static function maxFileSizeInt()
|
|
||||||
{
|
|
||||||
return min(ImageFile::strToInt(ini_get('post_max_size')),
|
|
||||||
ImageFile::strToInt(ini_get('upload_max_filesize')),
|
|
||||||
ImageFile::strToInt(ini_get('memory_limit')));
|
|
||||||
}
|
|
||||||
|
|
||||||
static function strToInt($str)
|
|
||||||
{
|
|
||||||
$unit = substr($str, -1);
|
|
||||||
$num = substr($str, 0, -1);
|
|
||||||
|
|
||||||
switch(strtoupper($unit)){
|
|
||||||
case 'G':
|
|
||||||
$num *= 1024;
|
|
||||||
case 'M':
|
|
||||||
$num *= 1024;
|
|
||||||
case 'K':
|
|
||||||
$num *= 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $num;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
|
public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
|
||||||
{
|
{
|
||||||
return self::getScalingValues($this->width, $this->height,
|
return self::getScalingValues(
|
||||||
$maxWidth, $maxHeight, $crop, $this->rotate);
|
$this->width,
|
||||||
|
$this->height,
|
||||||
|
$maxWidth,
|
||||||
|
$maxHeight,
|
||||||
|
$crop,
|
||||||
|
$this->rotate
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Gets scaling values for images of various types. Cropping can be enabled.
|
* Gets scaling values for images of various types. Cropping can be enabled.
|
||||||
*
|
*
|
||||||
* Values will scale _up_ to fit max values if cropping is enabled!
|
* Values will scale _up_ to fit max values if cropping is enabled!
|
||||||
@ -457,14 +426,21 @@ class ImageFile
|
|||||||
* @param $maxW int Resulting max width
|
* @param $maxW int Resulting max width
|
||||||
* @param $maxH int Resulting max height
|
* @param $maxH int Resulting max height
|
||||||
* @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
|
||||||
|
* @return array
|
||||||
|
* @throws ServerException
|
||||||
*/
|
*/
|
||||||
public static function getScalingValues($width, $height,
|
public static function getScalingValues(
|
||||||
$maxW=null, $maxH=null,
|
$width,
|
||||||
$crop=null, $rotate=0)
|
$height,
|
||||||
{
|
$maxW=null,
|
||||||
|
$maxH=null,
|
||||||
|
$crop=null,
|
||||||
|
$rotate=0
|
||||||
|
) {
|
||||||
$maxW = $maxW ?: common_config('thumbnail', 'width');
|
$maxW = $maxW ?: common_config('thumbnail', 'width');
|
||||||
$maxH = $maxH ?: common_config('thumbnail', 'height');
|
$maxH = $maxH ?: common_config('thumbnail', 'height');
|
||||||
|
|
||||||
if ($maxW < 1 || ($maxH !== null && $maxH < 1)) {
|
if ($maxW < 1 || ($maxH !== null && $maxH < 1)) {
|
||||||
throw new ServerException('Bad parameters for ImageFile::getScalingValues');
|
throw new ServerException('Bad parameters for ImageFile::getScalingValues');
|
||||||
} elseif ($maxH === null) {
|
} elseif ($maxH === null) {
|
||||||
@ -479,14 +455,14 @@ class ImageFile
|
|||||||
$width = $height;
|
$width = $height;
|
||||||
$height = $tmp;
|
$height = $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cropping data (for original image size). Default values, 0 and null,
|
// Cropping data (for original image size). Default values, 0 and null,
|
||||||
// imply no cropping and with preserved aspect ratio (per axis).
|
// imply no cropping and with preserved aspect ratio (per axis).
|
||||||
$cx = 0; // crop x
|
$cx = 0; // crop x
|
||||||
$cy = 0; // crop y
|
$cy = 0; // crop y
|
||||||
$cw = null; // crop area width
|
$cw = null; // crop area width
|
||||||
$ch = null; // crop area height
|
$ch = null; // crop area height
|
||||||
|
|
||||||
if ($crop) {
|
if ($crop) {
|
||||||
$s_ar = $width / $height;
|
$s_ar = $width / $height;
|
||||||
$t_ar = $maxW / $maxH;
|
$t_ar = $maxW / $maxH;
|
||||||
@ -513,9 +489,9 @@ class ImageFile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return array(intval($rw), intval($rh),
|
return array(intval($rw), intval($rh),
|
||||||
intval($cx), intval($cy),
|
intval($cx), intval($cy),
|
||||||
is_null($cw) ? $width : intval($cw),
|
is_null($cw) ? $width : intval($cw),
|
||||||
is_null($ch) ? $height : intval($ch));
|
is_null($ch) ? $height : intval($ch));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,7 +515,7 @@ class ImageFile
|
|||||||
|
|
||||||
// We read through the file til we reach the end of the file, or we've found
|
// We read through the file til we reach the end of the file, or we've found
|
||||||
// at least 2 frame headers
|
// at least 2 frame headers
|
||||||
while(!feof($fh) && $count < 2) {
|
while (!feof($fh) && $count < 2) {
|
||||||
$chunk = fread($fh, 1024 * 100); //read 100kb at a time
|
$chunk = fread($fh, 1024 * 100); //read 100kb at a time
|
||||||
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches);
|
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches);
|
||||||
// rewind in case we ended up in the middle of the header, but avoid
|
// rewind in case we ended up in the middle of the header, but avoid
|
||||||
@ -560,9 +536,9 @@ class ImageFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($width === null) {
|
if ($width === null) {
|
||||||
$width = common_config('thumbnail', 'width');
|
$width = common_config('thumbnail', 'width');
|
||||||
$height = common_config('thumbnail', 'height');
|
$height = common_config('thumbnail', 'height');
|
||||||
$crop = common_config('thumbnail', 'crop');
|
$crop = common_config('thumbnail', 'crop');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$upscale) {
|
if (!$upscale) {
|
||||||
@ -589,7 +565,7 @@ class ImageFile
|
|||||||
'file_id'=> $this->fileRecord->getID(),
|
'file_id'=> $this->fileRecord->getID(),
|
||||||
'width' => $width,
|
'width' => $width,
|
||||||
'height' => $height,
|
'height' => $height,
|
||||||
));
|
));
|
||||||
if ($thumb instanceof File_thumbnail) {
|
if ($thumb instanceof File_thumbnail) {
|
||||||
return $thumb;
|
return $thumb;
|
||||||
}
|
}
|
||||||
@ -610,11 +586,16 @@ class ImageFile
|
|||||||
|| $box['w'] < 1 || $box['x'] >= $this->width
|
|| $box['w'] < 1 || $box['x'] >= $this->width
|
||||||
|| $box['h'] < 1 || $box['y'] >= $this->height) {
|
|| $box['h'] < 1 || $box['y'] >= $this->height) {
|
||||||
// Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
|
// Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
|
||||||
common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box,true));
|
common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box, true));
|
||||||
throw new ServerException('Bad thumbnail size parameters.');
|
throw new ServerException('Bad thumbnail size parameters.');
|
||||||
}
|
}
|
||||||
|
|
||||||
common_debug(sprintf('Generating a thumbnail of File id==%u of size %ux%u', $this->fileRecord->getID(), $width, $height));
|
common_debug(sprintf(
|
||||||
|
'Generating a thumbnail of File id==%u of size %ux%u',
|
||||||
|
$this->fileRecord->getID(),
|
||||||
|
$width,
|
||||||
|
$height
|
||||||
|
));
|
||||||
|
|
||||||
// Perform resize and store into file
|
// Perform resize and store into file
|
||||||
$this->resizeTo($outpath, $box);
|
$this->resizeTo($outpath, $box);
|
||||||
@ -628,106 +609,14 @@ class ImageFile
|
|||||||
// $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it!
|
// $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it!
|
||||||
}
|
}
|
||||||
|
|
||||||
return File_thumbnail::saveThumbnail($this->fileRecord->getID(),
|
return File_thumbnail::saveThumbnail(
|
||||||
null, // no url since we generated it ourselves and can dynamically generate the url
|
$this->fileRecord->getID(),
|
||||||
$width, $height,
|
// no url since we generated it ourselves and can dynamically
|
||||||
$outname);
|
// generate the url
|
||||||
|
null,
|
||||||
|
$width,
|
||||||
|
$height,
|
||||||
|
$outname
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one
|
|
||||||
if(!function_exists('imagecreatefrombmp')){
|
|
||||||
//taken shamelessly from http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
|
|
||||||
function imagecreatefrombmp($p_sFile)
|
|
||||||
{
|
|
||||||
// Load the image into a string
|
|
||||||
$file = fopen($p_sFile,"rb");
|
|
||||||
$read = fread($file,10);
|
|
||||||
while(!feof($file)&&($read<>""))
|
|
||||||
$read .= fread($file,1024);
|
|
||||||
|
|
||||||
$temp = unpack("H*",$read);
|
|
||||||
$hex = $temp[1];
|
|
||||||
$header = substr($hex,0,108);
|
|
||||||
|
|
||||||
// Process the header
|
|
||||||
// Structure: http://www.fastgraph.com/help/bmp_header_format.html
|
|
||||||
if (substr($header,0,4)=="424d")
|
|
||||||
{
|
|
||||||
// Cut it in parts of 2 bytes
|
|
||||||
$header_parts = str_split($header,2);
|
|
||||||
|
|
||||||
// Get the width 4 bytes
|
|
||||||
$width = hexdec($header_parts[19].$header_parts[18]);
|
|
||||||
|
|
||||||
// Get the height 4 bytes
|
|
||||||
$height = hexdec($header_parts[23].$header_parts[22]);
|
|
||||||
|
|
||||||
// Unset the header params
|
|
||||||
unset($header_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define starting X and Y
|
|
||||||
$x = 0;
|
|
||||||
$y = 1;
|
|
||||||
|
|
||||||
// Create newimage
|
|
||||||
$image = imagecreatetruecolor($width,$height);
|
|
||||||
|
|
||||||
// Grab the body from the image
|
|
||||||
$body = substr($hex,108);
|
|
||||||
|
|
||||||
// Calculate if padding at the end-line is needed
|
|
||||||
// Divided by two to keep overview.
|
|
||||||
// 1 byte = 2 HEX-chars
|
|
||||||
$body_size = (strlen($body)/2);
|
|
||||||
$header_size = ($width*$height);
|
|
||||||
|
|
||||||
// Use end-line padding? Only when needed
|
|
||||||
$usePadding = ($body_size>($header_size*3)+4);
|
|
||||||
|
|
||||||
// Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
|
|
||||||
// Calculate the next DWORD-position in the body
|
|
||||||
for ($i=0;$i<$body_size;$i+=3)
|
|
||||||
{
|
|
||||||
// Calculate line-ending and padding
|
|
||||||
if ($x>=$width)
|
|
||||||
{
|
|
||||||
// If padding needed, ignore image-padding
|
|
||||||
// Shift i to the ending of the current 32-bit-block
|
|
||||||
if ($usePadding)
|
|
||||||
$i += $width%4;
|
|
||||||
|
|
||||||
// Reset horizontal position
|
|
||||||
$x = 0;
|
|
||||||
|
|
||||||
// Raise the height-position (bottom-up)
|
|
||||||
$y++;
|
|
||||||
|
|
||||||
// Reached the image-height? Break the for-loop
|
|
||||||
if ($y>$height)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculation of the RGB-pixel (defined as BGR in image-data)
|
|
||||||
// Define $i_pos as absolute position in the body
|
|
||||||
$i_pos = $i*2;
|
|
||||||
$r = hexdec($body[$i_pos+4].$body[$i_pos+5]);
|
|
||||||
$g = hexdec($body[$i_pos+2].$body[$i_pos+3]);
|
|
||||||
$b = hexdec($body[$i_pos].$body[$i_pos+1]);
|
|
||||||
|
|
||||||
// Calculate and draw the pixel
|
|
||||||
$color = imagecolorallocate($image,$r,$g,$b);
|
|
||||||
imagesetpixel($image,$x,$height-$y,$color);
|
|
||||||
|
|
||||||
// Raise the horizontal position
|
|
||||||
$x++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unset the body / free the memory
|
|
||||||
unset($body);
|
|
||||||
|
|
||||||
// Return image-object
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
} // if(!function_exists('imagecreatefrombmp'))
|
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* StatusNet, the distributed open-source microblogging tool
|
* GNU social - a federating social network
|
||||||
*
|
*
|
||||||
* Abstraction for media files in general
|
* Abstraction for media files
|
||||||
*
|
|
||||||
* TODO: combine with ImageFile?
|
|
||||||
*
|
|
||||||
* PHP version 5
|
|
||||||
*
|
*
|
||||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -22,37 +18,78 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* @category Media
|
* @category Media
|
||||||
* @package StatusNet
|
* @package GNUsocial
|
||||||
* @author Robin Millette <robin@millette.info>
|
* @author Robin Millette <robin@millette.info>
|
||||||
|
* @author Miguel Dantas <biodantas@gmail.com>
|
||||||
* @author Zach Copley <zach@status.net>
|
* @author Zach Copley <zach@status.net>
|
||||||
* @copyright 2008-2009 StatusNet, Inc.
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
|
* @copyright 2008-2009, 2019 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/
|
* @link https://www.gnu.org/software/social/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
if (!defined('GNUSOCIAL')) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class responsible for abstracting media files
|
||||||
|
*/
|
||||||
class MediaFile
|
class MediaFile
|
||||||
{
|
{
|
||||||
var $filename = null;
|
public $id = null;
|
||||||
var $fileRecord = null;
|
public $filepath = null;
|
||||||
var $fileurl = null;
|
public $filename = null;
|
||||||
var $short_fileurl = null;
|
public $fileRecord = null;
|
||||||
var $mimetype = null;
|
public $fileurl = null;
|
||||||
|
public $short_fileurl = null;
|
||||||
|
public $mimetype = null;
|
||||||
|
|
||||||
function __construct($filename = null, $mimetype = null, $filehash = null)
|
/**
|
||||||
|
* @param string $filepath The path of the file this media refers to. Required
|
||||||
|
* @param string $mimetype The mimetype of the file. Required
|
||||||
|
* @param $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.
|
||||||
|
* If null, it searches for it. If -1, it skips all DB
|
||||||
|
* interactions (useful for temporary objects)
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws ServerException
|
||||||
|
*/
|
||||||
|
public function __construct(string $filepath, string $mimetype, $filehash = null, $id = null)
|
||||||
{
|
{
|
||||||
$this->filename = $filename;
|
$this->filepath = $filepath;
|
||||||
$this->mimetype = $mimetype;
|
$this->filename = basename($this->filepath);
|
||||||
$this->filehash = $filehash;
|
$this->mimetype = $mimetype;
|
||||||
$this->fileRecord = $this->storeFile();
|
$this->filehash = self::getHashOfFile($this->filepath, $filehash);
|
||||||
|
$this->id = $id;
|
||||||
|
|
||||||
$this->fileurl = common_local_url('attachment',
|
// If id is -1, it means we're dealing with a temporary object and don't want to store it in the DB,
|
||||||
array('attachment' => $this->fileRecord->id));
|
// or add redirects
|
||||||
|
if ($this->id !== -1) {
|
||||||
|
if (!empty($this->id)) {
|
||||||
|
// If we have an id, load it
|
||||||
|
$this->fileRecord = new File();
|
||||||
|
$this->fileRecord->id = $this->id;
|
||||||
|
if (!$this->fileRecord->find(true)) {
|
||||||
|
// If we have set an ID, we need that ID to exist!
|
||||||
|
throw new NoResultException($this->fileRecord);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, store it
|
||||||
|
$this->fileRecord = $this->storeFile();
|
||||||
|
}
|
||||||
|
|
||||||
$this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
|
$this->fileurl = common_local_url(
|
||||||
$this->short_fileurl = common_shorten_url($this->fileurl);
|
'attachment',
|
||||||
$this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
|
array('attachment' => $this->fileRecord->id)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
|
||||||
|
$this->short_fileurl = common_shorten_url($this->fileurl);
|
||||||
|
$this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attachToNotice(Notice $notice)
|
public function attachToNotice(Notice $notice)
|
||||||
@ -65,20 +102,19 @@ class MediaFile
|
|||||||
return File::path($this->filename);
|
return File::path($this->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortUrl()
|
public function shortUrl()
|
||||||
{
|
{
|
||||||
return $this->short_fileurl;
|
return $this->short_fileurl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEnclosure()
|
public function getEnclosure()
|
||||||
{
|
{
|
||||||
return $this->getFile()->getEnclosure();
|
return $this->getFile()->getEnclosure();
|
||||||
}
|
}
|
||||||
|
|
||||||
function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
$filepath = File::path($this->filename);
|
@unlink($this->filepath);
|
||||||
@unlink($filepath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFile()
|
public function getFile()
|
||||||
@ -90,18 +126,38 @@ class MediaFile
|
|||||||
return $this->fileRecord;
|
return $this->fileRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function storeFile()
|
/**
|
||||||
|
* Calculate the hash of a file.
|
||||||
|
*
|
||||||
|
* This won't work for files >2GiB because PHP uses only 32bit.
|
||||||
|
* @param string $filepath
|
||||||
|
* @param string|null $filehash
|
||||||
|
* @return string
|
||||||
|
* @throws ServerException
|
||||||
|
*/
|
||||||
|
public static function getHashOfFile(string $filepath, $filehash = null)
|
||||||
{
|
{
|
||||||
$filepath = File::path($this->filename);
|
assert(!empty($filepath), __METHOD__ . ": filepath cannot be null");
|
||||||
if (!empty($this->filename) && $this->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...
|
||||||
$this->filehash = hash_file(File::FILEHASH_ALG, $filepath);
|
$filehash = hash_file(File::FILEHASH_ALG, $filepath);
|
||||||
if ($this->filehash === false) {
|
if ($filehash === false) {
|
||||||
throw new ServerException('Could not read file for hashing');
|
throw new ServerException('Could not read file for hashing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $filehash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve or insert as a file in the DB
|
||||||
|
*
|
||||||
|
* @return object File
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws ServerException
|
||||||
|
*/
|
||||||
|
protected function storeFile()
|
||||||
|
{
|
||||||
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.
|
// We're done here. Yes. Already. We assume sha256 won't collide on us anytime soon.
|
||||||
@ -118,14 +174,13 @@ class MediaFile
|
|||||||
$file->urlhash = File::hashurl($fileurl);
|
$file->urlhash = File::hashurl($fileurl);
|
||||||
$file->url = $fileurl;
|
$file->url = $fileurl;
|
||||||
$file->filehash = $this->filehash;
|
$file->filehash = $this->filehash;
|
||||||
$file->size = filesize($filepath);
|
$file->size = filesize($this->filepath);
|
||||||
if ($file->size === false) {
|
if ($file->size === false) {
|
||||||
throw new ServerException('Could not read file to get its size');
|
throw new ServerException('Could not read file to get its size');
|
||||||
}
|
}
|
||||||
$file->date = time();
|
$file->date = time();
|
||||||
$file->mimetype = $this->mimetype;
|
$file->mimetype = $this->mimetype;
|
||||||
|
|
||||||
|
|
||||||
$file_id = $file->insert();
|
$file_id = $file->insert();
|
||||||
|
|
||||||
if ($file_id===false) {
|
if ($file_id===false) {
|
||||||
@ -158,15 +213,24 @@ class MediaFile
|
|||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rememberFile($file, $short)
|
public function rememberFile($file, $short)
|
||||||
{
|
{
|
||||||
$this->maybeAddRedir($file->id, $short);
|
$this->maybeAddRedir($file->id, $short);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeAddRedir($file_id, $url)
|
/**
|
||||||
|
* Adds Redir if needed.
|
||||||
|
*
|
||||||
|
* @param $file_id
|
||||||
|
* @param $url
|
||||||
|
* @return bool false if no need to add, true if added
|
||||||
|
* @throws ClientException If failed adding
|
||||||
|
*/
|
||||||
|
public function maybeAddRedir($file_id, $url)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$file_redir = File_redirection::getByUrl($url);
|
File_redirection::getByUrl($url);
|
||||||
|
return false;
|
||||||
} catch (NoResultException $e) {
|
} catch (NoResultException $e) {
|
||||||
$file_redir = new File_redirection;
|
$file_redir = new File_redirection;
|
||||||
$file_redir->urlhash = File::hashurl($url);
|
$file_redir->urlhash = File::hashurl($url);
|
||||||
@ -180,10 +244,80 @@ class MediaFile
|
|||||||
// 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(_('There was a database error while saving your file. Please try again.'));
|
throw new ClientException(_('There was a database error while saving your file. Please try again.'));
|
||||||
}
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function fromUpload($param='media', Profile $scoped=null)
|
/**
|
||||||
|
* The maximum allowed file size, as a string
|
||||||
|
*/
|
||||||
|
public static function maxFileSize()
|
||||||
|
{
|
||||||
|
$value = self::maxFileSizeInt();
|
||||||
|
if ($value > 1024 * 1024) {
|
||||||
|
$value = $value/(1024*1024);
|
||||||
|
// TRANS: Number of megabytes. %d is the number.
|
||||||
|
return sprintf(_m('%dMB', '%dMB', $value), $value);
|
||||||
|
} elseif ($value > 1024) {
|
||||||
|
$value = $value/1024;
|
||||||
|
// TRANS: Number of kilobytes. %d is the number.
|
||||||
|
return sprintf(_m('%dkB', '%dkB', $value), $value);
|
||||||
|
} else {
|
||||||
|
// TRANS: Number of bytes. %d is the number.
|
||||||
|
return sprintf(_m('%dB', '%dB', $value), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum allowed file size, as an int
|
||||||
|
*/
|
||||||
|
public static function maxFileSizeInt()
|
||||||
|
{
|
||||||
|
return min(
|
||||||
|
self::sizeStrToInt(ini_get('post_max_size')),
|
||||||
|
self::sizeStrToInt(ini_get('upload_max_filesize')),
|
||||||
|
self::sizeStrToInt(ini_get('memory_limit'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string representing a file size (with units), to an int
|
||||||
|
* @param $str
|
||||||
|
* @return bool|int|string
|
||||||
|
*/
|
||||||
|
public static function sizeStrToInt($str)
|
||||||
|
{
|
||||||
|
$unit = substr($str, -1);
|
||||||
|
$num = substr($str, 0, -1);
|
||||||
|
switch (strtoupper($unit)) {
|
||||||
|
case 'G':
|
||||||
|
$num *= 1024;
|
||||||
|
// no break
|
||||||
|
case 'M':
|
||||||
|
$num *= 1024;
|
||||||
|
// no break
|
||||||
|
case 'K':
|
||||||
|
$num *= 1024;
|
||||||
|
}
|
||||||
|
return $num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new MediaFile or ImageFile object from an upload
|
||||||
|
*
|
||||||
|
* Tries to set the mimetype correctly, using the most secure method available and rejects the file otherwise.
|
||||||
|
* In case the upload is an image, this function returns an new ImageFile (which extends MediaFile)
|
||||||
|
* @param string $param
|
||||||
|
* @param Profile|null $scoped
|
||||||
|
* @return ImageFile|MediaFile
|
||||||
|
* @throws ClientException
|
||||||
|
* @throws NoResultException
|
||||||
|
* @throws NoUploadedMediaException
|
||||||
|
* @throws ServerException
|
||||||
|
* @throws UnsupportedMediaException
|
||||||
|
* @throws UseFileAsThumbnailException
|
||||||
|
*/
|
||||||
|
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]) || !isset($_FILES[$param]['error'])) {
|
||||||
@ -194,19 +328,17 @@ class MediaFile
|
|||||||
case UPLOAD_ERR_OK: // success, jump out
|
case UPLOAD_ERR_OK: // success, jump out
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
// TRANS: Client exception thrown when an uploaded file is larger than set in php.ini.
|
|
||||||
throw new ClientException(_('The uploaded file exceeds the ' .
|
|
||||||
'upload_max_filesize directive in php.ini.'));
|
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
throw new ClientException(
|
// TRANS: Exception thrown when too large a file is uploaded.
|
||||||
// TRANS: Client exception.
|
// TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
|
||||||
_('The uploaded file exceeds the MAX_FILE_SIZE directive' .
|
throw new ClientException(sprintf(
|
||||||
' that was specified in the HTML form.'));
|
_('That file is too big. The maximum file size is %s.'),
|
||||||
|
self::maxFileSize()
|
||||||
|
));
|
||||||
case UPLOAD_ERR_PARTIAL:
|
case UPLOAD_ERR_PARTIAL:
|
||||||
@unlink($_FILES[$param]['tmp_name']);
|
@unlink($_FILES[$param]['tmp_name']);
|
||||||
// TRANS: Client exception.
|
// TRANS: Client exception.
|
||||||
throw new ClientException(_('The uploaded file was only' .
|
throw new ClientException(_('The uploaded file was only partially uploaded.'));
|
||||||
' partially uploaded.'));
|
|
||||||
case UPLOAD_ERR_NO_FILE:
|
case UPLOAD_ERR_NO_FILE:
|
||||||
// No file; probably just a non-AJAX submission.
|
// No file; probably just a non-AJAX submission.
|
||||||
throw new NoUploadedMediaException($param);
|
throw new NoUploadedMediaException($param);
|
||||||
@ -220,39 +352,23 @@ 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(_('File upload stopped by extension.'));
|
throw new ClientException(_('File upload stopped by extension.'));
|
||||||
default:
|
default:
|
||||||
common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
|
common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['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(_('System error uploading file.'));
|
throw new ClientException(_('System error uploading file.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make documentation clearer that this won't work for files >2GiB because
|
$filehash = strtolower(self::getHashOfFile($_FILES[$param]['tmp_name']));
|
||||||
// PHP is stupid in its 32bit head. But noone accepts 2GiB files with PHP
|
|
||||||
// anyway... I hope.
|
|
||||||
$filehash = hash_file(File::FILEHASH_ALG, $_FILES[$param]['tmp_name']);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$file = File::getByHash($filehash);
|
$file = File::getByHash($filehash);
|
||||||
// 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
|
||||||
$filename = basename($file->getPath());
|
$filepath = $file->getPath();
|
||||||
$mimetype = $file->mimetype;
|
$mimetype = $file->mimetype;
|
||||||
|
// XXX PHP: Upgrade to PHP 7.1
|
||||||
} catch (FileNotFoundException $e) {
|
// catch (FileNotFoundException | NoResultException $e)
|
||||||
// The file does not exist in our local filesystem, so store this upload.
|
} catch (Exception $e) {
|
||||||
|
|
||||||
if (!move_uploaded_file($_FILES[$param]['tmp_name'], $e->path)) {
|
|
||||||
// TRANS: Client exception thrown when a file upload operation fails because the file could
|
|
||||||
// TRANS: not be moved from the temporary folder to the permanent file location.
|
|
||||||
throw new ClientException(_('File could not be moved to destination directory.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$filename = basename($file->getPath());
|
|
||||||
$mimetype = $file->mimetype;
|
|
||||||
|
|
||||||
} catch (NoResultException $e) {
|
|
||||||
// We have to save the upload as a new local file. This is the normal course of action.
|
// We have to save the upload as a new local file. This is the normal course of action.
|
||||||
|
|
||||||
if ($scoped instanceof Profile) {
|
if ($scoped instanceof Profile) {
|
||||||
// Throws exception if additional size does not respect quota
|
// Throws exception if additional size does not respect quota
|
||||||
// This test is only needed, of course, if we're uploading something new.
|
// This test is only needed, of course, if we're uploading something new.
|
||||||
@ -260,24 +376,38 @@ class MediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']);
|
$mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']);
|
||||||
|
$media = common_get_mime_media($mimetype);
|
||||||
|
|
||||||
$basename = basename($_FILES[$param]['name']);
|
$basename = basename($_FILES[$param]['name']);
|
||||||
|
$filename = $filehash . '.' . File::guessMimeExtension($mimetype, $basename);
|
||||||
$filename = strtolower($filehash) . '.' . File::guessMimeExtension($mimetype, $basename);
|
|
||||||
$filepath = File::path($filename);
|
$filepath = File::path($filename);
|
||||||
|
|
||||||
$result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);
|
$result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);
|
||||||
|
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
// TRANS: Client exception thrown when a file upload operation fails because the file could
|
// TRANS: 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.
|
||||||
|
// UX: too specific
|
||||||
throw new ClientException(_('File could not be moved to destination directory.'));
|
throw new ClientException(_('File could not be moved to destination directory.'));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return new MediaFile($filename, $mimetype, $filehash);
|
if ($media === 'image') {
|
||||||
|
// Use -1 for the id to avoid adding this temporary file to the DB
|
||||||
|
$img = new ImageFile(-1, $filepath);
|
||||||
|
// Validate the image by reencoding it. Additionally normalizes old formats to PNG,
|
||||||
|
// keeping JPEG and GIF untouched
|
||||||
|
$outpath = $img->resizeTo($img->filepath);
|
||||||
|
$ext = image_type_to_extension($img->preferredType());
|
||||||
|
$filename = $filehash . $ext;
|
||||||
|
$filepath = File::path($filename);
|
||||||
|
$result = rename($outpath, $filepath);
|
||||||
|
return new ImageFile(null, $filepath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MediaFile($filepath, $mimetype, $filehash);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
||||||
// so we can always do hash_file on $stream['uri'] as far as I can tell!
|
// so we can always do hash_file on $stream['uri'] as far as I can tell!
|
||||||
@ -310,7 +440,6 @@ class MediaFile
|
|||||||
|
|
||||||
$filename = basename($file->getPath());
|
$filename = basename($file->getPath());
|
||||||
$mimetype = $file->mimetype;
|
$mimetype = $file->mimetype;
|
||||||
|
|
||||||
} catch (NoResultException $e) {
|
} catch (NoResultException $e) {
|
||||||
if ($scoped instanceof Profile) {
|
if ($scoped instanceof Profile) {
|
||||||
File::respectsQuota($scoped, filesize($stream['uri']));
|
File::respectsQuota($scoped, filesize($stream['uri']));
|
||||||
@ -327,7 +456,7 @@ class MediaFile
|
|||||||
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(_('File could not be moved to destination directory.' ));
|
throw new ClientException(_('File could not be moved to destination directory.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,19 +465,105 @@ class MediaFile
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to identify the content type of a given file.
|
* Attempt to identify the content type of a given file.
|
||||||
*
|
*
|
||||||
* @param string $filepath filesystem path as string (file must exist)
|
* @param string $filepath filesystem path as string (file must exist)
|
||||||
* @param string $originalFilename (optional) for extension-based detection
|
* @param bool $originalFilename (optional) for extension-based detection
|
||||||
* @return string
|
* @return string
|
||||||
*
|
*
|
||||||
* @fixme this seems to tie a front-end error message in, kinda confusing
|
|
||||||
*
|
|
||||||
* @throws ClientException if type is known, but not supported for local uploads
|
* @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
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
static function getUploadedMimeType($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
|
||||||
$mimelookup = new finfo(FILEINFO_MIME_TYPE);
|
|
||||||
$mimetype = $mimelookup->file($filepath);
|
$mimetype = null;
|
||||||
|
|
||||||
|
// From CodeIgniter
|
||||||
|
// We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii)
|
||||||
|
$regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
|
||||||
|
/**
|
||||||
|
* Fileinfo extension - most reliable method
|
||||||
|
*
|
||||||
|
* Apparently XAMPP, CentOS, cPanel and who knows what
|
||||||
|
* other PHP distribution channels EXPLICITLY DISABLE
|
||||||
|
* ext/fileinfo, which is otherwise enabled by default
|
||||||
|
* since PHP 5.3 ...
|
||||||
|
*/
|
||||||
|
if (function_exists('finfo_file')) {
|
||||||
|
$finfo = @finfo_open(FILEINFO_MIME);
|
||||||
|
// It is possible that a FALSE value is returned, if there is no magic MIME database
|
||||||
|
// file found on the system
|
||||||
|
if (is_resource($finfo)) {
|
||||||
|
$mime = @finfo_file($finfo, $filepath);
|
||||||
|
finfo_close($finfo);
|
||||||
|
/* According to the comments section of the PHP manual page,
|
||||||
|
* it is possible that this function returns an empty string
|
||||||
|
* for some files (e.g. if they don't exist in the magic MIME database)
|
||||||
|
*/
|
||||||
|
if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
|
||||||
|
$mimetype = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type,
|
||||||
|
* which is still more secure than depending on the value of $_FILES[$field]['type'], and as it
|
||||||
|
* was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better
|
||||||
|
* than mime_content_type() as well, hence the attempts to try calling the command line with
|
||||||
|
* three different functions.
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system
|
||||||
|
* - many system admins would disable the exec(), shell_exec(), popen() and similar functions
|
||||||
|
* due to security concerns, hence the function_usable() checks
|
||||||
|
*/
|
||||||
|
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||||
|
$cmd = 'file --brief --mime '.escapeshellarg($filepath).' 2>&1';
|
||||||
|
if (function_exists('exec')) {
|
||||||
|
/* 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
|
||||||
|
* the actual return value of exec(), and as such - it overwrites anything that could
|
||||||
|
* already be set for $mime previously. This effectively makes the second parameter a
|
||||||
|
* dummy value, which is only put to allow us to get the return status code.
|
||||||
|
*/
|
||||||
|
$mime = @exec($cmd, $mime, $return_status);
|
||||||
|
if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) {
|
||||||
|
$mimetype = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (function_exists('shell_exec')) {
|
||||||
|
$mime = @shell_exec($cmd);
|
||||||
|
if (strlen($mime) > 0) {
|
||||||
|
$mime = explode("\n", trim($mime));
|
||||||
|
if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
|
||||||
|
$mimetype = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (function_exists('popen')) {
|
||||||
|
$proc = @popen($cmd, 'r');
|
||||||
|
if (is_resource($proc)) {
|
||||||
|
$mime = @fread($proc, 512);
|
||||||
|
@pclose($proc);
|
||||||
|
if ($mime !== false) {
|
||||||
|
$mime = explode("\n", trim($mime));
|
||||||
|
if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
|
||||||
|
$mimetype = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall back to mime_content_type(), if available (still better than $_FILES[$field]['type'])
|
||||||
|
if (function_exists('mime_content_type')) {
|
||||||
|
$mimetype = @mime_content_type($filepath);
|
||||||
|
// It's possible that mime_content_type() returns FALSE or an empty string
|
||||||
|
if ($mimetype == false && strlen($mimetype) > 0) {
|
||||||
|
throw new ServerException(_m('Could not determine file\'s MIME type.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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")
|
||||||
|
Loading…
Reference in New Issue
Block a user