'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