Completed
Push — master ( 77a181...8dd6aa )
by Mikael
02:29
created

CFileBasedContent::createBaseRouteToc()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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