<?php
require 'DBase.php';

//ini_set('error_reporting', E_ALL);
//ini_set('display_errors', 1);
//ini_set('display_startup_errors', 1);

// определяем максимальные размеры загружаемых файлов и время загрузки
ini_set('post_max_size', '1024M');
ini_set('upload_max_filesize', '1024M');
set_time_limit(3600); // 60 минут

try {
    defined('DS') || define('DS', DIRECTORY_SEPARATOR);

    new download();

} catch (Exception $e) {
    http_response_code(400);
    die($e->getMessage());
}

class download
{

    /** @var string Путь для сохранения загруженного архива */
    private $zipUploadPath = __DIR__ . '/tempSave';

    // для распаковки
    private $tempUnzip = __DIR__ . '/tempSave/tempUnzip';

    // куда будет сохраняться скрипт, название класса
    private $generatedClassPath;


    /**
     * Путь до файла класса для обработки относительно структуры архива
     */
    private $processingClassFilePath = 'script.php';


    /** @var string Namespace для нового класса */
    private $classNamespace = 'lib\Controllers\CreateFiles\AllScripts';

    // Название нового класса
    private $generatedClassName;

    // куда сохраняем файлы из разрешенных директорий
    private $dirSaveFiles;

    // путь к скрипту, нужен для бд
    private $pathScript;

    // id продукта появляется после его создания
    private $idProduct;

    // главный метод который будет запускать всю генерацию, нужно указать в бд
    private $installStaticMethod;

    // изначальное название архива
    private $initialTitle;

    private $domain = 'https://o.site5.com';

    // какие бывают форматы
    private $allFileFormats;

    // каким будет формат файла при сохранении
    private $format = NULL;

    // какие директории разрешено разархивировать
    private $allowedDirectoriesAndExtensions = [
        'fonts' => [
//            'path'    => 'fonts',
            'formats' => '*',
        ],
        'bg' => [
//            'path'    => 'bg',
            'formats' => [
                'jpg',
                'png',
                'gif',
                'svg',
            ],
        ],
//        'dompdf' => [
////            'path'    => 'files',
//            'formats' => '*',
//        ],
//        'vendor' => [
////            'path'    => '',
//            'formats' => '*',
//        ],
    ];

    function __construct()
    {
        // если архив - то скачивается архив.

        // если скрин - то передаются url и скрипт сам скачивает эти изображения и шрифты

        new DBase(); // открываем бд


        // какие бывают форматы
        $this->fileFormats();

        if ($_GET['type'] == 'demofile') {

            $this->updateDemoFile();


        } else if ($_GET['type'] == 'jsonimage') {

            $this->downldJson();

        } else if ($_GET['type'] == 'imagezip') {

            $this->downldImageArchive();

        } else if ($_GET['type'] == 'pdf') {

            // загрузка архива
        $this->downldArchive();

        }




    }


    private function updateDemoFile()
    {
        $getContent = file_get_contents('php://input');

        $decode = json_decode($getContent, 1);

        if (!is_array($decode)) {
            throw new Exception('Неверный формат данных');
        }

        if (!(isset($decode['ID'])  AND isset($decode['URL_FILE']) AND isset($decode['TYPE'])) ) {
            throw new Exception('Нет одного из составляющего');
        }

        if (!$idProd = DBase::getValue("SELECT `ID` FROM `PROD_scripts` WHERE `ID` = '{$decode['ID']}'")) {
            throw new Exception('Продукт не найден');
        }

        $typeFile = [
            'photo' => 1,
            'document' => 2,
            'video' => 3
        ];

        if (!isset($typeFile[$decode['TYPE']])) {
            throw new Exception('Неизвестный тип');
        }

        $prodPath = "PROD_files/prod-id-" . $decode['ID'] . "/demonstration";

        // Куда будем сохранять файлы
        $destinationPath = __DIR__ . "/../UMLyMG912T/" . $prodPath;

        // есть ли такая директория?
        if (!is_dir($destinationPath)) {
            // если нет директории, то создаем
            if (!mkdir($destinationPath, 0777, true)) {
                throw new Exception('Не удалось создать директорию для распаковки файла');
            }
        }

//        var_dump($decode);
//        exit;

        // извлекаем имя файла
        $nameFile = explode('/', $decode['URL_FILE']);
        $nameFile = end($nameFile);
        $nameFile = explode('.', $nameFile);
        $nameFile = 'Demonstration.' . end($nameFile); // расширение файла

        // путь с __DIR__
        $dirPathFile = $destinationPath . '/' . $nameFile;

//        var_dump($dirPathFile);
//        exit;

//        var_dump($decode['URL_FILE']);
//        exit;

        // Сохраняем файл
        if (file_put_contents($dirPathFile, fopen($this->encodeUrl($decode['URL_FILE']), 'r')) === false) {
            throw new Exception('Ошибка при сохранении файла.');
        }

        // имеется ли старый файл?
//        if ($oldFile = DBase::getRow("SELECT * FROM `PROD_scripts` WHERE `ID` = '{$decode['ID']}'")) {
////        if ($oldFile = DBase::getRow("SELECT `ID`, `URL` FROM `file_id` WHERE `ANSWER_ID` = '{$idMethod}'")) {
//
//            // удаляем старый файл
//            $pathOldFile = __DIR__ . "/../UMLyMG912T/" . $oldFile['URL'];
//            unlink($pathOldFile);
//
//            $resultDel = DBase::run("DELETE FROM `file_id` WHERE `ID` = " . $oldFile['ID']);
//
//            if (!$resultDel->rowCount()) {
//                throw new Exception('Не удалось удалить старый файл из таблицы');
//            }
//        }

        $query = "INSERT INTO `file_id` (
                        `TYPE`,
                        `URL`
                      )
                    VALUES (
                        :TYPE,
                        :URL
                    )";

