@artilingo/artiframe-cli 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/core-stubs/bin/SystemMethod.php +600 -521
  2. package/core-stubs/bin/ViewMethod.php +591 -393
  3. package/core-stubs/docs/de.html +4347 -4390
  4. package/core-stubs/docs/en.html +4347 -4389
  5. package/core-stubs/docs/es.html +4347 -4390
  6. package/core-stubs/docs/fr.html +4347 -4389
  7. package/core-stubs/docs/tr.html +138 -109
  8. package/package.json +1 -1
  9. package/src/App.php +8 -0
  10. package/src/Commands/AddCommand.php +1 -1
  11. package/src/Commands/ListCommand.php +2 -1
  12. package/src/Commands/MakeApiCommand.php +1 -1
  13. package/src/Commands/MakeClassCommand.php +1 -1
  14. package/src/Commands/MakeViewCommand.php +1 -1
  15. package/src/Commands/NewProjectCommand.php +37 -25
  16. package/src/Commands/RemoveCommand.php +21 -10
  17. package/src/Commands/ServeCommand.php +29 -9
  18. package/src/Commands/TableCommand.php +2 -1
  19. package/src/Commands/UpgradeCommand.php +209 -0
  20. package/src/Commands/VersionCommand.php +2 -1
  21. package/src/Lang/de.php +7 -1
  22. package/src/Lang/en.php +6 -0
  23. package/src/Lang/es.php +7 -1
  24. package/src/Lang/fr.php +6 -0
  25. package/src/Lang/tr.php +7 -1
  26. package/src/Services/Safeguard.php +25 -0
  27. package/stubs/service/image.stub +334 -334
  28. package/stubs/service/iyzico.stub +637 -637
  29. package/stubs/service/jwt.stub +312 -312
  30. package/stubs/service/phpmailer.stub +143 -143
  31. package/stubs/service/pusher.stub +232 -232
  32. package/stubs/service/qrcode.stub +169 -169
  33. package/stubs/service/redis.stub +361 -361
  34. package/stubs/service/s3.stub +377 -377
  35. package/stubs/service/sentry.stub +76 -106
  36. package/stubs/service/stripe.stub +526 -526
  37. package/stubs/service/twilio.stub +361 -361
  38. package/core-stubs/app/R2Manager.php +0 -130
