Completed
Push — master ( d0681a...916c2c )
by Mikael
02:18
created

CFileBasedContent::getAdditionalViewDataForRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
ccs 0
cts 2
cp 0
rs 9.4285
cc 1
eloc 6
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
24
    /**
25
     * Set default values from configuration.
26
     *
27
     * @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...
28
     */
29
    public function setDefaultsFromConfiguration()
30
    {
31
        $this->ignoreCache = isset($this->config["ignoreCache"])
32
            ? $this->config["ignoreCache"]
33
            : $this->ignoreCache;
34
35
        return $this;
36
    }
37
38
39
40
    /**
41
     * Should the cache be used or ignored.
42
     *
43
     * @param boolean $use true to use the cache or false to ignore the cache
44
     *
45
     * @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...
46
     */
47
    public function useCache($use)
48
    {
49
        $this->ignoreCache = !$use;
50
51
        return $this;
52
    }
53
54
55
56
    /**
57
     * Get the index as an array.
58
     *
59
     * @return array as index.
60
     */
61
    public function getIndex()
62
    {
63
        return $this->loadIndex();
64
    }
65
66
67
68
    /**
69
     * Create the index of all content into an array.
70
     *
71
     * @return array as index.
72
     */
73 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...
74
    {
75
        if ($this->index) {
76
            return $this->index;
77
        }
78
79
        $key = $this->di->cache->createKey(__CLASS__, "index");
80
        $this->index = $this->di->cache->get($key);
81
82
        if (!$this->index || $this->ignoreCache) {
83
            $this->index = $this->createIndex();
84
            $this->di->cache->put($key, $this->index);
85
        }
86
87
        return $this->index;
88
    }
89
90
91
92
    /**
93
     * Generate an index from the directory structure.
94
     *
95
     * @return array as table of content.
96
     */
97
    private function createIndex()
98
    {
99
        $basepath   = $this->config["basepath"];
100
        $pattern    = $this->config["pattern"];
101
        $path       = "$basepath/$pattern";
102
103
        $index = [];
104
        foreach (glob_recursive($path) as $file) {
105
            $filepath = substr($file, strlen($basepath) + 1);
106
107
            // Find content files
108
            $matches = [];
109
            preg_match("#^(\d*)_*([^\.]+)\.md$#", basename($filepath), $matches);
110
            $dirpart = dirname($filepath) . "/";
111
            if ($dirpart === "./") {
112
                $dirpart = null;
113
            }
114
            $key = $dirpart . $matches[2];
115
116
            // Create level depending on the file id
117
            $id = $matches[1];
118
            $level = 2;
119 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...
120
                $level = 0;
121
            } elseif ($id % 10 === 0) {
122
                $level = 1;
123
            }
124
125
            $index[$key] = [
126
                "file"    => $filepath,
127
                "section" => $matches[1],
128
                "level"   => $level,
129
            ];
130
        }
131
132
        return $index;
133
    }
134
135
136
137
    /**
138
     * Create the index of all meta content into an array.
139
     *
140
     * @return array as index.
141
     */
142 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...
143
    {
144
        if ($this->meta) {
145
            return $this->meta;
146
        }
147
148
        $key = $this->di->cache->createKey(__CLASS__, "meta");
149
        $this->meta = $this->di->cache->get($key);
150
151
        if (!$this->meta || $this->ignoreCache) {
152
            $this->meta = $this->createMetaIndex();
153
            $this->di->cache->put($key, $this->meta);
154
        }
155
156
        return $this->meta;
157
    }
158
159
160
161
    /**
162
     * Generate an index for meta files.
163
     *
164
     * @return array as table of content.
165
     */
166
    private function createMetaIndex()
167
    {
168
        $basepath = $this->config["basepath"];
169
        $filter   = $this->config["metafilter"];
170
        $meta     = $this->config["meta"];
171
        $path     = "$basepath/$meta";
172
173
        $meta = [];
174
        foreach (glob_recursive($path) as $file) {
175
            $filepath = substr($file, strlen($basepath) + 1);
176
            $src = file_get_contents($file);
177
            $filtered = $this->di->textFilter->parse($src, $filter);
178
179
            $key = dirname($filepath);
180
            $meta[$key] = $filtered->frontmatter;
181
182
            // Add Toc to the data array
183
            if (isset($meta[$key]["toc"])) {
184
                $meta[$key]["toc"]["data"]["toc"] = $this->createBaseRouteToc(dirname($filepath));
185
            }
186
        }
187
188
        return $meta;
189
    }
