Completed
Push — master ( 36d137...675a15 )
by Mikael
02:48
created

CFileBasedContent::getViewDataForRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 7
nc 1
nop 2
crap 2
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
     * Load extra info into views based of meta information provided in each
565
     * view.
566
     *
567
     * @param string $view    with current settings.
0 ignored issues
show
Bug introduced by
There is no parameter named $view. 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...
568
     * @param string $route   to load view from.
569
     * @param string $baseurl to prepend relative urls.
570
     *
571
     * @return array with view data details.
572
     */
573
    private function getViewDataForRoute($route, $baseurl)
574
    {
575
        // Get filtered content from route
576
        list(, , $filtered) =
577
            $this->mapRoute2Content($route);
578
579
        $data = $filtered->frontmatter;
580
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
581
        $data["content"] = $filtered->text;
582
583
        return $data;
584
585
    }
586
587
588
589
    /**
590
     * Order and limit toc items.
591
     *
592
     * @param string &$toc  array with current toc.
593
     * @param string &$meta on how to order and limit toc.
594
     *
595
     * @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...
596
     */
597
    private function orderAndlimitToc(&$toc, &$meta)
598
    {
599
        $defaults = [
600
            "items" => 7,
601
            "offset" => 0,
602
            "orderby" => "section",
603
            "orderorder" => "asc",
604
        ];
605
        $options = array_merge($defaults, $meta);
606
        $orderby = $options["orderby"];
607
        $order   = $options["orderorder"];
608
609
        $meta["totalItems"] = count($toc);
610
611
        // TODO support pagination by adding entries to $meta
612
613
        uksort($toc, function ($a, $b) use ($toc, $orderby, $order) {
614
            $a = $toc[$a][$orderby];
615
            $b = $toc[$b][$orderby];
616
617
            if ($order == "asc") {
618
                return strcmp($a, $b);
619
            }
620
            return strcmp($b, $a);
621
        });
622
623
        $toc = array_slice($toc, $options["offset"], $options["items"]);
624
        $meta["displayedItems"] = count($toc);
625
    }
626
627
628
629
    /**
630
     * Find next and previous links of current content.
631
     *
632
     * @param string $routeIndex target route to find next and previous for.
633
     *
634
     * @return array with next and previous if found.
635
     */
636
    private function findNextAndPrevious($routeIndex)
637
    {
638
        $key = dirname($routeIndex);
639
        if (!isset($this->meta[$key]["__toc__"])) {
640
            return [null, null];
641
        }
642
643
        $toc = $this->meta[$key]["__toc__"];
644
        if (!isset($toc[$routeIndex])) {
645
            return [null, null];
646
        }
647
648
        $index2Key = array_keys($toc);
649
        $keys = array_flip($index2Key);
650
        $values = array_values($toc);
651
        $count = count($keys);
652
653
        $current = $keys[$routeIndex];
654
        $previous = null;
655 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...
656
            $isSectionHeader = $values[$i]["sectionHeader"];
657
            $isInternal = $values[$i]["internal"];
658
            if ($isSectionHeader || $isInternal) {
659
                continue;
660
            }
661
            $previous = $values[$i];
662
            $previous["route"] = $index2Key[$i];
663
            break;
664
        }
665
        
666
        $next = null;
667 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...
668
            $isSectionHeader = $values[$i]["sectionHeader"];
669
            $isInternal = $values[$i]["internal"];
670
            if ($isSectionHeader || $isInternal) {
671
                continue;
672
            }
673
            $next = $values[$i];
674
            $next["route"] = $index2Key[$i];
675
            break;
676
        }
677
678
        return [$next, $previous];
679
    }
680
681
682
683
    /**
684
     * Load extra info into views based of meta information provided in each
685
     * view.
686
     *
687
     * @param array  &$views     with all views.
688
     * @param string $route      current route
689
     * @param string $routeIndex route with appended /index
690
     *
691
     * @throws NotFoundException when mapping can not be done.
692
     *
693
     * @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...
694
     */
695
    private function loadAdditionalContent(&$views, $route, $routeIndex)
696
    {
697
        foreach ($views as $id => $view) {
698
            $meta = isset($view["data"]["meta"])
699
                ? $view["data"]["meta"]
700
                : null;
701
702
            if (is_array($meta)) {
703
                switch ($meta["type"]) {
704
                    case "article-toc":
705
                        $content = $views["main"]["data"]["content"];
706
                        $views[$id]["data"]["articleToc"] = $this->di->textFilter->createToc($content);
707
                        break;
708
709
                    case "breadcrumb":
710
                        $views[$id]["data"]["breadcrumb"] = $this->createBreadcrumb($route);
711
                        break;
712
713 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...
714
                        $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...
715
                        list($next, $previous) = $this->findNextAndPrevious($routeIndex);
716
                        $views[$id]["data"]["next"] = $next;
717
                        $views[$id]["data"]["previous"] = $previous;
718
                        break;
719
720
                    case "single": // OBSOLETE
721
                    case "content":
722
                        $baseurl = $this->getBaseurl($views, $id);
723
                        $views[$id] = $this->getAdditionalViewDataForRoute($view, $meta["route"], $baseurl);
724
                        break;
725
726
                    case "columns":
727
                        $baseurl = $this->getBaseurl($views, $id);
728
                        $columns = $meta["columns"];
729
                        foreach ($columns as $key => $value) {
730
                            $data = $this->getViewDataForRoute($value["route"], $baseurl);
731
                            $columns[$key] = $data;
732
                        }
733
                        $views[$id]["data"]["columns"] = $columns;
734
                        break;
735
736 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...
737
                        $baseRoute = dirname($routeIndex);
738
                        $toc = $this->meta[$baseRoute]["__toc__"];
739
                        $this->orderAndlimitToc($toc, $meta);
740
                        $views[$id]["data"]["toc"] = $toc;
741
                        $views[$id]["data"]["meta"] = $meta;
742
                        break;
743
744
                    default:
745
                        throw new Exception(t("Unsupported data/meta/type for additional content."));
746
                }
747
            }
748
        }
749
    }