@@ -1,334 +1,334 @@
1
- <?php
2
-
3
- namespace Service;
4
-
5
- use Intervention\Image\ImageManager;
6
- use Intervention\Image\Drivers\Gd\Driver as GdDriver;
7
- use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
8
- use Intervention\Image\Interfaces\ImageInterface;
9
-
10
- /**
11
- * ImageProcessor — Wrapper for Intervention Image v3.
12
- *
13
- * Provides simple methods for common image operations such as
14
- * resizing, cropping, watermarking, format conversion, and compression.
15
- *
16
- * Required ENV variables:
17
- * IMAGE_DRIVER — "gd" or "imagick" (default: gd)
18
- * IMAGE_QUALITY — Default JPEG/WebP quality 1-100 (default: 80)
19
- *
20
- * @package Service
21
- */
22
- class ImageProcessor
23
- {
24
- /** @var ImageManager Intervention Image manager instance */
25
- private ImageManager $manager;
26
-
27
- /** @var int Default output quality (1-100) */
28
- private int $quality;
29
-
30
- /**
31
- * Initialize the image processor.
32
- *
33
- * Reads IMAGE_DRIVER and IMAGE_QUALITY from $_ENV.
34
- * Falls back to GD driver and quality 80 when not specified.
35
- */
36
- public function __construct()
37
- {
38
- $driver = strtolower($_ENV['IMAGE_DRIVER'] ?? 'gd');
39
-
40
- $this->manager = match ($driver) {
41
- 'imagick' => new ImageManager(new ImagickDriver()),
42
- default => new ImageManager(new GdDriver()),
43
- };
44
-
45
- $this->quality = (int) ($_ENV['IMAGE_QUALITY'] ?? 80);
46
- }
47
-
48
- // ---------------------------------------------------------------
49
- // Resize
50
- // ---------------------------------------------------------------
51
-
52
- /**
53
- * Resize an image.
54
- *
55
- * When $height is null the aspect ratio is preserved automatically.
56
- * When both dimensions are given the image is resized exactly.
57
- *
58
- * @param string $inputPath Absolute path to source image.
59
- * @param string $outputPath Absolute path for the resized image.
60
- * @param int $width Target width in pixels.
61
- * @param int|null $height Target height in pixels (null = auto).
62
- * @return bool True on success.
63
- */
64
- public function resize(string $inputPath, string $outputPath, int $width, int $height = null): bool
65
- {
66
- try {
67
- $image = $this->manager->read($inputPath);
68
-
69
- if ($height === null) {
70
- // Scale down proportionally
71
- $image->scaleDown(width: $width);
72
- } else {
73
- $image->resize($width, $height);
74
- }
75
-
76
- $image->save($outputPath, quality: $this->quality);
77
-
78
- return true;
79
- } catch (\Throwable $e) {
80
- error_log('[ImageProcessor::resize] ' . $e->getMessage());
81
- return false;
82
- }
83
- }
84
-
85
- // ---------------------------------------------------------------
86
- // Crop
87
- // ---------------------------------------------------------------
88
-
89
- /**
90
- * Crop a rectangular region from an image.
91
- *
92
- * @param string $inputPath Absolute path to source image.
93
- * @param string $outputPath Absolute path for the cropped image.
94
- * @param int $width Crop width in pixels.
95
- * @param int $height Crop height in pixels.
96
- * @param int $x X-offset from the top-left corner.
97
- * @param int $y Y-offset from the top-left corner.
98
- * @return bool True on success.
99
- */
100
- public function crop(string $inputPath, string $outputPath, int $width, int $height, int $x = 0, int $y = 0): bool
101
- {
102
- try {
103
- $image = $this->manager->read($inputPath);
104
- $image->crop($width, $height, $x, $y);
105
- $image->save($outputPath, quality: $this->quality);
106
-
107
- return true;
108
- } catch (\Throwable $e) {
109
- error_log('[ImageProcessor::crop] ' . $e->getMessage());
110
- return false;
111
- }
112
- }
113
-
114
- // ---------------------------------------------------------------
115
- // Thumbnail
116
- // ---------------------------------------------------------------
117
-
118
- /**
119
- * Create a square thumbnail by cover-cropping to the given size.
120
- *
121
- * The image is resized so the shortest side matches $size,
122
- * then center-cropped to produce a perfect square.
123
- *
124
- * @param string $inputPath Absolute path to source image.
125
- * @param string $outputPath Absolute path for the thumbnail.
126
- * @param int $size Square dimension in pixels (default 200).
127
- * @return bool True on success.
128
- */
129
- public function thumbnail(string $inputPath, string $outputPath, int $size = 200): bool
130
- {
131
- try {
132
- $image = $this->manager->read($inputPath);
133
- $image->cover($size, $size);
134
- $image->save($outputPath, quality: $this->quality);
135
-
136
- return true;
137
- } catch (\Throwable $e) {
138
- error_log('[ImageProcessor::thumbnail] ' . $e->getMessage());
139
- return false;
140
- }
141
- }
142
-
143
- // ---------------------------------------------------------------
144
- // Watermark
145
- // ---------------------------------------------------------------
146
-
147
- /**
148
- * Overlay a watermark image onto the source image.
149
- *
150
- * @param string $inputPath Absolute path to source image.
151
- * @param string $outputPath Absolute path for the watermarked image.
152
- * @param string $watermarkPath Absolute path to the watermark image (PNG recommended).
153
- * @param string $position Placement position: top-left, top, top-right,
154
- * left, center, right, bottom-left, bottom, bottom-right.
155
- * @param int $opacity Watermark opacity 0-100 (default 50).
156
- * @return bool True on success.
157
- */
158
- public function watermark(
159
- string $inputPath,
160
- string $outputPath,
161
- string $watermarkPath,
162
- string $position = 'bottom-right',
163
- int $opacity = 50
164
- ): bool {
165
- try {
166
- $image = $this->manager->read($inputPath);
167
- $watermark = $this->manager->read($watermarkPath);
168
-
169
- // Map human-readable position to Intervention's place() arguments
170
- $positionMap = [
171
- 'top-left' => 'top-left',
172
- 'top' => 'top',
173
- 'top-right' => 'top-right',
174
- 'left' => 'left',
175
- 'center' => 'center',
176
- 'right' => 'right',
177
- 'bottom-left' => 'bottom-left',
178
- 'bottom' => 'bottom',
179
- 'bottom-right' => 'bottom-right',
180
- ];
181
-
182
- $pos = $positionMap[$position] ?? 'bottom-right';
183
-
184
- $image->place($watermark, $pos, opacity: $opacity);
185
- $image->save($outputPath, quality: $this->quality);
186
-
187
- return true;
188
- } catch (\Throwable $e) {
189
- error_log('[ImageProcessor::watermark] ' . $e->getMessage());
190
- return false;
191
- }
192
- }
193
-
194
- // ---------------------------------------------------------------
195
- // Format Conversion — WebP
196
- // ---------------------------------------------------------------
197
-
198
- /**
199
- * Convert an image to WebP format.
200
- *
201
- * When $outputPath is null the file is saved alongside the original
202
- * with a .webp extension.
203
- *
204
- * @param string $inputPath Absolute path to source image.
205
- * @param string|null $outputPath Destination path (null = auto).
206
- * @param int|null $quality WebP quality 1-100 (null = env default).
207
- * @return bool True on success.
208
- */
209
- public function toWebp(string $inputPath, string $outputPath = null, int $quality = null): bool
210
- {
211
- try {
212
- $quality = $quality ?? $this->quality;
213
- $outputPath = $outputPath ?? preg_replace('/\.[^.]+$/', '.webp', $inputPath);
214
-
215
- $image = $this->manager->read($inputPath);
216
- $image->toWebp($quality)->save($outputPath);
217
-
218
- return true;
219
- } catch (\Throwable $e) {
220
- error_log('[ImageProcessor::toWebp] ' . $e->getMessage());
221
- return false;
222
- }
223
- }
224
-
225
- // ---------------------------------------------------------------
226
- // Compress
227
- // ---------------------------------------------------------------
228
-
229
- /**
230
- * Re-compress an image to reduce file size.
231
- *
232
- * When $outputPath is null the original file is overwritten.
233
- *
234
- * @param string $inputPath Absolute path to source image.
235
- * @param string|null $outputPath Destination path (null = overwrite original).
236
- * @param int|null $quality Quality 1-100 (null = env default).
237
- * @return bool True on success.
238
- */
239
- public function compress(string $inputPath, string $outputPath = null, int $quality = null): bool
240
- {
241
- try {
242
- $quality = $quality ?? $this->quality;
243
- $outputPath = $outputPath ?? $inputPath;
244
-
245
- $image = $this->manager->read($inputPath);
246
- $image->save($outputPath, quality: $quality);
247
-
248
- return true;
249
- } catch (\Throwable $e) {
250
- error_log('[ImageProcessor::compress] ' . $e->getMessage());
251
- return false;
252
- }
253
- }
254
-
255
- // ---------------------------------------------------------------
256
- // Info
257
- // ---------------------------------------------------------------
258
-
259
- /**
260
- * Retrieve basic information about an image file.
261
- *
262
- * @param string $path Absolute path to the image.
263
- * @return array{width: int, height: int, mime: string, size: int}
264
- * Associative array with image metadata.
265
- * Returns an array with an 'error' key on failure.
266
- */
267
- public function info(string $path): array
268
- {
269
- try {
270
- $image = $this->manager->read($path);
271
-
272
- return [
273
- 'width' => $image->width(),
274
- 'height' => $image->height(),
275
- 'mime' => mime_content_type($path),
276
- 'size' => filesize($path),
277
- ];
278
- } catch (\Throwable $e) {
279
- error_log('[ImageProcessor::info] ' . $e->getMessage());
280
- return ['error' => $e->getMessage()];
281
- }
282
- }
283
-
284
- // ---------------------------------------------------------------
285
- // Rotate
286
- // ---------------------------------------------------------------
287
-
288
- /**
289
- * Rotate an image by a given angle (counter-clockwise).
290
- *
291
- * @param string $inputPath Absolute path to source image.
292
- * @param string $outputPath Absolute path for the rotated image.
293
- * @param float $angle Rotation angle in degrees.
294
- * @return bool True on success.
295
- */
296
- public function rotate(string $inputPath, string $outputPath, float $angle): bool
297
- {
298
- try {
299
- $image = $this->manager->read($inputPath);
300
- $image->rotate($angle);
301
- $image->save($outputPath, quality: $this->quality);
302
-
303
- return true;
304
- } catch (\Throwable $e) {
305
- error_log('[ImageProcessor::rotate] ' . $e->getMessage());
306
- return false;
307
- }
308
- }
309
-
310
- // ---------------------------------------------------------------
311
- // Greyscale
312
- // ---------------------------------------------------------------
313
-
314
- /**
315
- * Convert an image to greyscale.
316
- *
317
- * @param string $inputPath Absolute path to source image.
318
- * @param string $outputPath Absolute path for the greyscale image.
319
- * @return bool True on success.
320
- */
321
- public function greyscale(string $inputPath, string $outputPath): bool
322
- {
323
- try {
324
- $image = $this->manager->read($inputPath);
325
- $image->greyscale();
326
- $image->save($outputPath, quality: $this->quality);
327
-
328
- return true;
329
- } catch (\Throwable $e) {
330
- error_log('[ImageProcessor::greyscale] ' . $e->getMessage());
331
- return false;
332
- }
333
- }
334
- }
1
+ <?php
2
+
3
+ namespace Src\Service;
4
+
5
+ use Intervention\Image\ImageManager;
6
+ use Intervention\Image\Drivers\Gd\Driver as GdDriver;
7
+ use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
8
+ use Intervention\Image\Interfaces\ImageInterface;
9
+
10
+ /**
11
+ * ImageProcessor — Wrapper for Intervention Image v3.
12
+ *
13
+ * Provides simple methods for common image operations such as
14
+ * resizing, cropping, watermarking, format conversion, and compression.
15
+ *
16
+ * Required ENV variables:
17
+ * IMAGE_DRIVER — "gd" or "imagick" (default: gd)
18
+ * IMAGE_QUALITY — Default JPEG/WebP quality 1-100 (default: 80)
19
+ *
20
+ * @package Service
21
+ */
22
+ class ImageProcessor
23
+ {
24
+ /** @var ImageManager Intervention Image manager instance */
25
+ private ImageManager $manager;
26
+
27
+ /** @var int Default output quality (1-100) */
28
+ private int $quality;
29
+
30
+ /**
31
+ * Initialize the image processor.
32
+ *
33
+ * Reads IMAGE_DRIVER and IMAGE_QUALITY from $_ENV.
34
+ * Falls back to GD driver and quality 80 when not specified.
35
+ */
36
+ public function __construct()
37
+ {
38
+ $driver = strtolower($_ENV['IMAGE_DRIVER'] ?? 'gd');
39
+
40
+ $this->manager = match ($driver) {
41
+ 'imagick' => new ImageManager(new ImagickDriver()),
42
+ default => new ImageManager(new GdDriver()),
43
+ };
44
+
45
+ $this->quality = (int) ($_ENV['IMAGE_QUALITY'] ?? 80);
46
+ }
47
+
48
+ // ---------------------------------------------------------------
49
+ // Resize
50
+ // ---------------------------------------------------------------
51
+
52
+ /**
53
+ * Resize an image.
54
+ *
55
+ * When $height is null the aspect ratio is preserved automatically.
56
+ * When both dimensions are given the image is resized exactly.
57
+ *
58
+ * @param string $inputPath Absolute path to source image.
59
+ * @param string $outputPath Absolute path for the resized image.
60
+ * @param int $width Target width in pixels.
61
+ * @param int|null $height Target height in pixels (null = auto).
62
+ * @return bool True on success.
63
+ */
64
+ public function resize(string $inputPath, string $outputPath, int $width, int $height = null): bool
65
+ {
66
+ try {
67
+ $image = $this->manager->read($inputPath);
68
+
69
+ if ($height === null) {
70
+ // Scale down proportionally
71
+ $image->scaleDown(width: $width);
72
+ } else {
73
+ $image->resize($width, $height);
74
+ }
75
+
76
+ $image->save($outputPath, quality: $this->quality);
77
+
78
+ return true;
79
+ } catch (\Throwable $e) {
80
+ error_log('[ImageProcessor::resize] ' . $e->getMessage());
81
+ return false;
82
+ }
83
+ }
84
+
85
+ // ---------------------------------------------------------------
86
+ // Crop
87
+ // ---------------------------------------------------------------
88
+
89
+ /**
90
+ * Crop a rectangular region from an image.
91
+ *
92
+ * @param string $inputPath Absolute path to source image.
93
+ * @param string $outputPath Absolute path for the cropped image.
94
+ * @param int $width Crop width in pixels.
95
+ * @param int $height Crop height in pixels.
96
+ * @param int $x X-offset from the top-left corner.
97
+ * @param int $y Y-offset from the top-left corner.
98
+ * @return bool True on success.
99
+ */
100
+ public function crop(string $inputPath, string $outputPath, int $width, int $height, int $x = 0, int $y = 0): bool
101
+ {
102
+ try {
103
+ $image = $this->manager->read($inputPath);
104
+ $image->crop($width, $height, $x, $y);
105
+ $image->save($outputPath, quality: $this->quality);
106
+
107
+ return true;
108
+ } catch (\Throwable $e) {
109
+ error_log('[ImageProcessor::crop] ' . $e->getMessage());
110
+ return false;
111
+ }
112
+ }
113
+
114
+ // ---------------------------------------------------------------
115
+ // Thumbnail
116
+ // ---------------------------------------------------------------
117
+
118
+ /**
119
+ * Create a square thumbnail by cover-cropping to the given size.
120
+ *
121
+ * The image is resized so the shortest side matches $size,
122
+ * then center-cropped to produce a perfect square.
123
+ *
124
+ * @param string $inputPath Absolute path to source image.
125
+ * @param string $outputPath Absolute path for the thumbnail.
126
+ * @param int $size Square dimension in pixels (default 200).
127
+ * @return bool True on success.
128
+ */
129
+ public function thumbnail(string $inputPath, string $outputPath, int $size = 200): bool
130
+ {
131
+ try {
132
+ $image = $this->manager->read($inputPath);
133
+ $image->cover($size, $size);
134
+ $image->save($outputPath, quality: $this->quality);
135
+
136
+ return true;
137
+ } catch (\Throwable $e) {
138
+ error_log('[ImageProcessor::thumbnail] ' . $e->getMessage());
139
+ return false;
140
+ }
141
+ }
142
+
143
+ // ---------------------------------------------------------------
144
+ // Watermark
145
+ // ---------------------------------------------------------------
146
+
147
+ /**
148
+ * Overlay a watermark image onto the source image.
149
+ *
150
+ * @param string $inputPath Absolute path to source image.
151
+ * @param string $outputPath Absolute path for the watermarked image.
152
+ * @param string $watermarkPath Absolute path to the watermark image (PNG recommended).
153
+ * @param string $position Placement position: top-left, top, top-right,
154
+ * left, center, right, bottom-left, bottom, bottom-right.
155
+ * @param int $opacity Watermark opacity 0-100 (default 50).
156
+ * @return bool True on success.
157
+ */
158
+ public function watermark(
159
+ string $inputPath,
160
+ string $outputPath,
161
+ string $watermarkPath,
162
+ string $position = 'bottom-right',
163
+ int $opacity = 50
164
+ ): bool {
165
+ try {
166
+ $image = $this->manager->read($inputPath);
167
+ $watermark = $this->manager->read($watermarkPath);
168
+
169
+ // Map human-readable position to Intervention's place() arguments
170
+ $positionMap = [
171
+ 'top-left' => 'top-left',
172
+ 'top' => 'top',
173
+ 'top-right' => 'top-right',
174
+ 'left' => 'left',
175
+ 'center' => 'center',
176
+ 'right' => 'right',
177
+ 'bottom-left' => 'bottom-left',
178
+ 'bottom' => 'bottom',
179
+ 'bottom-right' => 'bottom-right',
180
+ ];
181
+
182
+ $pos = $positionMap[$position] ?? 'bottom-right';
183
+
184
+ $image->place($watermark, $pos, opacity: $opacity);
185
+ $image->save($outputPath, quality: $this->quality);
186
+
187
+ return true;
188
+ } catch (\Throwable $e) {
189
+ error_log('[ImageProcessor::watermark] ' . $e->getMessage());
190
+ return false;
191
+ }
192
+ }
193
+
194
+ // ---------------------------------------------------------------
195
+ // Format Conversion — WebP
196
+ // ---------------------------------------------------------------
197
+
198
+ /**
199
+ * Convert an image to WebP format.
200
+ *
201
+ * When $outputPath is null the file is saved alongside the original
202
+ * with a .webp extension.
203
+ *
204
+ * @param string $inputPath Absolute path to source image.
205
+ * @param string|null $outputPath Destination path (null = auto).
206
+ * @param int|null $quality WebP quality 1-100 (null = env default).
207
+ * @return bool True on success.
208
+ */
209
+ public function toWebp(string $inputPath, string $outputPath = null, int $quality = null): bool
210
+ {
211
+ try {
212
+ $quality = $quality ?? $this->quality;
213
+ $outputPath = $outputPath ?? preg_replace('/\.[^.]+$/', '.webp', $inputPath);
214
+
215
+ $image = $this->manager->read($inputPath);
216
+ $image->toWebp($quality)->save($outputPath);
217
+
218
+ return true;
219
+ } catch (\Throwable $e) {
220
+ error_log('[ImageProcessor::toWebp] ' . $e->getMessage());
221
+ return false;
222
+ }
223
+ }
224
+
225
+ // ---------------------------------------------------------------
226
+ // Compress
227
+ // ---------------------------------------------------------------
228
+
229
+ /**
230
+ * Re-compress an image to reduce file size.
231
+ *
232
+ * When $outputPath is null the original file is overwritten.
233
+ *
234
+ * @param string $inputPath Absolute path to source image.
235
+ * @param string|null $outputPath Destination path (null = overwrite original).
236
+ * @param int|null $quality Quality 1-100 (null = env default).
237
+ * @return bool True on success.
238
+ */
239
+ public function compress(string $inputPath, string $outputPath = null, int $quality = null): bool
240
+ {
241
+ try {
242
+ $quality = $quality ?? $this->quality;
243
+ $outputPath = $outputPath ?? $inputPath;
244
+
245
+ $image = $this->manager->read($inputPath);
246
+ $image->save($outputPath, quality: $quality);
247
+
248
+ return true;
249
+ } catch (\Throwable $e) {
250
+ error_log('[ImageProcessor::compress] ' . $e->getMessage());
251
+ return false;
252
+ }
253
+ }
254
+
255
+ // ---------------------------------------------------------------
256
+ // Info
257
+ // ---------------------------------------------------------------
258
+
259
+ /**
260
+ * Retrieve basic information about an image file.
261
+ *
262
+ * @param string $path Absolute path to the image.
263
+ * @return array{width: int, height: int, mime: string, size: int}
264
+ * Associative array with image metadata.
265
+ * Returns an array with an 'error' key on failure.
266
+ */
267
+ public function info(string $path): array
268
+ {
269
+ try {
270
+ $image = $this->manager->read($path);
271
+
272
+ return [
273
+ 'width' => $image->width(),
274
+ 'height' => $image->height(),
275
+ 'mime' => mime_content_type($path),
276
+ 'size' => filesize($path),
277
+ ];
278
+ } catch (\Throwable $e) {
279
+ error_log('[ImageProcessor::info] ' . $e->getMessage());
280
+ return ['error' => $e->getMessage()];
281
+ }
282
+ }
283
+
284
+ // ---------------------------------------------------------------
285
+ // Rotate
286
+ // ---------------------------------------------------------------
287
+
288
+ /**
289
+ * Rotate an image by a given angle (counter-clockwise).
290
+ *
291
+ * @param string $inputPath Absolute path to source image.
292
+ * @param string $outputPath Absolute path for the rotated image.
293
+ * @param float $angle Rotation angle in degrees.
294
+ * @return bool True on success.
295
+ */
296
+ public function rotate(string $inputPath, string $outputPath, float $angle): bool
297
+ {
298
+ try {
299
+ $image = $this->manager->read($inputPath);
300
+ $image->rotate($angle);
301
+ $image->save($outputPath, quality: $this->quality);
302
+
303
+ return true;
304
+ } catch (\Throwable $e) {
305
+ error_log('[ImageProcessor::rotate] ' . $e->getMessage());
306
+ return false;
307
+ }
308
+ }
309
+
310
+ // ---------------------------------------------------------------
311
+ // Greyscale
312
+ // ---------------------------------------------------------------
313
+
314
+ /**
315
+ * Convert an image to greyscale.
316
+ *
317
+ * @param string $inputPath Absolute path to source image.
318
+ * @param string $outputPath Absolute path for the greyscale image.
319
+ * @return bool True on success.
320
+ */
321
+ public function greyscale(string $inputPath, string $outputPath): bool
322
+ {
323
+ try {
324
+ $image = $this->manager->read($inputPath);
325
+ $image->greyscale();
326
+ $image->save($outputPath, quality: $this->quality);
327
+
328
+ return true;
329
+ } catch (\Throwable $e) {
330
+ error_log('[ImageProcessor::greyscale] ' . $e->getMessage());
331
+ return false;
332
+ }
333
+ }
334
+ }