Block   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 549
Duplicated Lines 0 %

Test Coverage

Coverage 43.66%

Importance

Changes 0
Metric Value
eloc 177
dl 0
loc 549
ccs 124
cts 284
cp 0.4366
rs 3.04
c 0
b 0
f 0
wmc 67

22 Methods

Rating   Name   Duplication   Size   Complexity  
A createForm() 0 3 1
A getRequest() 0 3 1
A addJSData() 0 3 1
A redirect() 0 3 1
A setData() 0 8 2
A setAction() 0 3 1
A getTemplatePath() 0 3 1
A setTemplatePath() 0 3 1
A loadTemplate() 0 10 2
A setOverwrite() 0 3 1
F parsePagination() 0 191 31
A execute() 0 25 3
A getModule() 0 3 1
A setModule() 0 3 1
A addJS() 0 20 4
A __construct() 0 14 1
A getAction() 0 3 1
A getTemplate() 0 3 1
A getOverwrite() 0 3 1
A addCSS() 0 20 4
A getContent() 0 3 1
A setMeta() 0 21 6

How to fix   Complexity   

Complex Class

Complex classes like Block 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 Block, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Frontend\Core\Engine\Base;
4
5
use Common\Core\Header\Priority;
6
use Common\Doctrine\Entity\Meta;
7
use Common\Exception\RedirectException;
8
use ForkCMS\App\KernelLoader;
9
use Frontend\Core\Engine\Breadcrumb;
10
use Frontend\Core\Engine\Exception;
11
use Frontend\Core\Engine\Model;
12
use Frontend\Core\Engine\Url;
13
use Frontend\Core\Header\Header;
14
use Frontend\Core\Engine\TwigTemplate;
15
use Symfony\Component\Form\Form;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpKernel\KernelInterface;
19
20
/**
21
 * This class implements a lot of functionality that can be extended by a specific block
22
 *
23
 * @later  Check which methods are the same in FrontendBaseWidget, maybe we should extend from a general class
24
 */
25
class Block extends KernelLoader
26
{
27
    /**
28
     * The current action
29
     *
30
     * @var string
31
     */
32
    protected $action;
33
34
    /**
35
     * The breadcrumb object
36
     *
37
     * @var Breadcrumb
38
     */
39
    protected $breadcrumb;
40
41
    /**
42
     * The data
43
     *
44
     * @var mixed
45
     */
46
    protected $data;
47
48
    /**
49
     * The header object
50
     *
51
     * @var Header
52
     */
53
    protected $header;
54
55
    /**
56
     * The current module
57
     *
58
     * @var string
59
     */
60
    protected $module;
61
62
    /**
63
     * Should the current template be replaced with the blocks one?
64
     *
65
     * @var bool
66
     */
67
    private $overwrite;
68
69
    /**
70
     * Pagination array
71
     *
72
     * @var array
73
     */
74
    protected $pagination;
75
76
    /**
77
     * The path of the template to include, or that replaced the current one
78
     *
79
     * @var string
80
     */
81
    private $templatePath;
82
83
    /**
84
     * TwigTemplate instance
85
     *
86
     * @var TwigTemplate
87
     */
88
    protected $template;
89
90
    /**
91
     * URL instance
92
     *
93
     * @var Url
94
     */
95
    protected $url;
96
97
    /**
98
     * @param KernelInterface $kernel
99
     * @param string $module The name of the module.
100
     * @param string $action The name of the action.
101
     * @param string $data The data that should be available in this block.
102
     */
103 26
    public function __construct(KernelInterface $kernel, string $module, string $action, string $data = null)
104
    {
105 26
        parent::__construct($kernel);
106
107
        // get objects from the reference so they are accessible
108 26
        $this->header = $this->getContainer()->get('header');
109 26
        $this->url = $this->getContainer()->get('url');
110 26
        $this->template = $this->getContainer()->get('templating');
111 26
        $this->breadcrumb = $this->getContainer()->get('breadcrumb');
112
113
        // set properties
114 26
        $this->setModule($module);
115 26
        $this->setAction($action);
116 26
        $this->setData($data);
117 26
    }
118
119
    /**
120
     * Add a CSS file into the array
121
     *
122
     * @param string $file The path for the CSS-file that should be loaded.
123
     * @param bool $overwritePath Whether or not to add the module to this path. Module path is added by default.
124
     * @param bool $minify Should the CSS be minified?
125
     * @param bool $addTimestamp May we add a timestamp for caching purposes?
126
     */
127 4
    public function addCSS(
128
        string $file,
129
        bool $overwritePath = false,
130
        bool $minify = true,
131
        bool $addTimestamp = false
132
    ): void {
133
        // external urls always overwrite the path
134 4
        $overwritePath = $overwritePath || $this->get('fork.validator.url')->isExternalUrl($file);
135
136
        // use module path
137 4
        if (!$overwritePath) {
138 4
            $file = '/src/Frontend/Modules/' . $this->getModule() . '/Layout/Css/' . $file;
139
        }
140
141
        // add css to the header
142 4
        $this->header->addCSS(
143 4
            $file,
144 4
            $minify,
145 4
            $addTimestamp,
146 4
            $overwritePath ? Priority::standard() : Priority::forModule($this->getModule())
147
        );
148 4
    }
149
150
    /**
151
     * Add a javascript file into the array
152
     *
153
     * @param string $file The path to the javascript-file that should be loaded.
154
     * @param bool $overwritePath Whether or not to add the module to this path. Module path is added by default.
155
     * @param bool $minify Should the file be minified?
156
     * @param bool $addTimestamp May we add a timestamp for caching purposes?
157
     */
158 4
    public function addJS(
159
        string $file,
160
        bool $overwritePath = false,
161
        bool $minify = true,
162
        bool $addTimestamp = false
163
    ): void {
164
        // external urls always overwrite the path
165 4
        $overwritePath = $overwritePath || $this->get('fork.validator.url')->isExternalUrl($file);
166
167
        // use module path
168 4
        if (!$overwritePath) {
169
            $file = '/src/Frontend/Modules/' . $this->getModule() . '/Js/' . $file;
170
        }
171
172
        // add js to the header
173 4
        $this->header->addJS(
174 4
            $file,
175 4
            $minify,
176 4
            $addTimestamp,
177 4
            $overwritePath ? Priority::standard() : Priority::forModule($this->getModule())
178
        );
179 4
    }
180
181
    /**
182
     * Add data that should be available in JS
183
     *
184
     * @param string $key The key whereunder the value will be stored.
185
     * @param mixed $value The value to pass.
186
     */
187
    public function addJSData(string $key, $value): void
188
    {
189
        $this->header->addJsData($this->getModule(), $key, $value);
190
    }
191
192
    /**
193
     * Execute the action
194
     * If a javascript file with the name of the module or action exists it will be loaded.
195
     */
196 25
    public function execute(): void
197
    {
198
        // build path to the module
199 25
        $frontendModulePath = FRONTEND_MODULES_PATH . '/' . $this->getModule();
200
201
        // build URL to the module
202 25
        $frontendModuleUrl = '/src/Frontend/Modules/' . $this->getModule() . '/Js';
203
204
        // add javascript file with same name as module (if the file exists)
205 25
        if (is_file($frontendModulePath . '/Js/' . $this->getModule() . '.js')) {
206 12
            $this->header->addJS(
207 12
                $frontendModuleUrl . '/' . $this->getModule() . '.js',
208 12
                true,
209 12
                true,
210 12
                Priority::module()
211
            );
212
        }
213
214
        // add javascript file with same name as the action (if the file exists)
215 25
        if (is_file($frontendModulePath . '/Js/' . $this->getAction() . '.js')) {
216
            $this->header->addJS(
217
                $frontendModuleUrl . '/' . $this->getAction() . '.js',
218
                true,
219
                true,
220
                Priority::module()
221
            );
222
        }
223 25
    }
224
225 26
    public function getAction(): string
226
    {
227 26
        return $this->action;
228
    }
229
230
    /**
231
     * Get parsed template content.
232
     *
233
     * @return string
234
     */
235 19
    public function getContent(): string
236
    {
237 19
        return $this->template->getContent($this->templatePath);
238
    }
239
240 26
    public function getModule(): string
241
    {
242 26
        return $this->module;
243
    }
244
245
    /**
246
     * Get overwrite mode
247
     *
248
     * @return bool
249
     */
250 19
    public function getOverwrite(): bool
251
    {
252 19
        return $this->overwrite;
253
    }
254
255 19
    public function getTemplate(): TwigTemplate
256
    {
257 19
        return $this->template;
258
    }
259
260 19
    public function getTemplatePath(): string
261
    {
262 19
        return $this->templatePath;
263
    }
264
265
    /**
266
     * @param string $path The path for the template to use.
267
     * @param bool $overwrite Should the template overwrite the default?
268
     */
269 25
    protected function loadTemplate(string $path = null, bool $overwrite = false): void
270
    {
271
        // no template given, so we should build the path
272 25
        if ($path === null) {
273 25
            $path = $this->getModule() . '/Layout/Templates/' . $this->getAction() . '.html.twig';
274
        }
275
276
        // set properties
277 25
        $this->setOverwrite($overwrite);
278 25
        $this->setTemplatePath($path);
279 25
    }
280
281 9
    protected function parsePagination(string $query_parameter = 'page'): void
282
    {
283 9
        $pagination = null;
284 9
        $showFirstPages = false;
285 9
        $showLastPages = false;
286 9
        $useQuestionMark = true;
287
288
        // validate pagination array
289 9
        if (!isset($this->pagination['limit'])) {
290
            throw new Exception('no limit in the pagination-property.');
291
        }
292 9
        if (!isset($this->pagination['offset'])) {
293
            throw new Exception('no offset in the pagination-property.');
294
        }
295 9
        if (!isset($this->pagination['requested_page'])) {
296
            throw new Exception('no requested_page available in the pagination-property.');
297
        }
298 9
        if (!isset($this->pagination['num_items'])) {
299
            throw new Exception('no num_items available in the pagination-property.');
300
        }
301 9
        if (!isset($this->pagination['num_pages'])) {
302
            throw new Exception('no num_pages available in the pagination-property.');
303
        }
304 9
        if (!isset($this->pagination['url'])) {
305
            throw new Exception('no url available in the pagination-property.');
306
        }
307
308
        // should we use a questionmark or an ampersand
309 9
        if (mb_strpos($this->pagination['url'], '?') !== false) {
310 1
            $useQuestionMark = false;
311
        }
312
313
        // no pagination needed
314 9
        if ($this->pagination['num_pages'] < 1) {
315
            return;
316
        }
317
318
        // populate count fields
319 9
        $pagination['num_pages'] = $this->pagination['num_pages'];
320 9
        $pagination['current_page'] = $this->pagination['requested_page'];
321
322
        // define anchor
323 9
        $anchor = isset($this->pagination['anchor']) ? '#' . $this->pagination['anchor'] : '';
324
325
        // as long as we have more then 5 pages and are 5 pages from the end we should show all pages till the end
326 9
        if ($this->pagination['requested_page'] > 5
327 9
            && $this->pagination['requested_page'] >= ($this->pagination['num_pages'] - 4)
328
        ) {
329
            $pagesStart = ($this->pagination['num_pages'] > 7)
330
                ? $this->pagination['num_pages'] - 5 : $this->pagination['num_pages'] - 6;
331
            $pagesEnd = $this->pagination['num_pages'];
332
333
            // fix for page 6
334
            if ($this->pagination['num_pages'] === 6) {
335
                $pagesStart = 1;
336
            }
337
338
            // show first pages
339
            if ($this->pagination['num_pages'] > 7) {
340
                $showFirstPages = true;
341
            }
342 9
        } elseif ($this->pagination['requested_page'] <= 5) {
343
            // as long as we are below page 5 and below 5 from the end we should show all pages starting from 1
344 9
            $pagesStart = 1;
345 9
            $pagesEnd = 6;
346
347
            // when we have 7 pages, show 7 as end
348 9
            if ($this->pagination['num_pages'] === 7) {
349
                $pagesEnd = 7;
350 9
            } elseif ($this->pagination['num_pages'] <= 6) {
351
                // when we have less then 6 pages, show the maximum page
352 9
                $pagesEnd = $this->pagination['num_pages'];
353
            }
354
355
            // show last pages
356 9
            if ($this->pagination['num_pages'] > 7) {
357 9
                $showLastPages = true;
358
            }
359
        } else {
360
            // page 6
361
            $pagesStart = $this->pagination['requested_page'] - 2;
362
            $pagesEnd = $this->pagination['requested_page'] + 2;
363
            $showFirstPages = true;
364
            $showLastPages = true;
365
        }
366
367
        // show previous
368 9
        if ($this->pagination['requested_page'] > 1) {
369
            // build URL
370
            if ($useQuestionMark) {
371
                $url = $this->pagination['url'] . '?' . $query_parameter . '=' . ($this->pagination['requested_page'] - 1);
372
            } else {
373
                $url = $this->pagination['url'] . '&' . $query_parameter . '=' . ($this->pagination['requested_page'] - 1);
374
            }
375
376
            // set
377
            $pagination['show_previous'] = true;
378
            $pagination['previous_url'] = $url . $anchor;
379
380
            // flip ahead
381
            $this->header->addLink(
382
                [
383
                    'rel' => 'prev',
384
                    'href' => SITE_URL . $url . $anchor,
385
                ]
386
            );
387
        }
388
389
        // show first pages?
390 9
        if ($showFirstPages) {
391
            // init var
392
            $pagesFirstStart = 1;
393
            $pagesFirstEnd = 1;
394
395
            // loop pages
396
            for ($i = $pagesFirstStart; $i <= $pagesFirstEnd; ++$i) {
397
                // build URL
398
                if ($useQuestionMark) {
399
                    $url = $this->pagination['url'] . '?' . $query_parameter . '=' . $i;
400
                } else {
401
                    $url = $this->pagination['url'] . '&' . $query_parameter . '=' . $i;
402
                }
403
404
                // add
405
                $pagination['first'][] = ['url' => $url . $anchor, 'label' => $i];
406
            }
407
        }
408
409
        // build array
410 9
        for ($i = $pagesStart; $i <= $pagesEnd; ++$i) {
411
            // init var
412 9
            $current = ($i === $this->pagination['requested_page']);
413
414
            // build URL
415 9
            if ($useQuestionMark) {
416 8
                $url = $this->pagination['url'] . '?' . $query_parameter . '=' . $i;
417
            } else {
418 1
                $url = $this->pagination['url'] . '&' . $query_parameter . '=' . $i;
419
            }
420
421
            // add
422 9
            $pagination['pages'][] = ['url' => $url . $anchor, 'label' => $i, 'current' => $current];
423
        }
424
425
        // show last pages?
426 9
        if ($showLastPages) {
427
            // init var
428
            $pagesLastStart = $this->pagination['num_pages'];
429
            $pagesLastEnd = $this->pagination['num_pages'];
430
431
            // loop pages
432
            for ($i = $pagesLastStart; $i <= $pagesLastEnd; ++$i) {
433
                // build URL
434
                if ($useQuestionMark) {
435
                    $url = $this->pagination['url'] . '?' . $query_parameter . '=' . $i;
436
                } else {
437
                    $url = $this->pagination['url'] . '&' . $query_parameter . '=' . $i;
438
                }
439
440
                // add
441
                $pagination['last'][] = ['url' => $url . $anchor, 'label' => $i];
442
            }
443
        }
444
445
        // show next
446 9
        if ($this->pagination['requested_page'] < $this->pagination['num_pages']) {
447
            // build URL
448
            if ($useQuestionMark) {
449
                $url = $this->pagination['url'] . '?' . $query_parameter . '=' . ($this->pagination['requested_page'] + 1);
450
            } else {
451
                $url = $this->pagination['url'] . '&' . $query_parameter . '=' . ($this->pagination['requested_page'] + 1);
452
            }
453
454
            // set
455
            $pagination['show_next'] = true;
456
            $pagination['next_url'] = $url . $anchor;
457
458
            // flip ahead
459
            $this->header->addLink(
460
                [
461
                    'rel' => 'next',
462
                    'href' => SITE_URL . $url . $anchor,
463
                ]
464
            );
465
        }
466
467
        // multiple pages
468 9
        $pagination['multiple_pages'] = (int) $pagination['num_pages'] !== 1;
469
470
        // assign pagination
471 9
        $this->template->assign('pagination', $pagination);
472 9
    }
473
474
    /**
475
     * Redirect to a given URL
476
     *
477
     * @param string $url The URL whereto will be redirected.
478
     * @param int $code The redirect code, default is 302 which means this is a temporary redirect.
479
     *
480
     * @throws RedirectException
481
     */
482
    public function redirect(string $url, int $code = RedirectResponse::HTTP_FOUND): void
483
    {
484
        throw new RedirectException('Redirect', new RedirectResponse($url, $code));
485
    }
486
487 26
    private function setAction(string $action)
488
    {
489 26
        $this->action = $action;
490 26
    }
491
492 26
    private function setData(string $data = null): void
493
    {
494
        // data given?
495 26
        if ($data === null) {
496 26
            return;
497
        }
498
499
        $this->data = unserialize($data, ['allowed_classes' => false]);
500
    }
501
502 26
    private function setModule(string $module): void
503
    {
504 26
        $this->module = $module;
505 26
    }
506
507
    /**
508
     * Set overwrite mode
509
     *
510
     * @param bool $overwrite true if the template should overwrite the current template, false if not.
511
     */
512 25
    protected function setOverwrite(bool $overwrite): void
513
    {
514 25
        $this->overwrite = $overwrite;
515 25
    }
516
517
    /**
518
     * Set the path for the template to include or to replace the current one
519
     *
520
     * @param string $path The path to the template that should be loaded.
521
     */
522 25
    protected function setTemplatePath(string $path): void
523
    {
524 25
        $this->templatePath = $path;
525 25
    }
526
527 4
    protected function setMeta(Meta $meta): void
528
    {
529 4
        $this->header->setPageTitle($meta->getTitle(), $meta->isTitleOverwrite());
530 4
        $this->header->addMetaDescription($meta->getDescription(), $meta->isDescriptionOverwrite());
531 4
        $this->header->addMetaKeywords($meta->getKeywords(), $meta->isKeywordsOverwrite());
532 4
533 4
        if ($meta->isCanonicalUrlOverwrite() && !empty($meta->getCanonicalUrl())) {
534
            $this->header->setCanonicalUrl($meta->getCanonicalUrl());
535
        }
536 4
537
        $SEO = [];
538
        if ($meta->hasSEOFollow()) {
539 4
            $SEO[] = $meta->getSEOFollow();
540
        }
541
        if ($meta->hasSEOIndex()) {
542
            $SEO[] = $meta->getSEOIndex();
543
        }
544
        if (!empty($SEO)) {
545 4
            $this->header->addMetaData(
546
                ['name' => 'robots', 'content' => implode(', ', $SEO)],
547
                true
548
            );
549
        }
550
    }
551
552
    /**
553
     * Creates and returns a Form instance from the type of the form.
554
     *
555
     * @param string $type FQCN of the form type class i.e: MyClass::class
556
     * @param mixed $data The initial data for the form
557
     * @param array $options Options for the form
558
     *
559
     * @return Form
560
     */
561
    public function createForm(string $type, $data = null, array $options = []): Form
562
    {
563
        return $this->get('form.factory')->create($type, $data, $options);
564
    }
565
566 4
    /**
567
     * Get the request from the container.
568 4
     *
569
     * @return Request
570
     */
571
    public function getRequest(): Request
572
    {
573
        return Model::getRequest();
574
    }
575
}
576