Completed
Push — master ( 8480db...1a117b )
by Mikael
02:44
created

FileBasedContent::processMainContentPhaseTwo()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 69
c 0
b 0
f 0
ccs 0
cts 40
cp 0
rs 7.2452
cc 8
nc 128
nop 2
crap 72

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
847
        $content = $this->di->cache->get($key);
0 ignored issues
show
Bug introduced by
Accessing cache on the interface Psr\Container\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
848
849
        if (!$content || $this->ignoreCache) {
850
            $content = $this->createContentForInternalRoute($route);
851
            $this->di->cache->put($key, $content);
0 ignored issues
show
Bug introduced by
Accessing cache on the interface Psr\Container\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
852
        }
853
854
        return $content;
855
    }
856
857
858
859
    /**
860
     * Map url to content if such mapping can be done, exclude internal routes.
861
     *
862
     * @param string $route optional route to look up.
863
     *
864
     * @return object with content and filtered version.
865
     */
866
    public function contentForRoute($route = null)
867
    {
868
        $content = $this->contentForInternalRoute($route);
869
        if ($content->internal === true) {
870
            $msg = t("The content '!ROUTE' does not exists as a public route.", ["!ROUTE" => $route]);
871
            throw new \Anax\Exception\NotFoundException($msg);
872
        }
873
874
        return $content;
875
    }
876
}
877