Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResizeImagesCommand
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 3
156
0.00% covered (danger)
0.00%
0 / 1
 buildOptionParser
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 createImage
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4namespace App\Command;
5
6use App\Utility\SettingsManager;
7use Cake\Command\Command;
8use Cake\Console\Arguments;
9use Cake\Console\ConsoleIo;
10use Cake\Console\ConsoleOptionParser;
11use Cake\Log\LogTrait;
12use Exception;
13use Imagick;
14
15/**
16 * ResizeImages command.
17 *
18 * This command is responsible for resizing images for specified models.
19 */
20class ResizeImagesCommand extends Command
21{
22    use LogTrait;
23
24    /**
25     * Stores the model names and their respective columns to process.
26     *
27     * @var array<string, array<string, string>>
28     */
29    protected array $modelsWithImages = [
30        'Users' => [
31            'file' => 'image',
32            'dir' => 'dir',
33            'size' => 'size',
34            'type' => 'mime',
35        ],
36        'Images' => [
37            'file' => 'image',
38            'dir' => 'dir',
39            'size' => 'size',
40            'type' => 'mime',
41        ],
42        'Articles' => [
43            'file' => 'image',
44            'dir' => 'dir',
45            'size' => 'size',
46            'type' => 'mime',
47        ],
48        'Tags' => [
49            'file' => 'image',
50            'dir' => 'dir',
51            'size' => 'size',
52            'type' => 'mime',
53        ],
54    ];
55
56    /**
57     * Stores the ConsoleIo instance for output operations.
58     *
59     * @var \Cake\Console\ConsoleIo
60     */
61    protected ConsoleIo $io;
62
63    /**
64     * Hook method for defining this command's option parser.
65     *
66     * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options
67     * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
68     * @return \Cake\Console\ConsoleOptionParser The built parser.
69     */
70    public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
71    {
72        $parser = parent::buildOptionParser($parser);
73
74        return $parser;
75    }
76
77    /**
78     * Executes the command to resize images.
79     *
80     * This method iterates through the specified models, retrieves images,
81     * and resizes them according to the configured sizes.
82     *
83     * @param \Cake\Console\Arguments $args The command arguments.
84     * @param \Cake\Console\ConsoleIo $io The console io
85     * @return int The exit code of the command.
86     */
87    public function execute(Arguments $args, ConsoleIo $io): int
88    {
89        //save reference for IO
90        $this->io = $io;
91        //for our models that have images, get their table
92        foreach ($this->modelsWithImages as $model => $columns) {
93            $imagesTable = $this->fetchTable($model);
94            $images = $imagesTable->find('all')
95            ->select(['id', $columns['file'], $columns['dir']])
96            ->where([$columns['file'] . ' IS NOT' => null])
97            ->toArray();
98
99            foreach ($images as $image) {
100                $folder = ROOT . DS . $image->dir;
101                $original = $folder . $image->{$columns['file']};
102                if (file_exists($original)) {
103                    foreach (SettingsManager::read('ImageSizes') as $width) {
104                        $this->createImage($folder, $image->{$columns['file']}, intval($width));
105                    }
106                }
107            }
108        }
109
110        return static::CODE_SUCCESS;
111    }
112
113    /**
114     * Creates a resized image.
115     *
116     * This method resizes the given image to the specified width and saves it
117     * in the appropriate directory.
118     *
119     * @param string $folder The directory where the original image is stored.
120     * @param string $file The name of the image file.
121     * @param int $width The target width for the resized image.
122     * @throws \Exception If the directory cannot be created.
123     * @return void
124     */
125    private function createImage(string $folder, string $file, int $width): void
126    {
127        // Make sure folder for size exists
128        // Ensure the folder path ends with a directory separator
129        $folder = rtrim($folder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
130
131        // Create the full path including the width
132        $sizeFolder = $folder . $width . DIRECTORY_SEPARATOR;
133
134        // Check if the directory exists, if not, create it
135        if (!is_dir($sizeFolder)) {
136            if (!mkdir($sizeFolder, 0755, true)) {
137                throw new Exception("Failed to create directory: $sizeFolder");
138            }
139        }
140
141        try {
142            if (!file_exists($folder . $file)) {
143                $this->log(
144                    sprintf('Original image not found for resizing. Path: %s', $folder . $file),
145                    'error',
146                    ['group_name' => 'image_processing'],
147                );
148
149                return;
150            }
151
152            if (file_exists($sizeFolder . $file)) {
153                $this->log(
154                    sprintf(
155                        'Skipped resizing, image already exists. Path: %s',
156                        $sizeFolder . $file,
157                    ),
158                    'info',
159                    ['group_name' => 'image_processing'],
160                );
161
162                return;
163            }
164
165            $imagick = new Imagick($folder . $file);
166            $imagick->resizeImage($width, 0, Imagick::FILTER_LANCZOS, 1);
167            $imagick->writeImage($sizeFolder . $file);
168            $imagick->clear();
169
170            $this->log(
171                sprintf(
172                    'Successfully resized and saved image. Original: %s, Resized: %s, Width: %dpx',
173                    $folder . $file,
174                    $sizeFolder . $file,
175                    $width,
176                ),
177                'info',
178                ['group_name' => 'image_processing'],
179            );
180        } catch (Exception $e) {
181            $this->log(
182                sprintf(
183                    'Error resizing image. Original: %s, Target Width: %dpx, Error: %s',
184                    $folder . $file,
185                    $width,
186                    $e->getMessage(),
187                ),
188                'error',
189                ['group_name' => 'image_processing'],
190            );
191        }
192    }
193}