190
191
192
193
    /**
194
     * Get a reference to meta data for specific route.
195
     *
196
     * @param string $route current route used to access page.
197
     *
198
     * @return array as table of content.
199
     */
200
    private function getMetaForRoute($route)
201
    {
202
        $base = dirname($route);
203
        return isset($this->meta[$base])
204
            ? $this->meta[$base]
205
            : null;
206
    }
207
208
209
210
    /**
211
     * Get the title of a document.
212
     *
213
     * @param string $file to get title from.
214
     *
215
     * @return string as the title.
216
     */
217
    private function getTitle($file)
218
    {
219
        $basepath = $this->config["basepath"];
220
        $filter   = $this->config["textfilter"];
221
222
        $path = $basepath . "/" . $file;
223
        $src = file_get_contents($path);
224
        $filtered = $this->di->textFilter->parse($src, $filter);
225
        return $filtered->frontmatter["title"];
226
    }
227
228
229
230
    /**
231
     * Create a table of content for routes at particular level.
232
     *
233
     * @param string $route base route to use.
234
     *
235
     * @return array as the toc.
236
     */
237
    private function createBaseRouteToc($route)
238
    {
239
        $toc = [];
240
        $len = strlen($route);
241
242
echo "TOC for $route<br>";
243
        foreach ($this->index as $key => $value) {
244
            if (substr($key, 0, $len) === $route) {
245
                echo "MATCH $key<br>";
246
                $toc[$key] = $value;
247
                $toc[$key]["title"] = $this->getTitle($value["file"]);
248
            }
249
        };
250
        echo "DONE<br>";
251
252
        return $toc;
253
    }
254
255
256
257
    /**
258
     * Map the route to the correct entry in the index.
259
     *
260
     * @param string $route current route used to access page.
261
     *
262
     * @return array as the matched route.
263
     */
264
    private function mapRoute2Index($route)
265
    {
266
        if (key_exists($route, $this->index)) {
267
            return [$route, $this->index[$route]];
268
        } elseif (empty($route) && key_exists("index", $this->index)) {
269
            return ["index", $this->index["index"]];
270
        } elseif (key_exists($route . "/index", $this->index)) {
271
            return ["$route/index", $this->index["$route/index"]];
272
        }
273
274
        throw new \Anax\Exception\NotFoundException(t("The route '!ROUTE' does not exists in the index.", ["!ROUTE" => $route]));
275
    }
276
277
278
279
    /**
280
     * Get view by mergin information from meta and frontmatter.
281
     *
282
     * @param string $route       current route used to access page.
283
     * @param array  $frontmatter for the content.
284
     * @param string $key         for the view to retrive.
285
     * @param string $distinct    how to merge the array.
286
     *
287
     * @return array with data to add as view.
288
     */
289
    private function getView($route, $frontmatter, $key, $distinct = true)
290
    {
291
        $view = [];
292
293
        // From meta frontmatter
294
        $meta = $this->getMetaForRoute($route);
295
        if ($meta && isset($meta[$key])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meta 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...
296
            $view = $meta[$key];
297
        }
298
299
        // From document frontmatter
300
        if (isset($frontmatter[$key])) {
301
            if ($distinct) {
302
                $view = array_merge_recursive_distinct($view, $frontmatter[$key]);
303
            } else {
304
                $view = array_merge($view, $frontmatter[$key]);
305
            }
306
        }
307
308
        return $view;
309
    }
310
311
312
313
    /**
314
     * Get details on extra views.
315
     *
316
     * @param string $route       current route used to access page.
317
     * @param array  $frontmatter for the content.
318
     *
319
     * @return array with page data to send to view.
320
     */
321
    private function getViews($route, $frontmatter)
322
    {
323
        $views = $this->getView($route, $frontmatter, "views", false);
324
        $views["toc"]  = $this->getView($route, $frontmatter, "toc");
325
        $views["main"] = $this->getView($route, $frontmatter, "main");
326
327
        if (!isset($views["main"]["template"])) {
328
            $views["main"]["template"] = $this->config["template"];
329
        }
330
331
        return $views;
332
    }
