Completed
Push — master ( 751de4...36d137 )
by Mikael
02:34
created

CFileBasedContent::getBaseurl()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 9.2
cc 4
eloc 9
nc 6
nop 2
crap 20
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
13
14
15
    /**
16
     * Properties.
17
     */
18
    private $index = null;
19
    private $meta = null;
20
    private $ignoreCache = false;
21
    
22
    /**
23
     * File name pattern, all files must match this pattern and the first
24
     * numbered part is optional, the second part becomes the route.
25
     */
26
    private $filenamePattern = "#^(\d*)_*([^\.]+)\.md$#";
27
28
    /**
29
     * Internal routes that is marked as internal content routes and not
30
     * exposed as public routes.
31
     */
32
    private $internalRouteDirPattern = [
33
        "#block/#",
34
    ];
35
36
    private $internalRouteFilePattern = [
37
        "#^block[_-]{1}#",
38
        "#^_#",
39
    ];
40
41
    /**
42
     * Routes that should be used in toc.
43
     */
44
    private $allowedInTocPattern = "([\d]+_(\w)+)";
45
46
47
48
    /**
49
     * Create a breadcrumb, append slash / to all dirs.
50
     *
51
     * @param string $route      current route.
52
     *
53
     * @return array with values for the breadcrumb.
54
     */
55
    public function createBreadcrumb($route)
56
    {
57
        $breadcrumbs = [];
58
59
        while ($route !== "./" && $route !== "/") {
60
            $routeIndex = $this->mapRoute2IndexKey($route);
61
            $item["url"] = $route;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$item was never initialized. Although not strictly required by PHP, it is generally a good practice to add $item = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
62
            $item["text"] = $this->getBreadcrumbTitle($this->index[$routeIndex]["file"]);
0 ignored issues
show
Bug introduced by
The variable $item does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
63
            $breadcrumbs[] = $item;
64
            $route = dirname($route) . "/";
65
        }
66
67
        krsort($breadcrumbs);
68
        return $breadcrumbs;
69
    }
70
71
72
73
/**
74
 * Get time when the content was last updated.
75
 *
76
 * @return string with the time.
77
 */
78
/*public function PublishTime() {
79
  if(!empty($this['published'])) {
80
    return $this['published'];
81
  } else if(isset($this['updated'])) {
82
    return $this['updated'];
83
  } else {
84
    return $this['created'];
85
  } 
86
}
87
*/
88
/**
89
 * Get the action for latest updated of the content.
90
 *
91
 * @return string with the time.
92
 */
93
/*public function PublishAction() {
94
  if(!empty($this['published'])) {
95
    //return t('Published');
96
    return t('Last updated');
97
  } else if(isset($this['updated'])) {
98
    return t('Updated');
99
  } else {
100
    return t('Created');
101
  } 
102
}
103
*/
104
105
106
107
    /**
108
     * Set default values from configuration.
109
     *
110
     * @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...
111
     */
112
    public function setDefaultsFromConfiguration()
113
    {
114
        $this->ignoreCache = isset($this->config["ignoreCache"])
115
            ? $this->config["ignoreCache"]
116
            : $this->ignoreCache;
117
118
        return $this;
119
    }
120
121
122
123
    /**
124
     * Should the cache be used or ignored.
125
     *
126
     * @param boolean $use true to use the cache or false to ignore the cache
127
     *
128
     * @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...
129
     */
130
    public function useCache($use)
131
    {
132
        $this->ignoreCache = !$use;
133
134
        return $this;
135
    }
136
137
138
139
    /**
140
     * Get the index as an array.
141
     *
142
     * @return array as index.
143
     */
144
    public function getIndex()
145
    {
146
        return $this->loadIndex();
147
    }
148
149
150
151
    /**
152
     * Create the index of all content into an array.
153
     *
154
     * @return array as index.
155
     */
156 View Code Duplication
    private function loadIndex()
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...
157
    {
158
        if ($this->index) {
159
            return $this->index;
160
        }
161
162
        $key = $this->di->cache->createKey(__CLASS__, "index");
163
        $this->index = $this->di->cache->get($key);
164
165
        if (!$this->index || $this->ignoreCache) {
166
            $this->index = $this->createIndex();
167
            $this->di->cache->put($key, $this->index);
168
        }
169
170
        return $this->index;
171
    }
