Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / consolidation / robo / src / Task / Assets / Minify.php
1 <?php
2 namespace Robo\Task\Assets;
3
4 use Robo\Result;
5 use Robo\Task\BaseTask;
6
7 /**
8  * Minifies asset file (CSS or JS).
9  *
10  * ``` php
11  * <?php
12  * $this->taskMinify( 'web/assets/theme.css' )
13  *      ->run()
14  * ?>
15  * ```
16  * Please install additional dependencies to use:
17  *
18  * ```
19  * "patchwork/jsqueeze": "~1.0",
20  * "natxet/CssMin": "~3.0"
21  * ```
22  */
23 class Minify extends BaseTask
24 {
25     /**
26      * @var array
27      */
28     protected $types = ['css', 'js'];
29
30     /**
31      * @var string
32      */
33     protected $text;
34
35     /**
36      * @var string
37      */
38     protected $dst;
39
40     /**
41      * @var string
42      */
43     protected $type;
44
45     /**
46      * @var array
47      */
48     protected $squeezeOptions = [
49         'singleLine' => true,
50         'keepImportantComments' => true,
51         'specialVarRx' => false,
52     ];
53
54     /**
55      * Constructor. Accepts asset file path or string source.
56      *
57      * @param string $input
58      */
59     public function __construct($input)
60     {
61         if (file_exists($input)) {
62             $this->fromFile($input);
63             return;
64         }
65
66         $this->fromText($input);
67     }
68
69     /**
70      * Sets destination. Tries to guess type from it.
71      *
72      * @param string $dst
73      *
74      * @return $this
75      */
76     public function to($dst)
77     {
78         $this->dst = $dst;
79
80         if (!empty($this->dst) && empty($this->type)) {
81             $this->type($this->getExtension($this->dst));
82         }
83
84         return $this;
85     }
86
87     /**
88      * Sets type with validation.
89      *
90      * @param string $type css|js
91      *
92      * @return $this
93      */
94     public function type($type)
95     {
96         $type = strtolower($type);
97
98         if (in_array($type, $this->types)) {
99             $this->type = $type;
100         }
101
102         return $this;
103     }
104
105     /**
106      * Sets text from string source.
107      *
108      * @param string $text
109      *
110      * @return $this
111      */
112     protected function fromText($text)
113     {
114         $this->text = (string)$text;
115         unset($this->type);
116
117         return $this;
118     }
119
120     /**
121      * Sets text from asset file path. Tries to guess type and set default destination.
122      *
123      * @param string $path
124      *
125      * @return $this
126      */
127     protected function fromFile($path)
128     {
129         $this->text = file_get_contents($path);
130
131         unset($this->type);
132         $this->type($this->getExtension($path));
133
134         if (empty($this->dst) && !empty($this->type)) {
135             $ext_length = strlen($this->type) + 1;
136             $this->dst = substr($path, 0, -$ext_length) . '.min.' . $this->type;
137         }
138
139         return $this;
140     }
141
142     /**
143      * Gets file extension from path.
144      *
145      * @param string $path
146      *
147      * @return string
148      */
149     protected function getExtension($path)
150     {
151         return pathinfo($path, PATHINFO_EXTENSION);
152     }
153
154     /**
155      * Minifies and returns text.
156      *
157      * @return string|bool
158      */
159     protected function getMinifiedText()
160     {
161         switch ($this->type) {
162             case 'css':
163                 if (!class_exists('\CssMin')) {
164                     return Result::errorMissingPackage($this, 'CssMin', 'natxet/CssMin');
165                 }
166
167                 return \CssMin::minify($this->text);
168                 break;
169
170             case 'js':
171                 if (!class_exists('\JSqueeze') && !class_exists('\Patchwork\JSqueeze')) {
172                     return Result::errorMissingPackage($this, 'Patchwork\JSqueeze', 'patchwork/jsqueeze');
173                 }
174
175                 if (class_exists('\JSqueeze')) {
176                     $jsqueeze = new \JSqueeze();
177                 } else {
178                     $jsqueeze = new \Patchwork\JSqueeze();
179                 }
180
181                 return $jsqueeze->squeeze(
182                     $this->text,
183                     $this->squeezeOptions['singleLine'],
184                     $this->squeezeOptions['keepImportantComments'],
185                     $this->squeezeOptions['specialVarRx']
186                 );
187                 break;
188         }
189
190         return false;
191     }
192
193     /**
194      * Single line option for the JS minimisation.
195      *
196      * @param bool $singleLine
197      *
198      * @return $this
199      */
200     public function singleLine($singleLine)
201     {
202         $this->squeezeOptions['singleLine'] = (bool)$singleLine;
203         return $this;
204     }
205
206     /**
207      * keepImportantComments option for the JS minimisation.
208      *
209      * @param bool $keepImportantComments
210      *
211      * @return $this
212      */
213     public function keepImportantComments($keepImportantComments)
214     {
215         $this->squeezeOptions['keepImportantComments'] = (bool)$keepImportantComments;
216         return $this;
217     }
218
219     /**
220      * specialVarRx option for the JS minimisation.
221      *
222      * @param bool $specialVarRx
223      *
224      * @return $this ;
225      */
226     public function specialVarRx($specialVarRx)
227     {
228         $this->squeezeOptions['specialVarRx'] = (bool)$specialVarRx;
229         return $this;
230     }
231
232     /**
233      * @return string
234      */
235     public function __toString()
236     {
237         return (string) $this->getMinifiedText();
238     }
239
240     /**
241      * {@inheritdoc}
242      */
243     public function run()
244     {
245         if (empty($this->type)) {
246             return Result::error($this, 'Unknown asset type.');
247         }
248
249         if (empty($this->dst)) {
250             return Result::error($this, 'Unknown file destination.');
251         }
252
253         if (file_exists($this->dst) && !is_writable($this->dst)) {
254             return Result::error($this, 'Destination already exists and cannot be overwritten.');
255         }
256
257         $size_before = strlen($this->text);
258         $minified = $this->getMinifiedText();
259
260         if ($minified instanceof Result) {
261             return $minified;
262         } elseif (false === $minified) {
263             return Result::error($this, 'Minification failed.');
264         }
265
266         $size_after = strlen($minified);
267
268         // Minification did not reduce file size, so use original file.
269         if ($size_after > $size_before) {
270             $minified = $this->text;
271             $size_after = $size_before;
272         }
273
274         $dst = $this->dst . '.part';
275         $write_result = file_put_contents($dst, $minified);
276
277         if (false === $write_result) {
278             @unlink($dst);
279             return Result::error($this, 'File write failed.');
280         }
281         // Cannot be cross-volume; should always succeed.
282         @rename($dst, $this->dst);
283         if ($size_before === 0) {
284             $minified_percent = 0;
285         } else {
286             $minified_percent = number_format(100 - ($size_after / $size_before * 100), 1);
287         }
288         $this->printTaskSuccess('Wrote {filepath}', ['filepath' => $this->dst]);
289         $context = [
290             'bytes' => $this->formatBytes($size_after),
291             'reduction' => $this->formatBytes(($size_before - $size_after)),
292             'percentage' => $minified_percent,
293         ];
294         $this->printTaskSuccess('Wrote {bytes} (reduced by {reduction} / {percentage})', $context);
295         return Result::success($this, 'Asset minified.');
296     }
297 }