xref: /plugin/mizarverifiabledocs/action.php (revision 42c7ffb24cdd209535c3b7872e023bbee50ebc2e)
16b7e227cSYamadaMiz<?php
247595f9cSYamadaMiz
347595f9cSYamadaMizuse dokuwiki\Extension\ActionPlugin;
447595f9cSYamadaMizuse dokuwiki\Extension\EventHandler;
547595f9cSYamadaMizuse dokuwiki\Extension\Event;
647595f9cSYamadaMiz
7f9af2148SYamadaMiz/**
8b68e3724SYamadaMiz * DokuWiki Plugin Mizar Verifiable Docs (Action Component)
9f9af2148SYamadaMiz *
10f9af2148SYamadaMiz * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
11*42c7ffb2SYamadaMiz * @author
12f9af2148SYamadaMiz */
13f9af2148SYamadaMiz
14b68e3724SYamadaMizclass action_plugin_mizarverifiabledocs extends ActionPlugin
1547595f9cSYamadaMiz{
1647595f9cSYamadaMiz    /**
174754b0a7SYamadaMiz     * Registers a callback function for a given event
1847595f9cSYamadaMiz     *
1947595f9cSYamadaMiz     * @param EventHandler $controller DokuWiki's event controller object
2047595f9cSYamadaMiz     * @return void
2147595f9cSYamadaMiz     */
2247595f9cSYamadaMiz    public function register(EventHandler $controller)
2347595f9cSYamadaMiz    {
246b7e227cSYamadaMiz        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call');
256b7e227cSYamadaMiz    }
266b7e227cSYamadaMiz
2747595f9cSYamadaMiz    /**
2847595f9cSYamadaMiz     * Handles AJAX requests
2947595f9cSYamadaMiz     *
3047595f9cSYamadaMiz     * @param Event $event
3147595f9cSYamadaMiz     * @param $param
3247595f9cSYamadaMiz     */
3347595f9cSYamadaMiz    public function handle_ajax_call(Event $event, $param)
3447595f9cSYamadaMiz    {
356b7e227cSYamadaMiz        unset($param); // 未使用のパラメータを無効化
3647595f9cSYamadaMiz
3747595f9cSYamadaMiz        switch ($event->data) {
3847595f9cSYamadaMiz            case 'clear_temp_files':
3947595f9cSYamadaMiz                $event->preventDefault();
4047595f9cSYamadaMiz                $event->stopPropagation();
41*42c7ffb2SYamadaMiz                $this->clearTempFiles();
4247595f9cSYamadaMiz                break;
4347595f9cSYamadaMiz            case 'source_sse':
446b7e227cSYamadaMiz                $event->preventDefault();
456b7e227cSYamadaMiz                $event->stopPropagation();
466b7e227cSYamadaMiz                $this->handleSourceSSERequest();
4747595f9cSYamadaMiz                break;
4847595f9cSYamadaMiz            case 'source_compile':
496b7e227cSYamadaMiz                $event->preventDefault();
506b7e227cSYamadaMiz                $event->stopPropagation();
516b7e227cSYamadaMiz                $this->handleSourceCompileRequest();
5247595f9cSYamadaMiz                break;
5347595f9cSYamadaMiz            case 'view_compile':
546b7e227cSYamadaMiz                $event->preventDefault();
556b7e227cSYamadaMiz                $event->stopPropagation();
566b7e227cSYamadaMiz                $this->handleViewCompileRequest();
5747595f9cSYamadaMiz                break;
5847595f9cSYamadaMiz            case 'view_sse':
596b7e227cSYamadaMiz                $event->preventDefault();
606b7e227cSYamadaMiz                $event->stopPropagation();
616b7e227cSYamadaMiz                $this->handleViewSSERequest();
6247595f9cSYamadaMiz                break;
639fc5dc4bSYamadaMiz            case 'create_combined_file':
649fc5dc4bSYamadaMiz                $event->preventDefault();
659fc5dc4bSYamadaMiz                $event->stopPropagation();
669fc5dc4bSYamadaMiz                $this->handle_create_combined_file();
679fc5dc4bSYamadaMiz                break;
686b7e227cSYamadaMiz        }
696b7e227cSYamadaMiz    }
706b7e227cSYamadaMiz
716b7e227cSYamadaMiz    // source用のコンパイルリクエスト処理
7247595f9cSYamadaMiz    private function handleSourceCompileRequest()
7347595f9cSYamadaMiz    {
746b7e227cSYamadaMiz        global $INPUT;
756b7e227cSYamadaMiz        $pageContent = $INPUT->post->str('content');
766b7e227cSYamadaMiz        $mizarData = $this->extractMizarContent($pageContent);
776b7e227cSYamadaMiz
78*42c7ffb2SYamadaMiz        // エラーチェックを追加
796b7e227cSYamadaMiz        if ($mizarData === null) {
806b7e227cSYamadaMiz            $this->sendAjaxResponse(false, 'Mizar content not found');
816b7e227cSYamadaMiz            return;
82*42c7ffb2SYamadaMiz        } elseif (isset($mizarData['error'])) {
83*42c7ffb2SYamadaMiz            $this->sendAjaxResponse(false, $mizarData['error']);
84*42c7ffb2SYamadaMiz            return;
856b7e227cSYamadaMiz        }
866b7e227cSYamadaMiz
876b7e227cSYamadaMiz        $filePath = $this->saveMizarContent($mizarData);
886b7e227cSYamadaMiz
896b7e227cSYamadaMiz        session_start();
906b7e227cSYamadaMiz        $_SESSION['source_filepath'] = $filePath;
916b7e227cSYamadaMiz
926b7e227cSYamadaMiz        $this->sendAjaxResponse(true, 'Mizar content processed successfully');
936b7e227cSYamadaMiz    }
946b7e227cSYamadaMiz
956b7e227cSYamadaMiz    // source用のSSEリクエスト処理
9647595f9cSYamadaMiz    private function handleSourceSSERequest()
9747595f9cSYamadaMiz    {
986b7e227cSYamadaMiz        header('Content-Type: text/event-stream');
996b7e227cSYamadaMiz        header('Cache-Control: no-cache');
1006b7e227cSYamadaMiz
1016b7e227cSYamadaMiz        session_start();
1026b7e227cSYamadaMiz        if (!isset($_SESSION['source_filepath'])) {
1036b7e227cSYamadaMiz            echo "data: Mizar file path not found in session\n\n";
1046b7e227cSYamadaMiz            ob_flush();
1056b7e227cSYamadaMiz            flush();
1066b7e227cSYamadaMiz            return;
1076b7e227cSYamadaMiz        }
1086b7e227cSYamadaMiz
1096b7e227cSYamadaMiz        $filePath = $_SESSION['source_filepath'];
1106b7e227cSYamadaMiz        $this->streamSourceOutput($filePath);
1116b7e227cSYamadaMiz
1126b7e227cSYamadaMiz        echo "event: end\n";
1136b7e227cSYamadaMiz        echo "data: Compilation complete\n\n";
1146b7e227cSYamadaMiz        ob_flush();
1156b7e227cSYamadaMiz        flush();
1166b7e227cSYamadaMiz    }
1176b7e227cSYamadaMiz
11847595f9cSYamadaMiz    // view用のコンパイルリクエスト処理
11947595f9cSYamadaMiz    private function handleViewCompileRequest()
12047595f9cSYamadaMiz    {
12147595f9cSYamadaMiz        global $INPUT;
12247595f9cSYamadaMiz        $content = $INPUT->post->str('content');
12347595f9cSYamadaMiz
12447595f9cSYamadaMiz        $filePath = $this->createTempFile($content);
12547595f9cSYamadaMiz
12647595f9cSYamadaMiz        session_start();
12747595f9cSYamadaMiz        $_SESSION['view_filepath'] = $filePath;
12847595f9cSYamadaMiz
12947595f9cSYamadaMiz        $this->sendAjaxResponse(true, 'Mizar content processed successfully');
13047595f9cSYamadaMiz    }
13147595f9cSYamadaMiz
13247595f9cSYamadaMiz    // view用のSSEリクエスト処理
13347595f9cSYamadaMiz    private function handleViewSSERequest()
13447595f9cSYamadaMiz    {
13547595f9cSYamadaMiz        header('Content-Type: text/event-stream');
13647595f9cSYamadaMiz        header('Cache-Control: no-cache');
13747595f9cSYamadaMiz
13847595f9cSYamadaMiz        session_start();
13947595f9cSYamadaMiz        if (!isset($_SESSION['view_filepath'])) {
14047595f9cSYamadaMiz            echo "data: Mizar file path not found in session\n\n";
14147595f9cSYamadaMiz            ob_flush();
14247595f9cSYamadaMiz            flush();
14347595f9cSYamadaMiz            return;
14447595f9cSYamadaMiz        }
14547595f9cSYamadaMiz
14647595f9cSYamadaMiz        $filePath = $_SESSION['view_filepath'];
1479fc5dc4bSYamadaMiz        $this->streamViewCompileOutput($filePath);
14847595f9cSYamadaMiz
14947595f9cSYamadaMiz        echo "event: end\n";
15047595f9cSYamadaMiz        echo "data: Compilation complete\n\n";
15147595f9cSYamadaMiz        ob_flush();
15247595f9cSYamadaMiz        flush();
15347595f9cSYamadaMiz    }
15447595f9cSYamadaMiz
15547595f9cSYamadaMiz    // Mizarコンテンツの抽出
15647595f9cSYamadaMiz    private function extractMizarContent($pageContent)
15747595f9cSYamadaMiz    {
1586b7e227cSYamadaMiz        $pattern = '/<mizar\s+([^>]+)>(.*?)<\/mizar>/s';
1596b7e227cSYamadaMiz        preg_match_all($pattern, $pageContent, $matches, PREG_SET_ORDER);
1606b7e227cSYamadaMiz
1616b7e227cSYamadaMiz        if (empty($matches)) {
16247595f9cSYamadaMiz            return null;
1636b7e227cSYamadaMiz        }
1646b7e227cSYamadaMiz
165*42c7ffb2SYamadaMiz        // 最初のファイル名を取得し、拡張子を除去
16647595f9cSYamadaMiz        $fileName = trim($matches[0][1]);
167*42c7ffb2SYamadaMiz        $fileNameWithoutExt = preg_replace('/\.miz$/i', '', $fileName);
168*42c7ffb2SYamadaMiz
169*42c7ffb2SYamadaMiz        // ファイル名のバリデーションを追加
170*42c7ffb2SYamadaMiz        if (!$this->isValidFileName($fileNameWithoutExt)) {
171*42c7ffb2SYamadaMiz            return ['error' => "Invalid characters in file name: '{$fileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."];
172*42c7ffb2SYamadaMiz        }
173*42c7ffb2SYamadaMiz
1746b7e227cSYamadaMiz        $combinedContent = '';
1756b7e227cSYamadaMiz
1766b7e227cSYamadaMiz        foreach ($matches as $match) {
177*42c7ffb2SYamadaMiz            $currentFileName = trim($match[1]);
178*42c7ffb2SYamadaMiz            $currentFileNameWithoutExt = preg_replace('/\.miz$/i', '', $currentFileName);
179*42c7ffb2SYamadaMiz
180*42c7ffb2SYamadaMiz            if ($currentFileNameWithoutExt !== $fileNameWithoutExt) {
181*42c7ffb2SYamadaMiz                return ['error' => "File name mismatch in <mizar> tags: '{$fileNameWithoutExt}' and '{$currentFileNameWithoutExt}'"];
1826b7e227cSYamadaMiz            }
183*42c7ffb2SYamadaMiz
184*42c7ffb2SYamadaMiz            // バリデーションを各ファイル名にも適用
185*42c7ffb2SYamadaMiz            if (!$this->isValidFileName($currentFileNameWithoutExt)) {
186*42c7ffb2SYamadaMiz                return ['error' => "Invalid characters in file name: '{$currentFileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."];
187*42c7ffb2SYamadaMiz            }
188*42c7ffb2SYamadaMiz
1896b7e227cSYamadaMiz            $combinedContent .= trim($match[2]) . "\n";
1906b7e227cSYamadaMiz        }
1916b7e227cSYamadaMiz
192*42c7ffb2SYamadaMiz        // ファイル名に拡張子を付加
193*42c7ffb2SYamadaMiz        $fullFileName = $fileNameWithoutExt . '.miz';
194*42c7ffb2SYamadaMiz
195*42c7ffb2SYamadaMiz        return ['fileName' => $fullFileName, 'content' => $combinedContent];
196*42c7ffb2SYamadaMiz    }
197*42c7ffb2SYamadaMiz
198*42c7ffb2SYamadaMiz    // ファイル名のバリデーション関数を追加
199*42c7ffb2SYamadaMiz    private function isValidFileName($fileName)
200*42c7ffb2SYamadaMiz    {
201*42c7ffb2SYamadaMiz        // ファイル名の長さをチェック(最大8文字)
202*42c7ffb2SYamadaMiz        if (strlen($fileName) > 8) {
203*42c7ffb2SYamadaMiz            return false;
204*42c7ffb2SYamadaMiz        }
205*42c7ffb2SYamadaMiz
206*42c7ffb2SYamadaMiz        // 許可される文字のみを含むかチェック
207*42c7ffb2SYamadaMiz        if (!preg_match('/^[A-Za-z0-9_\']+$/', $fileName)) {
208*42c7ffb2SYamadaMiz            return false;
209*42c7ffb2SYamadaMiz        }
210*42c7ffb2SYamadaMiz
211*42c7ffb2SYamadaMiz        return true;
2126b7e227cSYamadaMiz    }
2136b7e227cSYamadaMiz
21447595f9cSYamadaMiz    // Mizarコンテンツの保存
21547595f9cSYamadaMiz    private function saveMizarContent($mizarData)
21647595f9cSYamadaMiz    {
2176b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\');
2186b7e227cSYamadaMiz        $filePath = $workPath . "/TEXT/" . $mizarData['fileName'];
2196b7e227cSYamadaMiz        file_put_contents($filePath, $mizarData['content']);
2206b7e227cSYamadaMiz        return $filePath;
2216b7e227cSYamadaMiz    }
2226b7e227cSYamadaMiz
22347595f9cSYamadaMiz    // source用の出力をストリーム
22447595f9cSYamadaMiz    private function streamSourceOutput($filePath)
22547595f9cSYamadaMiz    {
2266b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\');
2276b7e227cSYamadaMiz        chdir($workPath);
2286b7e227cSYamadaMiz
2296b7e227cSYamadaMiz        $command = "miz2prel " . escapeshellarg($filePath);
2306b7e227cSYamadaMiz        $process = proc_open($command, array(1 => array("pipe", "w")), $pipes);
2316b7e227cSYamadaMiz
2326b7e227cSYamadaMiz        if (is_resource($process)) {
2336b7e227cSYamadaMiz            while ($line = fgets($pipes[1])) {
2346b7e227cSYamadaMiz                echo "data: " . $line . "\n\n";
2356b7e227cSYamadaMiz                ob_flush();
2366b7e227cSYamadaMiz                flush();
2376b7e227cSYamadaMiz            }
2386b7e227cSYamadaMiz            fclose($pipes[1]);
2399fc5dc4bSYamadaMiz
2409fc5dc4bSYamadaMiz            // エラー処理の追加
2419fc5dc4bSYamadaMiz            $errFilename = str_replace('.miz', '.err', $filePath);
2429fc5dc4bSYamadaMiz            if ($this->handleCompilationErrors($errFilename, rtrim($this->getConf('mizar_share_dir'), '/\\') . '/mizar.msg')) {
2439fc5dc4bSYamadaMiz                // エラーがあった場合は処理を終了
2449fc5dc4bSYamadaMiz                proc_close($process);
2459fc5dc4bSYamadaMiz                return;
2469fc5dc4bSYamadaMiz            }
2479fc5dc4bSYamadaMiz
2486b7e227cSYamadaMiz            proc_close($process);
2496b7e227cSYamadaMiz        }
2506b7e227cSYamadaMiz    }
2516b7e227cSYamadaMiz
25247595f9cSYamadaMiz    // view用の一時ファイル作成
25347595f9cSYamadaMiz    private function createTempFile($content)
25447595f9cSYamadaMiz    {
2556b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/';
25647595f9cSYamadaMiz        $uniqueName = str_replace('.', '_', uniqid('tmp', true));
2576b7e227cSYamadaMiz        $tempFilename = $workPath . $uniqueName . ".miz";
2586b7e227cSYamadaMiz        file_put_contents($tempFilename, $content);
2596b7e227cSYamadaMiz        return $tempFilename;
2606b7e227cSYamadaMiz    }
2616b7e227cSYamadaMiz
262*42c7ffb2SYamadaMiz    // 一時ファイルの削除
26347595f9cSYamadaMiz    private function clearTempFiles()
26447595f9cSYamadaMiz    {
26547595f9cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/';
26647595f9cSYamadaMiz        $files = glob($workPath . '*');  // TEXTフォルダ内のすべてのファイルを取得
26747595f9cSYamadaMiz
26847595f9cSYamadaMiz        $errors = [];
26947595f9cSYamadaMiz        foreach ($files as $file) {
27047595f9cSYamadaMiz            if (is_file($file)) {
27147595f9cSYamadaMiz                // ファイルが使用中かどうか確認
27247595f9cSYamadaMiz                if (!$this->is_file_locked($file)) {
27347595f9cSYamadaMiz                    $retries = 3; // 最大3回リトライ
27447595f9cSYamadaMiz                    while ($retries > 0) {
27547595f9cSYamadaMiz                        if (unlink($file)) {
27647595f9cSYamadaMiz                            break; // 削除成功
27747595f9cSYamadaMiz                        }
2785cbf3a53SYamadaMiz                        $errors[] = "Error deleting $file: " . error_get_last()['message'];
27947595f9cSYamadaMiz                        $retries--;
28047595f9cSYamadaMiz                        sleep(1); // 1秒待ってリトライ
28147595f9cSYamadaMiz                    }
28247595f9cSYamadaMiz                    if ($retries === 0) {
28347595f9cSYamadaMiz                        $errors[] = "Failed to delete: $file";  // 削除失敗
28447595f9cSYamadaMiz                    }
28547595f9cSYamadaMiz                } else {
28647595f9cSYamadaMiz                    $errors[] = "File is locked: $file";  // ファイルがロックされている
28747595f9cSYamadaMiz                }
28847595f9cSYamadaMiz            }
28947595f9cSYamadaMiz        }
29047595f9cSYamadaMiz
29147595f9cSYamadaMiz        if (empty($errors)) {
29247595f9cSYamadaMiz            $this->sendAjaxResponse(true, 'Temporary files cleared successfully');
29347595f9cSYamadaMiz        } else {
29447595f9cSYamadaMiz            $this->sendAjaxResponse(false, 'Some files could not be deleted', $errors);
29547595f9cSYamadaMiz        }
29647595f9cSYamadaMiz    }
29747595f9cSYamadaMiz
29847595f9cSYamadaMiz    // ファイルがロックされているかをチェックする関数
29947595f9cSYamadaMiz    private function is_file_locked($file)
30047595f9cSYamadaMiz    {
30147595f9cSYamadaMiz        $fileHandle = @fopen($file, "r+");
30247595f9cSYamadaMiz
30347595f9cSYamadaMiz        if ($fileHandle === false) {
30447595f9cSYamadaMiz            return true; // ファイルが開けない、つまりロックされているかアクセス権がない
30547595f9cSYamadaMiz        }
30647595f9cSYamadaMiz
30747595f9cSYamadaMiz        $locked = !flock($fileHandle, LOCK_EX | LOCK_NB); // ロックの取得を試みる(非ブロッキングモード)
30847595f9cSYamadaMiz
30947595f9cSYamadaMiz        fclose($fileHandle);
31047595f9cSYamadaMiz        return $locked; // ロックが取得できなければファイルはロックされている
31147595f9cSYamadaMiz    }
31247595f9cSYamadaMiz
3139fc5dc4bSYamadaMiz    // View用コンパイル出力のストリーム
3149fc5dc4bSYamadaMiz    private function streamViewCompileOutput($filePath)
31547595f9cSYamadaMiz    {
3166b7e227cSYamadaMiz        $workPath = $this->getConf('mizar_work_dir');
3176b7e227cSYamadaMiz        $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\') . '/';
3186b7e227cSYamadaMiz
3196b7e227cSYamadaMiz        chdir($workPath);
3206b7e227cSYamadaMiz
3219fc5dc4bSYamadaMiz        $errFilename = str_replace('.miz', '.err', $filePath);
3226b7e227cSYamadaMiz        $command = "makeenv " . escapeshellarg($filePath);
3236b7e227cSYamadaMiz        $process = proc_open($command, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $pipes);
3246b7e227cSYamadaMiz
3256b7e227cSYamadaMiz        if (is_resource($process)) {
3266b7e227cSYamadaMiz            while ($line = fgets($pipes[1])) {
3276b7e227cSYamadaMiz                echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3286b7e227cSYamadaMiz                ob_flush();
3296b7e227cSYamadaMiz                flush();
3306b7e227cSYamadaMiz            }
3316b7e227cSYamadaMiz            fclose($pipes[1]);
3326b7e227cSYamadaMiz
3336b7e227cSYamadaMiz            while ($line = fgets($pipes[2])) {
3346b7e227cSYamadaMiz                echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3356b7e227cSYamadaMiz                ob_flush();
3366b7e227cSYamadaMiz                flush();
3376b7e227cSYamadaMiz            }
3386b7e227cSYamadaMiz            fclose($pipes[2]);
3396b7e227cSYamadaMiz            proc_close($process);
3406b7e227cSYamadaMiz
3411174571dSYamadaMiz            // makeenvのエラー処理
3429fc5dc4bSYamadaMiz            if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) {
3436b7e227cSYamadaMiz                return;
3446b7e227cSYamadaMiz            }
3456b7e227cSYamadaMiz
3466b7e227cSYamadaMiz            // verifierの実行
3476b7e227cSYamadaMiz            $exePath = rtrim($this->getConf('mizar_exe_dir'), '/\\') . '/';
3486b7e227cSYamadaMiz            $verifierPath = escapeshellarg($exePath . "verifier");
3496b7e227cSYamadaMiz            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
3506b7e227cSYamadaMiz                $verifierPath .= ".exe";
3516b7e227cSYamadaMiz            }
3526b7e227cSYamadaMiz            $verifierCommand = $verifierPath . " -q -l " . escapeshellarg("TEXT/" . basename($filePath));
3536b7e227cSYamadaMiz
3546b7e227cSYamadaMiz            $verifierProcess = proc_open($verifierCommand, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $verifierPipes);
3556b7e227cSYamadaMiz
3566b7e227cSYamadaMiz            if (is_resource($verifierProcess)) {
3576b7e227cSYamadaMiz                while ($line = fgets($verifierPipes[1])) {
3586b7e227cSYamadaMiz                    echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3596b7e227cSYamadaMiz                    ob_flush();
3606b7e227cSYamadaMiz                    flush();
3616b7e227cSYamadaMiz                }
3626b7e227cSYamadaMiz                fclose($verifierPipes[1]);
3636b7e227cSYamadaMiz
3646b7e227cSYamadaMiz                while ($line = fgets($verifierPipes[2])) {
3656b7e227cSYamadaMiz                    echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3666b7e227cSYamadaMiz                    ob_flush();
3676b7e227cSYamadaMiz                    flush();
3686b7e227cSYamadaMiz                }
3696b7e227cSYamadaMiz                fclose($verifierPipes[2]);
3706b7e227cSYamadaMiz                proc_close($verifierProcess);
3711174571dSYamadaMiz
3721174571dSYamadaMiz                // verifierのエラー処理
3731174571dSYamadaMiz                if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) {
3741174571dSYamadaMiz                    return;
3751174571dSYamadaMiz                }
3766b7e227cSYamadaMiz            } else {
3776b7e227cSYamadaMiz                echo "data: ERROR: Failed to execute verifier command.\n\n";
3786b7e227cSYamadaMiz                ob_flush();
3796b7e227cSYamadaMiz                flush();
3806b7e227cSYamadaMiz            }
3816b7e227cSYamadaMiz        } else {
3826b7e227cSYamadaMiz            echo "data: ERROR: Failed to execute makeenv command.\n\n";
3836b7e227cSYamadaMiz            ob_flush();
3846b7e227cSYamadaMiz            flush();
3856b7e227cSYamadaMiz        }
3866b7e227cSYamadaMiz    }
3876b7e227cSYamadaMiz
38847595f9cSYamadaMiz    private function getMizarErrorMessages($mizarMsgFile)
38947595f9cSYamadaMiz    {
3906b7e227cSYamadaMiz        $errorMessages = [];
3916b7e227cSYamadaMiz        $lines = file($mizarMsgFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
3926b7e227cSYamadaMiz
3936b7e227cSYamadaMiz        $isReadingErrorMsg = false;
3946b7e227cSYamadaMiz        $key = 0;
3956b7e227cSYamadaMiz
3966b7e227cSYamadaMiz        foreach ($lines as $line) {
3976b7e227cSYamadaMiz            if (preg_match('/# (\d+)/', $line, $matches)) {
3986b7e227cSYamadaMiz                $isReadingErrorMsg = true;
3996b7e227cSYamadaMiz                $key = intval($matches[1]);
4006b7e227cSYamadaMiz            } elseif ($isReadingErrorMsg) {
4016b7e227cSYamadaMiz                $errorMessages[$key] = $line;
4026b7e227cSYamadaMiz                $isReadingErrorMsg = false;
4036b7e227cSYamadaMiz            }
4046b7e227cSYamadaMiz        }
4056b7e227cSYamadaMiz
4066b7e227cSYamadaMiz        return $errorMessages;
4076b7e227cSYamadaMiz    }
4086b7e227cSYamadaMiz
40947595f9cSYamadaMiz    private function sendAjaxResponse($success, $message, $data = '')
41047595f9cSYamadaMiz    {
4116b7e227cSYamadaMiz        header('Content-Type: application/json');
4126b7e227cSYamadaMiz        echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]);
4136b7e227cSYamadaMiz        exit;
4146b7e227cSYamadaMiz    }
4159fc5dc4bSYamadaMiz
4169fc5dc4bSYamadaMiz    private function handleCompilationErrors($errFilename, $mizarMsgFilePath)
4179fc5dc4bSYamadaMiz    {
4189fc5dc4bSYamadaMiz        if (file_exists($errFilename)) {
4199fc5dc4bSYamadaMiz            $errors = [];
4209fc5dc4bSYamadaMiz            $errorLines = file($errFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
4219fc5dc4bSYamadaMiz            foreach ($errorLines as $errorLine) {
4229fc5dc4bSYamadaMiz                if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $errorLine, $matches)) {
4239fc5dc4bSYamadaMiz                    $errorCode = intval($matches[3]);
4249fc5dc4bSYamadaMiz                    $errors[] = [
4259fc5dc4bSYamadaMiz                        'code' => $errorCode,
4269fc5dc4bSYamadaMiz                        'line' => intval($matches[1]),
4279fc5dc4bSYamadaMiz                        'column' => intval($matches[2]),
4289fc5dc4bSYamadaMiz                        'message' => $this->getMizarErrorMessages($mizarMsgFilePath)[$errorCode] ?? 'Unknown error'
4299fc5dc4bSYamadaMiz                    ];
4309fc5dc4bSYamadaMiz                }
4319fc5dc4bSYamadaMiz            }
4329fc5dc4bSYamadaMiz            if (!empty($errors)) {
4339fc5dc4bSYamadaMiz                echo "event: compileErrors\n";
4349fc5dc4bSYamadaMiz                echo "data: " . json_encode($errors) . "\n\n";
4359fc5dc4bSYamadaMiz                ob_flush();
4369fc5dc4bSYamadaMiz                flush();
4379fc5dc4bSYamadaMiz                return true;
4389fc5dc4bSYamadaMiz            }
4399fc5dc4bSYamadaMiz        }
4409fc5dc4bSYamadaMiz        return false;
4419fc5dc4bSYamadaMiz    }
4429fc5dc4bSYamadaMiz
4439fc5dc4bSYamadaMiz    private function handle_create_combined_file()
4449fc5dc4bSYamadaMiz    {
4459fc5dc4bSYamadaMiz        global $INPUT;
4469fc5dc4bSYamadaMiz
4479fc5dc4bSYamadaMiz        // 投稿されたコンテンツを取得
4489fc5dc4bSYamadaMiz        $combinedContent = $INPUT->post->str('content');
449a0444036SYamadaMiz        $filename = $INPUT->post->str('filename', 'combined_file.miz'); // デフォルトのファイル名を指定
4509fc5dc4bSYamadaMiz
451a0444036SYamadaMiz        // ファイルを保存せず、コンテンツを直接返す
452a0444036SYamadaMiz        if (!empty($combinedContent)) {
453a0444036SYamadaMiz            // ファイルの内容をレスポンスで返す(PHP側でファイルを作成しない)
454a0444036SYamadaMiz            $this->sendAjaxResponse(true, 'File created successfully', [
455a0444036SYamadaMiz                'filename' => $filename,
456a0444036SYamadaMiz                'content' => $combinedContent
457a0444036SYamadaMiz            ]);
458a0444036SYamadaMiz            error_log("File content sent: " . $filename);
4599fc5dc4bSYamadaMiz        } else {
460a0444036SYamadaMiz            $this->sendAjaxResponse(false, 'Content is empty, no file created');
4619fc5dc4bSYamadaMiz        }
4629fc5dc4bSYamadaMiz    }
4636b7e227cSYamadaMiz}
464