308 lines
8.5 KiB
PHP
308 lines
8.5 KiB
PHP
<?php
|
|
|
|
const UPLOADS_ALLOWED_EXTENSIONS = [
|
|
'jpg', 'png', 'git', 'mp4', 'mp3', 'ogg', 'diff', 'txt', 'gz', 'tar',
|
|
'icc', 'icm', 'patch', 'zip', 'brd', 'pdf', 'lua', 'xpi', 'rar', '7z',
|
|
'tgz', 'bin', 'py', 'pac', 'yaml', 'toml', 'xml', 'json', 'yml',
|
|
];
|
|
|
|
class uploads {
|
|
|
|
static function getCount(): int {
|
|
$db = DB();
|
|
return (int)$db->result($db->query("SELECT COUNT(*) FROM uploads"));
|
|
}
|
|
|
|
static function isExtensionAllowed(string $ext): bool {
|
|
return in_array($ext, UPLOADS_ALLOWED_EXTENSIONS);
|
|
}
|
|
|
|
static function add(string $tmp_name, string $name, string $note): ?int {
|
|
global $config;
|
|
|
|
$name = sanitize_filename($name);
|
|
if (!$name)
|
|
$name = 'file';
|
|
|
|
$random_id = self::_getNewUploadRandomId();
|
|
$size = filesize($tmp_name);
|
|
$is_image = detect_image_type($tmp_name) !== false;
|
|
$image_w = 0;
|
|
$image_h = 0;
|
|
if ($is_image) {
|
|
list($image_w, $image_h) = getimagesize($tmp_name);
|
|
}
|
|
|
|
$db = DB();
|
|
if (!$db->insert('uploads', [
|
|
'random_id' => $random_id,
|
|
'ts' => time(),
|
|
'name' => $name,
|
|
'size' => $size,
|
|
'image' => (int)$is_image,
|
|
'image_w' => $image_w,
|
|
'image_h' => $image_h,
|
|
'note' => $note,
|
|
'downloads' => 0,
|
|
])) {
|
|
return null;
|
|
}
|
|
|
|
$id = $db->insertId();
|
|
|
|
$dir = $config['uploads_dir'].'/'.$random_id;
|
|
$path = $dir.'/'.$name;
|
|
|
|
mkdir($dir);
|
|
chmod($dir, 0775); // g+w
|
|
|
|
rename($tmp_name, $path);
|
|
chmod($path, 0664); // g+w
|
|
|
|
return $id;
|
|
}
|
|
|
|
static function delete(int $id): bool {
|
|
$upload = self::get($id);
|
|
if (!$upload)
|
|
return false;
|
|
|
|
$db = DB();
|
|
$db->query("DELETE FROM uploads WHERE id=?", $id);
|
|
|
|
rrmdir($upload->getDirectory());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return Upload[]
|
|
*/
|
|
static function getAllUploads(): array {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM uploads ORDER BY id DESC");
|
|
return array_map('Upload::create_instance', $db->fetchAll($q));
|
|
}
|
|
|
|
static function get(int $id): ?Upload {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM uploads WHERE id=?", $id);
|
|
if ($db->numRows($q)) {
|
|
return new Upload($db->fetch($q));
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string[] $ids
|
|
* @param bool $flat
|
|
* @return Upload[]
|
|
*/
|
|
static function getUploadsByRandomId(array $ids, bool $flat = false): array {
|
|
if (empty($ids)) {
|
|
return [];
|
|
}
|
|
|
|
$db = DB();
|
|
$uploads = array_fill_keys($ids, null);
|
|
|
|
$q = $db->query("SELECT * FROM uploads WHERE random_id IN('".implode('\',\'', array_map([$db, 'escape'], $ids))."')");
|
|
|
|
while ($row = $db->fetch($q)) {
|
|
$uploads[$row['random_id']] = new Upload($row);
|
|
}
|
|
|
|
if ($flat) {
|
|
$list = [];
|
|
foreach ($ids as $id) {
|
|
$list[] = $uploads[$id];
|
|
}
|
|
unset($uploads);
|
|
return $list;
|
|
}
|
|
|
|
return $uploads;
|
|
}
|
|
|
|
static function getUploadByRandomId(string $random_id): ?Upload {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM uploads WHERE random_id=? LIMIT 1", $random_id);
|
|
if ($db->numRows($q)) {
|
|
return new Upload($db->fetch($q));
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static function _getNewUploadRandomId(): string {
|
|
$db = DB();
|
|
do {
|
|
$random_id = strgen(8);
|
|
} while ($db->numRows($db->query("SELECT id FROM uploads WHERE random_id=?", $random_id)) > 0);
|
|
return $random_id;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class Upload extends model {
|
|
|
|
const DB_TABLE = 'uploads';
|
|
|
|
public static array $ImageExtensions = ['jpg', 'jpeg', 'png', 'gif'];
|
|
public static array $VideoExtensions = ['mp4', 'ogg'];
|
|
|
|
public int $id;
|
|
public string $randomId;
|
|
public int $ts;
|
|
public string $name;
|
|
public int $size;
|
|
public int $downloads;
|
|
public int $image; // TODO: remove
|
|
public int $imageW;
|
|
public int $imageH;
|
|
public string $note;
|
|
|
|
function getDirectory(): string {
|
|
global $config;
|
|
return $config['uploads_dir'].'/'.$this->randomId;
|
|
}
|
|
|
|
function getDirectUrl(): string {
|
|
global $config;
|
|
return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$this->name;
|
|
}
|
|
|
|
function getDirectPreviewUrl(int $w, int $h, bool $retina = false): string {
|
|
global $config;
|
|
if ($w == $this->imageW && $this->imageH == $h)
|
|
return $this->getDirectUrl();
|
|
|
|
if ($retina) {
|
|
$w *= 2;
|
|
$h *= 2;
|
|
}
|
|
|
|
$prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p';
|
|
return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg';
|
|
}
|
|
|
|
// TODO remove?
|
|
function incrementDownloads() {
|
|
$db = DB();
|
|
$db->query("UPDATE uploads SET downloads=downloads+1 WHERE id=?", $this->id);
|
|
$this->downloads++;
|
|
}
|
|
|
|
function getSize(): string {
|
|
return sizeString($this->size);
|
|
}
|
|
|
|
function getMarkdown(): string {
|
|
if ($this->isImage()) {
|
|
$md = '{image:'.$this->randomId.',w='.$this->imageW.',h='.$this->imageH.'}{/image}';
|
|
} else if ($this->isVideo()) {
|
|
$md = '{video:'.$this->randomId.'}{/video}';
|
|
} else {
|
|
$md = '{fileAttach:'.$this->randomId.'}{/fileAttach}';
|
|
}
|
|
$md .= ' <!-- '.$this->name.' -->';
|
|
return $md;
|
|
}
|
|
|
|
function setNote(string $note) {
|
|
$db = DB();
|
|
$db->query("UPDATE uploads SET note=? WHERE id=?", $note, $this->id);
|
|
}
|
|
|
|
function isImage(): bool {
|
|
return in_array(extension($this->name), self::$ImageExtensions);
|
|
}
|
|
|
|
// assume all png images have alpha channel
|
|
// i know this is wrong, but anyway
|
|
function imageMayHaveAlphaChannel(): bool {
|
|
return strtolower(extension($this->name)) == 'png';
|
|
}
|
|
|
|
function isVideo(): bool {
|
|
return in_array(extension($this->name), self::$VideoExtensions);
|
|
}
|
|
|
|
function getImageRatio(): float {
|
|
return $this->imageW / $this->imageH;
|
|
}
|
|
|
|
function getImagePreviewSize(?int $w = null, ?int $h = null): array {
|
|
if (is_null($w) && is_null($h))
|
|
throw new Exception(__METHOD__.': both width and height can\'t be null');
|
|
|
|
if (is_null($h))
|
|
$h = round($w / $this->getImageRatio());
|
|
|
|
if (is_null($w))
|
|
$w = round($h * $this->getImageRatio());
|
|
|
|
return [$w, $h];
|
|
}
|
|
|
|
function createImagePreview(?int $w = null,
|
|
?int $h = null,
|
|
bool $force_update = false,
|
|
bool $may_have_alpha = false): bool {
|
|
global $config;
|
|
|
|
$orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name;
|
|
$updated = false;
|
|
|
|
foreach (themes::getThemes() as $theme) {
|
|
if (!$may_have_alpha && $theme == 'dark')
|
|
continue;
|
|
|
|
for ($mult = 1; $mult <= 2; $mult++) {
|
|
$dw = $w * $mult;
|
|
$dh = $h * $mult;
|
|
|
|
$prefix = $may_have_alpha ? 'a' : 'p';
|
|
$dst = $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$dw.'x'.$dh.($theme == 'dark' ? '_dark' : '').'.jpg';
|
|
|
|
if (file_exists($dst)) {
|
|
if (!$force_update)
|
|
continue;
|
|
unlink($dst);
|
|
}
|
|
|
|
$img = imageopen($orig);
|
|
imageresize($img, $dw, $dh, themes::getThemeAlphaColorAsRGB($theme));
|
|
imagejpeg($img, $dst, $mult == 1 ? 93 : 67);
|
|
imagedestroy($img);
|
|
|
|
setperm($dst);
|
|
$updated = true;
|
|
}
|
|
}
|
|
|
|
return $updated;
|
|
}
|
|
|
|
/**
|
|
* @return int Number of deleted files
|
|
*/
|
|
function deleteAllImagePreviews(): int {
|
|
global $config;
|
|
$dir = $config['uploads_dir'].'/'.$this->randomId;
|
|
$files = scandir($dir);
|
|
$deleted = 0;
|
|
foreach ($files as $f) {
|
|
if (preg_match('/^[ap](\d+)x(\d+)(?:_dark)?\.jpg$/', $f)) {
|
|
if (is_file($dir.'/'.$f))
|
|
unlink($dir.'/'.$f);
|
|
else
|
|
logError(__METHOD__.': '.$dir.'/'.$f.' is not a file!');
|
|
$deleted++;
|
|
}
|
|
}
|
|
return $deleted;
|
|
}
|
|
|
|
} |