2
0
Fork 0
forked from flashii/eeprom
eeprom-nabucco/src/Upload.php

426 lines
15 KiB
PHP
Raw Normal View History

2020-05-08 22:53:21 +00:00
<?php
namespace EEPROM;
use Exception;
use Imagick;
2020-05-08 22:53:21 +00:00
use JsonSerializable;
2022-07-06 16:58:40 +00:00
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Index\Data\DbType;
2020-05-08 22:53:21 +00:00
class UploadNotFoundException extends Exception {};
class UploadCreationFailedException extends Exception {};
final class Upload implements JsonSerializable {
private const ID_CHARS = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789-_';
2022-07-06 16:58:40 +00:00
public function __construct(
private string $id = '',
private int $userId = 0,
private int $appId = 0,
private string $type = 'text/plain',
private string $name = '',
private int $size = 0,
private string $hash = '0000000000000000000000000000000000000000000000000000000000000000',
private int $created = 0,
private int $accessed = 0,
private int $expires = 0,
private int $deleted = 0,
private int $dmca = 0,
private int $bump = 0,
private string $ipAddress = '::1',
) {}
2020-05-08 22:53:21 +00:00
public function getId(): string {
2022-07-06 16:58:40 +00:00
return $this->id;
2020-05-08 22:53:21 +00:00
}
public function getPath(): string {
2022-07-06 16:58:40 +00:00
return PRM_UPLOADS . '/' . $this->id;
2020-05-08 22:53:21 +00:00
}
public function getThumbPath(): string {
2022-07-06 16:58:40 +00:00
return PRM_THUMBS . '/' . $this->id;
2020-05-08 22:53:21 +00:00
}
public function getRemotePath(): string {
2022-07-06 16:58:40 +00:00
return '/uploads/' . $this->id;
2020-05-08 22:53:21 +00:00
}
public function getPublicUrl(bool $forceReal = false): string {
2023-10-31 16:10:32 +00:00
global $cfg;
if(!$forceReal && $cfg->hasValues('domain:short'))
return '//' . $cfg->getString('domain:short') . '/' . $this->id;
2020-05-08 22:53:21 +00:00
return '//' . $_SERVER['HTTP_HOST'] . $this->getRemotePath();
}
public function getPublicThumbUrl(bool $forceReal = false): string {
return $this->getPublicUrl($forceReal) . '.t';
}
public function getUserId(): int {
2022-07-06 16:58:40 +00:00
return $this->userId;
2020-05-08 22:53:21 +00:00
}
public function getApplicationId(): int {
2022-07-06 16:58:40 +00:00
return $this->appId;
2020-05-08 22:53:21 +00:00
}
public function getType(): string {
2022-07-06 16:58:40 +00:00
return $this->type;
2020-05-08 22:53:21 +00:00
}
public function isImage(): bool {
2022-07-06 16:58:40 +00:00
return str_starts_with($this->type, 'image/');
}
public function isVideo(): bool {
2022-07-06 16:58:40 +00:00
return str_starts_with($this->type, 'video/');
}
public function isAudio(): bool {
2022-07-06 16:58:40 +00:00
return str_starts_with($this->type, 'audio/');
}
2020-05-08 22:53:21 +00:00
public function getName(): string {
2022-07-06 16:58:40 +00:00
return $this->name;
2020-05-08 22:53:21 +00:00
}
public function getSize(): int {
2022-07-06 16:58:40 +00:00
return $this->size;
2020-05-08 22:53:21 +00:00
}
public function getHash(): string {
2022-07-06 16:58:40 +00:00
return $this->hash;
2020-05-08 22:53:21 +00:00
}
public function getCreated(): int {
2022-07-06 16:58:40 +00:00
return $this->created;
2020-05-08 22:53:21 +00:00
}
public function getLastAccessed(): int {
2022-07-06 16:58:40 +00:00
return $this->accessed;
2020-05-08 22:53:21 +00:00
}
2022-07-06 16:58:40 +00:00
public function bumpAccess(IDbConnection $conn): void {
if(empty($this->id))
2020-05-08 22:53:21 +00:00
return;
2022-07-06 16:58:40 +00:00
$this->accessed = time();
$bump = $conn->prepare('UPDATE `prm_uploads` SET `upload_accessed` = NOW() WHERE `upload_id` = ?');
$bump->addParameter(1, $this->id, DbType::STRING);
$bump->execute();
2020-05-08 22:53:21 +00:00
}
public function getExpires(): int {
2022-07-06 16:58:40 +00:00
return $this->expires;
2020-05-08 22:53:21 +00:00
}
public function hasExpired(): bool {
2022-07-06 16:58:40 +00:00
return $this->expires > 1 && $this->expires <= time();
2020-05-08 22:53:21 +00:00
}
public function getDeleted(): int {
2022-07-06 16:58:40 +00:00
return $this->deleted;
2020-05-08 22:53:21 +00:00
}
public function isDeleted(): bool {
2023-10-31 15:30:22 +00:00
return $this->deleted > 0;
2020-05-08 22:53:21 +00:00
}
public function getDMCA(): int {
2022-07-06 16:58:40 +00:00
return $this->dmca;
2020-05-08 22:53:21 +00:00
}
public function isDMCA(): bool {
2022-07-06 16:58:40 +00:00
return $this->dmca > 0;
2020-05-08 22:53:21 +00:00
}
public function getExpiryBump(): int {
2022-07-06 16:58:40 +00:00
return $this->bump;
2020-05-08 22:53:21 +00:00
}
2023-10-31 15:30:22 +00:00
public function getRemoteAddress(): string {
return $this->ipAddress;
}
2022-07-06 16:58:40 +00:00
public function bumpExpiry(IDbConnection $conn): void {
if(empty($this->id) || $this->expires < 1)
2020-05-08 22:53:21 +00:00
return;
2022-07-06 16:58:40 +00:00
if($this->bump < 1)
2020-05-08 22:53:21 +00:00
return;
$this->expires = time() + $this->bump;
2020-05-08 22:53:21 +00:00
2022-07-06 16:58:40 +00:00
$bump = $conn->prepare('UPDATE `prm_uploads` SET `upload_expires` = NOW() + INTERVAL ? SECOND WHERE `upload_id` = ?');
$bump->addParameter(1, $this->bump, DbType::INTEGER);
$bump->addParameter(2, $this->id, DbType::STRING);
$bump->execute();
2020-05-08 22:53:21 +00:00
}
2022-07-06 16:58:40 +00:00
public function restore(IDbConnection $conn): void {
2023-10-31 15:30:22 +00:00
$this->deleted = 0;
2022-07-06 16:58:40 +00:00
$restore = $conn->prepare('UPDATE `prm_uploads` SET `upload_deleted` = NULL WHERE `upload_id` = ?');
$restore->addParameter(1, $this->id, DbType::STRING);
2020-05-08 22:53:21 +00:00
$restore->execute();
}
2022-07-06 16:58:40 +00:00
public function delete(IDbConnection $conn, bool $hard): void {
2023-10-31 15:30:22 +00:00
$this->deleted = time();
2020-05-08 22:53:21 +00:00
if($hard) {
if(is_file($this->getPath()))
unlink($this->getPath());
if(is_file($this->getThumbPath()))
unlink($this->getThumbPath());
2022-07-06 16:58:40 +00:00
if($this->dmca < 1) {
$delete = $conn->prepare('DELETE FROM `prm_uploads` WHERE `upload_id` = ?');
$delete->addParameter(1, $this->id, DbType::STRING);
2020-05-08 22:53:21 +00:00
$delete->execute();
}
} else {
2022-07-06 16:58:40 +00:00
$delete = $conn->prepare('UPDATE `prm_uploads` SET `upload_deleted` = NOW() WHERE `upload_id` = ?');
$delete->addParameter(1, $this->id, DbType::STRING);
2020-05-08 22:53:21 +00:00
$delete->execute();
}
}
public function jsonSerialize(): mixed {
2020-05-08 22:53:21 +00:00
return [
2022-07-06 16:58:40 +00:00
'id' => $this->id,
2020-05-08 22:53:21 +00:00
'url' => $this->getPublicUrl(),
'urlf' => $this->getPublicUrl(true),
'thumb' => $this->getPublicThumbUrl(),
2022-07-06 16:58:40 +00:00
'name' => $this->name,
'type' => $this->type,
'size' => $this->size,
'user' => $this->userId,
'appl' => $this->appId,
'hash' => $this->hash,
'created' => date('c', $this->created),
'accessed' => $this->accessed < 1 ? null : date('c', $this->accessed),
'expires' => $this->expires < 1 ? null : date('c', $this->expires),
'deleted' => $this->deleted < 1 ? null : date('c', $this->deleted),
'dmca' => $this->dmca < 1 ? null : date('c', $this->dmca),
2020-05-08 22:53:21 +00:00
];
}
public static function generateId(int $length = 32): string {
$bytes = str_repeat("\0", $length);
2020-05-08 22:53:21 +00:00
$chars = strlen(self::ID_CHARS);
for($i = 0; $i < $length; ++$i)
$bytes[$i] = self::ID_CHARS[random_int(0, $chars - 1)];
return $bytes;
2020-05-08 22:53:21 +00:00
}
public static function create(
2022-07-06 16:58:40 +00:00
IDbConnection $conn,
2020-05-08 22:53:21 +00:00
Application $app, User $user,
string $fileName, string $fileType,
string $fileSize, string $fileHash,
int $fileExpiry, bool $bumpExpiry
): self {
$appId = $app->getId();
$userId = $user->getId();
if(strpos($fileType, '/') === false || $fileSize < 1 || strlen($fileHash) !== 64 || $fileExpiry < 0)
throw new UploadCreationFailedException('Bad args.');
$id = self::generateId();
2022-07-06 16:58:40 +00:00
$create = $conn->prepare(
'INSERT INTO `prm_uploads` ('
. ' `upload_id`, `app_id`, `user_id`, `upload_name`,'
. ' `upload_type`, `upload_size`, `upload_hash`, `upload_ip`,'
. ' `upload_expires`, `upload_bump`'
. ') VALUES (?, ?, ?, ?, ?, ?, UNHEX(?), INET6_ATON(?), FROM_UNIXTIME(?), ?)'
);
$create->addParameter(1, $id, DbType::STRING);
$create->addParameter(2, $appId < 1 ? null : $appId, DbType::INTEGER);
$create->addParameter(3, $userId < 1 ? null : $userId, DbType::INTEGER);
$create->addParameter(4, $fileName, DbType::STRING);
$create->addParameter(5, $fileType, DbType::STRING);
$create->addParameter(6, $fileSize, DbType::INTEGER);
$create->addParameter(7, $fileHash, DbType::STRING);
$create->addParameter(8, $_SERVER['REMOTE_ADDR'], DbType::STRING);
$create->addParameter(9, $fileExpiry > 0 ? (time() + $fileExpiry) : 0, DbType::INTEGER);
$create->addParameter(10, $bumpExpiry ? $fileExpiry : 0, DbType::INTEGER);
2020-05-08 22:53:21 +00:00
$create->execute();
try {
2022-07-06 16:58:40 +00:00
return self::byId($conn, $id);
2020-05-08 22:53:21 +00:00
} catch(UploadNotFoundException $ex) {
throw new UploadCreationFailedException;
}
}
2022-07-06 16:58:40 +00:00
private static function constructDb(IDbResult $result): self {
return new static(
$result->getString(0),
$result->getInteger(2),
$result->getInteger(1),
$result->getString(4),
$result->getString(3),
$result->getInteger(5),
$result->getString(13),
$result->getInteger(7),
$result->getInteger(8),
$result->getInteger(9),
$result->getInteger(10),
$result->getInteger(11),
$result->getInteger(6),
$result->getString(12),
);
}
public static function byId(IDbConnection $conn, string $id): self {
$get = $conn->prepare(
'SELECT `upload_id`, `app_id`, `user_id`, `upload_name`, `upload_type`, `upload_size`, `upload_bump`,'
. ' UNIX_TIMESTAMP(`upload_created`) AS `upload_created`,'
. ' UNIX_TIMESTAMP(`upload_accessed`) AS `upload_accessed`,'
. ' UNIX_TIMESTAMP(`upload_expires`) AS `upload_expires`,'
. ' UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`,'
. ' UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`,'
. ' INET6_NTOA(`upload_ip`) AS `upload_ip`,'
. ' LOWER(HEX(`upload_hash`)) AS `upload_hash`'
. ' FROM `prm_uploads` WHERE `upload_id` = ? AND `upload_deleted` IS NULL'
);
$get->addParameter(1, $id, DbType::STRING);
$get->execute();
$result = $get->getResult();
if(!$result->next())
2020-05-08 22:53:21 +00:00
throw new UploadNotFoundException;
2022-07-06 16:58:40 +00:00
return self::constructDb($result);
}
public static function byHash(IDbConnection $conn, string $hash): ?self {
$get = $conn->prepare(
'SELECT `upload_id`, `app_id`, `user_id`, `upload_name`, `upload_type`, `upload_size`, `upload_bump`,'
. ' UNIX_TIMESTAMP(`upload_created`) AS `upload_created`,'
. ' UNIX_TIMESTAMP(`upload_accessed`) AS `upload_accessed`,'
. ' UNIX_TIMESTAMP(`upload_expires`) AS `upload_expires`,'
. ' UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`,'
. ' UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`,'
. ' INET6_NTOA(`upload_ip`) AS `upload_ip`,'
. ' LOWER(HEX(`upload_hash`)) AS `upload_hash`'
. ' FROM `prm_uploads` WHERE `upload_hash` = UNHEX(?)'
2022-07-06 16:58:40 +00:00
);
$get->addParameter(1, $hash, DbType::STRING);
$get->execute();
$result = $get->getResult();
if(!$result->next())
return null;
return self::constructDb($result);
}
public static function deleted(IDbConnection $conn): array {
$result = $conn->query(
'SELECT `upload_id`, `app_id`, `user_id`, `upload_name`, `upload_type`, `upload_size`, `upload_bump`,'
. ' UNIX_TIMESTAMP(`upload_created`) AS `upload_created`,'
. ' UNIX_TIMESTAMP(`upload_accessed`) AS `upload_accessed`,'
. ' UNIX_TIMESTAMP(`upload_expires`) AS `upload_expires`,'
. ' UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`,'
. ' UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`,'
. ' INET6_NTOA(`upload_ip`) AS `upload_ip`,'
. ' LOWER(HEX(`upload_hash`)) AS `upload_hash`'
. ' FROM `prm_uploads`'
. ' WHERE `upload_deleted` IS NOT NULL'
. ' OR `upload_dmca` IS NOT NULL'
. ' OR `user_id` IS NULL'
. ' OR `app_id` IS NULL'
);
2020-05-08 22:53:21 +00:00
$deleted = [];
2022-07-06 16:58:40 +00:00
while($result->next())
$deleted[] = self::constructDb($result);
2020-05-08 22:53:21 +00:00
return $deleted;
}
2022-07-06 16:58:40 +00:00
public static function expired(IDbConnection $conn): array {
$result = $conn->query(
'SELECT `upload_id`, `app_id`, `user_id`, `upload_name`, `upload_type`, `upload_size`, `upload_bump`,'
. ' UNIX_TIMESTAMP(`upload_created`) AS `upload_created`,'
. ' UNIX_TIMESTAMP(`upload_accessed`) AS `upload_accessed`,'
. ' UNIX_TIMESTAMP(`upload_expires`) AS `upload_expires`,'
. ' UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`,'
. ' UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`,'
. ' INET6_NTOA(`upload_ip`) AS `upload_ip`,'
. ' LOWER(HEX(`upload_hash`)) AS `upload_hash`'
. ' FROM `prm_uploads`'
. ' WHERE `upload_expires` IS NOT NULL'
. ' AND `upload_expires` <= NOW()'
. ' AND `upload_dmca` IS NULL'
);
$expired = [];
while($result->next())
$expired[] = self::constructDb($result);
return $expired;
2020-05-08 22:53:21 +00:00
}
public function supportsThumbnail(): bool {
return $this->isImage() || $this->isAudio() || $this->isVideo();
}
public function createThumbnail(): void {
$tmpFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'eeprom' . bin2hex(random_bytes(10)) . '.jpg';
try {
if($this->isImage())
$imagick = new Imagick($this->getPath());
elseif($this->isAudio())
$imagick = $this->getCoverFromAudio($tmpFile);
elseif($this->isVideo())
$imagick = $this->getFrameFromVideo($tmpFile);
if(!isset($imagick))
return;
$imagick->setImageFormat('jpg');
$imagick->setImageCompressionQuality(40);
$thumbRes = 100;
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
if ($width > $height) {
2023-10-31 15:30:22 +00:00
$resizeWidth = $width * $thumbRes / $height;
$resizeHeight = $thumbRes;
} else {
$resizeWidth = $thumbRes;
2023-10-31 15:30:22 +00:00
$resizeHeight = $height * $thumbRes / $width;
}
2023-10-31 15:30:22 +00:00
$resizeWidth = (int)$resizeWidth;
$resizeHeight = (int)$resizeHeight;
$imagick->resizeImage(
$resizeWidth, $resizeHeight,
Imagick::FILTER_GAUSSIAN, 0.7
);
$imagick->cropImage(
$thumbRes,
$thumbRes,
2023-10-31 15:30:22 +00:00
(int)ceil(($resizeWidth - $thumbRes) / 2),
(int)ceil(($resizeHeight - $thumbRes) / 2)
);
$imagick->writeImage($this->getThumbPath());
} catch(Exception $ex) {}
if(is_file($tmpFile))
unlink($tmpFile);
}
private function getFrameFromVideo(string $path): Imagick {
shell_exec(sprintf('ffmpeg -i %s -ss 00:00:01.000 -vframes 1 %s', $this->getPath(), $path));
return new Imagick($path);
}
private function getCoverFromAudio(string $path): Imagick {
shell_exec(sprintf('ffmpeg -i %s -an -vcodec copy %s', $this->getPath(), $path));
return new Imagick($path);
}
2020-05-08 22:53:21 +00:00
}