        $args = [
            'TYPE' => $typeFile[$decode['TYPE']], // в формате json
            'URL' => $prodPath . '/' . $nameFile
        ];

        DBase::sql($query, $args);

        $idNewFile = DBase::lastInsertId();

        if (!$idNewFile) {
            echo json_encode(['error' => 'false update']);
            exit;
        }

        $updateProdScript = DBase::fastQuery('UPDATE', 'PROD_scripts', ['DEMO_FILE' => $idNewFile], ['ID' => $idProd]);

        if ($updateProdScript->rowCount()) {
            $result = ['result' => 'success'];
        } else {
            $result = ['error' => 'false'];
        }

        echo json_encode($result);
    }


    private function downldJson()
    {

        # создать запись в PROD и получить ID для создания директорий
        # запись json в специальную таблицу image

            # создать директории prod-id-*

            # сохранить файлы: изображения и шрифты

        # сохранить все поля/строки в специальную таблицу

        $getContent = file_get_contents('php://input');


        $decode = json_decode($getContent, 1);

        if (!is_array($decode)) {

            throw new Exception('Неверный формат данных');
        }

        if (!isset($decode['JSON_CODE']) OR !isset($decode['fieldDesignations']) OR !isset($decode['URL_IMAGE'])) {

            throw new Exception('Нет одного из составляющего');
        }

        // раскодируем json_code
        $arrJsonCode = json_decode($decode['JSON_CODE'], 1);

//        print_r($arrJsonCode);
//        exit;

        // должен быть массивом и с ключом fields
        if (!(is_array($arrJsonCode) AND isset($arrJsonCode['fields']))) {

            throw new Exception('Нет fields');
        }

        // создаем новый продукт, закрепляем его ID
        if (!$this->createProduct()) {
            throw new Exception('Не удалось создать запись нового продукта');
        }


        // Куда будем сохранять файлы
        $this->dirSaveFiles = __DIR__ . "/../UMLyMG912T/PROD_files/prod-id-" . $this->idProduct;

        foreach ($arrJsonCode['fields'] as $nameField => $field) {

            if (isset($field['font'])) {

                // проверяем расширение шрифта
                $fontExt = ['ttf' => 1, 'otf' => 1, 'eot' => 1, 'woff' => 1, 'woff2' => 1];

                $fontExtension = pathinfo($field['font'], PATHINFO_EXTENSION);

                if (!isset($fontExt[$fontExtension])) {
                    throw new Exception('Неизвестный формат шрифта - ' . $fontExtension);
                }

                // куда сохраняем
                $destinationPath = $this->dirSaveFiles . '/fonts';

                // есть ли такая директория?
                if (!is_dir($destinationPath)) {
                    // если нет директории, то создаем
                    if (!mkdir($destinationPath, 0777, true)) {
                        throw new Exception('Не удалось создать директорию для распаковки файла');
                    }

                }

                // Сохраняем шрифты
                if (file_put_contents($destinationPath . '/' . basename($field['font']), fopen($this->domain . $field['font'], 'r')) === false) {

                    throw new Exception('Ошибка при сохранении шрифта  - ' . $field['font']);
                }

                // изменяем эту строку, оставляем только название файла
                $arrJsonCode['fields'][$nameField]['font'] = pathinfo($field['font'], PATHINFO_BASENAME);
            }

            // массив полей для добавления в бд
            $addArray[$nameField] = [
                'line_name' => $decode['fieldDesignations'][$nameField],
            ];
        }

        if (isset($decode['URL_IMAGE'])) {

            // проверяем расширение изображения
            $imgExt = ['jpeg' => 1, 'jpg' => 1, 'png' => 1, 'gif' => 1];

            $imageExtension = strtolower(pathinfo($decode['URL_IMAGE'], PATHINFO_EXTENSION)); // в нижнем регистре

            if (!(isset($imgExt[$imageExtension]) AND $this->format = $this->allFileFormats[$imageExtension])) {
                throw new Exception('Неизвестный формат изображения - ' . $imageExtension);
            }

            // куда сохраняем
            $destinationPath = $this->dirSaveFiles . '/imgs';

            // есть ли такая директория?
            if (!is_dir($destinationPath)) {
                // если нет директории, то создаем
                if (!mkdir($destinationPath, 0777, true)) {
                    throw new Exception('Не удалось создать директорию для распаковки файла');
                }
            }

            // Сохраняем файл
            if (file_put_contents($destinationPath . '/' . pathinfo($decode['URL_IMAGE'], PATHINFO_BASENAME), fopen($this->domain . $decode['URL_IMAGE'], 'r')) === false) {

                throw new Exception('Ошибка при сохранении изображения  - ' . $decode['URL_IMAGE']);
            }
        }

//        if (isset($decode['statusBar'])) {
//            $statusBar = [
//                'PLATFORM' => $decode['statusBar']['PLATFORM'],
//                'DARK_THEME' => $decode['statusBar']['DARK_THEME']??0
//            ];
//        }

        // добавление изображения в специальную таблицу
//        if (!$idImageCode = $this->addProdImageCode($arrJsonCode, pathinfo($decode['URL_IMAGE'], PATHINFO_BASENAME)/*, $statusBar??null*/)) {
//
//            throw new Exception('Не удалось сохранить массив полей в таблице изображения');
//        }


        $arrayProdScript = [
//            'IMAGE_CODE' => $idImageCode,
            'IMAGE_JSON_CODE' => json_encode($arrJsonCode),
            'IMAGE_PATH' => json_encode([pathinfo($decode['URL_IMAGE'], PATHINFO_BASENAME)]),
            'FORMAT' => $this->format
        ];

        if (isset($decode['statusBar'])) {
            $arrayProdScript['STATUS_BAR_PLATFORM'] = $decode['statusBar']['PLATFORM'];
            $arrayProdScript['DARK_THEME'] = $decode['statusBar']['DARK_THEME'];
        }


        if (!DBase::fastQuery('UPDATE', 'PROD_scripts', $arrayProdScript, ['ID' => $this->idProduct])) {

            throw new Exception('Ошибка при редактировании id кода в главной таблице PROD');
        }

        // добавляем поля на основе массива $descriptionOfFields
        if (!$this->createFields($addArray)) {
            throw new Exception('Не удалось добавить поля. Возникла проблема. Возможно дублирующие записи');
        }

        if ($this->idProduct) {
            $result = ['newProductID' => $this->idProduct];
        } else {
            $result = ['error' => 'false'];
        }

        echo json_encode($result);

//        var_dump($this->idProduct);
    }


