Files
xstore/inc/Hura8/System/Controller/bFileHandle.php
2025-10-04 11:46:59 +07:00

271 lines
9.2 KiB
PHP

<?php
namespace Hura8\System\Controller;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\FileHandleInfo;
use Hura8\Interfaces\FileHandleResponse;
use Hura8\System\FileSystem;
use Hura8\System\HuraImage;
use Hura8\System\IDGenerator;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
/**
* @date 16-Jan-2024
* @description based-class to handling files, this is used for :
* - FileUpload class
* - CopyFileFromUrl class
* ...
*
*/
abstract class bFileHandle
{
static $image_extensions = array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif', ".ico");
protected $permit_file_extensions = array(
".jpg",".jpeg",".gif", ".png", ".webp", '.avif',
".doc", ".docx",".xls",".xlsx", ".ppt", ".pdf",
".rar", ".zip",
//".avi",".mov",".mpg", ".wmv", ".mpeg",
//".mp3",".mp4", ".ogg", ".oga", ".wav", ".wma"
);
protected $permit_mine_types = array(
'image/jpeg', 'image/png','image/gif', 'image/webp', 'image/avif',
// zip files
'application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed',
);
// full system's path where the file will be finally stored or it's currently there for processing
// example: /var/www/html/domain.com/public_html/media/product/
protected $target_dir = "";
// directory appears to the public
// example: /media/product/
protected $public_dir = "";
protected $tmp_dir = ROOT_DIR . "/var/tmp_upload/"; // system tmp dir to store uploaded file for security examination before moving to target_dir
protected $tmp_folder = ""; // tmp folder per session in the $tmp_dir to hold user's files. This folder will be removed when operation is complete
protected $setup_success = false;
/**
* @param string $target_dir will be created if not exist i.e. media/product/ or media/user_upload/date('d-m-Y')
* @param ?array $permit_file_extensions null to allow all default file extensions
*/
public function __construct( string $target_dir, ?array $permit_file_extensions = null ) {
if(is_array($permit_file_extensions)) {
$this->permit_file_extensions = $permit_file_extensions;
}
if($target_dir) {
$this->target_dir = PUBLIC_DIR . DIRECTORY_SEPARATOR. $target_dir;
$this->public_dir = "/".$target_dir;
}
$setup_res = $this->setUp();
if($setup_res->getStatus() == AppResponse::SUCCESS) {
$this->setup_success = true;
}
}
/**
* @description (rename if set) and resize file
* @param string $name
* @param string $new_name
* @param array $resized_sizes [] array('small' => ['width' => 100, 'height' => 100], 'large' => ['width' => 200, 'height' => 200] )
* @return array|false
*/
public function resizeFile(string $name, string $new_name = '', array $resized_sizes = []) {
if($new_name) {
$renamed = $this->renameFile($name, $new_name);
if(!$renamed) {
return false;
}
//$file_name = $renamed['file_name'];
//$public_path = $renamed['public_path'];
$local_path = $renamed['local_path'];
}else{
//$file_name = $name;
//$public_path = $this->public_dir . "/".$name;
$local_path = $this->target_dir . "/" . $name;
}
$objHuraImage = new HuraImage();
list(, $expected_files, ) = $objHuraImage->resize($local_path, $resized_sizes);
return $expected_files;
}
/**
* @description we can rename the uploaded file to new file name, for example: we want product's image have the format [PRODUCT_ID]-name
* @param string $name
* @param string $new_name
* @return array | false
*/
public function renameFile(string $name, string $new_name) {
if(@rename($this->target_dir . "/" . $name, $this->target_dir . "/" . $new_name)){
return [
"file_name" => $new_name,
"public_path" => $this->public_dir . "/".$new_name,
"local_path" => $this->target_dir . "/" . $new_name,
];
}
return false;
}
// public utility
public static function getFileExtension($file_name) {
return strtolower(strrchr($file_name,"."));
}
/**
* @description run clean up after finish using the FileHandle instance
*/
public function cleanUp() {
if($this->tmp_folder) {
FileSystem::removeDir($this->tmp_folder);
}
}
protected function processFile(
$original_file_name,
$original_file_tmp_name, // temporary uploaded file as in case of $_FILES[$input_file_name]["tmp_name"]
$fixed_file_name="",
$max_file_size=0
) : FileHandleResponse {
if(!$original_file_name) {
return new FileHandleResponse(AppResponse::ERROR, 'no file', null);
}
$file_size = filesize($original_file_tmp_name);
if($max_file_size > 0 && $max_file_size < $file_size) {
return new FileHandleResponse(AppResponse::ERROR, 'Size is too large: '.round($file_size/1000).'KB', null);
}
//validate extension
$file_ext = self::getFileExtension($original_file_name);
if(!in_array($file_ext, $this->permit_file_extensions)) {
return new FileHandleResponse(AppResponse::ERROR, "Type ".$file_ext." is not allowed!", null);
}
$file_name = substr($original_file_name, 0, strrpos($original_file_name,"."));
$file_name = preg_replace("/[^a-z0-9_-]/i","", $file_name);
$file_name = substr($file_name, 0, 50); // max- length
$clean_file_name = ($fixed_file_name) ?: $file_name . $file_ext;
$tmp_file_path = $this->tmp_folder . "/". $clean_file_name;
$return_data = null;
//debug_var([$original_data, $tmp_file_path]);
if(@rename($original_file_tmp_name, $tmp_file_path)){
$is_file_image = (in_array($file_ext, static::$image_extensions ));
if($is_file_image) {
list($width, $height) = getimagesize($tmp_file_path);
}else{
$width = 0;
$height = 0;
}
$detector = new FinfoMimeTypeDetector();
$mimeType = $detector->detectMimeTypeFromPath($tmp_file_path);
// if image, we re-create and optimize it
if($is_file_image) {
if(in_array($mimeType, $this->permit_mine_types)) {
$objHuraImage = new HuraImage();
if($objHuraImage->create($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name)){
$return_data = new FileHandleInfo([
"file_name" => $clean_file_name,
"public_path" => $this->public_dir . "/".$clean_file_name,
"local_path" => $this->target_dir . "/" . $clean_file_name,
"mime_type" => $mimeType,
"file_size" => $file_size,
"file_ext" => $file_ext,
"width" => $width,
"height" => $height,
]);
}
}
}elseif(@rename ($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name )) {
$return_data = new FileHandleInfo([
"file_name" => $clean_file_name,
"public_path" => $this->public_dir . "/".$clean_file_name,
"local_path" => $this->target_dir . "/" . $clean_file_name,
"mime_type" => $mimeType,
"file_size" => $file_size,
"file_ext" => $file_ext,
"width" => 0,
"height" => 0,
]);
}
}
// delete tmp file on server
if(file_exists($original_file_tmp_name)) {
@unlink($original_file_tmp_name);
}
if($return_data) {
return new FileHandleResponse(AppResponse::SUCCESS, 'Success', $return_data);
}
return new FileHandleResponse(AppResponse::ERROR, 'Unknown', null);
}
protected function setUp(): AppResponse
{
// check target dir
if($this->target_dir && !is_dir($this->target_dir)) {
@mkdir($this->target_dir, 0755, true);
}
if(!file_exists($this->target_dir)) {
return new AppResponse(AppResponse::ERROR, $this->target_dir.' not exists');
}
// create tmp_folder to upload file to
$this->tmp_folder = $this->create_tmp_folder();
if(!$this->tmp_folder) {
return new AppResponse(AppResponse::ERROR, "Check ".$this->tmp_dir." and make sure it exists and writable");
}
return new AppResponse(AppResponse::SUCCESS);
}
protected function create_tmp_folder() : ?string {
$tmp_folder = $this->tmp_dir . IDGenerator::createStringId(5);
if(!@mkdir($tmp_folder, 0777, true)) {
return null;
}
// retest
if(!$tmp_folder || !is_dir($tmp_folder)) {
return null;
}
return $tmp_folder;
}
}