| Total Complexity | 69 | 
| Total Lines | 533 | 
| Duplicated Lines | 0 % | 
| Changes | 9 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like Extension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Extension, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 31 | class Extension extends SlugifyExtension  | 
            ||
| 32 | { | 
            ||
| 33 | /** @var Builder */  | 
            ||
| 34 | protected $builder;  | 
            ||
| 35 | /** @var Config */  | 
            ||
| 36 | protected $config;  | 
            ||
| 37 | /** @var Slugify */  | 
            ||
| 38 | private static $slugifier;  | 
            ||
| 39 | |||
| 40 | /**  | 
            ||
| 41 | * @param Builder $builder  | 
            ||
| 42 | */  | 
            ||
| 43 | public function __construct(Builder $builder)  | 
            ||
| 44 |     { | 
            ||
| 45 |         if (!self::$slugifier instanceof Slugify) { | 
            ||
| 46 | self::$slugifier = Slugify::create(['regexp' => Page::SLUGIFY_PATTERN]);  | 
            ||
| 47 | }  | 
            ||
| 48 | |||
| 49 | parent::__construct(self::$slugifier);  | 
            ||
| 50 | |||
| 51 | $this->builder = $builder;  | 
            ||
| 52 | $this->config = $this->builder->getConfig();  | 
            ||
| 53 | }  | 
            ||
| 54 | |||
| 55 | /**  | 
            ||
| 56 |      * {@inheritdoc} | 
            ||
| 57 | */  | 
            ||
| 58 | public function getName()  | 
            ||
| 59 |     { | 
            ||
| 60 | return 'cecil';  | 
            ||
| 61 | }  | 
            ||
| 62 | |||
| 63 | /**  | 
            ||
| 64 |      * {@inheritdoc} | 
            ||
| 65 | */  | 
            ||
| 66 | public function getFilters()  | 
            ||
| 67 |     { | 
            ||
| 68 | return [  | 
            ||
| 69 |             new \Twig\TwigFilter('filterBySection', [$this, 'filterBySection']), | 
            ||
| 70 |             new \Twig\TwigFilter('filterBy', [$this, 'filterBy']), | 
            ||
| 71 |             new \Twig\TwigFilter('sortByTitle', [$this, 'sortByTitle']), | 
            ||
| 72 |             new \Twig\TwigFilter('sortByWeight', [$this, 'sortByWeight']), | 
            ||
| 73 |             new \Twig\TwigFilter('sortByDate', [$this, 'sortByDate']), | 
            ||
| 74 |             new \Twig\TwigFilter('urlize', [$this, 'slugifyFilter']), | 
            ||
| 75 |             new \Twig\TwigFilter('url', [$this, 'createUrl']), | 
            ||
| 76 |             new \Twig\TwigFilter('minify', [$this, 'minify']), | 
            ||
| 77 |             new \Twig\TwigFilter('minifyCSS', [$this, 'minifyCss']), | 
            ||
| 78 |             new \Twig\TwigFilter('minifyJS', [$this, 'minifyJs']), | 
            ||
| 79 |             new \Twig\TwigFilter('SCSStoCSS', [$this, 'scssToCss']), | 
            ||
| 80 |             new \Twig\TwigFilter('excerpt', [$this, 'excerpt']), | 
            ||
| 81 |             new \Twig\TwigFilter('excerptHtml', [$this, 'excerptHtml']), | 
            ||
| 82 |             new \Twig\TwigFilter('resize', [$this, 'resize']), | 
            ||
| 83 | ];  | 
            ||
| 84 | }  | 
            ||
| 85 | |||
| 86 | /**  | 
            ||
| 87 |      * {@inheritdoc} | 
            ||
| 88 | */  | 
            ||
| 89 | public function getFunctions()  | 
            ||
| 90 |     { | 
            ||
| 91 | return [  | 
            ||
| 92 |             new \Twig\TwigFunction('url', [$this, 'createUrl']), | 
            ||
| 93 |             new \Twig\TwigFunction('minify', [$this, 'minify']), | 
            ||
| 94 |             new \Twig\TwigFunction('readtime', [$this, 'readtime']), | 
            ||
| 95 |             new \Twig\TwigFunction('toCSS', [$this, 'toCss']), | 
            ||
| 96 |             new \Twig\TwigFunction('hash', [$this, 'hashFile']), | 
            ||
| 97 |             new \Twig\TwigFunction('getenv', [$this, 'getEnv']), | 
            ||
| 98 |             new \Twig\TwigFunction('asset', [$this, 'asset']), | 
            ||
| 99 | ];  | 
            ||
| 100 | }  | 
            ||
| 101 | |||
| 102 | /**  | 
            ||
| 103 | * Filters by Section.  | 
            ||
| 104 | *  | 
            ||
| 105 |      * Alias of `filterBy('section', $value)`. | 
            ||
| 106 | *  | 
            ||
| 107 | * @param PagesCollection $pages  | 
            ||
| 108 | * @param string $section  | 
            ||
| 109 | *  | 
            ||
| 110 | * @return CollectionInterface  | 
            ||
| 111 | */  | 
            ||
| 112 | public function filterBySection(PagesCollection $pages, string $section): CollectionInterface  | 
            ||
| 113 |     { | 
            ||
| 114 | return $this->filterBy($pages, 'section', $section);  | 
            ||
| 115 | }  | 
            ||
| 116 | |||
| 117 | /**  | 
            ||
| 118 | * Filters by variable's name/value.  | 
            ||
| 119 | *  | 
            ||
| 120 | * @param PagesCollection $pages  | 
            ||
| 121 | * @param string $variable  | 
            ||
| 122 | * @param string $value  | 
            ||
| 123 | *  | 
            ||
| 124 | * @return CollectionInterface  | 
            ||
| 125 | */  | 
            ||
| 126 | public function filterBy(PagesCollection $pages, string $variable, string $value): CollectionInterface  | 
            ||
| 127 |     { | 
            ||
| 128 |         $filteredPages = $pages->filter(function (Page $page) use ($variable, $value) { | 
            ||
| 129 | $notVirtual = false;  | 
            ||
| 130 |             if (!$page->isVirtual()) { | 
            ||
| 131 | $notVirtual = true;  | 
            ||
| 132 | }  | 
            ||
| 133 | // is a dedicated getter exists?  | 
            ||
| 134 | $method = 'get'.ucfirst($variable);  | 
            ||
| 135 |             if (method_exists($page, $method) && $page->$method() == $value) { | 
            ||
| 136 | return $notVirtual && true;  | 
            ||
| 137 | }  | 
            ||
| 138 |             if ($page->getVariable($variable) == $value) { | 
            ||
| 139 | return $notVirtual && true;  | 
            ||
| 140 | }  | 
            ||
| 141 | });  | 
            ||
| 142 | |||
| 143 | return $filteredPages;  | 
            ||
| 144 | }  | 
            ||
| 145 | |||
| 146 | /**  | 
            ||
| 147 | * Sorts by title.  | 
            ||
| 148 | *  | 
            ||
| 149 | * @param \Traversable $collection  | 
            ||
| 150 | *  | 
            ||
| 151 | * @return array  | 
            ||
| 152 | */  | 
            ||
| 153 | public function sortByTitle(\Traversable $collection): array  | 
            ||
| 154 |     { | 
            ||
| 155 | $collection = iterator_to_array($collection);  | 
            ||
| 156 | array_multisort(array_keys($collection), SORT_NATURAL | SORT_FLAG_CASE, $collection);  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 157 | |||
| 158 | return $collection;  | 
            ||
| 159 | }  | 
            ||
| 160 | |||
| 161 | /**  | 
            ||
| 162 | * Sorts by weight.  | 
            ||
| 163 | *  | 
            ||
| 164 | * @param \Traversable $collection  | 
            ||
| 165 | *  | 
            ||
| 166 | * @return array  | 
            ||
| 167 | */  | 
            ||
| 168 | public function sortByWeight(\Traversable $collection): array  | 
            ||
| 169 |     { | 
            ||
| 170 |         $callback = function ($a, $b) { | 
            ||
| 171 |             if (!isset($a['weight'])) { | 
            ||
| 172 | return 1;  | 
            ||
| 173 | }  | 
            ||
| 174 |             if (!isset($b['weight'])) { | 
            ||
| 175 | return -1;  | 
            ||
| 176 | }  | 
            ||
| 177 |             if ($a['weight'] == $b['weight']) { | 
            ||
| 178 | return 0;  | 
            ||
| 179 | }  | 
            ||
| 180 | |||
| 181 | return ($a['weight'] < $b['weight']) ? -1 : 1;  | 
            ||
| 182 | };  | 
            ||
| 183 | |||
| 184 | $collection = iterator_to_array($collection);  | 
            ||
| 185 | usort($collection, $callback);  | 
            ||
| 186 | |||
| 187 | return $collection;  | 
            ||
| 188 | }  | 
            ||
| 189 | |||
| 190 | /**  | 
            ||
| 191 | * Sorts by date.  | 
            ||
| 192 | *  | 
            ||
| 193 | * @param \Traversable $collection  | 
            ||
| 194 | *  | 
            ||
| 195 | * @return array  | 
            ||
| 196 | */  | 
            ||
| 197 | public function sortByDate(\Traversable $collection): array  | 
            ||
| 198 |     { | 
            ||
| 199 |         $callback = function ($a, $b) { | 
            ||
| 200 |             if (!isset($a['date'])) { | 
            ||
| 201 | return -1;  | 
            ||
| 202 | }  | 
            ||
| 203 |             if (!isset($b['date'])) { | 
            ||
| 204 | return 1;  | 
            ||
| 205 | }  | 
            ||
| 206 |             if ($a['date'] == $b['date']) { | 
            ||
| 207 | return 0;  | 
            ||
| 208 | }  | 
            ||
| 209 | |||
| 210 | return ($a['date'] > $b['date']) ? -1 : 1;  | 
            ||
| 211 | };  | 
            ||
| 212 | |||
| 213 | $collection = iterator_to_array($collection);  | 
            ||
| 214 | usort($collection, $callback);  | 
            ||
| 215 | |||
| 216 | return $collection;  | 
            ||
| 217 | }  | 
            ||
| 218 | |||
| 219 | /**  | 
            ||
| 220 | * Creates an URL.  | 
            ||
| 221 | *  | 
            ||
| 222 | * $options[  | 
            ||
| 223 | * 'canonical' => null,  | 
            ||
| 224 | * 'addhash' => true,  | 
            ||
| 225 | * 'format' => 'json',  | 
            ||
| 226 | * ];  | 
            ||
| 227 | *  | 
            ||
| 228 | * @param Page|string|null $value  | 
            ||
| 229 | * @param array|bool|null $options  | 
            ||
| 230 | *  | 
            ||
| 231 | * @return string|null  | 
            ||
| 232 | */  | 
            ||
| 233 | public function createUrl($value = null, $options = null): ?string  | 
            ||
| 234 |     { | 
            ||
| 235 |         $baseurl = (string) $this->config->get('baseurl'); | 
            ||
| 236 |         $hash = md5((string) $this->config->get('time')); | 
            ||
| 237 | $base = '';  | 
            ||
| 238 | // handles options  | 
            ||
| 239 | $canonical = null;  | 
            ||
| 240 | $addhash = false;  | 
            ||
| 241 | $format = null;  | 
            ||
| 242 | extract(is_array($options) ? $options : []);  | 
            ||
| 243 | |||
| 244 | // set baseurl  | 
            ||
| 245 |         if ((bool) $this->config->get('canonicalurl') || $canonical === true) { | 
            ||
| 246 | $base = rtrim($baseurl, '/');  | 
            ||
| 247 | }  | 
            ||
| 248 |         if ($canonical === false) { | 
            ||
| 249 | $base = '';  | 
            ||
| 250 | }  | 
            ||
| 251 | |||
| 252 | // value is a Page item  | 
            ||
| 253 |         if ($value instanceof Page) { | 
            ||
| 254 |             if (!$format) { | 
            ||
| 255 |                 $format = $value->getVariable('output'); | 
            ||
| 256 |                 if (is_array($value->getVariable('output'))) { | 
            ||
| 257 |                     $format = $value->getVariable('output')[0]; | 
            ||
| 258 | }  | 
            ||
| 259 |                 if (!$format) { | 
            ||
| 260 | $format = 'html';  | 
            ||
| 261 | }  | 
            ||
| 262 | }  | 
            ||
| 263 | $url = $value->getUrl($format, $this->config);  | 
            ||
| 264 | $url = $base.'/'.ltrim($url, '/');  | 
            ||
| 265 | |||
| 266 | return $url;  | 
            ||
| 267 | }  | 
            ||
| 268 | |||
| 269 | // value is an external URL  | 
            ||
| 270 |         if ($value !== null) { | 
            ||
| 271 |             if (Util::isExternalUrl($value)) { | 
            ||
| 272 | $url = $value;  | 
            ||
| 273 | |||
| 274 | return $url;  | 
            ||
| 275 | }  | 
            ||
| 276 | }  | 
            ||
| 277 | |||
| 278 | // value is a string  | 
            ||
| 279 |         if (!is_null($value)) { | 
            ||
| 280 | // value is an external URL  | 
            ||
| 281 |             if (Util::isExternalUrl($value)) { | 
            ||
| 282 | $url = $value;  | 
            ||
| 283 | |||
| 284 | return $url;  | 
            ||
| 285 | }  | 
            ||
| 286 | $value = Util::joinPath($value);  | 
            ||
| 287 | }  | 
            ||
| 288 | |||
| 289 | // value is a ressource URL (ie: 'path/style.css')  | 
            ||
| 290 |         if (false !== strpos($value, '.')) { | 
            ||
| 291 | $url = $value;  | 
            ||
| 292 |             if ($addhash) { | 
            ||
| 293 | $url .= '?'.$hash;  | 
            ||
| 294 | }  | 
            ||
| 295 | $url = $base.'/'.ltrim($url, '/');  | 
            ||
| 296 | |||
| 297 | return $url;  | 
            ||
| 298 | }  | 
            ||
| 299 | |||
| 300 | // others cases  | 
            ||
| 301 | $url = $base.'/';  | 
            ||
| 302 |         if (!empty($value) && $value != '/') { | 
            ||
| 303 | $url = $base.'/'.$value;  | 
            ||
| 304 | |||
| 305 | // value is a page ID (ie: 'path/my-page')  | 
            ||
| 306 |             try { | 
            ||
| 307 | $pageId = $this->slugifyFilter($value);  | 
            ||
| 308 | $page = $this->builder->getPages()->get($pageId);  | 
            ||
| 309 | $url = $this->createUrl($page, $options);  | 
            ||
| 310 |             } catch (\DomainException $e) { | 
            ||
| 311 | // nothing to do  | 
            ||
| 312 | }  | 
            ||
| 313 | }  | 
            ||
| 314 | |||
| 315 | return $url;  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | /**  | 
            ||
| 319 | * Manages assets (CSS, JS and images).  | 
            ||
| 320 | *  | 
            ||
| 321 | * @param string $path File path (relative from static/ dir).  | 
            ||
| 322 | * @param array|null $options  | 
            ||
| 323 | *  | 
            ||
| 324 | * @return Asset  | 
            ||
| 325 | */  | 
            ||
| 326 | public function asset(string $path, array $options = null): Asset  | 
            ||
| 327 |     { | 
            ||
| 328 | return new Asset($this->builder, $path, $options);  | 
            ||
| 329 | }  | 
            ||
| 330 | |||
| 331 | /**  | 
            ||
| 332 | * Minifying an asset (CSS or JS).  | 
            ||
| 333 |      * ie: minify('css/style.css'). | 
            ||
| 334 | *  | 
            ||
| 335 | * @param string|Asset $asset  | 
            ||
| 336 | *  | 
            ||
| 337 | * @throws Exception  | 
            ||
| 338 | *  | 
            ||
| 339 | * @return Asset  | 
            ||
| 340 | */  | 
            ||
| 341 | public function minify($asset): Asset  | 
            ||
| 381 | }  | 
            ||
| 382 | |||
| 383 | /**  | 
            ||
| 384 | * Compiles a SCSS asset.  | 
            ||
| 385 | *  | 
            ||
| 386 | * @param string|Asset $path  | 
            ||
| 387 | *  | 
            ||
| 388 | * @throws Exception  | 
            ||
| 389 | *  | 
            ||
| 390 | * @return Asset  | 
            ||
| 391 | */  | 
            ||
| 392 | public function toCss($asset): Asset  | 
            ||
| 420 | }  | 
            ||
| 421 | |||
| 422 | /**  | 
            ||
| 423 | * Resizes an image.  | 
            ||
| 424 | *  | 
            ||
| 425 | * @param string $path Image path (relative from static/ dir or external).  | 
            ||
| 426 | * @param int $size Image new size (width).  | 
            ||
| 427 | *  | 
            ||
| 428 | * @return string  | 
            ||
| 429 | */  | 
            ||
| 430 | public function resize(string $path, int $size): string  | 
            ||
| 431 |     { | 
            ||
| 432 | return (new Image($this->builder))->resize($path, $size);  | 
            ||
| 433 | }  | 
            ||
| 434 | |||
| 435 | /**  | 
            ||
| 436 | * Hashing an asset with sha384.  | 
            ||
| 437 | * Useful for SRI (Subresource Integrity).  | 
            ||
| 438 | *  | 
            ||
| 439 | * @see https://developer.mozilla.org/fr/docs/Web/Security/Subresource_Integrity  | 
            ||
| 440 | *  | 
            ||
| 441 | * @param string|Asset $path  | 
            ||
| 442 | *  | 
            ||
| 443 | * @return string|null  | 
            ||
| 444 | */  | 
            ||
| 445 | public function hashFile($asset): ?string  | 
            ||
| 446 |     { | 
            ||
| 447 |         if (!$asset instanceof Asset) { | 
            ||
| 448 | $asset = new Asset($this->builder, $asset);  | 
            ||
| 449 | }  | 
            ||
| 450 | |||
| 451 |         return sprintf('sha384-%s', base64_encode(hash('sha384', $asset['content'], true))); | 
            ||
| 452 | }  | 
            ||
