<?php
/**
 * Created by Hurasoft
 * Date: 28-May-2022
 * Time: 1:44 PM
 * Description: Use this class in the cdn host to handle the upload of files from Hura8's admin and put the uploaded file in the cdn host's local directory
 */

namespace Hura8\System;


class CDNFileUploadHandle
{

    const SECURITY_SALT = 'ahss@3asdaaSDFSD';

    //default config
    protected $config = [
        //1. file settings
        "max_file_size"  => 2000000,//bytes ~ 1MB
        'allowed_file_types'    => [
            'image'  => ['.jpeg', '.jpg', '.gif', '.png'],
            'script' => ['.css', '.js'],
        ],

        //2. uploaded by client
        'uid'           => '',
        'token'         => '',
        'time'          => '',
        'item_type'     => 'product', // product|article|media
        'target_path'    => '',
        'upload_method'     => 'content', //file || content
        "file_content"      => "", //only needed if upload_method = content
        "file_name"         => "",//only needed if upload_method = content
    ];
    private $tmp_file_prop = null; //array, temporary file's props in tmp folder
    private $accepted_file_input_names = [
        "file",  //<input type=file name="file">
        //"qqfile", //name use for files uploaded through FineUploader library
    ];
    private $user_tmp_dir = '';

    public function __construct(){

    }

    protected function createToken($id, $time) {
        return sha1(join("-", [$id , $time , static::SECURITY_SALT]));
    }

    public function start() {

        $this->_get_post_info();

        if(!$this->validateUser()) {
            return $this->set_return_result('error', 'User failed to verify', []);
        }

        //receive files
        $file = $this->receive_file();

        //return file-location to upload API to return to client application
        return $this->set_return_result('success', 'Upload succeeded', $file );
    }

    protected function validateUser() {
        return ($this->config['token'] == $this->createToken($this->config['uid'], $this->config['time']));
    }

    protected function _get_post_info() {
        $expected_keys = [
            'upload_method',
            'file_name',
            'target_path',
            'file_content',
            'uid',
            'time',
            'token',
            'item_type',
        ];

        foreach ($expected_keys as $key) {
            $this->config[$key] = isset($_POST[$key]) ? $_POST[$key] : null;
        }
    }


    //return boolean
    protected function remove_tmp_file() {
        $this->deleteDirectory($this->user_tmp_dir);

        return true;
    }

    //return boolean
    protected function validate_file() {
        //$this->tmp_file_prop
        //validate file size
        if($this->config['max_file_size'] && $this->config['max_file_size'] < $this->tmp_file_prop['size']) {
            return 'Size too large';
        }

        //validate allowed extension: allow image but upload .docx files
        $has_extension = false;
        $file_type  = '';
        foreach ( $this->config['allowed_file_types'] as $group => $group_ext ) {
            if(in_array( $this->tmp_file_prop['ext'], $group_ext )) {
                $has_extension = true;
                $file_type  = $group;
                break ;
            }
        }
        if( ! $has_extension ) return "File type not allowed";

        //validate claimed extension: claim image but not actual image
        $full_file_path = $this->tmp_file_prop['tmp_location'] . DIRECTORY_SEPARATOR . $this->tmp_file_prop['name'];
        if( $file_type == 'image' && ! v::image()->validate( $full_file_path )) {
            return "Not actual image";
        }

        return '';
    }


    //return mixed : array $tmp_file_prop or false
    protected function receive_file() {

        //check upload method
        if( $this->config['upload_method'] == 'content' ) {
            //upload by content - file created on server
            $file_ext       = $this->get_ext($this->config['file_name']);
            $upload_folder  = $this->create_upload_folder() ;

            if( ! $this->move_file($upload_folder)) {
                return false;
            }

            return [
                "ext"           => $file_ext,
                "name"          => $this->config['file_name'],
                "folder"        => $upload_folder,
                "size"          => strlen($this->config['file_content']),
            ];

        } else {

            //upload by file
            //get list of files to be uploaded
            $file_uploaded = null;
            foreach ($this->accepted_file_input_names as $_name) {
                if(isset($_FILES[$_name])) {
                    $file_uploaded = $_FILES[$_name];
                    break;
                }
            }

            if ( ! $file_uploaded ) return false;

            //if file name too long, then error
            if ( strlen($file_uploaded['name']) > 225 ) return false;

            //move file to tmp folder
            $file_ext       = $this->get_ext($file_uploaded['name']);
            $upload_folder  = $this->create_upload_folder() ;

            if( ! $this->move_file($upload_folder) ){
                return false;
            }

            return [
                "ext"           => $file_ext,
                "name"          => $this->config['file_name'],
                "folder"        => $upload_folder,
                "size"          => $file_uploaded['size'],
            ];
        }
    }


    protected function set_return_result($status = 'success', $message = 'Upload success', $content = []) {
        return [
            "status"    => $status,
            "message"   => $message,
            "files"     => $content,
        ];
    }

    //create upload folder for user: use current date, user id or app id based on $this->config
    private function create_upload_folder() {
        //$build_folder = [$this->tmp_dir];
        /*$user_id = (isset($this->config['user_id'])) ? intval($this->config['user_id']) : null;
        if($user_id) {
            $build_folder[] = $user_id;
        }
        $build_folder[] = date("Ymd");*/

        //rebuild upload folder
        $build_folder = array_filter(explode("/", $this->config['target_path']));
        $folder = join("/", $build_folder);
        $this->createDir($folder);

        return $folder;
    }

    //get a file's extension
    private function get_ext($fileName){
        return strtolower(strrchr($fileName, '.'));
    }

    // check if find is image
    private function isImage($fileName) {
        return (in_array( $this->get_ext($fileName), ['.jpeg', '.jpg', '.gif', '.png'] ));
    }


    //for security reason: only allow a-z0-9_- in file name (no other dot . except for the extension)
    //example: bad-file.php.js -> bad-filephp.js
    private function rename_file($uploaded_file_name, $ext) {
        $new_name = preg_replace("/[^a-z0-9_\-]/i", "", str_replace( strrchr($uploaded_file_name, '.'), "", $uploaded_file_name ) ) . $ext;

        return strtolower($new_name);
    }


    private function move_file($folder) {
        $new_file = $folder . '/' . $this->config['file_name'];

        if( $this->config['upload_method'] == 'content' ) {
            // image upload by content
            if( $this->isImage($new_file) ) {
                //Store in the filesystem.
                $fp = fopen($new_file, "w+");
                $status = fwrite($fp, $this->config['file_content']);
                fclose($fp);

                return ($status !== false);
            }
            // file upload
            else if( file_put_contents($new_file, $this->config['file_content'])){
                return true;
            }
        }

        return false;
    }


    protected function createDir($path, $folder_permission = 0750){
        if(file_exists($path)) {
            return true;
        }

        return mkdir($path, $folder_permission, true);
    }

    protected function deleteDirectory($dirPath) {
        if (!is_dir($dirPath)) {
            return false;
        }

        $objects = scandir($dirPath);
        foreach ($objects as $object) {
            if ($object != "." && $object !="..") {
                if (filetype($dirPath . DIRECTORY_SEPARATOR . $object) == "dir") {
                    $this->deleteDirectory($dirPath . DIRECTORY_SEPARATOR . $object);
                } else {
                    unlink($dirPath . DIRECTORY_SEPARATOR . $object);
                }
            }
        }

        return rmdir($dirPath);
    }

}
