diff --git a/phpstan.neon b/phpstan.neon index e441081c3..e981ab187 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,8 +7,7 @@ parameters: symfony: container_xml_path: %rootDir%/../../../var/cache/test/appTestDebugProjectContainer.xml - # https://github.com/phpstan/phpstan/issues/694#issuecomment-350724288 - autoload_files: + bootstrapFiles: - vendor/bin/.phpunit/phpunit-8.3-0/vendor/autoload.php inferPrivatePropertyTypeFromConstructor: true diff --git a/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php new file mode 100644 index 000000000..d81becdc3 --- /dev/null +++ b/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php @@ -0,0 +1,101 @@ +setName('wallabag:clean-downloaded-images') + ->setDescription('Cleans downloaded images which are no more associated to an entry') + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Do not remove images, just dump counters' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $dryRun = (bool) $input->getOption('dry-run'); + + if ($dryRun) { + $io->text('Dry run mode enabled (no images will be removed)'); + } + + $downloadImages = $this->getContainer()->get('wallabag_core.entry.download_images'); + $baseFolder = $downloadImages->getBaseFolder(); + + $io->text('Retrieve existing images'); + + // retrieve _existing_ folders in the image folder + $finder = new Finder(); + $finder + ->directories() + ->ignoreDotFiles(true) + ->depth(2) + ->in($baseFolder); + + $existingPaths = []; + foreach ($finder as $file) { + $existingPaths[] = $file->getFilename(); + } + + $io->text(sprintf(' -> %d images found', \count($existingPaths))); + + $io->text('Retrieve valid folders attached to a user'); + + $entries = $this->getContainer()->get('wallabag_core.entry_repository')->findAllEntriesIdByUserId(); + + // retrieve _valid_ folders from existing entries + $validPaths = []; + foreach ($entries as $entry) { + $path = $downloadImages->getRelativePath($entry['id']); + + if (!file_exists($baseFolder . '/' . $path)) { + continue; + } + + // only store the hash, not the full path + $validPaths[] = explode('/', $path)[2]; + } + + $io->text(sprintf(' -> %d folders found', \count($validPaths))); + + $deletedCount = 0; + + $io->text('Remove images'); + + // check if existing path are valid, if not, remove all images and the folder + foreach ($existingPaths as $existingPath) { + if (!\in_array($existingPath, $validPaths, true)) { + $fullPath = $baseFolder . '/' . $existingPath[0] . '/' . $existingPath[1] . '/' . $existingPath; + $files = glob($fullPath . '/*.*'); + + if (!$dryRun) { + array_map('unlink', $files); + rmdir($fullPath); + } + + $deletedCount += \count($files); + + $io->text(sprintf('Deleted images in %s: %d', $existingPath, \count($files))); + } + } + + $io->success(sprintf('Finished cleaning. %d deleted images', $deletedCount)); + + return 0; + } +} diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 1d98fd1a9..018bc2c6c 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -37,6 +37,11 @@ class DownloadImages $this->setFolder(); } + public function getBaseFolder() + { + return $this->baseFolder; + } + /** * Process the html and extract images URLs from it. * @@ -99,7 +104,7 @@ class DownloadImages * @param string $url Url from where the image were found * @param string $relativePath Relative local path to saved the image * - * @return string Relative url to access the image from the web + * @return string|false Relative url to access the image from the web */ public function processSingleImage($entryId, $imagePath, $url, $relativePath = null) { @@ -210,6 +215,29 @@ class DownloadImages @rmdir($folderPath); } + /** + * Generate the folder where we are going to save images based on the entry url. + * + * @param int $entryId ID of the entry + * @param bool $createFolder Should we create the folder for the given id? + * + * @return string + */ + public function getRelativePath($entryId, $createFolder = true) + { + $hashId = hash('crc32', $entryId); + $relativePath = $hashId[0] . '/' . $hashId[1] . '/' . $hashId; + $folderPath = $this->baseFolder . '/' . $relativePath; + + if (!file_exists($folderPath) && $createFolder) { + mkdir($folderPath, 0777, true); + } + + $this->logger->debug('DownloadImages: Folder used for that Entry id', ['folder' => $folderPath, 'entryId' => $entryId]); + + return $relativePath; + } + /** * Get images urls from the srcset image attribute. * @@ -254,28 +282,6 @@ class DownloadImages } } - /** - * Generate the folder where we are going to save images based on the entry url. - * - * @param int $entryId ID of the entry - * - * @return string - */ - private function getRelativePath($entryId) - { - $hashId = hash('crc32', $entryId); - $relativePath = $hashId[0] . '/' . $hashId[1] . '/' . $hashId; - $folderPath = $this->baseFolder . '/' . $relativePath; - - if (!file_exists($folderPath)) { - mkdir($folderPath, 0777, true); - } - - $this->logger->debug('DownloadImages: Folder used for that Entry id', ['folder' => $folderPath, 'entryId' => $entryId]); - - return $relativePath; - } - /** * Make an $url absolute based on the $base. *