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.
		
		
		
		
		
			
		
			
				
					
					
						
							282 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							282 lines
						
					
					
						
							6.4 KiB
						
					
					
				<?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
 | 
						|
 |