Updated database code structure to match other projects.
This commit is contained in:
parent
2f47bef354
commit
868c443d71
18 changed files with 879 additions and 696 deletions
26
composer.lock
generated
26
composer.lock
generated
|
@ -78,7 +78,7 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.flash.moe/flash/index.git",
|
"url": "https://git.flash.moe/flash/index.git",
|
||||||
"reference": "82a350a5c719cc83aa22382201683a68a2629f2a"
|
"reference": "c563bb20e8dfc046ca3cb4bbcbd682b28f87a88f"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
],
|
],
|
||||||
"description": "Composer package for the common library for my projects.",
|
"description": "Composer package for the common library for my projects.",
|
||||||
"homepage": "https://railgun.sh/index",
|
"homepage": "https://railgun.sh/index",
|
||||||
"time": "2023-09-15T22:44:36+00:00"
|
"time": "2023-11-09T14:04:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flashwave/syokuhou",
|
"name": "flashwave/syokuhou",
|
||||||
|
@ -803,16 +803,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "php-http/promise",
|
"name": "php-http/promise",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/php-http/promise.git",
|
"url": "https://github.com/php-http/promise.git",
|
||||||
"reference": "ef4905bfb492ff389eb7f12e26925a0f20073050"
|
"reference": "44a67cb59f708f826f3bec35f22030b3edb90119"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/php-http/promise/zipball/ef4905bfb492ff389eb7f12e26925a0f20073050",
|
"url": "https://api.github.com/repos/php-http/promise/zipball/44a67cb59f708f826f3bec35f22030b3edb90119",
|
||||||
"reference": "ef4905bfb492ff389eb7f12e26925a0f20073050",
|
"reference": "44a67cb59f708f826f3bec35f22030b3edb90119",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -849,9 +849,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/php-http/promise/issues",
|
"issues": "https://github.com/php-http/promise/issues",
|
||||||
"source": "https://github.com/php-http/promise/tree/1.2.0"
|
"source": "https://github.com/php-http/promise/tree/1.2.1"
|
||||||
},
|
},
|
||||||
"time": "2023-10-24T09:20:26+00:00"
|
"time": "2023-11-08T12:57:08+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/container",
|
"name": "psr/container",
|
||||||
|
@ -1790,16 +1790,16 @@
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.10.40",
|
"version": "1.10.41",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d"
|
"reference": "c6174523c2a69231df55bdc65b61655e72876d76"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76",
|
||||||
"reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d",
|
"reference": "c6174523c2a69231df55bdc65b61655e72876d76",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1848,7 +1848,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-10-30T14:48:31+00:00"
|
"time": "2023-11-05T12:57:57+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|
27
cron.php
27
cron.php
|
@ -14,16 +14,23 @@ $semaphore = sem_get($ftok, 1);
|
||||||
if(!sem_acquire($semaphore))
|
if(!sem_acquire($semaphore))
|
||||||
die('Failed to acquire semaphore.' . PHP_EOL);
|
die('Failed to acquire semaphore.' . PHP_EOL);
|
||||||
|
|
||||||
require_once __DIR__ . '/eeprom.php';
|
try {
|
||||||
|
require_once __DIR__ . '/eeprom.php';
|
||||||
|
|
||||||
// Mark expired as deleted
|
$uploadsCtx = $eeprom->getUploadsContext();
|
||||||
$expired = Upload::expired($db);
|
$uploadsData = $uploadsCtx->getUploadsData();
|
||||||
foreach($expired as $upload)
|
|
||||||
$upload->delete($db, false);
|
|
||||||
|
|
||||||
// Hard delete soft deleted files
|
// Mark expired as deleted
|
||||||
$deleted = Upload::deleted($db);
|
$expired = $uploadsData->getUploads(expired: true, deleted: false, dmca: false);
|
||||||
foreach($deleted as $upload)
|
foreach($expired as $uploadInfo)
|
||||||
$upload->delete($db, true);
|
$uploadsData->deleteUpload($uploadInfo);
|
||||||
|
|
||||||
sem_release($semaphore);
|
// Hard delete soft deleted files
|
||||||
|
$deleted = $uploadsData->getUploads(deleted: true, dmca: false);
|
||||||
|
foreach($deleted as $uploadInfo) {
|
||||||
|
$uploadsCtx->deleteUploadData($uploadInfo);
|
||||||
|
$uploadsData->nukeUpload($uploadInfo);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sem_release($semaphore);
|
||||||
|
}
|
||||||
|
|
180
public/index.php
180
public/index.php
|
@ -34,23 +34,29 @@ function eepromOriginAllowed(string $origin): bool {
|
||||||
return in_array($origin, $allowed);
|
return in_array($origin, $allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function eepromUploadInfo(Upload $uploadInfo): array {
|
function eepromUploadInfo(Uploads\UploadInfo $uploadInfo): array {
|
||||||
|
global $eeprom;
|
||||||
|
|
||||||
|
$uploadsCtx = $eeprom->getUploadsContext();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $uploadInfo->getId(),
|
'id' => $uploadInfo->getId(),
|
||||||
'url' => $uploadInfo->getPublicUrl(),
|
'url' => $uploadsCtx->getFileUrlV1($uploadInfo),
|
||||||
'urlf' => $uploadInfo->getPublicUrl(true),
|
'urlf' => $uploadsCtx->getFileUrlV1($uploadInfo, true),
|
||||||
'thumb' => $uploadInfo->getPublicThumbUrl(),
|
'thumb' => $uploadsCtx->getThumbnailUrlV1($uploadInfo),
|
||||||
'name' => $uploadInfo->getName(),
|
'name' => $uploadInfo->getName(),
|
||||||
'type' => $uploadInfo->getType(),
|
'type' => $uploadInfo->getMediaTypeString(),
|
||||||
'size' => $uploadInfo->getSize(),
|
'size' => $uploadInfo->getDataSize(),
|
||||||
'user' => $uploadInfo->getUserId(),
|
'user' => (int)$uploadInfo->getUserId(),
|
||||||
'appl' => $uploadInfo->getApplicationId(),
|
'appl' => (int)$uploadInfo->getAppId(),
|
||||||
'hash' => $uploadInfo->getHash(),
|
'hash' => $uploadInfo->getHashString(),
|
||||||
'created' => date('c', $uploadInfo->getCreated()),
|
'created' => str_replace('+00:00', 'Z', $uploadInfo->getCreatedAt()->format(\DateTime::ATOM)),
|
||||||
'accessed' => $uploadInfo->hasBeenAccessed() ? date('c', $uploadInfo->getLastAccessed()) : null,
|
'accessed' => $uploadInfo->hasBeenAccessed() ? str_replace('+00:00', 'Z', $uploadInfo->getAccessedAt()->format(\DateTime::ATOM)) : null,
|
||||||
'expires' => $uploadInfo->hasExpired() ? date('c', $uploadInfo->getExpires()) : null,
|
'expires' => $uploadInfo->hasExpired() ? str_replace('+00:00', 'Z', $uploadInfo->getExpiredAt()->format(\DateTime::ATOM)) : null,
|
||||||
'deleted' => $uploadInfo->isDeleted() ? date('c', $uploadInfo->getDeleted()) : null,
|
|
||||||
'dmca' => $uploadInfo->isDMCA() ? date('c', $uploadInfo->getDMCA()) : null,
|
// These can never be reached, and in situation where they technically could it's because of an outdated local record
|
||||||
|
'deleted' => null,
|
||||||
|
'dmca' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +80,9 @@ $router->use('/', function($response, $request) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if($isApiDomain) {
|
if($isApiDomain) {
|
||||||
|
// this is illegal, don't do this
|
||||||
|
$userInfo = null;
|
||||||
|
|
||||||
$router->use('/', function($response, $request) {
|
$router->use('/', function($response, $request) {
|
||||||
if($request->hasHeader('Origin'))
|
if($request->hasHeader('Origin'))
|
||||||
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
||||||
|
@ -88,6 +97,8 @@ if($isApiDomain) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->use('/', function($response, $request) use ($db, $cfg) {
|
$router->use('/', function($response, $request) use ($db, $cfg) {
|
||||||
|
global $userInfo, $eeprom;
|
||||||
|
|
||||||
$auth = $request->getHeaderLine('Authorization');
|
$auth = $request->getHeaderLine('Authorization');
|
||||||
if(empty($auth)) {
|
if(empty($auth)) {
|
||||||
$mszAuth = (string)$request->getCookie('msz_auth');
|
$mszAuth = (string)$request->getCookie('msz_auth');
|
||||||
|
@ -111,7 +122,7 @@ if($isApiDomain) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($authUserId) && $authUserId > 0)
|
if(isset($authUserId) && $authUserId > 0)
|
||||||
User::byId($db, $authUserId)->setActive();
|
$userInfo = $eeprom->getUsersContext()->getUser($authUserId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,22 +164,22 @@ if($isApiDomain) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->post('/uploads', function($response, $request) use ($db) {
|
$router->post('/uploads', function($response, $request) use ($db) {
|
||||||
|
global $userInfo, $eeprom;
|
||||||
|
|
||||||
if(!$request->isFormContent())
|
if(!$request->isFormContent())
|
||||||
return 400;
|
return 400;
|
||||||
|
|
||||||
$content = $request->getContent();
|
$content = $request->getContent();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$appInfo = Application::byId($db, (int)$content->getParam('src', FILTER_VALIDATE_INT));
|
$appInfo = $eeprom->getAppsContext()->getApp($content->getParam('src', FILTER_VALIDATE_INT));
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!User::hasActive())
|
if($userInfo === null)
|
||||||
return 401;
|
return 401;
|
||||||
|
|
||||||
$userInfo = User::active();
|
|
||||||
|
|
||||||
if($userInfo->isRestricted())
|
if($userInfo->isRestricted())
|
||||||
return 403;
|
return 403;
|
||||||
|
|
||||||
|
@ -178,9 +189,9 @@ if($isApiDomain) {
|
||||||
return 400;
|
return 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
$maxFileSize = $appInfo->getSizeLimit();
|
$maxFileSize = $appInfo->getDataSizeLimit();
|
||||||
if($appInfo->allowSizeMultiplier())
|
if($appInfo->allowSizeMultiplier())
|
||||||
$maxFileSize *= $userInfo->getSizeMultiplier();
|
$maxFileSize *= $userInfo->getDataSizeMultiplier();
|
||||||
|
|
||||||
$localFile = $file->getLocalFileName();
|
$localFile = $file->getLocalFileName();
|
||||||
$fileSize = filesize($localFile);
|
$fileSize = filesize($localFile);
|
||||||
|
@ -191,34 +202,38 @@ if($isApiDomain) {
|
||||||
return 413;
|
return 413;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$uploadsCtx = $eeprom->getUploadsContext();
|
||||||
|
$uploadsData = $uploadsCtx->getUploadsData();
|
||||||
|
|
||||||
$hash = hash_file('sha256', $localFile);
|
$hash = hash_file('sha256', $localFile);
|
||||||
|
|
||||||
// this is stupid: dmca status is stored as a file record rather than in a separate table requiring this hack ass garbage
|
// this is stupid: dmca status is stored as a file record rather than in a separate table requiring this hack ass garbage
|
||||||
$uploadInfo = Upload::byAppUserHash($db, $appInfo, $userInfo, $hash) ?? Upload::byHash($db, $hash);
|
$uploadInfo = $uploadsData->getUpload(appInfo: $appInfo, userInfo: $userInfo, hashString: $hash)
|
||||||
|
?? $uploadsData->getUpload(hashString: $hash);
|
||||||
|
|
||||||
if($uploadInfo !== null) {
|
if($uploadInfo !== null) {
|
||||||
if($uploadInfo->isDMCA())
|
if($uploadInfo->isCopyrightTakedown())
|
||||||
return 451;
|
return 451;
|
||||||
|
|
||||||
if($uploadInfo->getUserId() !== $userInfo->getId()
|
if($uploadInfo->getUserId() !== $userInfo->getId()
|
||||||
|| $uploadInfo->getApplicationId() !== $appInfo->getId())
|
|| $uploadInfo->getAppId() !== $appInfo->getId())
|
||||||
unset($uploadInfo);
|
unset($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($uploadInfo)) {
|
if(empty($uploadInfo)) {
|
||||||
if($uploadInfo->isDeleted())
|
$uploadInfo = $uploadsData->createUpload(
|
||||||
$uploadInfo->restore($db);
|
$appInfo, $userInfo, $_SERVER['REMOTE_ADDR'],
|
||||||
$uploadInfo->bumpExpiry($db);
|
$file->getSuggestedFileName(), mime_content_type($localFile),
|
||||||
} else {
|
$fileSize, $hash, $appInfo->getBumpAmount(), true
|
||||||
$uploadInfo = Upload::create(
|
|
||||||
$db, $appInfo, $userInfo,
|
|
||||||
$file->getSuggestedFileName(),
|
|
||||||
mime_content_type($localFile),
|
|
||||||
$fileSize, $hash,
|
|
||||||
$appInfo->getExpiry(), true
|
|
||||||
);
|
);
|
||||||
|
$filePath = $uploadsCtx->getFileDataPath($uploadInfo);
|
||||||
|
$file->moveTo($filePath);
|
||||||
|
} else {
|
||||||
|
$filePath = $uploadsCtx->getFileDataPath($uploadInfo);
|
||||||
|
if($uploadInfo->isDeleted())
|
||||||
|
$uploadsData->restoreUpload($uploadInfo);
|
||||||
|
|
||||||
$file->moveTo($uploadInfo->getPath());
|
$uploadsData->bumpUploadExpires($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->setStatusCode(201);
|
$response->setStatusCode(201);
|
||||||
|
@ -228,14 +243,20 @@ if($isApiDomain) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) {
|
$router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) {
|
||||||
try {
|
global $userInfo, $eeprom;
|
||||||
$uploadInfo = Upload::byId($db, $fileId);
|
|
||||||
} catch(RuntimeException $ex) {
|
if($userInfo === null)
|
||||||
|
return 401;
|
||||||
|
|
||||||
|
$uploadsData = $eeprom->getUploadsContext()->getUploadsData();
|
||||||
|
|
||||||
|
$uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
|
||||||
|
if($uploadInfo === null) {
|
||||||
$response->setContent('File not found.');
|
$response->setContent('File not found.');
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($uploadInfo->isDMCA()) {
|
if($uploadInfo->isCopyrightTakedown()) {
|
||||||
$response->setContent('File is unavailable for copyright reasons.');
|
$response->setContent('File is unavailable for copyright reasons.');
|
||||||
return 451;
|
return 451;
|
||||||
}
|
}
|
||||||
|
@ -245,17 +266,16 @@ if($isApiDomain) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!User::hasActive())
|
if($userInfo->isRestricted() || $userInfo->getId() !== $uploadInfo->getUserId())
|
||||||
return 401;
|
|
||||||
|
|
||||||
if(User::active()->isRestricted() || User::active()->getId() !== $uploadInfo->getUserId())
|
|
||||||
return 403;
|
return 403;
|
||||||
|
|
||||||
$uploadInfo->delete($db, false);
|
$uploadsData->deleteUpload($uploadInfo);
|
||||||
return 204;
|
return 204;
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->get('/uploads/:filename', function($response, $request, $fileName) use ($db) {
|
$router->get('/uploads/:filename', function($response, $request, $fileName) use ($db) {
|
||||||
|
global $eeprom;
|
||||||
|
|
||||||
$pathInfo = pathinfo($fileName);
|
$pathInfo = pathinfo($fileName);
|
||||||
$fileId = $pathInfo['filename'];
|
$fileId = $pathInfo['filename'];
|
||||||
$fileExt = $pathInfo['extension'] ?? '';
|
$fileExt = $pathInfo['extension'] ?? '';
|
||||||
|
@ -265,17 +285,16 @@ if($isApiDomain) {
|
||||||
if($fileExt !== '' && $fileExt !== 't' && $fileExt !== 'json')
|
if($fileExt !== '' && $fileExt !== 't' && $fileExt !== 'json')
|
||||||
return 404;
|
return 404;
|
||||||
|
|
||||||
try {
|
$uploadsCtx = $eeprom->getUploadsContext();
|
||||||
$uploadInfo = Upload::byId($db, $fileId);
|
$uploadsData = $uploadsCtx->getUploadsData();
|
||||||
} catch(RuntimeException $ex) {
|
|
||||||
|
$uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
|
||||||
|
if($uploadInfo === null) {
|
||||||
$response->setContent('File not found.');
|
$response->setContent('File not found.');
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($isJson)
|
if($uploadInfo->isCopyrightTakedown()) {
|
||||||
return eepromUploadInfo($uploadInfo);
|
|
||||||
|
|
||||||
if($uploadInfo->isDMCA()) {
|
|
||||||
$response->setContent('File is unavailable for copyright reasons.');
|
$response->setContent('File is unavailable for copyright reasons.');
|
||||||
return 451;
|
return 451;
|
||||||
}
|
}
|
||||||
|
@ -285,32 +304,38 @@ if($isApiDomain) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!is_file($uploadInfo->getPath())) {
|
if($isJson)
|
||||||
|
return eepromUploadInfo($uploadInfo);
|
||||||
|
|
||||||
|
$filePath = $uploadsCtx->getFileDataPath($uploadInfo);
|
||||||
|
if(!is_file($filePath)) {
|
||||||
$response->setContent('Data is missing.');
|
$response->setContent('Data is missing.');
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$isThumbnail) {
|
if(!$isThumbnail) {
|
||||||
$uploadInfo->bumpAccess($db);
|
$uploadsData->bumpUploadAccess($uploadInfo);
|
||||||
$uploadInfo->bumpExpiry($db);
|
$uploadsData->bumpUploadExpires($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileName = $uploadInfo->getName();
|
$fileName = $uploadInfo->getName();
|
||||||
$contentType = $uploadInfo->getType();
|
$contentType = $uploadInfo->getMediaTypeString();
|
||||||
|
|
||||||
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
||||||
$contentType = 'text/plain';
|
$contentType = 'text/plain';
|
||||||
|
|
||||||
$sourceDir = basename($isThumbnail ? PRM_THUMBS : PRM_UPLOADS);
|
if($isThumbnail) {
|
||||||
|
if(!$uploadsCtx->supportsThumbnailing($uploadInfo))
|
||||||
|
return 404;
|
||||||
|
|
||||||
if($isThumbnail && $uploadInfo->supportsThumbnail()) {
|
|
||||||
if(!is_file($uploadInfo->getThumbPath()))
|
|
||||||
$uploadInfo->createThumbnail();
|
|
||||||
$contentType = 'image/jpeg';
|
$contentType = 'image/jpeg';
|
||||||
|
$accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo);
|
||||||
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
||||||
|
} else {
|
||||||
|
$accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->accelRedirect(sprintf('/%s/%s', $sourceDir, $uploadInfo->getId()));
|
$response->accelRedirect($accelRedirectPath);
|
||||||
$response->setContentType($contentType);
|
$response->setContentType($contentType);
|
||||||
$response->setFileName(addslashes($fileName));
|
$response->setFileName(addslashes($fileName));
|
||||||
});
|
});
|
||||||
|
@ -323,6 +348,8 @@ if($isApiDomain) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->get('/:filename', function($response, $request, $fileName) use ($db) {
|
$router->get('/:filename', function($response, $request, $fileName) use ($db) {
|
||||||
|
global $eeprom;
|
||||||
|
|
||||||
$pathInfo = pathinfo($fileName);
|
$pathInfo = pathinfo($fileName);
|
||||||
$fileId = $pathInfo['filename'];
|
$fileId = $pathInfo['filename'];
|
||||||
$fileExt = $pathInfo['extension'] ?? '';
|
$fileExt = $pathInfo['extension'] ?? '';
|
||||||
|
@ -331,14 +358,16 @@ if($isApiDomain) {
|
||||||
if($fileExt !== '' && $fileExt !== 't')
|
if($fileExt !== '' && $fileExt !== 't')
|
||||||
return 404;
|
return 404;
|
||||||
|
|
||||||
try {
|
$uploadsCtx = $eeprom->getUploadsContext();
|
||||||
$uploadInfo = Upload::byId($db, $fileId);
|
$uploadsData = $uploadsCtx->getUploadsData();
|
||||||
} catch(RuntimeException $ex) {
|
|
||||||
|
$uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
|
||||||
|
if($uploadInfo === null) {
|
||||||
$response->setContent('File not found.');
|
$response->setContent('File not found.');
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($uploadInfo->isDMCA()) {
|
if($uploadInfo->isCopyrightTakedown()) {
|
||||||
$response->setContent('File is unavailable for copyright reasons.');
|
$response->setContent('File is unavailable for copyright reasons.');
|
||||||
return 451;
|
return 451;
|
||||||
}
|
}
|
||||||
|
@ -348,32 +377,35 @@ if($isApiDomain) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!is_file($uploadInfo->getPath())) {
|
$filePath = $uploadsCtx->getFileDataPath($uploadInfo);
|
||||||
|
if(!is_file($filePath)) {
|
||||||
$response->setContent('Data is missing.');
|
$response->setContent('Data is missing.');
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$isThumbnail) {
|
if(!$isThumbnail) {
|
||||||
$uploadInfo->bumpAccess($db);
|
$uploadsData->bumpUploadAccess($uploadInfo);
|
||||||
$uploadInfo->bumpExpiry($db);
|
$uploadsData->bumpUploadExpires($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileName = $uploadInfo->getName();
|
$fileName = $uploadInfo->getName();
|
||||||
$contentType = $uploadInfo->getType();
|
$contentType = $uploadInfo->getMediaTypeString();
|
||||||
|
|
||||||
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
||||||
$contentType = 'text/plain';
|
$contentType = 'text/plain';
|
||||||
|
|
||||||
$sourceDir = basename($isThumbnail ? PRM_THUMBS : PRM_UPLOADS);
|
if($isThumbnail) {
|
||||||
|
if(!$uploadsCtx->supportsThumbnailing($uploadInfo))
|
||||||
|
return 404;
|
||||||
|
|
||||||
if($isThumbnail && $uploadInfo->supportsThumbnail()) {
|
|
||||||
if(!is_file($uploadInfo->getThumbPath()))
|
|
||||||
$uploadInfo->createThumbnail();
|
|
||||||
$contentType = 'image/jpeg';
|
$contentType = 'image/jpeg';
|
||||||
|
$accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo);
|
||||||
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
||||||
|
} else {
|
||||||
|
$accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->accelRedirect(sprintf('/%s/%s', $sourceDir, $uploadInfo->getId()));
|
$response->accelRedirect($accelRedirectPath);
|
||||||
$response->setContentType($contentType);
|
$response->setContentType($contentType);
|
||||||
$response->setFileName(addslashes($fileName));
|
$response->setFileName(addslashes($fileName));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace EEPROM;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
use Index\Data\IDbConnection;
|
|
||||||
|
|
||||||
final class Application {
|
|
||||||
public function __construct(
|
|
||||||
private int $id = 0,
|
|
||||||
private string $name = '',
|
|
||||||
private int $created = 0,
|
|
||||||
private int $sizeLimit = -1,
|
|
||||||
private bool $allowSizeMultiplier = false,
|
|
||||||
private int $expiry = -1,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string {
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): int {
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSizeLimit(): int {
|
|
||||||
return $this->sizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function allowSizeMultiplier(): bool {
|
|
||||||
return $this->allowSizeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpiry(): int {
|
|
||||||
return $this->expiry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function byId(IDbConnection $conn, int $appId): self {
|
|
||||||
$get = $conn->prepare(
|
|
||||||
'SELECT `app_id`, `app_name`, `app_size_limit`, `app_expiry`, `app_allow_size_multiplier`,'
|
|
||||||
. ' UNIX_TIMESTAMP(`app_created`) AS `app_created` FROM `prm_applications` WHERE `app_id` = ?'
|
|
||||||
);
|
|
||||||
$get->addParameter(1, $appId);
|
|
||||||
$get->execute();
|
|
||||||
$result = $get->getResult();
|
|
||||||
|
|
||||||
if(!$result->next())
|
|
||||||
throw new RuntimeException('Application $appId not found.');
|
|
||||||
|
|
||||||
return new static(
|
|
||||||
$result->getInteger(0),
|
|
||||||
$result->getString(1),
|
|
||||||
$result->getInteger(5),
|
|
||||||
$result->getInteger(2),
|
|
||||||
$result->getInteger(4) !== 0,
|
|
||||||
$result->getInteger(3),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
51
src/Apps/AppInfo.php
Normal file
51
src/Apps/AppInfo.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Apps;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
|
||||||
|
class AppInfo {
|
||||||
|
private string $id;
|
||||||
|
private string $name;
|
||||||
|
private int $created;
|
||||||
|
private int $sizeLimit;
|
||||||
|
private bool $allowSizeMultiplier;
|
||||||
|
private int $expiry;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->id = $result->getString(0);
|
||||||
|
$this->name = $result->getString(1);
|
||||||
|
$this->created = $result->getInteger(2);
|
||||||
|
$this->sizeLimit = $result->getInteger(3);
|
||||||
|
$this->allowSizeMultiplier = $result->getBoolean(4);
|
||||||
|
$this->expiry = $result->getInteger(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTime {
|
||||||
|
return DateTime::fromUnixTimeSeconds($this->created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataSizeLimit(): int {
|
||||||
|
return $this->sizeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allowSizeMultiplier(): bool {
|
||||||
|
return $this->allowSizeMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBumpAmount(): int {
|
||||||
|
return $this->expiry;
|
||||||
|
}
|
||||||
|
}
|
25
src/Apps/AppsContext.php
Normal file
25
src/Apps/AppsContext.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Apps;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
|
||||||
|
class AppsContext {
|
||||||
|
private AppsData $appsData;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->appsData = new AppsData($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAppsData(): AppsData {
|
||||||
|
return $this->appsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApp(string $appId): AppInfo {
|
||||||
|
$appInfo = $this->appsData->getApp($appId);
|
||||||
|
if($appInfo === null)
|
||||||
|
throw new RuntimeException('Not application with this ID exists.');
|
||||||
|
|
||||||
|
return $appInfo;
|
||||||
|
}
|
||||||
|
}
|
24
src/Apps/AppsData.php
Normal file
24
src/Apps/AppsData.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Apps;
|
||||||
|
|
||||||
|
use Index\Data\DbStatementCache;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
|
||||||
|
// this system is to be deprecated and replaced by pools system
|
||||||
|
|
||||||
|
class AppsData {
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApp(string $appId): ?AppInfo {
|
||||||
|
$stmt = $this->cache->get('SELECT app_id, app_name, UNIX_TIMESTAMP(app_created), app_size_limit, app_allow_size_multiplier, app_expiry FROM prm_applications WHERE app_id = ?');
|
||||||
|
$stmt->addParameter(1, $appId);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
return $result->next() ? new AppInfo($result) : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,9 @@ class MisuzuAuth implements IAuth {
|
||||||
public function getName(): string { return 'Misuzu'; }
|
public function getName(): string { return 'Misuzu'; }
|
||||||
|
|
||||||
public function verifyToken(string $token): int {
|
public function verifyToken(string $token): int {
|
||||||
if(!empty($token)) {
|
if(empty($token))
|
||||||
|
return 0;
|
||||||
|
|
||||||
$method = 'Misuzu';
|
$method = 'Misuzu';
|
||||||
$signature = sprintf('verify#%s#%s#%s', $method, $token, $_SERVER['REMOTE_ADDR']);
|
$signature = sprintf('verify#%s#%s#%s', $method, $token, $_SERVER['REMOTE_ADDR']);
|
||||||
$signature = hash_hmac('sha256', $signature, $this->secretKey);
|
$signature = hash_hmac('sha256', $signature, $this->secretKey);
|
||||||
|
@ -51,9 +53,6 @@ class MisuzuAuth implements IAuth {
|
||||||
$userInfo = json_decode($rawUserInfo);
|
$userInfo = json_decode($rawUserInfo);
|
||||||
curl_close($login);
|
curl_close($login);
|
||||||
|
|
||||||
return empty($userInfo->success) ? 0 : $userInfo->user_id;
|
return empty($userInfo->success) || empty($userInfo->user_id) ? 0 : $userInfo->user_id;
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,17 @@ class EEPROMContext {
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private DatabaseContext $dbCtx;
|
private DatabaseContext $dbCtx;
|
||||||
|
|
||||||
|
private Apps\AppsContext $appsCtx;
|
||||||
|
private Uploads\UploadsContext $uploadsCtx;
|
||||||
|
private Users\UsersContext $usersCtx;
|
||||||
|
|
||||||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->dbCtx = new DatabaseContext($dbConn);
|
$this->dbCtx = new DatabaseContext($dbConn);
|
||||||
|
|
||||||
|
$this->appsCtx = new Apps\AppsContext($dbConn);
|
||||||
|
$this->uploadsCtx = new Uploads\UploadsContext($config, $dbConn);
|
||||||
|
$this->usersCtx = new Users\UsersContext($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConfig(): IConfig {
|
public function getConfig(): IConfig {
|
||||||
|
@ -20,4 +28,16 @@ class EEPROMContext {
|
||||||
public function getDatabase(): DatabaseContext {
|
public function getDatabase(): DatabaseContext {
|
||||||
return $this->dbCtx;
|
return $this->dbCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAppsContext(): Apps\AppsContext {
|
||||||
|
return $this->appsCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploadsContext(): Uploads\UploadsContext {
|
||||||
|
return $this->uploadsCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsersContext(): Users\UsersContext {
|
||||||
|
return $this->usersCtx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
21
src/FFMPEG.php
Normal file
21
src/FFMPEG.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM;
|
||||||
|
|
||||||
|
use Index\IO\Stream;
|
||||||
|
use Index\IO\ProcessStream;
|
||||||
|
|
||||||
|
final class FFMPEG {
|
||||||
|
public static function grabVideoFrame(string $path): Stream {
|
||||||
|
return new ProcessStream(
|
||||||
|
sprintf('ffmpeg -i %s -ss 00:00:01.000 -vframes 1 -c:v png -f image2pipe -', escapeshellarg($path)),
|
||||||
|
'rb'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function grabAudioCover(string $path): Stream {
|
||||||
|
return new ProcessStream(
|
||||||
|
sprintf('ffmpeg -i %s -an -vcodec copy -f image2pipe -', escapeshellarg($path)),
|
||||||
|
'rb'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
428
src/Upload.php
428
src/Upload.php
|
@ -1,428 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace EEPROM;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use RuntimeException;
|
|
||||||
use Imagick;
|
|
||||||
use Index\XString;
|
|
||||||
use Index\Data\IDbConnection;
|
|
||||||
use Index\Data\IDbResult;
|
|
||||||
|
|
||||||
final class Upload {
|
|
||||||
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',
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getId(): string {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPath(): string {
|
|
||||||
return PRM_UPLOADS . '/' . $this->id;
|
|
||||||
}
|
|
||||||
public function getThumbPath(): string {
|
|
||||||
return PRM_THUMBS . '/' . $this->id;
|
|
||||||
}
|
|
||||||
public function getRemotePath(): string {
|
|
||||||
return '/uploads/' . $this->id;
|
|
||||||
}
|
|
||||||
public function getPublicUrl(bool $forceReal = false): string {
|
|
||||||
global $cfg;
|
|
||||||
|
|
||||||
if(!$forceReal && $cfg->hasValues('domain:short'))
|
|
||||||
return '//' . $cfg->getString('domain:short') . '/' . $this->id;
|
|
||||||
return '//' . $_SERVER['HTTP_HOST'] . $this->getRemotePath();
|
|
||||||
}
|
|
||||||
public function getPublicThumbUrl(bool $forceReal = false): string {
|
|
||||||
return $this->getPublicUrl($forceReal) . '.t';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): int {
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getApplicationId(): int {
|
|
||||||
return $this->appId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getType(): string {
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
public function isImage(): bool {
|
|
||||||
return str_starts_with($this->type, 'image/');
|
|
||||||
}
|
|
||||||
public function isVideo(): bool {
|
|
||||||
return str_starts_with($this->type, 'video/');
|
|
||||||
}
|
|
||||||
public function isAudio(): bool {
|
|
||||||
return str_starts_with($this->type, 'audio/');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string {
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSize(): int {
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHash(): string {
|
|
||||||
return $this->hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): int {
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasBeenAccessed(): bool {
|
|
||||||
return $this->accessed < 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastAccessed(): int {
|
|
||||||
return $this->accessed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function bumpAccess(IDbConnection $conn): void {
|
|
||||||
if(empty($this->id))
|
|
||||||
return;
|
|
||||||
$this->accessed = time();
|
|
||||||
|
|
||||||
$bump = $conn->prepare('UPDATE `prm_uploads` SET `upload_accessed` = NOW() WHERE `upload_id` = ?');
|
|
||||||
$bump->addParameter(1, $this->id);
|
|
||||||
$bump->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpires(): int {
|
|
||||||
return $this->expires;
|
|
||||||
}
|
|
||||||
public function hasExpired(): bool {
|
|
||||||
return $this->expires > 1 && $this->expires <= time();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDeleted(): int {
|
|
||||||
return $this->deleted;
|
|
||||||
}
|
|
||||||
public function isDeleted(): bool {
|
|
||||||
return $this->deleted > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDMCA(): int {
|
|
||||||
return $this->dmca;
|
|
||||||
}
|
|
||||||
public function isDMCA(): bool {
|
|
||||||
return $this->dmca > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpiryBump(): int {
|
|
||||||
return $this->bump;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRemoteAddress(): string {
|
|
||||||
return $this->ipAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function bumpExpiry(IDbConnection $conn): void {
|
|
||||||
if(empty($this->id) || $this->expires < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if($this->bump < 1)
|
|
||||||
return;
|
|
||||||
$this->expires = time() + $this->bump;
|
|
||||||
|
|
||||||
$bump = $conn->prepare('UPDATE `prm_uploads` SET `upload_expires` = NOW() + INTERVAL ? SECOND WHERE `upload_id` = ?');
|
|
||||||
$bump->addParameter(1, $this->bump);
|
|
||||||
$bump->addParameter(2, $this->id);
|
|
||||||
$bump->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restore(IDbConnection $conn): void {
|
|
||||||
$this->deleted = 0;
|
|
||||||
|
|
||||||
$restore = $conn->prepare('UPDATE `prm_uploads` SET `upload_deleted` = NULL WHERE `upload_id` = ?');
|
|
||||||
$restore->addParameter(1, $this->id);
|
|
||||||
$restore->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(IDbConnection $conn, bool $hard): void {
|
|
||||||
$this->deleted = time();
|
|
||||||
|
|
||||||
if($hard) {
|
|
||||||
if(is_file($this->getPath()))
|
|
||||||
unlink($this->getPath());
|
|
||||||
if(is_file($this->getThumbPath()))
|
|
||||||
unlink($this->getThumbPath());
|
|
||||||
|
|
||||||
if($this->dmca < 1) {
|
|
||||||
$delete = $conn->prepare('DELETE FROM `prm_uploads` WHERE `upload_id` = ?');
|
|
||||||
$delete->addParameter(1, $this->id);
|
|
||||||
$delete->execute();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$delete = $conn->prepare('UPDATE `prm_uploads` SET `upload_deleted` = NOW() WHERE `upload_id` = ?');
|
|
||||||
$delete->addParameter(1, $this->id);
|
|
||||||
$delete->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function create(
|
|
||||||
IDbConnection $conn,
|
|
||||||
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)
|
|
||||||
throw new InvalidArgumentException('$fileType must contain a /');
|
|
||||||
if($fileSize < 1)
|
|
||||||
throw new InvalidArgumentException('$fileSize must be more than 0.');
|
|
||||||
if(strlen($fileHash) !== 64)
|
|
||||||
throw new InvalidArgumentException('$fileHash must be 64 characters.');
|
|
||||||
if($fileExpiry < 0)
|
|
||||||
throw new InvalidArgumentException('$fileExpiry must be a positive integer.');
|
|
||||||
|
|
||||||
$id = XString::random(32);
|
|
||||||
$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);
|
|
||||||
$create->addParameter(2, $appId < 1 ? null : $appId);
|
|
||||||
$create->addParameter(3, $userId < 1 ? null : $userId);
|
|
||||||
$create->addParameter(4, $fileName);
|
|
||||||
$create->addParameter(5, $fileType);
|
|
||||||
$create->addParameter(6, $fileSize);
|
|
||||||
$create->addParameter(7, $fileHash);
|
|
||||||
$create->addParameter(8, $_SERVER['REMOTE_ADDR']);
|
|
||||||
$create->addParameter(9, $fileExpiry > 0 ? (time() + $fileExpiry) : 0);
|
|
||||||
$create->addParameter(10, $bumpExpiry ? $fileExpiry : 0);
|
|
||||||
$create->execute();
|
|
||||||
|
|
||||||
return self::byId($conn, $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
$get->execute();
|
|
||||||
$result = $get->getResult();
|
|
||||||
|
|
||||||
if(!$result->next())
|
|
||||||
throw new RuntimeException('Upload $id not found.');
|
|
||||||
|
|
||||||
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(?)'
|
|
||||||
);
|
|
||||||
$get->addParameter(1, $hash);
|
|
||||||
$get->execute();
|
|
||||||
$result = $get->getResult();
|
|
||||||
|
|
||||||
if(!$result->next())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return self::constructDb($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function byAppUserHash(
|
|
||||||
IDbConnection $conn,
|
|
||||||
Application|string $appInfo,
|
|
||||||
User|string $userInfo,
|
|
||||||
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(?) AND `user_id` = ? AND `app_id` = ?'
|
|
||||||
);
|
|
||||||
$get->addParameter(1, $hash);
|
|
||||||
$get->addParameter(2, $userInfo instanceof User ? $userInfo->getId() : $userInfo);
|
|
||||||
$get->addParameter(3, $appInfo instanceof Application ? $appInfo->getId() : $appInfo);
|
|
||||||
$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'
|
|
||||||
);
|
|
||||||
|
|
||||||
$deleted = [];
|
|
||||||
|
|
||||||
while($result->next())
|
|
||||||
$deleted[] = self::constructDb($result);
|
|
||||||
|
|
||||||
return $deleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
$resizeWidth = $width * $thumbRes / $height;
|
|
||||||
$resizeHeight = $thumbRes;
|
|
||||||
} else {
|
|
||||||
$resizeWidth = $thumbRes;
|
|
||||||
$resizeHeight = $height * $thumbRes / $width;
|
|
||||||
}
|
|
||||||
|
|
||||||
$resizeWidth = (int)$resizeWidth;
|
|
||||||
$resizeHeight = (int)$resizeHeight;
|
|
||||||
|
|
||||||
$imagick->resizeImage(
|
|
||||||
$resizeWidth, $resizeHeight,
|
|
||||||
Imagick::FILTER_GAUSSIAN, 0.7
|
|
||||||
);
|
|
||||||
|
|
||||||
$imagick->cropImage(
|
|
||||||
$thumbRes,
|
|
||||||
$thumbRes,
|
|
||||||
(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);
|
|
||||||
}
|
|
||||||
}
|
|
165
src/Uploads/UploadInfo.php
Normal file
165
src/Uploads/UploadInfo.php
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Uploads;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\MediaType;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Index\Net\IPAddress;
|
||||||
|
|
||||||
|
class UploadInfo {
|
||||||
|
private string $id;
|
||||||
|
private string $userId;
|
||||||
|
private string $appId;
|
||||||
|
private string $hash;
|
||||||
|
private string $remoteAddr;
|
||||||
|
private int $created;
|
||||||
|
private ?int $accessed;
|
||||||
|
private ?int $expires;
|
||||||
|
private ?int $deleted;
|
||||||
|
private ?int $dmca;
|
||||||
|
private int $bump;
|
||||||
|
private string $name;
|
||||||
|
private string $type;
|
||||||
|
private int $size;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->id = $result->getString(0);
|
||||||
|
$this->userId = $result->getStringOrNull(1);
|
||||||
|
$this->appId = $result->getStringOrNull(2);
|
||||||
|
$this->hash = $result->getString(3);
|
||||||
|
$this->remoteAddr = $result->getString(4);
|
||||||
|
$this->created = $result->getInteger(5);
|
||||||
|
$this->accessed = $result->getIntegerOrNull(6);
|
||||||
|
$this->expires = $result->getIntegerOrNull(7);
|
||||||
|
$this->deleted = $result->getIntegerOrNull(8);
|
||||||
|
$this->dmca = $result->getIntegerOrNull(9);
|
||||||
|
$this->bump = $result->getInteger(10);
|
||||||
|
$this->name = $result->getString(11);
|
||||||
|
$this->type = $result->getString(12);
|
||||||
|
$this->size = $result->getInteger(13);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasUserId(): bool {
|
||||||
|
return $this->userId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAppId(): bool {
|
||||||
|
return $this->appId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAppId(): ?string {
|
||||||
|
return $this->appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHashString(): string {
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemoteAddressRaw(): string {
|
||||||
|
return $this->remoteAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemoteAddress(): IPAddress {
|
||||||
|
return IPAddress::parse($this->remoteAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTime {
|
||||||
|
return DateTime::fromUnixTimeSeconds($this->created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasBeenAccessed(): bool {
|
||||||
|
return $this->accessed !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessedTime(): ?int {
|
||||||
|
return $this->accessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessedAt(): ?DateTime {
|
||||||
|
return $this->accessed === null ? null : DateTime::fromUnixTimeSeconds($this->accessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasExpiryTime(): bool {
|
||||||
|
return $this->expires !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasExpired(): bool {
|
||||||
|
return $this->expires !== null && $this->expires <= time();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiredTime(): ?int {
|
||||||
|
return $this->expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiredAt(): ?DateTime {
|
||||||
|
return $this->expires === null ? null : DateTime::fromUnixTimeSeconds($this->expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDeleted(): bool {
|
||||||
|
return $this->deleted !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeletedTime(): ?int {
|
||||||
|
return $this->deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeletedAt(): ?DateTime {
|
||||||
|
return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCopyrightTakedown(): bool {
|
||||||
|
return $this->dmca !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCopyrightTakedownTime(): ?int {
|
||||||
|
return $this->dmca;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCopyrightTakedownAt(): ?DateTime {
|
||||||
|
return $this->dmca === null ? null : DateTime::fromUnixTimeSeconds($this->dmca);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBumpAmount(): int {
|
||||||
|
return $this->bump;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMediaTypeString(): string {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMediaType(): MediaType {
|
||||||
|
return MediaType::parse($this->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isImage(): bool {
|
||||||
|
return str_starts_with($this->type, 'image/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isVideo(): bool {
|
||||||
|
return str_starts_with($this->type, 'video/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAudio(): bool {
|
||||||
|
return str_starts_with($this->type, 'audio/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataSize(): int {
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
}
|
141
src/Uploads/UploadsContext.php
Normal file
141
src/Uploads/UploadsContext.php
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Uploads;
|
||||||
|
|
||||||
|
use Imagick;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Syokuhou\IConfig;
|
||||||
|
use EEPROM\FFMPEG;
|
||||||
|
|
||||||
|
class UploadsContext {
|
||||||
|
private IConfig $config;
|
||||||
|
private UploadsData $uploadsData;
|
||||||
|
|
||||||
|
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||||
|
$this->config = $config;
|
||||||
|
$this->uploadsData = new UploadsData($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploadsData(): UploadsData {
|
||||||
|
return $this->uploadsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileDataPath(UploadInfo $uploadInfo): string {
|
||||||
|
return sprintf('%s%s%s', PRM_UPLOADS, DIRECTORY_SEPARATOR, $uploadInfo->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailDataPath(UploadInfo $uploadInfo): string {
|
||||||
|
return sprintf('%s%s%s', PRM_THUMBS, DIRECTORY_SEPARATOR, $uploadInfo->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileDataRedirectPath(UploadInfo $uploadInfo): string {
|
||||||
|
return sprintf('%s%s%s', str_replace(PRM_PUBLIC, '', PRM_UPLOADS), DIRECTORY_SEPARATOR, $uploadInfo->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailDataRedirectPath(UploadInfo $uploadInfo): string {
|
||||||
|
return sprintf('%s%s%s', str_replace(PRM_PUBLIC, '', PRM_THUMBS), DIRECTORY_SEPARATOR, $uploadInfo->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailDataPathOrCreate(UploadInfo $uploadInfo): string {
|
||||||
|
if(!$this->supportsThumbnailing($uploadInfo))
|
||||||
|
return '';
|
||||||
|
|
||||||
|
$thumbPath = $this->getThumbnailDataPath($uploadInfo);
|
||||||
|
if(is_file($thumbPath))
|
||||||
|
return $thumbPath;
|
||||||
|
|
||||||
|
return $this->createThumbnailInternal(
|
||||||
|
$uploadInfo,
|
||||||
|
$this->getFileDataPath($uploadInfo),
|
||||||
|
$thumbPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailDataRedirectPathOrCreate(UploadInfo $uploadInfo): string {
|
||||||
|
return str_replace(PRM_PUBLIC, '', $this->getThumbnailDataPathOrCreate($uploadInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileUrlV1(UploadInfo $uploadInfo, bool $forceApiDomain = false): string {
|
||||||
|
if(!$forceApiDomain && $this->config->hasValues('domain:short'))
|
||||||
|
return sprintf('//%s/%s', $this->config->getString('domain:short'), $uploadInfo->getId());
|
||||||
|
|
||||||
|
return sprintf('//%s/uploads/%s', $this->config->getString('domain:api'), $uploadInfo->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailUrlV1(UploadInfo $uploadInfo, bool $forceApiDomain = false): string {
|
||||||
|
return sprintf('%s.t', $this->getFileUrlV1($uploadInfo, $forceApiDomain));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsThumbnailing(UploadInfo $uploadInfo): bool {
|
||||||
|
return $uploadInfo->isImage()
|
||||||
|
|| $uploadInfo->isAudio()
|
||||||
|
|| $uploadInfo->isVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createThumbnail(UploadInfo $uploadInfo): string {
|
||||||
|
if(!$this->supportsThumbnailing($uploadInfo))
|
||||||
|
return '';
|
||||||
|
|
||||||
|
return $this->createThumbnailInternal(
|
||||||
|
$uploadInfo,
|
||||||
|
$this->getFileDataPath($uploadInfo),
|
||||||
|
$this->getThumbnailDataPath($uploadInfo)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createThumbnailInternal(UploadInfo $uploadInfo, string $filePath, string $thumbPath): string {
|
||||||
|
$imagick = new Imagick;
|
||||||
|
|
||||||
|
if($uploadInfo->isImage()) {
|
||||||
|
$imagick->readImageBlob(file_get_contents($filePath));
|
||||||
|
} elseif($uploadInfo->isAudio())
|
||||||
|
$imagick->readImageBlob(FFMPEG::grabAudioCover($filePath));
|
||||||
|
elseif($uploadInfo->isVideo())
|
||||||
|
$imagick->readImageBlob(FFMPEG::grabVideoFrame($filePath));
|
||||||
|
|
||||||
|
$imagick->setImageFormat('jpg');
|
||||||
|
$imagick->setImageCompressionQuality($this->config->getInteger('thumb:quality', 40));
|
||||||
|
|
||||||
|
$thumbRes = $this->config->getInteger('thumb:dimensions', 100);
|
||||||
|
$width = $imagick->getImageWidth();
|
||||||
|
$height = $imagick->getImageHeight();
|
||||||
|
|
||||||
|
if($width === $height) {
|
||||||
|
$resizeWidth = $resizeHeight = $thumbRes;
|
||||||
|
} elseif($width > $height) {
|
||||||
|
$resizeWidth = $width * $thumbRes / $height;
|
||||||
|
$resizeHeight = $thumbRes;
|
||||||
|
} else {
|
||||||
|
$resizeWidth = $thumbRes;
|
||||||
|
$resizeHeight = $height * $thumbRes / $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resizeWidth = (int)$resizeWidth;
|
||||||
|
$resizeHeight = (int)$resizeHeight;
|
||||||
|
|
||||||
|
$imagick->resizeImage(
|
||||||
|
$resizeWidth, $resizeHeight,
|
||||||
|
Imagick::FILTER_GAUSSIAN, 0.7
|
||||||
|
);
|
||||||
|
|
||||||
|
$imagick->cropImage(
|
||||||
|
$thumbRes,
|
||||||
|
$thumbRes,
|
||||||
|
(int)ceil(($resizeWidth - $thumbRes) / 2),
|
||||||
|
(int)ceil(($resizeHeight - $thumbRes) / 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
$imagick->writeImage($thumbPath);
|
||||||
|
|
||||||
|
return is_file($thumbPath) ? $thumbPath : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteUploadData(UploadInfo|string $uploadInfo): void {
|
||||||
|
$filePath = $this->getFileDataPath($uploadInfo);
|
||||||
|
if(is_file($filePath))
|
||||||
|
unlink($filePath);
|
||||||
|
|
||||||
|
$thumbPath = $this->getThumbnailDataPath($uploadInfo);
|
||||||
|
if(is_file($thumbPath))
|
||||||
|
unlink($thumbPath);
|
||||||
|
}
|
||||||
|
}
|
164
src/Uploads/UploadsData.php
Normal file
164
src/Uploads/UploadsData.php
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Uploads;
|
||||||
|
|
||||||
|
use Index\XString;
|
||||||
|
use Index\Data\DbStatementCache;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use EEPROM\Apps\AppInfo;
|
||||||
|
use EEPROM\Users\UserInfo;
|
||||||
|
|
||||||
|
class UploadsData {
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploads(
|
||||||
|
?bool $deleted = null,
|
||||||
|
?bool $expired = null,
|
||||||
|
?bool $dmca = null
|
||||||
|
): array {
|
||||||
|
$hasDeleted = $deleted !== null;
|
||||||
|
$hasExpired = $expired !== null;
|
||||||
|
$hasDMCA = $dmca !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT upload_id, user_id, app_id, LOWER(HEX(upload_hash)), INET6_NTOA(upload_ip), UNIX_TIMESTAMP(upload_created), UNIX_TIMESTAMP(upload_accessed), UNIX_TIMESTAMP(upload_expires), UNIX_TIMESTAMP(upload_deleted), UNIX_TIMESTAMP(upload_dmca), upload_bump, upload_name, upload_type, upload_size FROM prm_uploads';
|
||||||
|
if($hasDeleted) {
|
||||||
|
++$args;
|
||||||
|
$query .= sprintf(' WHERE upload_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||||||
|
}
|
||||||
|
if($hasExpired)
|
||||||
|
$query .= sprintf(' %s upload_expires %s NOW()', ++$args > 1 ? 'AND' : 'WHERE', $expired ? '<=' : '>');
|
||||||
|
if($hasDMCA)
|
||||||
|
$query .= sprintf(' %s upload_dmca %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $dmca ? 'IS NOT' : 'IS');
|
||||||
|
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
$uploads = [];
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$uploads[] = new UploadInfo($result);
|
||||||
|
|
||||||
|
return $uploads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpload(
|
||||||
|
?string $uploadId = null,
|
||||||
|
?string $hashString = null,
|
||||||
|
AppInfo|string|null $appInfo = null,
|
||||||
|
UserInfo|string|null $userInfo = null,
|
||||||
|
): ?UploadInfo {
|
||||||
|
$hasUploadId = $uploadId !== null;
|
||||||
|
$hasHashString = $hashString !== null;
|
||||||
|
$hasAppInfo = $appInfo !== null;
|
||||||
|
$hasUserInfo = $userInfo !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT upload_id, user_id, app_id, LOWER(HEX(upload_hash)), INET6_NTOA(upload_ip), UNIX_TIMESTAMP(upload_created), UNIX_TIMESTAMP(upload_accessed), UNIX_TIMESTAMP(upload_expires), UNIX_TIMESTAMP(upload_deleted), UNIX_TIMESTAMP(upload_dmca), upload_bump, upload_name, upload_type, upload_size FROM prm_uploads';
|
||||||
|
if($hasUploadId) {
|
||||||
|
++$args;
|
||||||
|
$query .= ' WHERE upload_id = ?';
|
||||||
|
}
|
||||||
|
if($hasHashString)
|
||||||
|
$query .= sprintf(' %s upload_hash = UNHEX(?)', ++$args > 1 ? 'AND' : 'WHERE');
|
||||||
|
if($hasAppInfo)
|
||||||
|
$query .= sprintf(' %s app_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||||
|
if($hasUserInfo)
|
||||||
|
$query .= sprintf(' %s user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
if($hasUploadId)
|
||||||
|
$stmt->addParameter(++$args, $uploadId);
|
||||||
|
if($hasHashString)
|
||||||
|
$stmt->addParameter(++$args, $hashString);
|
||||||
|
if($hasAppInfo)
|
||||||
|
$stmt->addParameter(++$args, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
|
if($hasUserInfo)
|
||||||
|
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
return $result->next() ? new UploadInfo($result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createUpload(
|
||||||
|
AppInfo|string $appInfo,
|
||||||
|
UserInfo|string $userInfo,
|
||||||
|
string $remoteAddr,
|
||||||
|
string $fileName,
|
||||||
|
string $fileType,
|
||||||
|
string $fileSize,
|
||||||
|
string $fileHash,
|
||||||
|
int $fileExpiry,
|
||||||
|
bool $bumpExpiry
|
||||||
|
): UploadInfo {
|
||||||
|
if(strpos($fileType, '/') === false)
|
||||||
|
throw new InvalidArgumentException('$fileType must contain a /');
|
||||||
|
if($fileSize < 1)
|
||||||
|
throw new InvalidArgumentException('$fileSize must be more than 0.');
|
||||||
|
if(strlen($fileHash) !== 64)
|
||||||
|
throw new InvalidArgumentException('$fileHash must be 64 characters.');
|
||||||
|
if($fileExpiry < 0)
|
||||||
|
throw new InvalidArgumentException('$fileExpiry must be a positive integer.');
|
||||||
|
|
||||||
|
$uploadId = XString::random(32);
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('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(?), ?)');
|
||||||
|
$stmt->addParameter(1, $uploadId);
|
||||||
|
$stmt->addParameter(2, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
|
$stmt->addParameter(3, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
|
||||||
|
$stmt->addParameter(4, $fileName);
|
||||||
|
$stmt->addParameter(5, $fileType);
|
||||||
|
$stmt->addParameter(6, $fileSize);
|
||||||
|
$stmt->addParameter(7, $fileHash);
|
||||||
|
$stmt->addParameter(8, $remoteAddr);
|
||||||
|
$stmt->addParameter(9, $fileExpiry > 0 ? (time() + $fileExpiry) : 0);
|
||||||
|
$stmt->addParameter(10, $bumpExpiry ? $fileExpiry : 0);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->getUpload(uploadId: $uploadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bumpUploadAccess(UploadInfo $uploadInfo): void {
|
||||||
|
$stmt = $this->cache->get('UPDATE prm_uploads SET upload_accessed = NOW() WHERE upload_id = ?');
|
||||||
|
$stmt->addParameter(1, $uploadInfo->getId());
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bumpUploadExpires(UploadInfo|string $uploadInfo): void {
|
||||||
|
if(!$uploadInfo->hasExpiryTime())
|
||||||
|
return;
|
||||||
|
|
||||||
|
$bumpAmount = $uploadInfo->getBumpAmount();
|
||||||
|
if($bumpAmount < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('UPDATE prm_uploads SET upload_expires = NOW() + INTERVAL ? SECOND WHERE upload_id = ?');
|
||||||
|
$stmt->addParameter(1, $bumpAmount);
|
||||||
|
$stmt->addParameter(2, $uploadInfo->getId());
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restoreUpload(UploadInfo|string $uploadInfo): void {
|
||||||
|
$stmt = $this->cache->get('UPDATE prm_uploads SET upload_deleted = NULL WHERE upload_id = ?');
|
||||||
|
$stmt->addParameter(1, $uploadInfo instanceof UploadInfo ? $uploadInfo->getId() : $uploadInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteUpload(UploadInfo|string $uploadInfo): void {
|
||||||
|
$stmt = $this->cache->get('UPDATE prm_uploads SET upload_deleted = COALESCE(upload_deleted, NOW()) WHERE upload_id = ?');
|
||||||
|
$stmt->addParameter(1, $uploadInfo instanceof UploadInfo ? $uploadInfo->getId() : $uploadInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nukeUpload(UploadInfo|string $uploadInfo): void {
|
||||||
|
$stmt = $this->cache->get('DELETE FROM prm_uploads WHERE upload_id = ? AND upload_dmca IS NULL');
|
||||||
|
$stmt->addParameter(1, $uploadInfo instanceof UploadInfo ? $uploadInfo->getId() : $uploadInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
}
|
75
src/User.php
75
src/User.php
|
@ -1,75 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace EEPROM;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
use Index\Data\IDbConnection;
|
|
||||||
|
|
||||||
class User {
|
|
||||||
private static $active;
|
|
||||||
|
|
||||||
public static function hasActive(): bool {
|
|
||||||
return !empty(self::$active);
|
|
||||||
}
|
|
||||||
public static function active(): self {
|
|
||||||
return self::$active;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private $id = 0,
|
|
||||||
private $sizeMultiplier = 0,
|
|
||||||
private $created = 0,
|
|
||||||
private $restricted = 0,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function __destruct() {
|
|
||||||
if($this === self::$active)
|
|
||||||
self::$active = null;
|
|
||||||
}
|
|
||||||
public function setActive(): self {
|
|
||||||
self::$active = $this;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSizeMultiplier(): int {
|
|
||||||
return $this->sizeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): int {
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRestricted(): int {
|
|
||||||
return $this->restricted;
|
|
||||||
}
|
|
||||||
public function isRestricted(): bool {
|
|
||||||
return $this->restricted > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function byId(IDbConnection $conn, int $userId): self {
|
|
||||||
$create = $conn->prepare('INSERT IGNORE INTO `prm_users` (`user_id`) VALUES (?)');
|
|
||||||
$create->addParameter(1, $userId);
|
|
||||||
$create->execute();
|
|
||||||
|
|
||||||
$get = $conn->prepare(
|
|
||||||
'SELECT `user_id`, `user_size_multiplier`, UNIX_TIMESTAMP(`user_created`) AS `user_created`,'
|
|
||||||
. ' UNIX_TIMESTAMP(`user_restricted`) AS `user_restricted` FROM `prm_users` WHERE `user_id` = ?'
|
|
||||||
);
|
|
||||||
$get->addParameter(1, $userId);
|
|
||||||
$get->execute();
|
|
||||||
$result = $get->getResult();
|
|
||||||
|
|
||||||
if(!$result->next())
|
|
||||||
throw new RuntimeException('User not found.');
|
|
||||||
|
|
||||||
return new User(
|
|
||||||
$result->getInteger(0),
|
|
||||||
$result->getInteger(1),
|
|
||||||
$result->getInteger(2),
|
|
||||||
$result->getInteger(3),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
47
src/Users/UserInfo.php
Normal file
47
src/Users/UserInfo.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
|
||||||
|
class UserInfo {
|
||||||
|
private string $id;
|
||||||
|
private int $created;
|
||||||
|
private ?int $restricted;
|
||||||
|
private int $sizeMultiplier;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->id = $result->getString(0);
|
||||||
|
$this->created = $result->getInteger(1);
|
||||||
|
$this->restricted = $result->getIntegerOrNull(2);
|
||||||
|
$this->sizeMultiplier = $result->getInteger(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTime {
|
||||||
|
return DateTime::fromUnixTimeSeconds($this->created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRestricted(): bool {
|
||||||
|
return $this->restricted !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRestrictedTime(): ?int {
|
||||||
|
return $this->restricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRestrictedAt(): ?DateTime {
|
||||||
|
return $this->restricted === null ? null : DateTime::fromUnixTimeSeconds($this->restricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataSizeMultiplier(): int {
|
||||||
|
return $this->sizeMultiplier;
|
||||||
|
}
|
||||||
|
}
|
21
src/Users/UsersContext.php
Normal file
21
src/Users/UsersContext.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Users;
|
||||||
|
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
|
||||||
|
class UsersContext {
|
||||||
|
private UsersData $usersData;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->usersData = new UsersData($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsersData(): UsersData {
|
||||||
|
return $this->usersData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(string $userId): UserInfo {
|
||||||
|
$this->usersData->ensureUserExists($userId);
|
||||||
|
return $this->usersData->getUser($userId);
|
||||||
|
}
|
||||||
|
}
|
31
src/Users/UsersData.php
Normal file
31
src/Users/UsersData.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
namespace EEPROM\Users;
|
||||||
|
|
||||||
|
use Index\Data\DbStatementCache;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
|
||||||
|
// restrictions and permissions should be checked with misuzu or cached or something
|
||||||
|
// size multiplier should also just be replaced with an alternate max size value for premioids, maybe based on rank?
|
||||||
|
|
||||||
|
class UsersData {
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(string $userId): ?UserInfo {
|
||||||
|
$stmt = $this->cache->get('SELECT user_id, UNIX_TIMESTAMP(user_created), UNIX_TIMESTAMP(user_restricted), user_size_multiplier FROM prm_users WHERE user_id = ?');
|
||||||
|
$stmt->addParameter(1, $userId);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
return $result->next() ? new UserInfo($result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensureUserExists(string $userId): void {
|
||||||
|
$stmt = $this->cache->get('INSERT IGNORE INTO prm_users (user_id) VALUES (?)');
|
||||||
|
$stmt->addParameter(1, $userId);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue