xref: /plugin/mizarverifiabledocs/action.php (revision 14f6cf5b8c7f685a15fa8c23f119ecc5a0265ef7)
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
114f65af2cSYamadaMiz * @author  Yamada, M.
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');
254f65af2cSYamadaMiz        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handle_tpl_content_display');
264f65af2cSYamadaMiz    }
274f65af2cSYamadaMiz
284f65af2cSYamadaMiz    /**
294f65af2cSYamadaMiz     * Handles the TPL_CONTENT_DISPLAY event to insert "Hide All" button
304f65af2cSYamadaMiz     *
314f65af2cSYamadaMiz     * @param Event $event
324f65af2cSYamadaMiz     * @param mixed $_param Unused parameter
334f65af2cSYamadaMiz     * @return void
344f65af2cSYamadaMiz     */
354f65af2cSYamadaMiz    public function handle_tpl_content_display(Event $event, $_param)
364f65af2cSYamadaMiz    {
374f65af2cSYamadaMiz        // データが文字列かどうか確認
384f65af2cSYamadaMiz        if (!is_string($event->data)) {
394f65af2cSYamadaMiz            error_log('handle_tpl_content_display: data is not a string! ' . print_r($event->data, true));
404f65af2cSYamadaMiz            return;
414f65af2cSYamadaMiz        }
424f65af2cSYamadaMiz
434f65af2cSYamadaMiz        $html = $event->data;
444f65af2cSYamadaMiz
454f65af2cSYamadaMiz        // "mizarWrapper" クラスが存在するか確認
464f65af2cSYamadaMiz        if (strpos($html, 'mizarWrapper') !== false) {
474f65af2cSYamadaMiz            // 既にボタンが挿入されているか確認(複数回挿入しないため)
484f65af2cSYamadaMiz            if (strpos($html, 'id="hideAllButton"') === false) {
494f65af2cSYamadaMiz                $buttonHtml = '<div class="hideAllContainer">'
504f65af2cSYamadaMiz                            . '<button id="hideAllButton" class="hide-all-button">Hide All</button>'
514f65af2cSYamadaMiz                            . '<button id="showAllButton" class="hide-all-button" style="display:none;">Show All</button>'
52fcd231cdSYamadaMiz                            . '<button id="resetAllButton" class="reset-all-button">Reset All</button>'    // ★ 追加
534f65af2cSYamadaMiz                            . '</div>';
544f65af2cSYamadaMiz
554f65af2cSYamadaMiz                // 先頭にボタンを挿入
564f65af2cSYamadaMiz                $html = $buttonHtml . $html;
574f65af2cSYamadaMiz                $event->data = $html;
584f65af2cSYamadaMiz                error_log('handle_tpl_content_display: "Hide All" ボタンを挿入しました。');
594f65af2cSYamadaMiz            } else {
604f65af2cSYamadaMiz                // ボタンが既に存在する場合
614f65af2cSYamadaMiz                error_log('handle_tpl_content_display: "Hide All" ボタンは既に存在します。');
624f65af2cSYamadaMiz            }
634f65af2cSYamadaMiz        }
646b7e227cSYamadaMiz    }
656b7e227cSYamadaMiz
6647595f9cSYamadaMiz    /**
6747595f9cSYamadaMiz     * Handles AJAX requests
6847595f9cSYamadaMiz     *
6947595f9cSYamadaMiz     * @param Event $event
7047595f9cSYamadaMiz     * @param $param
7147595f9cSYamadaMiz     */
7247595f9cSYamadaMiz    public function handle_ajax_call(Event $event, $param)
7347595f9cSYamadaMiz    {
746b7e227cSYamadaMiz        unset($param); // 未使用のパラメータを無効化
7547595f9cSYamadaMiz
7647595f9cSYamadaMiz        switch ($event->data) {
7747595f9cSYamadaMiz            case 'clear_temp_files':
7847595f9cSYamadaMiz                $event->preventDefault();
7947595f9cSYamadaMiz                $event->stopPropagation();
8042c7ffb2SYamadaMiz                $this->clearTempFiles();
8147595f9cSYamadaMiz                break;
8247595f9cSYamadaMiz            case 'source_sse':
836b7e227cSYamadaMiz                $event->preventDefault();
846b7e227cSYamadaMiz                $event->stopPropagation();
856b7e227cSYamadaMiz                $this->handleSourceSSERequest();
8647595f9cSYamadaMiz                break;
8747595f9cSYamadaMiz            case 'source_compile':
886b7e227cSYamadaMiz                $event->preventDefault();
896b7e227cSYamadaMiz                $event->stopPropagation();
906b7e227cSYamadaMiz                $this->handleSourceCompileRequest();
9147595f9cSYamadaMiz                break;
9247595f9cSYamadaMiz            case 'view_compile':
936b7e227cSYamadaMiz                $event->preventDefault();
946b7e227cSYamadaMiz                $event->stopPropagation();
956b7e227cSYamadaMiz                $this->handleViewCompileRequest();
9647595f9cSYamadaMiz                break;
9747595f9cSYamadaMiz            case 'view_sse':
986b7e227cSYamadaMiz                $event->preventDefault();
996b7e227cSYamadaMiz                $event->stopPropagation();
1006b7e227cSYamadaMiz                $this->handleViewSSERequest();
10147595f9cSYamadaMiz                break;
1029fc5dc4bSYamadaMiz            case 'create_combined_file':
1039fc5dc4bSYamadaMiz                $event->preventDefault();
1049fc5dc4bSYamadaMiz                $event->stopPropagation();
1059fc5dc4bSYamadaMiz                $this->handle_create_combined_file();
1069fc5dc4bSYamadaMiz                break;
1076b7e227cSYamadaMiz        }
1086b7e227cSYamadaMiz    }
1096b7e227cSYamadaMiz
1106b7e227cSYamadaMiz    // source用のコンパイルリクエスト処理
11147595f9cSYamadaMiz    private function handleSourceCompileRequest()
11247595f9cSYamadaMiz    {
1136b7e227cSYamadaMiz        global $INPUT;
1146b7e227cSYamadaMiz        $pageContent = $INPUT->post->str('content');
1156b7e227cSYamadaMiz        $mizarData = $this->extractMizarContent($pageContent);
1166b7e227cSYamadaMiz
11742c7ffb2SYamadaMiz        // エラーチェックを追加
1186b7e227cSYamadaMiz        if ($mizarData === null) {
1196b7e227cSYamadaMiz            $this->sendAjaxResponse(false, 'Mizar content not found');
1206b7e227cSYamadaMiz            return;
12142c7ffb2SYamadaMiz        } elseif (isset($mizarData['error'])) {
12242c7ffb2SYamadaMiz            $this->sendAjaxResponse(false, $mizarData['error']);
12342c7ffb2SYamadaMiz            return;
1246b7e227cSYamadaMiz        }
1256b7e227cSYamadaMiz
1266b7e227cSYamadaMiz        $filePath = $this->saveMizarContent($mizarData);
1276b7e227cSYamadaMiz
1286b7e227cSYamadaMiz        session_start();
1296b7e227cSYamadaMiz        $_SESSION['source_filepath'] = $filePath;
1306b7e227cSYamadaMiz
1316b7e227cSYamadaMiz        $this->sendAjaxResponse(true, 'Mizar content processed successfully');
1326b7e227cSYamadaMiz    }
1336b7e227cSYamadaMiz
1346b7e227cSYamadaMiz    // source用のSSEリクエスト処理
13547595f9cSYamadaMiz    private function handleSourceSSERequest()
13647595f9cSYamadaMiz    {
1376b7e227cSYamadaMiz        header('Content-Type: text/event-stream');
138*14f6cf5bSYamadaMiz        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
139*14f6cf5bSYamadaMiz        header('Pragma: no-cache');
140*14f6cf5bSYamadaMiz        header('Expires: 0');
1416b7e227cSYamadaMiz
1426b7e227cSYamadaMiz        session_start();
1436b7e227cSYamadaMiz        if (!isset($_SESSION['source_filepath'])) {
1446b7e227cSYamadaMiz            echo "data: Mizar file path not found in session\n\n";
1456b7e227cSYamadaMiz            ob_flush();
1466b7e227cSYamadaMiz            flush();
1476b7e227cSYamadaMiz            return;
1486b7e227cSYamadaMiz        }
1496b7e227cSYamadaMiz
1506b7e227cSYamadaMiz        $filePath = $_SESSION['source_filepath'];
1516b7e227cSYamadaMiz        $this->streamSourceOutput($filePath);
1526b7e227cSYamadaMiz
1536b7e227cSYamadaMiz        echo "event: end\n";
1546b7e227cSYamadaMiz        echo "data: Compilation complete\n\n";
1556b7e227cSYamadaMiz        ob_flush();
1566b7e227cSYamadaMiz        flush();
1576b7e227cSYamadaMiz    }
1586b7e227cSYamadaMiz
15947595f9cSYamadaMiz    // view用のコンパイルリクエスト処理
16047595f9cSYamadaMiz    private function handleViewCompileRequest()
16147595f9cSYamadaMiz    {
16247595f9cSYamadaMiz        global $INPUT;
16347595f9cSYamadaMiz        $content = $INPUT->post->str('content');
16447595f9cSYamadaMiz
16547595f9cSYamadaMiz        $filePath = $this->createTempFile($content);
16647595f9cSYamadaMiz
16747595f9cSYamadaMiz        session_start();
16847595f9cSYamadaMiz        $_SESSION['view_filepath'] = $filePath;
16947595f9cSYamadaMiz
17047595f9cSYamadaMiz        $this->sendAjaxResponse(true, 'Mizar content processed successfully');
17147595f9cSYamadaMiz    }
17247595f9cSYamadaMiz
17347595f9cSYamadaMiz    // view用のSSEリクエスト処理
17447595f9cSYamadaMiz    private function handleViewSSERequest()
17547595f9cSYamadaMiz    {
17647595f9cSYamadaMiz        header('Content-Type: text/event-stream');
177*14f6cf5bSYamadaMiz        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
178*14f6cf5bSYamadaMiz        header('Pragma: no-cache');
179*14f6cf5bSYamadaMiz        header('Expires: 0');
18047595f9cSYamadaMiz
18147595f9cSYamadaMiz        session_start();
18247595f9cSYamadaMiz        if (!isset($_SESSION['view_filepath'])) {
18347595f9cSYamadaMiz            echo "data: Mizar file path not found in session\n\n";
18447595f9cSYamadaMiz            ob_flush();
18547595f9cSYamadaMiz            flush();
18647595f9cSYamadaMiz            return;
18747595f9cSYamadaMiz        }
18847595f9cSYamadaMiz
18947595f9cSYamadaMiz        $filePath = $_SESSION['view_filepath'];
1909fc5dc4bSYamadaMiz        $this->streamViewCompileOutput($filePath);
19147595f9cSYamadaMiz
19247595f9cSYamadaMiz        echo "event: end\n";
19347595f9cSYamadaMiz        echo "data: Compilation complete\n\n";
19447595f9cSYamadaMiz        ob_flush();
19547595f9cSYamadaMiz        flush();
19647595f9cSYamadaMiz    }
19747595f9cSYamadaMiz
19847595f9cSYamadaMiz    // Mizarコンテンツの抽出
19947595f9cSYamadaMiz    private function extractMizarContent($pageContent)
20047595f9cSYamadaMiz    {
2016b7e227cSYamadaMiz        $pattern = '/<mizar\s+([^>]+)>(.*?)<\/mizar>/s';
2026b7e227cSYamadaMiz        preg_match_all($pattern, $pageContent, $matches, PREG_SET_ORDER);
2036b7e227cSYamadaMiz
2046b7e227cSYamadaMiz        if (empty($matches)) {
20547595f9cSYamadaMiz            return null;
2066b7e227cSYamadaMiz        }
2076b7e227cSYamadaMiz
20842c7ffb2SYamadaMiz        // 最初のファイル名を取得し、拡張子を除去
20947595f9cSYamadaMiz        $fileName = trim($matches[0][1]);
21042c7ffb2SYamadaMiz        $fileNameWithoutExt = preg_replace('/\.miz$/i', '', $fileName);
21142c7ffb2SYamadaMiz
21242c7ffb2SYamadaMiz        // ファイル名のバリデーションを追加
21342c7ffb2SYamadaMiz        if (!$this->isValidFileName($fileNameWithoutExt)) {
21442c7ffb2SYamadaMiz            return ['error' => "Invalid characters in file name: '{$fileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."];
21542c7ffb2SYamadaMiz        }
21642c7ffb2SYamadaMiz
2176b7e227cSYamadaMiz        $combinedContent = '';
2186b7e227cSYamadaMiz
2196b7e227cSYamadaMiz        foreach ($matches as $match) {
22042c7ffb2SYamadaMiz            $currentFileName = trim($match[1]);
22142c7ffb2SYamadaMiz            $currentFileNameWithoutExt = preg_replace('/\.miz$/i', '', $currentFileName);
22242c7ffb2SYamadaMiz
22342c7ffb2SYamadaMiz            if ($currentFileNameWithoutExt !== $fileNameWithoutExt) {
22442c7ffb2SYamadaMiz                return ['error' => "File name mismatch in <mizar> tags: '{$fileNameWithoutExt}' and '{$currentFileNameWithoutExt}'"];
2256b7e227cSYamadaMiz            }
22642c7ffb2SYamadaMiz
22742c7ffb2SYamadaMiz            // バリデーションを各ファイル名にも適用
22842c7ffb2SYamadaMiz            if (!$this->isValidFileName($currentFileNameWithoutExt)) {
22942c7ffb2SYamadaMiz                return ['error' => "Invalid characters in file name: '{$currentFileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."];
23042c7ffb2SYamadaMiz            }
23142c7ffb2SYamadaMiz
2326b7e227cSYamadaMiz            $combinedContent .= trim($match[2]) . "\n";
2336b7e227cSYamadaMiz        }
2346b7e227cSYamadaMiz
23542c7ffb2SYamadaMiz        // ファイル名に拡張子を付加
23642c7ffb2SYamadaMiz        $fullFileName = $fileNameWithoutExt . '.miz';
23742c7ffb2SYamadaMiz
23842c7ffb2SYamadaMiz        return ['fileName' => $fullFileName, 'content' => $combinedContent];
23942c7ffb2SYamadaMiz    }
24042c7ffb2SYamadaMiz
24142c7ffb2SYamadaMiz    // ファイル名のバリデーション関数を追加
24242c7ffb2SYamadaMiz    private function isValidFileName($fileName)
24342c7ffb2SYamadaMiz    {
24442c7ffb2SYamadaMiz        // ファイル名の長さをチェック(最大8文字)
24542c7ffb2SYamadaMiz        if (strlen($fileName) > 8) {
24642c7ffb2SYamadaMiz            return false;
24742c7ffb2SYamadaMiz        }
24842c7ffb2SYamadaMiz
24942c7ffb2SYamadaMiz        // 許可される文字のみを含むかチェック
25042c7ffb2SYamadaMiz        if (!preg_match('/^[A-Za-z0-9_\']+$/', $fileName)) {
25142c7ffb2SYamadaMiz            return false;
25242c7ffb2SYamadaMiz        }
25342c7ffb2SYamadaMiz
25442c7ffb2SYamadaMiz        return true;
2556b7e227cSYamadaMiz    }
2566b7e227cSYamadaMiz
25747595f9cSYamadaMiz    // Mizarコンテンツの保存
25847595f9cSYamadaMiz    private function saveMizarContent($mizarData)
25947595f9cSYamadaMiz    {
2606b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\');
2616b7e227cSYamadaMiz        $filePath = $workPath . "/TEXT/" . $mizarData['fileName'];
2626b7e227cSYamadaMiz        file_put_contents($filePath, $mizarData['content']);
2636b7e227cSYamadaMiz        return $filePath;
2646b7e227cSYamadaMiz    }
2656b7e227cSYamadaMiz
26647595f9cSYamadaMiz    // source用の出力をストリーム
26747595f9cSYamadaMiz    private function streamSourceOutput($filePath)
26847595f9cSYamadaMiz    {
2696b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\');
270a16ae7e3SYamadaMiz        $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\');
271a16ae7e3SYamadaMiz
272a16ae7e3SYamadaMiz        // ★追加:環境変数 MIZFILESをPHP内で設定
273a16ae7e3SYamadaMiz        putenv("MIZFILES=$sharePath");
274a16ae7e3SYamadaMiz
2756b7e227cSYamadaMiz        chdir($workPath);
2766b7e227cSYamadaMiz
2776b7e227cSYamadaMiz        $command = "miz2prel " . escapeshellarg($filePath);
2786b7e227cSYamadaMiz        $process = proc_open($command, array(1 => array("pipe", "w")), $pipes);
2796b7e227cSYamadaMiz
2806b7e227cSYamadaMiz        if (is_resource($process)) {
2816b7e227cSYamadaMiz            while ($line = fgets($pipes[1])) {
2826b7e227cSYamadaMiz                echo "data: " . $line . "\n\n";
2836b7e227cSYamadaMiz                ob_flush();
2846b7e227cSYamadaMiz                flush();
2856b7e227cSYamadaMiz            }
2866b7e227cSYamadaMiz            fclose($pipes[1]);
2879fc5dc4bSYamadaMiz
2889fc5dc4bSYamadaMiz            // エラー処理の追加
2899fc5dc4bSYamadaMiz            $errFilename = str_replace('.miz', '.err', $filePath);
2909fc5dc4bSYamadaMiz            if ($this->handleCompilationErrors($errFilename, rtrim($this->getConf('mizar_share_dir'), '/\\') . '/mizar.msg')) {
2919fc5dc4bSYamadaMiz                // エラーがあった場合は処理を終了
2929fc5dc4bSYamadaMiz                proc_close($process);
2939fc5dc4bSYamadaMiz                return;
2949fc5dc4bSYamadaMiz            }
2959fc5dc4bSYamadaMiz
2966b7e227cSYamadaMiz            proc_close($process);
2976b7e227cSYamadaMiz        }
2986b7e227cSYamadaMiz    }
2996b7e227cSYamadaMiz
30047595f9cSYamadaMiz    // view用の一時ファイル作成
30147595f9cSYamadaMiz    private function createTempFile($content)
30247595f9cSYamadaMiz    {
3036b7e227cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/';
30447595f9cSYamadaMiz        $uniqueName = str_replace('.', '_', uniqid('tmp', true));
3056b7e227cSYamadaMiz        $tempFilename = $workPath . $uniqueName . ".miz";
3066b7e227cSYamadaMiz        file_put_contents($tempFilename, $content);
3076b7e227cSYamadaMiz        return $tempFilename;
3086b7e227cSYamadaMiz    }
3096b7e227cSYamadaMiz
310*14f6cf5bSYamadaMiz    // 一時ファイルの削除 (clearTempFiles関数の修正版)
31147595f9cSYamadaMiz    private function clearTempFiles()
31247595f9cSYamadaMiz    {
31347595f9cSYamadaMiz        $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/';
31447595f9cSYamadaMiz        $files = glob($workPath . '*');  // TEXTフォルダ内のすべてのファイルを取得
31547595f9cSYamadaMiz
31647595f9cSYamadaMiz        $errors = [];
31747595f9cSYamadaMiz        foreach ($files as $file) {
31847595f9cSYamadaMiz            if (is_file($file)) {
31947595f9cSYamadaMiz                // ファイルが使用中かどうか確認
32047595f9cSYamadaMiz                if (!$this->is_file_locked($file)) {
321*14f6cf5bSYamadaMiz                    $retries = 5; // リトライを増やす
32247595f9cSYamadaMiz                    while ($retries > 0) {
323*14f6cf5bSYamadaMiz                        if (@unlink($file)) {  // ← エラー抑制を追加
32447595f9cSYamadaMiz                            break; // 削除成功
32547595f9cSYamadaMiz                        }
3265cbf3a53SYamadaMiz                        $errors[] = "Error deleting $file: " . error_get_last()['message'];
32747595f9cSYamadaMiz                        $retries--;
328*14f6cf5bSYamadaMiz                        sleep(2); // 2秒待ってリトライ
32947595f9cSYamadaMiz                    }
33047595f9cSYamadaMiz                    if ($retries === 0) {
33147595f9cSYamadaMiz                        $errors[] = "Failed to delete: $file";  // 削除失敗
33247595f9cSYamadaMiz                    }
33347595f9cSYamadaMiz                } else {
33447595f9cSYamadaMiz                    $errors[] = "File is locked: $file";  // ファイルがロックされている
33547595f9cSYamadaMiz                }
33647595f9cSYamadaMiz            }
33747595f9cSYamadaMiz        }
33847595f9cSYamadaMiz
33947595f9cSYamadaMiz        if (empty($errors)) {
34047595f9cSYamadaMiz            $this->sendAjaxResponse(true, 'Temporary files cleared successfully');
34147595f9cSYamadaMiz        } else {
34247595f9cSYamadaMiz            $this->sendAjaxResponse(false, 'Some files could not be deleted', $errors);
34347595f9cSYamadaMiz        }
34447595f9cSYamadaMiz    }
34547595f9cSYamadaMiz
346*14f6cf5bSYamadaMiz
34747595f9cSYamadaMiz    // ファイルがロックされているかをチェックする関数
34847595f9cSYamadaMiz    private function is_file_locked($file)
34947595f9cSYamadaMiz    {
35047595f9cSYamadaMiz        $fileHandle = @fopen($file, "r+");
35147595f9cSYamadaMiz
35247595f9cSYamadaMiz        if ($fileHandle === false) {
35347595f9cSYamadaMiz            return true; // ファイルが開けない、つまりロックされているかアクセス権がない
35447595f9cSYamadaMiz        }
35547595f9cSYamadaMiz
35647595f9cSYamadaMiz        $locked = !flock($fileHandle, LOCK_EX | LOCK_NB); // ロックの取得を試みる(非ブロッキングモード)
35747595f9cSYamadaMiz
35847595f9cSYamadaMiz        fclose($fileHandle);
35947595f9cSYamadaMiz        return $locked; // ロックが取得できなければファイルはロックされている
36047595f9cSYamadaMiz    }
36147595f9cSYamadaMiz
3629fc5dc4bSYamadaMiz    // View用コンパイル出力のストリーム
3639fc5dc4bSYamadaMiz    private function streamViewCompileOutput($filePath)
36447595f9cSYamadaMiz    {
3656b7e227cSYamadaMiz        $workPath = $this->getConf('mizar_work_dir');
3666b7e227cSYamadaMiz        $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\') . '/';
3676b7e227cSYamadaMiz
368a16ae7e3SYamadaMiz        // ★追加:環境変数 MIZFILESをPHP内で設定
369a16ae7e3SYamadaMiz        putenv("MIZFILES=$sharePath");
370a16ae7e3SYamadaMiz
3716b7e227cSYamadaMiz        chdir($workPath);
3726b7e227cSYamadaMiz
3739fc5dc4bSYamadaMiz        $errFilename = str_replace('.miz', '.err', $filePath);
3746b7e227cSYamadaMiz        $command = "makeenv " . escapeshellarg($filePath);
3756b7e227cSYamadaMiz        $process = proc_open($command, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $pipes);
3766b7e227cSYamadaMiz
3776b7e227cSYamadaMiz        if (is_resource($process)) {
3786b7e227cSYamadaMiz            while ($line = fgets($pipes[1])) {
3796b7e227cSYamadaMiz                echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3806b7e227cSYamadaMiz                ob_flush();
3816b7e227cSYamadaMiz                flush();
3826b7e227cSYamadaMiz            }
3836b7e227cSYamadaMiz            fclose($pipes[1]);
3846b7e227cSYamadaMiz
3856b7e227cSYamadaMiz            while ($line = fgets($pipes[2])) {
3866b7e227cSYamadaMiz                echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
3876b7e227cSYamadaMiz                ob_flush();
3886b7e227cSYamadaMiz                flush();
3896b7e227cSYamadaMiz            }
3906b7e227cSYamadaMiz            fclose($pipes[2]);
3916b7e227cSYamadaMiz            proc_close($process);
3926b7e227cSYamadaMiz
3931174571dSYamadaMiz            // makeenvのエラー処理
3949fc5dc4bSYamadaMiz            if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) {
3956b7e227cSYamadaMiz                return;
3966b7e227cSYamadaMiz            }
3976b7e227cSYamadaMiz
3986b7e227cSYamadaMiz            // verifierの実行
3996b7e227cSYamadaMiz            $exePath = rtrim($this->getConf('mizar_exe_dir'), '/\\') . '/';
4006b7e227cSYamadaMiz            $verifierPath = escapeshellarg($exePath . "verifier");
4016b7e227cSYamadaMiz            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
4026b7e227cSYamadaMiz                $verifierPath .= ".exe";
4036b7e227cSYamadaMiz            }
4046b7e227cSYamadaMiz            $verifierCommand = $verifierPath . " -q -l " . escapeshellarg("TEXT/" . basename($filePath));
4056b7e227cSYamadaMiz
4066b7e227cSYamadaMiz            $verifierProcess = proc_open($verifierCommand, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $verifierPipes);
4076b7e227cSYamadaMiz
4086b7e227cSYamadaMiz            if (is_resource($verifierProcess)) {
4096b7e227cSYamadaMiz                while ($line = fgets($verifierPipes[1])) {
4106b7e227cSYamadaMiz                    echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
4116b7e227cSYamadaMiz                    ob_flush();
4126b7e227cSYamadaMiz                    flush();
4136b7e227cSYamadaMiz                }
4146b7e227cSYamadaMiz                fclose($verifierPipes[1]);
4156b7e227cSYamadaMiz
4166b7e227cSYamadaMiz                while ($line = fgets($verifierPipes[2])) {
4176b7e227cSYamadaMiz                    echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n";
4186b7e227cSYamadaMiz                    ob_flush();
4196b7e227cSYamadaMiz                    flush();
4206b7e227cSYamadaMiz                }
4216b7e227cSYamadaMiz                fclose($verifierPipes[2]);
4226b7e227cSYamadaMiz                proc_close($verifierProcess);
4231174571dSYamadaMiz
4241174571dSYamadaMiz                // verifierのエラー処理
4251174571dSYamadaMiz                if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) {
4261174571dSYamadaMiz                    return;
4271174571dSYamadaMiz                }
4286b7e227cSYamadaMiz            } else {
4296b7e227cSYamadaMiz                echo "data: ERROR: Failed to execute verifier command.\n\n";
4306b7e227cSYamadaMiz                ob_flush();
4316b7e227cSYamadaMiz                flush();
4326b7e227cSYamadaMiz            }
4336b7e227cSYamadaMiz        } else {
4346b7e227cSYamadaMiz            echo "data: ERROR: Failed to execute makeenv command.\n\n";
4356b7e227cSYamadaMiz            ob_flush();
4366b7e227cSYamadaMiz            flush();
4376b7e227cSYamadaMiz        }
4386b7e227cSYamadaMiz    }
4396b7e227cSYamadaMiz
44047595f9cSYamadaMiz    private function getMizarErrorMessages($mizarMsgFile)
44147595f9cSYamadaMiz    {
4426b7e227cSYamadaMiz        $errorMessages = [];
4436b7e227cSYamadaMiz        $lines = file($mizarMsgFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
4446b7e227cSYamadaMiz
4456b7e227cSYamadaMiz        $isReadingErrorMsg = false;
4466b7e227cSYamadaMiz        $key = 0;
4476b7e227cSYamadaMiz
4486b7e227cSYamadaMiz        foreach ($lines as $line) {
4496b7e227cSYamadaMiz            if (preg_match('/# (\d+)/', $line, $matches)) {
4506b7e227cSYamadaMiz                $isReadingErrorMsg = true;
4516b7e227cSYamadaMiz                $key = intval($matches[1]);
4526b7e227cSYamadaMiz            } elseif ($isReadingErrorMsg) {
4536b7e227cSYamadaMiz                $errorMessages[$key] = $line;
4546b7e227cSYamadaMiz                $isReadingErrorMsg = false;
4556b7e227cSYamadaMiz            }
4566b7e227cSYamadaMiz        }
4576b7e227cSYamadaMiz
4586b7e227cSYamadaMiz        return $errorMessages;
4596b7e227cSYamadaMiz    }
4606b7e227cSYamadaMiz
46147595f9cSYamadaMiz    private function sendAjaxResponse($success, $message, $data = '')
46247595f9cSYamadaMiz    {
4636b7e227cSYamadaMiz        header('Content-Type: application/json');
464*14f6cf5bSYamadaMiz        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
465*14f6cf5bSYamadaMiz        header('Pragma: no-cache');
466*14f6cf5bSYamadaMiz        header('Expires: 0');
4676b7e227cSYamadaMiz        echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]);
4686b7e227cSYamadaMiz        exit;
4696b7e227cSYamadaMiz    }
4709fc5dc4bSYamadaMiz
4719fc5dc4bSYamadaMiz    private function handleCompilationErrors($errFilename, $mizarMsgFilePath)
4729fc5dc4bSYamadaMiz    {
4739fc5dc4bSYamadaMiz        if (file_exists($errFilename)) {
4749fc5dc4bSYamadaMiz            $errors = [];
4759fc5dc4bSYamadaMiz            $errorLines = file($errFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
4769fc5dc4bSYamadaMiz            foreach ($errorLines as $errorLine) {
4779fc5dc4bSYamadaMiz                if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $errorLine, $matches)) {
4789fc5dc4bSYamadaMiz                    $errorCode = intval($matches[3]);
4799fc5dc4bSYamadaMiz                    $errors[] = [
4809fc5dc4bSYamadaMiz                        'code' => $errorCode,
4819fc5dc4bSYamadaMiz                        'line' => intval($matches[1]),
4829fc5dc4bSYamadaMiz                        'column' => intval($matches[2]),
4839fc5dc4bSYamadaMiz                        'message' => $this->getMizarErrorMessages($mizarMsgFilePath)[$errorCode] ?? 'Unknown error'
4849fc5dc4bSYamadaMiz                    ];
4859fc5dc4bSYamadaMiz                }
4869fc5dc4bSYamadaMiz            }
4879fc5dc4bSYamadaMiz            if (!empty($errors)) {
4889fc5dc4bSYamadaMiz                echo "event: compileErrors\n";
4899fc5dc4bSYamadaMiz                echo "data: " . json_encode($errors) . "\n\n";
4909fc5dc4bSYamadaMiz                ob_flush();
4919fc5dc4bSYamadaMiz                flush();
4929fc5dc4bSYamadaMiz                return true;
4939fc5dc4bSYamadaMiz            }
4949fc5dc4bSYamadaMiz        }
4959fc5dc4bSYamadaMiz        return false;
4969fc5dc4bSYamadaMiz    }
4979fc5dc4bSYamadaMiz
4989fc5dc4bSYamadaMiz    private function handle_create_combined_file()
4999fc5dc4bSYamadaMiz    {
5009fc5dc4bSYamadaMiz        global $INPUT;
5019fc5dc4bSYamadaMiz
5029fc5dc4bSYamadaMiz        // 投稿されたコンテンツを取得
5039fc5dc4bSYamadaMiz        $combinedContent = $INPUT->post->str('content');
504a0444036SYamadaMiz        $filename = $INPUT->post->str('filename', 'combined_file.miz'); // デフォルトのファイル名を指定
5059fc5dc4bSYamadaMiz
506a0444036SYamadaMiz        // ファイルを保存せず、コンテンツを直接返す
507a0444036SYamadaMiz        if (!empty($combinedContent)) {
508a0444036SYamadaMiz            // ファイルの内容をレスポンスで返す(PHP側でファイルを作成しない)
509a0444036SYamadaMiz            $this->sendAjaxResponse(true, 'File created successfully', [
510a0444036SYamadaMiz                'filename' => $filename,
511a0444036SYamadaMiz                'content' => $combinedContent
512a0444036SYamadaMiz            ]);
513a0444036SYamadaMiz            error_log("File content sent: " . $filename);
5149fc5dc4bSYamadaMiz        } else {
515a0444036SYamadaMiz            $this->sendAjaxResponse(false, 'Content is empty, no file created');
5169fc5dc4bSYamadaMiz        }
5179fc5dc4bSYamadaMiz    }
5186b7e227cSYamadaMiz}
519