333
334
335
336
    /**
337
     * Load extra info intro views based of meta information provided in each
338
     * view.
339
     *
340
     * @param string $view  with current settings.
341
     * @param string $route to load view from.
342
     *
343
     * @return array with view details.
344
     */
345
    private function getAdditionalViewDataForRoute($view, $route)
346
    {
347
        // Get filtered content from route
348
        list(, , $filtered) =
349
            $this->mapRoute2Content($route);
350
351
        // From document frontmatter
352
        $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...
353
        $view["data"]["content"] = $filtered->text;
354
355
        return $view;
356
357
    }
358
359
360
361
    /**
362
     * Load extra info intro views based of meta information provided in each
363
     * view.
364
     *
365
     * @param array &$views array with all views.
366
     *
367
     * @throws NotFoundException when mapping can not be done.
368
     *
369
     * @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...
370
     */
371
    private function loadAdditionalContent(&$views)
372
    {
373
        foreach ($views as $id => $view) {
374
            $meta = isset($view["data"]["meta"])
375
                ? $view["data"]["meta"]
376
                : null;
377
378
            if (is_array($meta)) {
379
                switch ($meta["type"]) {
380
                    case "multi":
381
382
                    break;
383
384
                    case "single":
385
                        $views[$id] = $this->getAdditionalViewDataForRoute($view, $meta["route"]);
386
                    break;
387
388
                    default:
389
                        throw new Exception(t("Unsupported data/meta/type."));
390
                }
391
            }
392
        }
393
    }
394
395
396
397
    /**
398
     * Load extra info intro views based of meta information provided in each
399
     * view.
400
     *
401
     * @param string $key     array with all views.
402
     * @param string $content array with all views.
403
     *
404
     * @throws NotFoundException when mapping can not be done.
405
     *
406
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

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

Loading history...
407
     */
408
    private function loadFileContent($key, $content)
409
    {
410
        // Settings from config
411
        $basepath = $this->config["basepath"];
412
        $filter   = $this->config["textfilter"];
413
414
        // Whole path to file
415
        $path = $basepath . "/" . $content["file"];
416
        $content["path"] = $path;
417
418
        // Load content from file
419
        if (!is_file($path)) {
420
            throw new \Anax\Exception\NotFoundException(t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]));
421
        }
422
423
        // Get filtered content
424
        $src = file_get_contents($path);
425
        $filtered = $this->di->textFilter->parse($src, $filter);
426
427
        return [$content, $filtered];
428
    }
429
430
431
432
    /**
433
     * Look up the route in the index and use that to retrieve the filtered
434
     * content.
435
     *
436
     * @param string $route to look up.
437
     *
438
     * @return array with content and filtered version.
439
     */
440
    public function mapRoute2Content($route)
441
    {
442
        // Look it up in the index
443
        list($keyIndex, $content) = $this->mapRoute2Index($route);
444
        list($content, $filtered) = $this->loadFileContent($keyIndex, $content);
445
446
        return [$keyIndex, $content, $filtered];
447
    }
448
449
450
451
    /**
452
     * Map url to content if such mapping can be done.
453
     *
454
     * @param string $route optional route to look up.
455
     *
456
     * @return object with content and filtered version.
457
     */
458
    public function contentForRoute($route = null)
459
    {
460
        // Get the route
461
        if (is_null($route)) {
462
            $route = $this->di->request->getRoute();
463
        }
464
465
        // TODO cache route content.
466
467
        // Load index and map route to content
468
        $this->loadIndex();
469
        $this->loadMetaIndex();
470
        list($keyIndex, $content, $filtered) = $this->mapRoute2Content($route);
471
472
        // TODO Should not supply all frontmatter to theme, only the
473
        // parts valid to the index template. Separate that data into own
474
        // holder in frontmatter. Do not include whole frontmatter? Only
475
        // on debg?
476
        $content["frontmatter"] = $filtered->frontmatter;
477
478
        // Create and arrange the content as views
479
        $content["views"] = $this->getViews($keyIndex, $filtered->frontmatter);
480
481
        //
482
        // TODO Load content, pure or use data available
483
        // own functuion
484
        // perhaps load in separate view
485
        //
486
        $content["views"]["main"]["data"]["content"] = $filtered->text;
487
        $this->loadAdditionalContent($content["views"]);
488
489
        return (object) $content;
490
    }
491
}
492