‪TYPO3CMS  ‪main
CleanUpLocalProcessedFilesCommand.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Symfony\Component\Console\Attribute\AsCommand;
21 use Symfony\Component\Console\Command\Command;
22 use Symfony\Component\Console\Helper\QuestionHelper;
23 use Symfony\Component\Console\Input\InputInterface;
24 use Symfony\Component\Console\Input\InputOption;
25 use Symfony\Component\Console\Output\OutputInterface;
26 use Symfony\Component\Console\Question\ConfirmationQuestion;
27 use Symfony\Component\Console\Style\SymfonyStyle;
30 
31 #[AsCommand('cleanup:localprocessedfiles', 'Delete processed files and their database records.')]
33 {
34  public function ‪__construct(
35  private readonly ‪CleanUpLocalProcessedFilesService $cleanProcessedFilesService
36  ) {
37  parent::__construct();
38  }
39 
40  protected function ‪configure(): void
41  {
42  $this
43  ->setDescription(
44  'Deletes local processed files from local storage that are no longer referenced and ' .
45  'deletes references to processed files that do no longer exist. Also allows to reset ALL files.'
46  )
47  ->setHelp('If you want to get more detailed information, use the --verbose option.')
48  ->addOption(
49  'dry-run',
50  null,
51  InputOption::VALUE_NONE,
52  'If set, the records and files which would be deleted are displayed.'
53  )
54  ->addOption(
55  'all',
56  '',
57  InputOption::VALUE_NONE,
58  'If set, ALL processed-file (driver=Local) records will be removed, also those without identifier ("stubs" for unprocessed files) and existing files.'
59  )
60  ->addOption(
61  'force',
62  'f',
63  InputOption::VALUE_NONE,
64  'Force cleanup. When set the confirmation question will be skipped. When using --no-interaction, --force will be set automatically.'
65  );
66  }
67 
72  protected function ‪execute(InputInterface $input, OutputInterface ‪$output): int
73  {
74  $files = $this->cleanProcessedFilesService->getFilesToClean(0, $input->getOption('all'));
75  $records = $this->cleanProcessedFilesService->getRecordsToClean($input->getOption('all'));
76 
77  if (‪$output->isVerbose()) {
78  foreach ($records as ‪$record) {
79  ‪$output->writeln('[RECORD] Would delete ' . ‪$record['identifier'] . ' UID:' . ‪$record['uid']);
80  }
81 
83  foreach ($files as $file) {
84  $path = ‪PathUtility::stripPathSitePrefix($file->getRealPath());
85  ‪$output->writeln('[FILE] Would delete ' . $path);
86  }
87  }
88 
89  if ($files === [] && $records === []) {
90  ‪$output->writeln('<fg=green>✓</> No processed files or processed records found. Nothing to be done');
91 
92  return Command::SUCCESS;
93  }
94 
95  ‪$output->writeln('Found <options=bold,underscore>' . count($files) . ' files</> and <options=bold,underscore>' . count($records) . ' processed records</>');
96 
97  if ($input->getOption('dry-run')) {
98  return Command::SUCCESS;
99  }
100 
101  // Do not ask for confirmation when running the command in EXT:scheduler
102  if (!$input->isInteractive()) {
103  $input->setOption('force', true);
104  }
105 
106  if (!$input->getOption('force')) {
108  $questionHelper = $this->getHelper('question');
109  $fileDeleteQuestion = new ConfirmationQuestion(
110  'Are you sure you want to delete these processed files and records [default: no] ? ',
111  false
112  );
113  $answerDeleteFiles = $questionHelper->ask($input, ‪$output, $fileDeleteQuestion);
114  }
115 
116  if (($answerDeleteFiles ?? false) || $input->getOption('force')) {
117  [$success, $error] = $this->‪deleteFile($input, ‪$output, $files);
118  // Reload the list auf records to get the files deleted using deleteFile() as well
119  $recordsIncludingDeleted = $this->cleanProcessedFilesService->getRecordsToClean($input->getOption('all'));
120  $deletedRecordsCount = $this->cleanProcessedFilesService->deleteRecord(array_column($recordsIncludingDeleted, 'uid'));
121 
122  $failedRecordCount = count($records) - $deletedRecordsCount;
123  $failedRecords = '';
124  if ($failedRecordCount > 0) {
125  $failedRecords = 'Failed to delete <fg=red>' . $failedRecordCount . '</> records.';
126  }
127 
128  $failedFiles = '';
129  if (count($error) > 0) {
130  $failedFiles = 'Failed to delete <fg=red>' . count($error) . '</> files.';
131  }
132 
133  ‪$output->writeln('');
134 
135  if (count($records) > 0) {
136  ‪$output->writeln('Deleted <fg=green>' . $deletedRecordsCount . '</> processed records. ' . $failedRecords);
137  }
138 
139  if (count($files) > 0) {
140  ‪$output->writeln('Deleted <fg=green>' . count($success) . '</> processed files. ' . $failedFiles);
141  }
142  }
143 
144  return Command::SUCCESS;
145  }
146 
147  protected function ‪deleteFile(InputInterface $input, OutputInterface ‪$output, array $files): array
148  {
149  $isVerbose = ‪$output->isVerbose();
150  if (!$isVerbose) {
151  $io = new SymfonyStyle($input, ‪$output);
152  $progressBar = $io->createProgressBar(count($files));
153  }
154  $error = [];
155  $success = [];
156 
157  foreach ($files as $file) {
158  $path = ‪PathUtility::stripPathSitePrefix($file->getRealPath());
159  if (unlink($file->getRealPath()) === false) {
160  $error[] = $file;
161  $isVerbose ? ‪$output->writeln('[FILE] Failed to delete ' . $path) : $progressBar->advance();
162  } else {
163  $success[] = $file;
164  $isVerbose ? ‪$output->writeln('[FILE] Successfully deleted ' . $path) : $progressBar->advance();
165  }
166  }
167 
168  return [$success, $error];
169  }
170 }
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Lowlevel\Command\CleanUpLocalProcessedFilesCommand\execute
‪execute(InputInterface $input, OutputInterface $output)
Definition: CleanUpLocalProcessedFilesCommand.php:72
‪TYPO3\CMS\Lowlevel\Command\CleanUpLocalProcessedFilesCommand\deleteFile
‪deleteFile(InputInterface $input, OutputInterface $output, array $files)
Definition: CleanUpLocalProcessedFilesCommand.php:147
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Lowlevel\Command\CleanUpLocalProcessedFilesCommand
Definition: CleanUpLocalProcessedFilesCommand.php:33
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Lowlevel\Command\CleanUpLocalProcessedFilesCommand\__construct
‪__construct(private readonly CleanUpLocalProcessedFilesService $cleanProcessedFilesService)
Definition: CleanUpLocalProcessedFilesCommand.php:34
‪TYPO3\CMS\Lowlevel\Command\CleanUpLocalProcessedFilesCommand\configure
‪configure()
Definition: CleanUpLocalProcessedFilesCommand.php:40
‪TYPO3\CMS\Lowlevel\Service\CleanUpLocalProcessedFilesService
Definition: CleanUpLocalProcessedFilesService.php:35
‪TYPO3\CMS\Lowlevel\Command
Definition: CleanFlexFormsCommand.php:18