This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Anax\Content; |
||
4 | |||
5 | use Anax\Commons\ContainerInjectableInterface; |
||
6 | use Anax\Commons\ContainerInjectableTrait; |
||
7 | use Anax\Route\Exception\NotFoundException; |
||
8 | |||
9 | /** |
||
10 | * Pages based on file content. |
||
11 | */ |
||
12 | class FileBasedContent implements ContainerInjectableInterface |
||
13 | { |
||
14 | use ContainerInjectableTrait, |
||
15 | FBCBreadcrumbTrait, |
||
16 | FBCLoadAdditionalContentTrait, |
||
17 | FBCUtilitiesTrait; |
||
18 | |||
19 | |||
20 | |||
21 | /** |
||
22 | * All routes. |
||
23 | */ |
||
24 | private $index = null; |
||
25 | |||
26 | /** |
||
27 | * All authors. |
||
28 | */ |
||
29 | private $author = null; |
||
30 | |||
31 | /** |
||
32 | * All categories. |
||
33 | */ |
||
34 | private $category = null; |
||
35 | |||
36 | /** |
||
37 | * All routes having meta. |
||
38 | */ |
||
39 | private $meta = null; |
||
40 | |||
41 | /** |
||
42 | * This is the base route. |
||
43 | */ |
||
44 | private $baseRoute = null; |
||
45 | |||
46 | /** |
||
47 | * This is the extendede meta route, if any. |
||
48 | */ |
||
49 | private $metaRoute = null; |
||
50 | |||
51 | /** |
||
52 | * This is the current page, to supply pagination, if used. |
||
53 | */ |
||
54 | private $currentPage = null; |
||
55 | |||
56 | /** |
||
57 | * Use cache or recreate each time. |
||
58 | */ |
||
59 | private $ignoreCache = false; |
||
60 | |||
61 | /** |
||
62 | * File name pattern, all files must match this pattern and the first |
||
63 | * numbered part is optional, the second part becomes the route. |
||
64 | */ |
||
65 | private $filenamePattern = "#^(\d*)_*([^\.]+)\.md$#"; |
||
66 | |||
67 | /** |
||
68 | * Internal routes that is marked as internal content routes and not |
||
69 | * exposed as public routes. |
||
70 | */ |
||
71 | private $internalRouteDirPattern = [ |
||
72 | "#block/#", |
||
73 | ]; |
||
74 | |||
75 | private $internalRouteFilePattern = [ |
||
76 | "#^block[_-]{1}#", |
||
77 | "#^_#", |
||
78 | ]; |
||
79 | |||
80 | /** |
||
81 | * Routes that should be used in toc. |
||
82 | */ |
||
83 | private $allowedInTocPattern = "([\d]+_(\w)+)"; |
||
84 | |||
85 | |||
86 | |||
87 | /** |
||
88 | * Set default values from configuration. |
||
89 | * |
||
90 | * @param array $config the configuration to use. |
||
91 | * |
||
92 | * @return void |
||
93 | */ |
||
94 | 1 | public function configure(array $config) : void |
|
95 | { |
||
96 | 1 | $this->config = $config; |
|
0 ignored issues
–
show
|
|||
97 | 1 | $this->setDefaultsFromConfiguration(); |
|
98 | 1 | } |
|
99 | |||
100 | |||
101 | |||
102 | /** |
||
103 | * Set default values from configuration. |
||
104 | * |
||
105 | * @return this. |
||
106 | */ |
||
107 | 1 | private function setDefaultsFromConfiguration() |
|
108 | { |
||
109 | 1 | $this->ignoreCache = isset($this->config["ignoreCache"]) |
|
110 | 1 | ? $this->config["ignoreCache"] |
|
111 | : $this->ignoreCache; |
||
112 | |||
113 | 1 | return $this; |
|
114 | } |
||
115 | |||
116 | |||
117 | |||
118 | /** |
||
119 | * Should the cache be used or ignored. |
||
120 | * |
||
121 | * @param boolean $use true to use the cache or false to ignore the cache |
||
122 | * |
||
123 | * @return this. |
||
124 | */ |
||
125 | public function useCache($use) |
||
126 | { |
||
127 | $this->ignoreCache = !$use; |
||
128 | |||
129 | return $this; |
||
130 | } |
||
131 | |||
132 | |||
133 | |||
134 | /** |
||
135 | * Create the index of all content into an array. |
||
136 | * |
||
137 | * @param string $type of index to load. |
||
138 | * |
||
139 | * @return void. |
||
140 | */ |
||
141 | private function load($type) |
||
142 | { |
||
143 | $index = $this->$type; |
||
144 | if ($index) { |
||
145 | return; |
||
146 | } |
||
147 | |||
148 | $cache = $this->di->get("cache"); |
||
149 | $key = $cache->createKey(__CLASS__, $type); |
||
150 | $index = $cache->get($key); |
||
151 | |||
152 | if (is_null($index) || $this->ignoreCache) { |
||
153 | $createMethod = "create$type"; |
||
154 | $index = $this->$createMethod(); |
||
155 | $cache->set($key, $index); |
||
156 | } |
||
157 | |||
158 | $this->$type = $index; |
||
159 | } |
||
160 | |||
161 | |||
162 | |||
163 | |||
164 | // = Create and manage index ================================== |
||
165 | |||
166 | /** |
||
167 | * Generate an index from the directory structure. |
||
168 | * |
||
169 | * @return array as index for all content files. |
||
170 | */ |
||
171 | private function createIndex() |
||
172 | { |
||
173 | $basepath = $this->config["basePath"]; |
||
174 | $pattern = $this->config["pattern"]; |
||
175 | $path = "$basepath/$pattern"; |
||
176 | |||
177 | $index = []; |
||
178 | foreach (glob_recursive($path) as $file) { |
||
179 | $filepath = substr($file, strlen($basepath) + 1); |
||
180 | |||
181 | // Find content files |
||
182 | $matches = []; |
||
183 | preg_match($this->filenamePattern, basename($filepath), $matches); |
||
184 | $dirpart = dirname($filepath) . "/"; |
||
185 | if ($dirpart === "./") { |
||
186 | $dirpart = null; |
||
187 | } |
||
188 | $key = $dirpart . $matches[2]; |
||
189 | |||
190 | // Create level depending on the file id |
||
191 | // TODO ciamge doc, can be replaced by __toc__ in meta? |
||
192 | $id = (int) $matches[1]; |
||
193 | $level = 2; |
||
194 | if ($id % 100 === 0) { |
||
195 | $level = 0; |
||
196 | } elseif ($id % 10 === 0) { |
||
197 | $level = 1; |
||
198 | } |
||
199 | |||
200 | $index[$key] = [ |
||
201 | "file" => $filepath, |
||
202 | "section" => $matches[1], |
||
203 | "level" => $level, // TODO ? |
||
204 | "internal" => $this->isInternalRoute($filepath), |
||
205 | "tocable" => $this->allowInToc($filepath), |
||
206 | ]; |
||
207 | } |
||
208 | |||
209 | return $index; |
||
210 | } |
||
211 | |||
212 | |||
213 | |||
214 | /** |
||
215 | * Check if a filename is to be marked as an internal route.. |
||
216 | * |
||
217 | * @param string $filepath as the basepath (routepart) to the file. |
||
218 | * |
||
219 | * @return boolean true if the route content is internal, else false |
||
220 | */ |
||
221 | private function isInternalRoute($filepath) |
||
222 | { |
||
223 | foreach ($this->internalRouteDirPattern as $pattern) { |
||
224 | if (preg_match($pattern, $filepath)) { |
||
225 | return true; |
||
226 | } |
||
227 | } |
||
228 | |||
229 | $filename = basename($filepath); |
||
230 | foreach ($this->internalRouteFilePattern as $pattern) { |
||
231 | if (preg_match($pattern, $filename)) { |
||
232 | return true; |
||
233 | } |
||
234 | } |
||
235 | |||
236 | return false; |
||
237 | } |
||
238 | |||
239 | |||
240 | |||
241 | /** |
||
242 | * Check if filepath should be used as part of toc. |
||
243 | * |
||
244 | * @param string $filepath as the basepath (routepart) to the file. |
||
245 | * |
||
246 | * @return boolean true if the route content shoul dbe in toc, else false |
||
247 | */ |
||
248 | private function allowInToc($filepath) |
||
249 | { |
||
250 | return (boolean) preg_match($this->allowedInTocPattern, $filepath); |
||
251 | } |
||
252 | |||
253 | |||
254 | |||
255 | // = Create and manage meta ================================== |
||
256 | |||
257 | /** |
||
258 | * Generate an index for meta files. |
||
259 | * |
||
260 | * @return array as meta index. |
||
261 | */ |
||
262 | private function createMeta() |
||
263 | { |
||
264 | $basepath = $this->config["basePath"]; |
||
265 | $filter = $this->config["textfilter-frontmatter"]; |
||
266 | $pattern = $this->config["meta"]; |
||
267 | $path = "$basepath/$pattern"; |
||
268 | $textfilter = $this->di->get("textfilter"); |
||
269 | |||
270 | $index = []; |
||
271 | foreach (glob_recursive($path) as $file) { |
||
272 | // The key entry to index |
||
273 | $key = dirname(substr($file, strlen($basepath) + 1)); |
||
274 | |||
275 | // Get info from base document |
||
276 | $src = file_get_contents($file); |
||
277 | $filtered = $textfilter->parse($src, $filter); |
||
278 | $index[$key] = $filtered->frontmatter; |
||
279 | |||
280 | // Add Toc to the data array |
||
281 | $index[$key]["__toc__"] = $this->createBaseRouteToc($key); |
||
282 | } |
||
283 | |||
284 | // Add author details |
||
285 | $this->meta = $index; |
||
286 | $this->createAuthor(); |
||
287 | $this->createCategory(); |
||
288 | |||
289 | return $this->meta; |
||
290 | } |
||
291 | |||
292 | |||
293 | |||
294 | /** |
||
295 | * Get a reference to meta data for specific route. |
||
296 | * |
||
297 | * @param string $route current route used to access page. |
||
298 | * |
||
299 | * @return array as table of content. |
||
300 | */ |
||
301 | private function getMetaForRoute($route) |
||
302 | { |
||
303 | $base = dirname($route); |
||
304 | return isset($this->meta[$base]) |
||
305 | ? $this->meta[$base] |
||
306 | : []; |
||
307 | } |
||
308 | |||
309 | |||
310 | |||
311 | /** |
||
312 | * Create a table of content for routes at particular level. |
||
313 | * |
||
314 | * @param string $route base route to use. |
||
315 | * |
||
316 | * @return array as the toc. |
||
317 | */ |
||
318 | private function createBaseRouteToc($route) |
||
319 | { |
||
320 | $toc = []; |
||
321 | $len = strlen($route); |
||
322 | |||
323 | foreach ($this->index as $key => $value) { |
||
324 | if (substr($key, 0, $len + 1) === "$route/") { |
||
325 | if ($value["internal"] === false |
||
326 | && $value["tocable"] === true) { |
||
327 | $toc[$key] = $value; |
||
328 | |||
329 | $frontm = $this->getFrontmatter($value["file"]); |
||
330 | $toc[$key]["title"] = $frontm["title"]; |
||
331 | $toc[$key]["publishTime"] = $this->getPublishTime($frontm); |
||
332 | $toc[$key]["sectionHeader"] = isset($frontm["sectionHeader"]) |
||
333 | ? $frontm["sectionHeader"] |
||
334 | : null; |
||
335 | $toc[$key]["linkable"] = isset($frontm["linkable"]) |
||
336 | ? $frontm["linkable"] |
||
337 | : null; |
||
338 | } |
||
339 | } |
||
340 | }; |
||
341 | |||
342 | return $toc; |
||
343 | } |
||
344 | |||
345 | |||
346 | |||
347 | // = Deal with authors ==================================== |
||
348 | |||
349 | /** |
||
350 | * Generate a lookup index for authors that maps into the meta entry |
||
351 | * for the author. |
||
352 | * |
||
353 | * @return void. |
||
354 | */ |
||
355 | private function createAuthor() |
||
356 | { |
||
357 | $pattern = $this->config["author"]; |
||
358 | |||
359 | $index = []; |
||
360 | $matches = []; |
||
361 | foreach ($this->meta as $key => $entry) { |
||
362 | if (preg_match($pattern, $key, $matches)) { |
||
363 | $acronym = $matches[1]; |
||
364 | $index[$acronym] = $key; |
||
365 | $this->meta[$key]["acronym"] = $acronym; |
||
366 | $this->meta[$key]["url"] = $key; |
||
367 | unset($this->meta[$key]["__toc__"]); |
||
368 | |||
369 | // Get content for byline |
||
370 | $route = "$key/byline"; |
||
371 | $data = $this->getDataForAdditionalRoute($route); |
||
372 | $byline = isset($data["data"]["content"]) ? $data["data"]["content"] : null; |
||
373 | $this->meta[$key]["byline"] = $byline; |
||
374 | } |
||
375 | } |
||
376 | |||
377 | return $index; |
||
378 | } |
||
379 | |||
380 | |||
381 | |||
382 | /** |
||
383 | * Load details for the author. |
||
384 | * |
||
385 | * @param array|string $author with details on the author(s). |
||
386 | * |
||
387 | * @return array with more details on the authors(s). |
||
388 | */ |
||
389 | View Code Duplication | private function loadAuthorDetails($author) |
|
390 | { |
||
391 | if (is_array($author) && is_array(array_values($author)[0])) { |
||
392 | return $author; |
||
393 | } |
||
394 | |||
395 | if (!is_array($author)) { |
||
396 | $tmp = $author; |
||
397 | $author = []; |
||
398 | $author[] = $tmp; |
||
399 | } |
||
400 | |||
401 | $authors = []; |
||
402 | foreach ($author as $acronym) { |
||
403 | if (isset($this->author[$acronym])) { |
||
404 | $key = $this->author[$acronym]; |
||
405 | $authors[$acronym] = $this->meta[$key]; |
||
406 | } else { |
||
407 | $authors[$acronym]["acronym"] = $acronym; |
||
408 | } |
||
409 | } |
||
410 | |||
411 | return $authors; |
||
412 | } |
||
413 | |||
414 | |||
415 | |||
416 | // = Deal with categories ==================================== |
||
417 | |||
418 | /** |
||
419 | * Generate a lookup index for categories that maps into the meta entry |
||
420 | * for the category. |
||
421 | * |
||
422 | * @return void. |
||
423 | */ |
||
424 | private function createCategory() |
||
425 | { |
||
426 | $pattern = $this->config["category"]; |
||
427 | |||
428 | $index = []; |
||
429 | $matches = []; |
||
430 | foreach ($this->meta as $key => $entry) { |
||
431 | if (preg_match($pattern, $key, $matches)) { |
||
432 | $catKey = $matches[1]; |
||
433 | $index[$catKey] = $key; |
||
434 | $this->meta[$key]["key"] = $catKey; |
||
435 | $this->meta[$key]["url"] = $key; |
||
436 | unset($this->meta[$key]["__toc__"]); |
||
437 | } |
||
438 | } |
||
439 | |||
440 | return $index; |
||
441 | } |
||
442 | |||
443 | |||
444 | |||
445 | /** |
||
446 | * Find next and previous links of current content. |
||
447 | * |
||
448 | * @param array|string $author with details on the category(s). |
||
0 ignored issues
–
show
There is no parameter named
$author . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @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...
|
|||
449 | * |
||
450 | * @return array with more details on the category(s). |
||
451 | */ |
||
452 | View Code Duplication | private function loadCategoryDetails($category) |
|
453 | { |
||
454 | if (is_array($category) && is_array(array_values($category)[0])) { |
||
455 | return $category; |
||
456 | } |
||
457 | |||
458 | if (!is_array($category)) { |
||
459 | $tmp = $category; |
||
460 | $category = []; |
||
461 | $category[] = $tmp; |
||
462 | } |
||
463 | |||
464 | $categorys = []; |
||
465 | foreach ($category as $catKey) { |
||
466 | if (isset($this->category[$catKey])) { |
||
467 | $key = $this->category[$catKey]; |
||
468 | $categorys[$catKey] = $this->meta[$key]; |
||
469 | } else { |
||
470 | $categorys[$catKey]["key"] = $catKey; |
||
471 | } |
||
472 | } |
||
473 | |||
474 | return $categorys; |
||
475 | } |
||
476 | |||
477 | |||
478 | |||
479 | |||
480 | // == Used by meta and breadcrumb (to get title) =========================== |
||
481 | // TODO REFACTOR THIS? |
||
482 | // Support getting only frontmatter. |
||
483 | // Merge with function that retrieves whole filtered since getting |
||
484 | // frontmatter will involve full parsing of document. |
||
485 | // Title is retrieved from the HTML code. |
||
486 | // Also do cacheing of each retrieved and parsed document |
||
487 | // in this cycle, to gather code that loads and parses a individual |
||
488 | // document. |
||
489 | |||
490 | /** |
||
491 | * Get the frontmatter of a document. |
||
492 | * |
||
493 | * @param string $file to get frontmatter from. |
||
494 | * |
||
495 | * @return array as frontmatter. |
||
496 | */ |
||
497 | private function getFrontmatter($file) |
||
498 | { |
||
499 | $basepath = $this->config["basePath"]; |
||
500 | $filter1 = $this->config["textfilter-frontmatter"]; |
||
501 | $filter2 = $this->config["textfilter-title"]; |
||
502 | $filter = array_merge($filter1, $filter2); |
||
503 | |||
504 | $path = $basepath . "/" . $file; |
||
505 | $src = file_get_contents($path); |
||
506 | $filtered = $this->di->get("textfilter")->parse($src, $filter); |
||
507 | return $filtered->frontmatter; |
||
508 | } |
||
509 | |||
510 | |||
511 | |||
512 | // == Look up route in index =================================== |
||
513 | |||
514 | /** |
||
515 | * Check if currrent route is a supported meta route. |
||
516 | * |
||
517 | * @param string $route current route used to access page. |
||
518 | * |
||
519 | * @return string as route. |
||
520 | */ |
||
521 | private function checkForMetaRoute($route) |
||
522 | { |
||
523 | $this->baseRoute = $route; |
||
524 | $this->metaRoute = null; |
||
525 | |||
526 | // If route exits in index, use it |
||
527 | if ($this->mapRoute2IndexKey($route)) { |
||
528 | return $route; |
||
529 | } |
||
530 | |||
531 | // Check for pagination |
||
532 | $pagination = $this->config["pagination"]; |
||
533 | $matches = []; |
||
534 | $pattern = "/(.*?)\/($pagination)\/(\d+)$/"; |
||
535 | if (preg_match($pattern, $route, $matches)) { |
||
536 | $this->baseRoute = $matches[1]; |
||
537 | $this->metaRoute = $route; |
||
538 | $this->currentPage = $matches[3]; |
||
539 | } |
||
540 | |||
541 | return $this->baseRoute; |
||
542 | } |
||
543 | |||
544 | |||
545 | |||
546 | /** |
||
547 | * Map the route to the correct key in the index. |
||
548 | * |
||
549 | * @param string $route current route used to access page. |
||
550 | * |
||
551 | * @return string as key or false if no match. |
||
552 | */ |
||
553 | private function mapRoute2IndexKey($route) |
||
554 | { |
||
555 | $route = rtrim($route, "/"); |
||
556 | |||
557 | if (key_exists($route, $this->index)) { |
||
558 | return $route; |
||
559 | } elseif (empty($route) && key_exists("index", $this->index)) { |
||
560 | return "index"; |
||
561 | } elseif (key_exists($route . "/index", $this->index)) { |
||
562 | return "$route/index"; |
||
563 | } |
||
564 | |||
565 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Anax\Content\FileBasedContent::mapRoute2IndexKey of type string .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function
Loading history...
|
|||
566 | } |
||
567 | |||
568 | |||
569 | |||
570 | /** |
||
571 | * Map the route to the correct entry in the index. |
||
572 | * |
||
573 | * @param string $route current route used to access page. |
||
574 | * |
||
575 | * @return array as the matched route. |
||
576 | */ |
||
577 | private function mapRoute2Index($route) |
||
578 | { |
||
579 | $routeIndex = $this->mapRoute2IndexKey($route); |
||
580 | |||
581 | if ($routeIndex) { |
||
582 | return [$routeIndex, $this->index[$routeIndex]]; |
||
583 | } |
||
584 | |||
585 | $msg = t("The route '!ROUTE' does not exists in the index.", [ |
||
586 | "!ROUTE" => $route |
||
587 | ]); |
||
588 | throw new NotFoundException($msg); |
||
589 | } |
||
590 | |||
591 | |||
592 | |||
593 | // = Get view data by merging from meta and current frontmatter ========= |
||
594 | |||
595 | /** |
||
596 | * Get view by mergin information from meta and frontmatter. |
||
597 | * |
||
598 | * @param string $route current route used to access page. |
||
599 | * @param array $frontmatter for the content. |
||
600 | * @param string $key for the view to retrive. |
||
601 | * |
||
602 | * @return array with data to add as view. |
||
603 | */ |
||
604 | private function getView($route, $frontmatter, $key) |
||
605 | { |
||
606 | $view = []; |
||
607 | |||
608 | // From meta frontmatter |
||
609 | $meta = $this->getMetaForRoute($route); |
||
610 | if (isset($meta[$key])) { |
||
611 | $view = $meta[$key]; |
||
612 | } |
||
613 | |||
614 | // From document frontmatter |
||
615 | if (isset($frontmatter[$key])) { |
||
616 | $view = array_merge_recursive_distinct($view, $frontmatter[$key]); |
||
617 | //$view = array_merge($view, $frontmatter[$key]); |
||
618 | } |
||
619 | |||
620 | return $view; |
||
621 | } |
||
622 | |||
623 | |||
624 | |||
625 | /** |
||
626 | * Get details on extra views. |
||
627 | * |
||
628 | * @param string $route current route used to access page. |
||
629 | * @param array $frontmatter for the content. |
||
630 | * |
||
631 | * @return array with page data to send to view. |
||
632 | */ |
||
633 | private function getViews($route, $frontmatter) |
||
634 | { |
||
635 | // Arrange data into views |
||
636 | $views = $this->getView($route, $frontmatter, "views", true); |
||
637 | |||
638 | // Set defaults |
||
639 | if (!isset($views["main"]["template"])) { |
||
640 | $views["main"]["template"] = $this->config["template"]; |
||
641 | } |
||
642 | if (!isset($views["main"]["data"])) { |
||
643 | $views["main"]["data"] = []; |
||
644 | } |
||
645 | |||
646 | // Merge remaining frontmatter into view main data. |
||
647 | $data = $this->getMetaForRoute($route); |
||
648 | unset($data["__toc__"]); |
||
649 | unset($data["views"]); |
||
650 | unset($frontmatter["views"]); |
||
651 | |||
652 | if ($frontmatter) { |
||
0 ignored issues
–
show
The expression
$frontmatter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
653 | $data = array_merge_recursive_distinct($data, $frontmatter); |
||
654 | } |
||
655 | $views["main"]["data"] = array_merge_recursive_distinct($views["main"]["data"], $data); |
||
656 | |||
657 | return $views; |
||
658 | } |
||
659 | |||
660 | |||
661 | |||
662 | // == Create and load content =================================== |
||
663 | |||
664 | /** |
||
665 | * Map url to content, even internal content, if such mapping can be done. |
||
666 | * |
||
667 | * @param string $route route to look up. |
||
668 | * |
||
669 | * @return object with content and filtered version. |
||
670 | */ |
||
671 | private function createContentForInternalRoute($route) |
||
672 | { |
||
673 | // Load index and map route to content |
||
674 | $this->load("index"); |
||
675 | $this->load("meta"); |
||
676 | $this->load("author"); |
||
677 | $this->load("category"); |
||
678 | |||
679 | // Match the route |
||
680 | $route = rtrim($route, "/"); |
||
681 | $route = $this->checkForMetaRoute($route); |
||
682 | list($routeIndex, $content, $filtered) = $this->mapRoute2Content($route); |
||
683 | |||
684 | // Create and arrange the content as views, merge with .meta, |
||
685 | // frontmatter is complete. |
||
686 | $content["views"] = $this->getViews($routeIndex, $filtered->frontmatter); |
||
687 | |||
688 | // Do process content step two when all frontmatter is included. |
||
689 | $this->processMainContentPhaseTwo($content, $filtered); |
||
690 | |||
691 | // Set details of content |
||
692 | $content["views"]["main"]["data"]["content"] = $filtered->text; |
||
693 | $content["views"]["main"]["data"]["excerpt"] = $filtered->excerpt; |
||
694 | $this->loadAdditionalContent($content["views"], $route, $routeIndex); |
||
695 | |||
696 | // TODO Should not supply all frontmatter to theme, only the |
||
697 | // parts valid to the index template. Separate that data into own |
||
698 | // holder in frontmatter. Do not include whole frontmatter? Only |
||
699 | // on debg? |
||
700 | $content["frontmatter"] = $filtered->frontmatter; |
||
701 | |||
702 | return (object) $content; |
||
703 | } |
||
704 | |||
705 | |||
706 | |||
707 | /** |
||
708 | * Look up the route in the index and use that to retrieve the filtered |
||
709 | * content. |
||
710 | * |
||
711 | * @param string $route to look up. |
||
712 | * |
||
713 | * @return array with content and filtered version. |
||
714 | */ |
||
715 | private function mapRoute2Content($route) |
||
716 | { |
||
717 | // Look it up in the index |
||
718 | list($keyIndex, $content) = $this->mapRoute2Index($route); |
||
719 | $filtered = $this->loadFileContentPhaseOne($keyIndex); |
||
720 | |||
721 | return [$keyIndex, $content, $filtered]; |
||
722 | } |
||
723 | |||
724 | |||
725 | |||
726 | /** |
||
727 | * Load content file and frontmatter, this is the first time we process |
||
728 | * the content. |
||
729 | * |
||
730 | * @param string $key to index with details on the route. |
||
731 | * |
||
732 | * @throws NotFoundException when mapping can not be done. |
||
733 | * |
||
734 | * @return void. |
||
735 | */ |
||
736 | private function loadFileContentPhaseOne($key) |
||
737 | { |
||
738 | // Settings from config |
||
739 | $basepath = $this->config["basePath"]; |
||
740 | $filter = $this->config["textfilter-frontmatter"]; |
||
741 | |||
742 | // Whole path to file |
||
743 | $path = $basepath . "/" . $this->index[$key]["file"]; |
||
744 | |||
745 | // Load content from file |
||
746 | if (!is_file($path)) { |
||
747 | $msg = t("The content '!ROUTE' does not exists as a file '!FILE'.", ["!ROUTE" => $key, "!FILE" => $path]); |
||
748 | throw new \Anax\Exception\NotFoundException($msg); |
||
749 | } |
||
750 | |||
751 | // Get filtered content |
||
752 | $src = file_get_contents($path); |
||
753 | $filtered = $this->di->get("textfilter")->parse($src, $filter); |
||
754 | |||
755 | return $filtered; |
||
756 | } |
||
757 | |||
758 | |||
759 | |||
760 | // == Process content phase 2 =================================== |
||
761 | // TODO REFACTOR THIS? |
||
762 | |||
763 | /** |
||
764 | * Look up the route in the index and use that to retrieve the filtered |
||
765 | * content. |
||
766 | * |
||
767 | * @param array &$content to process. |
||
768 | * @param object &$filtered to use for settings. |
||
769 | * |
||
770 | * @return array with content and filtered version. |
||
771 | */ |
||
772 | private function processMainContentPhaseTwo(&$content, &$filtered) |
||
773 | { |
||
774 | // From configuration |
||
775 | $filter = $this->config["textfilter"]; |
||
776 | $revisionStart = $this->config["revision-history"]["start"]; |
||
777 | $revisionEnd = $this->config["revision-history"]["end"]; |
||
778 | $revisionClass = $this->config["revision-history"]["class"]; |
||
779 | $revisionSource = isset($this->config["revision-history"]["source"]) |
||
780 | ? $this->config["revision-history"]["source"] |
||
781 | : null; |
||
782 | |||
783 | $textFilter = $this->di->get("textfilter"); |
||
784 | $text = $filtered->text; |
||
785 | |||
786 | // Check if revision history is to be included |
||
787 | if (isset($content["views"]["main"]["data"]["revision"])) { |
||
788 | $text = $textFilter->addRevisionHistory( |
||
789 | $text, |
||
790 | $content["views"]["main"]["data"]["revision"], |
||
791 | $revisionStart, |
||
792 | $revisionEnd, |
||
793 | $revisionClass, |
||
794 | $revisionSource . "/" . $content["file"] |
||
795 | ); |
||
796 | } |
||
797 | |||
798 | // Get new filtered content (and updated frontmatter) |
||
799 | // Title in frontmatter overwrites title found in content |
||
800 | $new = $textFilter->parse($text, $filter); |
||
801 | $filtered->text = $new->text; |
||
802 | |||
803 | // Keep title if defined in frontmatter |
||
804 | $title = isset($filtered->frontmatter["title"]) |
||
805 | ? $filtered->frontmatter["title"] |
||
806 | : null; |
||
807 | |||
808 | $filtered->frontmatter = array_merge_recursive_distinct( |
||
809 | $filtered->frontmatter, |
||
810 | $new->frontmatter |
||
811 | ); |
||
812 | |||
813 | if ($title) { |
||
814 | $filtered->frontmatter["title"] = $title; |
||
815 | } |
||
816 | |||
817 | // Main data is |
||
818 | $data = &$content["views"]["main"]["data"]; |
||
819 | |||
820 | // Update all anchor urls to use baseurl, needs info about baseurl |
||
821 | // from merged frontmatter |
||
822 | $baseurl = isset($data["baseurl"]) |
||
823 | ? $data["baseurl"] |
||
824 | : null; |
||
825 | $this->addBaseurl2AnchorUrls($filtered, $baseurl); |
||
826 | $this->addBaseurl2ImageSource($filtered, $baseurl); |
||
827 | |||
828 | // Add excerpt and hasMore, if available |
||
829 | $textFilter->addExcerpt($filtered); |
||
830 | |||
831 | // Load details on author, if set. |
||
832 | if (isset($data["author"])) { |
||
833 | $data["author"] = $this->loadAuthorDetails($data["author"]); |
||
834 | } |
||
835 | |||
836 | // Load details on category, if set. |
||
837 | if (isset($data["category"])) { |
||
838 | $data["category"] = $this->loadCategoryDetails($data["category"]); |
||
839 | } |
||
840 | } |
||
841 | |||
842 | |||
843 | |||
844 | // == Public methods ============================================ |
||
845 | |||
846 | /** |
||
847 | * Map url to content, even internal content, if such mapping can be done. |
||
848 | * |
||
849 | * @param string $route optional route to look up. |
||
850 | * |
||
851 | * @return object with content and filtered version. |
||
852 | */ |
||
853 | public function contentForInternalRoute($route = null) |
||
854 | { |
||
855 | // Get the route |
||
856 | if (is_null($route)) { |
||
857 | $route = $this->di->get("request")->getRoute(); |
||
858 | } |
||
859 | |||
860 | // Check cache for content or create cached version of content |
||
861 | $cache = $this->di->get("cache"); |
||
862 | $slug = $this->di->get("url")->slugify($route); |
||
863 | $key = $cache->createKey(__CLASS__, "route-$slug"); |
||
864 | $content = $cache->get($key); |
||
865 | |||
866 | if (!$content || $this->ignoreCache) { |
||
867 | $content = $this->createContentForInternalRoute($route); |
||
868 | $cache->set($key, $content); |
||
869 | } |
||
870 | |||
871 | return $content; |
||
872 | } |
||
873 | |||
874 | |||
875 | |||
876 | /** |
||
877 | * Map url to content if such mapping can be done, exclude internal routes. |
||
878 | * |
||
879 | * @param string $route optional route to look up. |
||
880 | * |
||
881 | * @return object with content and filtered version. |
||
882 | */ |
||
883 | public function contentForRoute($route = null) |
||
884 | { |
||
885 | $content = $this->contentForInternalRoute($route); |
||
886 | if ($content->internal === true) { |
||
887 | $msg = t("The content '!ROUTE' does not exists as a public route.", ["!ROUTE" => $route]); |
||
888 | throw new NotFoundException($msg); |
||
889 | } |
||
890 | |||
891 | return $content; |
||
892 | } |
||
893 | } |
||
894 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: