lwazi 1.16.2 → 1.16.5
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 +279 -44
- 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,7 +225,176 @@ function extractCsrfToken(string $html): ?string {
|
|
|
204
225
|
return null;
|
|
205
226
|
}
|
|
206
227
|
|
|
207
|
-
function
|
|
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
|
+
$firstTextField = null;
|
|
302
|
+
|
|
303
|
+
if ($targetForm) {
|
|
304
|
+
$actionAttr = trim($targetForm->getAttribute('action'));
|
|
305
|
+
if ($actionAttr !== '') {
|
|
306
|
+
$action = normalizeUrl($actionAttr, $loginUrl);
|
|
307
|
+
}
|
|
308
|
+
$methodAttr = strtolower(trim($targetForm->getAttribute('method')));
|
|
309
|
+
if ($methodAttr !== '') {
|
|
310
|
+
$method = $methodAttr;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
$inputs = $targetForm->getElementsByTagName('input');
|
|
314
|
+
foreach ($inputs as $input) {
|
|
315
|
+
$name = $input->getAttribute('name');
|
|
316
|
+
if ($name === '') {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
$type = strtolower($input->getAttribute('type'));
|
|
320
|
+
$value = $input->getAttribute('value');
|
|
321
|
+
if ($type === '' || $type === 'text' || $type === 'email' || $type === 'password' || $type === 'hidden' || $type === 'checkbox' || $type === 'submit') {
|
|
322
|
+
if (!isset($fields[$name]) && $value !== '') {
|
|
323
|
+
$fields[$name] = $value;
|
|
324
|
+
} elseif ($type === 'hidden') {
|
|
325
|
+
$fields[$name] = $value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (in_array($type, ['text', 'email'], true)) {
|
|
329
|
+
if ($firstTextField === null) {
|
|
330
|
+
$firstTextField = $name;
|
|
331
|
+
}
|
|
332
|
+
if (preg_match('/email|user|username|login/i', $name)) {
|
|
333
|
+
$userField = $name;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if ($type === 'password' && $passField === null) {
|
|
337
|
+
$passField = $name;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if ($userField === null && $firstTextField !== null) {
|
|
343
|
+
$userField = $firstTextField;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return [
|
|
347
|
+
'action' => $action,
|
|
348
|
+
'method' => $method,
|
|
349
|
+
'fields' => $fields,
|
|
350
|
+
'user_field' => $userField,
|
|
351
|
+
'pass_field' => $passField,
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function readPasswordMasked(string $prompt): string {
|
|
356
|
+
// Masked password input for POSIX terminals, with a safe fallback.
|
|
357
|
+
$sttyMode = null;
|
|
358
|
+
if (function_exists('shell_exec')) {
|
|
359
|
+
$sttyMode = shell_exec('stty -g 2>/dev/null');
|
|
360
|
+
if ($sttyMode) {
|
|
361
|
+
shell_exec('stty -echo -icanon min 1 time 0 2>/dev/null');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
echo $prompt;
|
|
366
|
+
$password = '';
|
|
367
|
+
|
|
368
|
+
if ($sttyMode) {
|
|
369
|
+
while (true) {
|
|
370
|
+
$char = fread(STDIN, 1);
|
|
371
|
+
if ($char === false || $char === '') {
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
if ($char === "\n" || $char === "\r") {
|
|
375
|
+
echo "\n";
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if ($char === "\x7f" || $char === "\x08") { // backspace/delete
|
|
379
|
+
if (strlen($password) > 0) {
|
|
380
|
+
$password = substr($password, 0, -1);
|
|
381
|
+
echo "\x08 \x08";
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
$password .= $char;
|
|
386
|
+
echo "*";
|
|
387
|
+
}
|
|
388
|
+
shell_exec('stty ' . $sttyMode . ' 2>/dev/null');
|
|
389
|
+
return $password;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Fallback (no masking) if stty isn't available.
|
|
393
|
+
$line = fgets(STDIN);
|
|
394
|
+
return $line === false ? '' : rtrim($line, "\r\n");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function loginAndGetCookies(string $loginUrl, string $username, string $password, string $cookies, ?string $checkUrl = null): bool {
|
|
208
398
|
echo "Attempting to login to $loginUrl...\n";
|
|
209
399
|
|
|
210
400
|
$ch = curl_init();
|
|
@@ -224,32 +414,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
|
|
|
224
414
|
echo "Failed to fetch login page\n";
|
|
225
415
|
return false;
|
|
226
416
|
}
|
|
227
|
-
|
|
417
|
+
|
|
418
|
+
$form = parseLoginForm($loginPage, $loginUrl);
|
|
228
419
|
$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) {
|
|
420
|
+
$loginData = $form['fields'];
|
|
421
|
+
|
|
422
|
+
$userField = $form['user_field'] ?: 'email';
|
|
423
|
+
$passField = $form['pass_field'] ?: 'password';
|
|
424
|
+
$loginData[$userField] = $username;
|
|
425
|
+
$loginData[$passField] = $password;
|
|
426
|
+
|
|
427
|
+
if ($csrfToken && !isset($loginData['_token'])) {
|
|
246
428
|
$loginData['_token'] = $csrfToken;
|
|
247
429
|
}
|
|
248
430
|
|
|
249
431
|
$ch = curl_init();
|
|
250
|
-
curl_setopt($ch, CURLOPT_URL, $
|
|
251
|
-
curl_setopt($ch, CURLOPT_POST,
|
|
252
|
-
|
|
432
|
+
curl_setopt($ch, CURLOPT_URL, $form['action']);
|
|
433
|
+
curl_setopt($ch, CURLOPT_POST, ($form['method'] !== 'get'));
|
|
434
|
+
if ($form['method'] === 'get') {
|
|
435
|
+
curl_setopt($ch, CURLOPT_URL, $form['action'] . '?' . http_build_query($loginData));
|
|
436
|
+
} else {
|
|
437
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($loginData));
|
|
438
|
+
}
|
|
253
439
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
254
440
|
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
|
|
255
441
|
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
|
|
@@ -257,22 +443,47 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
|
|
|
257
443
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
258
444
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
259
445
|
curl_setopt($ch, CURLOPT_USERAGENT, 'Lwazi/1.0');
|
|
446
|
+
curl_setopt($ch, CURLOPT_REFERER, $loginUrl);
|
|
447
|
+
if ($csrfToken) {
|
|
448
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-CSRF-TOKEN: ' . $csrfToken]);
|
|
449
|
+
}
|
|
260
450
|
|
|
261
451
|
$result = curl_exec($ch);
|
|
262
452
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
263
453
|
curl_close($ch);
|
|
264
454
|
|
|
265
|
-
if ($httpCode >= 200 && $httpCode < 400) {
|
|
455
|
+
if ($httpCode >= 200 && $httpCode < 400 && $result && !isLoginPage($result)) {
|
|
266
456
|
echo "Login successful!\n";
|
|
267
457
|
return true;
|
|
268
458
|
}
|
|
269
|
-
|
|
459
|
+
|
|
460
|
+
if ($checkUrl) {
|
|
461
|
+
$probe = fetchPage($checkUrl, $cookies);
|
|
462
|
+
if ($probe['html'] && !isLoginPage($probe['html'])) {
|
|
463
|
+
echo "Login successful!\n";
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
270
468
|
echo "Login may have failed (HTTP $httpCode)\n";
|
|
271
469
|
return false;
|
|
272
470
|
}
|
|
273
471
|
|
|
472
|
+
function ensureSessionAlive(string $checkUrl, string $loginUrl, string $loginUser, string $loginPass, string $cookies): bool {
|
|
473
|
+
if (!$loginUrl || !$loginUser || !$loginPass) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
$probe = fetchPage($checkUrl, $cookies);
|
|
478
|
+
if ($probe['html'] && !isLoginPage($probe['html'])) {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
|
|
483
|
+
}
|
|
484
|
+
|
|
274
485
|
if ($loginUrl && $loginUser && $loginPass) {
|
|
275
|
-
$loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
|
|
486
|
+
$loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies, $rootUrl);
|
|
276
487
|
if (!$loggedIn) {
|
|
277
488
|
echo "Warning: Login may have failed. Continuing anyway...\n";
|
|
278
489
|
}
|
|
@@ -289,6 +500,8 @@ $adjacency = [];
|
|
|
289
500
|
$pageData = [];
|
|
290
501
|
|
|
291
502
|
$totalPages = 0;
|
|
503
|
+
$keepAliveInterval = 120;
|
|
504
|
+
$lastKeepAliveAt = time();
|
|
292
505
|
|
|
293
506
|
while (!empty($queue) && $totalPages < $maxPages) {
|
|
294
507
|
[$url, $depth] = array_shift($queue);
|
|
@@ -302,33 +515,48 @@ while (!empty($queue) && $totalPages < $maxPages) {
|
|
|
302
515
|
$totalPages++;
|
|
303
516
|
|
|
304
517
|
echo "Crawling [$totalPages/$maxPages]: $url (depth: $depth)\n";
|
|
305
|
-
|
|
306
|
-
$
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
$
|
|
317
|
-
$
|
|
318
|
-
|
|
518
|
+
|
|
519
|
+
if ($loginUrl && $loginUser && $loginPass) {
|
|
520
|
+
$now = time();
|
|
521
|
+
if (($now - $lastKeepAliveAt) >= $keepAliveInterval) {
|
|
522
|
+
ensureSessionAlive($rootUrl, $loginUrl, $loginUser, $loginPass, $cookies);
|
|
523
|
+
$lastKeepAliveAt = $now;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
$response = fetchPage($url, $cookies);
|
|
528
|
+
$html = $response['html'];
|
|
529
|
+
$httpCode = $response['http_code'];
|
|
530
|
+
$finalUrl = $response['final_url'];
|
|
531
|
+
$headers = $response['headers'];
|
|
319
532
|
|
|
320
533
|
if (!$html || $httpCode >= 400) {
|
|
321
534
|
echo " -> Failed (HTTP $httpCode)\n";
|
|
322
535
|
continue;
|
|
323
536
|
}
|
|
324
537
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
538
|
+
if ($html && isLoginPage($html)) {
|
|
539
|
+
if ($loginUrl && $loginUser && $loginPass) {
|
|
540
|
+
echo " -> Login required, re-authenticating and retrying\n";
|
|
541
|
+
$loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies, $rootUrl);
|
|
542
|
+
if ($loggedIn) {
|
|
543
|
+
$response = fetchPage($url, $cookies);
|
|
544
|
+
$html = $response['html'];
|
|
545
|
+
$httpCode = $response['http_code'];
|
|
546
|
+
$finalUrl = $response['final_url'];
|
|
547
|
+
$headers = $response['headers'];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!$html || isLoginPage($html)) {
|
|
552
|
+
echo " -> Login required, skipping\n";
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
328
555
|
}
|
|
329
556
|
|
|
330
557
|
$title = extractTitle($html);
|
|
331
558
|
$description = extractMeta($html, 'description');
|
|
559
|
+
$text = extractText($html);
|
|
332
560
|
$links = extractLinks($html, $url);
|
|
333
561
|
|
|
334
562
|
$nodes[$urlId] = [
|
|
@@ -342,6 +570,13 @@ while (!empty($queue) && $totalPages < $maxPages) {
|
|
|
342
570
|
$pageData[$urlId] = [
|
|
343
571
|
'html' => $html,
|
|
344
572
|
'url' => $url,
|
|
573
|
+
'final_url' => $finalUrl,
|
|
574
|
+
'title' => $title,
|
|
575
|
+
'description' => $description,
|
|
576
|
+
'text' => $text,
|
|
577
|
+
'headers' => $headers,
|
|
578
|
+
'fetched_at' => date('c'),
|
|
579
|
+
'http_code' => $httpCode,
|
|
345
580
|
];
|
|
346
581
|
|
|
347
582
|
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
|