These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Robo\Task\Assets; |
||
4 | |||
5 | use Robo\Result; |
||
6 | use Robo\Exception\TaskException; |
||
7 | use Robo\Task\BaseTask; |
||
8 | use Robo\Task\Base\Exec; |
||
9 | use Symfony\Component\Finder\Finder; |
||
10 | use Symfony\Component\Filesystem\Filesystem as sfFilesystem; |
||
11 | |||
12 | /** |
||
13 | * Minifies images. When the required minifier is not installed on the system |
||
14 | * the task will try to download it from the [imagemin](https://github.com/imagemin) repository. |
||
15 | * |
||
16 | * When the task is run without any specified minifier it will compress the images |
||
17 | * based on the extension. |
||
18 | * |
||
19 | * ```php |
||
20 | * $this->taskImageMinify('assets/images/*') |
||
21 | * ->to('dist/images/') |
||
22 | * ->run(); |
||
23 | * ``` |
||
24 | * |
||
25 | * This will use the following minifiers: |
||
26 | * |
||
27 | * - PNG: optipng |
||
28 | * - GIF: gifsicle |
||
29 | * - JPG, JPEG: jpegtran |
||
30 | * - SVG: svgo |
||
31 | * |
||
32 | * When the minifier is specified the task will use that for all the input files. In that case |
||
33 | * it is useful to filter the files with the extension: |
||
34 | * |
||
35 | * ```php |
||
36 | * $this->taskImageMinify('assets/images/*.png') |
||
37 | * ->to('dist/images/') |
||
38 | * ->minifier('pngcrush'); |
||
39 | * ->run(); |
||
40 | * ``` |
||
41 | * |
||
42 | * The task supports the following minifiers: |
||
43 | * |
||
44 | * - optipng |
||
45 | * - pngquant |
||
46 | * - advpng |
||
47 | * - pngout |
||
48 | * - zopflipng |
||
49 | * - pngcrush |
||
50 | * - gifsicle |
||
51 | * - jpegoptim |
||
52 | * - jpeg-recompress |
||
53 | * - jpegtran |
||
54 | * - svgo (only minification, no downloading) |
||
55 | * |
||
56 | * You can also specifiy extra options for the minifiers: |
||
57 | * |
||
58 | * ```php |
||
59 | * $this->taskImageMinify('assets/images/*.jpg') |
||
60 | * ->to('dist/images/') |
||
61 | * ->minifier('jpegtran', ['-progressive' => null, '-copy' => 'none']) |
||
62 | * ->run(); |
||
63 | * ``` |
||
64 | * |
||
65 | * This will execute as: |
||
66 | * `jpegtran -copy none -progressive -optimize -outfile "dist/images/test.jpg" "/var/www/test/assets/images/test.jpg"` |
||
67 | */ |
||
68 | class ImageMinify extends BaseTask |
||
69 | { |
||
70 | /** |
||
71 | * Destination directory for the minified images. |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | protected $to; |
||
76 | |||
77 | /** |
||
78 | * Array of the source files. |
||
79 | * |
||
80 | * @var array |
||
81 | */ |
||
82 | protected $dirs = []; |
||
83 | |||
84 | /** |
||
85 | * Symfony 2 filesystem. |
||
86 | * |
||
87 | * @var sfFilesystem |
||
88 | */ |
||
89 | protected $fs; |
||
90 | |||
91 | /** |
||
92 | * Target directory for the downloaded binary executables. |
||
93 | * |
||
94 | * @var string |
||
95 | */ |
||
96 | protected $executableTargetDir; |
||
97 | |||
98 | /** |
||
99 | * Array for the downloaded binary executables. |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | protected $executablePaths = []; |
||
104 | |||
105 | /** |
||
106 | * Array for the individual results of all the files. |
||
107 | * |
||
108 | * @var array |
||
109 | */ |
||
110 | protected $results = []; |
||
111 | |||
112 | /** |
||
113 | * Default minifier to use. |
||
114 | * |
||
115 | * @var string |
||
116 | */ |
||
117 | protected $minifier; |
||
118 | |||
119 | /** |
||
120 | * Array for minifier options. |
||
121 | * |
||
122 | * @var array |
||
123 | */ |
||
124 | protected $minifierOptions = []; |
||
125 | |||
126 | /** |
||
127 | * Supported minifiers. |
||
128 | * |
||
129 | * @var array |
||
130 | */ |
||
131 | protected $minifiers = [ |
||
132 | // Default 4 |
||
133 | 'optipng', |
||
134 | 'gifsicle', |
||
135 | 'jpegtran', |
||
136 | 'svgo', |
||
137 | // PNG |
||
138 | 'pngquant', |
||
139 | 'advpng', |
||
140 | 'pngout', |
||
141 | 'zopflipng', |
||
142 | 'pngcrush', |
||
143 | // JPG |
||
144 | 'jpegoptim', |
||
145 | 'jpeg-recompress', |
||
146 | ]; |
||
147 | |||
148 | /** |
||
149 | * Binary repositories of Imagemin. |
||
150 | * |
||
151 | * @link https://github.com/imagemin |
||
152 | * |
||
153 | * @var array |
||
154 | */ |
||
155 | protected $imageminRepos = [ |
||
156 | // PNG |
||
157 | 'optipng' => 'https://github.com/imagemin/optipng-bin', |
||
158 | 'pngquant' => 'https://github.com/imagemin/pngquant-bin', |
||
159 | 'advpng' => 'https://github.com/imagemin/advpng-bin', |
||
160 | 'pngout' => 'https://github.com/imagemin/pngout-bin', |
||
161 | 'zopflipng' => 'https://github.com/imagemin/zopflipng-bin', |
||
162 | 'pngcrush' => 'https://github.com/imagemin/pngcrush-bin', |
||
163 | // Gif |
||
164 | 'gifsicle' => 'https://github.com/imagemin/gifsicle-bin', |
||
165 | // JPG |
||
166 | 'jpegtran' => 'https://github.com/imagemin/jpegtran-bin', |
||
167 | 'jpegoptim' => 'https://github.com/imagemin/jpegoptim-bin', |
||
168 | 'cjpeg' => 'https://github.com/imagemin/mozjpeg-bin', // note: we do not support this minifier because it creates JPG from non-JPG files |
||
169 | 'jpeg-recompress' => 'https://github.com/imagemin/jpeg-recompress-bin', |
||
170 | // WebP |
||
171 | 'cwebp' => 'https://github.com/imagemin/cwebp-bin', // note: we do not support this minifier because it creates WebP from non-WebP files |
||
172 | ]; |
||
173 | |||
174 | public function __construct($dirs) |
||
175 | { |
||
176 | is_array($dirs) |
||
177 | ? $this->dirs = $dirs |
||
178 | : $this->dirs[] = $dirs; |
||
179 | |||
180 | $this->fs = new sfFilesystem(); |
||
181 | |||
182 | // guess the best path for the executables based on __DIR__ |
||
183 | if (($pos = strpos(__DIR__, 'consolidation/robo')) !== false) { |
||
184 | // the executables should be stored in vendor/bin |
||
185 | $this->executableTargetDir = substr(__DIR__, 0, $pos).'bin'; |
||
186 | } |
||
187 | |||
188 | // check if the executables are already available |
||
189 | foreach ($this->imageminRepos as $exec => $url) { |
||
190 | $path = $this->executableTargetDir.'/'.$exec; |
||
191 | // if this is Windows add a .exe extension |
||
192 | if (substr($this->getOS(), 0, 3) == 'win') { |
||
193 | $path .= '.exe'; |
||
194 | } |
||
195 | if (is_file($path)) { |
||
196 | $this->executablePaths[$exec] = $path; |
||
197 | } |
||
198 | } |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * {@inheritdoc} |
||
203 | */ |
||
204 | public function run() |
||
205 | { |
||
206 | // find the files |
||
207 | $files = $this->findFiles($this->dirs); |
||
208 | |||
209 | // minify the files |
||
210 | $result = $this->minify($files); |
||
0 ignored issues
–
show
|
|||
211 | // check if there was an error |
||
212 | if ($result instanceof Result) { |
||
213 | return $result; |
||
214 | } |
||
215 | |||
216 | $amount = (count($files) == 1 ? 'image' : 'images'); |
||
217 | $message = "Minified {filecount} out of {filetotal} $amount into {destination}"; |
||
218 | $context = ['filecount' => count($this->results['success']), 'filetotal' => count($files), 'destination' => $this->to]; |
||
219 | |||
220 | if (count($this->results['success']) == count($files)) { |
||
221 | $this->printTaskSuccess($message, $context); |
||
222 | |||
223 | return Result::success($this, $message, $context); |
||
224 | } else { |
||
225 | return Result::error($this, $message, $context); |
||
226 | } |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Sets the target directory where the files will be copied to. |
||
231 | * |
||
232 | * @param string $target |
||
233 | * |
||
234 | * @return $this |
||
235 | */ |
||
236 | public function to($target) |
||
237 | { |
||
238 | $this->to = rtrim($target, '/'); |
||
239 | |||
240 | return $this; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Sets the minifier. |
||
245 | * |
||
246 | * @param string $minifier |
||
247 | * @param array $options |
||
248 | * |
||
249 | * @return $this |
||
250 | */ |
||
251 | public function minifier($minifier, array $options = []) |
||
252 | { |
||
253 | $this->minifier = $minifier; |
||
254 | $this->minifierOptions = array_merge($this->minifierOptions, $options); |
||
255 | |||
256 | return $this; |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * @param array $dirs |
||
261 | * |
||
262 | * @return array|\Robo\Result |
||
263 | * |
||
264 | * @throws \Robo\Exception\TaskException |
||
265 | */ |
||
266 | View Code Duplication | protected function findFiles($dirs) |
|
267 | { |
||
268 | $files = array(); |
||
269 | |||
270 | // find the files |
||
271 | foreach ($dirs as $k => $v) { |
||
272 | // reset finder |
||
273 | $finder = new Finder(); |
||
274 | |||
275 | $dir = $k; |
||
276 | $to = $v; |
||
277 | // check if target was given with the to() method instead of key/value pairs |
||
278 | if (is_int($k)) { |
||
279 | $dir = $v; |
||
280 | if (isset($this->to)) { |
||
281 | $to = $this->to; |
||
282 | } else { |
||
283 | throw new TaskException($this, 'target directory is not defined'); |
||
284 | } |
||
285 | } |
||
286 | |||
287 | try { |
||
288 | $finder->files()->in($dir); |
||
289 | } catch (\InvalidArgumentException $e) { |
||
290 | // if finder cannot handle it, try with in()->name() |
||
291 | if (strpos($dir, '/') === false) { |
||
292 | $dir = './'.$dir; |
||
293 | } |
||
294 | $parts = explode('/', $dir); |
||
295 | $new_dir = implode('/', array_slice($parts, 0, -1)); |
||
296 | try { |
||
297 | $finder->files()->in($new_dir)->name(array_pop($parts)); |
||
298 | } catch (\InvalidArgumentException $e) { |
||
299 | return Result::fromException($this, $e); |
||
300 | } |
||
301 | } |
||
302 | |||
303 | foreach ($finder as $file) { |
||
304 | // store the absolute path as key and target as value in the files array |
||
305 | $files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to); |
||
306 | } |
||
307 | $fileNoun = count($finder) == 1 ? ' file' : ' files'; |
||
308 | $this->printTaskInfo("Found {filecount} $fileNoun in {dir}", ['filecount' => count($finder), 'dir' => $dir]); |
||
309 | } |
||
310 | |||
311 | return $files; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * @param string $file |
||
316 | * @param string $to |
||
317 | * |
||
318 | * @return string |
||
319 | */ |
||
320 | protected function getTarget($file, $to) |
||
321 | { |
||
322 | $target = $to.'/'.basename($file); |
||
323 | |||
324 | return $target; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * @param array $files |
||
329 | * |
||
330 | * @return \Robo\Result |
||
331 | */ |
||
332 | protected function minify($files) |
||
333 | { |
||
334 | // store the individual results into the results array |
||
335 | $this->results = [ |
||
336 | 'success' => [], |
||
337 | 'error' => [], |
||
338 | ]; |
||
339 | |||
340 | // loop through the files |
||
341 | foreach ($files as $from => $to) { |
||
342 | if (!isset($this->minifier)) { |
||
343 | // check filetype based on the extension |
||
344 | $extension = strtolower(pathinfo($from, PATHINFO_EXTENSION)); |
||
345 | |||
346 | // set the default minifiers based on the extension |
||
347 | switch ($extension) { |
||
348 | case 'png': |
||
349 | $minifier = 'optipng'; |
||
350 | break; |
||
351 | case 'jpg': |
||
352 | case 'jpeg': |
||
353 | $minifier = 'jpegtran'; |
||
354 | break; |
||
355 | case 'gif': |
||
356 | $minifier = 'gifsicle'; |
||
357 | break; |
||
358 | case 'svg': |
||
359 | $minifier = 'svgo'; |
||
360 | break; |
||
361 | } |
||
362 | } else { |
||
363 | View Code Duplication | if (!in_array($this->minifier, $this->minifiers, true) |
|
364 | && !is_callable(strtr($this->minifier, '-', '_')) |
||
365 | ) { |
||
366 | $message = sprintf('Invalid minifier %s!', $this->minifier); |
||
367 | |||
368 | return Result::error($this, $message); |
||
369 | } |
||
370 | $minifier = $this->minifier; |
||
371 | } |
||
372 | |||
373 | // Convert minifier name to camelCase (e.g. jpeg-recompress) |
||
374 | $funcMinifier = $this->camelCase($minifier); |
||
375 | |||
376 | // call the minifier method which prepares the command |
||
377 | if (is_callable($funcMinifier)) { |
||
378 | $command = call_user_func($funcMinifier, $from, $to, $this->minifierOptions); |
||
379 | } elseif (method_exists($this, $funcMinifier)) { |
||
380 | $command = $this->{$funcMinifier}($from, $to); |
||
381 | } else { |
||
382 | $message = sprintf('Minifier method <info>%s</info> cannot be found!', $funcMinifier); |
||
383 | |||
384 | return Result::error($this, $message); |
||
385 | } |
||
386 | |||
387 | // launch the command |
||
388 | $this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]); |
||
389 | $result = $this->executeCommand($command); |
||
390 | |||
391 | // check the return code |
||
392 | if ($result->getExitCode() == 127) { |
||
393 | $this->printTaskError('The {minifier} executable cannot be found', ['minifier' => $minifier]); |
||
394 | // try to install from imagemin repository |
||
395 | if (array_key_exists($minifier, $this->imageminRepos)) { |
||
396 | $result = $this->installFromImagemin($minifier); |
||
397 | if ($result instanceof Result) { |
||
398 | if ($result->wasSuccessful()) { |
||
399 | $this->printTaskSuccess($result->getMessage()); |
||
400 | // retry the conversion with the downloaded executable |
||
401 | if (is_callable($minifier)) { |
||
402 | $command = call_user_func($minifier, $from, $to, $minifierOptions); |
||
0 ignored issues
–
show
The variable
$minifierOptions does not exist. Did you forget to declare it?
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.
Loading history...
|
|||
403 | } elseif (method_exists($this, $minifier)) { |
||
404 | $command = $this->{$minifier}($from, $to); |
||
405 | } |
||
406 | // launch the command |
||
407 | $this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]); |
||
408 | $result = $this->executeCommand($command); |
||
409 | } else { |
||
410 | $this->printTaskError($result->getMessage()); |
||
411 | // the download was not successful |
||
412 | return $result; |
||
413 | } |
||
414 | } |
||
415 | } else { |
||
416 | return $result; |
||
417 | } |
||
418 | } |
||
419 | |||
420 | // check the success of the conversion |
||
421 | if ($result->getExitCode() !== 0) { |
||
422 | $this->results['error'][] = $from; |
||
423 | } else { |
||
424 | $this->results['success'][] = $from; |
||
425 | } |
||
426 | } |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * @return string |
||
431 | */ |
||
432 | View Code Duplication | protected function getOS() |
|
433 | { |
||
434 | $os = php_uname('s'); |
||
435 | $os .= '/'.php_uname('m'); |
||
436 | // replace x86_64 to x64, because the imagemin repo uses that |
||
437 | $os = str_replace('x86_64', 'x64', $os); |
||
438 | // replace i386, i686, etc to x86, because of imagemin |
||
439 | $os = preg_replace('/i[0-9]86/', 'x86', $os); |
||
440 | // turn info to lowercase, because of imagemin |
||
441 | $os = strtolower($os); |
||
442 | |||
443 | return $os; |
||
444 | } |
||
445 | |||
446 | /** |
||
447 | * @param string $command |
||
448 | * |
||
449 | * @return \Robo\Result |
||
450 | */ |
||
451 | protected function executeCommand($command) |
||
452 | { |
||
453 | // insert the options into the command |
||
454 | $a = explode(' ', $command); |
||
455 | $executable = array_shift($a); |
||
456 | foreach ($this->minifierOptions as $key => $value) { |
||
457 | // first prepend the value |
||
458 | if (!empty($value)) { |
||
459 | array_unshift($a, $value); |
||
460 | } |
||
461 | // then add the key |
||
462 | if (!is_numeric($key)) { |
||
463 | array_unshift($a, $key); |
||
464 | } |
||
465 | } |
||
466 | // check if the executable can be replaced with the downloaded one |
||
467 | if (array_key_exists($executable, $this->executablePaths)) { |
||
468 | $executable = $this->executablePaths[$executable]; |
||
469 | } |
||
470 | array_unshift($a, $executable); |
||
471 | $command = implode(' ', $a); |
||
472 | |||
473 | // execute the command |
||
474 | $exec = new Exec($command); |
||
475 | |||
476 | return $exec->inflect($this)->printed(false)->run(); |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * @param string $executable |
||
481 | * |
||
482 | * @return \Robo\Result |
||
483 | */ |
||
484 | protected function installFromImagemin($executable) |
||
485 | { |
||
486 | // check if there is an url defined for the executable |
||
487 | if (!array_key_exists($executable, $this->imageminRepos)) { |
||
488 | $message = sprintf('The executable %s cannot be found in the defined imagemin repositories', $executable); |
||
489 | |||
490 | return Result::error($this, $message); |
||
491 | } |
||
492 | $this->printTaskInfo('Downloading the {executable} executable from the imagemin repository', ['executable' => $executable]); |
||
493 | |||
494 | $os = $this->getOS(); |
||
495 | $url = $this->imageminRepos[$executable].'/blob/master/vendor/'.$os.'/'.$executable.'?raw=true'; |
||
496 | if (substr($os, 0, 3) == 'win') { |
||
497 | // if it is win, add a .exe extension |
||
498 | $url = $this->imageminRepos[$executable].'/blob/master/vendor/'.$os.'/'.$executable.'.exe?raw=true'; |
||
499 | } |
||
500 | $data = @file_get_contents($url, false, null); |
||
501 | if ($data === false) { |
||
502 | // there is something wrong with the url, try it without the version info |
||
503 | $url = preg_replace('/x[68][64]\//', '', $url); |
||
504 | $data = @file_get_contents($url, false, null); |
||
505 | if ($data === false) { |
||
506 | // there is still something wrong with the url if it is win, try with win32 |
||
507 | if (substr($os, 0, 3) == 'win') { |
||
508 | $url = preg_replace('win/', 'win32/', $url); |
||
509 | $data = @file_get_contents($url, false, null); |
||
510 | if ($data === false) { |
||
511 | // there is nothing more we can do |
||
512 | $message = sprintf('Could not download the executable <info>%s</info>', $executable); |
||
513 | |||
514 | return Result::error($this, $message); |
||
515 | } |
||
516 | } |
||
517 | // if it is not windows there is nothing we can do |
||
518 | $message = sprintf('Could not download the executable <info>%s</info>', $executable); |
||
519 | |||
520 | return Result::error($this, $message); |
||
521 | } |
||
522 | } |
||
523 | // check if target directory exists |
||
524 | if (!is_dir($this->executableTargetDir)) { |
||
525 | mkdir($this->executableTargetDir); |
||
526 | } |
||
527 | // save the executable into the target dir |
||
528 | $path = $this->executableTargetDir.'/'.$executable; |
||
529 | if (substr($os, 0, 3) == 'win') { |
||
530 | // if it is win, add a .exe extension |
||
531 | $path = $this->executableTargetDir.'/'.$executable.'.exe'; |
||
532 | } |
||
533 | $result = file_put_contents($path, $data); |
||
534 | if ($result === false) { |
||
535 | $message = sprintf('Could not copy the executable <info>%s</info> to %s', $executable, $target_dir); |
||
0 ignored issues
–
show
|
|||
536 | |||
537 | return Result::error($this, $message); |
||
538 | } |
||
539 | // set the binary to executable |
||
540 | chmod($path, 0755); |
||
541 | |||
542 | // if everything successful, store the executable path |
||
543 | $this->executablePaths[$executable] = $this->executableTargetDir.'/'.$executable; |
||
544 | // if it is win, add a .exe extension |
||
545 | if (substr($os, 0, 3) == 'win') { |
||
546 | $this->executablePaths[$executable] .= '.exe'; |
||
547 | } |
||
548 | |||
549 | $message = sprintf('Executable <info>%s</info> successfully downloaded', $executable); |
||
550 | |||
551 | return Result::success($this, $message); |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * @param string $from |
||
556 | * @param string $to |
||
557 | * |
||
558 | * @return string |
||
559 | */ |
||
560 | protected function optipng($from, $to) |
||
561 | { |
||
562 | $command = sprintf('optipng -quiet -out "%s" -- "%s"', $to, $from); |
||
563 | if ($from != $to && is_file($to)) { |
||
564 | // earlier versions of optipng do not overwrite the target without a backup |
||
565 | // http://sourceforge.net/p/optipng/bugs/37/ |
||
566 | unlink($to); |
||
567 | } |
||
568 | |||
569 | return $command; |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * @param string $from |
||
574 | * @param string $to |
||
575 | * |
||
576 | * @return string |
||
577 | */ |
||
578 | protected function jpegtran($from, $to) |
||
579 | { |
||
580 | $command = sprintf('jpegtran -optimize -outfile "%s" "%s"', $to, $from); |
||
581 | |||
582 | return $command; |
||
583 | } |
||
584 | |||
585 | protected function gifsicle($from, $to) |
||
586 | { |
||
587 | $command = sprintf('gifsicle -o "%s" "%s"', $to, $from); |
||
588 | |||
589 | return $command; |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * @param string $from |
||
594 | * @param string $to |
||
595 | * |
||
596 | * @return string |
||
597 | */ |
||
598 | protected function svgo($from, $to) |
||
599 | { |
||
600 | $command = sprintf('svgo "%s" "%s"', $from, $to); |
||
601 | |||
602 | return $command; |
||
603 | } |
||
604 | |||
605 | /** |
||
606 | * @param string $from |
||
607 | * @param string $to |
||
608 | * |
||
609 | * @return string |
||
610 | */ |
||
611 | protected function pngquant($from, $to) |
||
612 | { |
||
613 | $command = sprintf('pngquant --force --output "%s" "%s"', $to, $from); |
||
614 | |||
615 | return $command; |
||
616 | } |
||
617 | |||
618 | /** |
||
619 | * @param string $from |
||
620 | * @param string $to |
||
621 | * |
||
622 | * @return string |
||
623 | */ |
||
624 | protected function advpng($from, $to) |
||
625 | { |
||
626 | // advpng does not have any output parameters, copy the file and then compress the copy |
||
627 | $command = sprintf('advpng --recompress --quiet "%s"', $to); |
||
628 | $this->fs->copy($from, $to, true); |
||
629 | |||
630 | return $command; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * @param string $from |
||
635 | * @param string $to |
||
636 | * |
||
637 | * @return string |
||
638 | */ |
||
639 | protected function pngout($from, $to) |
||
640 | { |
||
641 | $command = sprintf('pngout -y -q "%s" "%s"', $from, $to); |
||
642 | |||
643 | return $command; |
||
644 | } |
||
645 | |||
646 | /** |
||
647 | * @param string $from |
||
648 | * @param string $to |
||
649 | * |
||
650 | * @return string |
||
651 | */ |
||
652 | protected function zopflipng($from, $to) |
||
653 | { |
||
654 | $command = sprintf('zopflipng -y "%s" "%s"', $from, $to); |
||
655 | |||
656 | return $command; |
||
657 | } |
||
658 | |||
659 | /** |
||
660 | * @param string $from |
||
661 | * @param string $to |
||
662 | * |
||
663 | * @return string |
||
664 | */ |
||
665 | protected function pngcrush($from, $to) |
||
666 | { |
||
667 | $command = sprintf('pngcrush -q -ow "%s" "%s"', $from, $to); |
||
668 | |||
669 | return $command; |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * @param string $from |
||
674 | * @param string $to |
||
675 | * |
||
676 | * @return string |
||
677 | */ |
||
678 | protected function jpegoptim($from, $to) |
||
679 | { |
||
680 | // jpegoptim only takes the destination directory as an argument |
||
681 | $command = sprintf('jpegoptim --quiet -o --dest "%s" "%s"', dirname($to), $from); |
||
682 | |||
683 | return $command; |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * @param string $from |
||
688 | * @param string $to |
||
689 | * |
||
690 | * @return string |
||
691 | */ |
||
692 | protected function jpegRecompress($from, $to) |
||
693 | { |
||
694 | $command = sprintf('jpeg-recompress --quiet "%s" "%s"', $from, $to); |
||
695 | |||
696 | return $command; |
||
697 | } |
||
698 | |||
699 | /** |
||
700 | * @param string $text |
||
701 | * |
||
702 | * @return string |
||
703 | */ |
||
704 | public static function camelCase($text) |
||
705 | { |
||
706 | // non-alpha and non-numeric characters become spaces |
||
707 | $text = preg_replace('/[^a-z0-9]+/i', ' ', $text); |
||
708 | $text = trim($text); |
||
709 | // uppercase the first character of each word |
||
710 | $text = ucwords($text); |
||
711 | $text = str_replace(" ", "", $text); |
||
712 | $text = lcfirst($text); |
||
713 | |||
714 | return $text; |
||
715 | } |
||
716 | } |
||
717 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.