Completed
Push — filterby ( a0d10d )
by Arnaud
02:12
created

Extension::hashFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
/*
3
 * Copyright (c) Arnaud Ligny <[email protected]>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Cecil\Renderer\Twig;
10
11
use Cecil\Collection\Collection;
12
use Cecil\Collection\CollectionInterface;
13
use Cecil\Collection\Page\Page;
14
use Cecil\Exception\Exception;
15
use Cocur\Slugify\Bridge\Twig\SlugifyExtension;
16
use Cocur\Slugify\Slugify;
17
use Leafo\ScssPhp\Compiler;
18
use MatthiasMullie\Minify;
19
use Symfony\Component\Filesystem\Filesystem;
20
21
/**
22
 * Class Twig\Extension.
23
 */
24
class Extension extends SlugifyExtension
25
{
26
    /* @var string */
27
    protected $destPath;
28
    /**
29
     * @var Filesystem
30
     */
31
    protected $fileSystem;
32
33
    /**
34
     * Constructor.
35
     *
36
     * @param string $destPath
37
     */
38
    public function __construct($destPath)
39
    {
40
        $this->destPath = $destPath;
41
        parent::__construct(Slugify::create([
42
            'regexp' => Page::SLUGIFY_PATTERN,
43
        ]));
44
45
        $this->fileSystem = new Filesystem();
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function getName()
52
    {
53
        return 'cecil';
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function getFilters()
60
    {
61
        return [
62
            new \Twig_SimpleFilter('filterBySection', [$this, 'filterBySection']),
63
            new \Twig_SimpleFilter('filterBy', [$this, 'filterBy']),
64
            new \Twig_SimpleFilter('sortByTitle', [$this, 'sortByTitle']),
65
            new \Twig_SimpleFilter('sortByWeight', [$this, 'sortByWeight']),
66
            new \Twig_SimpleFilter('sortByDate', [$this, 'sortByDate']),
67
            new \Twig_SimpleFilter('urlize', [$this, 'slugifyFilter']),
68
            new \Twig_SimpleFilter('minifyCSS', [$this, 'minifyCss']),
69
            new \Twig_SimpleFilter('minifyJS', [$this, 'minifyJs']),
70
            new \Twig_SimpleFilter('SCSStoCSS', [$this, 'scssToCss']),
71
            new \Twig_SimpleFilter('excerpt', [$this, 'excerpt']),
72
            new \Twig_SimpleFilter('excerptHtml', [$this, 'excerptHtml']),
73
        ];
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function getFunctions()
80
    {
81
        return [
82
            new \Twig_SimpleFunction('url', [$this, 'createUrl'], ['needs_environment' => true]),
83
            new \Twig_SimpleFunction('minify', [$this, 'minify']),
84
            new \Twig_SimpleFunction('readtime', [$this, 'readtime']),
85
            new \Twig_SimpleFunction('toCSS', [$this, 'toCss']),
86
            new \Twig_SimpleFunction('hash', [$this, 'hashFile']),
87
        ];
88
    }
89
90
    /**
91
     * Filter by section.
92
     *
93
     * @param \Cecil\Page\Collection $pages
94
     * @param string                 $section
95
     *
96
     * @return array
97
     */
98
    public function filterBySection($pages, $section)
99
    {
100
        return $this->filterBy($pages, 'section', $section);
101
    }
102
103
    /**
104
     * Filter by variable.
105
     *
106
     * @param \Cecil\Page\Collection $pages
107
     * @param string                 $variable
108
     * @param string                 $value
109
     *
110
     * @throws Exception
111
     *
112
     * @return array
113
     */
114
    public function filterBy($pages, $variable, $value)
115
    {
116
        $filteredPages = $pages->filter(function (Page $page) use ($variable, $value) {
117
            // dedicated getter?
118
            $method = 'get'.ucfirst($variable);
119
            if (method_exists($page, $method) && $page->$method() == $value) {
120
                return true;
121
            }
122
            if ($page->getVariable($variable) == $value) {
123
                return true;
124
            }
125
        });
126
127
        return $filteredPages;
128
    }
129
130
    /**
131
     * Sort by title.
132
     *
133
     * @param $array|CollectionInterface
134
     *
135
     * @return mixed
136
     */
137
    public function sortByTitle($array)
138
    {
139
        if ($array instanceof Collection) {
140
            $array = $array->toArray();
141
        }
142
        if (is_array($array)) {
143
            array_multisort(array_keys($array), SORT_NATURAL | SORT_FLAG_CASE, $array);
144
        }
145
146
        return $array;
147
    }
148
149
    /**
150
     * Sort by weight.
151
     *
152
     * @param $array|CollectionInterface
153
     *
154
     * @return mixed
155
     */
156 View Code Duplication
    public function sortByWeight($array)
157
    {
158
        $callback = function ($a, $b) {
159
            if (!isset($a['weight'])) {
160
                return 1;
161
            }
162
            if (!isset($b['weight'])) {
163
                return -1;
164
            }
165
            if ($a['weight'] == $b['weight']) {
166
                return 0;
167
            }
168
169
            return ($a['weight'] < $b['weight']) ? -1 : 1;
170
        };
171
172
        if ($array instanceof Collection) {
173
            $array = $array->toArray();
174
        }
175
        if (is_array($array)) {
176
            usort($array, $callback);
177
        }
178
179
        return $array;
180
    }
181
182
    /**
183
     * Sort by date.
184
     *
185
     * @param $array|CollectionInterface
186
     *
187
     * @return mixed
188
     */
189 View Code Duplication
    public function sortByDate($array)
190
    {
191
        $callback = function ($a, $b) {
192
            if (!isset($a['date'])) {
193
                return -1;
194
            }
195
            if (!isset($b['date'])) {
196
                return 1;
197
            }
198
            if ($a['date'] == $b['date']) {
199
                return 0;
200
            }
201
202
            return ($a['date'] > $b['date']) ? -1 : 1;
203
        };
204
205
        if ($array instanceof Collection) {
206
            $array = $array->toArray();
207
        }
208
        if (is_array($array)) {
209
            usort($array, $callback);
210
        }
211
212
        return $array;
213
    }
214
215
    /**
216
     * Create an URL.
217
     *
218
     * $options[
219
     *     'canonical' => null,
220
     *     'addhash'   => true,
221
     * ];
222
     *
223
     * @param \Twig_Environment            $env
224
     * @param string|\Cecil\Page\Page|null $value
225
     * @param array|null                   $options
226
     *
227
     * @return string|null
0 ignored issues
show
Documentation introduced by
Should the return type not be string|\Cecil\Page\Page|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
228
     */
229
    public function createUrl(\Twig_Environment $env, $value = null, $options = null)
230
    {
231
        $base = '';
232
        $baseurl = $env->getGlobals()['site']['baseurl'];
233
        $hash = md5($env->getGlobals()['site']['time']);
234
        $canonical = null;
235
        $addhash = true;
236
237
        if (isset($options['canonical'])) {
238
            $canonical = $options['canonical'];
239
        }
240
        if (is_bool($options)) { // backward compatibility
241
            $canonical = $options;
242
        }
243
        if (isset($options['addhash'])) {
244
            $addhash = $options['addhash'];
245
        }
246
247
        if ($env->getGlobals()['site']['canonicalurl'] === true || $canonical === true) {
248
            $base = rtrim($baseurl, '/');
249
        }
250
        if ($canonical === false) {
251
            $base = '';
252
        }
253
254
        if ($value instanceof Page) {
255
            $value = $value->getPermalink();
256
            if (false !== strpos($value, '.')) { // file URL (with a dot for extension)
257
                $url = $base.'/'.ltrim($value, '/');
258
            } else {
259
                $url = $base.'/'.ltrim(rtrim($value, '/').'/', '/');
260
            }
261
        } else {
262
            if (preg_match('~^(?:f|ht)tps?://~i', $value)) { // external URL
263
                $url = $value;
264
            } elseif (false !== strpos($value, '.')) { // file URL (with a dot for extension)
265
                $url = $base.'/'.ltrim($value, '/');
266
                if ($addhash) {
267
                    $url .= '?'.$hash;
268
                }
269
            } else {
270
                $url = $base.'/';
271
                if (!empty($value)) {
272
                    $value = $this->slugifyFilter($value);
273
                    $url .= ltrim(rtrim($value, '/').'/', '/');
274
                }
275
            }
276
        }
277
278
        return $url;
279
    }
280
281
    /**
282
     * Minify a CSS or a JS file.
283
     *
284
     * @param string $path
285
     *
286
     * @throws Exception
287
     *
288
     * @return string
289
     */
290
    public function minify($path)
291
    {
292
        $filePath = $this->destPath.'/'.$path;
293
        if (is_file($filePath)) {
294
            $extension = (new \SplFileInfo($filePath))->getExtension();
295
            switch ($extension) {
296
                case 'css':
297
                    $minifier = new Minify\CSS($filePath);
298
                    break;
299
                case 'js':
300
                    $minifier = new Minify\JS($filePath);
301
                    break;
302
                default:
303
                    throw new Exception(sprintf("File '%s' should be a '.css' or a '.js'!", $path));
304
            }
305
            $minifier->minify($filePath);
306
307
            return $path;
308
        }
309
310
        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
311
    }
312
313
    /**
314
     * Minify CSS.
315
     *
316
     * @param $value
317
     *
318
     * @return string
319
     */
320
    public function minifyCss($value)
321
    {
322
        $minifier = new Minify\CSS($value);
323
324
        return $minifier->minify();
325
    }
326
327
    /**
328
     * Minify JS.
329
     *
330
     * @param $value
331
     *
332
     * @return string
333
     */
334
    public function minifyJs($value)
335
    {
336
        $minifier = new Minify\JS($value);
337
338
        return $minifier->minify();
339
    }
340
341
    /**
342
     * Compile style file to CSS.
343
     *
344
     * @param string $path
345
     *
346
     * @throws Exception
347
     *
348
     * @return string
349
     */
350
    public function toCss($path)
351
    {
352
        $filePath = $this->destPath.'/'.$path;
353
        $subPath = substr($path, 0, strrpos($path, '/'));
354
355
        if (is_file($filePath)) {
356
            $extension = (new \SplFileInfo($filePath))->getExtension();
357
            switch ($extension) {
358
                case 'scss':
359
                    $scssPhp = new Compiler();
360
                    $scssPhp->setImportPaths($this->destPath.'/'.$subPath);
361
                    $targetPath = preg_replace('/scss/m', 'css', $path);
362
363
                    // compile if target file doesn't exists
364
                    if (!$this->fileSystem->exists($this->destPath.'/'.$targetPath)) {
365
                        $scss = file_get_contents($filePath);
366
                        $css = $scssPhp->compile($scss);
367
                        $this->fileSystem->dumpFile($this->destPath.'/'.$targetPath, $css);
368
                    }
369
370
                    return $targetPath;
371
                default:
372
                    throw new Exception(sprintf("File '%s' should be a '.scss'!", $path));
373
            }
374
        }
375
376
        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
377
    }
378
379
    /**
380
     * Compile SCSS string to CSS.
381
     *
382
     * @param $value
383
     *
384
     * @return string
385
     */
386
    public function scssToCss($value)
387
    {
388
        $scss = new Compiler();
389
390
        return $scss->compile($value);
391
    }
392
393
    /**
394
     * Read $lenght first characters of a string and add a suffix.
395
     *
396
     * @param $string
397
     * @param int    $length
398
     * @param string $suffix
399
     *
400
     * @return string
401
     */
402
    public function excerpt($string, $length = 450, $suffix = ' …')
403
    {
404
        $string = str_replace('</p>', '<br /><br />', $string);
405
        $string = trim(strip_tags($string, '<br>'), '<br />');
406
        if (mb_strlen($string) > $length) {
407
            $string = mb_substr($string, 0, $length);
408
            $string .= $suffix;
409
        }
410
411
        return $string;
412
    }
413
414
    /**
415
     * Read characters before '<!-- excerpt -->'.
416
     *
417
     * @param $string
418
     *
419
     * @return string
420
     */
421
    public function excerptHtml($string)
422
    {
423
        // https://regex101.com/r/mA2mG0/3
424
        $pattern = '^(.*)[\n\r\s]*<!-- excerpt -->[\n\r\s]*(.*)$';
425
        preg_match(
426
            '/'.$pattern.'/s',
427
            $string,
428
            $matches
429
        );
430
        if (empty($matches)) {
431
            return $string;
432
        }
433
434
        return trim($matches[1]);
435
    }
436
437
    /**
438
     * Calculate estimated time to read a text.
439
     *
440
     * @param $text
441
     *
442
     * @return float|string
443
     */
444
    public function readtime($text)
445
    {
446
        $words = str_word_count(strip_tags($text));
447
        $min = floor($words / 200);
448
        if ($min === 0) {
449
            return '1';
450
        }
451
452
        return $min;
453
    }
454
455
    /**
456
     * Hash file with sha384.
457
     *
458
     * @param string $path
459
     *
460
     * @return string|null
461
     */
462
    public function hashFile($path)
463
    {
464
        if (is_file($filePath = $this->destPath.'/'.$path)) {
465
            return sprintf('sha384-%s', base64_encode(hash_file('sha384', $filePath, true)));
466
        }
467
    }
468
}
469