Completed
Push — master ( 1a117b...43f8bd )
by Mikael
11:04
created

FileBasedContent   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 882
Duplicated Lines 5.44 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 2.63%

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 5
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
8
/**
9
 * Pages based on file content.
10
 */
11
class FileBasedContent implements ContainerInjectableInterface
12
{
13
    use ContainerInjectableTrait,
14
        FBCBreadcrumbTrait,
15
        FBCLoadAdditionalContentTrait,
16
        FBCUtilitiesTrait;
17
18
19
20
    /**
21
     * All routes.
22
     */
23
    private $index = null;
24
25
    /**
26
     * All authors.
27
     */
28
    private $author = null;
29
30
    /**
31
     * All categories.
32
     */
33
    private $category = null;
34
35
    /**
36
     * All routes having meta.
37
     */
38
    private $meta = null;
39
40
    /**
41
     * This is the base route.
42
     */
43
    private $baseRoute = null;
44
45
    /**
46
     * This is the extendede meta route, if any.
47
     */
48
    private $metaRoute = null;
49
50
    /**
51
     * This is the current page, to supply pagination, if used.
52
     */
53
    private $currentPage = null;
54
55
    /**
56
     * Use cache or recreate each time.
57
     */
58
    private $ignoreCache = false;
59
    
60
    /**
61
     * File name pattern, all files must match this pattern and the first
62
     * numbered part is optional, the second part becomes the route.
63
     */
64
    private $filenamePattern = "#^(\d*)_*([^\.]+)\.md$#";
65
66
    /**
67
     * Internal routes that is marked as internal content routes and not
68
     * exposed as public routes.
69
     */
70
    private $internalRouteDirPattern = [
71
        "#block/#",
72
    ];
73
74
    private $internalRouteFilePattern = [
75
        "#^block[_-]{1}#",
76
        "#^_#",
77
    ];
78
79
    /**
80
     * Routes that should be used in toc.
81
     */
82
    private $allowedInTocPattern = "([\d]+_(\w)+)";
83
84
85
86
    /**
87
     * Set default values from configuration.
88
     *
89
     * @param array $config the configuration to use.
90
     *
91
     * @return void
92
     */
93 1
    public function configure(array $config) : void
94
    {
95 1
        $this->config = $config;
0 ignored issues
show
Bug introduced by
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...
96 1
        $this->setDefaultsFromConfiguration();
97 1
    }
98
99
100
101
    /**
102
     * Set default values from configuration.
103
     *
104
     * @return this.
0 ignored issues
show
Documentation introduced by
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...
105
     */
106 1
    private function setDefaultsFromConfiguration()
107
    {
108 1
        $this->ignoreCache = isset($this->config["ignoreCache"])
109 1
            ? $this->config["ignoreCache"]
110
            : $this->ignoreCache;
111
112 1
        return $this;
113
    }
114
115
116
117
    /**
118
     * Should the cache be used or ignored.
119
     *
120
     * @param boolean $use true to use the cache or false to ignore the cache
121
     *
122
     * @return this.
0 ignored issues
show
Documentation introduced by
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...
123
     */
124
    public function useCache($use)
125
    {
126
        $this->ignoreCache = !$use;
127
128
        return $this;
129
    }
130
131
132
133
    /**
134
     * Create the index of all content into an array.
135
     *
136
     * @param string $type of index to load.
137
     *
138
     * @return void.
0 ignored issues
show
Documentation introduced by
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...
139
     */
140
    private function load($type)
141
    {
142
        $index = $this->$type;
143
        if ($index) {
144
            return;
145
        }
146
147
        $cache = $this->di->get("cache");
148
        $key = $cache->createKey(__CLASS__, $type);
149
        $index = $cache->get($key);
150
151
        if (is_null($index) || $this->ignoreCache) {
152
            $createMethod = "create$type";
153
            $index = $this->$createMethod();
154
            $cache->set($key, $index);
155
        }
156
157
        $this->$type = $index;
158
    }
159
160
161
162
163
    // = Create and manage index ==================================
164
165
    /**
166
     * Generate an index from the directory structure.
167
     *
168
     * @return array as index for all content files.
169
     */
170
    private function createIndex()
171
    {
172
        $basepath   = $this->config["basePath"];
173
        $pattern    = $this->config["pattern"];
174
        $path       = "$basepath/$pattern";
175
176
        $index = [];
177
        foreach (glob_recursive($path) as $file) {
178
            $filepath = substr($file, strlen($basepath) + 1);
179
180
            // Find content files
181
            $matches = [];
182
            preg_match($this->filenamePattern, basename($filepath), $matches);
183
            $dirpart = dirname($filepath) . "/";
184
            if ($dirpart === "./") {
185
                $dirpart = null;
186
            }
187
            $key = $dirpart . $matches[2];
188
            
189
            // Create level depending on the file id
190
            // TODO ciamge doc, can be replaced by __toc__ in meta?
191
            $id = (int) $matches[1];
192
            $level = 2;
193
            if ($id % 100 === 0) {
194
                $level = 0;
195
            } elseif ($id % 10 === 0) {
196
                $level = 1;
197
            }
198
199
            $index[$key] = [
200
                "file"     => $filepath,
201
                "section"  => $matches[1],
202
                "level"    => $level,  // TODO ?
203
                "internal" => $this->isInternalRoute($filepath),
204
                "tocable"  => $this->allowInToc($filepath),
205
            ];
206
        }
207
208
        return $index;
209
    }
210
211
212
213
    /**
214
     * Check if a filename is to be marked as an internal route..
215
     *
216
     * @param string $filepath as the basepath (routepart) to the file.
217
     *
218
     * @return boolean true if the route content is internal, else false
219
     */
220
    private function isInternalRoute($filepath)
221
    {
222
        foreach ($this->internalRouteDirPattern as $pattern) {
223
            if (preg_match($pattern, $filepath)) {
224
                return true;
225
            }
226
        }
227
228
        $filename = basename($filepath);
229
        foreach ($this->internalRouteFilePattern as $pattern) {
230
            if (preg_match($pattern, $filename)) {
231
                return true;
232
            }
233
        }
234
235
        return false;
236
    }
237
238
239
240
    /**
241
     * Check if filepath should be used as part of toc.
242
     *
243
     * @param string $filepath as the basepath (routepart) to the file.
244
     *
245
     * @return boolean true if the route content shoul dbe in toc, else false
246
     */
247
    private function allowInToc($filepath)
248
    {
249
        return (boolean) preg_match($this->allowedInTocPattern, $filepath);
250
    }
251
252
253
254
    // = Create and manage meta ==================================
255
256
    /**
257
     * Generate an index for meta files.
258
     *
259
     * @return array as meta index.
260
     */
261
    private function createMeta()
262
    {
263
        $basepath = $this->config["basePath"];
264
        $filter   = $this->config["textfilter-frontmatter"];
265
        $pattern  = $this->config["meta"];
266
        $path     = "$basepath/$pattern";
267
        $textfilter = $this->di->get("textfilter");
268
269
        $index = [];
270
        foreach (glob_recursive($path) as $file) {
271
            // The key entry to index
272
            $key = dirname(substr($file, strlen($basepath) + 1));
273
274
            // Get info from base document
275
            $src = file_get_contents($file);
276
            $filtered = $textfilter->parse($src, $filter);
277
            $index[$key] = $filtered->frontmatter;
278
279
            // Add Toc to the data array
280
            $index[$key]["__toc__"] = $this->createBaseRouteToc($key);
281
        }
282
283
        // Add author details
284
        $this->meta = $index;
285
        $this->createAuthor();
286
        $this->createCategory();
287
288
        return $this->meta;
289
    }
290
291
292
293
    /**
294
     * Get a reference to meta data for specific route.
295
     *
296
     * @param string $route current route used to access page.
297
     *
298
     * @return array as table of content.
299
     */
300
    private function getMetaForRoute($route)
301
    {
302
        $base = dirname($route);
303
        return isset($this->meta[$base])
304
            ? $this->meta[$base]
305
            : [];
306
    }
307
308
309
310
    /**
311
     * Create a table of content for routes at particular level.
312
     *
313
     * @param string $route base route to use.
314
     *
315
     * @return array as the toc.
316
     */
317
    private function createBaseRouteToc($route)
318
    {
319
        $toc = [];
320
        $len = strlen($route);
321
322
        foreach ($this->index as $key => $value) {
323
            if (substr($key, 0, $len + 1) === "$route/") {
324
                if ($value["internal"] === false
325
                    && $value["tocable"] === true) {
326
                    $toc[$key] = $value;
327
                    
328
                    $frontm = $this->getFrontmatter($value["file"]);
329
                    $toc[$key]["title"] = $frontm["title"];
330
                    $toc[$key]["publishTime"] = $this->getPublishTime($frontm);
331
                    $toc[$key]["sectionHeader"] = isset($frontm["sectionHeader"])
332
                        ? $frontm["sectionHeader"]
333
                        : null;
334
                    $toc[$key]["linkable"] = isset($frontm["linkable"])
335
                        ? $frontm["linkable"]
336
                        : null;
337
                }
338
            }
339
        };
340
341
        return $toc;
342
    }
343
344
345
346
    // = Deal with authors ====================================
347
    
348
    /**
349
     * Generate a lookup index for authors that maps into the meta entry
350
     * for the author.
351
     *
352
     * @return void.
0 ignored issues
show
Documentation introduced by
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...
353
     */
354
    private function createAuthor()
355
    {
356
        $pattern = $this->config["author"];
357
358
        $index = [];
359
        $matches = [];
360
        foreach ($this->meta as $key => $entry) {
361
            if (preg_match($pattern, $key, $matches)) {
362
                $acronym = $matches[1];
363
                $index[$acronym] = $key;
364
                $this->meta[$key]["acronym"] = $acronym;
365
                $this->meta[$key]["url"] = $key;
366
                unset($this->meta[$key]["__toc__"]);
367
368
                // Get content for byline
369
                $route = "$key/byline";
370
                $data = $this->getDataForAdditionalRoute($route);
371
                $byline = isset($data["data"]["content"]) ? $data["data"]["content"] : null;
372
                $this->meta[$key]["byline"] = $byline;
373
            }
374
        }
375
376
        return $index;
377
    }
378
379
380
381
    /**
382
     * Load details for the author.
383
     *
384
     * @param array|string $author with details on the author(s).
385
     *
386
     * @return array with more details on the authors(s).
387
     */
388 View Code Duplication
    private function loadAuthorDetails($author)
0 ignored issues
show
Duplication introduced by
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...
389
    {
390
        if (is_array($author) && is_array(array_values($author)[0])) {
391
            return $author;
392
        }
393
394
        if (!is_array($author)) {
395
            $tmp = $author;
396
            $author = [];
397
            $author[] = $tmp;
398
        }
399
400
        $authors = [];
401
        foreach ($author as $acronym) {
402
            if (isset($this->author[$acronym])) {
403
                $key = $this->author[$acronym];
404
                $authors[$acronym] = $this->meta[$key];
405
            } else {
406
                $authors[$acronym]["acronym"] = $acronym;
407
            }
408
        }
409
410
        return $authors;
411
    }
412
413
414
415
    // = Deal with categories ====================================
416
    
417
    /**
418
     * Generate a lookup index for categories that maps into the meta entry
419
     * for the category.
420
     *
421
     * @return void.
0 ignored issues
show
Documentation introduced by
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...
422
     */
423
    private function createCategory()
424
    {
425
        $pattern = $this->config["category"];
426
427
        $index = [];
428
        $matches = [];
429
        foreach ($this->meta as $key => $entry) {
430
            if (preg_match($pattern, $key, $matches)) {
431
                $catKey = $matches[1];
432
                $index[$catKey] = $key;
433
                $this->meta[$key]["key"] = $catKey;
434
                $this->meta[$key]["url"] = $key;
435
                unset($this->meta[$key]["__toc__"]);
436
            }
437
        }
438
439
        return $index;
440
    }
441
442
443
444
    /**
445
     * Find next and previous links of current content.
446
     *
447
     * @param array|string $author with details on the category(s).
0 ignored issues
show
Bug introduced by
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...
448
     *
449
     * @return array with more details on the category(s).
450
     */
451 View Code Duplication
    private function loadCategoryDetails($category)
0 ignored issues
show
Duplication introduced by
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...
452
    {
453
        if (is_array($category) && is_array(array_values($category)[0])) {
454
            return $category;
455
        }
456
457
        if (!is_array($category)) {
458
            $tmp = $category;
459
            $category = [];
460
            $category[] = $tmp;
461
        }
462
463
        $categorys = [];
464
        foreach ($category as $catKey) {
465
            if (isset($this->category[$catKey])) {
466
                $key = $this->category[$catKey];
467
                $categorys[$catKey] = $this->meta[$key];
468
            } else {
469
                $categorys[$catKey]["key"] = $catKey;
470
            }
471
        }
472
473
        return $categorys;
474
    }
475
476
477
478
479
    // == Used by meta and breadcrumb (to get title) ===========================
480
    // TODO REFACTOR THIS?
481
    // Support getting only frontmatter.
482
    // Merge with function that retrieves whole filtered since getting
483
    // frontmatter will involve full parsing of document.
484
    // Title is retrieved from the HTML code.
485
    // Also do cacheing of each retrieved and parsed document
486
    // in this cycle, to gather code that loads and parses a individual
487
    // document.
488
    
489
    /**
490
     * Get the frontmatter of a document.
491
     *
492
     * @param string $file to get frontmatter from.
493
     *
494
     * @return array as frontmatter.
495
     */
496
    private function getFrontmatter($file)
497
    {
498
        $basepath = $this->config["basePath"];
499
        $filter1  = $this->config["textfilter-frontmatter"];
500
        $filter2  = $this->config["textfilter-title"];
501
        $filter = array_merge($filter1, $filter2);
502
        
503
        $path = $basepath . "/" . $file;
504
        $src = file_get_contents($path);
505
        $filtered = $this->di->get("textfilter")->parse($src, $filter);
506
        return $filtered->frontmatter;
507
    }
508
509
510
511
    // == Look up route in index ===================================
512
    
513
    /**
514
     * Check if currrent route is a supported meta route.
515
     *
516
     * @param string $route current route used to access page.
517
     *
518
     * @return string as route.
519
     */
520
    private function checkForMetaRoute($route)
521
    {
522
        $this->baseRoute = $route;
523
        $this->metaRoute = null;
524
525
        // If route exits in index, use it
526
        if ($this->mapRoute2IndexKey($route)) {
527
            return $route;
528
        }
529
530
        // Check for pagination
531
        $pagination = $this->config["pagination"];
532
        $matches = [];
533
        $pattern = "/(.*?)\/($pagination)\/(\d+)$/";
534
        if (preg_match($pattern, $route, $matches)) {
535
            $this->baseRoute = $matches[1];
536
            $this->metaRoute = $route;
537
            $this->currentPage = $matches[3];
538
        }
539
540
        return $this->baseRoute;
541
    }
542
543
544
545
    /**
546
     * Map the route to the correct key in the index.
547
     *
548
     * @param string $route current route used to access page.
549
     *
550
     * @return string as key or false if no match.
551
     */
552
    private function mapRoute2IndexKey($route)
553
    {
554
        $route = rtrim($route, "/");
555
556
        if (key_exists($route, $this->index)) {
557
            return $route;
558
        } elseif (empty($route) && key_exists("index", $this->index)) {
559
            return "index";
560
        } elseif (key_exists($route . "/index", $this->index)) {
561
            return "$route/index";
562
        }
563
564
        return false;
0 ignored issues
show
Bug Best Practice introduced by
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...
565
    }
566
567
568
569
    /**
570
     * Map the route to the correct entry in the index.
571
     *
572
     * @param string $route current route used to access page.
573
     *
574
     * @return array as the matched route.
575
     */
576
    private function mapRoute2Index($route)
577
    {
578
        $routeIndex = $this->mapRoute2IndexKey($route);
579
580
        if ($routeIndex) {
581
            return [$routeIndex, $this->index[$routeIndex]];
582
        }
583
584
        $msg = t("The route '!ROUTE' does not exists in the index.", [
585
            "!ROUTE" => $route
586
        ]);
587
        throw new \Anax\Exception\NotFoundException($msg);
588
    }
589
590
591
592
    // = Get view data by merging from meta and current frontmatter =========
593
    
594
    /**
595
     * Get view by mergin information from meta and frontmatter.
596
     *
597
     * @param string $route       current route used to access page.
598
     * @param array  $frontmatter for the content.
599
     * @param string $key         for the view to retrive.
600
     *
601
     * @return array with data to add as view.
602
     */
603
    private function getView($route, $frontmatter, $key)
604
    {
605
        $view = [];
606
607
        // From meta frontmatter
608
        $meta = $this->getMetaForRoute($route);
609
        if (isset($meta[$key])) {
610
            $view = $meta[$key];
611
        }
612
613
        // From document frontmatter
614
        if (isset($frontmatter[$key])) {
615
            $view = array_merge_recursive_distinct($view, $frontmatter[$key]);
616
            //$view = array_merge($view, $frontmatter[$key]);
617
        }
618
619
        return $view;
620
    }
621
622
623
624
    /**
625
     * Get details on extra views.
626
     *
627
     * @param string $route       current route used to access page.
628
     * @param array  $frontmatter for the content.
629
     *
630
     * @return array with page data to send to view.
631
     */
632
    private function getViews($route, $frontmatter)
633
    {
634
        // Arrange data into views
635
        $views = $this->getView($route, $frontmatter, "views", true);
0 ignored issues
show
Unused Code introduced by
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...
636
637
        // Set defaults
638
        if (!isset($views["main"]["template"])) {
639
            $views["main"]["template"] = $this->config["template"];
640
        }
641
        if (!isset($views["main"]["data"])) {
642
            $views["main"]["data"] = [];
643
        }
644
645
        // Merge remaining frontmatter into view main data.
646
        $data = $this->getMetaForRoute($route);
647
        unset($data["__toc__"]);
648
        unset($data["views"]);
649
        unset($frontmatter["views"]);
650
651
        if ($frontmatter) {
0 ignored issues
show
Bug Best Practice introduced by
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...
652
            $data = array_merge_recursive_distinct($data, $frontmatter);
653
        }
654
        $views["main"]["data"] = array_merge_recursive_distinct($views["main"]["data"], $data);
655
656
        return $views;
657
    }
658
659
660
661
    // == Create and load content ===================================
662
663
    /**
664
     * Map url to content, even internal content, if such mapping can be done.
665
     *
666
     * @param string $route route to look up.
667
     *
668
     * @return object with content and filtered version.
669
     */
670
    private function createContentForInternalRoute($route)
671
    {
672
        // Load index and map route to content
673
        $this->load("index");
674
        $this->load("meta");
675
        $this->load("author");
676
        $this->load("category");
677
        
678
        // Match the route
679
        $route = rtrim($route, "/");
680
        $route = $this->checkForMetaRoute($route);
681
        list($routeIndex, $content, $filtered) = $this->mapRoute2Content($route);
682
683
        // Create and arrange the content as views, merge with .meta,
684
        // frontmatter is complete.
685
        $content["views"] = $this->getViews($routeIndex, $filtered->frontmatter);
686
687
        // Do process content step two when all frontmatter is included.
688
        $this->processMainContentPhaseTwo($content, $filtered);
689
        
690
        // Set details of content
691
        $content["views"]["main"]["data"]["content"] = $filtered->text;
692
        $content["views"]["main"]["data"]["excerpt"] = $filtered->excerpt;
693
        $this->loadAdditionalContent($content["views"], $route, $routeIndex);
694
695
        // TODO Should not supply all frontmatter to theme, only the
696
        // parts valid to the index template. Separate that data into own
697
        // holder in frontmatter. Do not include whole frontmatter? Only
698
        // on debg?
699
        $content["frontmatter"] = $filtered->frontmatter;
700
701
        return (object) $content;
702
    }
703
704
705
706
    /**
707
     * Look up the route in the index and use that to retrieve the filtered
708
     * content.
709
     *
710
     * @param string $route to look up.
711
     *
712
     * @return array with content and filtered version.
713
     */
714
    private function mapRoute2Content($route)
715
    {
716
        // Look it up in the index
717
        list($keyIndex, $content) = $this->mapRoute2Index($route);
718
        $filtered = $this->loadFileContentPhaseOne($keyIndex);
719
720
        return [$keyIndex, $content, $filtered];
721
    }
722
723
724
725
    /**
726
     * Load content file and frontmatter, this is the first time we process
727
     * the content.
728
     *
729
     * @param string $key     to index with details on the route.
730
     *
731
     * @throws NotFoundException when mapping can not be done.
732
     *
733
     * @return void.
0 ignored issues
show
Documentation introduced by
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...
734
     */
735
    private function loadFileContentPhaseOne($key)
736
    {
737
        // Settings from config
738
        $basepath = $this->config["basePath"];
739
        $filter   = $this->config["textfilter-frontmatter"];
740
741
        // Whole path to file
742
        $path = $basepath . "/" . $this->index[$key]["file"];
743
744
        // Load content from file
745
        if (!is_file($path)) {
746
            $msg = t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]);
747
            throw new \Anax\Exception\NotFoundException($msg);
748
        }
749
750
        // Get filtered content
751
        $src = file_get_contents($path);
752
        $filtered = $this->di->get("textfilter")->parse($src, $filter);
753
754
        return $filtered;
755
    }
756
757
758
759
    // == Process content phase 2 ===================================
760
    // TODO REFACTOR THIS?
761
    
762
    /**
763
     * Look up the route in the index and use that to retrieve the filtered
764
     * content.
765
     *
766
     * @param array  &$content   to process.
767
     * @param object &$filtered to use for settings.
768
     *
769
     * @return array with content and filtered version.
770
     */
771
    private function processMainContentPhaseTwo(&$content, &$filtered)
772
    {
773
        // From configuration
774
        $filter = $this->config["textfilter"];
775
        $revisionStart = $this->config["revision-history"]["start"];
776
        $revisionEnd   = $this->config["revision-history"]["end"];
777
        $revisionClass = $this->config["revision-history"]["class"];
778
        $revisionSource = isset($this->config["revision-history"]["source"])
779
            ? $this->config["revision-history"]["source"]
780
            : null;
781
782
        $textFilter = $this->di->get("textfilter");
783
        $text = $filtered->text;
784
785
        // Check if revision history is to be included
786
        if (isset($content["views"]["main"]["data"]["revision"])) {
787
            $text = $textFilter->addRevisionHistory(
788
                $text,
789
                $content["views"]["main"]["data"]["revision"],
790
                $revisionStart,
791
                $revisionEnd,
792
                $revisionClass,
793
                $revisionSource . "/" . $content["file"]
794
            );
795
        }
796
797
        // Get new filtered content (and updated frontmatter)
798
        // Title in frontmatter overwrites title found in content
799
        $new = $textFilter->parse($text, $filter);
800
        $filtered->text = $new->text;
801
         
802
        // Keep title if defined in frontmatter
803
        $title = isset($filtered->frontmatter["title"])
804
          ? $filtered->frontmatter["title"]
805
          : null;
806
807
        $filtered->frontmatter = array_merge_recursive_distinct(
808
            $filtered->frontmatter,
809
            $new->frontmatter
810
        );
811
812
        if ($title) {
813
            $filtered->frontmatter["title"] = $title;
814
        }
815
816
        // Main data is
817
        $data = &$content["views"]["main"]["data"];
818
819
        // Update all anchor urls to use baseurl, needs info about baseurl
820
        // from merged frontmatter
821
        $baseurl = isset($data["baseurl"])
822
          ? $data["baseurl"]
823
          : null;
824
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
825
        $this->addBaseurl2ImageSource($filtered, $baseurl);
826
827
        // Add excerpt and hasMore, if available
828
        $textFilter->addExcerpt($filtered);
829
830
        // Load details on author, if set.
831
        if (isset($data["author"])) {
832
            $data["author"] = $this->loadAuthorDetails($data["author"]);
833
        }
834
835
        // Load details on category, if set.
836
        if (isset($data["category"])) {
837
            $data["category"] = $this->loadCategoryDetails($data["category"]);
838
        }
839
    }
840
841
842
843
    // == Public methods ============================================
844
    
845
    /**
846
     * Map url to content, even internal content, if such mapping can be done.
847
     *
848
     * @param string $route optional route to look up.
849
     *
850
     * @return object with content and filtered version.
851
     */
852
    public function contentForInternalRoute($route = null)
853
    {
854
        // Get the route
855
        if (is_null($route)) {
856
            $route = $this->di->get("request")->getRoute();
857
        }
858
859
        // Check cache for content or create cached version of content
860
        $cache = $this->di->get("cache");
861
        $slug = $this->di->get("url")->slugify($route);
862
        $key = $cache->createKey(__CLASS__, "route-$slug");
863
        $content = $cache->get($key);
864
865
        if (!$content || $this->ignoreCache) {
866
            $content = $this->createContentForInternalRoute($route);
867
            $cache->set($key, $content);
868
        }
869
870
        return $content;
871
    }
872
873
874
875
    /**
876
     * Map url to content if such mapping can be done, exclude internal routes.
877
     *
878
     * @param string $route optional route to look up.
879
     *
880
     * @return object with content and filtered version.
881
     */
882
    public function contentForRoute($route = null)
883
    {
884
        $content = $this->contentForInternalRoute($route);
885
        if ($content->internal === true) {
886
            $msg = t("The content '!ROUTE' does not exists as a public route.", ["!ROUTE" => $route]);
887
            throw new \Anax\Exception\NotFoundException($msg);
888
        }
889
890
        return $content;
891
    }
892
}
893