172
173
174
175
    /**
176
     * Check if a filename is to be marked as an internal route..
177
     *
178
     * @param string $filepath as the basepath (routepart) to the file.
179
     *
180
     * @return boolean true if the route content is internal, else false
181
     */
182
    private function isInternalRoute($filepath)
183
    {
184
        foreach ($this->internalRouteDirPattern as $pattern) {
185
            if (preg_match($pattern, $filepath)) {
186
                return true;
187
            }
188
        }
189
190
        $filename = basename($filepath);
191
        foreach ($this->internalRouteFilePattern as $pattern) {
192
            if (preg_match($pattern, $filename)) {
193
                return true;
194
            }
195
        }
196
197
        return false;
198
    }
199
200
201
202
    /**
203
     * Check if filepath should be used as part of toc.
204
     *
205
     * @param string $filepath as the basepath (routepart) to the file.
206
     *
207
     * @return boolean true if the route content shoul dbe in toc, else false
208
     */
209
    private function allowInToc($filepath)
210
    {
211
        return (boolean) preg_match($this->allowedInTocPattern, $filepath);
212
    }
213
214
215
216
    /**
217
     * Generate an index from the directory structure.
218
     *
219
     * @return array as index for all content files.
220
     */
221
    private function createIndex()
222
    {
223
        $basepath   = $this->config["basepath"];
224
        $pattern    = $this->config["pattern"];
225
        $path       = "$basepath/$pattern";
226
227
        $index = [];
228
        foreach (glob_recursive($path) as $file) {
229
            $filepath = substr($file, strlen($basepath) + 1);
230
231
            // Find content files
232
            $matches = [];
233
            preg_match($this->filenamePattern, basename($filepath), $matches);
234
            $dirpart = dirname($filepath) . "/";
235
            if ($dirpart === "./") {
236
                $dirpart = null;
237
            }
238
            $key = $dirpart . $matches[2];
239
            
240
            // Create level depending on the file id
241
            $id = $matches[1];
242
            $level = 2;
243 View Code Duplication
            if ($id % 100 === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
                $level = 0;
245
            } elseif ($id % 10 === 0) {
246
                $level = 1;
247
            }
248
249
            $index[$key] = [
250
                "file"     => $filepath,
251
                "section"  => $matches[1],
252
                "level"    => $level,
253
                "internal" => $this->isInternalRoute($filepath),
254
                "tocable"  => $this->allowInToc($filepath),
255
            ];
256
        }
257
258
        return $index;
259
    }
260
261
262
263
    /**
264
     * Create the index of all meta content into an array.
265
     *
266
     * @return array as index.
267
     */
268 View Code Duplication
    private function loadMetaIndex()
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...
269
    {
270
        if ($this->meta) {
271
            return $this->meta;
272
        }
273
274
        $key = $this->di->cache->createKey(__CLASS__, "meta");
275
        $this->meta = $this->di->cache->get($key);
276
277
        if (!$this->meta || $this->ignoreCache) {
278
            $this->meta = $this->createMetaIndex();
279
            $this->di->cache->put($key, $this->meta);
280
        }
281
282
        return $this->meta;
283
    }
284
285
286
287
    /**
288
     * Generate an index for meta files.
289
     *
290
     * @return array as table of content.
291
     */
292
    private function createMetaIndex()
293
    {
294
        $basepath = $this->config["basepath"];
295
        $filter   = $this->config["metafilter"];
296
        $meta     = $this->config["meta"];
297
        $path     = "$basepath/$meta";
298
299
        $meta = [];
300
        foreach (glob_recursive($path) as $file) {
301
            $filepath = substr($file, strlen($basepath) + 1);
302
            
303
            $src = file_get_contents($file);
304
            $filtered = $this->di->get("textFilter")->parse($src, $filter);
305
306
            $key = dirname($filepath);
307
            $meta[$key] = $filtered->frontmatter;
308
309
            // Add Toc to the data array
310
            $meta[$key]["__toc__"] = $this->createBaseRouteToc(dirname($filepath));
311
        }
312
313
        return $meta;
314
    }
315
316
317
318
    /**
319
     * Get a reference to meta data for specific route.
320
     *
321
     * @param string $route current route used to access page.
322
     *
323
     * @return array as table of content.
324
     */
325
    private function getMetaForRoute($route)
