Completed
Push — include-lib ( fd24a6...4173d3 )
by Arnaud
13:24
created

Extension::sortByWeight()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 25

Duplication

Lines 25
Ratio 100 %

Importance

Changes 0
Metric Value
dl 25
loc 25
rs 8.5866
c 0
b 0
f 0
cc 7
nc 4
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 Cocur\Slugify\Bridge\Twig\SlugifyExtension;
12
use Cocur\Slugify\Slugify;
13
use Leafo\ScssPhp\Compiler;
14
use MatthiasMullie\Minify;
15
use Cecil\Collection\Collection;
16
use Cecil\Collection\CollectionInterface;
17
use Cecil\Collection\Page\Page;
18
use Cecil\Exception\Exception;
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
            // filter virtual pages in section
118
            if ($variable == 'section' && $page->getVariable('virtual')) {
119
                return false;
120
            }
121
            // dedicated getter?
122
            $method = 'get'.ucfirst($variable);
123
            if (method_exists($page, $method) && $page->$method() == $value) {
124
                return true;
125
            }
126
            if ($page->getVariable($variable) == $value) {
127
                return true;
128
            }
129
        });
130
131
        return $filteredPages;
132
    }
133
134
    /**
135
     * Sort by title.
136
     *
137
     * @param $array|CollectionInterface
138
     *
139
     * @return mixed
140
     */
141
    public function sortByTitle($array)
142
    {
143
        if ($array instanceof Collection) {
144
            $array = $array->toArray();
145
        }
146
        if (is_array($array)) {
147
            array_multisort(array_keys($array), SORT_NATURAL | SORT_FLAG_CASE, $array);
148
        }
149
150
        return $array;
151
    }
152
153
    /**
154
     * Sort by weight.
155
     *
156
     * @param $array|CollectionInterface
157
     *
158
     * @return mixed
159
     */
160 View Code Duplication
    public function sortByWeight($array)
161
    {
162
        $callback = function ($a, $b) {
163
            if (!isset($a['weight'])) {
164
                return 1;
165
            }
166
            if (!isset($b['weight'])) {
167
                return -1;
168
            }
169
            if ($a['weight'] == $b['weight']) {
170
                return 0;
171
            }
172
173
            return ($a['weight'] < $b['weight']) ? -1 : 1;
174
        };
175
176
        if ($array instanceof Collection) {
177
            $array = $array->toArray();
178
        }
179
        if (is_array($array)) {
180
            usort($array, $callback);
181
        }
182
183
        return $array;
184
    }
185
186
    /**
187
     * Sort by date.
188
     *
189
     * @param $array|CollectionInterface
190
     *
191
     * @return mixed
192
     */
193 View Code Duplication
    public function sortByDate($array)
194
    {
195
        $callback = function ($a, $b) {
196
            if (!isset($a['date'])) {
197
                return -1;
198
            }
199
            if (!isset($b['date'])) {
200
                return 1;
201
            }
202
            if ($a['date'] == $b['date']) {
203
                return 0;
204
            }
205
206
            return ($a['date'] > $b['date']) ? -1 : 1;
207
        };
208
209
        if ($array instanceof Collection) {
210
            $array = $array->toArray();
211
        }
212
        if (is_array($array)) {
213
            usort($array, $callback);
214
        }
215
216
        return $array;
217
    }
218
219
    /**
220
     * Create an URL.
221
     *
222
     * $options[
223
     *     'canonical' => null,
224
     *     'addhash'   => true,
225
     * ];
226
     *
227
     * @param \Twig_Environment            $env
228
     * @param string|\Cecil\Page\Page|null $value
229
     * @param array|null                   $options
230
     *
231
     * @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...
232
     */
233
    public function createUrl(\Twig_Environment $env, $value = null, $options = null)
234
    {
235
        $base = '';
236
        $baseurl = $env->getGlobals()['site']['baseurl'];
237
        $hash = md5($env->getGlobals()['site']['time']);
238
        $canonical = null;
239
        $addhash = true;
240
241
        if (isset($options['canonical'])) {
242
            $canonical = $options['canonical'];
243
        }
244
        if (is_bool($options)) { // backward compatibility
245
            $canonical = $options;
246
        }
247
        if (isset($options['addhash'])) {
248
            $addhash = $options['addhash'];
249
        }
250
251
        if ($env->getGlobals()['site']['canonicalurl'] === true || $canonical === true) {
252
            $base = rtrim($baseurl, '/');
253
        }
254
        if ($canonical === false) {
255
            $base = '';
256
        }
257
258
        if ($value instanceof Page) {
259
            $value = $value->getPermalink();
260
            if (false !== strpos($value, '.')) { // file URL (with a dot for extension)
261
                $url = $base.'/'.ltrim($value, '/');
262
            } else {
263
                $url = $base.'/'.ltrim(rtrim($value, '/').'/', '/');
264
            }
265
        } else {
266
            if (preg_match('~^(?:f|ht)tps?://~i', $value)) { // external URL
267
                $url = $value;
268
            } elseif (false !== strpos($value, '.')) { // file URL (with a dot for extension)
269
                $url = $base.'/'.ltrim($value, '/');
270
                if ($addhash) {
271
                    $url .= '?'.$hash;
272
                }
273
            } else {
274
                $url = $base.'/';
275
                if (!empty($value)) {
276
                    $value = $this->slugifyFilter($value);
277
                    $url .= ltrim(rtrim($value, '/').'/', '/');
278
                }
279
            }
280
        }
281
282
        return $url;
283
    }
284
285
    /**
286
     * Minify a CSS or a JS file.
287
     *
288
     * @param string $path
289
     *
290
     * @throws Exception
291
     *
292
     * @return string
293
     */
294
    public function minify($path)
295
    {
296
        $filePath = $this->destPath.'/'.$path;
297
        if (is_file($filePath)) {
298
            $extension = (new \SplFileInfo($filePath))->getExtension();
299
            switch ($extension) {
300
                case 'css':
301
                    $minifier = new Minify\CSS($filePath);
302
                    break;
303
                case 'js':
304
                    $minifier = new Minify\JS($filePath);
305
                    break;
306
                default:
307
                    throw new Exception(sprintf("File '%s' should be a '.css' or a '.js'!", $path));
308
            }
309
            $minifier->minify($filePath);
310
311
            return $path;
312
        }
313
314
        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
315
    }
316
317
    /**
318
     * Minify CSS.
319
     *
320
     * @param $value
321
     *
322
     * @return string
323
     */
324
    public function minifyCss($value)
325
    {
326
        $minifier = new Minify\CSS($value);
327
328
        return $minifier->minify();
329
    }
330
331
    /**
332
     * Minify JS.
333
     *
334
     * @param $value
335
     *
336
     * @return string
337
     */
338
    public function minifyJs($value)
339
    {
340
        $minifier = new Minify\JS($value);
341
342
        return $minifier->minify();
343
    }
344
345
    /**
346
     * Compile style file to CSS.
347
     *
348
     * @param string $path
349
     *
350
     * @throws Exception
351
     *
352
     * @return string
353
     */
354
    public function toCss($path)
355
    {
356
        $filePath = $this->destPath.'/'.$path;
357
        $subPath = substr($path, 0, strrpos($path, '/'));
358
359
        if (is_file($filePath)) {
360
            $extension = (new \SplFileInfo($filePath))->getExtension();
361
            switch ($extension) {
362
                case 'scss':
363
                    $scssPhp = new Compiler();
364
                    $scssPhp->setImportPaths($this->destPath.'/'.$subPath);
365
                    $targetPath = preg_replace('/scss/m', 'css', $path);
366
367
                    // compile if target file doesn't exists
368
                    if (!$this->fileSystem->exists($this->destPath.'/'.$targetPath)) {
369
                        $scss = file_get_contents($filePath);
370
                        $css = $scssPhp->compile($scss);
371
                        $this->fileSystem->dumpFile($this->destPath.'/'.$targetPath, $css);
372
                    }
373
374
                    return $targetPath;
375
                default:
376
                    throw new Exception(sprintf("File '%s' should be a '.scss'!", $path));
377
            }
378
        }
379
380
        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
381
    }
382
383
    /**
384
     * Compile SCSS string to CSS.
385
     *
386
     * @param $value
387
     *
388
     * @return string
389
     */
390
    public function scssToCss($value)
391
    {
392
        $scss = new Compiler();
393
394
        return $scss->compile($value);
395
    }
396
397
    /**
398
     * Read $lenght first characters of a string and add a suffix.
399
     *
400
     * @param $string
401
     * @param int    $length
402
     * @param string $suffix
403
     *
404
     * @return string
405
     */
406
    public function excerpt($string, $length = 450, $suffix = ' …')
407
    {
408
        $string = str_replace('</p>', '<br /><br />', $string);
409
        $string = trim(strip_tags($string, '<br>'), '<br />');
410
        if (mb_strlen($string) > $length) {
411
            $string = mb_substr($string, 0, $length);
412
            $string .= $suffix;
413
        }
414
415
        return $string;
416
    }
417
418
    /**
419
     * Read characters before '<!-- excerpt -->'.
420
     *
421
     * @param $string
422
     *
423
     * @return string
424
     */
425
    public function excerptHtml($string)
426
    {
427
        // https://regex101.com/r/mA2mG0/3
428
        $pattern = '^(.*)[\n\r\s]*<!-- excerpt -->[\n\r\s]*(.*)$';
429
        preg_match(
430
            '/'.$pattern.'/s',
431
            $string,
432
            $matches
433
        );
434
        if (empty($matches)) {
435
            return $string;
436
        }
437
438
        return trim($matches[1]);
439
    }
440
441
    /**
442
     * Calculate estimated time to read a text.
443
     *
444
     * @param $text
445
     *
446
     * @return float|string
447
     */
448
    public function readtime($text)
449
    {
450
        $words = str_word_count(strip_tags($text));
451
        $min = floor($words / 200);
452
        if ($min === 0) {
453
            return '1';
454
        }
455
456
        return $min;
457
    }
458
459
    /**
460
     * Hash file with sha384.
461
     *
462
     * @param string $path
463
     *
464
     * @return string|null
465
     */
466
    public function hashFile($path)
467
    {
468
        if (is_file($filePath = $this->destPath.'/'.$path)) {
469
            return sprintf('sha384-%s', base64_encode(hash_file('sha384', $filePath, true)));
470
        }
471
    }
472
}
473