Issues (165)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Content/CFileBasedContent.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     * This is the base route.
40
     */
41
    private $baseRoute = null;
42
43
    /**
44
     * This is the extendede meta route, if any.
45
     */
46
    private $metaRoute = null;
47
48
    /**
49
     * This is the current page, to supply pagination, if used.
50
     */
51
    private $currentPage = null;
52
53
    /**
54
     * Use cache or recreate each time.
55
     */
56
    private $ignoreCache = false;
57
    
58
    /**
59
     * File name pattern, all files must match this pattern and the first
60
     * numbered part is optional, the second part becomes the route.
61
     */
62
    private $filenamePattern = "#^(\d*)_*([^\.]+)\.md$#";
63
64
    /**
65
     * Internal routes that is marked as internal content routes and not
66
     * exposed as public routes.
67
     */
68
    private $internalRouteDirPattern = [
69
        "#block/#",
70
    ];
71
72
    private $internalRouteFilePattern = [
73
        "#^block[_-]{1}#",
74
        "#^_#",
75
    ];
76
77
    /**
78
     * Routes that should be used in toc.
79
     */
80
    private $allowedInTocPattern = "([\d]+_(\w)+)";
81
82
83
84
    /**
85
     * Set default values from configuration.
86
     *
87
     * @return this.
0 ignored issues
show
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...
88
     */
89
    public function setDefaultsFromConfiguration()
90
    {
91
        $this->ignoreCache = isset($this->config["ignoreCache"])
92
            ? $this->config["ignoreCache"]
93
            : $this->ignoreCache;
94
95
        return $this;
96
    }
97
98
99
100
    /**
101
     * Should the cache be used or ignored.
102
     *
103
     * @param boolean $use true to use the cache or false to ignore the cache
104
     *
105
     * @return this.
0 ignored issues
show
The doc-type this. could not be parsed: Unknown type name "this." at position 0. (view supported doc-types)

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

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