326
    {
327
        $base = dirname($route);
328
        return isset($this->meta[$base])
329
            ? $this->meta[$base]
330
            : [];
331
    }
332
333
334
335
    /**
336
     * Get the frontmatter of a document.
337
     *
338
     * @param string $file to get frontmatter from.
339
     *
340
     * @return array as frontmatter.
341
     */
342
    private function getFrontmatter($file)
343
    {
344
        $basepath = $this->config["basepath"];
345
        $filter   = $this->config["textfilter"];
346
347
        $path = $basepath . "/" . $file;
348
        $src = file_get_contents($path);
349
        $filtered = $this->di->textFilter->parse($src, $filter);
350
        return $filtered->frontmatter;
351
    }
352
353
354
355
    /**
356
     * Get the title of a document.
357
     *
358
     * @deprecated in favor of getFrontmatter
359
     *
360
     * @param string $file to get title from.
361
     *
362
     * @return string as the title.
363
     */
364
    private function getTitle($file)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
365
    {
366
        $frontmatter = $this->getFrontmatter($file);
367
        return $frontmatter["title"];
368
    }
369
370
371
372
    /**
373
     * Get the title of a document to use for breadcrumb.
374
     *
375
     * @param string $file to get title from.
376
     *
377
     * @return string as the breadcrumb title.
378
     */
379
    private function getBreadcrumbTitle($file)
380
    {
381
        $frontmatter = $this->getFrontmatter($file);
382
383
        $title = $frontmatter["title"];
384
        if (isset($frontmatter["titleBreadcrumb"])) {
385
            $title = $frontmatter["titleBreadcrumb"];
386
        }
387
388
        return $title;
389
    }
390
391
392
393
    /**
394
     * Create a table of content for routes at particular level.
395
     *
396
     * @param string $route base route to use.
397
     *
398
     * @return array as the toc.
399
     */
400
    private function createBaseRouteToc($route)
401
    {
402
        $toc = [];
403
        $len = strlen($route);
404
405
        foreach ($this->index as $key => $value) {
406
            if (substr($key, 0, $len) === $route) {
407
                if ($value["internal"] === false
408
                    && $value["tocable"] === true) {
409
                    $toc[$key] = $value;
410
                    
411
                    $frontm = $this->getFrontmatter($value["file"]);
412
                    $toc[$key]["title"] = $frontm["title"];
413
                    $toc[$key]["sectionHeader"] = isset($frontm["sectionHeader"]) ? $frontm["sectionHeader"] : null;
414
                    $toc[$key]["linkable"] = isset($frontm["linkable"]) ? $frontm["linkable"] : null;
415
                }
416
            }
417
        };
418
419
        return $toc;
420
    }
421
422
423
424
    /**
425
     * Map the route to the correct key in the index.
426
     *
427
     * @param string $route current route used to access page.
428
     *
429
     * @return string as key or false if no match.
430
     */
431
    private function mapRoute2IndexKey($route)
432
    {
433
        $route = rtrim($route, "/");
434
435
        if (key_exists($route, $this->index)) {
436
            return $route;
437
        } elseif (empty($route) && key_exists("index", $this->index)) {
438
            return "index";
439
        } elseif (key_exists($route . "/index", $this->index)) {
440
            return "$route/index";
441
        }
442
443
        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...
444
    }
445
446
447
448
    /**
449
     * Map the route to the correct entry in the index.
450
     *
451
     * @param string $route current route used to access page.
452
     *
453
     * @return array as the matched route.
454
     */
455
    private function mapRoute2Index($route)
456
    {
457
        $routeIndex = $this->mapRoute2IndexKey($route);
458
459
        if ($routeIndex) {
460
            return [$routeIndex, $this->index[$routeIndex]];
461
        }
462
463
        throw new \Anax\Exception\NotFoundException(t("The route '!ROUTE' does not exists in the index.", ["!ROUTE" => $route]));
464
    }
465
466
467
468
    /**
469
     * Get view by mergin information from meta and frontmatter.
470
     *
471
     * @param string $route       current route used to access page.
472
     * @param array  $frontmatter for the content.
473
     * @param string $key         for the view to retrive.
474
     * @param string $distinct    how to merge the array.
475
     *
476
     * @return array with data to add as view.
477
     */
478
    private function getView($route, $frontmatter, $key, $distinct = true)
479
    {
480
        $view = [];
481
482
        // From meta frontmatter
483
        $meta = $this->getMetaForRoute($route);
484
        if (isset($meta[$key])) {
485
            $view = $meta[$key];
486
        }
487
488
        // From document frontmatter
489
        if (isset($frontmatter[$key])) {
490
            if ($distinct) {
491
                $view = array_merge_recursive_distinct($view, $frontmatter[$key]);
492
            } else {
493
                $view = array_merge($view, $frontmatter[$key]);
494
            }
495
        }
496
497
        return $view;
498
    }
499
500
501
502
    /**
503
     * Get details on extra views.
504
     *
505
     * @param string $route       current route used to access page.
506
     * @param array  $frontmatter for the content.
507
     *
508
     * @return array with page data to send to view.
509
     */
510
    private function getViews($route, $frontmatter)
511
    {
512
        // Arrange data into views
513
        $views = $this->getView($route, $frontmatter, "views", false);
514
515
        // Set defaults
516
        if (!isset($views["main"]["template"])) {
517
            $views["main"]["template"] = $this->config["template"];
518
        }
519
        if (!isset($views["main"]["data"])) {
520
            $views["main"]["data"] = [];
521
        }
522
523
        // Merge remaining frontmatter into view main data.
524
        $data = $this->getMetaForRoute($route);
525
        unset($data["__toc__"]);
526
        unset($data["views"]);
527
        unset($frontmatter["views"]);
528
        $data = array_merge_recursive_distinct($data, $frontmatter);
529
        $views["main"]["data"] = array_merge_recursive_distinct($views["main"]["data"], $data);
530
531
        return $views;
532
    }
533
534
535
536
    /**
537
     * Load extra info into views based of meta information provided in each
538
     * view.
539
     *
540
     * @param string $view    with current settings.
541
     * @param string $route   to load view from.
542
     * @param string $baseurl to prepend relative urls.
543
     *
544
     * @return array with view details.
545
     */
546
    private function getAdditionalViewDataForRoute($view, $route, $baseurl)
547
    {
548
        // Get filtered content from route
549
        list(, , $filtered) =
550
            $this->mapRoute2Content($route);
551
552
        // From document frontmatter
553
        $view["data"] = array_merge_recursive_distinct($view["data"], $filtered->frontmatter);
0 ignored issues
show
Documentation introduced by
$view['data'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
554
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
555
        $view["data"]["content"] = $filtered->text;
556
557
        return $view;
558
559
    }
560
561
562
563
    /**
564
     * Order and limit toc items.
565
     *
566
     * @param string &$toc  array with current toc.
567
     * @param string &$meta on how to order and limit toc.
568
     *
569
     * @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...
570
     */
571
    private function orderAndlimitToc(&$toc, &$meta)
572
    {
573
        $defaults = [
574
            "items" => 7,
575
            "offset" => 0,
576
            "orderby" => "section",
577
            "orderorder" => "asc",
578
        ];
579
        $options = array_merge($defaults, $meta);
580
        $orderby = $options["orderby"];
581
        $order   = $options["orderorder"];
582
583
        $meta["totalItems"] = count($toc);
584
585
        // TODO support pagination by adding entries to $meta
586
587
        uksort($toc, function ($a, $b) use ($toc, $orderby, $order) {
588
            $a = $toc[$a][$orderby];
589
            $b = $toc[$b][$orderby];
590
591
            if ($order == "asc") {
592
                return strcmp($a, $b);
593
            }
594
            return strcmp($b, $a);
595
        });
596
597
        $toc = array_slice($toc, $options["offset"], $options["items"]);
598
        $meta["displayedItems"] = count($toc);
599
    }
600
601
602
603
    /**
604
     * Find next and previous links of current content.
605
     *
606
     * @param string $routeIndex target route to find next and previous for.
607
     *
608
     * @return array with next and previous if found.
609
     */
610
    private function findNextAndPrevious($routeIndex)
611
    {
612
        $key = dirname($routeIndex);
613
        if (!isset($this->meta[$key]["__toc__"])) {
614
            return [null, null];
615
        }
616
617
        $toc = $this->meta[$key]["__toc__"];
618
        if (!isset($toc[$routeIndex])) {
619
            return [null, null];
620
        }
621
622
        $index2Key = array_keys($toc);
623
        $keys = array_flip($index2Key);
624
        $values = array_values($toc);
625
        $count = count($keys);
626
627
        $current = $keys[$routeIndex];
628
        $previous = null;
629 View Code Duplication
        for ($i = $current - 1; $i >= 0; $i--) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
            $isSectionHeader = $values[$i]["sectionHeader"];
631
            $isInternal = $values[$i]["internal"];
632
            if ($isSectionHeader || $isInternal) {
633
                continue;
634
            }
635
            $previous = $values[$i];
636
            $previous["route"] = $index2Key[$i];
637
            break;
638
        }
639
        
640
        $next = null;
641 View Code Duplication
        for ($i = $current + 1; $i < $count; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
642
            $isSectionHeader = $values[$i]["sectionHeader"];
643
            $isInternal = $values[$i]["internal"];
644
            if ($isSectionHeader || $isInternal) {
645
                continue;
646
            }
647
            $next = $values[$i];
648
            $next["route"] = $index2Key[$i];
649
            break;
650
        }
651
652
        return [$next, $previous];
653
    }
654
655
656
657
    /**
658
     * Load extra info into views based of meta information provided in each
659
     * view.
660
     *
661
     * @param array  &$views     with all views.
662
     * @param string $route      current route
663
     * @param string $routeIndex route with appended /index
664
     *
665
     * @throws NotFoundException when mapping can not be done.
666
     *
667
     * @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...
668
     */
669
    private function loadAdditionalContent(&$views, $route, $routeIndex)
670
    {
671
        foreach ($views as $id => $view) {
672
            $meta = isset($view["data"]["meta"])
673
                ? $view["data"]["meta"]
674
                : null;
675
676
            if (is_array($meta)) {
677
                switch ($meta["type"]) {
678
                    case "article-toc":
679
                        $content = $views["main"]["data"]["content"];
680
                        $views[$id]["data"]["articleToc"] = $this->di->textFilter->createToc($content);
681
                        break;
682
683
                    case "breadcrumb":
684
                        $views[$id]["data"]["breadcrumb"] = $this->createBreadcrumb($route);
685
                        break;
686
687 View Code Duplication
                    case "next-previous":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
688
                        $baseRoute = dirname($routeIndex);
0 ignored issues
show
Unused Code introduced by
$baseRoute is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
689
                        list($next, $previous) = $this->findNextAndPrevious($routeIndex);
690
                        $views[$id]["data"]["next"] = $next;
691
                        $views[$id]["data"]["previous"] = $previous;
692
                        break;
693
694
                    case "single": // OBSOLETE
695
                    case "content":
696
                        $baseurl = $this->getBaseurl($views, $id);
697
                        $views[$id] = $this->getAdditionalViewDataForRoute($view, $meta["route"], $baseurl);
698
                        break;
699
700 View Code Duplication
                    case "toc":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
701
                        $baseRoute = dirname($routeIndex);
702
                        $toc = $this->meta[$baseRoute]["__toc__"];
703
                        $this->orderAndlimitToc($toc, $meta);
704
                        $views[$id]["data"]["toc"] = $toc;
705
                        $views[$id]["data"]["meta"] = $meta;
706
                        break;
707
708
                    default:
709
                        throw new Exception(t("Unsupported data/meta/type for additional content."));
710
                }
711
            }
712
        }
713
    }
714
715
716
717
    /**
718
     * Get basurl from view, if it is defined.
719
     *
720
     * @param array  $views   data for all views.
721
     * @param string $current for current view if any.
722
     *
723
     * @return string | null as baseurl.
724
     */
725
    private function getBaseurl($views, $current = null)
726
    {
727
        $baseurl = isset($views["main"]["data"]["baseurl"])
728
            ? $views["main"]["data"]["baseurl"]
729
            : null;
730
731
        if ($current) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $current of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
732
            $baseurl = isset($views[$current]["data"]["baseurl"])
733
                ? $views[$current]["data"]["baseurl"]
734
                : $baseurl;
735
        }
736
737
        return $baseurl;
738
    }
739
740
741
742
    /**
743
     * Parse text, find and update all a href to use baseurl.
744
     *
745
     * @param object &$filtered with text and excerpt to process.
746
     * @param string $baseurl   add as baseurl for all relative urls.
747
     *
748
     * @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...
749
     */
750
    private function addBaseurl2AnchorUrls(&$filtered, $baseurl)
751
    {
752
        $textf  = $this->di->get("textFilter");
753
        $url    = $this->di->get("url");
754
755
        // Use callback to url->create() instead of string concat
756
        $callback = function ($route) use ($url, $baseurl) {
757
            return $url->create($route, $baseurl);
758
        };
759
760
        $filtered->text =
761
            $textf->addBaseurlToRelativeLinks($filtered->text, $baseurl, $callback);
762
    }
763
764
765
766
    /**
767
     * Load extra info intro views based of meta information provided in each
768
     * view.
769
     *
770
     * @param string $key     array with all views.
771
     * @param string $content array with all views.
772
     *
773
     * @throws NotFoundException when mapping can not be done.
774
     *
775
     * @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...
776
     */
777
    private function loadFileContent($key, $content)
778
    {
779
        // Settings from config
780
        $basepath = $this->config["basepath"];
781
        $filter   = $this->config["textfilter"];
782
783
        // Whole path to file
784
        $path = $basepath . "/" . $content["file"];
785
        $content["path"] = $path;
786
787
        // Load content from file
788
        if (!is_file($path)) {
789
            $msg = t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]);
790
            throw new \Anax\Exception\NotFoundException($msg);
791
        }
792
793
        // Get filtered content
794
        $src = file_get_contents($path);
795
        $filtered = $this->di->get("textFilter")->parse($src, $filter);
796
797
        return [$content, $filtered];
798
    }
799
800
801
802
    /**
803
     * Look up the route in the index and use that to retrieve the filtered
804
     * content.
805
     *
806
     * @param string $route to look up.
807
     *
808
     * @return array with content and filtered version.
809
     */
810
    public function mapRoute2Content($route)
811
    {
812
        // Look it up in the index
813
        list($keyIndex, $content) = $this->mapRoute2Index($route);
814
        list($content, $filtered) = $this->loadFileContent($keyIndex, $content);
815
816
        return [$keyIndex, $content, $filtered];
817
    }
818
819
820
821
    /**
822
     * Map url to content if such mapping can be done, exclude internal routes.
823
     *
824
     * @param string $route optional route to look up.
825
     *
826
     * @return object with content and filtered version.
827
     */
828
    public function contentForRoute($route = null)
829
    {
830
        $content = $this->contentForInternalRoute($route);
831
        if ($content->internal === true) {
832
            $msg = t("The content '!ROUTE' does not exists as a public route.", ["!ROUTE" => $route]);
833
            throw new \Anax\Exception\NotFoundException($msg);
834
        }
835
836
        return $content;
837
    }
838
839
840
841
    /**
842
     * Map url to content, even internal content, if such mapping can be done.
843
     *
844
     * @param string $route optional route to look up.
845
     *
846
     * @return object with content and filtered version.
847
     */
848
    public function contentForInternalRoute($route = null)
849
    {
850
        // Get the route
851
        if (is_null($route)) {
852
            $route = $this->di->get("request")->getRoute();
853
        }
854
855
        // TODO cache route content.
856
857
        // Load index and map route to content
858
        $this->loadIndex();
859
        $this->loadMetaIndex();
860
        list($routeIndex, $content, $filtered) = $this->mapRoute2Content($route);
861
862
        // TODO Should not supply all frontmatter to theme, only the
863
        // parts valid to the index template. Separate that data into own
864
        // holder in frontmatter. Do not include whole frontmatter? Only
865
        // on debg?
866
        $content["frontmatter"] = $filtered->frontmatter;
867
868
        // Create and arrange the content as views, merge with .meta,
869
        // frontmatter is complete.
870
        $content["views"] = $this->getViews($routeIndex, $filtered->frontmatter);
871
872
        // Update all anchor urls to use baseurl, needs info about baseurl
873
        // from merged frontmatter
874
        $baseurl = $this->getBaseurl($content["views"]);
875
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
876
877
        // Add excerpt and hasMore, if available
878
        $this->di->get("textFilter")->addExcerpt($filtered);
879
880
        //
881
        // TODO Load content, pure or use data available
882
        // own function
883
        // perhaps load in separate view
884
        //
885
        $content["views"]["main"]["data"]["content"] = $filtered->text;
886
        $content["views"]["main"]["data"]["excerpt"] = $filtered->excerpt;
887
        $this->loadAdditionalContent($content["views"], $route, $routeIndex);
888
889
        return (object) $content;
890
    }
891
}
892