1*6b7e227cSYamadaMiz<?php 2*6b7e227cSYamadaMizclass action_plugin_mizarproofchecker extends \dokuwiki\Extension\ActionPlugin { 3*6b7e227cSYamadaMiz /** @inheritDoc */ 4*6b7e227cSYamadaMiz function register(Doku_Event_Handler $controller) { 5*6b7e227cSYamadaMiz $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call'); 6*6b7e227cSYamadaMiz } 7*6b7e227cSYamadaMiz 8*6b7e227cSYamadaMiz function handle_ajax_call(Doku_Event $event, $param) { 9*6b7e227cSYamadaMiz unset($param); // 未使用のパラメータを無効化 10*6b7e227cSYamadaMiz if ($event->data === 'source_sse') { 11*6b7e227cSYamadaMiz $event->preventDefault(); 12*6b7e227cSYamadaMiz $event->stopPropagation(); 13*6b7e227cSYamadaMiz $this->handleSourceSSERequest(); 14*6b7e227cSYamadaMiz return; 15*6b7e227cSYamadaMiz } elseif ($event->data === 'source_compile') { 16*6b7e227cSYamadaMiz $event->preventDefault(); 17*6b7e227cSYamadaMiz $event->stopPropagation(); 18*6b7e227cSYamadaMiz $this->handleSourceCompileRequest(); 19*6b7e227cSYamadaMiz return; 20*6b7e227cSYamadaMiz } elseif ($event->data === 'view_compile') { 21*6b7e227cSYamadaMiz $event->preventDefault(); 22*6b7e227cSYamadaMiz $event->stopPropagation(); 23*6b7e227cSYamadaMiz $this->handleViewCompileRequest(); 24*6b7e227cSYamadaMiz return; 25*6b7e227cSYamadaMiz } elseif ($event->data === 'view_sse') { 26*6b7e227cSYamadaMiz $event->preventDefault(); 27*6b7e227cSYamadaMiz $event->stopPropagation(); 28*6b7e227cSYamadaMiz $this->handleViewSSERequest(); 29*6b7e227cSYamadaMiz return; 30*6b7e227cSYamadaMiz } 31*6b7e227cSYamadaMiz } 32*6b7e227cSYamadaMiz 33*6b7e227cSYamadaMiz // source用のコンパイルリクエスト処理 34*6b7e227cSYamadaMiz private function handleSourceCompileRequest() { 35*6b7e227cSYamadaMiz global $INPUT; 36*6b7e227cSYamadaMiz $pageContent = $INPUT->post->str('content'); 37*6b7e227cSYamadaMiz 38*6b7e227cSYamadaMiz $mizarData = $this->extractMizarContent($pageContent); 39*6b7e227cSYamadaMiz 40*6b7e227cSYamadaMiz if ($mizarData === null) { 41*6b7e227cSYamadaMiz $this->sendAjaxResponse(false, 'Mizar content not found'); 42*6b7e227cSYamadaMiz return; 43*6b7e227cSYamadaMiz } 44*6b7e227cSYamadaMiz 45*6b7e227cSYamadaMiz $filePath = $this->saveMizarContent($mizarData); 46*6b7e227cSYamadaMiz 47*6b7e227cSYamadaMiz // セッションにファイルパスを保存 48*6b7e227cSYamadaMiz session_start(); 49*6b7e227cSYamadaMiz $_SESSION['source_filepath'] = $filePath; 50*6b7e227cSYamadaMiz 51*6b7e227cSYamadaMiz // コンパイルが成功したことをクライアントに通知 52*6b7e227cSYamadaMiz $this->sendAjaxResponse(true, 'Mizar content processed successfully'); 53*6b7e227cSYamadaMiz } 54*6b7e227cSYamadaMiz 55*6b7e227cSYamadaMiz // source用のSSEリクエスト処理 56*6b7e227cSYamadaMiz private function handleSourceSSERequest() { 57*6b7e227cSYamadaMiz header('Content-Type: text/event-stream'); 58*6b7e227cSYamadaMiz header('Cache-Control: no-cache'); 59*6b7e227cSYamadaMiz 60*6b7e227cSYamadaMiz session_start(); 61*6b7e227cSYamadaMiz if (!isset($_SESSION['source_filepath'])) { 62*6b7e227cSYamadaMiz echo "data: Mizar file path not found in session\n\n"; 63*6b7e227cSYamadaMiz ob_flush(); 64*6b7e227cSYamadaMiz flush(); 65*6b7e227cSYamadaMiz return; 66*6b7e227cSYamadaMiz } 67*6b7e227cSYamadaMiz 68*6b7e227cSYamadaMiz $filePath = $_SESSION['source_filepath']; 69*6b7e227cSYamadaMiz 70*6b7e227cSYamadaMiz // miz2prelコマンドを実行し、結果をストリーミング 71*6b7e227cSYamadaMiz $this->streamSourceOutput($filePath); 72*6b7e227cSYamadaMiz 73*6b7e227cSYamadaMiz echo "event: end\n"; 74*6b7e227cSYamadaMiz echo "data: Compilation complete\n\n"; 75*6b7e227cSYamadaMiz ob_flush(); 76*6b7e227cSYamadaMiz flush(); 77*6b7e227cSYamadaMiz } 78*6b7e227cSYamadaMiz 79*6b7e227cSYamadaMiz private function extractMizarContent($pageContent) { 80*6b7e227cSYamadaMiz $pattern = '/<mizar\s+([^>]+)>(.*?)<\/mizar>/s'; 81*6b7e227cSYamadaMiz preg_match_all($pattern, $pageContent, $matches, PREG_SET_ORDER); 82*6b7e227cSYamadaMiz 83*6b7e227cSYamadaMiz if (empty($matches)) { 84*6b7e227cSYamadaMiz return null; // <mizar>タグが見つからない場合 85*6b7e227cSYamadaMiz } 86*6b7e227cSYamadaMiz 87*6b7e227cSYamadaMiz $fileName = trim($matches[0][1]); // 最初の<mizar>タグからファイル名を取得 88*6b7e227cSYamadaMiz $combinedContent = ''; 89*6b7e227cSYamadaMiz 90*6b7e227cSYamadaMiz foreach ($matches as $match) { 91*6b7e227cSYamadaMiz // 各<mizar>タグのファイル名が一致することを確認 92*6b7e227cSYamadaMiz if (trim($match[1]) !== $fileName) { 93*6b7e227cSYamadaMiz return ['error' => 'File name mismatch in <mizar> tags']; 94*6b7e227cSYamadaMiz } 95*6b7e227cSYamadaMiz 96*6b7e227cSYamadaMiz // 各<mizar>タグの内容を連結 97*6b7e227cSYamadaMiz $combinedContent .= trim($match[2]) . "\n"; 98*6b7e227cSYamadaMiz } 99*6b7e227cSYamadaMiz 100*6b7e227cSYamadaMiz return ['fileName' => $fileName, 'content' => $combinedContent]; 101*6b7e227cSYamadaMiz } 102*6b7e227cSYamadaMiz 103*6b7e227cSYamadaMiz private function saveMizarContent($mizarData) { 104*6b7e227cSYamadaMiz // ワークパスを取得し、末尾にスラッシュやバックスラッシュがあれば削除 105*6b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\'); 106*6b7e227cSYamadaMiz $filePath = $workPath . "/TEXT/" . $mizarData['fileName']; 107*6b7e227cSYamadaMiz file_put_contents($filePath, $mizarData['content']); 108*6b7e227cSYamadaMiz return $filePath; 109*6b7e227cSYamadaMiz } 110*6b7e227cSYamadaMiz 111*6b7e227cSYamadaMiz private function streamSourceOutput($filePath) { 112*6b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\'); 113*6b7e227cSYamadaMiz chdir($workPath); 114*6b7e227cSYamadaMiz 115*6b7e227cSYamadaMiz $command = "miz2prel " . escapeshellarg($filePath); 116*6b7e227cSYamadaMiz $process = proc_open($command, array(1 => array("pipe", "w")), $pipes); 117*6b7e227cSYamadaMiz 118*6b7e227cSYamadaMiz if (is_resource($process)) { 119*6b7e227cSYamadaMiz while ($line = fgets($pipes[1])) { 120*6b7e227cSYamadaMiz echo "data: " . $line . "\n\n"; 121*6b7e227cSYamadaMiz ob_flush(); 122*6b7e227cSYamadaMiz flush(); 123*6b7e227cSYamadaMiz } 124*6b7e227cSYamadaMiz fclose($pipes[1]); 125*6b7e227cSYamadaMiz proc_close($process); 126*6b7e227cSYamadaMiz } 127*6b7e227cSYamadaMiz } 128*6b7e227cSYamadaMiz 129*6b7e227cSYamadaMiz // view用のコンパイルリクエスト処理 130*6b7e227cSYamadaMiz private function handleViewCompileRequest() { 131*6b7e227cSYamadaMiz global $INPUT; 132*6b7e227cSYamadaMiz $content = $INPUT->post->str('content'); 133*6b7e227cSYamadaMiz 134*6b7e227cSYamadaMiz // 一時ファイルの作成 135*6b7e227cSYamadaMiz $filePath = $this->createTempFile($content); 136*6b7e227cSYamadaMiz 137*6b7e227cSYamadaMiz // セッションにファイルパスを保存 138*6b7e227cSYamadaMiz session_start(); 139*6b7e227cSYamadaMiz $_SESSION['view_filepath'] = $filePath; 140*6b7e227cSYamadaMiz 141*6b7e227cSYamadaMiz // コンパイル準備完了をレスポンス 142*6b7e227cSYamadaMiz $this->sendAjaxResponse(true, 'Mizar content processed successfully'); 143*6b7e227cSYamadaMiz } 144*6b7e227cSYamadaMiz 145*6b7e227cSYamadaMiz // view用のSSEリクエスト処理 146*6b7e227cSYamadaMiz private function handleViewSSERequest() { 147*6b7e227cSYamadaMiz header('Content-Type: text/event-stream'); 148*6b7e227cSYamadaMiz header('Cache-Control: no-cache'); 149*6b7e227cSYamadaMiz 150*6b7e227cSYamadaMiz session_start(); 151*6b7e227cSYamadaMiz if (!isset($_SESSION['view_filepath'])) { 152*6b7e227cSYamadaMiz echo "data: Mizar file path not found in session\n\n"; 153*6b7e227cSYamadaMiz ob_flush(); 154*6b7e227cSYamadaMiz flush(); 155*6b7e227cSYamadaMiz return; 156*6b7e227cSYamadaMiz } 157*6b7e227cSYamadaMiz 158*6b7e227cSYamadaMiz $filePath = $_SESSION['view_filepath']; 159*6b7e227cSYamadaMiz 160*6b7e227cSYamadaMiz // コンパイル結果をストリーミング 161*6b7e227cSYamadaMiz $this->streamCompileOutput($filePath); 162*6b7e227cSYamadaMiz 163*6b7e227cSYamadaMiz echo "event: end\n"; 164*6b7e227cSYamadaMiz echo "data: Compilation complete\n\n"; 165*6b7e227cSYamadaMiz ob_flush(); 166*6b7e227cSYamadaMiz flush(); 167*6b7e227cSYamadaMiz } 168*6b7e227cSYamadaMiz 169*6b7e227cSYamadaMiz // 一時ファイル作成はviewでのみ使用 170*6b7e227cSYamadaMiz private function createTempFile($content) { 171*6b7e227cSYamadaMiz $workPath = rtrim($this->getConf('mizar_work_dir'), '/\\') . '/TEXT/'; 172*6b7e227cSYamadaMiz $uniqueName = str_replace('.', '_', uniqid('mizar', true)); 173*6b7e227cSYamadaMiz $tempFilename = $workPath . $uniqueName . ".miz"; 174*6b7e227cSYamadaMiz file_put_contents($tempFilename, $content); 175*6b7e227cSYamadaMiz return $tempFilename; 176*6b7e227cSYamadaMiz } 177*6b7e227cSYamadaMiz 178*6b7e227cSYamadaMiz private function streamCompileOutput($filePath) { 179*6b7e227cSYamadaMiz $workPath = $this->getConf('mizar_work_dir'); 180*6b7e227cSYamadaMiz $sharePath = rtrim($this->getConf('mizar_share_dir'), '/\\') . '/'; 181*6b7e227cSYamadaMiz 182*6b7e227cSYamadaMiz chdir($workPath); 183*6b7e227cSYamadaMiz 184*6b7e227cSYamadaMiz $tempErrFilename = str_replace('.miz', '.err', $filePath); 185*6b7e227cSYamadaMiz $command = "makeenv " . escapeshellarg($filePath); 186*6b7e227cSYamadaMiz $process = proc_open($command, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $pipes); 187*6b7e227cSYamadaMiz 188*6b7e227cSYamadaMiz if (is_resource($process)) { 189*6b7e227cSYamadaMiz while ($line = fgets($pipes[1])) { 190*6b7e227cSYamadaMiz echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 191*6b7e227cSYamadaMiz ob_flush(); 192*6b7e227cSYamadaMiz flush(); 193*6b7e227cSYamadaMiz } 194*6b7e227cSYamadaMiz fclose($pipes[1]); 195*6b7e227cSYamadaMiz 196*6b7e227cSYamadaMiz while ($line = fgets($pipes[2])) { 197*6b7e227cSYamadaMiz echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 198*6b7e227cSYamadaMiz ob_flush(); 199*6b7e227cSYamadaMiz flush(); 200*6b7e227cSYamadaMiz } 201*6b7e227cSYamadaMiz fclose($pipes[2]); 202*6b7e227cSYamadaMiz proc_close($process); 203*6b7e227cSYamadaMiz 204*6b7e227cSYamadaMiz if (file_exists($tempErrFilename)) { 205*6b7e227cSYamadaMiz $errors = []; 206*6b7e227cSYamadaMiz $errorLines = file($tempErrFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 207*6b7e227cSYamadaMiz foreach ($errorLines as $errorLine) { 208*6b7e227cSYamadaMiz if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $errorLine, $matches)) { 209*6b7e227cSYamadaMiz $errors[] = [ 210*6b7e227cSYamadaMiz 'code' => intval($matches[3]), 211*6b7e227cSYamadaMiz 'line' => intval($matches[1]), 212*6b7e227cSYamadaMiz 'column' => intval($matches[2]), 213*6b7e227cSYamadaMiz 'message' => $this->getMizarErrorMessages($sharePath . '/mizar.msg')[intval($matches[3])] ?? 'Unknown error' 214*6b7e227cSYamadaMiz ]; 215*6b7e227cSYamadaMiz } 216*6b7e227cSYamadaMiz } 217*6b7e227cSYamadaMiz if (!empty($errors)) { 218*6b7e227cSYamadaMiz echo "event: compileErrors\n"; 219*6b7e227cSYamadaMiz echo "data: " . json_encode($errors) . "\n\n"; 220*6b7e227cSYamadaMiz ob_flush(); 221*6b7e227cSYamadaMiz flush(); 222*6b7e227cSYamadaMiz return; 223*6b7e227cSYamadaMiz } 224*6b7e227cSYamadaMiz } 225*6b7e227cSYamadaMiz 226*6b7e227cSYamadaMiz // verifierの実行 227*6b7e227cSYamadaMiz $exePath = rtrim($this->getConf('mizar_exe_dir'), '/\\') . '/'; 228*6b7e227cSYamadaMiz $verifierPath = escapeshellarg($exePath . "verifier"); 229*6b7e227cSYamadaMiz if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 230*6b7e227cSYamadaMiz $verifierPath .= ".exe"; 231*6b7e227cSYamadaMiz } 232*6b7e227cSYamadaMiz $verifierCommand = $verifierPath . " -q -l " . escapeshellarg("TEXT/" . basename($filePath)); 233*6b7e227cSYamadaMiz 234*6b7e227cSYamadaMiz $verifierProcess = proc_open($verifierCommand, array(1 => array("pipe", "w"), 2 => array("pipe", "w")), $verifierPipes); 235*6b7e227cSYamadaMiz 236*6b7e227cSYamadaMiz if (is_resource($verifierProcess)) { 237*6b7e227cSYamadaMiz while ($line = fgets($verifierPipes[1])) { 238*6b7e227cSYamadaMiz echo "data: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 239*6b7e227cSYamadaMiz ob_flush(); 240*6b7e227cSYamadaMiz flush(); 241*6b7e227cSYamadaMiz } 242*6b7e227cSYamadaMiz fclose($verifierPipes[1]); 243*6b7e227cSYamadaMiz 244*6b7e227cSYamadaMiz while ($line = fgets($verifierPipes[2])) { 245*6b7e227cSYamadaMiz echo "data: ERROR: " . mb_convert_encoding($line, 'UTF-8', 'SJIS') . "\n\n"; 246*6b7e227cSYamadaMiz ob_flush(); 247*6b7e227cSYamadaMiz flush(); 248*6b7e227cSYamadaMiz } 249*6b7e227cSYamadaMiz fclose($verifierPipes[2]); 250*6b7e227cSYamadaMiz proc_close($verifierProcess); 251*6b7e227cSYamadaMiz 252*6b7e227cSYamadaMiz if (file_exists($tempErrFilename)) { 253*6b7e227cSYamadaMiz $errors = []; 254*6b7e227cSYamadaMiz $errorLines = file($tempErrFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 255*6b7e227cSYamadaMiz foreach ($errorLines as $errorLine) { 256*6b7e227cSYamadaMiz if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $errorLine, $matches)) { 257*6b7e227cSYamadaMiz $errors[] = [ 258*6b7e227cSYamadaMiz 'code' => intval($matches[3]), 259*6b7e227cSYamadaMiz 'line' => intval($matches[1]), 260*6b7e227cSYamadaMiz 'column' => intval($matches[2]), 261*6b7e227cSYamadaMiz 'message' => $this->getMizarErrorMessages($workPath . '/MIZAR/mizar.msg')[intval($matches[3])] ?? 'Unknown error' 262*6b7e227cSYamadaMiz ]; 263*6b7e227cSYamadaMiz } 264*6b7e227cSYamadaMiz } 265*6b7e227cSYamadaMiz echo "event: compileErrors\n"; 266*6b7e227cSYamadaMiz echo "data: " . json_encode($errors) . "\n\n"; 267*6b7e227cSYamadaMiz ob_flush(); 268*6b7e227cSYamadaMiz flush(); 269*6b7e227cSYamadaMiz } 270*6b7e227cSYamadaMiz } else { 271*6b7e227cSYamadaMiz echo "data: ERROR: Failed to execute verifier command.\n\n"; 272*6b7e227cSYamadaMiz ob_flush(); 273*6b7e227cSYamadaMiz flush(); 274*6b7e227cSYamadaMiz } 275*6b7e227cSYamadaMiz } else { 276*6b7e227cSYamadaMiz echo "data: ERROR: Failed to execute makeenv command.\n\n"; 277*6b7e227cSYamadaMiz ob_flush(); 278*6b7e227cSYamadaMiz flush(); 279*6b7e227cSYamadaMiz } 280*6b7e227cSYamadaMiz } 281*6b7e227cSYamadaMiz 282*6b7e227cSYamadaMiz // Mizarエラーメッセージを取得するメソッド 283*6b7e227cSYamadaMiz private function getMizarErrorMessages($mizarMsgFile) { 284*6b7e227cSYamadaMiz $errorMessages = []; 285*6b7e227cSYamadaMiz $lines = file($mizarMsgFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 286*6b7e227cSYamadaMiz 287*6b7e227cSYamadaMiz $isReadingErrorMsg = false; 288*6b7e227cSYamadaMiz $key = 0; 289*6b7e227cSYamadaMiz 290*6b7e227cSYamadaMiz foreach ($lines as $line) { 291*6b7e227cSYamadaMiz if (preg_match('/# (\d+)/', $line, $matches)) { 292*6b7e227cSYamadaMiz $isReadingErrorMsg = true; 293*6b7e227cSYamadaMiz $key = intval($matches[1]); 294*6b7e227cSYamadaMiz } elseif ($isReadingErrorMsg) { 295*6b7e227cSYamadaMiz $errorMessages[$key] = $line; 296*6b7e227cSYamadaMiz $isReadingErrorMsg = false; 297*6b7e227cSYamadaMiz } 298*6b7e227cSYamadaMiz } 299*6b7e227cSYamadaMiz 300*6b7e227cSYamadaMiz return $errorMessages; 301*6b7e227cSYamadaMiz } 302*6b7e227cSYamadaMiz 303*6b7e227cSYamadaMiz private function sendAjaxResponse($success, $message, $data = '') { 304*6b7e227cSYamadaMiz header('Content-Type: application/json'); 305*6b7e227cSYamadaMiz echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]); 306*6b7e227cSYamadaMiz exit; 307*6b7e227cSYamadaMiz } 308*6b7e227cSYamadaMiz} 309