2 declare(strict_types = 1);
18 use Symfony\Component\Console\Command\Command;
19 use Symfony\Component\Console\Input\InputInterface;
20 use Symfony\Component\Console\Input\InputOption;
21 use Symfony\Component\Console\Output\OutputInterface;
22 use Symfony\Component\Console\Style\SymfonyStyle;
44 ->setDescription(
'Looking up all occurrences of RTEmagic images in the database and check existence of parent and copy files on the file system plus report possibly lost RTE files.')
47 - a perfect integrity of the reference index table (always update the reference index table before using this tool!)
48 - that all RTEmagic image files in the database are registered with the soft reference parser "images"
49 - images found in deleted records are included (means that you might find lost RTEmagic images after flushing deleted records)
51 The assumptions are not requirements by the TYPO3 API but reflects the de facto implementation of most TYPO3 installations.
52 However, many custom fields using an RTE will probably not have the "images" soft reference parser registered and so the index will be incomplete and not listing all RTEmagic image files.
53 The consequence of this limitation is that you should be careful if you wish to delete lost RTEmagic images - they could be referenced from a field not parsed by the "images" soft reference parser!
55 Automatic Repair of Errors:
56 - Will search for double-usages of RTEmagic images and make copies as required.
57 - Lost files can be deleted automatically, but it is recommended to delete them manually if you do not recognize them as used somewhere the system does not know about.
59 Manual repair suggestions:
60 - Missing files: Re-insert missing files or edit record where the reference is found.
62 If the option "--dry-run" is not set, the files are then deleted automatically.
63 Warning: First, make sure those files are not used somewhere TYPO3 does not know about! See the assumptions above.
65 If you want to get more detailed information, use the --verbose option.')
69 InputOption::VALUE_NONE,
70 'If this option is set, the files will not actually be deleted, but just the output which files would be deleted are shown'
75 InputOption::VALUE_NONE,
76 'Setting this option automatically updates the reference index and does not ask on command line. Alternatively, use -n to avoid the interactive mode'
94 $io =
new SymfonyStyle($input,
$output);
95 $io->title($this->getDescription());
97 $dryRun = $input->hasOption(
'dry-run') && $input->getOption(
'dry-run') !=
false ? true :
false;
104 if (count($allRteImagesInUse)) {
105 $allRteImagesWithOriginals = [];
106 $multipleReferenced = [];
111 foreach ($allRteImagesInUse as $fileName => $fileInfo) {
112 $allRteImagesWithOriginals[$fileName]++;
113 $allRteImagesWithOriginals[$fileInfo[
'original']]++;
114 if ($fileInfo[
'count'] > 1 && $fileInfo[
'exists'] && $fileInfo[
'original_exists']) {
115 $multipleReferenced[$fileName] = $fileInfo[
'softReferences'];
118 if (!$fileInfo[
'exists']) {
119 $missingFiles[$fileName] = $fileInfo[
'softReferences'];
121 if (!$fileInfo[
'original_exists']) {
122 $missingFiles[$fileInfo[
'original']] = $fileInfo[
'softReferences'];
128 foreach ($magicFiles as $fileName) {
129 if (!isset($allRteImagesWithOriginals[$fileName])) {
130 $lostFiles[$fileName] = $fileName;
133 ksort($missingFiles);
134 ksort($multipleReferenced);
137 if (!$io->isQuiet()) {
138 $io->note(
'Found ' . count($missingFiles) .
' RTE images that are referenced, but missing.');
139 if ($io->isVerbose()) {
140 $io->listing($missingFiles);
157 if (count($lostFiles)) {
160 $io->success(
'Deleted ' . count($lostFiles) .
' lost files.');
163 $io->success(
'Nothing to do, your system does not have any RTE images.');
179 $io->note(
'Finding RTE images used in TYPO3 requires a clean reference index (sys_refindex)');
180 $updateReferenceIndex =
false;
181 if ($input->hasOption(
'update-refindex') && $input->getOption(
'update-refindex')) {
182 $updateReferenceIndex =
true;
183 } elseif ($input->isInteractive()) {
184 $updateReferenceIndex = $io->confirm(
'Should the reference index be updated right now?',
false);
188 if ($updateReferenceIndex) {
189 $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
190 $referenceIndex->updateIndex(
false, !$io->isQuiet());
192 $io->writeln(
'Reference index is assumed to be up to date, continuing.');
203 $allRteImagesInUse = [];
206 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
207 ->getQueryBuilderForTable(
'sys_refindex');
209 $result = $queryBuilder
211 ->from(
'sys_refindex')
213 $queryBuilder->expr()->eq(
215 $queryBuilder->createNamedParameter(
'_FILE', \PDO::PARAM_STR)
217 $queryBuilder->expr()->like(
219 $queryBuilder->createNamedParameter(
'%/RTEmagic%', \PDO::PARAM_STR)
221 $queryBuilder->expr()->eq(
223 $queryBuilder->createNamedParameter(
'images', \PDO::PARAM_STR)
229 while ($rec = $result->fetch()) {
230 $file = $rec[
'ref_string'];
232 if (strpos($filename,
'RTEmagicC_') === 0) {
234 if (!is_array($allRteImagesInUse[$file])) {
235 $original =
'RTEmagicP_' . preg_replace(
'/\\.[[:alnum:]]+$/',
'', substr($filename, 10));
236 $original = substr($file, 0, -strlen($filename)) . $original;
237 $allRteImagesInUse[$file] = [
239 'original' => $original,
242 'softReferences' => []
245 $allRteImagesInUse[$file][
'count']++;
250 ksort($allRteImagesInUse);
251 return $allRteImagesInUse;
270 foreach ($files as $key => $value) {
273 $filesFound[] = $value;
290 foreach ($lostFiles as $lostFile) {
291 $absoluteFileName = GeneralUtility::getFileAbsFileName($lostFile);
292 if ($io->isVeryVerbose()) {
293 $io->writeln(
'Deleting file "' . $absoluteFileName .
'"');
296 if ($absoluteFileName && @is_file($absoluteFileName)) {
297 unlink($absoluteFileName);
298 if (!$io->isQuiet()) {
299 $io->writeln(
'Permanently deleted file "' . $absoluteFileName .
'".');
302 $io->error(
'File "' . $absoluteFileName .
'" was not found!');
319 $fileProcObj = GeneralUtility::makeInstance(BasicFileUtility::class);
320 foreach ($multipleReferencedImages as $fileName => $fileInfo) {
323 foreach ($fileInfo[
'usedIn'] as $hash => $recordID) {
325 $io->writeln(
'Keeping file ' . $fileName .
' for record ' . $recordID);
336 $pI = pathinfo($fileName);
338 if (!@is_file($copyDestName) && !@is_file($origDestName) && $origDestName === GeneralUtility::getFileAbsFileName($origDestName) && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)) {
345 if (@is_file($copyDestName)) {
346 $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
349 $io->error(
'ReferenceIndex::setReferenceValue() reported "' . $error .
'"');
352 $io->error(
'File "' . $copyDestName .
'" could not be created.');
356 $io->error(
'Could not construct new unique names for file.');
359 $io->error(
'Maybe directory of file was not within "uploads/"?');
375 return $record[
'tablename']
376 .
':' . $record[
'recuid']
377 .
':' . $record[
'field']
378 . ($record[
'flexpointer'] ?
':' . $record[
'flexpointer'] :
'')
379 . ($record[
'softref_key'] ?
':' . $record[
'softref_key'] .
' (Soft Reference) ' :
'')
380 . ($record[
'deleted'] ?
' (DELETED)' :
'');