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 | /** |
||
4 | * Pico |
||
5 | * |
||
6 | * Pico is a stupidly simple, blazing fast, flat file CMS. |
||
7 | * - Stupidly Simple: Pico makes creating and maintaining a |
||
8 | * website as simple as editing text files. |
||
9 | * - Blazing Fast: Pico is seriously lightweight and doesn't |
||
10 | * use a database, making it super fast. |
||
11 | * - No Database: Pico is a "flat file" CMS, meaning no |
||
12 | * database woes, no MySQL queries, nothing. |
||
13 | * - Markdown Formatting: Edit your website in your favourite |
||
14 | * text editor using simple Markdown formatting. |
||
15 | * - Twig Templates: Pico uses the Twig templating engine, |
||
16 | * for powerful and flexible themes. |
||
17 | * - Open Source: Pico is completely free and open source, |
||
18 | * released under the MIT license. |
||
19 | * See <http://picocms.org/> for more info. |
||
20 | * |
||
21 | * @author Gilbert Pellegrom |
||
22 | * @author Daniel Rudolf |
||
23 | * @link <http://picocms.org> |
||
24 | * @license The MIT License <http://opensource.org/licenses/MIT> |
||
25 | * @version 1.0 |
||
26 | */ |
||
27 | class Pico |
||
28 | { |
||
29 | /** |
||
30 | * Sort files in alphabetical ascending order |
||
31 | * |
||
32 | * @see Pico::getFiles() |
||
33 | * @var int |
||
34 | */ |
||
35 | const SORT_ASC = 0; |
||
36 | |||
37 | /** |
||
38 | * Sort files in alphabetical descending order |
||
39 | * |
||
40 | * @see Pico::getFiles() |
||
41 | * @var int |
||
42 | */ |
||
43 | const SORT_DESC = 1; |
||
44 | |||
45 | /** |
||
46 | * Don't sort files |
||
47 | * |
||
48 | * @see Pico::getFiles() |
||
49 | * @var int |
||
50 | */ |
||
51 | const SORT_NONE = 2; |
||
52 | |||
53 | /** |
||
54 | * Root directory of this Pico instance |
||
55 | * |
||
56 | * @see Pico::getRootDir() |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $rootDir; |
||
60 | |||
61 | /** |
||
62 | * Config directory of this Pico instance |
||
63 | * |
||
64 | * @see Pico::getConfigDir() |
||
65 | * @var string |
||
66 | */ |
||
67 | protected $configDir; |
||
68 | |||
69 | /** |
||
70 | * Plugins directory of this Pico instance |
||
71 | * |
||
72 | * @see Pico::getPluginsDir() |
||
73 | * @var string |
||
74 | */ |
||
75 | protected $pluginsDir; |
||
76 | |||
77 | /** |
||
78 | * Themes directory of this Pico instance |
||
79 | * |
||
80 | * @see Pico::getThemesDir() |
||
81 | * @var string |
||
82 | */ |
||
83 | protected $themesDir; |
||
84 | |||
85 | /** |
||
86 | * Boolean indicating whether Pico started processing yet |
||
87 | * |
||
88 | * @var boolean |
||
89 | */ |
||
90 | protected $locked = false; |
||
91 | |||
92 | /** |
||
93 | * List of loaded plugins |
||
94 | * |
||
95 | * @see Pico::getPlugins() |
||
96 | * @var object[]|null |
||
97 | */ |
||
98 | protected $plugins; |
||
99 | |||
100 | /** |
||
101 | * Current configuration of this Pico instance |
||
102 | * |
||
103 | * @see Pico::getConfig() |
||
104 | * @var array|null |
||
105 | */ |
||
106 | protected $config; |
||
107 | |||
108 | /** |
||
109 | * Part of the URL describing the requested contents |
||
110 | * |
||
111 | * @see Pico::getRequestUrl() |
||
112 | * @var string|null |
||
113 | */ |
||
114 | protected $requestUrl; |
||
115 | |||
116 | /** |
||
117 | * Absolute path to the content file being served |
||
118 | * |
||
119 | * @see Pico::getRequestFile() |
||
120 | * @var string|null |
||
121 | */ |
||
122 | protected $requestFile; |
||
123 | |||
124 | /** |
||
125 | * Raw, not yet parsed contents to serve |
||
126 | * |
||
127 | * @see Pico::getRawContent() |
||
128 | * @var string|null |
||
129 | */ |
||
130 | protected $rawContent; |
||
131 | |||
132 | /** |
||
133 | * Meta data of the page to serve |
||
134 | * |
||
135 | * @see Pico::getFileMeta() |
||
136 | * @var array|null |
||
137 | */ |
||
138 | protected $meta; |
||
139 | |||
140 | /** |
||
141 | * Parsedown Extra instance used for markdown parsing |
||
142 | * |
||
143 | * @see Pico::getParsedown() |
||
144 | * @var ParsedownExtra|null |
||
145 | */ |
||
146 | protected $parsedown; |
||
147 | |||
148 | /** |
||
149 | * Parsed content being served |
||
150 | * |
||
151 | * @see Pico::getFileContent() |
||
152 | * @var string|null |
||
153 | */ |
||
154 | protected $content; |
||
155 | |||
156 | /** |
||
157 | * List of known pages |
||
158 | * |
||
159 | * @see Pico::getPages() |
||
160 | * @var array[]|null |
||
161 | */ |
||
162 | protected $pages; |
||
163 | |||
164 | /** |
||
165 | * Data of the page being served |
||
166 | * |
||
167 | * @see Pico::getCurrentPage() |
||
168 | * @var array|null |
||
169 | */ |
||
170 | protected $currentPage; |
||
171 | |||
172 | /** |
||
173 | * Data of the previous page relative to the page being served |
||
174 | * |
||
175 | * @see Pico::getPreviousPage() |
||
176 | * @var array|null |
||
177 | */ |
||
178 | protected $previousPage; |
||
179 | |||
180 | /** |
||
181 | * Data of the next page relative to the page being served |
||
182 | * |
||
183 | * @see Pico::getNextPage() |
||
184 | * @var array|null |
||
185 | */ |
||
186 | protected $nextPage; |
||
187 | |||
188 | /** |
||
189 | * Twig instance used for template parsing |
||
190 | * |
||
191 | * @see Pico::getTwig() |
||
192 | * @var Twig_Environment|null |
||
193 | */ |
||
194 | protected $twig; |
||
195 | |||
196 | /** |
||
197 | * Variables passed to the twig template |
||
198 | * |
||
199 | * @see Pico::getTwigVariables |
||
200 | * @var array|null |
||
201 | */ |
||
202 | protected $twigVariables; |
||
203 | |||
204 | /** |
||
205 | * Constructs a new Pico instance |
||
206 | * |
||
207 | * To carry out all the processing in Pico, call {@link Pico::run()}. |
||
208 | * |
||
209 | * @param string $rootDir root directory of this Pico instance |
||
210 | * @param string $configDir config directory of this Pico instance |
||
211 | * @param string $pluginsDir plugins directory of this Pico instance |
||
212 | * @param string $themesDir themes directory of this Pico instance |
||
213 | */ |
||
214 | public function __construct($rootDir, $configDir, $pluginsDir, $themesDir) |
||
215 | { |
||
216 | $this->rootDir = rtrim($rootDir, '/\\') . '/'; |
||
217 | $this->configDir = $this->getAbsolutePath($configDir); |
||
218 | $this->pluginsDir = $this->getAbsolutePath($pluginsDir); |
||
219 | $this->themesDir = $this->getAbsolutePath($themesDir); |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Returns the root directory of this Pico instance |
||
224 | * |
||
225 | * @return string root directory path |
||
226 | */ |
||
227 | public function getRootDir() |
||
228 | { |
||
229 | return $this->rootDir; |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Returns the config directory of this Pico instance |
||
234 | * |
||
235 | * @return string config directory path |
||
236 | */ |
||
237 | public function getConfigDir() |
||
238 | { |
||
239 | return $this->configDir; |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Returns the plugins directory of this Pico instance |
||
244 | * |
||
245 | * @return string plugins directory path |
||
246 | */ |
||
247 | public function getPluginsDir() |
||
248 | { |
||
249 | return $this->pluginsDir; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Returns the themes directory of this Pico instance |
||
254 | * |
||
255 | * @return string themes directory path |
||
256 | */ |
||
257 | public function getThemesDir() |
||
258 | { |
||
259 | return $this->themesDir; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Runs this Pico instance |
||
264 | * |
||
265 | * Loads plugins, evaluates the config file, does URL routing, parses |
||
266 | * meta headers, processes Markdown, does Twig processing and returns |
||
267 | * the rendered contents. |
||
268 | * |
||
269 | * @return string rendered Pico contents |
||
270 | * @throws Exception thrown when a not recoverable error occurs |
||
271 | */ |
||
272 | public function run() |
||
0 ignored issues
–
show
|
|||
273 | { |
||
274 | // lock Pico |
||
275 | $this->locked = true; |
||
276 | |||
277 | // load plugins |
||
278 | $this->loadPlugins(); |
||
279 | $this->triggerEvent('onPluginsLoaded', array(&$this->plugins)); |
||
280 | |||
281 | // load config |
||
282 | $this->loadConfig(); |
||
283 | $this->triggerEvent('onConfigLoaded', array(&$this->config)); |
||
284 | |||
285 | // check content dir |
||
286 | if (!is_dir($this->getConfig('content_dir'))) { |
||
287 | throw new RuntimeException('Invalid content directory "' . $this->getConfig('content_dir') . '"'); |
||
288 | } |
||
289 | |||
290 | // evaluate request url |
||
291 | $this->evaluateRequestUrl(); |
||
292 | $this->triggerEvent('onRequestUrl', array(&$this->requestUrl)); |
||
293 | |||
294 | // discover requested file |
||
295 | $this->discoverRequestFile(); |
||
296 | $this->triggerEvent('onRequestFile', array(&$this->requestFile)); |
||
297 | |||
298 | // load raw file content |
||
299 | $this->triggerEvent('onContentLoading', array(&$this->requestFile)); |
||
300 | |||
301 | if (file_exists($this->requestFile)) { |
||
302 | $this->rawContent = $this->loadFileContent($this->requestFile); |
||
303 | } else { |
||
304 | $this->triggerEvent('on404ContentLoading', array(&$this->requestFile)); |
||
305 | |||
306 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); |
||
307 | $this->rawContent = $this->load404Content($this->requestFile); |
||
308 | |||
309 | $this->triggerEvent('on404ContentLoaded', array(&$this->rawContent)); |
||
310 | } |
||
311 | |||
312 | $this->triggerEvent('onContentLoaded', array(&$this->rawContent)); |
||
313 | |||
314 | // parse file meta |
||
315 | $headers = $this->getMetaHeaders(); |
||
316 | |||
317 | $this->triggerEvent('onMetaParsing', array(&$this->rawContent, &$headers)); |
||
318 | $this->meta = $this->parseFileMeta($this->rawContent, $headers); |
||
319 | $this->triggerEvent('onMetaParsed', array(&$this->meta)); |
||
320 | |||
321 | // register parsedown |
||
322 | $this->triggerEvent('onParsedownRegistration'); |
||
323 | $this->registerParsedown(); |
||
324 | |||
325 | // parse file content |
||
326 | $this->triggerEvent('onContentParsing', array(&$this->rawContent)); |
||
327 | |||
328 | $this->content = $this->prepareFileContent($this->rawContent, $this->meta); |
||
329 | $this->triggerEvent('onContentPrepared', array(&$this->content)); |
||
330 | |||
331 | $this->content = $this->parseFileContent($this->content); |
||
332 | $this->triggerEvent('onContentParsed', array(&$this->content)); |
||
333 | |||
334 | // read pages |
||
335 | $this->triggerEvent('onPagesLoading'); |
||
336 | |||
337 | $this->readPages(); |
||
338 | $this->sortPages(); |
||
339 | $this->discoverCurrentPage(); |
||
340 | |||
341 | $this->triggerEvent('onPagesLoaded', array( |
||
342 | &$this->pages, |
||
343 | &$this->currentPage, |
||
344 | &$this->previousPage, |
||
345 | &$this->nextPage |
||
346 | )); |
||
347 | |||
348 | // register twig |
||
349 | $this->triggerEvent('onTwigRegistration'); |
||
350 | $this->registerTwig(); |
||
351 | |||
352 | // render template |
||
353 | $this->twigVariables = $this->getTwigVariables(); |
||
354 | if (isset($this->meta['template']) && $this->meta['template']) { |
||
355 | $templateName = $this->meta['template']; |
||
356 | } else { |
||
357 | $templateName = 'index'; |
||
358 | } |
||
359 | if (file_exists($this->getThemesDir() . $this->getConfig('theme') . '/' . $templateName . '.twig')) { |
||
360 | $templateName .= '.twig'; |
||
361 | } else { |
||
362 | $templateName .= '.html'; |
||
363 | } |
||
364 | |||
365 | $this->triggerEvent('onPageRendering', array(&$this->twig, &$this->twigVariables, &$templateName)); |
||
366 | |||
367 | $output = $this->twig->render($templateName, $this->twigVariables); |
||
368 | $this->triggerEvent('onPageRendered', array(&$output)); |
||
369 | |||
370 | return $output; |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Loads plugins from Pico::$pluginsDir in alphabetical order |
||
375 | * |
||
376 | * Plugin files MAY be prefixed by a number (e.g. 00-PicoDeprecated.php) |
||
377 | * to indicate their processing order. Plugins without a prefix will be |
||
378 | * loaded last. If you want to use a prefix, you MUST consider the |
||
379 | * following directives: |
||
380 | * - 00 to 19: Reserved |
||
381 | * - 20 to 39: Low level code helper plugins |
||
382 | * - 40 to 59: Plugins manipulating routing or the pages array |
||
383 | * - 60 to 79: Plugins hooking into template or markdown parsing |
||
384 | * - 80 to 99: Plugins using the `onPageRendered` event |
||
385 | * |
||
386 | * @see Pico::getPlugin() |
||
387 | * @see Pico::getPlugins() |
||
388 | * @return void |
||
389 | * @throws RuntimeException thrown when a plugin couldn't be loaded |
||
390 | */ |
||
391 | protected function loadPlugins() |
||
392 | { |
||
393 | $this->plugins = array(); |
||
394 | $pluginFiles = $this->getFiles($this->getPluginsDir(), '.php'); |
||
395 | foreach ($pluginFiles as $pluginFile) { |
||
396 | require_once($pluginFile); |
||
397 | |||
398 | $className = preg_replace('/^[0-9]+-/', '', basename($pluginFile, '.php')); |
||
399 | if (class_exists($className)) { |
||
400 | // class name and file name can differ regarding case sensitivity |
||
401 | $plugin = new $className($this); |
||
402 | $className = get_class($plugin); |
||
403 | |||
404 | $this->plugins[$className] = $plugin; |
||
405 | } else { |
||
0 ignored issues
–
show
This
else statement is empty and can be removed.
This check looks for the These if (rand(1, 6) > 3) {
print "Check failed";
} else {
//print "Check succeeded";
}
could be turned into if (rand(1, 6) > 3) {
print "Check failed";
}
This is much more concise to read. ![]() |
|||
406 | // TODO: breaks backward compatibility |
||
407 | //throw new RuntimeException("Unable to load plugin '".$className."'"); |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
408 | } |
||
409 | } |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * Returns the instance of a named plugin |
||
414 | * |
||
415 | * Plugins SHOULD implement {@link PicoPluginInterface}, but you MUST NOT |
||
416 | * rely on it. For more information see {@link PicoPluginInterface}. |
||
417 | * |
||
418 | * @see Pico::loadPlugins() |
||
419 | * @see Pico::getPlugins() |
||
420 | * @param string $pluginName name of the plugin |
||
421 | * @return object instance of the plugin |
||
422 | * @throws RuntimeException thrown when the plugin wasn't found |
||
423 | */ |
||
424 | public function getPlugin($pluginName) |
||
425 | { |
||
426 | if (isset($this->plugins[$pluginName])) { |
||
427 | return $this->plugins[$pluginName]; |
||
428 | } |
||
429 | |||
430 | throw new RuntimeException("Missing plugin '" . $pluginName . "'"); |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * Returns all loaded plugins |
||
435 | * |
||
436 | * @see Pico::loadPlugins() |
||
437 | * @see Pico::getPlugin() |
||
438 | * @return object[]|null |
||
439 | */ |
||
440 | public function getPlugins() |
||
441 | { |
||
442 | return $this->plugins; |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Loads the config.php from Pico::$configDir |
||
447 | * |
||
448 | * @see Pico::setConfig() |
||
449 | * @see Pico::getConfig() |
||
450 | * @return void |
||
451 | */ |
||
452 | protected function loadConfig() |
||
453 | { |
||
454 | $config = null; |
||
455 | if (file_exists($this->getConfigDir() . 'config.php')) { |
||
456 | require($this->getConfigDir() . 'config.php'); |
||
457 | } |
||
458 | |||
459 | $defaultConfig = array( |
||
460 | 'site_title' => 'Pico', |
||
461 | 'base_url' => '', |
||
462 | 'rewrite_url' => null, |
||
463 | 'theme' => 'default', |
||
464 | 'date_format' => '%D %T', |
||
465 | 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), |
||
466 | 'pages_order_by' => 'alpha', |
||
467 | 'pages_order' => 'asc', |
||
468 | 'content_dir' => null, |
||
469 | 'content_ext' => '.md', |
||
470 | 'timezone' => '' |
||
471 | ); |
||
472 | |||
473 | $this->config = is_array($this->config) ? $this->config : array(); |
||
474 | $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig; |
||
475 | |||
476 | if (empty($this->config['base_url'])) { |
||
477 | $this->config['base_url'] = $this->getBaseUrl(); |
||
478 | } else { |
||
479 | $this->config['base_url'] = rtrim($this->config['base_url'], '/') . '/'; |
||
480 | } |
||
481 | |||
482 | if ($this->config['rewrite_url'] === null) { |
||
483 | $this->config['rewrite_url'] = $this->isUrlRewritingEnabled(); |
||
484 | } |
||
485 | |||
486 | if (empty($this->config['content_dir'])) { |
||
487 | // try to guess the content directory |
||
488 | if (is_dir($this->getRootDir() . 'content')) { |
||
489 | $this->config['content_dir'] = $this->getRootDir() . 'content/'; |
||
490 | } else { |
||
491 | $this->config['content_dir'] = $this->getRootDir() . 'content-sample/'; |
||
492 | } |
||
493 | } else { |
||
494 | $this->config['content_dir'] = $this->getAbsolutePath($this->config['content_dir']); |
||
0 ignored issues
–
show
It seems like
$this->config['content_dir'] can also be of type boolean ; however, Pico::getAbsolutePath() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
495 | } |
||
496 | |||
497 | if (empty($this->config['timezone'])) { |
||
498 | // explicitly set a default timezone to prevent a E_NOTICE |
||
499 | // when no timezone is set; the `date_default_timezone_get()` |
||
500 | // function always returns a timezone, at least UTC |
||
501 | $this->config['timezone'] = date_default_timezone_get(); |
||
502 | } |
||
503 | date_default_timezone_set($this->config['timezone']); |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Sets Pico's config before calling Pico::run() |
||
508 | * |
||
509 | * This method allows you to modify Pico's config without creating a |
||
510 | * {@path "config/config.php"} or changing some of its variables before |
||
511 | * Pico starts processing. |
||
512 | * |
||
513 | * You can call this method between {@link Pico::__construct()} and |
||
514 | * {@link Pico::run()} only. Options set with this method cannot be |
||
515 | * overwritten by {@path "config/config.php"}. |
||
516 | * |
||
517 | * @see Pico::loadConfig() |
||
518 | * @see Pico::getConfig() |
||
519 | * @param array $config array with config variables |
||
520 | * @return void |
||
521 | * @throws LogicException thrown if Pico already started processing |
||
522 | */ |
||
523 | public function setConfig(array $config) |
||
524 | { |
||
525 | if ($this->locked) { |
||
526 | throw new LogicException("You cannot modify Pico's config after processing has started"); |
||
527 | } |
||
528 | |||
529 | $this->config = $config; |
||
530 | } |
||
531 | |||
532 | /** |
||
533 | * Returns either the value of the specified config variable or |
||
534 | * the config array |
||
535 | * |
||
536 | * @see Pico::setConfig() |
||
537 | * @see Pico::loadConfig() |
||
538 | * @param string $configName optional name of a config variable |
||
539 | * @return mixed returns either the value of the named config |
||
540 | * variable, null if the config variable doesn't exist or the config |
||
541 | * array if no config name was supplied |
||
542 | */ |
||
543 | public function getConfig($configName = null) |
||
544 | { |
||
545 | if ($configName !== null) { |
||
546 | return isset($this->config[$configName]) ? $this->config[$configName] : null; |
||
547 | } else { |
||
548 | return $this->config; |
||
549 | } |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Evaluates the requested URL |
||
554 | * |
||
555 | * Pico 1.0 uses the `QUERY_STRING` routing method (e.g. `/pico/?sub/page`) |
||
556 | * to support SEO-like URLs out-of-the-box with any webserver. You can |
||
557 | * still setup URL rewriting (e.g. using `mod_rewrite` on Apache) to |
||
558 | * basically remove the `?` from URLs, but your rewritten URLs must follow |
||
559 | * the new `QUERY_STRING` principles. URL rewriting requires some special |
||
560 | * configuration on your webserver, but this should be "basic work" for |
||
561 | * any webmaster... |
||
562 | * |
||
563 | * Pico 0.9 and older required Apache with `mod_rewrite` enabled, thus old |
||
564 | * plugins, templates and contents may require you to enable URL rewriting |
||
565 | * to work. If you're upgrading from Pico 0.9, you will probably have to |
||
566 | * update your rewriting rules. |
||
567 | * |
||
568 | * We recommend you to use the `link` filter in templates to create |
||
569 | * internal links, e.g. `{{ "sub/page"|link }}` is equivalent to |
||
570 | * `{{ base_url }}/sub/page` and `{{ base_url }}?sub/page`, depending on |
||
571 | * enabled URL rewriting. In content files you can use the `%base_url%` |
||
572 | * variable; e.g. `%base_url%?sub/page` will be replaced accordingly. |
||
573 | * |
||
574 | * @see Pico::getRequestUrl() |
||
575 | * @return void |
||
576 | */ |
||
577 | protected function evaluateRequestUrl() |
||
0 ignored issues
–
show
evaluateRequestUrl uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
578 | { |
||
579 | // use QUERY_STRING; e.g. /pico/?sub/page |
||
580 | // if you want to use rewriting, you MUST make your rules to |
||
581 | // rewrite the URLs to follow the QUERY_STRING method |
||
582 | // |
||
583 | // Note: you MUST NOT call the index page with /pico/?someBooleanParameter; |
||
584 | // use /pico/?someBooleanParameter= or /pico/?index&someBooleanParameter instead |
||
585 | $pathComponent = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; |
||
586 | if (($pathComponentLength = strpos($pathComponent, '&')) !== false) { |
||
587 | $pathComponent = substr($pathComponent, 0, $pathComponentLength); |
||
588 | } |
||
589 | $this->requestUrl = (strpos($pathComponent, '=') === false) ? rawurldecode($pathComponent) : ''; |
||
590 | $this->requestUrl = trim($this->requestUrl, '/'); |
||
591 | } |
||
592 | |||
593 | /** |
||
594 | * Returns the URL where a user requested the page |
||
595 | * |
||
596 | * @see Pico::evaluateRequestUrl() |
||
597 | * @return string|null request URL |
||
598 | */ |
||
599 | public function getRequestUrl() |
||
600 | { |
||
601 | return $this->requestUrl; |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * Uses the request URL to discover the content file to serve |
||
606 | * |
||
607 | * @see Pico::getRequestFile() |
||
608 | * @return void |
||
609 | */ |
||
610 | protected function discoverRequestFile() |
||
611 | { |
||
612 | if (empty($this->requestUrl)) { |
||
613 | $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); |
||
614 | } else { |
||
615 | // prevent content_dir breakouts using malicious request URLs |
||
616 | // we don't use realpath() here because we neither want to check for file existance |
||
617 | // nor prohibit symlinks which intentionally point to somewhere outside the content_dir |
||
618 | // it is STRONGLY RECOMMENDED to use open_basedir - always, not just with Pico! |
||
619 | $requestUrl = str_replace('\\', '/', $this->requestUrl); |
||
620 | $requestUrlParts = explode('/', $requestUrl); |
||
621 | |||
622 | $requestFileParts = array(); |
||
623 | foreach ($requestUrlParts as $requestUrlPart) { |
||
624 | if (($requestUrlPart === '') || ($requestUrlPart === '.')) { |
||
625 | continue; |
||
626 | } elseif ($requestUrlPart === '..') { |
||
627 | array_pop($requestFileParts); |
||
628 | continue; |
||
629 | } |
||
630 | |||
631 | $requestFileParts[] = $requestUrlPart; |
||
632 | } |
||
633 | |||
634 | if (empty($requestFileParts)) { |
||
635 | $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); |
||
636 | return; |
||
637 | } |
||
638 | |||
639 | // discover the content file to serve |
||
640 | // Note: $requestFileParts neither contains a trailing nor a leading slash |
||
641 | $this->requestFile = $this->getConfig('content_dir') . implode('/', $requestFileParts); |
||
642 | if (is_dir($this->requestFile)) { |
||
643 | // if no index file is found, try a accordingly named file in the previous dir |
||
644 | // if this file doesn't exist either, show the 404 page, but assume the index |
||
645 | // file as being requested (maintains backward compatibility to Pico < 1.0) |
||
646 | $indexFile = $this->requestFile . '/index' . $this->getConfig('content_ext'); |
||
647 | if (file_exists($indexFile) || !file_exists($this->requestFile . $this->getConfig('content_ext'))) { |
||
648 | $this->requestFile = $indexFile; |
||
649 | return; |
||
650 | } |
||
651 | } |
||
652 | $this->requestFile .= $this->getConfig('content_ext'); |
||
653 | } |
||
654 | } |
||
655 | |||
656 | /** |
||
657 | * Returns the absolute path to the content file to serve |
||
658 | * |
||
659 | * @see Pico::discoverRequestFile() |
||
660 | * @return string|null file path |
||
661 | */ |
||
662 | public function getRequestFile() |
||
663 | { |
||
664 | return $this->requestFile; |
||
665 | } |
||
666 | |||
667 | /** |
||
668 | * Returns the raw contents of a file |
||
669 | * |
||
670 | * @see Pico::getRawContent() |
||
671 | * @param string $file file path |
||
672 | * @return string raw contents of the file |
||
673 | */ |
||
674 | public function loadFileContent($file) |
||
675 | { |
||
676 | return file_get_contents($file); |
||
677 | } |
||
678 | |||
679 | /** |
||
680 | * Returns the raw contents of the first found 404 file when traversing |
||
681 | * up from the directory the requested file is in |
||
682 | * |
||
683 | * @see Pico::getRawContent() |
||
684 | * @param string $file path to requested (but not existing) file |
||
685 | * @return string raw contents of the 404 file |
||
686 | * @throws RuntimeException thrown when no suitable 404 file is found |
||
687 | */ |
||
688 | public function load404Content($file) |
||
689 | { |
||
690 | $errorFileDir = substr($file, strlen($this->getConfig('content_dir'))); |
||
691 | do { |
||
692 | $errorFileDir = dirname($errorFileDir); |
||
693 | $errorFile = $errorFileDir . '/404' . $this->getConfig('content_ext'); |
||
694 | } while (!file_exists($this->getConfig('content_dir') . $errorFile) && ($errorFileDir !== '.')); |
||
695 | |||
696 | if (!file_exists($this->getConfig('content_dir') . $errorFile)) { |
||
697 | $errorFile = ($errorFileDir === '.') ? '404' . $this->getConfig('content_ext') : $errorFile; |
||
698 | throw new RuntimeException('Required "' . $this->getConfig('content_dir') . $errorFile . '" not found'); |
||
699 | } |
||
700 | |||
701 | return $this->loadFileContent($this->getConfig('content_dir') . $errorFile); |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * Returns the raw contents, either of the requested or the 404 file |
||
706 | * |
||
707 | * @see Pico::loadFileContent() |
||
708 | * @see Pico::load404Content() |
||
709 | * @return string|null raw contents |
||
710 | */ |
||
711 | public function getRawContent() |
||
712 | { |
||
713 | return $this->rawContent; |
||
714 | } |
||
715 | |||
716 | /** |
||
717 | * Returns known meta headers and triggers the onMetaHeaders event |
||
718 | * |
||
719 | * Heads up! Calling this method triggers the `onMetaHeaders` event. |
||
720 | * Keep this in mind to prevent a infinite loop! |
||
721 | * |
||
722 | * @return string[] known meta headers; the array value specifies the |
||
723 | * YAML key to search for, the array key is later used to access the |
||
724 | * found value |
||
725 | */ |
||
726 | public function getMetaHeaders() |
||
727 | { |
||
728 | $headers = array( |
||
729 | 'title' => 'Title', |
||
730 | 'description' => 'Description', |
||
731 | 'author' => 'Author', |
||
732 | 'date' => 'Date', |
||
733 | 'robots' => 'Robots', |
||
734 | 'template' => 'Template' |
||
735 | ); |
||
736 | |||
737 | $this->triggerEvent('onMetaHeaders', array(&$headers)); |
||
738 | return $headers; |
||
739 | } |
||
740 | |||
741 | /** |
||
742 | * Parses the file meta from raw file contents |
||
743 | * |
||
744 | * Meta data MUST start on the first line of the file, either opened and |
||
745 | * closed by `---` or C-style block comments (deprecated). The headers are |
||
746 | * parsed by the YAML component of the Symfony project, keys are lowered. |
||
747 | * If you're a plugin developer, you MUST register new headers during the |
||
748 | * `onMetaHeaders` event first. The implicit availability of headers is |
||
749 | * for users and pure (!) theme developers ONLY. |
||
750 | * |
||
751 | * @see Pico::getFileMeta() |
||
752 | * @see <http://symfony.com/doc/current/components/yaml/introduction.html> |
||
753 | * @param string $rawContent the raw file contents |
||
754 | * @param string[] $headers known meta headers |
||
755 | * @return array parsed meta data |
||
756 | * @throws \Symfony\Component\Yaml\Exception\ParseException thrown when the |
||
757 | * meta data is invalid |
||
758 | */ |
||
759 | public function parseFileMeta($rawContent, array $headers) |
||
760 | { |
||
761 | $meta = array(); |
||
0 ignored issues
–
show
$meta 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 ![]() |
|||
762 | $pattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" |
||
763 | . "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; |
||
764 | if (preg_match($pattern, $rawContent, $rawMetaMatches) && isset($rawMetaMatches[3])) { |
||
765 | $yamlParser = new \Symfony\Component\Yaml\Parser(); |
||
766 | $meta = $yamlParser->parse($rawMetaMatches[3]); |
||
767 | $meta = ($meta !== null) ? array_change_key_case($meta, CASE_LOWER) : array(); |
||
768 | |||
769 | foreach ($headers as $fieldId => $fieldName) { |
||
770 | $fieldName = strtolower($fieldName); |
||
771 | if (isset($meta[$fieldName])) { |
||
772 | // rename field (e.g. remove whitespaces) |
||
773 | if ($fieldId != $fieldName) { |
||
774 | $meta[$fieldId] = $meta[$fieldName]; |
||
775 | unset($meta[$fieldName]); |
||
776 | } |
||
777 | } elseif (!isset($meta[$fieldId])) { |
||
778 | // guarantee array key existance |
||
779 | $meta[$fieldId] = ''; |
||
780 | } |
||
781 | } |
||
782 | |||
783 | if (!empty($meta['date'])) { |
||
784 | $meta['time'] = strtotime($meta['date']); |
||
785 | $meta['date_formatted'] = utf8_encode(strftime($this->getConfig('date_format'), $meta['time'])); |
||
786 | } else { |
||
787 | $meta['time'] = $meta['date_formatted'] = ''; |
||
788 | } |
||
789 | } else { |
||
790 | // guarantee array key existance |
||
791 | $meta = array_fill_keys(array_keys($headers), ''); |
||
792 | $meta['time'] = $meta['date_formatted'] = ''; |
||
793 | } |
||
794 | |||
795 | return $meta; |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Returns the parsed meta data of the requested page |
||
800 | * |
||
801 | * @see Pico::parseFileMeta() |
||
802 | * @return array|null parsed meta data |
||
803 | */ |
||
804 | public function getFileMeta() |
||
805 | { |
||
806 | return $this->meta; |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Registers the Parsedown Extra markdown parser |
||
811 | * |
||
812 | * @see Pico::getParsedown() |
||
813 | * @return void |
||
814 | */ |
||
815 | protected function registerParsedown() |
||
816 | { |
||
817 | $this->parsedown = new ParsedownExtra(); |
||
818 | } |
||
819 | |||
820 | /** |
||
821 | * Returns the Parsedown Extra markdown parser |
||
822 | * |
||
823 | * @see Pico::registerParsedown() |
||
824 | * @return ParsedownExtra|null Parsedown Extra markdown parser |
||
825 | */ |
||
826 | public function getParsedown() |
||
827 | { |
||
828 | return $this->parsedown; |
||
829 | } |
||
830 | |||
831 | /** |
||
832 | * Applies some static preparations to the raw contents of a page, |
||
833 | * e.g. removing the meta header and replacing %base_url% |
||
834 | * |
||
835 | * @see Pico::parseFileContent() |
||
836 | * @see Pico::getFileContent() |
||
837 | * @param string $rawContent raw contents of a page |
||
838 | * @param array $meta meta data to use for %meta.*% replacement |
||
839 | * @return string contents prepared for parsing |
||
840 | */ |
||
841 | public function prepareFileContent($rawContent, array $meta) |
||
842 | { |
||
843 | // remove meta header |
||
844 | $metaHeaderPattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" |
||
845 | . "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; |
||
846 | $content = preg_replace($metaHeaderPattern, '', $rawContent, 1); |
||
847 | |||
848 | // replace %site_title% |
||
849 | $content = str_replace('%site_title%', $this->getConfig('site_title'), $content); |
||
850 | |||
851 | // replace %base_url% |
||
852 | if ($this->isUrlRewritingEnabled()) { |
||
853 | // always use `%base_url%?sub/page` syntax for internal links |
||
854 | // we'll replace the links accordingly, depending on enabled rewriting |
||
855 | $content = str_replace('%base_url%?', $this->getBaseUrl(), $content); |
||
856 | } else { |
||
857 | // actually not necessary, but makes the URL look a little nicer |
||
858 | $content = str_replace('%base_url%?', $this->getBaseUrl() . '?', $content); |
||
859 | } |
||
860 | $content = str_replace('%base_url%', rtrim($this->getBaseUrl(), '/'), $content); |
||
861 | |||
862 | // replace %theme_url% |
||
863 | $themeUrl = $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'); |
||
864 | $content = str_replace('%theme_url%', $themeUrl, $content); |
||
865 | |||
866 | // replace %meta.*% |
||
867 | if (!empty($meta)) { |
||
868 | $metaKeys = $metaValues = array(); |
||
869 | foreach ($meta as $metaKey => $metaValue) { |
||
870 | if (is_scalar($metaValue) || ($metaValue === null)) { |
||
871 | $metaKeys[] = '%meta.' . $metaKey . '%'; |
||
872 | $metaValues[] = strval($metaValue); |
||
873 | } |
||
874 | } |
||
875 | $content = str_replace($metaKeys, $metaValues, $content); |
||
876 | } |
||
877 | |||
878 | return $content; |
||
879 | } |
||
880 | |||
881 | /** |
||
882 | * Parses the contents of a page using ParsedownExtra |
||
883 | * |
||
884 | * @see Pico::prepareFileContent() |
||
885 | * @see Pico::getFileContent() |
||
886 | * @param string $content raw contents of a page (Markdown) |
||
887 | * @return string parsed contents (HTML) |
||
888 | */ |
||
889 | public function parseFileContent($content) |
||
890 | { |
||
891 | if ($this->parsedown === null) { |
||
892 | throw new LogicException("Unable to parse file contents: Parsedown instance wasn't registered yet"); |
||
893 | } |
||
894 | |||
895 | return $this->parsedown->text($content); |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Returns the cached contents of the requested page |
||
900 | * |
||
901 | * @see Pico::prepareFileContent() |
||
902 | * @see Pico::parseFileContent() |
||
903 | * @return string|null parsed contents |
||
904 | */ |
||
905 | public function getFileContent() |
||
906 | { |
||
907 | return $this->content; |
||
908 | } |
||
909 | |||
910 | /** |
||
911 | * Reads the data of all pages known to Pico |
||
912 | * |
||
913 | * The page data will be an array containing the following values: |
||
914 | * |
||
915 | * | Array key | Type | Description | |
||
916 | * | -------------- | ------ | ---------------------------------------- | |
||
917 | * | id | string | relative path to the content file | |
||
918 | * | url | string | URL to the page | |
||
919 | * | title | string | title of the page (YAML header) | |
||
920 | * | description | string | description of the page (YAML header) | |
||
921 | * | author | string | author of the page (YAML header) | |
||
922 | * | time | string | timestamp derived from the Date header | |
||
923 | * | date | string | date of the page (YAML header) | |
||
924 | * | date_formatted | string | formatted date of the page | |
||
925 | * | raw_content | string | raw, not yet parsed contents of the page | |
||
926 | * | meta | string | parsed meta data of the page | |
||
927 | * |
||
928 | * @see Pico::sortPages() |
||
929 | * @see Pico::getPages() |
||
930 | * @return void |
||
931 | */ |
||
932 | protected function readPages() |
||
933 | { |
||
934 | $this->pages = array(); |
||
935 | $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext'), Pico::SORT_NONE); |
||
936 | foreach ($files as $i => $file) { |
||
937 | // skip 404 page |
||
938 | if (basename($file) === '404' . $this->getConfig('content_ext')) { |
||
939 | unset($files[$i]); |
||
940 | continue; |
||
941 | } |
||
942 | |||
943 | $id = substr($file, strlen($this->getConfig('content_dir')), -strlen($this->getConfig('content_ext'))); |
||
944 | |||
945 | // drop inaccessible pages (e.g. drop "sub.md" if "sub/index.md" exists) |
||
946 | $conflictFile = $this->getConfig('content_dir') . $id . '/index' . $this->getConfig('content_ext'); |
||
947 | if (in_array($conflictFile, $files, true)) { |
||
948 | continue; |
||
949 | } |
||
950 | |||
951 | $url = $this->getPageUrl($id); |
||
952 | if ($file != $this->requestFile) { |
||
953 | $rawContent = file_get_contents($file); |
||
954 | |||
955 | $headers = $this->getMetaHeaders(); |
||
956 | try { |
||
957 | $meta = $this->parseFileMeta($rawContent, $headers); |
||
958 | } catch (\Symfony\Component\Yaml\Exception\ParseException $e) { |
||
959 | $meta = $this->parseFileMeta('', $headers); |
||
960 | $meta['YAML_ParseError'] = $e->getMessage(); |
||
961 | } |
||
962 | } else { |
||
963 | $rawContent = &$this->rawContent; |
||
964 | $meta = &$this->meta; |
||
965 | } |
||
966 | |||
967 | // build page data |
||
968 | // title, description, author and date are assumed to be pretty basic data |
||
969 | // everything else is accessible through $page['meta'] |
||
970 | $page = array( |
||
971 | 'id' => $id, |
||
972 | 'url' => $url, |
||
973 | 'title' => &$meta['title'], |
||
974 | 'description' => &$meta['description'], |
||
975 | 'author' => &$meta['author'], |
||
976 | 'time' => &$meta['time'], |
||
977 | 'date' => &$meta['date'], |
||
978 | 'date_formatted' => &$meta['date_formatted'], |
||
979 | 'raw_content' => &$rawContent, |
||
980 | 'meta' => &$meta |
||
981 | ); |
||
982 | |||
983 | if ($file === $this->requestFile) { |
||
984 | $page['content'] = &$this->content; |
||
985 | } |
||
986 | |||
987 | unset($rawContent, $meta); |
||
988 | |||
989 | // trigger event |
||
990 | $this->triggerEvent('onSinglePageLoaded', array(&$page)); |
||
991 | |||
992 | $this->pages[$id] = $page; |
||
993 | } |
||
994 | } |
||
995 | |||
996 | /** |
||
997 | * Sorts all pages known to Pico |
||
998 | * |
||
999 | * @see Pico::readPages() |
||
1000 | * @see Pico::getPages() |
||
1001 | * @return void |
||
1002 | */ |
||
1003 | protected function sortPages() |
||
1004 | { |
||
1005 | // sort pages |
||
1006 | $order = $this->getConfig('pages_order'); |
||
1007 | $alphaSortClosure = function ($a, $b) use ($order) { |
||
1008 | $aSortKey = (basename($a['id']) === 'index') ? dirname($a['id']) : $a['id']; |
||
1009 | $bSortKey = (basename($b['id']) === 'index') ? dirname($b['id']) : $b['id']; |
||
1010 | |||
1011 | $cmp = strcmp($aSortKey, $bSortKey); |
||
1012 | return $cmp * (($order === 'desc') ? -1 : 1); |
||
1013 | }; |
||
1014 | |||
1015 | if ($this->getConfig('pages_order_by') === 'date') { |
||
1016 | // sort by date |
||
1017 | uasort($this->pages, function ($a, $b) use ($alphaSortClosure, $order) { |
||
1018 | if (empty($a['time']) || empty($b['time'])) { |
||
1019 | $cmp = (empty($a['time']) - empty($b['time'])); |
||
1020 | } else { |
||
1021 | $cmp = ($b['time'] - $a['time']); |
||
1022 | } |
||
1023 | |||
1024 | if ($cmp === 0) { |
||
1025 | // never assume equality; fallback to alphabetical order |
||
1026 | return $alphaSortClosure($a, $b); |
||
1027 | } |
||
1028 | |||
1029 | return $cmp * (($order === 'desc') ? 1 : -1); |
||
1030 | }); |
||
1031 | } else { |
||
1032 | // sort alphabetically |
||
1033 | uasort($this->pages, $alphaSortClosure); |
||
1034 | } |
||
1035 | } |
||
1036 | |||
1037 | /** |
||
1038 | * Returns the list of known pages |
||
1039 | * |
||
1040 | * @see Pico::readPages() |
||
1041 | * @see Pico::sortPages() |
||
1042 | * @return array[]|null the data of all pages |
||
1043 | */ |
||
1044 | public function getPages() |
||
1045 | { |
||
1046 | return $this->pages; |
||
1047 | } |
||
1048 | |||
1049 | /** |
||
1050 | * Walks through the list of known pages and discovers the requested page |
||
1051 | * as well as the previous and next page relative to it |
||
1052 | * |
||
1053 | * @see Pico::getCurrentPage() |
||
1054 | * @see Pico::getPreviousPage() |
||
1055 | * @see Pico::getNextPage() |
||
1056 | * @return void |
||
1057 | */ |
||
1058 | protected function discoverCurrentPage() |
||
1059 | { |
||
1060 | $pageIds = array_keys($this->pages); |
||
1061 | |||
1062 | $contentDir = $this->getConfig('content_dir'); |
||
1063 | $contentExt = $this->getConfig('content_ext'); |
||
1064 | $currentPageId = substr($this->requestFile, strlen($contentDir), -strlen($contentExt)); |
||
1065 | $currentPageIndex = array_search($currentPageId, $pageIds); |
||
1066 | if ($currentPageIndex !== false) { |
||
1067 | $this->currentPage = &$this->pages[$currentPageId]; |
||
1068 | |||
1069 | if (($this->getConfig('order_by') === 'date') && ($this->getConfig('order') === 'desc')) { |
||
1070 | $previousPageOffset = 1; |
||
1071 | $nextPageOffset = -1; |
||
1072 | } else { |
||
1073 | $previousPageOffset = -1; |
||
1074 | $nextPageOffset = 1; |
||
1075 | } |
||
1076 | |||
1077 | if (isset($pageIds[$currentPageIndex + $previousPageOffset])) { |
||
1078 | $previousPageId = $pageIds[$currentPageIndex + $previousPageOffset]; |
||
1079 | $this->previousPage = &$this->pages[$previousPageId]; |
||
1080 | } |
||
1081 | |||
1082 | if (isset($pageIds[$currentPageIndex + $nextPageOffset])) { |
||
1083 | $nextPageId = $pageIds[$currentPageIndex + $nextPageOffset]; |
||
1084 | $this->nextPage = &$this->pages[$nextPageId]; |
||
1085 | } |
||
1086 | } |
||
1087 | } |
||
1088 | |||
1089 | /** |
||
1090 | * Returns the data of the requested page |
||
1091 | * |
||
1092 | * @see Pico::discoverCurrentPage() |
||
1093 | * @return array|null page data |
||
1094 | */ |
||
1095 | public function getCurrentPage() |
||
1096 | { |
||
1097 | return $this->currentPage; |
||
1098 | } |
||
1099 | |||
1100 | /** |
||
1101 | * Returns the data of the previous page relative to the page being served |
||
1102 | * |
||
1103 | * @see Pico::discoverCurrentPage() |
||
1104 | * @return array|null page data |
||
1105 | */ |
||
1106 | public function getPreviousPage() |
||
1107 | { |
||
1108 | return $this->previousPage; |
||
1109 | } |
||
1110 | |||
1111 | /** |
||
1112 | * Returns the data of the next page relative to the page being served |
||
1113 | * |
||
1114 | * @see Pico::discoverCurrentPage() |
||
1115 | * @return array|null page data |
||
1116 | */ |
||
1117 | public function getNextPage() |
||
1118 | { |
||
1119 | return $this->nextPage; |
||
1120 | } |
||
1121 | |||
1122 | /** |
||
1123 | * Registers the twig template engine |
||
1124 | * |
||
1125 | * This method also registers Picos core Twig filters `link` and `content` |
||
1126 | * as well as Picos {@link PicoTwigExtension} Twig extension. |
||
1127 | * |
||
1128 | * @see Pico::getTwig() |
||
1129 | * @return void |
||
1130 | */ |
||
1131 | protected function registerTwig() |
||
1132 | { |
||
1133 | $twigLoader = new Twig_Loader_Filesystem($this->getThemesDir() . $this->getConfig('theme')); |
||
1134 | $this->twig = new Twig_Environment($twigLoader, $this->getConfig('twig_config')); |
||
1135 | $this->twig->addExtension(new Twig_Extension_Debug()); |
||
1136 | $this->twig->addExtension(new PicoTwigExtension($this)); |
||
1137 | |||
1138 | // register link filter |
||
1139 | $this->twig->addFilter(new Twig_SimpleFilter('link', array($this, 'getPageUrl'))); |
||
1140 | |||
1141 | // register content filter |
||
1142 | // we pass the $pages array by reference to prevent multiple parser runs for the same page |
||
1143 | // this is the reason why we can't register this filter as part of PicoTwigExtension |
||
1144 | $pico = $this; |
||
1145 | $pages = &$this->pages; |
||
1146 | $this->twig->addFilter(new Twig_SimpleFilter('content', function ($page) use ($pico, &$pages) { |
||
1147 | if (isset($pages[$page])) { |
||
1148 | $pageData = &$pages[$page]; |
||
1149 | View Code Duplication | if (!isset($pageData['content'])) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
1150 | $pageData['content'] = $pico->prepareFileContent($pageData['raw_content'], $pageData['meta']); |
||
1151 | $pageData['content'] = $pico->parseFileContent($pageData['content']); |
||
1152 | } |
||
1153 | return $pageData['content']; |
||
1154 | } |
||
1155 | return null; |
||
1156 | })); |
||
1157 | } |
||
1158 | |||
1159 | /** |
||
1160 | * Returns the twig template engine |
||
1161 | * |
||
1162 | * @see Pico::registerTwig() |
||
1163 | * @return Twig_Environment|null Twig template engine |
||
1164 | */ |
||
1165 | public function getTwig() |
||
1166 | { |
||
1167 | return $this->twig; |
||
1168 | } |
||
1169 | |||
1170 | /** |
||
1171 | * Returns the variables passed to the template |
||
1172 | * |
||
1173 | * URLs and paths (namely `base_dir`, `base_url`, `theme_dir` and |
||
1174 | * `theme_url`) don't add a trailing slash for historic reasons. |
||
1175 | * |
||
1176 | * @return array template variables |
||
1177 | */ |
||
1178 | protected function getTwigVariables() |
||
1179 | { |
||
1180 | $frontPage = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); |
||
1181 | return array( |
||
1182 | 'config' => $this->getConfig(), |
||
1183 | 'base_dir' => rtrim($this->getRootDir(), '/'), |
||
1184 | 'base_url' => rtrim($this->getBaseUrl(), '/'), |
||
1185 | 'theme_dir' => $this->getThemesDir() . $this->getConfig('theme'), |
||
1186 | 'theme_url' => $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'), |
||
1187 | 'rewrite_url' => $this->isUrlRewritingEnabled(), |
||
1188 | 'site_title' => $this->getConfig('site_title'), |
||
1189 | 'meta' => $this->meta, |
||
1190 | 'content' => $this->content, |
||
1191 | 'pages' => $this->pages, |
||
1192 | 'prev_page' => $this->previousPage, |
||
1193 | 'current_page' => $this->currentPage, |
||
1194 | 'next_page' => $this->nextPage, |
||
1195 | 'is_front_page' => ($this->requestFile === $frontPage), |
||
1196 | ); |
||
1197 | } |
||
1198 | |||
1199 | /** |
||
1200 | * Returns the base URL of this Pico instance |
||
1201 | * |
||
1202 | * @return string the base url |
||
1203 | */ |
||
1204 | public function getBaseUrl() |
||
0 ignored issues
–
show
getBaseUrl uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
1205 | { |
||
1206 | $baseUrl = $this->getConfig('base_url'); |
||
1207 | if (!empty($baseUrl)) { |
||
1208 | return $baseUrl; |
||
1209 | } |
||
1210 | |||
1211 | $protocol = 'http'; |
||
1212 | if (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] !== 'off')) { |
||
1213 | $protocol = 'https'; |
||
1214 | } elseif ($_SERVER['SERVER_PORT'] == 443) { |
||
1215 | $protocol = 'https'; |
||
1216 | } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) { |
||
1217 | $protocol = 'https'; |
||
1218 | } |
||
1219 | |||
1220 | $this->config['base_url'] = |
||
1221 | $protocol . "://" . $_SERVER['HTTP_HOST'] |
||
1222 | . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/'; |
||
1223 | |||
1224 | return $this->getConfig('base_url'); |
||
1225 | } |
||
1226 | |||
1227 | /** |
||
1228 | * Returns true if URL rewriting is enabled |
||
1229 | * |
||
1230 | * @return boolean true if URL rewriting is enabled, false otherwise |
||
1231 | */ |
||
1232 | public function isUrlRewritingEnabled() |
||
0 ignored issues
–
show
isUrlRewritingEnabled uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
1233 | { |
||
1234 | $urlRewritingEnabled = $this->getConfig('rewrite_url'); |
||
1235 | if ($urlRewritingEnabled !== null) { |
||
1236 | return $urlRewritingEnabled; |
||
1237 | } |
||
1238 | |||
1239 | $this->config['rewrite_url'] = (isset($_SERVER['PICO_URL_REWRITING']) && $_SERVER['PICO_URL_REWRITING']); |
||
1240 | return $this->getConfig('rewrite_url'); |
||
1241 | } |
||
1242 | |||
1243 | /** |
||
1244 | * Returns the URL to a given page |
||
1245 | * |
||
1246 | * @param string $page identifier of the page to link to |
||
1247 | * @param array|string $queryData either an array containing properties to |
||
1248 | * create a URL-encoded query string from, or a already encoded string |
||
1249 | * @return string URL |
||
1250 | */ |
||
1251 | public function getPageUrl($page, $queryData = null) |
||
1252 | { |
||
1253 | if (is_array($queryData)) { |
||
1254 | $queryData = http_build_query($queryData, '', '&'); |
||
1255 | } elseif (($queryData !== null) && !is_string($queryData)) { |
||
1256 | throw new InvalidArgumentException( |
||
1257 | 'Argument 2 passed to ' . get_called_class() . '::getPageUrl() must be of the type array or string, ' |
||
1258 | . (is_object($queryData) ? get_class($queryData) : gettype($queryData)) . ' given' |
||
1259 | ); |
||
1260 | } |
||
1261 | if (!empty($queryData)) { |
||
1262 | $page = !empty($page) ? $page : 'index'; |
||
1263 | $queryData = $this->isUrlRewritingEnabled() ? '?' . $queryData : '&' . $queryData; |
||
1264 | } |
||
1265 | |||
1266 | if (empty($page)) { |
||
1267 | return $this->getBaseUrl() . $queryData; |
||
1268 | } elseif (!$this->isUrlRewritingEnabled()) { |
||
1269 | return $this->getBaseUrl() . '?' . rawurlencode($page) . $queryData; |
||
1270 | } else { |
||
1271 | return $this->getBaseUrl() . implode('/', array_map('rawurlencode', explode('/', $page))) . $queryData; |
||
1272 | } |
||
1273 | } |
||
1274 | |||
1275 | /** |
||
1276 | * Recursively walks through a directory and returns all containing files |
||
1277 | * matching the specified file extension |
||
1278 | * |
||
1279 | * @param string $directory start directory |
||
1280 | * @param string $fileExtension return files with the given file extension |
||
1281 | * only (optional) |
||
1282 | * @param int $order specify whether and how files should be |
||
1283 | * sorted; use Pico::SORT_ASC for a alphabetical ascending order (this |
||
1284 | * is the default behaviour), Pico::SORT_DESC for a descending order |
||
1285 | * or Pico::SORT_NONE to leave the result unsorted |
||
1286 | * @return array list of found files |
||
1287 | */ |
||
1288 | protected function getFiles($directory, $fileExtension = '', $order = self::SORT_ASC) |
||
1289 | { |
||
1290 | $directory = rtrim($directory, '/'); |
||
1291 | $result = array(); |
||
1292 | |||
1293 | // scandir() reads files in alphabetical order |
||
1294 | $files = scandir($directory, $order); |
||
1295 | $fileExtensionLength = strlen($fileExtension); |
||
1296 | if ($files !== false) { |
||
1297 | foreach ($files as $file) { |
||
1298 | // exclude hidden files/dirs starting with a .; this also excludes the special dirs . and .. |
||
1299 | // exclude files ending with a ~ (vim/nano backup) or # (emacs backup) |
||
1300 | if ((substr($file, 0, 1) === '.') || in_array(substr($file, -1), array('~', '#'))) { |
||
1301 | continue; |
||
1302 | } |
||
1303 | |||
1304 | if (is_dir($directory . '/' . $file)) { |
||
1305 | // get files recursively |
||
1306 | $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension, $order)); |
||
1307 | } elseif (empty($fileExtension) || (substr($file, -$fileExtensionLength) === $fileExtension)) { |
||
1308 | $result[] = $directory . '/' . $file; |
||
1309 | } |
||
1310 | } |
||
1311 | } |
||
1312 | |||
1313 | return $result; |
||
1314 | } |
||
1315 | |||
1316 | /** |
||
1317 | * Makes a relative path absolute to Pico's root dir |
||
1318 | * |
||
1319 | * This method also guarantees a trailing slash. |
||
1320 | * |
||
1321 | * @param string $path relative or absolute path |
||
1322 | * @return string absolute path |
||
1323 | */ |
||
1324 | public function getAbsolutePath($path) |
||
1325 | { |
||
1326 | if (strncasecmp(PHP_OS, 'WIN', 3) === 0) { |
||
1327 | if (preg_match('/^([a-zA-Z]:\\\\|\\\\\\\\)/', $path) !== 1) { |
||
1328 | $path = $this->getRootDir() . $path; |
||
1329 | } |
||
1330 | } else { |
||
1331 | if (substr($path, 0, 1) !== '/') { |
||
1332 | $path = $this->getRootDir() . $path; |
||
1333 | } |
||
1334 | } |
||
1335 | return rtrim($path, '/\\') . '/'; |
||
1336 | } |
||
1337 | |||
1338 | /** |
||
1339 | * Triggers events on plugins which implement PicoPluginInterface |
||
1340 | * |
||
1341 | * Deprecated events (as used by plugins not implementing |
||
1342 | * {@link PicoPluginInterface}) are triggered by {@link PicoDeprecated}. |
||
1343 | * |
||
1344 | * @see PicoPluginInterface |
||
1345 | * @see AbstractPicoPlugin |
||
1346 | * @see DummyPlugin |
||
1347 | * @param string $eventName name of the event to trigger |
||
1348 | * @param array $params optional parameters to pass |
||
1349 | * @return void |
||
1350 | */ |
||
1351 | protected function triggerEvent($eventName, array $params = array()) |
||
1352 | { |
||
1353 | if (!empty($this->plugins)) { |
||
1354 | foreach ($this->plugins as $plugin) { |
||
1355 | // only trigger events for plugins that implement PicoPluginInterface |
||
1356 | // deprecated events (plugins for Pico 0.9 and older) will be triggered by `PicoDeprecated` |
||
1357 | if (is_a($plugin, 'PicoPluginInterface')) { |
||
1358 | $plugin->handleEvent($eventName, $params); |
||
1359 | } |
||
1360 | } |
||
1361 | } |
||
1362 | } |
||
1363 | } |
||
1364 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: