FileBasedContent   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 882
Duplicated Lines 5.44 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 2.63%

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 6
dl 48
loc 882
ccs 8
cts 304
cp 0.0263
rs 1.718
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 5 1
A setDefaultsFromConfiguration() 0 8 2
A useCache() 0 6 1
A load() 0 19 4
B createIndex() 0 40 5
A isInternalRoute() 0 17 5
A allowInToc() 0 4 1
A createMeta() 0 29 2
A getMetaForRoute() 0 7 2
B createBaseRouteToc() 0 26 7
A createAuthor() 0 24 4
B loadAuthorDetails() 24 24 6
A createCategory() 0 18 3
B loadCategoryDetails() 24 24 6
A getFrontmatter() 0 12 1
A checkForMetaRoute() 0 22 3
A mapRoute2IndexKey() 0 14 5
A mapRoute2Index() 0 13 2
A getView() 0 18 3
A getViews() 0 26 4
A createContentForInternalRoute() 0 33 1
A mapRoute2Content() 0 8 1
A loadFileContentPhaseOne() 0 21 2
B processMainContentPhaseTwo() 0 69 8
A contentForInternalRoute() 0 20 4
A contentForRoute() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FileBasedContent 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 FileBasedContent, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Anax\Content;
4
5
use Anax\Commons\ContainerInjectableInterface;
6
use Anax\Commons\ContainerInjectableTrait;
7
use Anax\Route\Exception\NotFoundException;
8
9
/**
10
 * Pages based on file content.
11
 */
12
class FileBasedContent implements ContainerInjectableInterface
13
{
14
    use ContainerInjectableTrait,
15
        FBCBreadcrumbTrait,
16
        FBCLoadAdditionalContentTrait,
17
        FBCUtilitiesTrait;
18
19
20
21
    /**
22
     * All routes.
23
     */
24
    private $index = null;
25
26
    /**
27
     * All authors.
28
     */
29
    private $author = null;
30
31
    /**
32
     * All categories.
33
     */
34
    private $category = null;
35
36
    /**
37
     * All routes having meta.
38
     */
39
    private $meta = null;
40
41
    /**
42
     * This is the base route.
43
     */
44
    private $baseRoute = null;
45
46
    /**
47
     * This is the extendede meta route, if any.
48
     */
49
    private $metaRoute = null;
50
51
    /**
52
     * This is the current page, to supply pagination, if used.
53
     */
54
    private $currentPage = null;
55
56
    /**
57
     * Use cache or recreate each time.
58
     */
59
    private $ignoreCache = false;
60
    
61
    /**
62
     * File name pattern, all files must match this pattern and the first
63
     * numbered part is optional, the second part becomes the route.
64
     */
65
    private $filenamePattern = "#^(\d*)_*([^\.]+)\.md$#";
66
67
    /**
68
     * Internal routes that is marked as internal content routes and not
69
     * exposed as public routes.
70
     */
71
    private $internalRouteDirPattern = [
72
        "#block/#",
73
    ];
74
75
    private $internalRouteFilePattern = [
76
        "#^block[_-]{1}#",
77
        "#^_#",
78
    ];
79
80
    /**
81
     * Routes that should be used in toc.
82
     */
83
    private $allowedInTocPattern = "([\d]+_(\w)+)";
84
85
86
87
    /**
88
     * Set default values from configuration.
89
     *
90
     * @param array $config the configuration to use.
91
     *
92
     * @return void
93
     */
94 1
    public function configure(array $config) : void
95
    {
96 1
        $this->config = $config;
0 ignored issues
show
Bug introduced by Mikael Roos
The property config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
97 1
        $this->setDefaultsFromConfiguration();
98 1
    }
99
100
101
102
    /**
103
     * Set default values from configuration.
104
     *
105
     * @return this.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type this. could not be parsed: Unknown type name "this." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
106
     */
107 1
    private function setDefaultsFromConfiguration()
108
    {
109 1
        $this->ignoreCache = isset($this->config["ignoreCache"])
110 1
            ? $this->config["ignoreCache"]
111
            : $this->ignoreCache;
112
113 1
        return $this;
114
    }
115
116
117
118
    /**
119
     * Should the cache be used or ignored.
120
     *
121
     * @param boolean $use true to use the cache or false to ignore the cache
122
     *
123
     * @return this.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type this. could not be parsed: Unknown type name "this." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
124
     */
125
    public function useCache($use)
126
    {
127
        $this->ignoreCache = !$use;
128
129
        return $this;
130
    }
131
132
133
134
    /**
135
     * Create the index of all content into an array.
136
     *
137
     * @param string $type of index to load.
138
     *
139
     * @return void.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
140
     */
141
    private function load($type)
142
    {
143
        $index = $this->$type;
144
        if ($index) {
145
            return;
146
        }
147
148
        $cache = $this->di->get("cache");
149
        $key = $cache->createKey(__CLASS__, $type);
150
        $index = $cache->get($key);
151
152
        if (is_null($index) || $this->ignoreCache) {
153
            $createMethod = "create$type";
154
            $index = $this->$createMethod();
155
            $cache->set($key, $index);
156
        }
157
158
        $this->$type = $index;
159
    }
160
161
162
163
164
    // = Create and manage index ==================================
165
166
    /**
167
     * Generate an index from the directory structure.
168
     *
169
     * @return array as index for all content files.
170
     */
171
    private function createIndex()
172
    {
173
        $basepath   = $this->config["basePath"];
174
        $pattern    = $this->config["pattern"];
175
        $path       = "$basepath/$pattern";
176
177
        $index = [];
178
        foreach (glob_recursive($path) as $file) {
179
            $filepath = substr($file, strlen($basepath) + 1);
180
181
            // Find content files
182
            $matches = [];
183
            preg_match($this->filenamePattern, basename($filepath), $matches);
184
            $dirpart = dirname($filepath) . "/";
185
            if ($dirpart === "./") {
186
                $dirpart = null;
187
            }
188
            $key = $dirpart . $matches[2];
189
            
190
            // Create level depending on the file id
191
            // TODO ciamge doc, can be replaced by __toc__ in meta?
192
            $id = (int) $matches[1];
193
            $level = 2;
194
            if ($id % 100 === 0) {
195
                $level = 0;
196
            } elseif ($id % 10 === 0) {
197
                $level = 1;
198
            }
199
200
            $index[$key] = [
201
                "file"     => $filepath,
202
                "section"  => $matches[1],
203
                "level"    => $level,  // TODO ?
204
                "internal" => $this->isInternalRoute($filepath),
205
                "tocable"  => $this->allowInToc($filepath),
206
            ];
207
        }
208
209
        return $index;
210
    }
211
212
213
214
    /**
215
     * Check if a filename is to be marked as an internal route..
216
     *
217
     * @param string $filepath as the basepath (routepart) to the file.
218
     *
219
     * @return boolean true if the route content is internal, else false
220
     */
221
    private function isInternalRoute($filepath)
222
    {
223
        foreach ($this->internalRouteDirPattern as $pattern) {
224
            if (preg_match($pattern, $filepath)) {
225
                return true;
226
            }
227
        }
228
229
        $filename = basename($filepath);
230
        foreach ($this->internalRouteFilePattern as $pattern) {
231
            if (preg_match($pattern, $filename)) {
232
                return true;
233
            }
234
        }
235
236
        return false;
237
    }
238
239
240
241
    /**
242
     * Check if filepath should be used as part of toc.
243
     *
244
     * @param string $filepath as the basepath (routepart) to the file.
245
     *
246
     * @return boolean true if the route content shoul dbe in toc, else false
247
     */
248
    private function allowInToc($filepath)
249
    {
250
        return (boolean) preg_match($this->allowedInTocPattern, $filepath);
251
    }
252
253
254
255
    // = Create and manage meta ==================================
256
257
    /**
258
     * Generate an index for meta files.
259
     *
260
     * @return array as meta index.
261
     */
262
    private function createMeta()
263
    {
264
        $basepath = $this->config["basePath"];
265
        $filter   = $this->config["textfilter-frontmatter"];
266
        $pattern  = $this->config["meta"];
267
        $path     = "$basepath/$pattern";
268
        $textfilter = $this->di->get("textfilter");
269
270
        $index = [];
271
        foreach (glob_recursive($path) as $file) {
272
            // The key entry to index
273
            $key = dirname(substr($file, strlen($basepath) + 1));
274
275
            // Get info from base document
276
            $src = file_get_contents($file);
277
            $filtered = $textfilter->parse($src, $filter);
278
            $index[$key] = $filtered->frontmatter;
279
280
            // Add Toc to the data array
281
            $index[$key]["__toc__"] = $this->createBaseRouteToc($key);
282
        }
283
284
        // Add author details
285
        $this->meta = $index;
286
        $this->createAuthor();
287
        $this->createCategory();
288
289
        return $this->meta;
290
    }
291
292
293
294
    /**
295
     * Get a reference to meta data for specific route.
296
     *
297
     * @param string $route current route used to access page.
298
     *
299
     * @return array as table of content.
300
     */
301
    private function getMetaForRoute($route)
302
    {
303
        $base = dirname($route);
304
        return isset($this->meta[$base])
305
            ? $this->meta[$base]
306
            : [];
307
    }
308
309
310
311
    /**
312
     * Create a table of content for routes at particular level.
313
     *
314
     * @param string $route base route to use.
315
     *
316
     * @return array as the toc.
317
     */
318
    private function createBaseRouteToc($route)
319
    {
320
        $toc = [];
321
        $len = strlen($route);
322
323
        foreach ($this->index as $key => $value) {
324
            if (substr($key, 0, $len + 1) === "$route/") {
325
                if ($value["internal"] === false
326
                    && $value["tocable"] === true) {
327
                    $toc[$key] = $value;
328
                    
329
                    $frontm = $this->getFrontmatter($value["file"]);
330
                    $toc[$key]["title"] = $frontm["title"];
331
                    $toc[$key]["publishTime"] = $this->getPublishTime($frontm);
332
                    $toc[$key]["sectionHeader"] = isset($frontm["sectionHeader"])
333
                        ? $frontm["sectionHeader"]
334
                        : null;
335
                    $toc[$key]["linkable"] = isset($frontm["linkable"])
336
                        ? $frontm["linkable"]
337
                        : null;
338
                }
339
            }
340
        };
341
342
        return $toc;
343
    }
344
345
346
347
    // = Deal with authors ====================================
348
    
349
    /**
350
     * Generate a lookup index for authors that maps into the meta entry
351
     * for the author.
352
     *
353
     * @return void.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
354
     */
355
    private function createAuthor()
356
    {
357
        $pattern = $this->config["author"];
358
359
        $index = [];
360
        $matches = [];
361
        foreach ($this->meta as $key => $entry) {
362
            if (preg_match($pattern, $key, $matches)) {
363
                $acronym = $matches[1];
364
                $index[$acronym] = $key;
365
                $this->meta[$key]["acronym"] = $acronym;
366
                $this->meta[$key]["url"] = $key;
367
                unset($this->meta[$key]["__toc__"]);
368
369
                // Get content for byline
370
                $route = "$key/byline";
371
                $data = $this->getDataForAdditionalRoute($route);
372
                $byline = isset($data["data"]["content"]) ? $data["data"]["content"] : null;
373
                $this->meta[$key]["byline"] = $byline;
374
            }
375
        }
376
377
        return $index;
378
    }
379
380
381
382
    /**
383
     * Load details for the author.
384
     *
385
     * @param array|string $author with details on the author(s).
386
     *
387
     * @return array with more details on the authors(s).
388
     */
389 View Code Duplication
    private function loadAuthorDetails($author)
0 ignored issues
show
Duplication introduced by Mikael Roos
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
390
    {
391
        if (is_array($author) && is_array(array_values($author)[0])) {
392
            return $author;
393
        }
394
395
        if (!is_array($author)) {
396
            $tmp = $author;
397
            $author = [];
398
            $author[] = $tmp;
399
        }
400
401
        $authors = [];
402
        foreach ($author as $acronym) {
403
            if (isset($this->author[$acronym])) {
404
                $key = $this->author[$acronym];
405
                $authors[$acronym] = $this->meta[$key];
406
            } else {
407
                $authors[$acronym]["acronym"] = $acronym;
408
            }
409
        }
410
411
        return $authors;
412
    }
413
414
415
416
    // = Deal with categories ====================================
417
    
418
    /**
419
     * Generate a lookup index for categories that maps into the meta entry
420
     * for the category.
421
     *
422
     * @return void.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
423
     */
424
    private function createCategory()
425
    {
426
        $pattern = $this->config["category"];
427
428
        $index = [];
429
        $matches = [];
430
        foreach ($this->meta as $key => $entry) {
431
            if (preg_match($pattern, $key, $matches)) {
432
                $catKey = $matches[1];
433
                $index[$catKey] = $key;
434
                $this->meta[$key]["key"] = $catKey;
435
                $this->meta[$key]["url"] = $key;
436
                unset($this->meta[$key]["__toc__"]);
437
            }
438
        }
439
440
        return $index;
441
    }
442
443
444
445
    /**
446
     * Find next and previous links of current content.
447
     *
448
     * @param array|string $author with details on the category(s).
0 ignored issues
show
Bug introduced by Mikael Roos
There is no parameter named $author. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
449
     *
450
     * @return array with more details on the category(s).
451
     */
452 View Code Duplication
    private function loadCategoryDetails($category)
0 ignored issues
show
Duplication introduced by Mikael Roos
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
453
    {
454
        if (is_array($category) && is_array(array_values($category)[0])) {
455
            return $category;
456
        }
457
458
        if (!is_array($category)) {
459
            $tmp = $category;
460
            $category = [];
461
            $category[] = $tmp;
462
        }
463
464
        $categorys = [];
465
        foreach ($category as $catKey) {
466
            if (isset($this->category[$catKey])) {
467
                $key = $this->category[$catKey];
468
                $categorys[$catKey] = $this->meta[$key];
469
            } else {
470
                $categorys[$catKey]["key"] = $catKey;
471
            }
472
        }
473
474
        return $categorys;
475
    }
476
477
478
479
480
    // == Used by meta and breadcrumb (to get title) ===========================
481
    // TODO REFACTOR THIS?
482
    // Support getting only frontmatter.
483
    // Merge with function that retrieves whole filtered since getting
484
    // frontmatter will involve full parsing of document.
485
    // Title is retrieved from the HTML code.
486
    // Also do cacheing of each retrieved and parsed document
487
    // in this cycle, to gather code that loads and parses a individual
488
    // document.
489
    
490
    /**
491
     * Get the frontmatter of a document.
492
     *
493
     * @param string $file to get frontmatter from.
494
     *
495
     * @return array as frontmatter.
496
     */
497
    private function getFrontmatter($file)
498
    {
499
        $basepath = $this->config["basePath"];
500
        $filter1  = $this->config["textfilter-frontmatter"];
501
        $filter2  = $this->config["textfilter-title"];
502
        $filter = array_merge($filter1, $filter2);
503
        
504
        $path = $basepath . "/" . $file;
505
        $src = file_get_contents($path);
506
        $filtered = $this->di->get("textfilter")->parse($src, $filter);
507
        return $filtered->frontmatter;
508
    }
509
510
511
512
    // == Look up route in index ===================================
513
    
514
    /**
515
     * Check if currrent route is a supported meta route.
516
     *
517
     * @param string $route current route used to access page.
518
     *
519
     * @return string as route.
520
     */
521
    private function checkForMetaRoute($route)
522
    {
523
        $this->baseRoute = $route;
524
        $this->metaRoute = null;
525
526
        // If route exits in index, use it
527
        if ($this->mapRoute2IndexKey($route)) {
528
            return $route;
529
        }
530
531
        // Check for pagination
532
        $pagination = $this->config["pagination"];
533
        $matches = [];
534
        $pattern = "/(.*?)\/($pagination)\/(\d+)$/";
535
        if (preg_match($pattern, $route, $matches)) {
536
            $this->baseRoute = $matches[1];
537
            $this->metaRoute = $route;
538
            $this->currentPage = $matches[3];
539
        }
540
541
        return $this->baseRoute;
542
    }
543
544
545
546
    /**
547
     * Map the route to the correct key in the index.
548
     *
549
     * @param string $route current route used to access page.
550
     *
551
     * @return string as key or false if no match.
552
     */
553
    private function mapRoute2IndexKey($route)
554
    {
555
        $route = rtrim($route, "/");
556
557
        if (key_exists($route, $this->index)) {
558
            return $route;
559
        } elseif (empty($route) && key_exists("index", $this->index)) {
560
            return "index";
561
        } elseif (key_exists($route . "/index", $this->index)) {
562
            return "$route/index";
563
        }
564
565
        return false;
0 ignored issues
show
Bug Best Practice introduced by Mikael Roos
The return type of return false; (false) is incompatible with the return type documented by Anax\Content\FileBasedContent::mapRoute2IndexKey of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
566
    }
567
568
569
570
    /**
571
     * Map the route to the correct entry in the index.
572
     *
573
     * @param string $route current route used to access page.
574
     *
575
     * @return array as the matched route.
576
     */
577
    private function mapRoute2Index($route)
578
    {
579
        $routeIndex = $this->mapRoute2IndexKey($route);
580
581
        if ($routeIndex) {
582
            return [$routeIndex, $this->index[$routeIndex]];
583
        }
584
585
        $msg = t("The route '!ROUTE' does not exists in the index.", [
586
            "!ROUTE" => $route
587
        ]);
588
        throw new NotFoundException($msg);
589
    }
590
591
592
593
    // = Get view data by merging from meta and current frontmatter =========
594
    
595
    /**
596
     * Get view by mergin information from meta and frontmatter.
597
     *
598
     * @param string $route       current route used to access page.
599
     * @param array  $frontmatter for the content.
600
     * @param string $key         for the view to retrive.
601
     *
602
     * @return array with data to add as view.
603
     */
604
    private function getView($route, $frontmatter, $key)
605
    {
606
        $view = [];
607
608
        // From meta frontmatter
609
        $meta = $this->getMetaForRoute($route);
610
        if (isset($meta[$key])) {
611
            $view = $meta[$key];
612
        }
613
614
        // From document frontmatter
615
        if (isset($frontmatter[$key])) {
616
            $view = array_merge_recursive_distinct($view, $frontmatter[$key]);
617
            //$view = array_merge($view, $frontmatter[$key]);
618
        }
619
620
        return $view;
621
    }
622
623
624
625
    /**
626
     * Get details on extra views.
627
     *
628
     * @param string $route       current route used to access page.
629
     * @param array  $frontmatter for the content.
630
     *
631
     * @return array with page data to send to view.
632
     */
633
    private function getViews($route, $frontmatter)
634
    {
635
        // Arrange data into views
636
        $views = $this->getView($route, $frontmatter, "views", true);
0 ignored issues
show
Unused Code introduced by Mikael Roos
The call to FileBasedContent::getView() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
637
638
        // Set defaults
639
        if (!isset($views["main"]["template"])) {
640
            $views["main"]["template"] = $this->config["template"];
641
        }
642
        if (!isset($views["main"]["data"])) {
643
            $views["main"]["data"] = [];
644
        }
645
646
        // Merge remaining frontmatter into view main data.
647
        $data = $this->getMetaForRoute($route);
648
        unset($data["__toc__"]);
649
        unset($data["views"]);
650
        unset($frontmatter["views"]);
651
652
        if ($frontmatter) {
0 ignored issues
show
Bug Best Practice introduced by Mikael Roos
The expression $frontmatter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
653
            $data = array_merge_recursive_distinct($data, $frontmatter);
654
        }
655
        $views["main"]["data"] = array_merge_recursive_distinct($views["main"]["data"], $data);
656
657
        return $views;
658
    }
659
660
661
662
    // == Create and load content ===================================
663
664
    /**
665
     * Map url to content, even internal content, if such mapping can be done.
666
     *
667
     * @param string $route route to look up.
668
     *
669
     * @return object with content and filtered version.
670
     */
671
    private function createContentForInternalRoute($route)
672
    {
673
        // Load index and map route to content
674
        $this->load("index");
675
        $this->load("meta");
676
        $this->load("author");
677
        $this->load("category");
678
        
679
        // Match the route
680
        $route = rtrim($route, "/");
681
        $route = $this->checkForMetaRoute($route);
682
        list($routeIndex, $content, $filtered) = $this->mapRoute2Content($route);
683
684
        // Create and arrange the content as views, merge with .meta,
685
        // frontmatter is complete.
686
        $content["views"] = $this->getViews($routeIndex, $filtered->frontmatter);
687
688
        // Do process content step two when all frontmatter is included.
689
        $this->processMainContentPhaseTwo($content, $filtered);
690
        
691
        // Set details of content
692
        $content["views"]["main"]["data"]["content"] = $filtered->text;
693
        $content["views"]["main"]["data"]["excerpt"] = $filtered->excerpt;
694
        $this->loadAdditionalContent($content["views"], $route, $routeIndex);
695
696
        // TODO Should not supply all frontmatter to theme, only the
697
        // parts valid to the index template. Separate that data into own
698
        // holder in frontmatter. Do not include whole frontmatter? Only
699
        // on debg?
700
        $content["frontmatter"] = $filtered->frontmatter;
701
702
        return (object) $content;
703
    }
704
705
706
707
    /**
708
     * Look up the route in the index and use that to retrieve the filtered
709
     * content.
710
     *
711
     * @param string $route to look up.
712
     *
713
     * @return array with content and filtered version.
714
     */
715
    private function mapRoute2Content($route)
716
    {
717
        // Look it up in the index
718
        list($keyIndex, $content) = $this->mapRoute2Index($route);
719
        $filtered = $this->loadFileContentPhaseOne($keyIndex);
720
721
        return [$keyIndex, $content, $filtered];
722
    }
723
724
725
726
    /**
727
     * Load content file and frontmatter, this is the first time we process
728
     * the content.
729
     *
730
     * @param string $key     to index with details on the route.
731
     *
732
     * @throws NotFoundException when mapping can not be done.
733
     *
734
     * @return void.
0 ignored issues
show
Documentation introduced by Mikael Roos
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
735
     */
736
    private function loadFileContentPhaseOne($key)
737
    {
738
        // Settings from config
739
        $basepath = $this->config["basePath"];
740
        $filter   = $this->config["textfilter-frontmatter"];
741
742
        // Whole path to file
743
        $path = $basepath . "/" . $this->index[$key]["file"];
744
745
        // Load content from file
746
        if (!is_file($path)) {
747
            $msg = t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]);
748
            throw new \Anax\Exception\NotFoundException($msg);
749
        }
750
751
        // Get filtered content
752
        $src = file_get_contents($path);
753
        $filtered = $this->di->get("textfilter")->parse($src, $filter);
754
755
        return $filtered;
756
    }
757
758
759
760
    // == Process content phase 2 ===================================
761
    // TODO REFACTOR THIS?
762
    
763
    /**
764
     * Look up the route in the index and use that to retrieve the filtered
765
     * content.
766
     *
767
     * @param array  &$content   to process.
768
     * @param object &$filtered to use for settings.
769
     *
770
     * @return array with content and filtered version.
771
     */
772
    private function processMainContentPhaseTwo(&$content, &$filtered)
773
    {
774
        // From configuration
775
        $filter = $this->config["textfilter"];
776
        $revisionStart = $this->config["revision-history"]["start"];
777
        $revisionEnd   = $this->config["revision-history"]["end"];
778
        $revisionClass = $this->config["revision-history"]["class"];
779
        $revisionSource = isset($this->config["revision-history"]["source"])
780
            ? $this->config["revision-history"]["source"]
781
            : null;
782
783
        $textFilter = $this->di->get("textfilter");
784
        $text = $filtered->text;
785
786
        // Check if revision history is to be included
787
        if (isset($content["views"]["main"]["data"]["revision"])) {
788
            $text = $textFilter->addRevisionHistory(
789
                $text,
790
                $content["views"]["main"]["data"]["revision"],
791
                $revisionStart,
792
                $revisionEnd,
793
                $revisionClass,
794
                $revisionSource . "/" . $content["file"]
795
            );
796
        }
797
798
        // Get new filtered content (and updated frontmatter)
799
        // Title in frontmatter overwrites title found in content
800
        $new = $textFilter->parse($text, $filter);
801
        $filtered->text = $new->text;
802
         
803
        // Keep title if defined in frontmatter
804
        $title = isset($filtered->frontmatter["title"])
805
          ? $filtered->frontmatter["title"]
806
          : null;
807
808
        $filtered->frontmatter = array_merge_recursive_distinct(
809
            $filtered->frontmatter,
810
            $new->frontmatter
811
        );
812
813
        if ($title) {
814
            $filtered->frontmatter["title"] = $title;
815
        }
816
817
        // Main data is
818
        $data = &$content["views"]["main"]["data"];
819
820
        // Update all anchor urls to use baseurl, needs info about baseurl
821
        // from merged frontmatter
822
        $baseurl = isset($data["baseurl"])
823
          ? $data["baseurl"]
824
          : null;
825
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
826
        $this->addBaseurl2ImageSource($filtered, $baseurl);
827
828
        // Add excerpt and hasMore, if available
829
        $textFilter->addExcerpt($filtered);
830
831
        // Load details on author, if set.
832
        if (isset($data["author"])) {
833
            $data["author"] = $this->loadAuthorDetails($data["author"]);
834
        }
835
836
        // Load details on category, if set.
837
        if (isset($data["category"])) {
838
            $data["category"] = $this->loadCategoryDetails($data["category"]);
839
        }
840
    }
