lwazi 1.16.2 → 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/crawl.php +253 -42
- package/package.json +1 -1
- package/uninstall +22 -3
- package/uninstall.bat +9 -0
- package/uninstall.ps1 +65 -47
package/bin/crawl.php
CHANGED
|
@@ -96,8 +96,7 @@ if ($options['url'] === 'http://localhost' && $argc === 1) {
|
|
|
96
96
|
$input = readline("Login email/username: ");
|
|
97
97
|
$options['login_user'] = trim($input);
|
|
98
98
|
|
|
99
|
-
$
|
|
100
|
-
$options['login_pass'] = trim($input);
|
|
99
|
+
$options['login_pass'] = readPasswordMasked("Login password: ");
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
|
|
@@ -197,6 +196,28 @@ function extractMeta(string $html, string $name): string {
|
|
|
197
196
|
return '';
|
|
198
197
|
}
|
|
199
198
|
|
|
199
|
+
function extractText(string $html): string {
|
|
200
|
+
$dom = new DOMDocument();
|
|
201
|
+
libxml_use_internal_errors(true);
|
|
202
|
+
$dom->loadHTML($html);
|
|
203
|
+
libxml_clear_errors();
|
|
204
|
+
|
|
205
|
+
$remove = [];
|
|
206
|
+
foreach (['script', 'style', 'noscript'] as $tag) {
|
|
207
|
+
$nodes = $dom->getElementsByTagName($tag);
|
|
208
|
+
foreach ($nodes as $node) {
|
|
209
|
+
$remove[] = $node;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
foreach ($remove as $node) {
|
|
213
|
+
$node->parentNode->removeChild($node);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
$text = $dom->textContent ?? '';
|
|
217
|
+
$text = preg_replace('/\s+/', ' ', $text);
|
|
218
|
+
return trim($text);
|
|
219
|
+
}
|
|
220
|
+
|
|
200
221
|
function extractCsrfToken(string $html): ?string {
|
|
201
222
|
if (preg_match('/name="_token"\s+value="([^"]+)"/', $html, $m)) return $m[1];
|
|
202
223
|
if (preg_match('/name="_token"\s*value="([^"]+)"/', $html, $m)) return $m[1];
|
|
@@ -204,6 +225,163 @@ function extractCsrfToken(string $html): ?string {
|
|
|
204
225
|
return null;
|
|
205
226
|
}
|
|
206
227
|
|
|
228
|
+
function isLoginPage(string $html): bool {
|
|
229
|
+
$dom = new DOMDocument();
|
|
230
|
+
libxml_use_internal_errors(true);
|
|
231
|
+
$dom->loadHTML($html);
|
|
232
|
+
libxml_clear_errors();
|
|
233
|
+
|
|
234
|
+
$inputs = $dom->getElementsByTagName('input');
|
|
235
|
+
foreach ($inputs as $input) {
|
|
236
|
+
$type = strtolower($input->getAttribute('type'));
|
|
237
|
+
if ($type === 'password') {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function fetchPage(string $url, string $cookies): array {
|
|
245
|
+
$headers = [];
|
|
246
|
+
$ch = curl_init();
|
|
247
|
+
curl_setopt($ch, CURLOPT_URL, $url);
|
|
248
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
249
|
+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
|
250
|
+
curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
|
|
251
|
+
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
252
|
+
curl_setopt($ch, CURLOPT_USERAGENT, 'Lwazi/1.0');
|
|
253
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
254
|
+
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
|
|
255
|
+
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $headerLine) use (&$headers) {
|
|
256
|
+
$line = trim($headerLine);
|
|
257
|
+
if ($line !== '') {
|
|
258
|
+
$headers[] = $line;
|
|
259
|
+
}
|
|
260
|
+
return strlen($headerLine);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
$html = curl_exec($ch);
|
|
264
|
+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
265
|
+
$finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) ?: $url;
|
|
266
|
+
$error = curl_error($ch);
|
|
267
|
+
curl_close($ch);
|
|
268
|
+
|
|
269
|
+
return [
|
|
270
|
+
'html' => $html ?: '',
|
|
271
|
+
'http_code' => $httpCode,
|
|
272
|
+
'final_url' => $finalUrl,
|
|
273
|
+
'headers' => $headers,
|
|
274
|
+
'error' => $error,
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function parseLoginForm(string $html, string $loginUrl): array {
|
|
279
|
+
$dom = new DOMDocument();
|
|
280
|
+
libxml_use_internal_errors(true);
|
|
281
|
+
$dom->loadHTML($html);
|
|
282
|
+
libxml_clear_errors();
|
|
283
|
+
|
|
284
|
+
$forms = $dom->getElementsByTagName('form');
|
|
285
|
+
$targetForm = null;
|
|
286
|
+
foreach ($forms as $form) {
|
|
287
|
+
$inputs = $form->getElementsByTagName('input');
|
|
288
|
+
foreach ($inputs as $input) {
|
|
289
|
+
if (strtolower($input->getAttribute('type')) === 'password') {
|
|
290
|
+
$targetForm = $form;
|
|
291
|
+
break 2;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
$action = $loginUrl;
|
|
297
|
+
$method = 'post';
|
|
298
|
+
$fields = [];
|
|
299
|
+
$userField = null;
|
|
300
|
+
$passField = null;
|
|
301
|
+
|
|
302
|
+
if ($targetForm) {
|
|
303
|
+
$actionAttr = trim($targetForm->getAttribute('action'));
|
|
304
|
+
if ($actionAttr !== '') {
|
|
305
|
+
$action = normalizeUrl($actionAttr, $loginUrl);
|
|
306
|
+
}
|
|
307
|
+
$methodAttr = strtolower(trim($targetForm->getAttribute('method')));
|
|
308
|
+
if ($methodAttr !== '') {
|
|
309
|
+
$method = $methodAttr;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
$inputs = $targetForm->getElementsByTagName('input');
|
|
313
|
+
foreach ($inputs as $input) {
|
|
314
|
+
$name = $input->getAttribute('name');
|
|
315
|
+
if ($name === '') {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
$type = strtolower($input->getAttribute('type'));
|
|
319
|
+
$value = $input->getAttribute('value');
|
|
320
|
+
if ($type === 'hidden') {
|
|
321
|
+
$fields[$name] = $value;
|
|
322
|
+
}
|
|
323
|
+
if ($userField === null && in_array($type, ['text', 'email'], true)) {
|
|
324
|
+
if (preg_match('/email|user|username|login/i', $name)) {
|
|
325
|
+
$userField = $name;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if ($type === 'password' && $passField === null) {
|
|
329
|
+
$passField = $name;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return [
|
|
335
|
+
'action' => $action,
|
|
336
|
+
'method' => $method,
|
|
337
|
+
'fields' => $fields,
|
|
338
|
+
'user_field' => $userField,
|
|
339
|
+
'pass_field' => $passField,
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function readPasswordMasked(string $prompt): string {
|
|
344
|
+
// Masked password input for POSIX terminals, with a safe fallback.
|
|
345
|
+
$sttyMode = null;
|
|
346
|
+
if (function_exists('shell_exec')) {
|
|
347
|
+
$sttyMode = shell_exec('stty -g 2>/dev/null');
|
|
348
|
+
if ($sttyMode) {
|
|
349
|
+
shell_exec('stty -echo -icanon min 1 time 0 2>/dev/null');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
echo $prompt;
|
|
354
|
+
$password = '';
|
|
355
|
+
|
|
356
|
+
if ($sttyMode) {
|
|
357
|
+
while (true) {
|
|
358
|
+
$char = fread(STDIN, 1);
|
|
359
|
+
if ($char === false || $char === '') {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
if ($char === "\n" || $char === "\r") {
|
|
363
|
+
echo "\n";
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
if ($char === "\x7f" || $char === "\x08") { // backspace/delete
|
|
367
|
+
if (strlen($password) > 0) {
|
|
368
|
+
$password = substr($password, 0, -1);
|
|
369
|
+
echo "\x08 \x08";
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
$password .= $char;
|
|
374
|
+
echo "*";
|
|
375
|
+
}
|
|
376
|
+
shell_exec('stty ' . $sttyMode . ' 2>/dev/null');
|
|
377
|
+
return $password;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Fallback (no masking) if stty isn't available.
|
|
381
|
+
$line = fgets(STDIN);
|
|
382
|
+
return $line === false ? '' : rtrim($line, "\r\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
207
385
|
function loginAndGetCookies(string $loginUrl, string $username, string $password, string $cookies): bool {
|
|
208
386
|
echo "Attempting to login to $loginUrl...\n";
|
|
209
387
|
|
|
@@ -224,32 +402,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
|
|
|
224
402
|
echo "Failed to fetch login page\n";
|
|
225
403
|
return false;
|
|
226
404
|
}
|
|
227
|
-
|
|
405
|
+
|
|
406
|
+
$form = parseLoginForm($loginPage, $loginUrl);
|
|
228
407
|
$csrfToken = extractCsrfToken($loginPage);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
$
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
$passwordField = $m[1];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
$loginData = [
|
|
241
|
-
$emailField => $username,
|
|
242
|
-
$passwordField => $password,
|
|
243
|
-
];
|
|
244
|
-
|
|
245
|
-
if ($csrfToken) {
|
|
408
|
+
$loginData = $form['fields'];
|
|
409
|
+
|
|
410
|
+
$userField = $form['user_field'] ?: 'email';
|
|
411
|
+
$passField = $form['pass_field'] ?: 'password';
|
|
412
|
+
$loginData[$userField] = $username;
|
|
413
|
+
$loginData[$passField] = $password;
|
|
414
|
+
|
|
415
|
+
if ($csrfToken && !isset($loginData['_token'])) {
|
|
246
416
|
$loginData['_token'] = $csrfToken;
|
|
247
417
|
}
|
|
248
418
|
|
|
249
419
|
$ch = curl_init();
|
|
250
|
-
curl_setopt($ch, CURLOPT_URL, $
|
|
251
|
-
curl_setopt($ch, CURLOPT_POST,
|
|
252
|
-
|
|
420
|
+
curl_setopt($ch, CURLOPT_URL, $form['action']);
|
|
421
|
+
curl_setopt($ch, CURLOPT_POST, ($form['method'] !== 'get'));
|
|
422
|
+
if ($form['method'] === 'get') {
|
|
423
|
+
curl_setopt($ch, CURLOPT_URL, $form['action'] . '?' . http_build_query($loginData));
|
|
424
|
+
} else {
|
|
425
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($loginData));
|
|
426
|
+
}
|
|
253
427
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
254
428
|
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
|
|
255
429
|
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
|
|
@@ -262,15 +436,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
|
|
|
262
436
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
263
437
|
curl_close($ch);
|
|
264
438
|
|
|
265
|
-
if ($httpCode >= 200 && $httpCode < 400) {
|
|
439
|
+
if ($httpCode >= 200 && $httpCode < 400 && $result && !isLoginPage($result)) {
|
|
266
440
|
echo "Login successful!\n";
|
|
267
441
|
return true;
|
|
268
442
|
}
|
|
269
|
-
|
|
443
|
+
|
|
270
444
|
echo "Login may have failed (HTTP $httpCode)\n";
|
|
271
445
|
return false;
|
|
272
446
|
}
|
|
273
447
|
|
|
448
|
+
function ensureSessionAlive(string $checkUrl, string $loginUrl, string $loginUser, string $loginPass, string $cookies): bool {
|
|
449
|
+
if (!$loginUrl || !$loginUser || !$loginPass) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
$probe = fetchPage($checkUrl, $cookies);
|
|
454
|
+
if ($probe['html'] && !isLoginPage($probe['html'])) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
|
|
459
|
+
}
|
|
460
|
+
|
|
274
461
|
if ($loginUrl && $loginUser && $loginPass) {
|
|
275
462
|
$loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
|
|
276
463
|
if (!$loggedIn) {
|
|
@@ -289,6 +476,8 @@ $adjacency = [];
|
|
|
289
476
|
$pageData = [];
|
|
290
477
|
|
|
291
478
|
$totalPages = 0;
|
|
479
|
+
$keepAliveInterval = 120;
|
|
480
|
+
$lastKeepAliveAt = time();
|
|
292
481
|
|
|
293
482
|
while (!empty($queue) && $totalPages < $maxPages) {
|
|
294
483
|
[$url, $depth] = array_shift($queue);
|
|
@@ -302,33 +491,48 @@ while (!empty($queue) && $totalPages < $maxPages) {
|
|
|
302
491
|
$totalPages++;
|
|
303
492
|
|
|
304
493
|
echo "Crawling [$totalPages/$maxPages]: $url (depth: $depth)\n";
|
|
305
|
-
|
|
306
|
-
$
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
$
|
|
317
|
-
$
|
|
318
|
-
|
|
494
|
+
|
|
495
|
+
if ($loginUrl && $loginUser && $loginPass) {
|
|
496
|
+
$now = time();
|
|
497
|
+
if (($now - $lastKeepAliveAt) >= $keepAliveInterval) {
|
|
498
|
+
ensureSessionAlive($rootUrl, $loginUrl, $loginUser, $loginPass, $cookies);
|
|
499
|
+
$lastKeepAliveAt = $now;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
$response = fetchPage($url, $cookies);
|
|
504
|
+
$html = $response['html'];
|
|
505
|
+
$httpCode = $response['http_code'];
|
|
506
|
+
$finalUrl = $response['final_url'];
|
|
507
|
+
$headers = $response['headers'];
|
|
319
508
|
|
|
320
509
|
if (!$html || $httpCode >= 400) {
|
|
321
510
|
echo " -> Failed (HTTP $httpCode)\n";
|
|
322
511
|
continue;
|
|
323
512
|
}
|
|
324
513
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
514
|
+
if ($html && isLoginPage($html)) {
|
|
515
|
+
if ($loginUrl && $loginUser && $loginPass) {
|
|
516
|
+
echo " -> Login required, re-authenticating and retrying\n";
|
|
517
|
+
$loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
|
|
518
|
+
if ($loggedIn) {
|
|
519
|
+
$response = fetchPage($url, $cookies);
|
|
520
|
+
$html = $response['html'];
|
|
521
|
+
$httpCode = $response['http_code'];
|
|
522
|
+
$finalUrl = $response['final_url'];
|
|
523
|
+
$headers = $response['headers'];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!$html || isLoginPage($html)) {
|
|
528
|
+
echo " -> Login required, skipping\n";
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
328
531
|
}
|
|
329
532
|
|
|
330
533
|
$title = extractTitle($html);
|
|
331
534
|
$description = extractMeta($html, 'description');
|
|
535
|
+
$text = extractText($html);
|
|
332
536
|
$links = extractLinks($html, $url);
|
|
333
537
|
|
|
334
538
|
$nodes[$urlId] = [
|
|
@@ -342,6 +546,13 @@ while (!empty($queue) && $totalPages < $maxPages) {
|
|
|
342
546
|
$pageData[$urlId] = [
|
|
343
547
|
'html' => $html,
|
|
344
548
|
'url' => $url,
|
|
549
|
+
'final_url' => $finalUrl,
|
|
550
|
+
'title' => $title,
|
|
551
|
+
'description' => $description,
|
|
552
|
+
'text' => $text,
|
|
553
|
+
'headers' => $headers,
|
|
554
|
+
'fetched_at' => date('c'),
|
|
555
|
+
'http_code' => $httpCode,
|
|
345
556
|
];
|
|
346
557
|
|
|
347
558
|
foreach ($links as $link) {
|
package/package.json
CHANGED
package/uninstall
CHANGED
|
@@ -62,11 +62,18 @@ if [ -f "$INDEX_FILE" ]; then
|
|
|
62
62
|
php -r '
|
|
63
63
|
$file = "public/index.php";
|
|
64
64
|
$content = file_get_contents($file);
|
|
65
|
+
$hadNewline = str_ends_with($content, "\n");
|
|
65
66
|
$content = preg_replace(
|
|
66
67
|
"/\n*require __DIR__\.\"\/..\/lwazi\/auto_prepend\.php\";\n*/",
|
|
67
|
-
"\n
|
|
68
|
+
"\n",
|
|
68
69
|
$content
|
|
69
70
|
);
|
|
71
|
+
$content = preg_replace("/\n{3,}/", "\n\n", $content);
|
|
72
|
+
if ($hadNewline) {
|
|
73
|
+
$content = rtrim($content, "\n") . "\n";
|
|
74
|
+
} else {
|
|
75
|
+
$content = rtrim($content, "\n");
|
|
76
|
+
}
|
|
70
77
|
file_put_contents($file, $content);
|
|
71
78
|
'
|
|
72
79
|
echo "Removed injection from public/index.php"
|
|
@@ -79,11 +86,18 @@ if [ -f "$PROJECT_DIR/index.php" ]; then
|
|
|
79
86
|
php -r '
|
|
80
87
|
$file = "index.php";
|
|
81
88
|
$content = file_get_contents($file);
|
|
89
|
+
$hadNewline = str_ends_with($content, "\n");
|
|
82
90
|
$content = preg_replace(
|
|
83
91
|
"/\n*require __DIR__\.\"\/lwazi\/auto_prepend\.php\";\n*/",
|
|
84
|
-
"\n
|
|
92
|
+
"\n",
|
|
85
93
|
$content
|
|
86
94
|
);
|
|
95
|
+
$content = preg_replace("/\n{3,}/", "\n\n", $content);
|
|
96
|
+
if ($hadNewline) {
|
|
97
|
+
$content = rtrim($content, "\n") . "\n";
|
|
98
|
+
} else {
|
|
99
|
+
$content = rtrim($content, "\n");
|
|
100
|
+
}
|
|
87
101
|
file_put_contents($file, $content);
|
|
88
102
|
'
|
|
89
103
|
echo "Removed injection from index.php"
|
|
@@ -104,6 +118,7 @@ php -r '
|
|
|
104
118
|
$file = "composer.json";
|
|
105
119
|
if (file_exists($file)) {
|
|
106
120
|
$json = json_decode(file_get_contents($file), true);
|
|
121
|
+
$hadNewline = str_ends_with(file_get_contents($file), "\n");
|
|
107
122
|
$changed = false;
|
|
108
123
|
|
|
109
124
|
if (isset($json["require"]["lwazi/core"])) {
|
|
@@ -130,7 +145,11 @@ if (file_exists($file)) {
|
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
if ($changed) {
|
|
133
|
-
|
|
148
|
+
$encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
149
|
+
if ($hadNewline) {
|
|
150
|
+
$encoded .= "\n";
|
|
151
|
+
}
|
|
152
|
+
file_put_contents($file, $encoded);
|
|
134
153
|
echo "Removed lwazi from composer.json\n";
|
|
135
154
|
}
|
|
136
155
|
}
|
package/uninstall.bat
CHANGED
|
@@ -22,6 +22,15 @@ echo.
|
|
|
22
22
|
echo Uninstalling Lwazi...
|
|
23
23
|
echo -------------------------------------------
|
|
24
24
|
|
|
25
|
+
REM Prefer PowerShell uninstaller if available for cleaner cleanup
|
|
26
|
+
where powershell >nul 2>&1
|
|
27
|
+
if %ERRORLEVEL%==0 (
|
|
28
|
+
if exist "%PROJECT_DIR%\uninstall.ps1" (
|
|
29
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%PROJECT_DIR%\uninstall.ps1" -Force
|
|
30
|
+
exit /b %ERRORLEVEL%
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
25
34
|
REM Remove lwazi folder
|
|
26
35
|
if exist "%PROJECT_DIR%\lwazi" (
|
|
27
36
|
rmdir /s /q "%PROJECT_DIR%\lwazi"
|
package/uninstall.ps1
CHANGED
|
@@ -7,6 +7,30 @@ param(
|
|
|
7
7
|
|
|
8
8
|
$ErrorActionPreference = "Stop"
|
|
9
9
|
|
|
10
|
+
function Get-LineEnding {
|
|
11
|
+
param([string]$Content)
|
|
12
|
+
if ($Content -match "`r`n") { return "`r`n" }
|
|
13
|
+
return "`n"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function Write-FilePreserveNewline {
|
|
17
|
+
param(
|
|
18
|
+
[string]$Path,
|
|
19
|
+
[string]$Content,
|
|
20
|
+
[string]$OriginalContent
|
|
21
|
+
)
|
|
22
|
+
$lineEnding = Get-LineEnding -Content $OriginalContent
|
|
23
|
+
$hadNewline = $OriginalContent -match "(`r?`n)$"
|
|
24
|
+
|
|
25
|
+
if ($hadNewline) {
|
|
26
|
+
$Content = $Content.TrimEnd("`r", "`n") + $lineEnding
|
|
27
|
+
} else {
|
|
28
|
+
$Content = $Content.TrimEnd("`r", "`n")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[IO.File]::WriteAllText($Path, $Content)
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
Write-Host "==========================================" -ForegroundColor Cyan
|
|
11
35
|
Write-Host " Lwazi AI Assistant Uninstaller" -ForegroundColor Cyan
|
|
12
36
|
Write-Host "==========================================" -ForegroundColor Cyan
|
|
@@ -28,16 +52,6 @@ Write-Host ""
|
|
|
28
52
|
Write-Host "Uninstalling Lwazi..." -ForegroundColor Yellow
|
|
29
53
|
Write-Host "-------------------------------------------"
|
|
30
54
|
|
|
31
|
-
# Check if composer was used
|
|
32
|
-
$composerUsed = $false
|
|
33
|
-
$modeFile = "$ProjectDir\lwazi\.lwazi_mode"
|
|
34
|
-
if (Test-Path $modeFile) {
|
|
35
|
-
$modeContent = Get-Content $modeFile -Raw
|
|
36
|
-
if ($modeContent -match "mode=true") {
|
|
37
|
-
$composerUsed = $true
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
55
|
# Remove lwazi folder
|
|
42
56
|
if (Test-Path "$ProjectDir\lwazi") {
|
|
43
57
|
Remove-Item -Recurse -Force "$ProjectDir\lwazi"
|
|
@@ -50,7 +64,8 @@ if (Test-Path "$ProjectDir\.env") {
|
|
|
50
64
|
if ($envContent -match "LWAZI_") {
|
|
51
65
|
$lines = Get-Content "$ProjectDir\.env"
|
|
52
66
|
$newLines = $lines | Where-Object { $_ -notmatch "^LWAZI_" -and $_ -notmatch "^# Lwazi AI Configuration" }
|
|
53
|
-
|
|
67
|
+
$content = $newLines -join (Get-LineEnding -Content $envContent)
|
|
68
|
+
Write-FilePreserveNewline -Path "$ProjectDir\.env" -Content $content -OriginalContent $envContent
|
|
54
69
|
Write-Host "Removed Lwazi config from .env" -ForegroundColor Green
|
|
55
70
|
}
|
|
56
71
|
}
|
|
@@ -62,7 +77,8 @@ if (Test-Path $userIni) {
|
|
|
62
77
|
if ($userIniContent -match "auto_prepend_file.*lwazi") {
|
|
63
78
|
$lines = Get-Content $userIni
|
|
64
79
|
$newLines = $lines | Where-Object { $_ -notmatch "auto_prepend_file.*lwazi" }
|
|
65
|
-
|
|
80
|
+
$content = $newLines -join (Get-LineEnding -Content $userIniContent)
|
|
81
|
+
Write-FilePreserveNewline -Path $userIni -Content $content -OriginalContent $userIniContent
|
|
66
82
|
Write-Host "Removed auto_prepend_file from .user.ini" -ForegroundColor Green
|
|
67
83
|
}
|
|
68
84
|
}
|
|
@@ -72,8 +88,9 @@ $indexFile = "$ProjectDir\public\index.php"
|
|
|
72
88
|
if (Test-Path $indexFile) {
|
|
73
89
|
$content = Get-Content $indexFile -Raw
|
|
74
90
|
if ($content -match "lwazi/auto_prepend.php") {
|
|
91
|
+
$original = $content
|
|
75
92
|
$content = $content -replace "`r?`n?require __DIR__`.\"/`../lwazi/auto_prepend.php`";`r?`n?", "`r`n"
|
|
76
|
-
|
|
93
|
+
Write-FilePreserveNewline -Path $indexFile -Content $content -OriginalContent $original
|
|
77
94
|
Write-Host "Removed injection from public\index.php" -ForegroundColor Green
|
|
78
95
|
}
|
|
79
96
|
}
|
|
@@ -83,8 +100,9 @@ $rootIndexFile = "$ProjectDir\index.php"
|
|
|
83
100
|
if (Test-Path $rootIndexFile) {
|
|
84
101
|
$content = Get-Content $rootIndexFile -Raw
|
|
85
102
|
if ($content -match "lwazi/auto_prepend.php") {
|
|
103
|
+
$original = $content
|
|
86
104
|
$content = $content -replace "`r?`n?require __DIR__`.\"/lwazi/auto_prepend.php`";`r?`n?", "`r`n"
|
|
87
|
-
|
|
105
|
+
Write-FilePreserveNewline -Path $rootIndexFile -Content $content -OriginalContent $original
|
|
88
106
|
Write-Host "Removed injection from index.php" -ForegroundColor Green
|
|
89
107
|
}
|
|
90
108
|
}
|
|
@@ -96,46 +114,46 @@ if (Test-Path "$ProjectDir\artisan") {
|
|
|
96
114
|
Write-Host "Cleared caches" -ForegroundColor Green
|
|
97
115
|
}
|
|
98
116
|
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if ($composerJson.PSObject.Properties.Name -contains "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Write-Host "Removed lwazi/core from composer.json" -ForegroundColor Green
|
|
113
|
-
}
|
|
117
|
+
# Always clean up composer.json (lwazi might have added it even if mode wasn't saved)
|
|
118
|
+
Write-Host "Cleaning up composer.json..." -ForegroundColor Yellow
|
|
119
|
+
|
|
120
|
+
$composerJsonFile = "$ProjectDir\composer.json"
|
|
121
|
+
if (Test-Path $composerJsonFile) {
|
|
122
|
+
$composerJson = Get-Content $composerJsonFile | ConvertFrom-Json
|
|
123
|
+
$changed = $false
|
|
124
|
+
|
|
125
|
+
if ($composerJson.PSObject.Properties.Name -contains "require") {
|
|
126
|
+
if ($composerJson.require.PSObject.Properties.Name -contains "lwazi/core") {
|
|
127
|
+
$composerJson.require.PSObject.Properties.Remove("lwazi/core")
|
|
128
|
+
$changed = $true
|
|
129
|
+
Write-Host "Removed lwazi/core from composer.json" -ForegroundColor Green
|
|
114
130
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if ($composerJson.repositories.Count -eq 0) {
|
|
123
|
-
$composerJson.PSObject.Properties.Remove("repositories")
|
|
124
|
-
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if ($composerJson.PSObject.Properties.Name -contains "repositories" -and $composerJson.repositories) {
|
|
134
|
+
$originalCount = $composerJson.repositories.Count
|
|
135
|
+
$composerJson.repositories = $composerJson.repositories | Where-Object { $_.url -ne "lwazi" }
|
|
136
|
+
if ($composerJson.repositories.Count -ne $originalCount) {
|
|
137
|
+
$changed = $true
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
$composerJson | ConvertTo-Json -Depth 10 | Set-Content $composerJsonFile -Encoding UTF8
|
|
139
|
+
if ($composerJson.repositories.Count -eq 0) {
|
|
140
|
+
$composerJson.PSObject.Properties.Remove("repositories")
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
|
|
144
|
+
if ($changed) {
|
|
145
|
+
$original = Get-Content $composerJsonFile -Raw
|
|
146
|
+
$jsonText = $composerJson | ConvertTo-Json -Depth 10
|
|
147
|
+
Write-FilePreserveNewline -Path $composerJsonFile -Content $jsonText -OriginalContent $original
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
|
|
151
|
+
# Run composer dump-autoload
|
|
152
|
+
$composerCmd = Get-Command composer -ErrorAction SilentlyContinue
|
|
153
|
+
if ($composerCmd) {
|
|
154
|
+
composer dump-autoload 2>&1 | Out-Null
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
Write-Host ""
|
|
140
158
|
Write-Host "==========================================" -ForegroundColor Cyan
|
|
141
159
|
Write-Host " Uninstallation Complete!" -ForegroundColor Cyan
|