<?php

namespace Internetgalerie\IgDatapoolFe\Lib;

/**
 * Simple Ajax Uploader
 * Version 2.6.6
 * https://github.com/LPology/Simple-Ajax-Uploader
 *
 * Copyright 2012-2019 LPology, LLC
 * Released under the MIT license
 *
 * View the documentation for an example of how to use this class.
 */

class FileUpload
{
    private $fileName;                    // Filename of the uploaded file
    private $fileSize;                    // Size of uploaded file in bytes
    private $fileExtension;               // File extension of uploaded file
    private $fileNameWithoutExt;
    private $savedFile;                   // Path to newly uploaded file (after upload completed)
    private $errorMsg;                    // Error message if handleUpload() returns false (use getErrorMsg() to retrieve)
    private $isXhr;
    public $uploadDir;                    // File upload directory (include trailing slash)
    public $allowedExtensions;            // Array of permitted file extensions
    public $sizeLimit = 104857600;         // Max file upload size in bytes (default 100MB) -> real default is in ActionController.php
    public $newFileName;                  // Optionally save uploaded files with a new name by setting this
    public $corsInputName = 'XHR_CORS_TARGETORIGIN';
    public $uploadName = 'uploadfile';

    public function __construct($uploadName = null)
    {
        if ($uploadName !== null) {
            $this->uploadName = $uploadName;
        }

        if (isset($_FILES[$this->uploadName])) {
            $this->isXhr = false;

            if ($_FILES[$this->uploadName]['error'] === UPLOAD_ERR_OK) {
                $this->fileName = $_FILES[$this->uploadName]['name'];
                $this->fileSize = $_FILES[$this->uploadName]['size'];
            } else {
                $this->setErrorMsg($this->errorCodeToMsg($_FILES[$this->uploadName]['error']));
            }
        } elseif (isset($_SERVER['HTTP_X_FILE_NAME']) || isset($_GET[$this->uploadName])) {
            $this->isXhr = true;

            $this->fileName = $_SERVER['HTTP_X_FILE_NAME'] ?? $_GET[$this->uploadName];

            if (isset($_SERVER['CONTENT_LENGTH'])) {
                $this->fileSize = (int)$_SERVER['CONTENT_LENGTH'];
            } else {
                throw new Exception('Content length is empty.');
            }
        }
        $this->updateFileName();
    }
    private static function basename($filepath, $suffix = null)
    {
        $splited = preg_split('/\//', rtrim((string) $filepath, '/ '));
        return substr(basename('X'.$splited[count($splited)-1], $suffix), 1);
    }


    public static function sanitizeFilename(string $name): string
    {
        $name = trim((string) self::basename(stripslashes($name)), ".\x00..\x20");
        $name = str_replace(['Ä','ä','Ö','ö','Ü','ü','é','è','à','ç'], ['Ae','ae','Oe','oe','Ue','ue','e','e','a','c'], $name);
        $name = str_replace(['(', ')', '?', '\\', '/', '[', ']','!','*','"','\'', ',','~',  '`','=','&',';',':','<','>'], '_', $name);

        $allowed = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '_'];
        $cleanName = '';
        for ($i = 0; $i < strlen($name); $i++) {
            if (in_array(strtolower($name[$i]), $allowed)) {
                $cleanName .= $name[$i];
            }
        }
        $name = $cleanName;

        // Use timestamp for empty filenames
        if (!$name) {
            $name = str_replace('.', '-', microtime(true));
        }