841
842
843
844
    // == Public methods ============================================
845
    
846
    /**
847
     * Map url to content, even internal content, if such mapping can be done.
848
     *
849
     * @param string $route optional route to look up.
850
     *
851
     * @return object with content and filtered version.
852
     */
853
    public function contentForInternalRoute($route = null)
854
    {
855
        // Get the route
856
        if (is_null($route)) {
857
            $route = $this->di->get("request")->getRoute();
858
        }
859
860
        // Check cache for content or create cached version of content
861
        $cache = $this->di->get("cache");
862
        $slug = $this->di->get("url")->slugify($route);
863
        $key = $cache->createKey(__CLASS__, "route-$slug");
864
        $content = $cache->get($key);
865
866
        if (!$content || $this->ignoreCache) {
867
            $content = $this->createContentForInternalRoute($route);
868
            $cache->set($key, $content);
869
        }
870
871
        return $content;
872
    }
873
874
875
876
    /**
877
     * Map url to content if such mapping can be done, exclude internal routes.
878
     *
879
     * @param string $route optional route to look up.
880
     *
881
     * @return object with content and filtered version.
882
     */
883
    public function contentForRoute($route = null)
884
    {
885
        $content = $this->contentForInternalRoute($route);
886
        if ($content->internal === true) {
887
            $msg = t("The content '!ROUTE' does not exists as a public route.", ["!ROUTE" => $route]);
888
            throw new NotFoundException($msg);
889
        }
890
891
        return $content;
892
    }
893
}
894