false, 'err'=>'not found', 'file'=>$file]; $content = file_get_contents($file); // Skip si deja patched if (strpos($content, 'DOCTRINE-141-SHUTDOWN') !== false) { return ['ok'=>false, 'err'=>'already patched', 'file'=>$file]; } // Backup $backup = '/var/www/html/vault-gold/opus/' . basename($file) . '.doctrine141-' . date('Ymd-His') . '.bak'; @mkdir(dirname($backup), 0755, true); copy($file, $backup); // Injection APRES le premier header(...) ou require (avant les die/echo) // Trouver la première ligne après les includes/headers/require // Pattern d'injection : on cherche la première ligne avec $input ou $msg = ou $lo = $inject = "\n// === DOCTRINE-141-SHUTDOWN · memory bridge === if (@file_exists(__DIR__.'/wevia-memory-bridge.php')) { @require_once __DIR__.'/wevia-memory-bridge.php'; } \$__chat_id = '" . $chat_id . "'; \$__user_id = \$_COOKIE['weval_chat_session'] ?? (\$_SERVER['HTTP_X_USER_ID'] ?? ('anon-'.substr(md5((\$_SERVER['REMOTE_ADDR']??'').(\$_SERVER['HTTP_USER_AGENT']??'')),0,12))); \$__msg_for_mem = ''; register_shutdown_function(function() use (&\$__msg_for_mem, \$__chat_id, \$__user_id) { if (!function_exists('wevia_mem_save')) return; \$out = ob_get_contents(); if (!\$out) return; \$d = @json_decode(\$out, true); \$resp = is_array(\$d) ? (\$d['response'] ?? \$d['answer'] ?? \$d['content'] ?? '') : \$out; if (\$resp && \$__msg_for_mem) { @wevia_mem_save(\$__chat_id, \$__user_id, \$__msg_for_mem, is_string(\$resp)?\$resp:json_encode(\$resp), 'internal'); } }); ob_start(); // === /DOCTRINE-141-SHUTDOWN === \n"; // Insertion : après la dernière header() ou require() initiale, avant la logique // On cherche la ligne la plus safe : après '$input = json_decode' ou '$msg = ' $anchors = [ '$input = json_decode(file_get_contents("php://input"), true) ?: [];', '$input = @json_decode(file_get_contents("php://input"), true) ?: [];', '$raw = @file_get_contents("php://input");' ]; $injected = false; foreach ($anchors as $anchor) { $pos = strpos($content, $anchor); if ($pos !== false) { // Insertion juste après la ligne de l'anchor (aprs le ;) $end = strpos($content, "\n", $pos) + 1; $before = substr($content, 0, $end); $after = substr($content, $end); $content = $before . $inject . $after; $injected = true; break; } } if (!$injected) { return ['ok'=>false, 'err'=>'anchor not found', 'file'=>$file]; } // Ajouter la capture du msg juste après sa définition $msg_captures = [ ['old' => '$msg = $input["message"] ?? $_GET["message"] ?? "";', 'new' => '$msg = $input["message"] ?? $_GET["message"] ?? ""; $__msg_for_mem = $msg;'], ['old' => '$msg = trim($in["message"] ?? "");', 'new' => '$msg = trim($in["message"] ?? ""); $__msg_for_mem = $msg;'], ['old' => '$message = trim($input[\'message\'] ?? \'\');', 'new' => '$message = trim($input[\'message\'] ?? \'\'); $__msg_for_mem = $message;'], ['old' => "\$msg = trim(\$input['message'] ?? '');", 'new' => "\$msg = trim(\$input['message'] ?? ''); \$__msg_for_mem = \$msg;"] ]; foreach ($msg_captures as $cap) { if (strpos($content, $cap['old']) !== false) { $content = str_replace($cap['old'], $cap['new'], $content); break; } } // Lint $tmp = tempnam('/tmp', 'cbpatch-'); file_put_contents($tmp, $content); $lint = shell_exec("php -l $tmp 2>&1"); if (strpos($lint, 'No syntax errors') === false) { unlink($tmp); return ['ok'=>false, 'err'=>'php lint fail', 'lint'=>$lint, 'file'=>$file]; } // Write shell_exec("sudo chattr -i $file 2>/dev/null"); file_put_contents($file, $content); shell_exec("sudo chown www-data:www-data $file"); shell_exec("sudo chattr +i $file 2>/dev/null"); unlink($tmp); return ['ok'=>true, 'file'=>$file, 'backup'=>$backup, 'chat_id'=>$chat_id]; } // Apply to 2 chatbots $results = [ 'weval-chatbot-api' => patch_chatbot('/var/www/html/api/weval-chatbot-api.php', 'weval-chatbot'), 'openclaw-proxy' => patch_chatbot('/var/www/html/api/openclaw-proxy.php', 'openclaw-proxy'), ]; @opcache_reset(); // Tests foreach (['weval-chatbot-api', 'openclaw-proxy'] as $name) { if (($results[$name]['ok'] ?? false)) { $sess_cookie = 'opus-phase4-' . $name; $test = shell_exec("curl -sk -m 8 -H 'X-User-Id: $sess_cookie' 'http://localhost/api/$name.php' -H 'Content-Type: application/json' -d '{\"message\":\"hello test phase4\"}' 2>&1 | head -c 300"); $results[$name]['test_response'] = substr(trim($test), 0, 250); } } // Verify Redis if (file_exists('/var/www/html/api/wevia-memory-bridge.php')) { require_once '/var/www/html/api/wevia-memory-bridge.php'; $mem_stats = [ 'weval-chatbot' => wevia_mem_load('weval-chatbot', 'opus-phase4-weval-chatbot-api', 5), 'openclaw-proxy' => wevia_mem_load('openclaw-proxy', 'opus-phase4-openclaw-proxy', 5), ]; $results['_memory_check'] = [ 'weval-chatbot_msgs' => count($mem_stats['weval-chatbot']), 'openclaw-proxy_msgs' => count($mem_stats['openclaw-proxy']), ]; } echo json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);