FileBasedContent::createMeta()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 16
nc 2
nop 0
dl 0
loc 28
rs 9.7333
c 0
b 0
f 0
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
    public function configure(array $config) : void
95
    {
96
        $this->config = $config;
0 ignored issues
show
Bug Best Practice introduced by
The property config does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
97
        $this->setDefaultsFromConfiguration();
98
    }
99
100
101
102
    /**
103
     * Set default values from configuration.
104
     *
105
     * @return this.
0 ignored issues
show
Documentation Bug introduced by
The doc comment this. at position 0 could not be parsed: Unknown type name 'this.' at position 0 in this..
Loading history...
106
     */
107
    private function setDefaultsFromConfiguration()
108
    {
109
        $this->ignoreCache = isset($this->config["ignoreCache"])
110
            ? $this->config["ignoreCache"]
111
            : $this->ignoreCache;
112
113
        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 Bug introduced by
The doc comment this. at position 0 could not be parsed: Unknown type name 'this.' at position 0 in this..
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 Bug introduced by
The doc comment void. at position 0 could not be parsed: Unknown type name 'void.' at position 0 in void..
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) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of glob_recursive($path) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
The expression glob_recursive($path) of type void is not traversable.
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression glob_recursive($path) of type void is not traversable.
Loading history...
Bug introduced by
Are you sure the usage of glob_recursive($path) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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 Bug introduced by
The doc comment void. at position 0 could not be parsed: Unknown type name 'void.' at position 0 in void..
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
    private function loadAuthorDetails($author)
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 Bug introduced by
The doc comment void. at position 0 could not be parsed: Unknown type name 'void.' at position 0 in void..
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).
449
     *
450
     * @return array with more details on the category(s).
451
     */
452
    private function loadCategoryDetails($category)
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
The expression return false returns the type false which is incompatible with the documented return type string.
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
The call to Anax\Content\FileBasedContent::getView() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

636
        /** @scrutinizer ignore-call */ 
637
        $views = $this->getView($route, $frontmatter, "views", 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. Please note the @ignore annotation hint above.

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
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 Bug introduced by
The doc comment void. at position 0 could not be parsed: Unknown type name 'void.' at position 0 in void..
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);
0 ignored issues
show
Bug introduced by
The type Anax\Exception\NotFoundException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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