PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / Ajax / Explore.php
wp-staging / Backup / Ajax Last commit date
Backup 1 day ago FileList 1 day ago Restore 1 day ago Backup.php 1 day ago BackupDownloader.php 1 day ago BackupSizeCalculator.php 1 day ago BackupSpeedIndex.php 1 day ago BaseFileList.php 1 day ago BaseListing.php 1 day ago Delete.php 1 day ago Edit.php 1 day ago Explore.php 1 day ago ExploreCache.php 1 day ago FileInfo.php 1 day ago FileList.php 1 day ago Listing.php 1 day ago Parts.php 1 day ago Restore.php 1 day ago ScheduleList.php 1 day ago Upload.php 1 day ago
Explore.php
854 lines
1 <?php
2
3 namespace WPStaging\Backup\Ajax;
4
5 use WPStaging\Backup\BackupFileIndex;
6 use WPStaging\Backup\Entity\BackupMetadata;
7 use WPStaging\Backup\FileHeader;
8 use WPStaging\Backup\Utils\BackupPathResolver;
9 use WPStaging\Core\WPStaging;
10 use WPStaging\Framework\Component\AbstractTemplateComponent;
11 use WPStaging\Framework\Filesystem\FileObject;
12 use WPStaging\Framework\Filesystem\Filesystem;
13 use WPStaging\Framework\Filesystem\PathIdentifier;
14 use WPStaging\Framework\TemplateEngine\TemplateEngine;
15
16
17
18
19
20
21
22 class Explore extends AbstractTemplateComponent
23 {
24
25
26
27 const MAX_PER_PAGE = 200;
28
29
30
31
32 const MAX_TREE_ITEMS = 300;
33
34
35
36
37 private $pathIdentifier;
38
39
40 private $backupPathResolver;
41
42
43 private $filesystem;
44
45
46 private $exploreCache;
47
48 public function __construct(
49 TemplateEngine $templateEngine,
50 PathIdentifier $pathIdentifier,
51 BackupPathResolver $backupPathResolver,
52 Filesystem $filesystem,
53 ExploreCache $exploreCache
54 ) {
55 parent::__construct($templateEngine);
56 $this->pathIdentifier = $pathIdentifier;
57 $this->backupPathResolver = $backupPathResolver;
58 $this->filesystem = $filesystem;
59 $this->exploreCache = $exploreCache;
60 }
61
62
63
64
65
66
67 public function browse()
68 {
69 if (!$this->canRenderAjax()) {
70 return;
71 }
72
73 $filePath = isset($_POST['filePath']) ? sanitize_text_field(wp_unslash($_POST['filePath'])) : '';
74 if (empty($filePath)) {
75 wp_send_json_error(['message' => __('Backup file is missing.', 'wp-staging')]);
76 }
77
78 $backupFile = $this->backupPathResolver->resolveBackupPath($filePath);
79 if (empty($backupFile) || !file_exists($backupFile)) {
80 wp_send_json_error(['message' => __('Backup file not found.', 'wp-staging')]);
81 }
82
83 try {
84 $metadata = (new BackupMetadata())->hydrateByFilePath($backupFile);
85 } catch (\Throwable $e) {
86 wp_send_json_error(['message' => $e->getMessage()]);
87 }
88
89 $perPage = isset($_POST['perPage']) ? absint($_POST['perPage']) : self::MAX_PER_PAGE;
90 $perPage = max(1, min(self::MAX_PER_PAGE, $perPage));
91
92 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
93 $page = max(1, $page);
94
95 $folder = isset($_POST['folder']) ? sanitize_text_field(wp_unslash($_POST['folder'])) : '';
96 $folder = $this->normalizeFolder($folder);
97
98 $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
99 $sort = 'name_asc';
100
101 $withTree = isset($_POST['withTree'])
102 ? filter_var(wp_unslash($_POST['withTree']), FILTER_VALIDATE_BOOLEAN)
103 : false;
104
105 try {
106 $entries = $this->getDirectoryEntries($backupFile, $metadata, $folder, $search, $sort);
107 } catch (\Throwable $e) {
108 wp_send_json_error(['message' => $e->getMessage()]);
109 }
110
111 $totalEntries = count($entries);
112 $totalPages = (int)ceil($totalEntries / $perPage);
113 $offset = ($page - 1) * $perPage;
114 $pagedEntries = array_slice($entries, $offset, $perPage);
115
116 $response = [
117 'entries' => $pagedEntries,
118 'paging' => [
119 'totalItems' => $totalEntries,
120 'totalPages' => $totalPages,
121 'page' => $page,
122 'hasMore' => $page < $totalPages,
123 ],
124 ];
125
126 if ($withTree) {
127 try {
128 $response['directories'] = $this->getDirectoryTree($backupFile, $metadata, $folder);
129 } catch (\Throwable $e) {
130 $response['directories'] = [];
131 }
132 }
133
134 wp_send_json_success($response);
135 }
136
137
138
139
140 public function listFiles()
141 {
142 if (!$this->canRenderAjax()) {
143 return;
144 }
145
146 $filePath = isset($_POST['filePath']) ? sanitize_text_field(wp_unslash($_POST['filePath'])) : '';
147 if (empty($filePath)) {
148 wp_send_json_error(['message' => __('Backup file is missing.', 'wp-staging')]);
149 }
150
151 $backupFile = $this->backupPathResolver->resolveBackupPath($filePath);
152 if (empty($backupFile) || !file_exists($backupFile)) {
153 wp_send_json_error(['message' => __('Backup file not found.', 'wp-staging')]);
154 }
155
156 try {
157 $metadata = (new BackupMetadata())->hydrateByFilePath($backupFile);
158 } catch (\Throwable $e) {
159 wp_send_json_error(['message' => $e->getMessage()]);
160 }
161
162 $perPage = isset($_POST['perPage']) ? absint($_POST['perPage']) : self::MAX_PER_PAGE;
163 $perPage = max(1, min(self::MAX_PER_PAGE, $perPage));
164
165 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
166 $page = max(1, $page);
167
168 $folder = isset($_POST['folder']) ? sanitize_text_field(wp_unslash($_POST['folder'])) : '';
169 $folder = $this->normalizeFolder($folder);
170
171 $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
172 $sort = 'name_asc';
173
174 try {
175 $entries = $this->getDirectoryEntries($backupFile, $metadata, $folder, $search, $sort);
176 } catch (\Throwable $e) {
177 wp_send_json_error(['message' => $e->getMessage()]);
178 }
179
180 $totalEntries = count($entries);
181 $totalPages = (int)ceil($totalEntries / $perPage);
182 $offset = ($page - 1) * $perPage;
183 $pagedEntries = array_slice($entries, $offset, $perPage);
184
185 wp_send_json_success([
186 'entries' => $pagedEntries,
187 'paging' => [
188 'totalItems' => $totalEntries,
189 'totalPages' => $totalPages,
190 'page' => $page,
191 'hasMore' => $page < $totalPages,
192 ],
193 ]);
194 }
195
196
197
198
199 public function listTree()
200 {
201 if (!$this->canRenderAjax()) {
202 return;
203 }
204
205 $filePath = isset($_POST['filePath']) ? sanitize_text_field(wp_unslash($_POST['filePath'])) : '';
206 if (empty($filePath)) {
207 wp_send_json_error(['message' => __('Backup file is missing.', 'wp-staging')]);
208 }
209
210 $backupFile = $this->backupPathResolver->resolveBackupPath($filePath);
211 if (empty($backupFile) || !file_exists($backupFile)) {
212 wp_send_json_error(['message' => __('Backup file not found.', 'wp-staging')]);
213 }
214
215 try {
216 $metadata = (new BackupMetadata())->hydrateByFilePath($backupFile);
217 } catch (\Throwable $e) {
218 wp_send_json_error(['message' => $e->getMessage()]);
219 }
220
221 $folder = isset($_POST['folder']) ? sanitize_text_field(wp_unslash($_POST['folder'])) : '';
222 $folder = $this->normalizeFolder($folder);
223
224 try {
225 $directories = $this->getDirectoryTree($backupFile, $metadata, $folder);
226 } catch (\Throwable $e) {
227 wp_send_json_error(['message' => $e->getMessage()]);
228 }
229
230 wp_send_json_success([
231 'directories' => $directories,
232 ]);
233 }
234
235
236
237
238 public function listDirectoryFiles()
239 {
240 if (!$this->canRenderAjax()) {
241 return;
242 }
243
244 $filePath = isset($_POST['filePath']) ? sanitize_text_field(wp_unslash($_POST['filePath'])) : '';
245 if (empty($filePath)) {
246 wp_send_json_error(['message' => __('Backup file is missing.', 'wp-staging')]);
247 }
248
249 $backupFile = $this->backupPathResolver->resolveBackupPath($filePath);
250 if (empty($backupFile) || !file_exists($backupFile)) {
251 wp_send_json_error(['message' => __('Backup file not found.', 'wp-staging')]);
252 }
253
254 try {
255 $metadata = (new BackupMetadata())->hydrateByFilePath($backupFile);
256 } catch (\Throwable $e) {
257 wp_send_json_error(['message' => $e->getMessage()]);
258 }
259
260 $folder = isset($_POST['folder']) ? sanitize_text_field(wp_unslash($_POST['folder'])) : '';
261 $folder = $this->normalizeFolder($folder);
262 $summaryOnly = isset($_POST['summaryOnly'])
263 ? filter_var(wp_unslash($_POST['summaryOnly']), FILTER_VALIDATE_BOOLEAN)
264 : false;
265
266 try {
267 if ($summaryOnly) {
268 $summary = $this->getDirectoryStatsForSelection($backupFile, $metadata, $folder);
269 wp_send_json_success([
270 'summary' => $summary,
271 ]);
272 }
273
274 $files = $this->getDirectoryFilesForSelection($backupFile, $metadata, $folder);
275 } catch (\Throwable $e) {
276 wp_send_json_error(['message' => $e->getMessage()]);
277 }
278
279 wp_send_json_success([
280 'files' => $files,
281 ]);
282 }
283
284
285
286
287
288
289
290
291
292
293
294 private function getDirectoryEntries(string $backupFile, BackupMetadata $metadata, string $folder, string $search, string $sort): array
295 {
296 $isSearching = $search !== '';
297
298
299 if (!$isSearching) {
300 $tree = $this->exploreCache->getOrBuild($backupFile, $metadata);
301 if ($tree !== null) {
302 return $this->getEntriesFromCache($tree, $folder, $sort);
303 }
304 }
305
306 return $this->getDirectoryEntriesFromIndex($backupFile, $metadata, $folder, $search, $sort);
307 }
308
309
310
311
312
313
314
315
316
317 private function getEntriesFromCache(array $tree, string $folder, string $sort): array
318 {
319 if (!isset($tree[$folder])) {
320 return [];
321 }
322
323 $bucket = $tree[$folder];
324 $directories = [];
325 foreach ($bucket['dirs'] as $dir) {
326 $directories[] = [
327 'type' => 'dir',
328 'name' => $dir['name'],
329 'path' => $dir['path'],
330 'items' => $dir['items'] ?? 0,
331 'hasChildren' => $dir['hasChildren'] ?? false,
332 ];
333 }
334
335 $files = [];
336 foreach ($bucket['files'] as $file) {
337 $files[] = [
338 'type' => 'file',
339 'name' => $file['name'],
340 'path' => $file['path'],
341 'size' => $file['size'],
342 'sizeFormatted' => size_format($file['size'], 2),
343 'offset' => $file['offset'],
344 ];
345 }
346
347 $directories = $this->sortDirectories($directories, $sort);
348 $files = $this->sortFiles($files, $sort);
349
350 return array_merge($directories, $files);
351 }
352
353
354
355
356
357
358
359
360
361 private function getDirectoryTree(string $backupFile, BackupMetadata $metadata, string $folder): array
362 {
363 $tree = $this->exploreCache->getOrBuild($backupFile, $metadata);
364 if ($tree !== null) {
365 return $this->getTreeFromCache($tree, $folder);
366 }
367
368 return $this->getDirectoryTreeFromIndex($backupFile, $metadata, $folder);
369 }
370
371
372
373
374
375
376
377
378 private function getTreeFromCache(array $tree, string $folder): array
379 {
380 if (!isset($tree[$folder])) {
381 return [];
382 }
383
384 $dirs = $tree[$folder]['dirs'];
385
386 $result = [];
387 foreach ($dirs as $dir) {
388 $result[] = [
389 'name' => $dir['name'],
390 'path' => $dir['path'],
391 'hasChildren' => $dir['hasChildren'] ?? false,
392 ];
393 }
394
395 usort($result, function ($a, $b) {
396 return strcasecmp($a['name'], $b['name']);
397 });
398
399 return array_slice($result, 0, self::MAX_TREE_ITEMS);
400 }
401
402
403
404
405
406
407
408
409
410 private function getDirectoryStatsForSelection(string $backupFile, BackupMetadata $metadata, string $folder): array
411 {
412 $tree = $this->exploreCache->getOrBuild($backupFile, $metadata);
413 if ($tree !== null) {
414 return $this->getStatsFromCache($tree, $folder);
415 }
416
417 return $this->getDirectoryStatsFromIndex($backupFile, $metadata, $folder);
418 }
419
420
421
422
423
424
425
426
427 private function getStatsFromCache(array $tree, string $folder): array
428 {
429 $count = 0;
430 $size = 0;
431
432 $stack = [$folder];
433 while (!empty($stack)) {
434 $dir = array_pop($stack);
435 if (!isset($tree[$dir])) {
436 continue;
437 }
438
439 foreach ($tree[$dir]['files'] as $file) {
440 $count++;
441 $size += $file['size'];
442 }
443
444 foreach ($tree[$dir]['dirs'] as $subdir) {
445 $stack[] = $subdir['path'];
446 }
447 }
448
449 return [
450 'count' => $count,
451 'size' => $size,
452 ];
453 }
454
455
456
457
458
459
460
461
462
463 private function getDirectoryFilesForSelection(string $backupFile, BackupMetadata $metadata, string $folder): array
464 {
465 $tree = $this->exploreCache->getOrBuild($backupFile, $metadata);
466 if ($tree !== null) {
467 return $this->getFilesFromCache($tree, $folder);
468 }
469
470 return $this->getDirectoryFilesFromIndex($backupFile, $metadata, $folder);
471 }
472
473
474
475
476
477
478
479
480 private function getFilesFromCache(array $tree, string $folder): array
481 {
482 $files = [];
483 $stack = [$folder];
484
485 while (!empty($stack)) {
486 $dir = array_pop($stack);
487 if (!isset($tree[$dir])) {
488 continue;
489 }
490
491 foreach ($tree[$dir]['files'] as $file) {
492 $files[] = [
493 'offset' => $file['offset'],
494 'path' => $file['path'],
495 'size' => $file['size'],
496 ];
497 }
498
499 foreach ($tree[$dir]['dirs'] as $subdir) {
500 $stack[] = $subdir['path'];
501 }
502 }
503
504 return $files;
505 }
506
507
508
509
510
511
512
513
514
515
516
517 private function getDirectoryEntriesFromIndex(string $backupFile, BackupMetadata $metadata, string $folder, string $search, string $sort): array
518 {
519 $isSearching = $search !== '';
520 $prefix = $isSearching ? '' : ($folder === '' ? '' : trailingslashit($folder));
521 $directories = [];
522 $directoryChildren = [];
523 $directoryHasSubdirs = [];
524 $files = [];
525
526 $indexLineDto = $this->createIndexLineDto($metadata);
527 $fileObject = new FileObject($backupFile, FileObject::MODE_READ);
528 $fileObject->fseek((int)$metadata->getHeaderStart());
529
530 while ($fileObject->valid() && $fileObject->ftell() < (int)$metadata->getHeaderEnd()) {
531 $indexOffset = $fileObject->ftell();
532 $rawIndexFile = $fileObject->readAndMoveNext();
533 if (!$indexLineDto->isIndexLine($rawIndexFile)) {
534 continue;
535 }
536
537 $backupFileIndex = $indexLineDto->readIndexLine($rawIndexFile);
538 $relativePath = $this->pathIdentifier->transformIdentifiableToRelativePath($backupFileIndex->getIdentifiablePath());
539 $relativePath = $this->filesystem->normalizePath($relativePath);
540
541 if ($prefix !== '' && strpos($relativePath, $prefix) !== 0) {
542 continue;
543 }
544
545 $remaining = $prefix === '' ? $relativePath : substr($relativePath, strlen($prefix));
546 if ($remaining === '') {
547 continue;
548 }
549
550 $parts = explode('/', $remaining);
551 if (!$isSearching && count($parts) > 1) {
552 $dirName = $parts[0];
553 $childName = $parts[1] ?? '';
554 if (!empty($childName)) {
555 $directoryChildren[$dirName][$childName] = true;
556 }
557
558 if (count($parts) > 2) {
559 $directoryHasSubdirs[$dirName] = true;
560 }
561
562 if ($this->matchesSearch($dirName, $search)) {
563 $directories[$dirName] = [
564 'type' => 'dir',
565 'name' => $dirName,
566 'path' => $prefix . $dirName,
567 'items' => 0,
568 ];
569 }
570
571 continue;
572 }
573
574 $fileName = basename($relativePath);
575 if (!$this->matchesSearch($fileName, $search)) {
576 continue;
577 }
578
579 $size = (int)$backupFileIndex->getUncompressedSize();
580 $files[] = [
581 'type' => 'file',
582 'name' => $fileName,
583 'path' => $relativePath,
584 'size' => $size,
585 'sizeFormatted' => size_format($size, 2),
586 'offset' => (int)$indexOffset,
587 ];
588 }
589
590 $fileObject = null;
591
592 if (!$isSearching) {
593 foreach ($directories as $dirName => $dirData) {
594 $directories[$dirName]['items'] = isset($directoryChildren[$dirName]) ? count($directoryChildren[$dirName]) : 0;
595 $directories[$dirName]['hasChildren'] = isset($directoryHasSubdirs[$dirName]);
596 }
597
598 $directories = array_values($directories);
599 $directories = $this->sortDirectories($directories, $sort);
600 $files = $this->sortFiles($files, $sort);
601
602 return array_merge($directories, $files);
603 }
604
605 return $this->sortFiles($files, $sort);
606 }
607
608
609
610
611
612
613
614 private function getDirectoryTreeFromIndex(string $backupFile, BackupMetadata $metadata, string $folder): array
615 {
616 $prefix = $folder === '' ? '' : trailingslashit($folder);
617 $directories = [];
618
619 $indexLineDto = $this->createIndexLineDto($metadata);
620 $fileObject = new FileObject($backupFile, FileObject::MODE_READ);
621 $fileObject->fseek((int)$metadata->getHeaderStart());
622
623 while ($fileObject->valid() && $fileObject->ftell() < (int)$metadata->getHeaderEnd()) {
624 $rawIndexFile = $fileObject->readAndMoveNext();
625 if (!$indexLineDto->isIndexLine($rawIndexFile)) {
626 continue;
627 }
628
629 $backupFileIndex = $indexLineDto->readIndexLine($rawIndexFile);
630 $relativePath = $this->pathIdentifier->transformIdentifiableToRelativePath($backupFileIndex->getIdentifiablePath());
631 $relativePath = $this->filesystem->normalizePath($relativePath);
632
633 if ($prefix !== '' && strpos($relativePath, $prefix) !== 0) {
634 continue;
635 }
636
637 $remaining = $prefix === '' ? $relativePath : substr($relativePath, strlen($prefix));
638 if ($remaining === '') {
639 continue;
640 }
641
642 $parts = explode('/', $remaining);
643 if (count($parts) <= 1) {
644 continue;
645 }
646
647 $dirName = $parts[0];
648 if (!isset($directories[$dirName])) {
649 $directories[$dirName] = [
650 'name' => $dirName,
651 'path' => $prefix . $dirName,
652 'hasChildren' => false,
653 ];
654 }
655
656 if (count($parts) > 2) {
657 $directories[$dirName]['hasChildren'] = true;
658 }
659
660 if (count($directories) >= self::MAX_TREE_ITEMS) {
661 break;
662 }
663 }
664
665 $fileObject = null;
666
667 $directories = array_values($directories);
668 usort($directories, function ($a, $b) {
669 return strcasecmp($a['name'], $b['name']);
670 });
671
672 return $directories;
673 }
674
675
676
677
678
679
680
681 private function getDirectoryFilesFromIndex(string $backupFile, BackupMetadata $metadata, string $folder): array
682 {
683 $prefix = $folder === '' ? '' : trailingslashit($folder);
684
685 $files = [];
686 $indexLineDto = $this->createIndexLineDto($metadata);
687 $fileObject = new FileObject($backupFile, FileObject::MODE_READ);
688 $fileObject->fseek((int)$metadata->getHeaderStart());
689
690 while ($fileObject->valid() && $fileObject->ftell() < (int)$metadata->getHeaderEnd()) {
691 $indexOffset = $fileObject->ftell();
692 $rawIndexFile = $fileObject->readAndMoveNext();
693 if (!$indexLineDto->isIndexLine($rawIndexFile)) {
694 continue;
695 }
696
697 $backupFileIndex = $indexLineDto->readIndexLine($rawIndexFile);
698 $relativePath = $this->pathIdentifier->transformIdentifiableToRelativePath($backupFileIndex->getIdentifiablePath());
699 $relativePath = $this->filesystem->normalizePath($relativePath);
700
701 if ($prefix !== '' && strpos($relativePath, $prefix) !== 0) {
702 continue;
703 }
704
705 if ($relativePath === $prefix) {
706 continue;
707 }
708
709 $files[] = [
710 'offset' => (int)$indexOffset,
711 'path' => $relativePath,
712 'size' => (int)$backupFileIndex->getUncompressedSize(),
713 ];
714 }
715
716 $fileObject = null;
717
718 return $files;
719 }
720
721
722
723
724
725
726
727 private function getDirectoryStatsFromIndex(string $backupFile, BackupMetadata $metadata, string $folder): array
728 {
729 $prefix = $folder === '' ? '' : trailingslashit($folder);
730
731 $count = 0;
732 $size = 0;
733
734 $indexLineDto = $this->createIndexLineDto($metadata);
735 $fileObject = new FileObject($backupFile, FileObject::MODE_READ);
736 $fileObject->fseek((int)$metadata->getHeaderStart());
737
738 while ($fileObject->valid() && $fileObject->ftell() < (int)$metadata->getHeaderEnd()) {
739 $rawIndexFile = $fileObject->readAndMoveNext();
740 if (!$indexLineDto->isIndexLine($rawIndexFile)) {
741 continue;
742 }
743
744 $backupFileIndex = $indexLineDto->readIndexLine($rawIndexFile);
745 $relativePath = $this->pathIdentifier->transformIdentifiableToRelativePath($backupFileIndex->getIdentifiablePath());
746 $relativePath = $this->filesystem->normalizePath($relativePath);
747
748 if ($prefix !== '' && strpos($relativePath, $prefix) !== 0) {
749 continue;
750 }
751
752 if ($relativePath === $prefix) {
753 continue;
754 }
755
756 $count++;
757 $size += (int)$backupFileIndex->getUncompressedSize();
758 }
759
760 $fileObject = null;
761
762 return [
763 'count' => $count,
764 'size' => $size,
765 ];
766 }
767
768
769
770
771
772
773
774 private function normalizeFolder(string $folder): string
775 {
776 $folder = trim($folder);
777 $folder = trim($folder, '/');
778
779 return $this->filesystem->normalizePath($folder);
780 }
781
782
783
784
785
786
787 private function matchesSearch(string $name, string $search): bool
788 {
789 if ($search === '') {
790 return true;
791 }
792
793 $normalizedName = basename($name);
794
795
796 return stripos($normalizedName, $search) === 0;
797 }
798
799
800
801
802
803
804 private function sortDirectories(array $directories, string $sort): array
805 {
806 usort($directories, function ($a, $b) use ($sort) {
807 if ($sort === 'name_desc') {
808 return strcasecmp($b['name'], $a['name']);
809 }
810
811 return strcasecmp($a['name'], $b['name']);
812 });
813
814 return $directories;
815 }
816
817
818
819
820
821
822 private function sortFiles(array $files, string $sort): array
823 {
824 usort($files, function ($a, $b) use ($sort) {
825 switch ($sort) {
826 case 'size_asc':
827 return $a['size'] <=> $b['size'];
828 case 'size_desc':
829 return $b['size'] <=> $a['size'];
830 case 'name_desc':
831 return strcasecmp($b['name'], $a['name']);
832 case 'name_asc':
833 default:
834 return strcasecmp($a['name'], $b['name']);
835 }
836 });
837
838 return $files;
839 }
840
841
842
843
844
845 private function createIndexLineDto(BackupMetadata $metadata)
846 {
847 if ($metadata->getIsBackupFormatV1()) {
848 return new BackupFileIndex();
849 }
850
851 return WPStaging::make(FileHeader::class);
852 }
853 }
854