44
55use Magento \Framework \App \Filesystem \DirectoryList ;
66use Magento \Framework \App \ResourceConnection ;
7+ use Magento \Framework \Console \Cli ;
78use Magento \Framework \Filesystem ;
89use RecursiveDirectoryIterator ;
910use RecursiveIteratorIterator ;
1415
1516class RemoveUnusedMediaCommand extends Command
1617{
18+ private const OPTION_DRY_RUN = 'dry-run ' ;
19+ private const OPTION_INCLUDING_CACHE = 'including-cache ' ;
20+ private const OPTION_FORCE = 'force ' ;
21+ private const COMMAND_NAME_EAV_MEDIA_REMOVE_UNUSED = 'eav:media:remove-unused ' ;
22+
1723 /**
1824 * @var ResourceConnection
1925 */
@@ -28,71 +34,76 @@ public function __construct(Filesystem $filesystem, ResourceConnection $resource
2834 {
2935 parent ::__construct ($ name );
3036 $ this ->resourceConnection = $ resourceConnection ;
31- $ this ->filesystem = $ filesystem ;
32- }
33-
34- /**
35- * Init command
36- */
37- protected function configure ()
38- {
39- $ this
40- ->setName ('eav:media:remove-unused ' )
41- ->setDescription ('Remove unused product images ' )
42- ->addOption ('dry-run ' )
43- ->addOption ('force ' );
37+ $ this ->filesystem = $ filesystem ;
4438 }
4539
4640 public function execute (InputInterface $ input , OutputInterface $ output ): int
4741 {
48- $ fileSize = 0 ;
42+ $ fileSize = 0 ;
4943 $ countFiles = 0 ;
50- $ isDryRun = $ input ->getOption ('dry-run ' );
51- $ isForce = $ input ->getOption ('force ' );
44+ $ isForce = $ input ->getOption (self ::OPTION_FORCE );
45+ $ isDryRun = $ input ->getOption (self ::OPTION_DRY_RUN );
46+ $ deleteCacheAsWell = $ input ->getOption (self ::OPTION_INCLUDING_CACHE );
5247
5348 if (!$ isDryRun && !$ isForce ) {
5449 if (!$ input ->isInteractive ()) {
55- $ output ->writeln ('ERROR: neither --dry-run nor --force options were supplied, and we are not running interactively. ' );
56-
57- return 1 ; // error.
50+ $ output ->writeln (
51+ sprintf (
52+ 'ERROR: neither --%s nor --%s options were supplied, and we are not running interactively. ' , self ::OPTION_DRY_RUN ,
53+ self ::OPTION_FORCE
54+ )
55+ );
56+
57+ return Cli::RETURN_FAILURE ;
5858 }
5959
6060 $ output ->writeln (
61- '<comment>WARNING: this is not a dry run. If you want to do a dry-run, add --dry-run .</comment> '
61+ sprintf ( '<comment>WARNING: this is not a dry run. If you want to do a dry-run, add --%s .</comment> ' , self :: OPTION_DRY_RUN )
6262 );
63+
6364 $ question = new ConfirmationQuestion ('<comment>Are you sure you want to continue? [No]</comment> ' , false );
6465
6566 if (!$ this ->getHelper ('question ' )->ask ($ input , $ output , $ question )) {
66- return 1 ; // error.
67+ return Cli:: RETURN_FAILURE ;
6768 }
6869 }
6970
70- $ imageDir = $ this ->getImageDir ();
71- $ connection = $ this ->resourceConnection ->getConnection ('core_read ' );
71+ $ imageDir = $ this ->getImageDir ();
72+ $ connection = $ this ->resourceConnection ->getConnection ('core_read ' );
7273 $ mediaGalleryTable = $ this ->resourceConnection ->getTableName ('catalog_product_entity_media_gallery ' );
7374
7475 $ directoryIterator = new RecursiveDirectoryIterator ($ imageDir );
7576
7677 $ imagesToKeep = $ connection ->fetchCol ('SELECT value FROM ' . $ mediaGalleryTable );
7778
7879 foreach (new RecursiveIteratorIterator ($ directoryIterator ) as $ file ) {
79- // Cached and placeholder path guard
80- if ($ this -> isInCachePath ( $ file ) || $ this -> isInPlaceholderPath ($ file )) {
80+ // Directory guard
81+ if (is_dir ($ file )) {
8182 continue ;
8283 }
8384
84- // Directory guard
85- if (is_dir ($ file )) {
85+ // Cached guard
86+ if ($ this -> isInCachePath ($ file ) && ! $ deleteCacheAsWell ) {
8687 continue ;
8788 }
8889
89- // Filepath guard
9090 $ filePath = str_replace ($ imageDir , "" , $ file );
91+ // Filepath guard
9192 if (empty ($ filePath )) {
9293 continue ;
9394 }
9495
95- if (in_array ($ filePath , $ imagesToKeep )) {
96+ $ filePathWithoutCacheDir = preg_replace ('#/cache_*/[a-z0-9]+(/[a-z0-9]/[a-z0-9]/.+?)#i ' , '$1 ' , $ filePath );
97+ if (in_array ($ filePathWithoutCacheDir , $ imagesToKeep , true )) {
98+ continue ;
99+ }
100+
101+ // Placeholder guard
102+ if ($ this ->isInPlaceholderPath ($ file )) {
103+ continue ;
104+ }
105+
106+ if (in_array ($ filePath , $ imagesToKeep , true )) {
96107 continue ;
97108 }
98109
@@ -109,20 +120,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
109120
110121 $ this ->printResult ($ output , $ isDryRun , $ countFiles , $ fileSize );
111122
112- return 0 ; // success.
113- }
114-
115- private function printResult (OutputInterface $ output , $ isDryRun , int $ countFiles , int $ filesize ): void
116- {
117- $ actionName = 'Deleted ' ;
118-
119- if ($ isDryRun ) {
120- $ actionName = 'Would delete ' ;
121- }
122-
123- $ fileSizeInMB = number_format ($ filesize / 1024 / 1024 , '2 ' );
124-
125- $ output ->writeln ("<info> {$ actionName } {$ countFiles } unused images. {$ fileSizeInMB } MB</info> " );
123+ return Cli::RETURN_SUCCESS ;
126124 }
127125
128126 private function getImageDir (): string
@@ -141,4 +139,42 @@ private function isInPlaceholderPath(?string $file): bool
141139 {
142140 return strpos ($ file , '/placeholder ' ) !== false ;
143141 }
142+
143+ private function printResult (OutputInterface $ output , $ isDryRun , int $ countFiles , int $ filesize ): void
144+ {
145+ $ actionName = 'Deleted ' ;
146+
147+ if ($ isDryRun ) {
148+ $ actionName = 'Would delete ' ;
149+ }
150+
151+ $ fileSizeInMB = number_format ($ filesize / 1024 / 1024 , '2 ' );
152+
153+ $ output ->writeln ("<info> {$ actionName } {$ countFiles } unused images. {$ fileSizeInMB } MB</info> " );
154+ }
155+
156+ protected function configure (): void
157+ {
158+ $ this ->setName (self ::COMMAND_NAME_EAV_MEDIA_REMOVE_UNUSED );
159+ $ this ->setDescription ('Remove unused product images ' );
160+
161+ $ this ->addOption (
162+ self ::OPTION_INCLUDING_CACHE ,
163+ 'c ' ,
164+ null ,
165+ 'Also clear the ./cache/* entries for the corresponding images '
166+ );
167+ $ this ->addOption (
168+ self ::OPTION_DRY_RUN ,
169+ 'd ' ,
170+ null ,
171+ 'Only process files and output what would be deleted, but don \'t delete anything '
172+ );
173+ $ this ->addOption (
174+ self ::OPTION_FORCE ,
175+ 'f ' ,
176+ null ,
177+ 'Prevent confirmation question and force execution. Option is required for non-interactive execution. '
178+ );
179+ }
144180}
0 commit comments