‪TYPO3CMS  10.4
MissingFilesCommand.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\Command\Command;
21 use Symfony\Component\Console\Input\InputInterface;
22 use Symfony\Component\Console\Input\InputOption;
23 use Symfony\Component\Console\Output\OutputInterface;
24 use Symfony\Component\Console\Style\SymfonyStyle;
31 
35 class ‪MissingFilesCommand extends Command
36 {
37 
41  public function ‪configure()
42  {
43  $this
44  ->setDescription('Find all file references from records pointing to a missing (non-existing) file.')
45  ->setHelp('
46 Assumptions:
47 - a perfect integrity of the reference index table (always update the reference index table before using this tool!)
48 - relevant soft reference parsers applied everywhere file references are used inline
49 
50 Files may be missing for these reasons (except software bugs):
51 - someone manually deleted the file inside fileadmin/ or another user maintained folder. If the reference was a soft reference (opposite to a DataHandler managed file relation from "group" type fields), technically it is not an error although it might be a mistake that someone did so.
52 - someone manually deleted the file inside the uploads/ folder (typically containing managed files) which is an error since no user interaction should take place there.
53 
54 Manual repair suggestions (using --dry-run):
55 - Managed files: You might be able to locate the file and re-insert it in the correct location. However, no automatic fix can do that for you.
56 - Soft References: You should investigate each case and edit the content accordingly. A soft reference to a file could be in an HTML image tag (for example <img src="missing_file.jpg" />) and you would have to either remove the whole tag, change the filename or re-create the missing file.
57 
58 If the option "--dry-run" is not set, all managed files (TCA/FlexForm attachments) will silently remove the reference
59 from the record since the file is missing. For this reason you might prefer a manual approach instead.
60 All soft references with missing files require manual fix if you consider it an error.
61 
62 If you want to get more detailed information, use the --verbose option.')
63  ->addOption(
64  'dry-run',
65  null,
66  InputOption::VALUE_NONE,
67  'If this option is set, the references will not be removed, but just the output which files would be deleted are shown'
68  )
69  ->addOption(
70  'update-refindex',
71  null,
72  InputOption::VALUE_NONE,
73  'Setting this option automatically updates the reference index and does not ask on command line. Alternatively, use -n to avoid the interactive mode'
74  );
75  }
76 
87  protected function ‪execute(InputInterface $input, OutputInterface ‪$output)
88  {
89  // Make sure the _cli_ user is loaded
91 
92  $io = new SymfonyStyle($input, ‪$output);
93  $io->title($this->getDescription());
94 
95  $dryRun = $input->hasOption('dry-run') && $input->getOption('dry-run') != false ? true : false;
96 
97  // Update the reference index
98  $this->‪updateReferenceIndex($input, $io);
99 
100  // Find missing soft references (cannot be updated / deleted)
101  $missingSoftReferencedFiles = $this->‪findMissingSoftReferencedFiles();
102  if (count($missingSoftReferencedFiles)) {
103  $io->note('Found ' . count($missingSoftReferencedFiles) . ' soft-referenced files that need manual repair.');
104  $io->listing($missingSoftReferencedFiles);
105  }
106 
107  // Find missing references
108  $missingReferencedFiles = $this->‪findMissingReferencedFiles();
109  if (count($missingReferencedFiles)) {
110  $io->note('Found ' . count($missingReferencedFiles) . ' references to non-existing files.');
111 
112  $this->‪removeReferencesToMissingFiles($missingReferencedFiles, $dryRun, $io);
113  $io->success('All references were updated accordingly.');
114  }
115 
116  if (!count($missingSoftReferencedFiles) && !count($missingReferencedFiles)) {
117  $io->success('Nothing to do, no missing files found. Everything is in place.');
118  }
119  return 0;
120  }
121 
131  protected function ‪updateReferenceIndex(InputInterface $input, SymfonyStyle $io)
132  {
133  // Check for reference index to update
134  $io->note('Finding missing files referenced by TYPO3 requires a clean reference index (sys_refindex)');
135  if ($input->hasOption('update-refindex') && $input->getOption('update-refindex')) {
136  $updateReferenceIndex = true;
137  } elseif ($input->isInteractive()) {
138  $updateReferenceIndex = $io->confirm('Should the reference index be updated right now?', false);
139  } else {
140  $updateReferenceIndex = false;
141  }
142 
143  // Update the reference index
144  if ($updateReferenceIndex) {
145  $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
146  $progressListener->initialize($io);
147  $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
148  $referenceIndex->updateIndex(false, $progressListener);
149  } else {
150  $io->writeln('Reference index is assumed to be up to date, continuing.');
151  }
152  }
153 
161  protected function ‪findMissingReferencedFiles(): array
162  {
163  $missingReferences = [];
164  // Select all files in the reference table
165  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
166  ->getQueryBuilderForTable('sys_refindex');
167 
168  $result = $queryBuilder
169  ->select('*')
170  ->from('sys_refindex')
171  ->where(
172  $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('_FILE', \PDO::PARAM_STR)),
173  $queryBuilder->expr()->isNull('softref_key')
174  )
175  ->execute();
176 
177  // Traverse the references and check if the files exists
178  while ($record = $result->fetch()) {
179  $fileName = $this->‪getFileNameWithoutAnchor($record['ref_string']);
180  if (empty($record['softref_key']) && !@is_file(‪Environment::getPublicPath() . '/' . $fileName)) {
181  $missingReferences[$fileName][$record['hash']] = $this->‪formatReferenceIndexEntryToString($record);
182  }
183  }
184 
185  return $missingReferences;
186  }
187 
194  protected function ‪findMissingSoftReferencedFiles(): array
195  {
196  $missingReferences = [];
197  // Select all files in the reference table
198  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
199  ->getQueryBuilderForTable('sys_refindex');
200 
201  $result = $queryBuilder
202  ->select('*')
203  ->from('sys_refindex')
204  ->where(
205  $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('_FILE', \PDO::PARAM_STR)),
206  $queryBuilder->expr()->isNotNull('softref_key')
207  )
208  ->execute();
209 
210  // Traverse the references and check if the files exists
211  while ($record = $result->fetch()) {
212  $fileName = $this->‪getFileNameWithoutAnchor($record['ref_string']);
213  if (!@is_file(‪Environment::getPublicPath() . '/' . $fileName)) {
214  $missingReferences[] = $fileName . ' - ' . $record['hash'] . ' - ' . $this->‪formatReferenceIndexEntryToString($record);
215  }
216  }
217  return $missingReferences;
218  }
219 
226  protected function ‪getFileNameWithoutAnchor(string $fileName): string
227  {
228  if (strpos($fileName, '#') !== false) {
229  [$fileName] = explode('#', $fileName);
230  }
231  return $fileName;
232  }
233 
241  protected function ‪removeReferencesToMissingFiles(array $missingManagedFiles, bool $dryRun, SymfonyStyle $io)
242  {
243  foreach ($missingManagedFiles as $fileName => $references) {
244  if ($io->isVeryVerbose()) {
245  $io->writeln('Deleting references to missing file "' . $fileName . '"');
246  }
247  foreach ($references as $hash => $recordReference) {
248  $io->writeln('Removing reference in record "' . $recordReference . '"');
249  if (!$dryRun) {
250  $sysRefObj = GeneralUtility::makeInstance(ReferenceIndex::class);
251  $error = $sysRefObj->setReferenceValue($hash, null);
252  if ($error) {
253  $io->error('ReferenceIndex::setReferenceValue() reported "' . $error . '"');
254  }
255  }
256  }
257  }
258  }
259 
266  protected function ‪formatReferenceIndexEntryToString(array $record): string
267  {
268  return $record['tablename'] . ':' . $record['recuid'] . ':' . $record['field'] . ':' . $record['flexpointer'] . ':' . $record['softref_key'] . ($record['deleted'] ? ' (DELETED)' : '');
269  }
270 }
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\findMissingSoftReferencedFiles
‪array findMissingSoftReferencedFiles()
Definition: MissingFilesCommand.php:194
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\formatReferenceIndexEntryToString
‪string formatReferenceIndexEntryToString(array $record)
Definition: MissingFilesCommand.php:266
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:180
‪TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener
Definition: ReferenceIndexProgressListener.php:30
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:48
‪TYPO3\CMS\Core\Core\Bootstrap\initializeBackendAuthentication
‪static initializeBackendAuthentication($proceedIfNoUserIsLoggedIn=false)
Definition: Bootstrap.php:607
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\configure
‪configure()
Definition: MissingFilesCommand.php:41
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\getFileNameWithoutAnchor
‪string getFileNameWithoutAnchor(string $fileName)
Definition: MissingFilesCommand.php:226
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\findMissingReferencedFiles
‪array findMissingReferencedFiles()
Definition: MissingFilesCommand.php:161
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\execute
‪int execute(InputInterface $input, OutputInterface $output)
Definition: MissingFilesCommand.php:87
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:40
‪TYPO3\CMS\Core\Core\Bootstrap
Definition: Bootstrap.php:66
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand
Definition: MissingFilesCommand.php:36
‪TYPO3\CMS\Lowlevel\Command
Definition: CleanFlexFormsCommand.php:18
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\removeReferencesToMissingFiles
‪removeReferencesToMissingFiles(array $missingManagedFiles, bool $dryRun, SymfonyStyle $io)
Definition: MissingFilesCommand.php:241
‪TYPO3\CMS\Lowlevel\Command\MissingFilesCommand\updateReferenceIndex
‪updateReferenceIndex(InputInterface $input, SymfonyStyle $io)
Definition: MissingFilesCommand.php:131