750
751
752
753
    /**
754
     * Get basurl from view, if it is defined.
755
     *
756
     * @param array  $views   data for all views.
757
     * @param string $current for current view if any.
758
     *
759
     * @return string | null as baseurl.
760
     */
761
    private function getBaseurl($views, $current = null)
762
    {
763
        $baseurl = isset($views["main"]["data"]["baseurl"])
764
            ? $views["main"]["data"]["baseurl"]
765
            : null;
766
767
        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...
768
            $baseurl = isset($views[$current]["data"]["baseurl"])
769
                ? $views[$current]["data"]["baseurl"]
770
                : $baseurl;
771
        }
772
773
        return $baseurl;
774
    }
775
776
777
778
    /**
779
     * Parse text, find and update all a href to use baseurl.
780
     *
781
     * @param object &$filtered with text and excerpt to process.
782
     * @param string $baseurl   add as baseurl for all relative urls.
783
     *
784
     * @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...
785
     */
786
    private function addBaseurl2AnchorUrls(&$filtered, $baseurl)
787
    {
788
        $textf  = $this->di->get("textFilter");
789
        $url    = $this->di->get("url");
790
791
        // Use callback to url->create() instead of string concat
792
        $callback = function ($route) use ($url, $baseurl) {
793
            return $url->create($route, $baseurl);
794
        };
795
796
        $filtered->text =
797
            $textf->addBaseurlToRelativeLinks($filtered->text, $baseurl, $callback);
798
    }
799
800
801
802
    /**
803
     * Load extra info intro views based of meta information provided in each
804
     * view.
805
     *
806
     * @param string $key     array with all views.
807
     * @param string $content array with all views.
808
     *
809
     * @throws NotFoundException when mapping can not be done.
810
     *
811
     * @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...
812
     */
813
    private function loadFileContent($key, $content)
814
    {
815
        // Settings from config
816
        $basepath = $this->config["basepath"];
817
        $filter   = $this->config["textfilter"];
818
819
        // Whole path to file
820
        $path = $basepath . "/" . $content["file"];
821
        $content["path"] = $path;
822
823
        // Load content from file
824
        if (!is_file($path)) {
825
            $msg = t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]);
826
            throw new \Anax\Exception\NotFoundException($msg);
827
        }
828
829
        // Get filtered content
830
        $src = file_get_contents($path);
831
        $filtered = $this->di->get("textFilter")->parse($src, $filter);
832
833
        return [$content, $filtered];
834
    }
835
836
837
838
    /**
839
     * Look up the route in the index and use that to retrieve the filtered
840
     * content.
841
     *
842
     * @param string $route to look up.
843
     *
844
     * @return array with content and filtered version.
845
     */
846
    public function mapRoute2Content($route)
847
    {
848
        // Look it up in the index
849
        list($keyIndex, $content) = $this->mapRoute2Index($route);
850
        list($content, $filtered) = $this->loadFileContent($keyIndex, $content);
851
852
        return [$keyIndex, $content, $filtered];
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
876
877
    /**
878
     * Map url to content, even internal content, if such mapping can be done.
879
     *
880
     * @param string $route optional route to look up.
881
     *
882
     * @return object with content and filtered version.
883
     */
884
    public function contentForInternalRoute($route = null)
885
    {
886
        // Get the route
887
        if (is_null($route)) {
888
            $route = $this->di->get("request")->getRoute();
889
        }
890
891
        // TODO cache route content.
892
893
        // Load index and map route to content
894
        $this->loadIndex();
895
        $this->loadMetaIndex();
896
        list($routeIndex, $content, $filtered) = $this->mapRoute2Content($route);
897
898
        // TODO Should not supply all frontmatter to theme, only the
899
        // parts valid to the index template. Separate that data into own
900
        // holder in frontmatter. Do not include whole frontmatter? Only
901
        // on debg?
902
        $content["frontmatter"] = $filtered->frontmatter;
903
904
        // Create and arrange the content as views, merge with .meta,
905
        // frontmatter is complete.
906
        $content["views"] = $this->getViews($routeIndex, $filtered->frontmatter);
907
908
        // Update all anchor urls to use baseurl, needs info about baseurl
909
        // from merged frontmatter
910
        $baseurl = $this->getBaseurl($content["views"]);
911
        $this->addBaseurl2AnchorUrls($filtered, $baseurl);
912
913
        // Add excerpt and hasMore, if available
914
        $this->di->get("textFilter")->addExcerpt($filtered);
915
916
        //
917
        // TODO Load content, pure or use data available
918
        // own function
919
        // perhaps load in separate view
920
        //
921
        $content["views"]["main"]["data"]["content"] = $filtered->text;
922
        $content["views"]["main"]["data"]["excerpt"] = $filtered->excerpt;
923
        $this->loadAdditionalContent($content["views"], $route, $routeIndex);
924
925
        return (object) $content;
926
    }
927
}
928