| 453 | |||
| 454 | /**  | 
            ||
| 455 | * Minifying a CSS string.  | 
            ||
| 456 | *  | 
            ||
| 457 | * @param string $value  | 
            ||
| 458 | *  | 
            ||
| 459 | * @return string  | 
            ||
| 460 | */  | 
            ||
| 461 | public function minifyCss(string $value): string  | 
            ||
| 462 |     { | 
            ||
| 463 | $minifier = new Minify\CSS($value);  | 
            ||
| 464 | |||
| 465 | return $minifier->minify();  | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 | /**  | 
            ||
| 469 | * Minifying a JavaScript string.  | 
            ||
| 470 | *  | 
            ||
| 471 | * @param string $value  | 
            ||
| 472 | *  | 
            ||
| 473 | * @return string  | 
            ||
| 474 | */  | 
            ||
| 475 | public function minifyJs(string $value): string  | 
            ||
| 476 |     { | 
            ||
| 477 | $minifier = new Minify\JS($value);  | 
            ||
| 478 | |||
| 479 | return $minifier->minify();  | 
            ||
| 480 | }  | 
            ||
| 481 | |||
| 482 | /**  | 
            ||
| 483 | * Compiles a SCSS string.  | 
            ||
| 484 | *  | 
            ||
| 485 | * @param string $value  | 
            ||
| 486 | *  | 
            ||
| 487 | * @return string  | 
            ||
| 488 | */  | 
            ||
| 489 | public function scssToCss(string $value): string  | 
            ||
| 490 |     { | 
            ||
| 491 | $scss = new Compiler();  | 
            ||
| 492 | |||
| 493 | return $scss->compile($value);  | 
            ||
| 494 | }  | 
            ||
| 495 | |||
| 496 | /**  | 
            ||
| 497 | * Reads $length first characters of a string and adds a suffix.  | 
            ||
| 498 | *  | 
            ||
| 499 | * @param string|null $string  | 
            ||
| 500 | * @param int $length  | 
            ||
| 501 | * @param string $suffix  | 
            ||
| 502 | *  | 
            ||
| 503 | * @return string|null  | 
            ||
| 504 | */  | 
            ||
| 505 | public function excerpt(string $string = null, int $length = 450, string $suffix = ' …'): ?string  | 
            ||
| 506 |     { | 
            ||
| 507 |         $string = str_replace('</p>', '<br /><br />', $string); | 
            ||
| 508 | $string = trim(strip_tags($string, '<br>'), '<br />');  | 
            ||
| 509 |         if (mb_strlen($string) > $length) { | 
            ||
| 510 | $string = mb_substr($string, 0, $length);  | 
            ||
| 511 | $string .= $suffix;  | 
            ||
| 512 | }  | 
            ||
| 513 | |||
| 514 | return $string;  | 
            ||
| 515 | }  | 
            ||
| 516 | |||
| 517 | /**  | 
            ||
| 518 | * Reads characters before '<!-- excerpt|break -->'.  | 
            ||
| 519 | *  | 
            ||
| 520 | * @param string|null $string  | 
            ||
| 521 | *  | 
            ||
| 522 | * @return string|null  | 
            ||
| 523 | */  | 
            ||
| 524 | public function excerptHtml(string $string = null): ?string  | 
            ||
| 525 |     { | 
            ||
| 526 | // https://regex101.com/r/Xl7d5I/3  | 
            ||
| 527 | $pattern = '(.*)(<!--[[:blank:]]?(excerpt|break)[[:blank:]]?-->)(.*)';  | 
            ||
| 528 |         preg_match('/'.$pattern.'/is', $string, $matches); | 
            ||
| 529 |         if (empty($matches)) { | 
            ||
| 530 | return $string;  | 
            ||
| 531 | }  | 
            ||
| 532 | |||
| 533 | return trim($matches[1]);  | 
            ||
| 534 | }  | 
            ||
| 535 | |||
| 536 | /**  | 
            ||
| 537 | * Calculates estimated time to read a text.  | 
            ||
| 538 | *  | 
            ||
| 539 | * @param string|null $text  | 
            ||
| 540 | *  | 
            ||
| 541 | * @return string  | 
            ||
| 542 | */  | 
            ||
| 543 | public function readtime(string $text = null): string  | 
            ||
| 544 |     { | 
            ||
| 545 | $words = str_word_count(strip_tags($text));  | 
            ||
| 546 | $min = floor($words / 200);  | 
            ||
| 547 |         if ($min === 0) { | 
            ||
| 548 | return '1';  | 
            ||
| 549 | }  | 
            ||
| 550 | |||
| 551 | return (string) $min;  | 
            ||
| 552 | }  | 
            ||
| 553 | |||
| 554 | /**  | 
            ||
| 555 | * Gets the value of an environment variable.  | 
            ||
| 556 | *  | 
            ||
| 557 | * @param string $var  | 
            ||
| 558 | *  | 
            ||
| 559 | * @return string|null  | 
            ||
| 560 | */  | 
            ||
| 561 | public function getEnv(string $var): ?string  | 
            ||
| 564 | }  | 
            ||
| 565 | }  | 
            ||
| 566 |