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 } 594f65af2cSYamadaMiz } 606b7e227cSYamadaMiz } 616b7e227cSYamadaMiz 6247595f9cSYamadaMiz /** 6347595f9cSYamadaMiz * Handles AJAX requests 6447595f9cSYamadaMiz * 6547595f9cSYamadaMiz * @param Event $event 6647595f9cSYamadaMiz * @param $param 6747595f9cSYamadaMiz */ 6847595f9cSYamadaMiz public function handle_ajax_call(Event $event, $param) 6947595f9cSYamadaMiz { 706b7e227cSYamadaMiz unset($param); // 未使用のパラメータを無効化 7147595f9cSYamadaMiz 7247595f9cSYamadaMiz switch ($event->data) { 7347595f9cSYamadaMiz case 'clear_temp_files': 7447595f9cSYamadaMiz $event->preventDefault(); 7547595f9cSYamadaMiz $event->stopPropagation(); 7642c7ffb2SYamadaMiz $this->clearTempFiles(); 7747595f9cSYamadaMiz break; 7847595f9cSYamadaMiz case 'source_sse': 796b7e227cSYamadaMiz $event->preventDefault(); 806b7e227cSYamadaMiz $event->stopPropagation(); 816b7e227cSYamadaMiz $this->handleSourceSSERequest(); 8247595f9cSYamadaMiz break; 8347595f9cSYamadaMiz case 'source_compile': 846b7e227cSYamadaMiz $event->preventDefault(); 856b7e227cSYamadaMiz $event->stopPropagation(); 866b7e227cSYamadaMiz $this->handleSourceCompileRequest(); 8747595f9cSYamadaMiz break; 8847595f9cSYamadaMiz case 'view_compile': 896b7e227cSYamadaMiz $event->preventDefault(); 906b7e227cSYamadaMiz $event->stopPropagation(); 916b7e227cSYamadaMiz $this->handleViewCompileRequest(); 9247595f9cSYamadaMiz break; 9347595f9cSYamadaMiz case 'view_sse': 946b7e227cSYamadaMiz $event->preventDefault(); 956b7e227cSYamadaMiz $event->stopPropagation(); 966b7e227cSYamadaMiz $this->handleViewSSERequest(); 9747595f9cSYamadaMiz break; 989fc5dc4bSYamadaMiz case 'create_combined_file': 999fc5dc4bSYamadaMiz $event->preventDefault(); 1009fc5dc4bSYamadaMiz $event->stopPropagation(); 1019fc5dc4bSYamadaMiz $this->handle_create_combined_file(); 1029fc5dc4bSYamadaMiz break; 103*3f7dd076SYamadaMiz case 'view_graph': 104*3f7dd076SYamadaMiz $event->preventDefault(); 105*3f7dd076SYamadaMiz $event->stopPropagation(); 106*3f7dd076SYamadaMiz $this->handleViewGraphRequest(); 107*3f7dd076SYamadaMiz break; 1086b7e227cSYamadaMiz } 1096b7e227cSYamadaMiz } 1106b7e227cSYamadaMiz 1116b7e227cSYamadaMiz // source用のコンパイルリクエスト処理 11247595f9cSYamadaMiz private function handleSourceCompileRequest() 11347595f9cSYamadaMiz { 1146b7e227cSYamadaMiz global $INPUT; 1156b7e227cSYamadaMiz $pageContent = $INPUT->post->str('content'); 1166b7e227cSYamadaMiz $mizarData = $this->extractMizarContent($pageContent); 1176b7e227cSYamadaMiz 11842c7ffb2SYamadaMiz // エラーチェックを追加 1196b7e227cSYamadaMiz if ($mizarData === null) { 1206b7e227cSYamadaMiz $this->sendAjaxResponse(false, 'Mizar content not found'); 1216b7e227cSYamadaMiz return; 12242c7ffb2SYamadaMiz } elseif (isset($mizarData['error'])) { 12342c7ffb2SYamadaMiz $this->sendAjaxResponse(false, $mizarData['error']); 12442c7ffb2SYamadaMiz return; 1256b7e227cSYamadaMiz } 1266b7e227cSYamadaMiz 1276b7e227cSYamadaMiz $filePath = $this->saveMizarContent($mizarData); 1286b7e227cSYamadaMiz 1296b7e227cSYamadaMiz session_start(); 1306b7e227cSYamadaMiz $_SESSION['source_filepath'] = $filePath; 1316b7e227cSYamadaMiz 1326b7e227cSYamadaMiz $this->sendAjaxResponse(true, 'Mizar content processed successfully'); 1336b7e227cSYamadaMiz } 1346b7e227cSYamadaMiz 1356b7e227cSYamadaMiz // source用のSSEリクエスト処理 13647595f9cSYamadaMiz private function handleSourceSSERequest() 13747595f9cSYamadaMiz { 1386b7e227cSYamadaMiz header('Content-Type: text/event-stream'); 13914f6cf5bSYamadaMiz header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); 14014f6cf5bSYamadaMiz header('Pragma: no-cache'); 14114f6cf5bSYamadaMiz header('Expires: 0'); 1426b7e227cSYamadaMiz 1436b7e227cSYamadaMiz session_start(); 1446b7e227cSYamadaMiz if (!isset($_SESSION['source_filepath'])) { 1456b7e227cSYamadaMiz echo "data: Mizar file path not found in session\n\n"; 1466b7e227cSYamadaMiz ob_flush(); 1476b7e227cSYamadaMiz flush(); 1486b7e227cSYamadaMiz return; 1496b7e227cSYamadaMiz } 1506b7e227cSYamadaMiz 1516b7e227cSYamadaMiz $filePath = $_SESSION['source_filepath']; 1526b7e227cSYamadaMiz $this->streamSourceOutput($filePath); 1536b7e227cSYamadaMiz 1546b7e227cSYamadaMiz echo "event: end\n"; 1556b7e227cSYamadaMiz echo "data: Compilation complete\n\n"; 1566b7e227cSYamadaMiz ob_flush(); 1576b7e227cSYamadaMiz flush(); 1586b7e227cSYamadaMiz } 1596b7e227cSYamadaMiz 16047595f9cSYamadaMiz // view用のコンパイルリクエスト処理 16147595f9cSYamadaMiz private function handleViewCompileRequest() 16247595f9cSYamadaMiz { 16347595f9cSYamadaMiz global $INPUT; 16447595f9cSYamadaMiz $content = $INPUT->post->str('content'); 16547595f9cSYamadaMiz 16647595f9cSYamadaMiz $filePath = $this->createTempFile($content); 16747595f9cSYamadaMiz 16847595f9cSYamadaMiz session_start(); 16947595f9cSYamadaMiz $_SESSION['view_filepath'] = $filePath; 17047595f9cSYamadaMiz 17147595f9cSYamadaMiz $this->sendAjaxResponse(true, 'Mizar content processed successfully'); 17247595f9cSYamadaMiz } 17347595f9cSYamadaMiz 17447595f9cSYamadaMiz // view用のSSEリクエスト処理 17547595f9cSYamadaMiz private function handleViewSSERequest() 17647595f9cSYamadaMiz { 17747595f9cSYamadaMiz header('Content-Type: text/event-stream'); 17814f6cf5bSYamadaMiz header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); 17914f6cf5bSYamadaMiz header('Pragma: no-cache'); 18014f6cf5bSYamadaMiz header('Expires: 0'); 18147595f9cSYamadaMiz 18247595f9cSYamadaMiz session_start(); 18347595f9cSYamadaMiz if (!isset($_SESSION['view_filepath'])) { 18447595f9cSYamadaMiz echo "data: Mizar file path not found in session\n\n"; 18547595f9cSYamadaMiz ob_flush(); 18647595f9cSYamadaMiz flush(); 18747595f9cSYamadaMiz return; 18847595f9cSYamadaMiz } 18947595f9cSYamadaMiz 19047595f9cSYamadaMiz $filePath = $_SESSION['view_filepath']; 1919fc5dc4bSYamadaMiz $this->streamViewCompileOutput($filePath); 19247595f9cSYamadaMiz 19347595f9cSYamadaMiz echo "event: end\n"; 19447595f9cSYamadaMiz echo "data: Compilation complete\n\n"; 19547595f9cSYamadaMiz ob_flush(); 19647595f9cSYamadaMiz flush(); 19747595f9cSYamadaMiz } 19847595f9cSYamadaMiz 199*3f7dd076SYamadaMiz /***** view_graph: 上から該当ブロックまでを結合し SVG を返す *****/ 200*3f7dd076SYamadaMiz private function handleViewGraphRequest() 201*3f7dd076SYamadaMiz { 202*3f7dd076SYamadaMiz global $INPUT; 203*3f7dd076SYamadaMiz $content = $INPUT->post->str('content', ''); 204*3f7dd076SYamadaMiz 205*3f7dd076SYamadaMiz // 空チェック 206*3f7dd076SYamadaMiz if ($content === '') { 207*3f7dd076SYamadaMiz $this->sendAjaxResponse(false, 'Empty content'); 208*3f7dd076SYamadaMiz return; 209*3f7dd076SYamadaMiz } 210*3f7dd076SYamadaMiz 211*3f7dd076SYamadaMiz // 一時 .miz ファイルを作成 212*3f7dd076SYamadaMiz $tmp = tempnam(sys_get_temp_dir(), 'miz'); 213*3f7dd076SYamadaMiz $miz = $tmp . '.miz'; 214*3f7dd076SYamadaMiz rename($tmp, $miz); // tempnam が拡張子無しなのでリネーム 215*3f7dd076SYamadaMiz file_put_contents($miz, $content); 216*3f7dd076SYamadaMiz 217*3f7dd076SYamadaMiz // Python 可視化スクリプト呼び出し 218*3f7dd076SYamadaMiz $parser = escapeshellarg(__DIR__ . '/script/miz2svg.py'); 219*3f7dd076SYamadaMiz $py = escapeshellcmd($this->getConf('py_cmd') ?: 'python'); 220*3f7dd076SYamadaMiz $svg = shell_exec("$py $parser ".escapeshellarg($miz)); 221*3f7dd076SYamadaMiz unlink($miz); // 後片付け 222*3f7dd076SYamadaMiz 223*3f7dd076SYamadaMiz // 結果返却 224*3f7dd076SYamadaMiz if ($svg) { 225*3f7dd076SYamadaMiz $this->sendAjaxResponse(true, 'success', ['svg' => $svg]); 226*3f7dd076SYamadaMiz } else { 227*3f7dd076SYamadaMiz $this->sendAjaxResponse(false, 'conversion failed'); 228*3f7dd076SYamadaMiz } 229*3f7dd076SYamadaMiz } 230*3f7dd076SYamadaMiz 23147595f9cSYamadaMiz // Mizarコンテンツの抽出 23247595f9cSYamadaMiz private function extractMizarContent($pageContent) 23347595f9cSYamadaMiz { 2346b7e227cSYamadaMiz $pattern = '/<mizar\s+([^>]+)>(.*?)<\/mizar>/s'; 2356b7e227cSYamadaMiz preg_match_all($pattern, $pageContent, $matches, PREG_SET_ORDER); 2366b7e227cSYamadaMiz 2376b7e227cSYamadaMiz if (empty($matches)) { 23847595f9cSYamadaMiz return null; 2396b7e227cSYamadaMiz } 2406b7e227cSYamadaMiz 24142c7ffb2SYamadaMiz // 最初のファイル名を取得し、拡張子を除去 24247595f9cSYamadaMiz $fileName = trim($matches[0][1]); 24342c7ffb2SYamadaMiz $fileNameWithoutExt = preg_replace('/\.miz$/i', '', $fileName); 24442c7ffb2SYamadaMiz 24542c7ffb2SYamadaMiz // ファイル名のバリデーションを追加 24642c7ffb2SYamadaMiz if (!$this->isValidFileName($fileNameWithoutExt)) { 24742c7ffb2SYamadaMiz return ['error' => "Invalid characters in file name: '{$fileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."]; 24842c7ffb2SYamadaMiz } 24942c7ffb2SYamadaMiz 2506b7e227cSYamadaMiz $combinedContent = ''; 2516b7e227cSYamadaMiz 2526b7e227cSYamadaMiz foreach ($matches as $match) { 25342c7ffb2SYamadaMiz $currentFileName = trim($match[1]); 25442c7ffb2SYamadaMiz $currentFileNameWithoutExt = preg_replace('/\.miz$/i', '', $currentFileName); 25542c7ffb2SYamadaMiz 25642c7ffb2SYamadaMiz if ($currentFileNameWithoutExt !== $fileNameWithoutExt) { 25742c7ffb2SYamadaMiz return ['error' => "File name mismatch in <mizar> tags: '{$fileNameWithoutExt}' and '{$currentFileNameWithoutExt}'"]; 2586b7e227cSYamadaMiz } 25942c7ffb2SYamadaMiz 26042c7ffb2SYamadaMiz // バリデーションを各ファイル名にも適用 26142c7ffb2SYamadaMiz if (!$this->isValidFileName($currentFileNameWithoutExt)) { 26242c7ffb2SYamadaMiz return ['error' => "Invalid characters in file name: '{$currentFileNameWithoutExt}'. Only letters, numbers, underscores (_), and apostrophes (') are allowed, up to 8 characters."]; 26342c7ffb2SYamadaMiz } 26442c7ffb2SYamadaMiz 2656b7e227cSYamadaMiz $combinedContent .= trim($match[2]) . "\n"; 2666b7e227cSYamadaMiz } 2676b7e227cSYamadaMiz 26842c7ffb2SYamadaMiz // ファイル名に拡張子を付加 26942c7ffb2SYamadaMiz $fullFileName = $fileNameWithoutExt . '.miz'; 27042c7ffb2SYamadaMiz 27142c7ffb2SYamadaMiz return ['fileName' => $fullFileName, 'content' => $combinedContent]; 27242c7ffb2SYamadaMiz } 27342c7ffb2SYamadaMiz 27442c7ffb2SYamadaMiz // ファイル名のバリデーション関数を追加 27542c7ffb2SYamadaMiz private function isValidFileName($fileName) 27642c7ffb2SYamadaMiz { 27742c7ffb2SYamadaMiz // ファイル名の長さをチェック(最大8文字) 27842c7ffb2SYamadaMiz if (strlen($fileName) > 8) { 27942c7ffb2SYamadaMiz return false; 28042c7ffb2SYamadaMiz } 28142c7ffb2SYamadaMiz 28242c7ffb2SYamadaMiz // 許可される文字のみを含むかチェック 28342c7ffb2SYamadaMiz if (!preg_match('/^[A-Za-z0-9_\']+$/', $fileName)) { 28442c7ffb2SYamadaMiz return false; 28542c7ffb2SYamadaMiz } 28642c7ffb2SYamadaMiz 28742c7ffb2SYamadaMiz return true; 2886b7e227cSYamadaMiz } 2896b7e227cSYamadaMiz 29047595f9cSYamadaMiz // Mizarコンテンツの保存 29147595f9cSYamadaMiz private function saveMizarContent($mizarData) 29247595f9cSYamadaMiz { 2936b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\'); 2946b7e227cSYamadaMiz $filePath = $workPath . "/TEXT/" . $mizarData['fileName']; 2956b7e227cSYamadaMiz file_put_contents($filePath, $mizarData['content']); 2966b7e227cSYamadaMiz return $filePath; 2976b7e227cSYamadaMiz } 2986b7e227cSYamadaMiz 29947595f9cSYamadaMiz // source用の出力をストリーム 30047595f9cSYamadaMiz private function streamSourceOutput($filePath) 30147595f9cSYamadaMiz { 3026b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\'); 303a16ae7e3SYamadaMiz $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\'); 304a16ae7e3SYamadaMiz 305a16ae7e3SYamadaMiz // ★追加:環境変数 MIZFILESをPHP内で設定 306a16ae7e3SYamadaMiz putenv("MIZFILES=$sharePath"); 307a16ae7e3SYamadaMiz 3086b7e227cSYamadaMiz chdir($workPath); 3096b7e227cSYamadaMiz 3106b7e227cSYamadaMiz $command = "miz2prel " . escapeshellarg($filePath); 3116b7e227cSYamadaMiz $process = proc_open($command, array(1 => array("pipe", "w")), $pipes); 3126b7e227cSYamadaMiz 3136b7e227cSYamadaMiz if (is_resource($process)) { 3146b7e227cSYamadaMiz while ($line = fgets($pipes[1])) { 3156b7e227cSYamadaMiz echo "data: " . $line . "\n\n"; 3166b7e227cSYamadaMiz ob_flush(); 3176b7e227cSYamadaMiz flush(); 3186b7e227cSYamadaMiz } 3196b7e227cSYamadaMiz fclose($pipes[1]); 3209fc5dc4bSYamadaMiz 3219fc5dc4bSYamadaMiz // エラー処理の追加 3229fc5dc4bSYamadaMiz $errFilename = str_replace('.miz', '.err', $filePath); 3239fc5dc4bSYamadaMiz if ($this->handleCompilationErrors($errFilename, rtrim($this->getConf('mizar_share_dir'), '/\\') . '/mizar.msg')) { 3249fc5dc4bSYamadaMiz // エラーがあった場合は処理を終了 3259fc5dc4bSYamadaMiz proc_close($process); 3269fc5dc4bSYamadaMiz return; 3279fc5dc4bSYamadaMiz } 3289fc5dc4bSYamadaMiz 3296b7e227cSYamadaMiz proc_close($process); 3306b7e227cSYamadaMiz } 3316b7e227cSYamadaMiz } 3326b7e227cSYamadaMiz 33347595f9cSYamadaMiz // view用の一時ファイル作成 33447595f9cSYamadaMiz private function createTempFile($content) 33547595f9cSYamadaMiz { 3366b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/'; 33747595f9cSYamadaMiz $uniqueName = str_replace('.', '_', uniqid('tmp', true)); 3386b7e227cSYamadaMiz $tempFilename = $workPath . $uniqueName . ".miz"; 3396b7e227cSYamadaMiz file_put_contents($tempFilename, $content); 3406b7e227cSYamadaMiz return $tempFilename; 3416b7e227cSYamadaMiz } 3426b7e227cSYamadaMiz 34314f6cf5bSYamadaMiz // 一時ファイルの削除 (clearTempFiles関数の修正版) 34447595f9cSYamadaMiz private function clearTempFiles() 34547595f9cSYamadaMiz { 34647595f9cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/'; 34747595f9cSYamadaMiz $files = glob($workPath . '*'); // TEXTフォルダ内のすべてのファイルを取得 34847595f9cSYamadaMiz 34947595f9cSYamadaMiz $errors = []; 35047595f9cSYamadaMiz foreach ($files as $file) { 35147595f9cSYamadaMiz if (is_file($file)) { 35247595f9cSYamadaMiz // ファイルが使用中かどうか確認 35347595f9cSYamadaMiz if (!$this->is_file_locked($file)) { 35414f6cf5bSYamadaMiz $retries = 5; // リトライを増やす 35547595f9cSYamadaMiz while ($retries > 0) { 35614f6cf5bSYamadaMiz if (@unlink($file)) { // ← エラー抑制を追加 35747595f9cSYamadaMiz break; // 削除成功 35847595f9cSYamadaMiz } 3595cbf3a53SYamadaMiz $errors[] = "Error deleting $file: " . error_get_last()['message']; 36047595f9cSYamadaMiz $retries--; 36114f6cf5bSYamadaMiz sleep(2); // 2秒待ってリトライ 36247595f9cSYamadaMiz } 36347595f9cSYamadaMiz if ($retries === 0) { 36447595f9cSYamadaMiz $errors[] = "Failed to delete: $file"; // 削除失敗 36547595f9cSYamadaMiz } 36647595f9cSYamadaMiz } else { 36747595f9cSYamadaMiz $errors[] = "File is locked: $file"; // ファイルがロックされている 36847595f9cSYamadaMiz } 36947595f9cSYamadaMiz } 37047595f9cSYamadaMiz } 37147595f9cSYamadaMiz 37247595f9cSYamadaMiz if (empty($errors)) { 37347595f9cSYamadaMiz $this->sendAjaxResponse(true, 'Temporary files cleared successfully'); 37447595f9cSYamadaMiz } else { 37547595f9cSYamadaMiz $this->sendAjaxResponse(false, 'Some files could not be deleted', $errors); 37647595f9cSYamadaMiz } 37747595f9cSYamadaMiz } 37847595f9cSYamadaMiz 37914f6cf5bSYamadaMiz 38047595f9cSYamadaMiz // ファイルがロックされているかをチェックする関数 38147595f9cSYamadaMiz private function is_file_locked($file) 38247595f9cSYamadaMiz { 38347595f9cSYamadaMiz $fileHandle = @fopen($file, "r+"); 38447595f9cSYamadaMiz 38547595f9cSYamadaMiz if ($fileHandle === false) { 38647595f9cSYamadaMiz return true; // ファイルが開けない、つまりロックされているかアクセス権がない 38747595f9cSYamadaMiz } 38847595f9cSYamadaMiz 38947595f9cSYamadaMiz $locked = !flock($fileHandle, LOCK_EX | LOCK_NB); // ロックの取得を試みる(非ブロッキングモード) 39047595f9cSYamadaMiz 39147595f9cSYamadaMiz fclose($fileHandle); 39247595f9cSYamadaMiz return $locked; // ロックが取得できなければファイルはロックされている 39347595f9cSYamadaMiz } 39447595f9cSYamadaMiz 3959fc5dc4bSYamadaMiz // View用コンパイル出力のストリーム 3969fc5dc4bSYamadaMiz private function streamViewCompileOutput($filePath) 39747595f9cSYamadaMiz { 3986b7e227cSYamadaMiz $workPath = $this->getConf('mizar_work_dir'); 3996b7e227cSYamadaMiz $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\') . '/'; 4006b7e227cSYamadaMiz 401a16ae7e3SYamadaMiz // ★追加:環境変数 MIZFILESをPHP内で設定 402a16ae7e3SYamadaMiz putenv("MIZFILES=$sharePath"); 403a16ae7e3SYamadaMiz 4046b7e227cSYamadaMiz chdir($workPath); 4056b7e227cSYamadaMiz 4069fc5dc4bSYamadaMiz $errFilename = str_replace('.miz', '.err', $filePath); 4076b7e227cSYamadaMiz $command = "makeenv " . escapeshellarg($filePath); 4086b7e227cSYamadaMiz $process = proc_open($command, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $pipes); 4096b7e227cSYamadaMiz 4106b7e227cSYamadaMiz if (is_resource($process)) { 4116b7e227cSYamadaMiz while ($line = fgets($pipes[1])) { 4126b7e227cSYamadaMiz echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 4136b7e227cSYamadaMiz ob_flush(); 4146b7e227cSYamadaMiz flush(); 4156b7e227cSYamadaMiz } 4166b7e227cSYamadaMiz fclose($pipes[1]); 4176b7e227cSYamadaMiz 4186b7e227cSYamadaMiz while ($line = fgets($pipes[2])) { 4196b7e227cSYamadaMiz echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 4206b7e227cSYamadaMiz ob_flush(); 4216b7e227cSYamadaMiz flush(); 4226b7e227cSYamadaMiz } 4236b7e227cSYamadaMiz fclose($pipes[2]); 4246b7e227cSYamadaMiz proc_close($process); 4256b7e227cSYamadaMiz 4261174571dSYamadaMiz // makeenvのエラー処理 4279fc5dc4bSYamadaMiz if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) { 4286b7e227cSYamadaMiz return; 4296b7e227cSYamadaMiz } 4306b7e227cSYamadaMiz 4316b7e227cSYamadaMiz // verifierの実行 4326b7e227cSYamadaMiz $exePath = rtrim($this->getConf('mizar_exe_dir'), '/\\') . '/'; 4336b7e227cSYamadaMiz $verifierPath = escapeshellarg($exePath . "verifier"); 4346b7e227cSYamadaMiz if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 4356b7e227cSYamadaMiz $verifierPath .= ".exe"; 4366b7e227cSYamadaMiz } 4376b7e227cSYamadaMiz $verifierCommand = $verifierPath . " -q -l " . escapeshellarg("TEXT/" . basename($filePath)); 4386b7e227cSYamadaMiz 4396b7e227cSYamadaMiz $verifierProcess = proc_open($verifierCommand, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $verifierPipes); 4406b7e227cSYamadaMiz 4416b7e227cSYamadaMiz if (is_resource($verifierProcess)) { 4426b7e227cSYamadaMiz while ($line = fgets($verifierPipes[1])) { 4436b7e227cSYamadaMiz echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 4446b7e227cSYamadaMiz ob_flush(); 4456b7e227cSYamadaMiz flush(); 4466b7e227cSYamadaMiz } 4476b7e227cSYamadaMiz fclose($verifierPipes[1]); 4486b7e227cSYamadaMiz 4496b7e227cSYamadaMiz while ($line = fgets($verifierPipes[2])) { 4506b7e227cSYamadaMiz echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 4516b7e227cSYamadaMiz ob_flush(); 4526b7e227cSYamadaMiz flush(); 4536b7e227cSYamadaMiz } 4546b7e227cSYamadaMiz fclose($verifierPipes[2]); 4556b7e227cSYamadaMiz proc_close($verifierProcess); 4561174571dSYamadaMiz 4571174571dSYamadaMiz // verifierのエラー処理 4581174571dSYamadaMiz if ($this->handleCompilationErrors($errFilename, $sharePath . '/mizar.msg')) { 4591174571dSYamadaMiz return; 4601174571dSYamadaMiz } 4616b7e227cSYamadaMiz } else { 4626b7e227cSYamadaMiz echo "data: ERROR: Failed to execute verifier command.\n\n"; 4636b7e227cSYamadaMiz ob_flush(); 4646b7e227cSYamadaMiz flush(); 4656b7e227cSYamadaMiz } 4666b7e227cSYamadaMiz } else { 4676b7e227cSYamadaMiz echo "data: ERROR: Failed to execute makeenv command.\n\n"; 4686b7e227cSYamadaMiz ob_flush(); 4696b7e227cSYamadaMiz flush(); 4706b7e227cSYamadaMiz } 4716b7e227cSYamadaMiz } 4726b7e227cSYamadaMiz 47347595f9cSYamadaMiz private function getMizarErrorMessages($mizarMsgFile) 47447595f9cSYamadaMiz { 4756b7e227cSYamadaMiz $errorMessages = []; 4766b7e227cSYamadaMiz $lines = file($mizarMsgFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 4776b7e227cSYamadaMiz 4786b7e227cSYamadaMiz $isReadingErrorMsg = false; 4796b7e227cSYamadaMiz $key = 0; 4806b7e227cSYamadaMiz 4816b7e227cSYamadaMiz foreach ($lines as $line) { 4826b7e227cSYamadaMiz if (preg_match('/# (\d+)/', $line, $matches)) { 4836b7e227cSYamadaMiz $isReadingErrorMsg = true; 4846b7e227cSYamadaMiz $key = intval($matches[1]); 4856b7e227cSYamadaMiz } elseif ($isReadingErrorMsg) { 4866b7e227cSYamadaMiz $errorMessages[$key] = $line; 4876b7e227cSYamadaMiz $isReadingErrorMsg = false; 4886b7e227cSYamadaMiz } 4896b7e227cSYamadaMiz } 4906b7e227cSYamadaMiz 4916b7e227cSYamadaMiz return $errorMessages; 4926b7e227cSYamadaMiz } 4936b7e227cSYamadaMiz 49447595f9cSYamadaMiz private function sendAjaxResponse($success, $message, $data = '') 49547595f9cSYamadaMiz { 4966b7e227cSYamadaMiz header('Content-Type: application/json'); 49714f6cf5bSYamadaMiz header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); 49814f6cf5bSYamadaMiz header('Pragma: no-cache'); 49914f6cf5bSYamadaMiz header('Expires: 0'); 5006b7e227cSYamadaMiz echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]); 5016b7e227cSYamadaMiz exit; 5026b7e227cSYamadaMiz } 5039fc5dc4bSYamadaMiz 5049fc5dc4bSYamadaMiz private function handleCompilationErrors($errFilename, $mizarMsgFilePath) 5059fc5dc4bSYamadaMiz { 5069fc5dc4bSYamadaMiz if (file_exists($errFilename)) { 5079fc5dc4bSYamadaMiz $errors = []; 5089fc5dc4bSYamadaMiz $errorLines = file($errFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 5099fc5dc4bSYamadaMiz foreach ($errorLines as $errorLine) { 5109fc5dc4bSYamadaMiz if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $errorLine, $matches)) { 5119fc5dc4bSYamadaMiz $errorCode = intval($matches[3]); 5129fc5dc4bSYamadaMiz $errors[] = [ 5139fc5dc4bSYamadaMiz 'code' => $errorCode, 5149fc5dc4bSYamadaMiz 'line' => intval($matches[1]), 5159fc5dc4bSYamadaMiz 'column' => intval($matches[2]), 5169fc5dc4bSYamadaMiz 'message' => $this->getMizarErrorMessages($mizarMsgFilePath)[$errorCode] ?? 'Unknown error' 5179fc5dc4bSYamadaMiz ]; 5189fc5dc4bSYamadaMiz } 5199fc5dc4bSYamadaMiz } 5209fc5dc4bSYamadaMiz if (!empty($errors)) { 5219fc5dc4bSYamadaMiz echo "event: compileErrors\n"; 5229fc5dc4bSYamadaMiz echo "data: " . json_encode($errors) . "\n\n"; 5239fc5dc4bSYamadaMiz ob_flush(); 5249fc5dc4bSYamadaMiz flush(); 5259fc5dc4bSYamadaMiz return true; 5269fc5dc4bSYamadaMiz } 5279fc5dc4bSYamadaMiz } 5289fc5dc4bSYamadaMiz return false; 5299fc5dc4bSYamadaMiz } 5309fc5dc4bSYamadaMiz 5319fc5dc4bSYamadaMiz private function handle_create_combined_file() 5329fc5dc4bSYamadaMiz { 5339fc5dc4bSYamadaMiz global $INPUT; 5349fc5dc4bSYamadaMiz 5359fc5dc4bSYamadaMiz // 投稿されたコンテンツを取得 5369fc5dc4bSYamadaMiz $combinedContent = $INPUT->post->str('content'); 537a0444036SYamadaMiz $filename = $INPUT->post->str('filename', 'combined_file.miz'); // デフォルトのファイル名を指定 5389fc5dc4bSYamadaMiz 539a0444036SYamadaMiz // ファイルを保存せず、コンテンツを直接返す 540a0444036SYamadaMiz if (!empty($combinedContent)) { 541a0444036SYamadaMiz // ファイルの内容をレスポンスで返す(PHP側でファイルを作成しない) 542a0444036SYamadaMiz $this->sendAjaxResponse(true, 'File created successfully', [ 543a0444036SYamadaMiz 'filename' => $filename, 544a0444036SYamadaMiz 'content' => $combinedContent 545a0444036SYamadaMiz ]); 546a0444036SYamadaMiz error_log("File content sent: " . $filename); 5479fc5dc4bSYamadaMiz } else { 548a0444036SYamadaMiz $this->sendAjaxResponse(false, 'Content is empty, no file created'); 5499fc5dc4bSYamadaMiz } 5509fc5dc4bSYamadaMiz } 5516b7e227cSYamadaMiz} 552