Your IP : 216.73.217.13


Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-lowlevel/Classes/Command/
Upload File :
Current File : /var/www/surf/TYPO3/vendor/typo3/cms-lowlevel/Classes/Command/CleanUpLocalProcessedFilesCommand.php

<?php

declare(strict_types=1);

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace TYPO3\CMS\Lowlevel\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Lowlevel\Service\CleanUpLocalProcessedFilesService;

class CleanUpLocalProcessedFilesCommand extends Command
{
    public function __construct(
        private readonly CleanUpLocalProcessedFilesService $cleanProcessedFilesService
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setDescription(
                'Deletes local processed files from local storage that are no longer referenced and ' .
                'deletes references to processed files that do no longer exist'
            )
            ->setHelp('If you want to get more detailed information, use the --verbose option.')
            ->addOption(
                'dry-run',
                null,
                InputOption::VALUE_NONE,
                'If set, the records and files which would be deleted are displayed.'
            )
            ->addOption(
                'force',
                'f',
                InputOption::VALUE_NONE,
                'Force cleanup. When set the confirmation question will be skipped. When using --no-interaction, --force will be set automatically.'
            );
    }

    /**
     * Executes the command to find processed files from local storages that
     * are no longer referenced and deletes references to files that no longer exist
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $files = $this->cleanProcessedFilesService->getFilesToClean();
        $records = $this->cleanProcessedFilesService->getRecordsToClean();

        if ($output->isVerbose()) {
            foreach ($records as $record) {
                $output->writeln('[RECORD] Would delete ' . $record['identifier'] . ' UID:' . $record['uid']);
            }

            /** @var \SplFileInfo $file */
            foreach ($files as $file) {
                $path = PathUtility::stripPathSitePrefix($file->getRealPath());
                $output->writeln('[FILE] Would delete ' . $path);
            }
        }

        if ($files === [] && $records === []) {
            $output->writeln('<fg=green>✓</> No processed files or processed records found. Nothing to be done');

            return Command::SUCCESS;
        }

        $output->writeln('Found <options=bold,underscore>' . count($files) . ' files</> and <options=bold,underscore>' . (count($records) + count($files)) . ' processed records</>');

        if ($input->getOption('dry-run')) {
            return Command::SUCCESS;
        }

        // Do not ask for confirmation when running the command in EXT:scheduler
        if (!$input->isInteractive()) {
            $input->setOption('force', true);
        }

        if (!$input->getOption('force')) {
            /** @var QuestionHelper $questionHelper */
            $questionHelper = $this->getHelper('question');
            $fileDeleteQuestion = new ConfirmationQuestion(
                'Are you sure you want to delete these processed files and records [default: no] ? ',
                false
            );
            $answerDeleteFiles = $questionHelper->ask($input, $output, $fileDeleteQuestion);
        }

        if (($answerDeleteFiles ?? false) || $input->getOption('force')) {
            [$success, $error] = $this->deleteFile($input, $output, $files);
            // Reload the list auf records to get the files deleted using deleteFile() as well
            $recordsIncludingDeleted = $this->cleanProcessedFilesService->getRecordsToClean();
            $deletedRecordsCount = $this->cleanProcessedFilesService->deleteRecord(array_column($recordsIncludingDeleted, 'uid'));

            $failedRecordCount = count($records) - $deletedRecordsCount;
            $failedRecords = '';
            if ($failedRecordCount > 0) {
                $failedRecords = 'Failed to delete <fg=red>' . $failedRecordCount . '</> records.';
            }

            $failedFiles = '';
            if (count($error) > 0) {
                $failedFiles = 'Failed to delete <fg=red>' . count($error) . '</> files.';
            }

            $output->writeln('');

            if (count($records) > 0) {
                $output->writeln('Deleted <fg=green>' . $deletedRecordsCount . '</> processed records. ' . $failedRecords);
            }

            if (count($files) > 0) {
                $output->writeln('Deleted <fg=green>' . count($success) . '</> processed files. ' . $failedFiles);
            }
        }

        return Command::SUCCESS;
    }

    protected function deleteFile(InputInterface $input, OutputInterface $output, array $files): array
    {
        $isVerbose = $output->isVerbose();
        if (!$isVerbose) {
            $io = new SymfonyStyle($input, $output);
            $progressBar = $io->createProgressBar(count($files));
        }
        $error = [];
        $success = [];

        foreach ($files as $file) {
            $path = PathUtility::stripPathSitePrefix($file->getRealPath());
            if (unlink($file->getRealPath()) === false) {
                $error[] = $file;
                $isVerbose ? $output->writeln('[FILE] Failed to delete ' . $path) : $progressBar->advance();
            } else {
                $success[] = $file;
                $isVerbose ? $output->writeln('[FILE] Successfully deleted ' . $path) : $progressBar->advance();
            }
        }

        return [$success, $error];
    }
}