php-file-layer/FileHandler.php

154 lines
4.8 KiB
PHP

<?php
/**
* Simple file upload layer. Handles file metadata and storage
* FileHandler: part that handles a specific disk storage layout
*
* Version 2019-05-01
* (c) Vitaliy Filippov 2018+
*/
class FileHandler
{
public static $uploadCurl;
public function __construct($config)
{
$this->config = $config + [
'basedir' => '',
'baseurl' => '',
'mime_blacklist' => '#'.
// HTML may contain cookie-stealing JavaScript and web bugs
'^text/(html|(x-)?javascript)$|^application/x-shellscript$'.
// PHP/Perl/Bash/etc scripts may execute arbitrary code on the server
'|php|perl|python|bash|x-c?sh(e|$)'.
// Client-side hazards on Internet Explorer
'|^text/scriptlet$|^application/x-msdownload$'.
// Windows metafile, client-side vulnerability on some systems
'|^application/x-msmetafile$'.
'#is',
'thumb_path' => 'thumb/',
];
}
public function getPath($web_or_fs, $file)
{
$s = $file['sha1'];
return $this->config[$web_or_fs ? 'baseurl' : 'basedir'] . '/' .
substr($s, 0, 1) . '/' . substr($s, 0, 2) . '/' . $s . '.' . $file['format'];
}
public function getThumbPath($web_or_fs, $file, $type)
{
$s = $file['sha1'];
$ext = $file['format'] == 'jpg' || $file['format'] == 'png' || $file['format'] == 'gif'
? $file['format'] : 'jpg';
return $this->config[$web_or_fs ? 'baseurl' : 'basedir'] . '/' .
$this->config['thumb_path'] . '/' . $type . '/' .substr($s, 0, 1) . '/' . substr($s, 0, 2) . '/' . $s . '.' . $ext;
}
public function upload($localFile, $allowedFormats = FileUtils::ANYTHING)
{
$tmp_name = $localFile->getLocalPath();
$props = FileUtils::getProps($allowedFormats, $this->config['mime_blacklist'], $tmp_name, $localFile->getFileName());
$fn = $this->getPath(false, $props);
if (file_exists($fn) && filesize($fn) == $props['size'])
{
return $props;
}
FileUtils::mkpath(dirname($fn), true);
$m = $localFile->shouldMove ? 'rename' : 'copy';
if (!@$m($tmp_name, $fn))
{
$error = error_get_last();
throw new Exception($error['message']);
}
chmod($fn, 0666 & ~umask());
return $props;
}
public function uploadUrl($url, $flags = FileUtils::ONLY_IMAGES, $curl_options = [])
{
if (!$url)
{
return NULL;
}
$file = NULL;
if (substr($url, 0, 2) == '//')
{
$url = "http:$url";
}
if (!self::$uploadCurl)
{
// Reuse handle to use keepalive when possible
self::$uploadCurl = curl_init();
}
curl_setopt_array(self::$uploadCurl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
] + $curl_options);
$s = curl_exec(self::$uploadCurl);
if ($s)
{
$tmp = tempnam(sys_get_temp_dir(), 'upl');
file_put_contents($tmp, $s);
unset($s);
$file = $this->upload(new LocalFile($tmp, true), $flags);
@unlink($tmp);
}
else
{
// Log it as E_USER_NOTICE
trigger_error(curl_error(self::$uploadCurl));
}
return $file;
}
public function deleteFile($e)
{
$disk_name = $this->getPath(false, $e);
if (file_exists($disk_name))
{
// Remove old file
unlink($disk_name);
}
$thumb_glob = $this->getThumbPath(false, $e, '*');
foreach (glob($thumb_glob) as $thumb)
{
if (file_exists($thumb))
{
unlink($thumb);
}
}
}
/**
* Get or generate a thumbnail and return its URL
*/
public function getThumb($file, $width, $height, $force = false, $crop = false, $alignY = 0.5)
{
$type = FileUtils::getThumbType($file, $width, $height, $crop, $alignY);
if (!$type)
{
return NULL;
}
$fn = $this->getThumbPath(false, $file, $type['type']);
if (!file_exists($fn) || $force)
{
if (substr($file['mimetype'], 0, 6) === 'video/')
{
$sourcefn = $this->getThumbPath(false, $file, 'src');
}
else
{
$sourcefn = $this->getPath(false, $file);
}
if (!FileUtils::generateThumbnail($sourcefn, $fn, $file, $type['width'], $type['height'], $crop, $alignY))
{
return NULL;
}
}
return $this->getThumbPath(true, $file, $type);
}
}