        return $name;
    }

    
    public static function generateRandomString($length = 10)
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[random_int(0, $charactersLength - 1)];
        }
        return $randomString;
    }


    public function getSizeLimit()
    {
        return $this->sizeLimit;
    }
    public function setSizeLimit(int $sizeLimit): void
    {
        $this->sizeLimit = $sizeLimit;
    }

    public function getFileName()
    {
        return $this->fileName;
    }

    public function getFileSize()
    {
        return $this->fileSize;
    }

    public function getFileNameWithoutExt()
    {
        return $this->fileNameWithoutExt;
    }

    public function getExtension()
    {
        return $this->fileExtension;
    }

    public function getErrorMsg()
    {
        return $this->errorMsg;
    }

    public function getSavedFile()
    {
        return $this->savedFile;
    }

    private function errorCodeToMsg($code)
    {
        $message = match ($code) {
            UPLOAD_ERR_INI_SIZE => 'File size exceeds limit.',
            UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.',
            UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
            UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
            UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
            UPLOAD_ERR_EXTENSION => 'File upload stopped by extension.',
            default => 'Unknown upload error.',
        };
        return $message;
    }

    private function checkExtension($ext, $allowedExtensions)
    {
        if (!is_array($allowedExtensions)) {
            return false;
        }

        if (!in_array(strtolower((string) $ext), array_map('strtolower', $allowedExtensions))) {
            return false;
        }

        return true;
    }

    private function setErrorMsg($msg): void
    {
        if (empty($this->errorMsg)) {
            $this->errorMsg = $msg;
        }
    }

    private function fixDir($dir)
    {
        if (empty($dir)) {
            return $dir;
        }

        $slash = DIRECTORY_SEPARATOR;
        $dir = str_replace('/', $slash, $dir);
        $dir = str_replace('\\', $slash, $dir);
        return substr($dir, -1) == $slash ? $dir : $dir . $slash;
    }

    // escapeJS and jsMatcher are adapted from the Escaper component of
    // Zend Framework, Copyright (c) 2005-2013, Zend Technologies USA, Inc.
    // https://github.com/zendframework/zf2/tree/master/library/Zend/Escaper
    private function escapeJS($string)
    {
        return preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, (string) $string);
    }

    private function jsMatcher($matches)
    {
        $chr = $matches[0];

        if (strlen((string) $chr) == 1) {
            return sprintf('\\x%02X', ord($chr));
        }

        if (function_exists('iconv')) {
            $chr = iconv('UTF-16BE', 'UTF-8', (string) $chr);
        } elseif (function_exists('mb_convert_encoding')) {
            $chr = mb_convert_encoding($chr, 'UTF-8', 'UTF-16BE');
        }

        return sprintf('\\u%04s', strtoupper(bin2hex((string) $chr)));
    }

    public function corsResponse($data)
    {
        if (isset($_REQUEST[$this->corsInputName])) {
            $targetOrigin = $this->escapeJS($_REQUEST[$this->corsInputName]);
            $targetOrigin = htmlspecialchars((string) $targetOrigin, ENT_QUOTES, 'UTF-8');
            return "<script>window.parent.postMessage('$data','$targetOrigin');</script>";
        }
        return $data;
    }

    public function getMimeType($path)
    {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $fileContents = file_get_contents($path);
        $mime = $finfo->buffer($fileContents);
        $fileContents = null;
        return $mime;
    }

    public function isWebImage($path)
    {
        $pathinfo = pathinfo((string) $path);

        if (isset($pathinfo['extension'])) {
            if (!in_array(strtolower($pathinfo['extension']), ['gif', 'png', 'jpg', 'jpeg'])) {
                return false;
            }
        }

        $type = exif_imagetype($path);

        if (!$type) {
            return false;
        }

        return ($type == IMAGETYPE_GIF || $type == IMAGETYPE_JPEG || $type == IMAGETYPE_PNG);
    }

    private function saveXhr($path)
    {
        if (false !== file_put_contents($path, fopen('php://input', 'r'))) {
            return true;
        }
        return false;
    }

    private function saveForm($path)
    {
        if (move_uploaded_file($_FILES[$this->uploadName]['tmp_name'], $path)) {
            return true;
        }
        return false;
    }

    private function save($path)
    {
        if (true === $this->isXhr) {
            return $this->saveXhr($path);
        }
        return $this->saveForm($path);
    }

    public function handleUpload(?string $uploadDir = null, ?array $allowedExtensions = null, bool $uniqueSafeFilename = true)
    {
        if (!$this->fileName) {
            $this->setErrorMsg('Incorrect upload name or no file uploaded');
            return false;
        }

        if ($this->fileSize == 0) {
            $this->setErrorMsg('File is empty');
            return false;
        }

        if ($this->fileSize > $this->sizeLimit) {
            $this->setErrorMsg('File size exceeds limit');
            return false;
        }

        if (!empty($uploadDir)) {
            $this->uploadDir = $uploadDir;
        }

        $this->uploadDir = $this->fixDir($this->uploadDir);

        if (!file_exists($this->uploadDir)) {
            $this->setErrorMsg('Upload directory "' . $this->uploadDir . '" does not exist');
            return false;
        } elseif (!is_writable($this->uploadDir)) {
            $this->setErrorMsg('Upload directory exists, but is not writable');
            return false;
        }

        if (is_array($allowedExtensions)) {
            $this->allowedExtensions = $allowedExtensions;
        }

        if (!empty($this->allowedExtensions)) {
            if (!$this->checkExtension($this->fileExtension, $this->allowedExtensions)) {
                $this->setErrorMsg('Invalid file type');
                return false;
            }
        }
        //$uniqueName = str_replace('.', '-', microtime(true));
        if ($uniqueSafeFilename) {
            $uniqueSafeName = uniqid() . '-' . self::generateRandomString() . '-';
        } else {
            $uniqueSafeName = '';
        }


        
        $this->savedFile = $this->uploadDir . $uniqueSafeName . $this->fileName;

        if (!empty($this->newFileName)) {
            $this->fileName = $this->newFileName;
            $this->savedFile = $this->uploadDir . $this->fileName;
            $this->updateFileName();
        }

        if (!$this->save($this->savedFile)) {
            $this->setErrorMsg('File could not be saved');
            return false;
        }

        return true;
    }

    protected function updateFileName()
    {
        if ($this->fileName) {
            $this->fileName = static::sanitizeFilename($this->fileName);

            $pathinfo = pathinfo($this->fileName);

            $this->fileNameWithoutExt = null;
            $this->fileExtension = null;

            if (isset($pathinfo['filename'])) {
                $this->fileNameWithoutExt = $pathinfo['filename'];
            }

            if (isset($pathinfo['extension'])) {
                $this->fileExtension = strtolower($pathinfo['extension']);
            }
        }
    }
}