//    private function addProdImageCode($arrJsonCode, $imageName/*, $statusBar = null*/)
//    {
//        $imageExtension = strtolower(pathinfo($imageName, PATHINFO_EXTENSION));
//        if (!isset($this->allFileFormats[$imageExtension])) {
//            return false;
//        }
//
//        $query = "INSERT INTO `PROD_image_code` (
//                        `JSON_CODE`,
//                        `IMAGE_PATH`,
//                        `EXP`
//                      )
//                    VALUES (
//                        :JSON_CODE,
//                        :IMAGE_PATH,
//                        :EXP
//                    )";
//
//        $args = [
//            'JSON_CODE' => json_encode($arrJsonCode),
//            'IMAGE_PATH' => json_encode([$imageName]), // в формате json
//            'EXP' => $this->allFileFormats[$imageExtension]
//        ];
//
//        DBase::sql($query, $args);
//
//        if ($newId = DBase::lastInsertId()) {
//            return $newId;
//        }
//
//        return false;
//    }



    private function downldArchive()
    {
        $getContent = file_get_contents('php://input');
        $decode = json_decode($getContent, 1);

        if (!isset($decode['URL'])) {
            echo 'error';
            exit;
        }

        $url = $decode['URL'];

        // откуда скачиваем архив
//        $url = 'https://o.site5.com/testclass/saves/archive.zip';

        $this->initialTitle = basename($url); // сохраним название файла

        // ПЕРЕМЕЩЕНИЕ АРХИВА В ДИРЕКТОРИЮ С КЛАССОМ ИЛИ С ФАЙЛАМИ

        // метод который будет служить за инсталяцию класса продукта, добавляется в бд
        $this->installStaticMethod = 'create';

        // устанавливаем формат этого файла
        $this->format = $this->allFileFormats['pdf'];

//        var_dump($this->format);
//        exit;

        // генерируем название для класса
        $x=0;
        while ($x++<10) {
            $this->generatedClassName = $this->randomText(); // так будет называться временный файл и будущий класс

            $this->pathScript = "CreateFiles/AllScripts/" . $this->generatedClassName; // путь к скрипту, сохраняется в бд

            $this->generatedClassPath = __DIR__ . "/../UMLyMG912T/lib/Controllers/" . $this->pathScript;

            if (!file_exists($this->generatedClassPath)) break; // если такой директории не существует, выходим из цикла
            $this->generatedClassPath = null;
        }

        if ($this->generatedClassPath == null) {
            throw new Exception('Из 10 попыток не нашлось названия для директории.');
        }


        $fileExtension = pathinfo($url, PATHINFO_EXTENSION);
        if ($fileExtension !== 'zip') {
            throw new Exception('Разрешены только ZIP архивы'); // Разрешены только ZIP-архивы.
        }


        // проверяем на месте ли директория для временного хранилища
        if (!is_dir($this->zipUploadPath)) {
            if (!mkdir($this->zipUploadPath, 0777, true)) {
                throw new Exception('Не удалось создать директорию для загрузки архива: ' . $this->zipUploadPath);
            }
        }


//        $zipFilePath = $this->zipUploadPath . '/sAPOInWRKu.zip';


         // НА ВРЕМЯ РАЗРАБОТКИ ОСТАВИМ ТОЛЬКО ОДИН ЗАГРУЖЕННЫЙ ФАЙЛ И БУДЕМ РАБОТАТЬ ПО НЕМУ

        $zipFilePath = $this->zipUploadPath . '/' . $this->generatedClassName . '.zip';

        if (!$this->downloadFile($zipFilePath, $url)) {
            throw new Exception('Не удалось сохранить ZIP-архив на сервере.');
        }


        // уже сохранили во временном хранилище
        // Дальше распаковка уже


        $extraFiles = [];
        $zip = new ZipArchive();
        if ($zip->open($zipFilePath) === true) {
            /**
             * Извлечение файла класса во временную директорию
             */
            $tempDir = $this->tempUnzip . '/' . uniqid('zip_processing');

            if (!is_dir($tempDir)) {
                if (!mkdir($tempDir, 0777, true)) {
                    throw new Exception('Не удалось создать временную директорию для распаковки файла класса: ' . $tempDir);
                }
            }

            $scriptPathInZip = ltrim($this->processingClassFilePath, '/');
            $tempScriptPath = $tempDir . '/' . basename($this->processingClassFilePath);

            if ($zip->locateName($scriptPathInZip) !== false) {
                file_put_contents($tempScriptPath, $zip->getFromName($scriptPathInZip));
            } else {
                $zip->close();

                throw new Exception('Файл класса ' . $this->processingClassFilePath . ' не найден в архиве.');
            }


            /**
             * Обработка класса
             */

            // Загрузка исходного кода класса
            $sourceCode = file_get_contents($tempScriptPath);
            if (!$sourceCode) {
                throw new Exception('Ошибка чтения скрипта для обработки: ' . $tempScriptPath);
            }

            // Извлечение параметра descriptionOfFields класса
            $descriptionOfFields = [];
            preg_match('/\$descriptionOfFields\s*=\s*\[.*?];/sxui', $sourceCode, $matches);
            if (!empty($matches[0])) {
                // Преобразуем строку с массивом в реальный массив
                eval('$descriptionOfFields = ' . substr($matches[0], strpos($matches[0], '[')) . ';');
            } else {
                throw new Exception('Переменная $descriptionOfFields не найдена.');
            }

            if (!(count($descriptionOfFields)>0)) {

                throw new Exception('Нет полей! Поля обязаны быть!');
            }


            // ВОТ ЗДЕСЬ ТОЛЬКО ПРОВЕРИЛИ ГЛАВНЫЕ ПАРАМЕТРЫ СКРИПТА
            // ПРОВЕРИЛИ ФАЙЛ КЛАССА, можем создавать продукт

            // создаем новый продукт
            if (!$this->createProduct(1)) {
                throw new Exception('Не удалось создать запись нового продукта');
            }


            // добавляем поля на основе массива $descriptionOfFields
            if (!$this->createFields($descriptionOfFields)) {
                throw new Exception('Не удалось добавить поля. Возникла проблема. Возможно дублирующие записи');
            }

            // Куда будем сохранять файлы
            $this->dirSaveFiles = __DIR__ . "/../UMLyMG912T/PROD_files/prod-id-" . $this->idProduct;


            // Извлечение подключенных библиотек в use класса
            preg_match_all('/use\s+([^;]+);/', $sourceCode, $processingClassUseMatches);
            $processingClassUseStatements = $processingClassUseMatches[1] ?? [];

            // Извлечение статических методов класса
            preg_match_all(
                '/(public|private|protected)?\s*static\s+(public|private|protected)?\s*function\s+(\w+)\s*\((.*?)\)\s*(\{(?:[^{}]++|(?5))*+})(?=\s*(?:[pP]ublic|[pP]rivate|[pP]rotected|static|}[^}]*$|\s*\w+\s*\([^)]*\)\s*;|\s*\w+\s*=.*?;))/sxui',
                $sourceCode,
                $methodMatches,
                PREG_SET_ORDER
            );
            $staticMethods = [];
            foreach ($methodMatches as $match) {
                $staticMethods[$match[3]] = [
                    'visibility' => $match[1] ?: $match[2] ?: 'public',
                    'params' => $match[4],
                    'code' => $match[5],
                ];
            }

            // Подготовка статических методов для нового класса
            $methods = '';
            foreach ($staticMethods as $method => $data) {
                $methods .= "    static {$data['visibility']} function $method({$data['params']})\r\n    {$data['code']}\n\n";
            }

            // Создание кода нового класса
            $template = "<?php\r\n\r\nnamespace $this->classNamespace;";
            if (!empty($this->generatedClassLibrariesUse)) {
                $template .= "\r\n\r\nuse " . implode(", ", $this->generatedClassLibrariesUse) . ";";
            }
            $template .= "\r\n\r\nclass $this->generatedClassName\r\n{\r\n$methods\r\n}\r\n";



            // Создаем директорию для класса
            if (!is_dir($this->generatedClassPath)) {
                if (!mkdir($this->generatedClassPath, 0777, true)) {
                    throw new Exception('Не удалось создать директорию для нового класса: ' . $this->generatedClassPath);
                }
            }

            // Сохранение нового класса
            file_put_contents($this->generatedClassPath . '/' . $this->generatedClassName . '.php', $template);

            // Удаление временного файла оригинального класса
            @unlink($tempScriptPath);
            if (is_dir($tempDir)) {
                @rmdir($tempDir);
            }


            /**
             * Распаковка файлов из архива
             */
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $fileInZip = $zip->getNameIndex($i);
                $allowed = false;
                foreach ($this->allowedDirectoriesAndExtensions as $allowedDir => $settings) {
                    if (stripos($fileInZip, $allowedDir) === 0) {
                        $allowed = true;
                        $fileExtensionInZip = pathinfo($fileInZip, PATHINFO_EXTENSION);

                        if (
                            '*' === $settings['formats']
                            || in_array($fileExtensionInZip, $settings['formats'])
                        ) {
                            $destinationPath = $this->dirSaveFiles // в какую директорию сохраняем
//                                . ltrim($settings['path'], DS) // создавало лишнюю директорию
                                . DS
                                . str_replace('/', DS, $fileInZip);

                            if (DS === substr($destinationPath, -1)) {
                                // Это директория, создаём её
                                if (!is_dir($destinationPath)) {
                                    if (!mkdir($destinationPath, 0777, true)) {
                                        throw new Exception('Не удалось создать директорию для распаковки: ' . $destinationPath);
                                    }
                                }
                            } else {
                                // Это файл, создаём родительскую директорию
                                if (!is_dir(dirname($destinationPath))) {
                                    if (!mkdir(dirname($destinationPath), 0777, true)) {
                                        throw new Exception('Не удалось создать директорию для распаковки файла: ' . $destinationPath);
                                    }
                                }
                                // Записываем файл
                                file_put_contents($destinationPath, $zip->getFromIndex($i));
                            }
                        }
                        break;
                    }
                }

                if (!$allowed) {
                    $extraFiles[] = $fileInZip;
                }
            }

            $zip->close();

//            var_dump($this->dirSaveFiles . '/archive');

            // перемещаем архив и переименовываем его в начальное название
            if (mkdir($this->dirSaveFiles . '/archive', 0777, true)) {
                if (!rename($zipFilePath, $this->dirSaveFiles . '/archive/' . $this->initialTitle)) {

                    throw new Exception('Не удалось переместить и переименовать оригинальный архив');
                }
            } else {

                throw new Exception('Не удалось создать директорию для перемещения архива');
            }


            echo json_encode(['newProductID' => $this->idProduct]);

        } else {
            throw new Exception('Не удалось открыть ZIP-архив.');
        }


    }


    // создание нового продукта, получение ID


    private function downldImageArchive()
    {
        $getContent = file_get_contents('php://input');
        $decode = json_decode($getContent, 1);

        if (!is_array($decode) || !isset($decode['URL'])) {
            throw new Exception('No image archive URL');
        }

        $url = $decode['URL'];
        $this->initialTitle = basename(parse_url($url, PHP_URL_PATH));
        $this->installStaticMethod = 'create';
        $this->format = isset($this->allFileFormats['png']) ? $this->allFileFormats['png'] : 3;

        $x = 0;
        while ($x++ < 10) {
            $this->generatedClassName = $this->randomText();
            $this->pathScript = "CreateFiles/AllScripts/" . $this->generatedClassName;
            $this->generatedClassPath = __DIR__ . "/../UMLyMG912T/lib/Controllers/" . $this->pathScript;
            if (!file_exists($this->generatedClassPath)) break;
            $this->generatedClassPath = null;
        }

        if ($this->generatedClassPath == null) {
            throw new Exception('No free directory name for image script');
        }

        if (strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION)) !== 'zip') {
            throw new Exception('Only ZIP archives are allowed');
        }

        if (!is_dir($this->zipUploadPath) && !mkdir($this->zipUploadPath, 0777, true)) {
            throw new Exception('Cannot create archive upload directory: ' . $this->zipUploadPath);
        }

        $zipFilePath = $this->zipUploadPath . '/' . $this->generatedClassName . '.zip';
        if (!$this->downloadFile($zipFilePath, $url)) {
            throw new Exception('Cannot save image ZIP archive');
        }

        $tempDir = $this->tempUnzip . '/' . uniqid('imagezip_', true);
        if (!is_dir($tempDir) && !mkdir($tempDir, 0777, true)) {
            throw new Exception('Cannot create image archive temp directory');
        }

        $zip = new ZipArchive();
        if ($zip->open($zipFilePath) !== true) {
            throw new Exception('Cannot open image ZIP archive');
        }
        $this->safeExtractZip($zip, $tempDir);
        $zip->close();

        $archiveRoot = $this->detectImageArchiveRoot($tempDir);
        $classFile = $this->findFirstPhpFile($archiveRoot . '/lib/Controllers/MANUAL_Install/images');
        if (!$classFile) {
            throw new Exception('Image PHP file was not found in lib/Controllers/MANUAL_Install/images');
        }
        if (!is_dir($archiveRoot . '/PROD_files')) {
            throw new Exception('PROD_files directory was not found in image archive');
        }
        foreach (['PROD_scripts.sql', 'PROD_valid_fields.sql', 'navigation.sql', 'file_id.sql'] as $sqlFile) {
            if (!is_file($archiveRoot . '/' . $sqlFile)) {
                throw new Exception($sqlFile . ' was not found in image archive');
            }
        }

        $sourceCode = file_get_contents($classFile);
        if (!preg_match('/static\s+public\s+function\s+create\s*\(\s*array\s+\$data\s*,\s*\$dir\s*\)/i', $sourceCode)) {
            throw new Exception('Image script create(array $data, $dir) method was not found');
        }
        if (!preg_match('/return\s*\[\s*[\'\"]file[\'\"]\s*=>/i', $sourceCode)) {
            throw new Exception("Image script must return ['file' => ...]");
        }
        $this->assertImageScriptIsSafe($sourceCode);

        if (!$this->createProduct()) {
            throw new Exception('Cannot create image product');
        }

        $nameProduct = isset($decode['NAME_PRODUCT']) && trim($decode['NAME_PRODUCT']) !== '' ? trim($decode['NAME_PRODUCT']) : $this->generatedClassName;
        $description = isset($decode['DESCRIPTION']) ? trim($decode['DESCRIPTION']) : 'Image script';
        $price = isset($decode['PRICE']) ? (int)$decode['PRICE'] : 0;
        $currency = isset($decode['CURRENCY']) ? (int)$decode['CURRENCY'] : 1;

        DBase::fastQuery('UPDATE', 'PROD_scripts', [
            'NAME_PRODUCT' => $nameProduct,
            'DESCRIPTION' => $description,
            'PRICE' => $price,
            'CURRENCY' => $currency,
        ], ['ID' => $this->idProduct]);

        if ($navId = DBase::getValue('SELECT `ID_NAVIGATION` FROM `PROD_scripts` WHERE `ID` = ?', [$this->idProduct])) {
            DBase::fastQuery('UPDATE', 'navigation', ['TITLE' => $nameProduct], ['ID' => $navId]);
        }

        if (!is_dir($this->generatedClassPath) && !mkdir($this->generatedClassPath, 0777, true)) {
            throw new Exception('Cannot create image script class directory');
        }
        $newClassCode = $this->prepareImageClassCode($sourceCode, $this->generatedClassName);
        file_put_contents($this->generatedClassPath . '/' . $this->generatedClassName . '.php', $newClassCode);

        $this->dirSaveFiles = __DIR__ . "/../UMLyMG912T/PROD_files/prod-id-" . $this->idProduct;
        $prodDirs = glob($archiveRoot . '/PROD_files/prod-id-*', GLOB_ONLYDIR);
        if (!$prodDirs) {
            throw new Exception('PROD_files/prod-id-* directory was not found');
        }
        $this->copyDirectory($prodDirs[0], $this->dirSaveFiles);
        if (!is_dir($this->dirSaveFiles . '/archive') && !mkdir($this->dirSaveFiles . '/archive', 0777, true)) {
            throw new Exception('Cannot create image product archive directory');
        }
        copy($zipFilePath, $this->dirSaveFiles . '/archive/' . $this->initialTitle);

        $fields = $this->extractImageFields($sourceCode);
        if (!$fields) {
            $fields = ['text' => ['line_name' => 'Text', 'example' => 'Test value', 'id_valid_field' => 13]];
        }
        if (!$this->createFields($fields)) {
            throw new Exception('Cannot create image product fields');
        }

        $this->removeDirectory($tempDir);
        @unlink($zipFilePath);

        echo json_encode(['newProductID' => $this->idProduct]);
    }

    private function safeExtractZip(ZipArchive $zip, $destination)
    {
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = str_replace('\\', '/', $zip->getNameIndex($i));
            if ($name === '' || strpos($name, '../') !== false || strpos($name, '/..') !== false || preg_match('#^[A-Za-z]:/#', $name) || substr($name, 0, 1) === '/') {
                throw new Exception('Unsafe path in archive: ' . $name);
            }
        }
        if (!$zip->extractTo($destination)) {
            throw new Exception('Cannot extract image archive');
        }
    }

    private function detectImageArchiveRoot($dir)
    {
        $dir = rtrim($dir, '/');
        if (is_dir($dir . '/lib') || is_dir($dir . '/PROD_files')) return $dir;
        $items = array_values(array_filter(scandir($dir), function ($item) use ($dir) {
            return $item !== '.' && $item !== '..' && is_dir($dir . '/' . $item);
        }));
        if (count($items) === 1) {
            $nested = $dir . '/' . $items[0];
            if (is_dir($nested . '/lib') || is_dir($nested . '/PROD_files')) return $nested;
        }
        return $dir;
    }

    private function findFirstPhpFile($dir)
    {
        if (!is_dir($dir)) return null;
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
        foreach ($it as $file) {
            if ($file->isFile() && strtolower($file->getExtension()) === 'php') return $file->getPathname();
        }
        return null;
    }

    private function assertImageScriptIsSafe($sourceCode)
    {
        $patterns = [
            '/\bexec\s*\(/i' => 'exec()', '/\bshell_exec\s*\(/i' => 'shell_exec()', '/\bsystem\s*\(/i' => 'system()',
            '/\bpassthru\s*\(/i' => 'passthru()', '/\bproc_open\s*\(/i' => 'proc_open()', '/\bpopen\s*\(/i' => 'popen()',
            '/\bcurl_exec\s*\(/i' => 'curl_exec()', '/file_get_contents\s*\(\s*[\'\"]http/i' => 'file_get_contents(http...)',
            '/file_put_contents\s*\(\s*[\'\"]\//i' => 'file_put_contents(/...)',
        ];
        foreach ($patterns as $pattern => $label) {
            if (preg_match($pattern, $sourceCode)) {
                throw new Exception('Forbidden call in image script: ' . $label);
            }
        }
    }

    private function prepareImageClassCode($sourceCode, $className)
    {
        $sourceCode = preg_replace('/namespace\s+[^;]+;/i', 'namespace lib\\Controllers\\CreateFiles\\AllScripts;', $sourceCode, 1);
        $sourceCode = preg_replace('/class\s+([A-Za-z_][A-Za-z0-9_]*)/i', 'class ' . $className, $sourceCode, 1);
        if (strpos($sourceCode, 'namespace lib\\Controllers\\CreateFiles\\AllScripts;') === false) {
            $sourceCode = preg_replace('/^<\?php\s*/', "<?php\n\nnamespace lib\\Controllers\\CreateFiles\\AllScripts;\n\n", $sourceCode, 1);
        }
        return $sourceCode;
    }

    private function extractImageFields($sourceCode)
    {
        preg_match_all('/\$data\s*\[\s*[\'\"]([A-Za-z0-9_]+)[\'\"]\s*\]/', $sourceCode, $matches);
        $fields = [];
        foreach (array_unique($matches[1]) as $name) {
            $fields[$name] = [
                'line_name' => $this->titleFromFieldName($name),
                'example' => $this->exampleForFieldName($name),
                'id_valid_field' => 13,
            ];
        }
        return $fields;
    }

    private function titleFromFieldName($name)
    {
        $map = ['amount' => 'Amount', 'left_amount' => 'Left amount', 'right_amount' => 'Right amount', 'name' => 'Name', 'phone' => 'Phone', 'date' => 'Date', 'time' => 'Time'];
        return isset($map[$name]) ? $map[$name] : ucfirst(str_replace('_', ' ', $name));
    }

    private function exampleForFieldName($name)
    {
        if (stripos($name, 'phone') !== false) return '+79121234567';
        if (stripos($name, 'name') !== false) return 'Ivan Ivanov';
        if (stripos($name, 'date') !== false) return date('d.m.Y');
        if (stripos($name, 'time') !== false) return date('H:i');
        if (stripos($name, 'amount') !== false || stripos($name, 'sum') !== false) return '10000';
        return 'Test value';
    }

    private function copyDirectory($source, $destination)
    {
        if (!is_dir($destination) && !mkdir($destination, 0777, true)) {
            throw new Exception('Cannot create directory: ' . $destination);
        }
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
        foreach ($it as $item) {
            $target = $destination . DIRECTORY_SEPARATOR . $it->getSubPathName();
            if ($item->isDir()) {
                if (!is_dir($target) && !mkdir($target, 0777, true)) {
                    throw new Exception('Cannot create directory: ' . $target);
                }
            } else {
                copy($item->getPathname(), $target);
            }
        }
    }

    private function removeDirectory($dir)
    {
        if (!is_dir($dir)) return;
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($it as $item) {
            $item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
        }
        rmdir($dir);
    }


    private function createProduct($pdf = null)
    {

        if (!$navId = $this->createNavigation()) return false;

        $query = "INSERT INTO `PROD_scripts` (
                      `ID_NAVIGATION`,
                      `PATH_SCRIPT`,
                      `UNIQ_NAME_FILE`,
                      `FORMAT`,
                      `LIBRARY_CODE`,
                      `TEST`,
                      `CREATE_DATETIME`
                      )
                    VALUES (
                      :ID_NAVIGATION,
                      :PATH_SCRIPT,
                      :UNIQ_NAME_FILE,
                      :FORMAT,
                      :LIBRARY_CODE,
                      :TEST,
                      :CREATE_DATETIME
                    )";

        if ($this->pathScript) {
            $pathScript = $this->pathScript . '/' . $this->installStaticMethod;
        } else {
            $pathScript = NULL;
        }

        $args = [
            'ID_NAVIGATION' => $navId, // create - название метода для генерации PDF
            'PATH_SCRIPT' => $pathScript, // create - название метода для генерации PDF
            'UNIQ_NAME_FILE' => $pdf==1?1:NULL, // уникальное имя файла, по умолчанию всегда уникальное
            'FORMAT' => $this->format,
            'LIBRARY_CODE' => $pdf==1?2:NULL, // dompdf
            'TEST' => 1, // 2 режим тестирования, только админам видно
            'CREATE_DATETIME' => date('Y-m-d H:i:s')
        ];

        DBase::sql($query, $args);

        if ($this->idProduct = DBase::lastInsertId()) {
            return $this->idProduct;
        }

        return false;
    }


    private function createNavigation()
    {
        $query = "INSERT INTO `navigation` (
                      `TITLE`,
                      `CODE_MENU`,
                      `PARENT`,
                      `FILE`,
                      `APP_METHOD`
                      )
                    VALUES (
                      :TITLE,
                      :CODE_MENU,
                      :PARENT,
                      :FILE,
                      :APP_METHOD
                    )";


        $code = substr(md5(time().rand(1000,9999)), -10);


        $args = [
            'TITLE' => $code, // create - название метода для генерации PDF
            'CODE_MENU' => $code, // уникальное имя файла, по умолчанию всегда уникальное
            'PARENT' => 45,
            'FILE' => 174, // dompdf
            'APP_METHOD' => 'NavCreateFiles/index'
        ];

        DBase::sql($query, $args);

        if ($newNavId = DBase::lastInsertId()) {
            return $newNavId;
        }

        return false;
    }


    // Добавление строк для продукта
    private function createFields($descriptionOfFields)
    {
        if (!$this->idProduct) {
            throw new Exception('Нет ID продукта');
        }

        $addArray = [];

        $sort = 1;

        foreach ($descriptionOfFields as $nameField => $param)
        {
            $addArray[$sort] = [
                'ID_PRODUCT' => $this->idProduct,
                'NAME_FIELD' => $nameField,
                'TITLE_LINE' => $param['line_name'],
                'ID_VALID_FIELD' => $param['id_valid_field'] ?? $param['ID_VALID_FIELD'] ?? 13,
                'EXAMPLE' => $param['example'] ?? $param['EXAMPLE'] ?? null,
                'SORT' => $sort,
                'OPTIONAL' => NULL,
                'VALUE_IN_SCRIPT' => NULL
            ];

            // если имеется необязательное поле, тогда установим его НЕОБЯЗАТЕЛЬНЫМ
            if (isset($param['optional']) AND $param['optional'] == 1) {
                $addArray[$sort]['OPTIONAL'] = 1;
                $addArray[$sort]['VALUE_IN_SCRIPT'] = 1;
            }

            $sort++;
        }

//        var_dump($addArray);
//        exit;

        // если добавилось все, тогда получим true, или false
        if (DBase::OnDuplicateKeyUpdate('PROD_valid_fields', $addArray, 1) === false) {
            return false;
        }

        return true;
    }



    # форматы файлов по ID: ['pdf'] => 1
    private function fileFormats()
    {
        $formats = DBase::getRows('SELECT `ID`, `EXP` FROM `file_formats`');

        foreach ($formats as $format) {
            $this->allFileFormats[$format['EXP']] = $format['ID'];
        }
    }


    // скачиваем файл в нужную директорию
    private function downloadFile($pathSaveFile, $url)
    {
        return file_put_contents($pathSaveFile, fopen($url, 'r'));
    }


    private function randomText($length = 14)
    {
        $permitted_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $randomText = substr(str_shuffle($permitted_chars), 0, $length);
        return $randomText;
    }


    # Некоторые функции не поддерживают кириллицу. Эта функция изменит кириллицу на url encode
    private function encodeUrl($url) {
        $parts = parse_url($url);

        if (!$parts) return $url; // если parse_url не смог разобрать

        $scheme   = isset($parts['scheme']) ? $parts['scheme'] . '://' : '';
        $host     = $parts['host'] ?? '';
        $port     = isset($parts['port']) ? ':' . $parts['port'] : '';
        $user     = $parts['user'] ?? '';
        $pass     = isset($parts['pass']) ? ':' . $parts['pass']  : '';
        $pass     = ($user || $pass) ? "$pass@" : '';
        $path     = isset($parts['path']) ? implode('/', array_map('rawurlencode', explode('/', $parts['path']))) : '';
        $query    = isset($parts['query']) ? '?' . $parts['query'] : '';
        $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';

        return "$scheme$user$pass$host$port$path$query$fragment";
    }
}