You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
6.4 KiB
283 lines
6.4 KiB
4 years ago
|
<?php
|
||
|
|
||
|
namespace App\Classes;
|
||
|
|
||
|
use App\Model\MediaModel;
|
||
|
|
||
|
class Media {
|
||
|
|
||
|
public static $pagination = 20;
|
||
|
public static $directory = 'media';
|
||
|
|
||
|
public static function errorMessage(int $errorCode): string
|
||
|
{
|
||
|
// https://www.php.net/manual/en/features.file-upload.errors.php
|
||
|
$errorMessages = [
|
||
|
0 => 'Uploaded with success.',
|
||
|
1 => 'Uploaded file exceeds the maximum upload size. (1)', // php.ini
|
||
|
2 => 'Uploaded file exceeds the maximum upload size. (2)', // HTML Form
|
||
|
3 => 'Uploaded file was only partially uploaded.',
|
||
|
4 => 'No file was uploaded.',
|
||
|
6 => 'Missing a temporary folder.',
|
||
|
7 => 'Failed to write file to disk.',
|
||
|
8 => 'A PHP extension stopped the file upload.',
|
||
|
9 => 'User was not logged in.',
|
||
|
10 => 'Missing media folder.',
|
||
|
11 => 'Uploaded file has invalid MIME type.',
|
||
|
12 => 'Uploaded file exceeds the maximum upload size. (3)', // Media.php
|
||
|
13 => 'Uploaded file already exists.',
|
||
|
14 => 'DB entry creation failed.',
|
||
|
15 => 'Moving file from temporary location failed.',
|
||
|
];
|
||
|
|
||
|
return $errorMessages[$errorCode];
|
||
|
}
|
||
|
|
||
|
public static function deleteMedia(int $id): bool
|
||
|
{
|
||
|
if (!User::check()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$media = MediaModel::find($id);
|
||
|
|
||
|
if (!$media->exists()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Delete file
|
||
|
$file = self::$directory . '/' . $media->filename . '.' . $media->extension;
|
||
|
if (file_exists($file)) {
|
||
|
unlink($file);
|
||
|
}
|
||
|
|
||
|
return $media->delete();
|
||
|
}
|
||
|
|
||
|
public static function uploadMedia(bool $overwrite = false): int
|
||
|
{
|
||
|
// Check if User is logged in
|
||
|
if (!User::check()) {
|
||
|
return 9;
|
||
|
}
|
||
|
|
||
|
// Check if "media" directory exists
|
||
|
if (!is_dir(self::$directory) && !mkdir(self::$directory, 0755)) {
|
||
|
return 10;
|
||
|
}
|
||
|
|
||
|
$files = $_FILES['file'];
|
||
|
|
||
|
// Check for file errors
|
||
|
foreach ($files['error'] as $error) {
|
||
|
if ($error != 0) {
|
||
|
return $error;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!Media::checkMimeType($files['type'], $files['tmp_name'])) {
|
||
|
return 11;
|
||
|
}
|
||
|
|
||
|
if (!Media::checkSize($files['size'])) {
|
||
|
return 12;
|
||
|
}
|
||
|
|
||
|
// Append random string to filename that already exists
|
||
|
$nameExt = Media::duplicateName($files['name'], $overwrite);
|
||
|
|
||
|
// Check if the file already exists
|
||
|
$hash = Media::hashExists($files['tmp_name']);
|
||
|
if (!$hash[0]) {
|
||
|
return 13;
|
||
|
}
|
||
|
|
||
|
$count = count($files['name']);
|
||
|
for ($i = 0; $i < $count; $i++) {
|
||
|
$filename = $nameExt[0][$i];
|
||
|
$extension = $nameExt[1][$i];
|
||
|
$md5 = $hash[1][$i];
|
||
|
$tmpName = $files['tmp_name'][$i];
|
||
|
|
||
|
// Store record
|
||
|
$media = MediaModel::create([
|
||
|
'filename' => $filename,
|
||
|
'extension' => $extension,
|
||
|
'md5' => $md5,
|
||
|
]);
|
||
|
|
||
|
if (!$media->exists()) {
|
||
|
return 14;
|
||
|
}
|
||
|
|
||
|
// Store image
|
||
|
$name = self::$directory . '/'. $filename . '.' . $extension;
|
||
|
if (!move_uploaded_file($tmpName, $name)) {
|
||
|
return 15;
|
||
|
}
|
||
|
|
||
|
// After storing successfully, remove old entries with duplicate names
|
||
|
if ($overwrite) {
|
||
|
Media::destroyDuplicates($filename, $extension);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------//
|
||
|
|
||
|
private static function checkMimeType(array $fileTypes, array $fileTmpNames): bool
|
||
|
{
|
||
|
$allowedMimeType = [
|
||
|
// .tar.gz
|
||
|
'application/gzip',
|
||
|
'application/json',
|
||
|
'application/octet-stream',
|
||
|
'application/pdf',
|
||
|
'application/sql',
|
||
|
// .tar.xz
|
||
|
'application/x-xz',
|
||
|
'application/xml',
|
||
|
'application/zip',
|
||
|
'audio/mp3',
|
||
|
'audio/mpeg',
|
||
|
'audio/ogg',
|
||
|
'audio/wav',
|
||
|
'audio/webm',
|
||
|
'image/jpg',
|
||
|
'image/jpeg',
|
||
|
'image/png',
|
||
|
'image/gif',
|
||
|
'text/plain',
|
||
|
'text/csv',
|
||
|
'text/xml',
|
||
|
'video/mp4',
|
||
|
'video/webm',
|
||
|
// .doc
|
||
|
'application/msword',
|
||
|
// .xls
|
||
|
'application/vnd.ms-excel',
|
||
|
// .ppt
|
||
|
'application/vnd.ms-powerpoint',
|
||
|
// .docx
|
||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
|
// .xlsx
|
||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
|
// .pptx
|
||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||
|
];
|
||
|
|
||
|
// Files type check
|
||
|
$count = count($fileTypes);
|
||
|
for ($i = 0; $i < $count; $i++) {
|
||
|
if ($fileTypes[$i] != mime_content_type($fileTmpNames[$i]) ||
|
||
|
!in_array($fileTypes[$i], $allowedMimeType)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private static function checkSize(array $fileSizes): bool
|
||
|
{
|
||
|
// Files should not exceed 10MiB
|
||
|
foreach ($fileSizes as $fileSize) {
|
||
|
if ($fileSize > 10485760) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private static function duplicateName(array $filenames, bool $overwrite): array
|
||
|
{
|
||
|
// Split name from extension
|
||
|
$names = [];
|
||
|
$extensions = [];
|
||
|
foreach ($filenames as $name) {
|
||
|
$dotPos = strrpos($name, '.');
|
||
|
$names[] = substr($name, 0, $dotPos);
|
||
|
$extensions[] = substr($name, $dotPos + 1);
|
||
|
}
|
||
|
|
||
|
// Early return if names are specified to be overwritten
|
||
|
if ($overwrite) {
|
||
|
return [$names, $extensions];
|
||
|
}
|
||
|
|
||
|
// Get duplicate filenames
|
||
|
$in1 = str_repeat('?, ', count($names) - 1) . '?';
|
||
|
$in2 = str_repeat('?, ', count($extensions) - 1) . '?';
|
||
|
$data = array_merge($names, $extensions);
|
||
|
$duplicates = MediaModel::selectAll(
|
||
|
'*', "WHERE filename IN ($in1) AND extension IN ($in2)", $data, '?'
|
||
|
);
|
||
|
|
||
|
foreach ($filenames as $key => $filename) {
|
||
|
$hasDuplicate = false;
|
||
|
foreach ($duplicates as $duplicate) {
|
||
|
if ($filename == $duplicate['filename'] . '.' . $duplicate['extension']) {
|
||
|
$hasDuplicate = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Append to filename if there are duplicates
|
||
|
if ($hasDuplicate) {
|
||
|
$names[$key] = $names[$key] . '-' . _randomStr(5);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return [$names, $extensions];
|
||
|
}
|
||
|
|
||
|
private static function hashExists(array $fileTmpNames): array
|
||
|
{
|
||
|
$md5 = [];
|
||
|
foreach ($fileTmpNames as $tmpName) {
|
||
|
$md5[] = md5_file($tmpName);
|
||
|
}
|
||
|
|
||
|
// If exact file already exists
|
||
|
$in = str_repeat('?, ', count($md5) - 1) . '?';
|
||
|
$exists = MediaModel::selectAll(
|
||
|
'*', "WHERE md5 IN ($in)", $md5, '?'
|
||
|
);
|
||
|
|
||
|
if (!empty($exists)) {
|
||
|
return [false];
|
||
|
}
|
||
|
|
||
|
return [true, $md5];
|
||
|
}
|
||
|
|
||
|
private static function destroyDuplicates(string $filename, string $extension): void
|
||
|
{
|
||
|
$media = MediaModel::selectAll(
|
||
|
'*', 'WHERE filename = ? AND extension = ? ORDER BY id ASC',
|
||
|
[$filename, $extension], '?'
|
||
|
);
|
||
|
|
||
|
if (!_exists($media)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
foreach ($media as $key => $value) {
|
||
|
|
||
|
// Dont delete the new entry
|
||
|
if ($key === array_key_last($media)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
MediaModel::destroy($value['id']);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// @Todo
|
||
|
// - If a file fails to store in the loop, destruct all files of that request, by tracking all IDs
|