From adc356251ecd79689935ec1446c7c846db167d46 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:11:45 +0200 Subject: [PATCH 001/118] Remove index.html A empty index.html is a solution for nothing... --- content-sample/index.html | 0 lib/index.html | 0 plugins/index.html | 0 themes/index.html | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 content-sample/index.html delete mode 100644 lib/index.html delete mode 100644 plugins/index.html delete mode 100644 themes/index.html diff --git a/content-sample/index.html b/content-sample/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/lib/index.html b/lib/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/index.html b/plugins/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/themes/index.html b/themes/index.html deleted file mode 100644 index e69de29..0000000 From c496297c44dd694e9192e715d18388812567dd14 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:13:50 +0200 Subject: [PATCH 002/118] Class files should exactly match the class name --- lib/{pico.php => Pico.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{pico.php => Pico.php} (100%) diff --git a/lib/pico.php b/lib/Pico.php similarity index 100% rename from lib/pico.php rename to lib/Pico.php From 284e0ce7de3bace3112c5f4f8f1aec3fb4642213 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:19:03 +0200 Subject: [PATCH 003/118] Move constants to global.php --- global.php | 13 +++++++++++++ index.php | 14 +------------- 2 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 global.php diff --git a/global.php b/global.php new file mode 100644 index 0000000..43a8146 --- /dev/null +++ b/global.php @@ -0,0 +1,13 @@ + Date: Fri, 28 Aug 2015 18:22:32 +0200 Subject: [PATCH 004/118] Pico 1.0 I unfortunately messed up my repo so this is just a single commit... :( --- lib/Pico.php | 1220 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 922 insertions(+), 298 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 3e9b43f..8149876 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -1,170 +1,565 @@ getPlugin('PicoParsePagesContent')->setEnabled(false);` +# or adding `$config['PicoParsePagesContent.enabled'] = false;` to your +# `config.php`. +# - The meta headers are now parsed by the YAML component of the Symfony +# project (see #116), but still requires you to register new headers during +# the `onMetaHeaders` event. I'm uncertain about still requiring that. What +# do you think? +# - Meta header variables are now accessible in content files using `%meta.*%`, +# so you mustn't repeat yourself. You can now put an excerpt into the +# `description` meta variable and output the same content at the beginning +# of the article using `%meta.description%`. +# - I decided explicitly to NOT implement pages as objects ("stupidly simple", +# see above). Anyway, I think plugin developers shouldn't manipulate data in +# "wrong" events, this could lead us to unexpected behaviour. Sure, plugin +# developers still can do this, we're passing variables by reference, but +# it's not that obvious. I even thought about dereferencing the values after +# the corrosponding event was called, but that would be backward incompatible. +# What do you think? +# - How to fix the "composer problem" discussed in #221 and #223? There's a +# very simple solution: When creating a release on GitHub (after you've +# pushed the tag) you can upload "binaries". Simply execute composer locally, +# create a ZIP archive and upload the result as "binary". +# - I didn't care much about #110, #238, #239 and #240 because I simply don't +# need these features. But I think they are good ideas and the core should +# support this. Just my 2 cents :smile: +# - #201 and #231 should be closed - this can easily be achieved with plugins. +# In fact, there are already plugins adding support for these features... +# - Imo distinct documentations for users, theme designers and plugin devs is +# MUCH more important than unit tests... Pico is a project with just about +# 500 LoC (+ comments), such a manageable project doesn't necessarily require +# unit tests - they are nice to have, but that's it. Documentation should be +# the top priority! +# - Note: I'm no english native speaker. Maybe someone should look through my +# code comments :smile: +# + /** * Pico * - * @author Gilbert Pellegrom - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 0.8 + * Pico is a stupidly simple, blazing fast, flat file CMS. + * - Stupidly Simple: Picos makes creating and maintaining a + * website as simple as editing text files. + * - Blazing Fast: Pico is seriously lightweight and doesn't + * use a database, making it super fast. + * - No Database: Pico is a "flat file" CMS, meaning no + * database woes, no MySQL queries, nothing. + * - Markdown Formatting: Edit your website in your favourite + * text editor using simple Markdown formatting. + * - Twig Templates: Pico uses the Twig templating engine, + * for powerful and flexible themes. + * - Open Source: Pico is completely free and open source, + * released under the MIT license. + * See for more info. + * + * @author Gilbert Pellegrom + * @author Daniel Rudolf + * @link + * @license The MIT License + * @version 1.0 */ class Pico { - - private $config; - private $plugins; + /** + * List of loaded plugins + * + * @see Pico::loadPlugins() + * @var array + */ + protected $plugins; /** + * Current configuration of this Pico instance + * + * @see Pico::loadConfig() + * @var array + */ + protected $config; + + /** + * URL with which the user requested the page + * + * @see Pico::evaluateRequestUrl() + * @var string + */ + protected $requestUrl; + + /** + * Path to the content file being served + * + * @see Pico::discoverRequestFile() + * @var string + */ + protected $requestFile; + + /** + * Raw, not yet parsed contents to serve + * + * @see Pico::loadFileContent() + * @var string + */ + protected $rawContent; + + /** + * Meta data of the page to serve + * + * @see Pico::parseFileMeta() + * @var array + */ + protected $meta; + + /** + * Parsed content being served + * + * @see Pico::prepareFileContent() + * @see Pico::parseFileContent() + * @var string + */ + protected $content; + + /** + * List of known pages + * + * @see Pico::readPages() + * @var array + */ + protected $pages; + + /** + * Data of the page being served + * + * @see Pico::discoverCurrentPage() + * @var array + */ + protected $currentPage; + + /** + * Data of the previous page relative to the page being served + * + * @see Pico::discoverCurrentPage() + * @var array + */ + protected $previousPage; + + /** + * Data of the next page relative to the page being served + * + * @see Pico::discoverCurrentPage() + * @var array + */ + protected $nextPage; + + /** + * Twig instance used for template parsing + * + * @see Pico::registerTwig() + * @var Twig_Environment + */ + protected $twig; + + /** + * Variables passed to the twig template + * + * @var array + */ + protected $twigVariables; + + /** + * Constructs a new Pico instance + * * The constructor carries out all the processing in Pico. * Does URL routing, Markdown processing and Twig processing. */ public function __construct() { - // Load plugins - $this->load_plugins(); - $this->run_hooks('plugins_loaded'); + // load plugins + $this->loadPlugins(); + $this->triggerEvent('onPluginsLoaded', array(&$this->plugins)); - // Load the settings - $settings = $this->get_config(); - $this->run_hooks('config_loaded', array(&$settings)); + // load config + $this->loadConfig(); + $this->triggerEvent('onConfigLoaded', array(&$this->config)); - // Get request url and script url - $url = ''; - $request_url = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; - $script_url = (isset($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : ''; + // evaluate request url + $this->evaluateRequestUrl(); + $this->triggerEvent('onRequestUrl', array(&$this->requestUrl)); - // Get our url path and trim the / of the left and the right - if ($request_url != $script_url) { - $url = trim(preg_replace('/' . str_replace('/', '\/', str_replace('index.php', '', $script_url)) . '/', '', - $request_url, 1), '/'); - } - $url = preg_replace('/\?.*/', '', $url); // Strip query string - $this->run_hooks('request_url', array(&$url)); + // discover requested file + $this->discoverRequestFile(); + $this->triggerEvent('onRequestFile', array(&$this->requestFile)); - // Get the file path - if ($url) { - $file = $settings['content_dir'] . $url; + // load raw file content + $this->triggerEvent('onContentLoading', array(&$this->requestFile)); + + if (file_exists($this->requestFile)) { + $this->rawContent = $this->loadFileContent($this->requestFile); } else { - $file = $settings['content_dir'] . 'index'; - } + $this->triggerEvent('on404ContentLoading', array(&$this->requestFile)); - // Load the file - if (is_dir($file)) { - $file = $settings['content_dir'] . $url . '/index' . CONTENT_EXT; - } else { - $file .= CONTENT_EXT; - } - - $this->run_hooks('before_load_content', array(&$file)); - if (file_exists($file)) { - $content = file_get_contents($file); - } else { - $this->run_hooks('before_404_load_content', array(&$file)); - $content = file_get_contents($settings['content_dir'] . '404' . CONTENT_EXT); header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); - $this->run_hooks('after_404_load_content', array(&$file, &$content)); + $this->rawContent = $this->load404Content(); + + $this->triggerEvent('on404ContentLoaded', array(&$this->rawContent)); } - $this->run_hooks('after_load_content', array(&$file, &$content)); - $meta = $this->read_file_meta($content); - $this->run_hooks('file_meta', array(&$meta)); + $this->triggerEvent('onContentLoaded', array(&$this->rawContent)); - $this->run_hooks('before_parse_content', array(&$content)); - $content = $this->parse_content($content); - $this->run_hooks('after_parse_content', array(&$content)); - $this->run_hooks('content_parsed', array(&$content)); // Depreciated @ v0.8 + // parse file meta + $headers = $this->getMetaHeaders(); - // Get all the pages - $pages = $this->get_pages($settings['base_url'], $settings['pages_order_by'], $settings['pages_order'], - $settings['excerpt_length']); - $prev_page = array(); - $current_page = array(); - $next_page = array(); - while ($current_page = current($pages)) { - if ((isset($meta['title'])) && ($meta['title'] == $current_page['title'])) { - break; - } - next($pages); + $this->triggerEvent('onMetaParsing', array(&$this->rawContent, &$headers)); + $this->meta = $this->parseFileMeta($this->rawContent, $headers); + $this->triggerEvent('onMetaParsed', array(&$this->meta)); + + // parse file content + $this->triggerEvent('onContentParsing', array(&$this->rawContent)); + + $this->content = $this->prepareFileContent($this->rawContent); + $this->triggerEvent('onContentPrepared', array(&$this->content)); + + $this->content = $this->parseFileContent($this->content); + $this->triggerEvent('onContentParsed', array(&$this->content)); + + // read pages + $this->triggerEvent('onPagesLoading'); + + $this->readPages(); + $this->discoverCurrentPage(); + + $this->triggerEvent('onPagesLoaded', array( + &$this->pages, + &$this->currentPage, + &$this->previousPage, + &$this->nextPage + )); + + // register twig + $this->triggerEvent('onTwigRegistration'); + $this->registerTwig(); + + // render template + $this->twigVariables = $this->getTwigVariables(); + if (isset($this->meta['template']) && $this->meta['template']) { + $templateName = $this->meta['template']; + } else { + $templateName = 'index'; + } + if (file_exists(THEMES_DIR . $this->getConfig('theme') . '/' . $templateName . '.twig')) { + $templateName .= '.twig'; + } else { + $templateName .= '.html'; } - $prev_page = next($pages); - prev($pages); - $next_page = prev($pages); - $this->run_hooks('get_pages', array(&$pages, &$current_page, &$prev_page, &$next_page)); - // Load the theme - $this->run_hooks('before_twig_register'); - Twig_Autoloader::register(); - $loader = new Twig_Loader_Filesystem(THEMES_DIR . $settings['theme']); - $twig = new Twig_Environment($loader, $settings['twig_config']); - $twig->addExtension(new Twig_Extension_Debug()); - $twig_vars = array( - 'config' => $settings, - 'base_dir' => rtrim(ROOT_DIR, '/'), - 'base_url' => $settings['base_url'], - 'theme_dir' => THEMES_DIR . $settings['theme'], - 'theme_url' => $settings['base_url'] . '/' . basename(THEMES_DIR) . '/' . $settings['theme'], - 'site_title' => $settings['site_title'], - 'meta' => $meta, - 'content' => $content, - 'pages' => $pages, - 'prev_page' => $prev_page, - 'current_page' => $current_page, - 'next_page' => $next_page, - 'is_front_page' => $url ? false : true, - ); + $this->triggerEvent('onPageRendering', array(&$this->twig, &$this->twigVariables, &$templateName)); + + $output = $this->twig->render($templateName, $this->twigVariables); + $this->triggerEvent('onPageRendered', array(&$output)); - $template = (isset($meta['template']) && $meta['template']) ? $meta['template'] : 'index'; - $this->run_hooks('before_render', array(&$twig_vars, &$twig, &$template)); - $output = $twig->render($template . '.html', $twig_vars); - $this->run_hooks('after_render', array(&$output)); echo $output; } /** - * Load any plugins + * Loads plugins from PLUGINS_DIR in alphabetical order + * + * Plugin files may be prefixed by a number (e.g. 00-PicoDeprecated.php) + * to indicate their processsing order. You MUST NOT use prefixes between + * 00 and 19 (reserved for built-in plugins). + * + * @return void + * @throws RuntimeException thrown when a plugin couldn't be loaded */ - protected function load_plugins() + protected function loadPlugins() { $this->plugins = array(); - $plugins = $this->get_files(PLUGINS_DIR, '.php'); - if (!empty($plugins)) { - foreach ($plugins as $plugin) { - include_once($plugin); - $plugin_name = preg_replace("/\\.[^.\\s]{3}$/", '', basename($plugin)); - if (class_exists($plugin_name)) { - $obj = new $plugin_name; - $this->plugins[] = $obj; - } + $pluginFiles = $this->getFiles(PLUGINS_DIR, '.php'); + foreach ($pluginFiles as $pluginFile) { + require_once($pluginFile); + + $className = preg_replace('/^[0-9]+-/', '', basename($pluginFile, '.php')); + if (class_exists($className)) { + $this->plugins[$className] = new $className($this); + } else { + // TODO: breaks backward compatibility + //throw new RuntimeException("Unable to load plugin '".$className."'"); } } } /** - * Parses the content using Parsedown-extra + * Returns the instance of a named plugin * - * @param string $content the raw txt content - * @return string $content the Markdown formatted content + * Plugins SHOULD implement {@link IPicoPlugin}, but you MUST NOT rely on + * it. For more information see {@link IPicoPlugin}. + * + * @see Pico::loadPlugins() + * @param string $pluginName name of the plugin + * @return object instance of the plugin + * @throws RuntimeException thrown when the plugin wasn't found */ - protected function parse_content($content) + public function getPlugin($pluginName) { - $content = preg_replace('#/\*.+?\*/#s', '', $content, 1); // Remove first comment (with meta) - $content = str_replace('%base_url%', $this->base_url(), $content); - $Parsedown = new ParsedownExtra(); - $content= $Parsedown->text($content); + if (isset($this->plugins[$pluginName])) { + return $this->plugins[$pluginName]; + } - return $content; + throw new RuntimeException("Missing plugin '".$pluginName."'"); } /** - * Parses the file meta from the txt file header + * Returns all loaded plugins * - * @param string $content the raw txt content - * @return array $headers an array of meta values + * @see Pico::loadPlugins() + * @return array */ - protected function read_file_meta($content) + public function getPlugins() { - $config = $this->config; + return $this->plugins; + } + /** + * Loads the config.php from CONFIG_DIR + * + * @return void + */ + protected function loadConfig() + { + $defaultConfig = array( + 'site_title' => 'Pico', + 'base_url' => '', + 'rewrite_url' => null, + 'theme' => 'default', + 'date_format' => '%D %T', + 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), + 'pages_order_by' => 'alpha', + 'pages_order' => 'asc', + 'content_dir' => ROOT_DIR . 'content-sample/', + 'content_ext' => '.md', + 'timezone' => '' + ); + + $config = require(CONFIG_DIR . 'config.php'); + $this->config = is_array($config) ? $config + $defaultConfig : $defaultConfig; + + if (empty($this->config['base_url'])) { + $this->config['base_url'] = $this->getBaseUrl(); + } + if (!empty($this->config['content_dir'])) { + $this->config['content_dir'] = rtrim($this->config['content_dir'], '/') . '/'; + } + if (!empty($this->config['timezone'])) { + date_default_timezone_set($this->config['timezone']); + } else { + // explicitly set a default timezone to prevent a E_NOTICE + // when no timezone is set; the `date_default_timezone_get()` + // function always returns a timezone, at least UTC + $defaultTimezone = date_default_timezone_get(); + date_default_timezone_set($defaultTimezone); + } + } + + /** + * Returns either the value of the specified config variable or + * the config array + * + * @see Pico::loadConfig() + * @param string $configName optional name of a config variable + * @return mixed returns either the value of the named config + * variable, null if the config variable doesn't exist or the config + * array if no config name was supplied + */ + public function getConfig($configName = null) + { + if ($configName !== null) { + return isset($this->config[$configName]) ? $this->config[$configName] : null; + } else { + return $this->config; + } + } + + /** + * Evaluates the requested URL + * + * Pico 1.0 uses the QUERY_STRING routing method (e.g. /pico/?sub/page) to + * support SEO-like URLs out-of-the-box with any webserver. You can still + * setup URL rewriting (e.g. using mod_rewrite on Apache) to basically + * remove the `?` from URLs, but your rewritten URLs must follow the + * new QUERY_STRING principles. URL rewriting requires some special + * configuration on your webserver, but this should be "basic work" for + * any webmaster... + * + * Pico 0.9 and older required Apache with mod_rewrite enabled, thus old + * plugins, templates and contents may require you to enable URL rewriting + * to work. If you're upgrading from Pico 0.9, you probably have to update + * your rewriting rules. + * + * We recommend you to use the `link` filter in templates to create + * internal links, e.g. `{{ "sub/page"|link }}` is equivalent to + * `{{ base_url }}sub/page`. In content files you can still use the + * `%base_url%` variable; e.g. `%base_url%?sub/page` is automatically + * replaced accordingly. + * + * @return void + */ + protected function evaluateRequestUrl() + { + // use QUERY_STRING; e.g. /pico/?sub/page + // if you want to use rewriting, you MUST make your rules to + // rewrite the URLs to follow the QUERY_STRING method + // + // Note: you MUST NOT call the index page with /pico/?someBooleanParameter; + // use /pico/?someBooleanParameter= or /pico/?index&someBooleanParameter instead + $pathComponent = $_SERVER['QUERY_STRING']; + if (($pathComponentLength = strpos($pathComponent, '&')) !== false) { + $pathComponent = substr($pathComponent, 0, $pathComponentLength); + } + $this->requestUrl = (strpos($pathComponent, '=') === false) ? urldecode($pathComponent) : ''; + } + + /** + * Returns the URL with which the user requested the page + * + * @see Pico::evaluateRequestUrl() + * @return string request URL + */ + public function getRequestUrl() + { + return $this->requestUrl; + } + + /** + * Uses the request URL to discover the content file to serve + * + * @return void + */ + protected function discoverRequestFile() + { + if (empty($this->requestUrl)) { + $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); + } else { + $this->requestFile = $this->getConfig('content_dir') . $this->requestUrl; + if (is_dir($this->requestFile)) { + // if no index file is found, try a accordingly named file in the previous dir + // if this file doesn't exist either, show the 404 page, but assume the index + // file as being requested (maintains backward compatibility to Pico < 1.0) + $indexFile = $this->requestFile . '/index' . $this->getConfig('content_ext'); + if (file_exists($indexFile) || !file_exists($this->requestFile . $this->getConfig('content_ext'))) { + $this->requestFile = $indexFile; + return; + } + } + $this->requestFile .= $this->getConfig('content_ext'); + } + } + + /** + * Returns the path to the content file to serve + * + * @see Pico::discoverRequestFile() + * @return string file path + */ + public function getRequestFile() + { + return $this->requestFile; + } + + /** + * Returns the raw contents of a file + * + * @param string $file file path + * @return string raw contents of the file + */ + public function loadFileContent($file) + { + return file_get_contents($file); + } + + /** + * Returns the raw contents of the 404 file if the requested file wasn't found + * + * @return string raw contents of the 404 file + */ + public function load404Content() + { + return $this->loadFileContent($this->getConfig('content_dir') . '404' . $this->getConfig('content_ext')); + } + + /** + * Returns the cached raw contents, either of the requested or the 404 file + * + * @see Pico::loadFileContent() + * @return string raw contents + */ + public function getRawContent() + { + return $this->rawContent; + } + + /** + * Returns known meta headers and triggers the onMetaHeaders event + * + * Heads up! Calling this method triggers the `onMetaHeaders` event. + * Keep this in mind to prevent a infinite loop! + * + * @return array known meta headers + */ + public function getMetaHeaders() + { $headers = array( 'title' => 'Title', 'description' => 'Description', @@ -174,232 +569,461 @@ class Pico 'template' => 'Template' ); - // Add support for custom headers by hooking into the headers array - $this->run_hooks('before_read_file_meta', array(&$headers)); - - foreach ($headers as $field => $regex) { - if (preg_match('/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $content, $match) && $match[1]) { - $headers[$field] = trim(preg_replace("/\s*(?:\*\/|\?>).*/", '', $match[1])); - } else { - $headers[$field] = ''; - } - } - - if (isset($headers['date'])) { - $headers['date_formatted'] = utf8_encode(strftime($config['date_format'], strtotime($headers['date']))); - } - + $this->triggerEvent('onMetaHeaders', array(&$headers)); return $headers; } /** - * Loads the config + * Parses the file meta from raw file contents * - * @return array $config an array of config values - */ - protected function get_config() - { - if (file_exists(CONFIG_DIR . 'config.php')) { - $this->config = require(CONFIG_DIR . 'config.php'); - } else if (file_exists(ROOT_DIR . 'config.php')) { - // deprecated - $this->config = require(ROOT_DIR . 'config.php'); - } - - $defaults = array( - 'site_title' => 'Pico', - 'base_url' => $this->base_url(), - 'theme' => 'default', - 'date_format' => '%D %T', - 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), - 'pages_order_by' => 'alpha', - 'pages_order' => 'asc', - 'excerpt_length' => 50, - 'content_dir' => 'content-sample/', - ); - - if (is_array($this->config)) { - $this->config = array_merge($defaults, $this->config); - } else { - $this->config = $defaults; - } - - return $this->config; - } - - /** - * Get a list of pages + * Meta data MUST start on the first line of the file, either opened and + * closed by --- or C-style block comments (deprecated). The headers are + * parsed by the YAML component of the Symfony project. You MUST register + * new headers during the `onMetaHeaders` event first, otherwise they are + * ignored and won't be returned. * - * @param string $base_url the base URL of the site - * @param string $order_by order by "alpha" or "date" - * @param string $order order "asc" or "desc" - * @return array $sorted_pages an array of pages + * @see + * @param string $content the raw file contents + * @param array $headers a array containing the known headers + * @return array parsed meta data */ - protected function get_pages($base_url, $order_by = 'alpha', $order = 'asc', $excerpt_length = 50) + public function parseFileMeta($rawContent, array $headers) { - $config = $this->config; + $meta = array(); + $pattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" + . "(.*?)(?:\r)?\n(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; + if (preg_match($pattern, $rawContent, $rawMetaMatches)) { + $yamlParser = new \Symfony\Component\Yaml\Parser(); + $rawMeta = $yamlParser->parse($rawMetaMatches[3]); + $rawMeta = array_change_key_case($rawMeta, CASE_LOWER); - $pages = $this->get_files($config['content_dir'], CONTENT_EXT); - $sorted_pages = array(); - $date_id = 0; - foreach ($pages as $key => $page) { - // Skip 404 - if (basename($page) == '404' . CONTENT_EXT) { - unset($pages[$key]); - continue; - } - - // Ignore Emacs (and Nano) temp files - if (in_array(substr($page, -1), array('~', '#'))) { - unset($pages[$key]); - continue; - } - // Get title and format $page - $page_content = file_get_contents($page); - $page_meta = $this->read_file_meta($page_content); - $page_content = $this->parse_content($page_content); - $url = str_replace($config['content_dir'], $base_url . '/', $page); - $url = str_replace('index' . CONTENT_EXT, '', $url); - $url = str_replace(CONTENT_EXT, '', $url); - $data = array( - 'title' => isset($page_meta['title']) ? $page_meta['title'] : '', - 'url' => $url, - 'author' => isset($page_meta['author']) ? $page_meta['author'] : '', - 'date' => isset($page_meta['date']) ? $page_meta['date'] : '', - 'date_formatted' => isset($page_meta['date']) ? utf8_encode(strftime($config['date_format'], - strtotime($page_meta['date']))) : '', - 'content' => $page_content, - 'excerpt' => $this->limit_words(strip_tags($page_content), $excerpt_length), - //this addition allows the 'description' meta to be picked up in content areas... specifically to replace 'excerpt' - 'description' => isset($page_meta['description']) ? $page_meta['description'] : '', - - ); - - // Extend the data provided with each page by hooking into the data array - $this->run_hooks('get_page_data', array(&$data, $page_meta)); - - if ($order_by == 'date' && isset($page_meta['date'])) { - $sorted_pages[$page_meta['date'] . $date_id] = $data; - $date_id++; - } else { - $sorted_pages[$page] = $data; - } - } - - if ($order == 'desc') { - krsort($sorted_pages); - } else { - ksort($sorted_pages); - } - - return $sorted_pages; - } - - /** - * Processes any hooks and runs them - * - * @param string $hook_id the ID of the hook - * @param array $args optional arguments - */ - protected function run_hooks($hook_id, $args = array()) - { - if (!empty($this->plugins)) { - foreach ($this->plugins as $plugin) { - if (is_callable(array($plugin, $hook_id))) { - call_user_func_array(array($plugin, $hook_id), $args); + // TODO: maybe we should change this to pass all headers, no matter + // they are registered during the `onMetaHeaders` event or not... + foreach ($headers as $fieldId => $fieldName) { + $fieldName = strtolower($fieldName); + if (isset($rawMeta[$fieldName])) { + $meta[$fieldId] = $rawMeta[$fieldName]; + } else { + $meta[$fieldId] = ''; } } + + if (!empty($meta['date'])) { + $meta['time'] = strtotime($meta['date']); + $meta['date_formatted'] = utf8_encode(strftime($this->getConfig('date_format'), $meta['time'])); + } else { + $meta['time'] = $meta['date_formatted'] = ''; + } + } else { + foreach ($headers as $id => $field) { + $meta[$id] = ''; + } + + $meta['time'] = $meta['date_formatted'] = ''; + } + + return $meta; + } + + /** + * Returns the parsed meta data of the requested page + * + * @see Pico::parseFileMeta() + * @return array parsed meta data + */ + public function getFileMeta() + { + return $this->meta; + } + + /** + * Applies some static preparations to the raw contents of a page, + * e.g. removing the meta header and replacing %base_url% + * + * @param string $rawContent raw contents of a page + * @return string contents prepared for parsing + */ + public function prepareFileContent($rawContent) + { + // remove meta header + $metaHeaderPattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" + . "(.*?)(?:\r)?\n(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; + $content = preg_replace($metaHeaderPattern, '', $rawContent, 1); + + // replace %site_title% + $content = str_replace('%site_title%', $this->getConfig('site_title'), $content); + + // replace %base_url% + if ($this->isUrlRewritingEnabled()) { + // always use `%base_url%?sub/page` syntax for internal links + // we'll replace the links accordingly, depending on enabled rewriting + $content = str_replace('%base_url%?', $this->getBaseUrl(), $content); + } else { + // actually not necessary, but makes the URLs look a little nicer + $content = str_replace('%base_url%?', $this->getBaseUrl() . '?', $content); + } + $content = str_replace('%base_url%', rtrim($this->getBaseUrl(), '/'), $content); + + // replace %theme_url% + $themeUrl = $this->getBaseUrl() . basename(THEMES_DIR) . '/' . $this->getConfig('theme'); + $content = str_replace('%theme_url%', $themeUrl, $content); + + // replace %meta.*% + $metaKeys = array_map(function ($metaKey) { + return '%meta.' . $metaKey . '%'; + }, array_keys($this->meta)); + $metaValues = array_values($this->meta); + $content = str_replace($metaKeys, $metaValues, $content); + + return $content; + } + + /** + * Parses the contents of a page using ParsedownExtra + * + * @param string $content raw contents of a page (Markdown) + * @return string parsed contents (HTML) + */ + public function parseFileContent($content) + { + $parsedown = new ParsedownExtra(); + return $parsedown->text($content); + } + + /** + * Returns the cached contents of the requested page + * + * @see Pico::parseFileContent() + * @return string parsed contents + */ + public function getFileContent() + { + return $this->content; + } + + /** + * Reads the data of all pages known to Pico + * + * @return void + */ + protected function readPages() + { + $pages = array(); + $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext')); + foreach ($files as $i => $file) { + // skip 404 page + if (basename($file) == '404' . $this->getConfig('content_ext')) { + unset($files[$i]); + continue; + } + + $id = substr($file, strlen($this->getConfig('content_dir')), -strlen($this->getConfig('content_ext'))); + $url = $this->getPageUrl($id); + if ($file != $this->requestFile) { + $rawContent = file_get_contents($file); + $meta = $this->parseFileMeta($rawContent, $this->getMetaHeaders()); + } else { + $rawContent = &$this->rawContent; + $meta = &$this->meta; + } + + // build page data + // title, description, author and date are assumed to be pretty basic data + // everything else is accessible through $page['meta'] + $page = array( + 'id' => $id, + 'url' => $url, + 'title' => &$meta['title'], + 'description' => &$meta['description'], + 'author' => &$meta['author'], + 'time' => &$meta['time'], + 'date' => &$meta['date'], + 'date_formatted' => &$meta['date_formatted'], + 'raw_content' => &$rawContent, + 'meta' => &$meta + ); + + if ($file == $this->requestFile) { + $page['content'] = &$this->content; + } + + unset($rawContent, $meta); + + // trigger event + $this->triggerEvent('onSinglePageLoaded', array(&$page)); + + $pages[$id] = $page; + } + + // sort pages by date + // Pico::getFiles() already sorts alphabetically + $this->pages = $pages; + if ($this->getConfig('pages_order_by') == 'date') { + $pageIds = array_keys($this->pages); + $order = $this->getConfig('pages_order'); + + uasort($this->pages, function ($a, $b) use ($pageIds, $order) { + if (empty($a['time']) || empty($b['time'])) { + $cmp = (empty($a['time']) - empty($b['time'])); + } else { + $cmp = ($b['time'] - $a['time']); + } + + if ($cmp === 0) { + // never assume equality; fallback to the original order (= alphabetical) + $cmp = (array_search($b['id'], $pageIds) - array_search($a['id'], $pageIds)); + } + + return $cmp * (($order == 'desc') ? 1 : -1); + }); + } elseif ($this->getConfig('pages_order') == 'desc') { + $this->pages = array_reverse($this->pages); } } /** - * Helper function to work out the base URL + * Returns the list of known pages + * + * @see Pico::readPages() + * @return array the data of all pages + */ + public function getPages() + { + return $this->pages; + } + + /** + * Walks through the list of known pages and discovers the requested page + * as well as the previous and next page relative to it + * + * @return void + */ + protected function discoverCurrentPage() + { + $pageIds = array_keys($this->pages); + + $contentDir = $this->getConfig('content_dir'); + $contentExt = $this->getConfig('content_ext'); + $currentPageId = substr($this->requestFile, strlen($contentDir), -strlen($contentExt)); + $currentPageIndex = array_search($currentPageId, $pageIds); + if ($currentPageIndex !== false) { + $this->currentPage = &$this->pages[$currentPageId]; + + if (($this->getConfig('order_by') == 'date') && ($this->getConfig('order') == 'desc')) { + $previousPageOffset = 1; + $nextPageOffset = -1; + } else { + $previousPageOffset = -1; + $nextPageOffset = 1; + } + + if (isset($pageIds[$currentPageIndex + $previousPageOffset])) { + $previousPageId = $pageIds[$currentPageIndex + $previousPageOffset]; + $this->previousPage = &$this->pages[$previousPageId]; + } + + if (isset($pageIds[$currentPageIndex + $nextPageOffset])) { + $nextPageId = $pageIds[$currentPageIndex + $nextPageOffset]; + $this->nextPage = &$this->pages[$nextPageId]; + } + } + } + + /** + * Returns the data of the requested page + * + * @see Pico::discoverCurrentPage() + * @return array page data + */ + public function getCurrentPage() + { + return $this->currentPage; + } + + /** + * Returns the data of the previous page relative to the page being served + * + * @see Pico::discoverCurrentPage() + * @return array page data + */ + public function getPreviousPage() + { + return $this->previousPage; + } + + /** + * Returns the data of the next page relative to the page being served + * + * @see Pico::discoverCurrentPage() + * @return array page data + */ + public function getNextPage() + { + return $this->nextPage; + } + + /** + * Registers the twig template engine + * + * @return void + */ + protected function registerTwig() + { + $twigLoader = new Twig_Loader_Filesystem(THEMES_DIR . $this->getConfig('theme')); + $this->twig = new Twig_Environment($twigLoader, $this->getConfig('twig_config')); + $this->twig->addExtension(new Twig_Extension_Debug()); + $this->twig->addFilter(new Twig_SimpleFilter('link', array($this, 'getPageUrl'))); + } + + /** + * Returns the twig template engine + * + * @return Twig_Environment twig template engine + */ + public function getTwig() + { + return $this->twig; + } + + /** + * Returns the variables passed to the template + * + * URLs and paths (namely base_dir, base_url, theme_dir and theme_url) + * don't add a trailing slash for historic reasons. + * + * @return array template variables + */ + protected function getTwigVariables() + { + $frontPage = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); + return array( + 'config' => $this->getConfig(), + 'base_dir' => rtrim(ROOT_DIR, '/'), + 'base_url' => rtrim($this->getBaseUrl(), '/'), + 'theme_dir' => THEMES_DIR . $this->getConfig('theme'), + 'theme_url' => $this->getBaseUrl() . basename(THEMES_DIR) . '/' . $this->getConfig('theme'), + 'rewrite_url' => $this->isUrlRewritingEnabled(), + 'site_title' => $this->getConfig('site_title'), + 'meta' => $this->meta, + 'content' => $this->content, + 'pages' => $this->pages, + 'prev_page' => $this->previousPage, + 'current_page' => $this->currentPage, + 'next_page' => $this->nextPage, + 'is_front_page' => ($this->requestFile == $frontPage), + ); + } + + /** + * Returns the base URL of this Pico instance * * @return string the base url */ - protected function base_url() + public function getBaseUrl() { - $config = $this->config; - - if (isset($config['base_url']) && $config['base_url']) { - return $config['base_url']; + if (!empty($this->getConfig('base_url'))) { + return $this->getConfig('base_url'); } - $url = ''; - $request_url = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; - $script_url = (isset($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : ''; - if ($request_url != $script_url) { - $url = trim(preg_replace('/' . str_replace('/', '\/', str_replace('index.php', '', $script_url)) . '/', '', - $request_url, 1), '/'); - } - - $protocol = $this->get_protocol(); - - return rtrim(str_replace($url, '', $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']), '/'); - } - - /** - * Tries to guess the server protocol. Used in base_url() - * - * @return string the current protocol - */ - protected function get_protocol() - { - $protocol = 'http'; - if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' && $_SERVER['HTTPS'] != '') { + if ( + (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') + || ($_SERVER['SERVER_PORT'] == 443) + || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + ) { $protocol = 'https'; + } else { + $protocol = 'http'; } - return $protocol; + $this->config['base_url'] = + $protocol . "://" . $_SERVER['HTTP_HOST'] + . dirname($_SERVER['SCRIPT_NAME']) . '/'; + + return $this->getConfig('base_url'); } /** - * Helper function to recusively get all files in a directory + * Returns true if URL rewriting is enabled * - * @param string $directory start directory - * @param string $ext optional limit to file extensions - * @return array the matched files + * @return boolean true if URL rewriting is enabled, false otherwise */ - protected function get_files($directory, $ext = '') + public function isUrlRewritingEnabled() { - $array_items = array(); - if ($files = scandir($directory)) { + if (($this->getConfig('rewrite_url') === null) && isset($_SERVER['PICO_URL_REWRITING'])) { + return (bool) $_SERVER['PICO_URL_REWRITING']; + } elseif ($this->getConfig('rewrite_url')) { + return true; + } + + return false; + } + + /** + * Returns the URL to a given page + * + * @param string $page identifier of the page to link to + * @return string URL + */ + public function getPageUrl($page) + { + return $this->getBaseUrl() . ((!$this->isUrlRewritingEnabled() && !empty($page)) ? '?' : '') . $page; + } + + /** + * Recursively walks through a directory and returns all containing files + * matching the specified file extension in alphabetical order + * + * @param string $directory start directory + * @param string $ext return files with this file extension only (optional) + * @return array list of found files + */ + protected function getFiles($directory, $fileExtension = '') + { + $directory = rtrim($directory, '/'); + $result = array(); + + // scandir() reads files in alphabetical order + $files = scandir($directory); + $fileExtensionLength = strlen($fileExtension); + if ($files !== false) { foreach ($files as $file) { - if (in_array(substr($file, -1), array('~', '#'))) { + // exclude hidden files/dirs starting with a .; this also excludes the special dirs . and .. + // exclude files ending with a ~ (vim/nano backup) or # (emacs backup) + if ((substr($file, 0, 1) === '.') || in_array(substr($file, -1), array('~', '#'))) { continue; } - if (preg_match("/^(^\.)/", $file) === 0) { - if (is_dir($directory . "/" . $file)) { - $array_items = array_merge($array_items, $this->get_files($directory . "/" . $file, $ext)); - } else { - $file = $directory . "/" . $file; - if (!$ext || strstr($file, $ext)) { - $array_items[] = preg_replace("/\/\//si", "/", $file); - } - } + + if (is_dir($directory . '/' . $file)) { + // get files recursively + $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension)); + } elseif (empty($fileExtension) || (substr($file, -strlen($fileExtension)) === $fileExtension)) { + $result[] = $directory . '/' . $file; } } } - return $array_items; + return $result; } /** - * Helper function to limit the words in a string + * Triggers events on plugins which implement {@link IPicoPlugin} * - * @param string $string the given string - * @param int $word_limit the number of words to limit to - * @return string the limited string + * Deprecated events (as used by plugins not implementing + * {@link IPocPlugin}) are triggered by {@link PicoDeprecated}. + * + * @param string $eventName name of the event to trigger + * @param array $params optional parameters to pass + * @return void */ - protected function limit_words($string, $word_limit) + protected function triggerEvent($eventName, array $params = array()) { - $words = explode(' ', $string); - $excerpt = trim(implode(' ', array_splice($words, 0, $word_limit))); - if (count($words) > $word_limit) { - $excerpt .= '…'; + foreach ($this->plugins as $plugin) { + // only trigger events for plugins that implement IPicoPlugin + // deprecated events (plugins for Pico 0.9 and older) will be + // triggered by the `PicoPluginDeprecated` plugin + if (is_a($plugin, 'IPicoPlugin')) { + $plugin->handleEvent($eventName, $params); + } } - - return $excerpt; } - } From cd145821baac7147ec24103719efec3bd473804d Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:24:06 +0200 Subject: [PATCH 005/118] Remove pull request message --- lib/Pico.php | 90 ---------------------------------------------------- 1 file changed, 90 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 8149876..7e4a7f5 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -1,95 +1,5 @@ getPlugin('PicoParsePagesContent')->setEnabled(false);` -# or adding `$config['PicoParsePagesContent.enabled'] = false;` to your -# `config.php`. -# - The meta headers are now parsed by the YAML component of the Symfony -# project (see #116), but still requires you to register new headers during -# the `onMetaHeaders` event. I'm uncertain about still requiring that. What -# do you think? -# - Meta header variables are now accessible in content files using `%meta.*%`, -# so you mustn't repeat yourself. You can now put an excerpt into the -# `description` meta variable and output the same content at the beginning -# of the article using `%meta.description%`. -# - I decided explicitly to NOT implement pages as objects ("stupidly simple", -# see above). Anyway, I think plugin developers shouldn't manipulate data in -# "wrong" events, this could lead us to unexpected behaviour. Sure, plugin -# developers still can do this, we're passing variables by reference, but -# it's not that obvious. I even thought about dereferencing the values after -# the corrosponding event was called, but that would be backward incompatible. -# What do you think? -# - How to fix the "composer problem" discussed in #221 and #223? There's a -# very simple solution: When creating a release on GitHub (after you've -# pushed the tag) you can upload "binaries". Simply execute composer locally, -# create a ZIP archive and upload the result as "binary". -# - I didn't care much about #110, #238, #239 and #240 because I simply don't -# need these features. But I think they are good ideas and the core should -# support this. Just my 2 cents :smile: -# - #201 and #231 should be closed - this can easily be achieved with plugins. -# In fact, there are already plugins adding support for these features... -# - Imo distinct documentations for users, theme designers and plugin devs is -# MUCH more important than unit tests... Pico is a project with just about -# 500 LoC (+ comments), such a manageable project doesn't necessarily require -# unit tests - they are nice to have, but that's it. Documentation should be -# the top priority! -# - Note: I'm no english native speaker. Maybe someone should look through my -# code comments :smile: -# - /** * Pico * From 43a7540f46ac5195e084531b959d6b2ec5d3a30b Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:25:54 +0200 Subject: [PATCH 006/118] Add IPicoPlugin --- lib/IPicoPlugin.php | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lib/IPicoPlugin.php diff --git a/lib/IPicoPlugin.php b/lib/IPicoPlugin.php new file mode 100644 index 0000000..2a9e284 --- /dev/null +++ b/lib/IPicoPlugin.php @@ -0,0 +1,97 @@ + required plugins + */ + public function getDependencies(); + + /** + * Returns a list of plugins which depend on this plugin + * + * @return array dependant plugins + */ + public function getDependants(); + + /** + * Returns the plugins instance of Pico + * + * @return Pico the plugins instance of Pico + */ + public function getPico(); +} From 07ae26789c5daa04e880772d7bbcaa1e3fca3231 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:26:56 +0200 Subject: [PATCH 007/118] Add AbstractPicoPlugin The plugin magic takes place here... --- lib/AbstractPicoPlugin.php | 248 +++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 lib/AbstractPicoPlugin.php diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php new file mode 100644 index 0000000..ef47cc4 --- /dev/null +++ b/lib/AbstractPicoPlugin.php @@ -0,0 +1,248 @@ + + * @see IPicoPlugin::getDependencies() + * @see AbstractPicoPlugin::checkDependencies() + */ + protected $dependsOn = array(); + + /** + * List of plugin which depend on this plugin + * + * @var array + * @see IPicoPlugin::getDependants() + * @see AbstractPicoPlugin::checkDependants() + */ + private $dependants; + + /** + * @see IPicoPlugin::__construct() + */ + public function __construct(Pico $pico) + { + $this->pico = $pico; + } + + /** + * @see IPicoPlugin::handleEvent() + */ + public function handleEvent($eventName, array $params) + { + // plugins can be enabled/disabled using the config + if ($eventName === 'onConfigLoaded') { + $pluginEnabled = $this->getConfig(get_called_class().'.enabled'); + if ($pluginEnabled !== null) { + $this->setEnabled($pluginEnabled); + } + } + + if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) { + if (method_exists($this, $eventName)) { + call_user_func_array(array($this, $eventName), $params); + } + } + } + + /** + * @see IPicoPlugin::setEnabled() + */ + public function setEnabled($enabled, $recursive = true, $auto = false) + { + $this->statusChanged = (!$this->statusChanged) ? !$auto : true; + $this->enabled = (bool) $enabled; + + if ($enabled) { + $this->checkDependencies($recursive); + } else { + $this->checkDependants($recursive); + } + } + + /** + * @see IPicoPlugin::isEnabled() + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * @see IPicoPlugin::isStatusChanged() + */ + public function isStatusChanged() + { + return $this->statusChanged; + } + + /** + * @see IPicoPlugin::getPico() + */ + public function getPico() + { + return $this->pico; + } + + /** + * Passes all not satisfiable method calls to {@link Pico} + * + * @param string $methodName name of the method to call + * @param array $params parameters to pass + * @return mixed return value of the called method + */ + public function __call($methodName, array $params) + { + if (method_exists($this->getPico(), $methodName)) { + return call_user_func_array(array($this->getPico(), $methodName), $params); + } + + throw new BadMethodCallException( + 'Call to undefined method '.get_class($this->getPico()).'::'.$methodName.'() ' + . 'through '.get_called_class().'::__call()' + ); + } + + /** + * Enables all plugins on which this plugin depends + * + * @param boolean $recursive enable required plugins automatically + * @return void + * @throws RuntimeException thrown when a dependency fails + */ + protected function checkDependencies($recursive) + { + foreach ($this->getDependencies() as $pluginName) { + try { + $plugin = $this->getPlugin($pluginName); + } catch (RuntimeException $e) { + throw new RuntimeException( + "Unable to enable plugin '" . get_called_class() . "':" + . "Required plugin '" . $pluginName . "' not found" + ); + } + + // plugins which don't implement IPicoPlugin are always enabled + if (is_a($plugin, 'IPicoPlugin') && !$plugin->isEnabled()) { + if ($recursive) { + if (!$plugin->isStatusChanged()) { + $plugin->setEnabled(true, true, true); + } else { + throw new RuntimeException( + "Unable to enable plugin '" . get_called_class() . "':" + . "Required plugin '" . $pluginName . "' was disabled manually" + ); + } + } else { + throw new RuntimeException( + "Unable to enable plugin '" . get_called_class() . "':" + . "Required plugin '" . $pluginName . "' is disabled" + ); + } + } + } + } + + /** + * @see IPicoPlugin::getDependencies() + */ + public function getDependencies() + { + return $this->dependsOn; + } + + /** + * Disables all plugins which depend on this plugin + * + * @param boolean $recursive disabled dependant plugins automatically + * @return void + * @throws RuntimeException thrown when a dependency fails + */ + protected function checkDependants($recursive) + { + $dependants = $this->getDependants(); + if (!empty($dependants)) { + if ($recursive) { + foreach ($this->getDependants() as $pluginName => $plugin) { + if ($plugin->isEnabled()) { + if (!$plugin->isStatusChanged()) { + $plugin->setEnabled(false, true, true); + } else { + throw new RuntimeException( + "Unable to disable plugin '" . get_called_class() . "': " + + "Required by manually enabled plugin '" . $pluginName . "'" + ); + } + } + } + } else { + $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' '; + $dependantsList .= "'" + implode("', '", array_keys($dependants)) + "'"; + throw new RuntimeException( + "Unable to disable plugin '" . get_called_class() . "': " + + "Required by " . $dependantsList + ); + } + } + } + + /** + * @see IPicoPlugin::getDependants() + */ + public function getDependants() + { + if ($this->dependants === null) { + $this->dependants = array(); + foreach ($this->getPlugins() as $pluginName => $plugin) { + // only plugins which implement IPicoPlugin support dependencies + if (is_a($plugin, 'IPicoPlugin')) { + $dependencies = $plugin->getDependencies(); + if (in_array(get_called_class(), $dependencies)) { + $this->dependants[$pluginName] = $plugin; + } + } + } + } + + return $this->dependants; + } +} From b3477906f16f4f133423e5824632e3fb7874ae61 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:28:25 +0200 Subject: [PATCH 008/118] Replace Pico_Plugin with DummyPlugin DummyPlugin is a template for Pico plugins. You're a plugin developer? This template may be helpful :-) --- plugins/DummyPlugin.php | 267 ++++++++++++++++++++++++++++++++++++++++ plugins/pico_plugin.php | 95 -------------- 2 files changed, 267 insertions(+), 95 deletions(-) create mode 100644 plugins/DummyPlugin.php delete mode 100644 plugins/pico_plugin.php diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php new file mode 100644 index 0000000..46b0d9f --- /dev/null +++ b/plugins/DummyPlugin.php @@ -0,0 +1,267 @@ + &$plugins loaded plugin instances + * @return void + */ + public function onPluginsLoaded(&$plugins) + { + // your code + } + + /** + * Triggered after Pico readed its configuration + * + * @see Pico::getConfig() + * @param array &$config array of config variables + * @return void + */ + public function onConfigLoaded(&$config) + { + // your code + } + + /** + * Triggered after Pico evaluated the request URL + * + * @see Pico::getBaseUrl() + * @see Pico::getRequestUrl() + * @param string &$url request URL + * @return void + */ + public function onRequestUrl(&$url) + { + // your code + } + + /** + * Triggered after Pico discovered the content file to serve + * + * @see Pico::getRequestFile() + * @param string &$file path to the content file to serve + * @return void + */ + public function onRequestFile(&$file) + { + // your code + } + + /** + * Triggered before Pico reads the contents of the file to serve + * + * @see Pico::loadFileContent() + * @param string &$file path to the file which contents will be read + * @return void + */ + public function onContentLoading(&$file) + { + // your code + } + + /** + * Triggered after Pico read the contents of the file to serve + * + * @see Pico::getRawContent() + * @param string &$rawContent raw file contents + * @return void + */ + public function onContentLoaded(&$rawContent) + { + // your code + } + + /** + * Triggered before Pico reads the contents of the 404 file + * + * @see Pico::load404Content() + * @param string &$file path to the file which contents were requested + * @return void + */ + public function on404ContentLoading(&$file) + { + // your code + } + + /** + * Triggered after Pico read the contents of the 404 file + * + * @see Pico::getRawContent() + * @param string &$rawContent raw file contents + * @return void + */ + public function on404ContentLoaded(&$rawContent) + { + // your code + } + + /** + * Triggered when Pico reads its known meta header fields + * + * @see Pico::getMetaHeaders() + * @param array &$headers list of known meta header fields + * @return void + */ + public function onMetaHeaders(&$headers) + { + // your code + } + + /** + * Triggered before Pico parses the meta header + * + * @see Pico::parseFileMeta() + * @param string &$rawContent raw file contents + * @param array &$headers known meta header fields + * @return void + */ + public function onMetaParsing(&$rawContent, &$headers) + { + // your code + } + + /** + * Triggered after Pico parsed the meta header + * + * @see Pico::getFileMeta() + * @param array &$meta parsed meta data + * @return void + */ + public function onMetaParsed(&$meta) + { + // your code + } + + /** + * Triggered before Pico parses the pages content + * + * @see Pico::prepareFileContent() + * @param string &$rawContent raw file contents + * @return void + */ + public function onContentParsing(&$rawContent) + { + // your code + } + + /** + * Triggered after Pico prepared the raw file contents for parsing + * + * @see Pico::parseFileContent() + * @param string &$content prepared file contents for parsing + * @return void + */ + public function prepareFileContent(&$content) + { + // your code + } + + /** + * Triggered after Pico parsed the contents of the file to serve + * + * @see Pico::getFileContent() + * @param stirng &$content parsed contents + * @return void + */ + public function onContentParsed(&$content) + { + // your code + } + + /** + * Triggered when Pico reads a single page for the list of all known pages + * + * @param array &$pageData data of the loaded page + * @return void + */ + public function onSinglePageLoaded(&$pageData) + { + // your code + } + + /** + * Triggered after Pico read all known pages + * + * @see Pico::getPages() + * @see Pico::getCurrentPage() + * @see Pico::getPreviousPage() + * @see Pico::getNextPage() + * @param array &$pages data of all known pages + * @param array &$currentPage data of the page being served + * @param array &$previousPage data of the previous page + * @param array &$nextPage data of the next page + * @return void + */ + public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage) + { + // your code + } + + /** + * Triggered before Pico registers the twig template engine + * + * @return void + */ + public function onTwigRegistration() + { + // your code + } + + /** + * Triggered before Pico renders the page + * + * @see Pico::getTwig() + * @param Twig_Environment &$twig twig template engine + * @param array &$twigVariables variables passed to the template + * @param string &$templateName name of the template to render + * @return void + */ + public function onPageRendering(&$twig, &$twigVariables, &$templateName) + { + // your code + } + + /** + * Triggered after Pico rendered the page + * + * @param string &$output contents which will be sent to the user + * @return void + */ + public function onPageRendered(&$output) + { + // your code + } +} diff --git a/plugins/pico_plugin.php b/plugins/pico_plugin.php deleted file mode 100644 index 236b12e..0000000 --- a/plugins/pico_plugin.php +++ /dev/null @@ -1,95 +0,0 @@ - From fd1b94e990f4cc73c331f6f9e4f1e7ee7c304758 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:28:49 +0200 Subject: [PATCH 009/118] Update composer.json --- composer.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 40d4c06..38192f4 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,14 @@ "require": { "php": ">=5.3.2", "twig/twig": "1.18.*", - "erusev/parsedown-extra": "dev-master@dev" + "erusev/parsedown-extra": "dev-master@dev", + "symfony/yaml" : "2.3" }, "autoload": { - "files": ["lib/pico.php"] + "files": [ + "lib/Pico.php", + "lib/IPicoPlugin.php", + "lib/AbstractPicoPlugin.php" + ] } } From 70316eca8776ad2506a63cf338da05fa6f8fa943 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:30:19 +0200 Subject: [PATCH 010/118] Add PicoDeprecated, PicoParsePagesContent, PicoExcerpt These plugins are crucial for backward compatibility --- plugins/00-PicoDeprecated.php | 319 +++++++++++++++++++++++++++ plugins/01-PicoParsePagesContent.php | 40 ++++ plugins/02-PicoExcerpt.php | 79 +++++++ 3 files changed, 438 insertions(+) create mode 100644 plugins/00-PicoDeprecated.php create mode 100644 plugins/01-PicoParsePagesContent.php create mode 100644 plugins/02-PicoExcerpt.php diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php new file mode 100644 index 0000000..92fb73b --- /dev/null +++ b/plugins/00-PicoDeprecated.php @@ -0,0 +1,319 @@ +isStatusChanged()) { + $this->setEnabled(true, true, true); + } + break; + } + } + + if ($this->isEnabled()) { + $this->triggerEvent('plugins_loaded'); + } + } + + /** + * Triggers the deprecated event config_loaded($config), tries to read + * {@path "config.php"} in Picos root dir, enables the plugins + * {@link PicoParsePagesContent} and {@link PicoExcerpt} and defines the + * deprecated constants CONTENT_DIR and CONTENT_EXT + * + * @see DummyPlugin::onConfigLoaded() + */ + public function onConfigLoaded(&$config) + { + if (file_exists(ROOT_DIR . 'config.php')) { + // config.php in ROOT_DIR is deprecated; use CONFIG_DIR instead + $newConfig = require(ROOT_DIR . 'config.php'); + if (is_array($newConfig)) { + $config = $newConfig + $config; + } + } + + // enable PicoParsePagesContent and PicoExcerpt + // we can't enable them during onPluginsLoaded because we can't know + // if the user disabled us (PicoDeprecated) manually in the config + if (isset($plugins['PicoParsePagesContent'])) { + // parse all pages content if this plugin hasn't + // be explicitly enabled/disabled yet + if (!$plugins['PicoParsePagesContent']->isStatusChanged()) { + $plugins['PicoParsePagesContent']->setEnabled(true, true, true); + } + } + if (isset($plugins['PicoExcerpt'])) { + // enable excerpt plugin if it hasn't be explicitly enabled/disabled yet + if (!$plugins['PicoExcerpt']->isStatusChanged()) { + $plugins['PicoExcerpt']->setEnabled(true, true, true); + } + } + + // CONTENT_DIR constant is deprecated since v0.9, + // CONTENT_EXT constant since v1.0 + if (!defined('CONTENT_DIR')) { + define('CONTENT_DIR', $config['content_dir']); + } + if (!defined('CONTENT_EXT')) { + define('CONTENT_EXT', $config['content_ext']); + } + + $this->triggerEvent('config_loaded', array(&$config)); + } + + /** + * Triggers the deprecated event request_url($url) + * + * @see DummyPlugin::onRequestUrl() + */ + public function onRequestUrl(&$url) + { + $this->triggerEvent('request_url', array(&$url)); + } + + /** + * Sets {@link PicoDeprecated::$requestFile} to trigger the deprecated + * events after_load_content() and after_404_load_content() + * + * @see DummyPlugin::onRequestFile() + */ + public function onRequestFile(&$file) + { + $this->requestFile = &$file; + } + + /** + * Triggers the deprecated before_load_content($file) + * + * @see DummyPlugin::onContentLoading() + */ + public function onContentLoading(&$file) + { + $this->triggerEvent('before_load_content', array(&$file)); + } + + /** + * Triggers the deprecated event after_load_content($file, $rawContent) + * + * @see DummyPlugin::onContentLoaded() + */ + public function onContentLoaded(&$rawContent) + { + $this->triggerEvent('after_load_content', array(&$this->requestFile, &$rawContent)); + } + + /** + * Triggers the deprecated before_404_load_content($file) + * + * @see DummyPlugin::on404ContentLoading() + */ + public function on404ContentLoading(&$file) + { + $this->triggerEvent('before_404_load_content', array(&$file)); + } + + /** + * Triggers the deprecated event after_404_load_content($file, $rawContent) + * + * @see DummyPlugin::on404ContentLoaded() + */ + public function on404ContentLoaded(&$rawContent) + { + $this->triggerEvent('after_404_load_content', array(&$this->requestFile, &$rawContent)); + } + + /** + * Triggers the deprecated event before_read_file_meta($headers) + * + * @see DummyPlugin::onMetaHeaders() + */ + public function onMetaHeaders(&$headers) + { + $this->triggerEvent('before_read_file_meta', array(&$headers)); + } + + /** + * Triggers the deprecated event file_meta($meta) + * + * @see DummyPlugin::onMetaParsed() + */ + public function onMetaParsed(&$meta) + { + $this->triggerEvent('file_meta', array(&$meta)); + } + + /** + * Triggers the deprecated event before_parse_content($rawContent) + * + * @see DummyPlugin::onContentParsing() + */ + public function onContentParsing(&$rawContent) + { + $this->triggerEvent('before_parse_content', array(&$rawContent)); + } + + /** + * Triggers the deprecated events after_parse_content($content) and + * content_parsed($content) + * + * @see DummyPlugin::onContentParsed() + */ + public function onContentParsed(&$content) + { + $this->triggerEvent('after_parse_content', array(&$content)); + + // deprecated since v0.8 + $this->triggerEvent('content_parsed', array(&$content)); + } + + /** + * Triggers the deprecated event get_page_data($pages, $meta) + * + * @see DummyPlugin::onSinglePageLoaded() + */ + public function onSinglePageLoaded(&$pageData) + { + // remove array keys + $pages = array(); + foreach ($pageData as &$page) { + $pages[] = &$page; + } + + $this->triggerEvent('get_page_data', array(&$pages, $pageData['meta'])); + } + + /** + * Triggers the deprecated event get_pages($pages, $currentPage, $previousPage, $nextPage) + * + * @see DummyPlugin::onPagesLoaded() + */ + public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage) + { + $this->triggerEvent('get_pages', array(&$pages, &$currentPage, &$previousPage, &$nextPage)); + } + + /** + * Triggers the deprecated event before_twig_register() + * + * @see DummyPlugin::onTwigRegistration() + */ + public function onTwigRegistration() + { + $this->triggerEvent('before_twig_register'); + } + + /** + * Triggers the deprecated event before_render($twigVariables, $twig, $templateName) + * + * @see DummyPlugin::onPageRendering() + */ + public function onPageRendering(&$twig, &$twigVariables, &$templateName) + { + // template name contains file extension since Pico 1.0 + $fileExtension = ''; + if (($fileExtensionPos = strrpos($templateName, '.')) !== false) { + $fileExtension = substr($templateName, $fileExtensionPos); + $templateName = substr($templateName, 0, $fileExtensionPos); + } + + $this->triggerEvent('before_render', array(&$twigVariables, &$twig, &$templateName)); + + // add original file extension + $templateName = $templateName . $fileExtension; + } + + /** + * Triggers the deprecated event after_render($output) + * + * @see DummyPlugin::onPageRendered() + */ + public function onPageRendered(&$output) + { + $this->triggerEvent('after_render', array(&$output)); + } + + /** + * Triggers a deprecated event on all plugins + * + * @param string $eventName event to trigger + * @param array $params parameters to pass + * @return void + */ + protected function triggerEvent($eventName, array $params = array()) + { + foreach ($this->getPlugins() as $plugin) { + if (method_exists($plugin, $eventName)) { + call_user_func_array(array($plugin, $eventName), $params); + } + } + } +} diff --git a/plugins/01-PicoParsePagesContent.php b/plugins/01-PicoParsePagesContent.php new file mode 100644 index 0000000..5b2ea36 --- /dev/null +++ b/plugins/01-PicoParsePagesContent.php @@ -0,0 +1,40 @@ +prepareFileContent($pageData['raw_content']); + $pageData['content'] = $this->parseFileContent($pageData['content']); + } + } +} diff --git a/plugins/02-PicoExcerpt.php b/plugins/02-PicoExcerpt.php new file mode 100644 index 0000000..3767cc7 --- /dev/null +++ b/plugins/02-PicoExcerpt.php @@ -0,0 +1,79 @@ +. + * + * @author Daniel Rudolf + * @link http://picocms.org + * @license http://opensource.org/licenses/MIT + * @version 1.0 + */ +class PicoExcerpt extends AbstractPicoPlugin +{ + /** + * This plugin is disabled by default + * + * @see AbstractPicoPlugin::$enabled + */ + protected $enabled = false; + + /** + * This plugin depends on {@link PicoParsePagesContent} + * + * @see AbstractPicoPlugin::$dependsOn + */ + protected $dependsOn = array('PicoParsePagesContent'); + + /** + * Adds the default excerpt length of 50 words to the config + * + * @see DummyPlugin::onConfigLoaded() + */ + public function onConfigLoaded(&$config) + { + if (!isset($config['excerpt_length'])) { + $config['excerpt_length'] = 50; + } + } + + /** + * Creates a excerpt for the contents of each page + * + * @see DummyPlugin::onSinglePageLoaded() + */ + public function onSinglePageLoaded(&$pageData) + { + if (!isset($pageData['excerpt'])) { + $pageData['excerpt'] = $this->createExcerpt( + strip_tags($pageData['content']), + $this->getConfig('excerpt_length') + ); + } + } + + /** + * Helper function to create a excerpt of a string + * + * @param string $string the string to create a excerpt from + * @param int $wordLimit the maximum number of words the excerpt should be long + * @return string excerpt of $string + */ + protected function createExcerpt($string, $wordLimit) + { + $words = explode(' ', $string); + if (count($words) > $wordLimit) { + return trim(implode(' ', array_slice($words, 0, $wordLimit))) . '…'; + } + return $string; + } +} From 144939b978d7595c15c61e90cce3dd79db8683e9 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:33:27 +0200 Subject: [PATCH 011/118] Update config.php.template --- config/config.php.template | 49 ++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/config/config.php.template b/config/config.php.template index 5d186b1..c78eec5 100644 --- a/config/config.php.template +++ b/config/config.php.template @@ -1,18 +1,19 @@ false, // To enable Twig caching change this to CACHE_DIR -// 'autoescape' => false, // Autoescape Twig vars -// 'debug' => false // Enable Twig debug +// 'cache' => false, // To enable Twig caching change this to CACHE_DIR +// 'autoescape' => false, // Autoescape Twig vars +// 'debug' => false // Enable Twig debug // ); /* * CONTENT */ -// $config['date_format'] = '%D %T'; // Set the PHP date format as described here: http://php.net/manual/en/function.strftime.php -// $config['pages_order_by'] = 'alpha'; // Order pages by "alpha" or "date" -// $config['pages_order'] = 'asc'; // Order pages "asc" or "desc" -// $config['excerpt_length'] = 50; // The pages excerpt length (in words) -// $config['content_dir'] = 'content-sample/'; // Content directory +// $config['date_format'] = '%D %T'; // Set the PHP date format as described here: http://php.net/manual/en/function.strftime.php +// $config['pages_order_by'] = 'alpha'; // Order pages by "alpha" or "date" +// $config['pages_order'] = 'asc'; // Order pages "asc" or "desc" +// $config['content_dir'] = 'content-sample/'; // Content directory +// $config['content_ext'] = '.md'; // File extension of content files to serve /* * TIMEZONE */ -// date_default_timezone_set('UTC'); // Timezone may be reqired by your php install +// $config['timezone'] = 'UTC'; // Timezone may be required by your php install + +/* + * PLUGINS + */ +// $config['DummyPlugin.enabled'] = false; // Force DummyPlugin to be disabled /* * CUSTOM */ -// $config['custom_setting'] = 'Hello'; // Can be accessed by {{ config.custom_setting }} in a theme +// $config['custom_setting'] = 'Hello'; // Can be accessed by {{ config.custom_setting }} in a theme -// Keep this line +// DO NOT REMOVE THIS LINE return $config; From 39e1d788c56c4343a0e2ee50c207a2a543b186b5 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:34:14 +0200 Subject: [PATCH 012/118] Update sample content --- content-sample/404.md | 6 +- content-sample/index.md | 172 +++++++++++++++++++++++------------- content-sample/sub/index.md | 6 +- content-sample/sub/page.md | 6 +- 4 files changed, 119 insertions(+), 71 deletions(-) diff --git a/content-sample/404.md b/content-sample/404.md index ef7988e..e6ddf30 100644 --- a/content-sample/404.md +++ b/content-sample/404.md @@ -1,9 +1,9 @@ -/* +--- Title: Error 404 Robots: noindex,nofollow -*/ +--- Error 404 ========= -Woops. Looks like this page doesn't exist. \ No newline at end of file +Woops. Looks like this page doesn't exist. diff --git a/content-sample/index.md b/content-sample/index.md index 8ebddac..511b338 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -1,97 +1,139 @@ -/* +--- Title: Welcome -Description: This description will go in the meta description tag -*/ +Description: Pico is a stupidly simple, blazing fast, flat file CMS. +--- ## Welcome to Pico -Congratulations, you have successfully installed [Pico](http://picocms.org/). Pico is a stupidly simple, blazing fast, flat file CMS. +Congratulations, you have successfully installed [Pico](http://picocms.org/). +%meta.description% ### Creating Content -Pico is a flat file CMS, this means there is no administration backend and database to deal with. You simply create `.md` files in the "content-sample" -folder and that becomes a page. For example, this file is called `index.md` and is shown as the main landing page. +Pico is a flat file CMS, this means there is no administration backend and +database to deal with. You simply create `.md` files in the `content-sample` +folder and that becomes a page. For example, this file is called `index.md` +and is shown as the main landing page. -If you create a folder within the content-sample folder (e.g. `content-sample/sub`) and put an `index.md` inside it, you can access that folder at the URL -`http://yoursite.com/sub`. If you want another page within the sub folder, simply create a text file with the corresponding name (e.g. `content-sample/sub/page.md`) -and you will be able to access it from the URL `http://yoursite.com/sub/page`. Below we've shown some examples of content-sample locations and their corresponing URL's: +If you create a folder within the content folder (e.g. `content-sample/sub`) +and put an `index.md` inside it, you can access that folder at the URL +`http://yoursite.com/?sub`. If you want another page within the sub folder, +simply create a text file with the corresponding name and you will be able to +access it (e.g. `content-sample/sub/page.md` is accessible from the URL +`http://yoursite.com/?sub/page`). Below we've shown some examples of locations +and their corresponing URLs: - - - - - - - - - - - +
Physical LocationURL
content-sample/index.md/
content-sample/sub.md/sub
content-sample/sub/index.md/sub (same as above)
content-sample/sub/page.md/sub/page
content-sample/a/very/long/url.md/a/very/long/url
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Physical LocationURL
content-sample/index.md/
content-sample/sub.md?sub (not accessible, see below)
content-sample/sub/index.md?sub (same as above)
content-sample/sub/page.md?sub/page
content-sample/a/very/long/url.md?a/very/long/url (doesn't exist)
If a file cannot be found, the file `content-sample/404.md` will be shown. ### Text File Markup -Text files are marked up using [Markdown](http://daringfireball.net/projects/markdown/syntax). They can also contain regular HTML. +Text files are marked up using [Markdown][]. They can also contain regular HTML. -At the top of text files you can place a block comment and specify certain attributes of the page. For example: +At the top of text files you can place a block comment and specify certain +attributes of the page. For example: - /* - Title: Welcome - Description: This description will go in the meta description tag - Author: Joe Bloggs - Date: 2013/01/01 - Robots: noindex,nofollow - */ + --- + Title: Welcome + Description: This description will go in the meta description tag + Author: Joe Bloggs + Date: 2013/01/01 + Robots: noindex,nofollow + --- -These values will be contained in the `{{ meta }}` variable in themes (see below). +These values will be contained in the `{{ meta }}` variable in themes +(see below). There are also certain variables that you can use in your text files: -* %base_url% - The URL to your Pico site +* %site_title% - The title of your Pico site +* %base_url% - The URL to your Pico site; internal links + can be specified using %base_url%?sub/page +* %theme_url% - The URL to the currently used theme +* %meta.*% - Access any meta variable of the current page, + e.g. %meta.author% returns `Joe Bloggs` ### Themes -You can create themes for your Pico installation in the "themes" folder. Check out the default theme for an example of a theme. Pico uses -[Twig](http://twig.sensiolabs.org/documentation) for it's templating engine. You can select your theme by setting the `$config['theme']` variable -in `config/config.php` to your theme folder. +You can create themes for your Pico installation in the `themes` folder. Check +out the default theme for an example of a theme. Pico uses [Twig][] for +template rendering. You can select your theme by setting the `$config['theme']` +variable in `config/config.php` to your theme folder. -All themes must include an `index.html` file to define the HTML structure of the theme. Below are the Twig variables that are available to use in your theme: +All themes must include an `index.twig` file to define the HTML structure of +the theme. Below are the Twig variables that are available to use in your +theme. Paths (e.g. `{{ base_dir }}``) and URLs (e.g. `{{ base_url }}`) don't +have a trailing slash. -* `{{ config }}` - Conatins the values you set in `config/config.php` (e.g. `{{ config.theme }}` = "default") +* `{{ config }}` - Conatins the values you set in `config/config.php` + (e.g. `{{ config.theme }}` = "default") * `{{ base_dir }}` - The path to your Pico root directory * `{{ base_url }}` - The URL to your Pico site * `{{ theme_dir }}` - The path to the Pico active theme directory * `{{ theme_url }}` - The URL to the Pico active theme directory -* `{{ site_title }}` - Shortcut to the site title (defined in `config/config.php`) +* `{{ rewrite_url }}` - A boolean flag indicating enabled/disabled URL rewriting +* `{{ site_title }}` - Shortcut to the site title (see `config/config.php`) * `{{ meta }}` - Contains the meta values from the current page - * `{{ meta.title }}` - * `{{ meta.description }}` - * `{{ meta.author }}` - * `{{ meta.date }}` - * `{{ meta.date_formatted }}` - * `{{ meta.robots }}` -* `{{ content-sample }}` - The content-sample of the current page (after it has been processed through Markdown) -* `{{ pages }}` - A collection of all the content-sample in your site - * `{{ page.title }}` - * `{{ page.url }}` - * `{{ page.author }}` - * `{{ page.date }}` - * `{{ page.date_formatted }}` - * `{{ page.content-sample }}` - * `{{ page.excerpt }}` -* `{{ prev_page }}` - A page object of the previous page (relative to current_page) -* `{{ current_page }}` - A page object of the current_page -* `{{ next_page }}` - A page object of the next page (relative to current_page) + * `{{ meta.title }}` + * `{{ meta.description }}` + * `{{ meta.author }}` + * `{{ meta.date }}` + * `{{ meta.date_formatted }}` + * `{{ meta.robots }}` +* `{{ content }}` - The content of the current page + (after it has been processed through Markdown) +* `{{ pages }}` - A collection of all the content pages in your site + * `{{ page.id }}` + * `{{ page.url }}` + * `{{ page.title }}` + * `{{ page.description }}` + * `{{ page.author }}` + * `{{ page.time }}` + * `{{ page.date }}` + * `{{ page.date_formatted }}` + * `{{ page.raw_content }}` + * `{{ page.meta }}` +* `{{ prev_page }}` - The data of the previous page (relative to `current_page`) +* `{{ current_page }}` - The data of the current page +* `{{ next_page }}` - The data of the next page (relative to `current_page`) * `{{ is_front_page }}` - A boolean flag for the front page Pages can be used like:
<ul class="nav">
-	{% for page in pages %}
-	<li><a href="{{ page.url }}">{{ page.title }}</a></li>
-	{% endfor %}
+    {% for page in pages %}
+    <li><a href="{{ page.url }}">{{ page.title }}</a></li>
+    {% endfor %}
 </ul>
### Plugins @@ -100,10 +142,16 @@ See [http://pico.dev7studios.com/plugins](http://picocms.org/plugins) ### Config -You can override the default Pico settings (and add your own custom settings) by editing `config/config.php` in the Pico directory. -The `config/config.php.template` lists all of the settings and their defaults. To override a setting simply copy -`config/config.php.template` to `config/config.php`, uncomment the setting and set your custom value. +You can override the default Pico settings (and add your own custom settings) +by editing `config/config.php` in the Pico directory. For a brief overview of +the available settings and their defaults see `config/config.php.template`. To +override a setting copy `config/config.php.template` to `config/config.php`, +uncomment the setting and set your custom value. ### Documentation -For more help have a look at the Pico documentation at [http://picocms.org/docs](http://picocms.org/docs) +For more help have a look at the Pico documentation at +[http://picocms.org/docs](http://picocms.org/docs) + +[Twig]: http://twig.sensiolabs.org/documentation +[Markdown]: http://daringfireball.net/projects/markdown/syntax diff --git a/content-sample/sub/index.md b/content-sample/sub/index.md index 517e191..42edf3f 100644 --- a/content-sample/sub/index.md +++ b/content-sample/sub/index.md @@ -1,10 +1,10 @@ -/* +--- Title: Sub Page Index -*/ +--- ## This is a Sub Page Index -This is index.md in the "sub" folder. +This is `index.md` in the `sub` folder. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies tristique nulla et mattis. Phasellus id massa eget nisl congue blandit sit amet id ligula. Praesent et nulla eu augue tempus sagittis. Mauris faucibus nibh et nibh cursus in vestibulum sapien egestas. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc. Mauris mattis nunc sed mi sagittis et facilisis tortor volutpat. Etiam tincidunt urna mattis erat placerat placerat ac eu tellus. Ut nec velit id nisl tincidunt vehicula id a metus. Pellentesque erat neque, faucibus id ultricies vel, mattis in ante. Donec lobortis, mauris id congue scelerisque, diam nisl accumsan orci, condimentum porta est magna vel arcu. Curabitur varius ante dui. Vivamus sit amet ante ac diam ullamcorper sodales sed a odio. diff --git a/content-sample/sub/page.md b/content-sample/sub/page.md index 2e2aebd..95d67bd 100644 --- a/content-sample/sub/page.md +++ b/content-sample/sub/page.md @@ -1,10 +1,10 @@ -/* +--- Title: Sub Page -*/ +--- ## This is a Sub Page -This is page.md in the "sub" folder. +This is `page.md` in the `sub` folder. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies tristique nulla et mattis. Phasellus id massa eget nisl congue blandit sit amet id ligula. Praesent et nulla eu augue tempus sagittis. Mauris faucibus nibh et nibh cursus in vestibulum sapien egestas. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc. Mauris mattis nunc sed mi sagittis et facilisis tortor volutpat. Etiam tincidunt urna mattis erat placerat placerat ac eu tellus. Ut nec velit id nisl tincidunt vehicula id a metus. Pellentesque erat neque, faucibus id ultricies vel, mattis in ante. Donec lobortis, mauris id congue scelerisque, diam nisl accumsan orci, condimentum porta est magna vel arcu. Curabitur varius ante dui. Vivamus sit amet ante ac diam ullamcorper sodales sed a odio. From 5438fdb3680581ee839327a64677a22f6149cab9 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:35:53 +0200 Subject: [PATCH 013/118] Rename index.html of default theme to index.twig; Update template --- themes/default/{index.html => index.twig} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename themes/default/{index.html => index.twig} (100%) diff --git a/themes/default/index.html b/themes/default/index.twig similarity index 100% rename from themes/default/index.html rename to themes/default/index.twig From c44afd396dae1c11db8ba1f21b78a2bb159f2692 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 28 Aug 2015 18:37:36 +0200 Subject: [PATCH 014/118] Update .htaccess --- .htaccess | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.htaccess b/.htaccess index 553cd57..b12e916 100644 --- a/.htaccess +++ b/.htaccess @@ -4,7 +4,11 @@ #RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . index.php [L] + RewriteRule ^(.*)$ index.php?$1 [L,QSA] + + + SetEnv PICO_URL_REWRITING 1 + # Prevent file browsing From df10d60846d640687fdd77a7bc6dd8d14e728fb9 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 30 Aug 2015 21:29:20 +0200 Subject: [PATCH 015/118] Fixing paths in global.php That shouldn't be there... Thanks @theshka for spotting --- global.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/global.php b/global.php index 43a8146..02bb91c 100644 --- a/global.php +++ b/global.php @@ -1,11 +1,9 @@ Date: Sun, 30 Aug 2015 21:29:52 +0200 Subject: [PATCH 016/118] Add missing update of index.twig --- themes/default/index.twig | 97 ++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/themes/default/index.twig b/themes/default/index.twig index 9b5ed0a..5d9d371 100644 --- a/themes/default/index.twig +++ b/themes/default/index.twig @@ -1,48 +1,49 @@ - - - - - - {% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }} -{% if meta.description %} - -{% endif %}{% if meta.robots %} - -{% endif %} - - - - - - - - - - -
-
- {{ content }} -
-
- - - - - + + + + + + {% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }} + {% if meta.description %} + + {% endif %}{% if meta.robots %} + + {% endif %} + + + + + + + + + + +
+
+ {{ content }} +
+
+ + + + + From 533822320e8ca9efe9505f09a58cbd9c9b3d89d1 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 30 Aug 2015 21:31:47 +0200 Subject: [PATCH 017/118] Workaround for webservers omitting QUERY_STRING Thanks @theshka for spotting --- lib/Pico.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Pico.php b/lib/Pico.php index 7e4a7f5..b5bc088 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -374,7 +374,7 @@ class Pico // // Note: you MUST NOT call the index page with /pico/?someBooleanParameter; // use /pico/?someBooleanParameter= or /pico/?index&someBooleanParameter instead - $pathComponent = $_SERVER['QUERY_STRING']; + $pathComponent = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; if (($pathComponentLength = strpos($pathComponent, '&')) !== false) { $pathComponent = substr($pathComponent, 0, $pathComponentLength); } From a83b01ef4f3da6317ccc8df8cef4c398880a1a05 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 6 Sep 2015 14:35:23 +0200 Subject: [PATCH 018/118] Access plugins by class name, not file name Class name and file name can differ regarding case sensitivity --- lib/Pico.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Pico.php b/lib/Pico.php index b5bc088..aff3dee 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -243,7 +243,11 @@ class Pico $className = preg_replace('/^[0-9]+-/', '', basename($pluginFile, '.php')); if (class_exists($className)) { - $this->plugins[$className] = new $className($this); + // class name and file name can differ regarding case sensitivity + $plugin = new $className($this); + $className = get_class($plugin); + + $this->plugins[$className] = $plugin; } else { // TODO: breaks backward compatibility //throw new RuntimeException("Unable to load plugin '".$className."'"); From 71e7da28cc98eb5f668c41a9ce2b7b19be962156 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 13 Sep 2015 20:46:09 +0200 Subject: [PATCH 019/118] Various fixes Thanks @PontusHorn for spotting! --- lib/AbstractPicoPlugin.php | 6 +++--- lib/Pico.php | 17 +++++++++-------- plugins/DummyPlugin.php | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index ef47cc4..790a656 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -209,17 +209,17 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } else { throw new RuntimeException( "Unable to disable plugin '" . get_called_class() . "': " - + "Required by manually enabled plugin '" . $pluginName . "'" + . "Required by manually enabled plugin '" . $pluginName . "'" ); } } } } else { $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' '; - $dependantsList .= "'" + implode("', '", array_keys($dependants)) + "'"; + $dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'"; throw new RuntimeException( "Unable to disable plugin '" . get_called_class() . "': " - + "Required by " . $dependantsList + . "Required by " . $dependantsList ); } } diff --git a/lib/Pico.php b/lib/Pico.php index aff3dee..9ae862f 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -228,7 +228,7 @@ class Pico * Loads plugins from PLUGINS_DIR in alphabetical order * * Plugin files may be prefixed by a number (e.g. 00-PicoDeprecated.php) - * to indicate their processsing order. You MUST NOT use prefixes between + * to indicate their processing order. You MUST NOT use prefixes between * 00 and 19 (reserved for built-in plugins). * * @return void @@ -497,9 +497,9 @@ class Pico * ignored and won't be returned. * * @see - * @param string $content the raw file contents - * @param array $headers a array containing the known headers - * @return array parsed meta data + * @param string $rawContent the raw file contents + * @param array $headers a array containing the known headers + * @return array parsed meta data */ public function parseFileMeta($rawContent, array $headers) { @@ -887,9 +887,10 @@ class Pico * Recursively walks through a directory and returns all containing files * matching the specified file extension in alphabetical order * - * @param string $directory start directory - * @param string $ext return files with this file extension only (optional) - * @return array list of found files + * @param string $directory start directory + * @param string $fileExtension return files with the given file extension + * only (optional) + * @return array list of found files */ protected function getFiles($directory, $fileExtension = '') { @@ -910,7 +911,7 @@ class Pico if (is_dir($directory . '/' . $file)) { // get files recursively $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension)); - } elseif (empty($fileExtension) || (substr($file, -strlen($fileExtension)) === $fileExtension)) { + } elseif (empty($fileExtension) || (substr($file, -$fileExtensionLength) === $fileExtension)) { $result[] = $directory . '/' . $file; } } diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 46b0d9f..70133c7 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -193,7 +193,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico parsed the contents of the file to serve * * @see Pico::getFileContent() - * @param stirng &$content parsed contents + * @param string &$content parsed contents * @return void */ public function onContentParsed(&$content) From 5731ede2977ee29a4998f96ba78d872dccd82841 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 14 Sep 2015 23:01:08 +0200 Subject: [PATCH 020/118] Allow omitting config/config.php; Fix write context on return value Thanks @Lomanic for reporting --- lib/Pico.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 9ae862f..a134343 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -307,7 +307,7 @@ class Pico 'timezone' => '' ); - $config = require(CONFIG_DIR . 'config.php'); + $config = file_exists(CONFIG_DIR . 'config.php') ? require(CONFIG_DIR . 'config.php') : null; $this->config = is_array($config) ? $config + $defaultConfig : $defaultConfig; if (empty($this->config['base_url'])) { @@ -835,8 +835,9 @@ class Pico */ public function getBaseUrl() { - if (!empty($this->getConfig('base_url'))) { - return $this->getConfig('base_url'); + $baseUrl = $this->getConfig('base_url'); + if (!empty($baseUrl)) { + return $baseUrl; } if ( From 4821454ad5f243c5a182ebb75d8c93122bee58ab Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 14 Sep 2015 23:08:02 +0200 Subject: [PATCH 021/118] Declare in config/config.php.template to prevent a E_NOTICE --- config/config.php.template | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.php.template b/config/config.php.template index c78eec5..000a60d 100644 --- a/config/config.php.template +++ b/config/config.php.template @@ -16,6 +16,8 @@ * @version 0.9 */ +$config = array(); + /* * BASIC */ From 70f187fb4586cde29f1ad60cada77197828fc0a3 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 15 Sep 2015 13:15:45 +0200 Subject: [PATCH 022/118] Rename IPicoPlugin to PicoPluginInterface --- composer.json | 2 +- lib/AbstractPicoPlugin.php | 42 +++++++-------- lib/IPicoPlugin.php | 97 ----------------------------------- lib/Pico.php | 10 ++-- plugins/00-PicoDeprecated.php | 8 +-- 5 files changed, 31 insertions(+), 128 deletions(-) delete mode 100644 lib/IPicoPlugin.php diff --git a/composer.json b/composer.json index 38192f4..2ec4593 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "autoload": { "files": [ "lib/Pico.php", - "lib/IPicoPlugin.php", + "lib/PicoPluginInterface.php", "lib/AbstractPicoPlugin.php" ] } diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 790a656..07905d1 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -3,21 +3,21 @@ /** * Abstract class to extend from when implementing a Pico plugin * - * @see IPicoPlugin + * @see PicoPluginInterface * * @author Daniel Rudolf * @link http://picocms.org * @license http://opensource.org/licenses/MIT * @version 1.0 */ -abstract class AbstractPicoPlugin implements IPicoPlugin +abstract class AbstractPicoPlugin implements PicoPluginInterface { /** * Current instance of Pico * * @var Pico - * @see IPicoPlugin::__construct() - * @see IPicoPlugin::getPico() + * @see PicoPluginInterface::__construct() + * @see PicoPluginInterface::getPico() */ private $pico; @@ -25,8 +25,8 @@ abstract class AbstractPicoPlugin implements IPicoPlugin * Boolean indicating if this plugin is enabled (true) or disabled (false) * * @var boolean - * @see IPicoPlugin::isEnabled() - * @see IPicoPlugin::setEnabled() + * @see PicoPluginInterface::isEnabled() + * @see PicoPluginInterface::setEnabled() */ protected $enabled = true; @@ -34,7 +34,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin * Boolean indicating if this plugin was ever enabled/disabled manually * * @var boolean - * @see IPicoPlugin::isStatusChanged() + * @see PicoPluginInterface::isStatusChanged() */ protected $statusChanged = false; @@ -42,7 +42,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin * List of plugins this plugin depends on * * @var array - * @see IPicoPlugin::getDependencies() + * @see PicoPluginInterface::getDependencies() * @see AbstractPicoPlugin::checkDependencies() */ protected $dependsOn = array(); @@ -51,13 +51,13 @@ abstract class AbstractPicoPlugin implements IPicoPlugin * List of plugin which depend on this plugin * * @var array - * @see IPicoPlugin::getDependants() + * @see PicoPluginInterface::getDependants() * @see AbstractPicoPlugin::checkDependants() */ private $dependants; /** - * @see IPicoPlugin::__construct() + * @see PicoPluginInterface::__construct() */ public function __construct(Pico $pico) { @@ -65,7 +65,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::handleEvent() + * @see PicoPluginInterface::handleEvent() */ public function handleEvent($eventName, array $params) { @@ -85,7 +85,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::setEnabled() + * @see PicoPluginInterface::setEnabled() */ public function setEnabled($enabled, $recursive = true, $auto = false) { @@ -100,7 +100,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::isEnabled() + * @see PicoPluginInterface::isEnabled() */ public function isEnabled() { @@ -108,7 +108,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::isStatusChanged() + * @see PicoPluginInterface::isStatusChanged() */ public function isStatusChanged() { @@ -116,7 +116,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::getPico() + * @see PicoPluginInterface::getPico() */ public function getPico() { @@ -161,8 +161,8 @@ abstract class AbstractPicoPlugin implements IPicoPlugin ); } - // plugins which don't implement IPicoPlugin are always enabled - if (is_a($plugin, 'IPicoPlugin') && !$plugin->isEnabled()) { + // plugins which don't implement PicoPluginInterface are always enabled + if (is_a($plugin, 'PicoPluginInterface') && !$plugin->isEnabled()) { if ($recursive) { if (!$plugin->isStatusChanged()) { $plugin->setEnabled(true, true, true); @@ -183,7 +183,7 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::getDependencies() + * @see PicoPluginInterface::getDependencies() */ public function getDependencies() { @@ -226,15 +226,15 @@ abstract class AbstractPicoPlugin implements IPicoPlugin } /** - * @see IPicoPlugin::getDependants() + * @see PicoPluginInterface::getDependants() */ public function getDependants() { if ($this->dependants === null) { $this->dependants = array(); foreach ($this->getPlugins() as $pluginName => $plugin) { - // only plugins which implement IPicoPlugin support dependencies - if (is_a($plugin, 'IPicoPlugin')) { + // only plugins which implement PicoPluginInterface support dependencies + if (is_a($plugin, 'PicoPluginInterface')) { $dependencies = $plugin->getDependencies(); if (in_array(get_called_class(), $dependencies)) { $this->dependants[$pluginName] = $plugin; diff --git a/lib/IPicoPlugin.php b/lib/IPicoPlugin.php deleted file mode 100644 index 2a9e284..0000000 --- a/lib/IPicoPlugin.php +++ /dev/null @@ -1,97 +0,0 @@ - required plugins - */ - public function getDependencies(); - - /** - * Returns a list of plugins which depend on this plugin - * - * @return array dependant plugins - */ - public function getDependants(); - - /** - * Returns the plugins instance of Pico - * - * @return Pico the plugins instance of Pico - */ - public function getPico(); -} diff --git a/lib/Pico.php b/lib/Pico.php index a134343..35d66cd 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -258,8 +258,8 @@ class Pico /** * Returns the instance of a named plugin * - * Plugins SHOULD implement {@link IPicoPlugin}, but you MUST NOT rely on - * it. For more information see {@link IPicoPlugin}. + * Plugins SHOULD implement {@link PicoPluginInterface}, but you MUST NOT + * rely on it. For more information see {@link PicoPluginInterface}. * * @see Pico::loadPlugins() * @param string $pluginName name of the plugin @@ -922,7 +922,7 @@ class Pico } /** - * Triggers events on plugins which implement {@link IPicoPlugin} + * Triggers events on plugins which implement {@link PicoPluginInterface} * * Deprecated events (as used by plugins not implementing * {@link IPocPlugin}) are triggered by {@link PicoDeprecated}. @@ -934,10 +934,10 @@ class Pico protected function triggerEvent($eventName, array $params = array()) { foreach ($this->plugins as $plugin) { - // only trigger events for plugins that implement IPicoPlugin + // only trigger events for plugins that implement PicoPluginInterface // deprecated events (plugins for Pico 0.9 and older) will be // triggered by the `PicoPluginDeprecated` plugin - if (is_a($plugin, 'IPicoPlugin')) { + if (is_a($plugin, 'PicoPluginInterface')) { $plugin->handleEvent($eventName, $params); } } diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 92fb73b..98a8859 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -5,8 +5,8 @@ * * This plugin exists for backward compatibility and is disabled by default. * It gets automatically enabled when a plugin which doesn't implement - * {@link IPicoPlugin} is loaded. This plugin mainly triggers deprecated - * events, but also automatically enables {@link PicoParsePagesContent} and + * {@link PicoPluginInterface} is loaded. This plugin triggers deprecated + * events and automatically enables {@link PicoParsePagesContent} and * {@link PicoExcerpt}. These plugins heavily impact Picos performance! You * can disable this plugin by calling {@link PicoDeprecated::setEnabled()}. * @@ -68,8 +68,8 @@ class PicoDeprecated extends AbstractPicoPlugin public function onPluginsLoaded(&$plugins) { foreach ($plugins as $plugin) { - if (!is_a($plugin, 'IPicoPlugin')) { - // the plugin doesn't implement IPicoPlugin; it uses deprecated events + if (!is_a($plugin, 'PicoPluginInterface')) { + // the plugin doesn't implement PicoPluginInterface; it uses deprecated events // enable PicoDeprecated if it hasn't be explicitly enabled/disabled yet if (!$this->isStatusChanged()) { $this->setEnabled(true, true, true); From f76a0b75bede55388c0dafc0520b8bebf4902cdb Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 15 Sep 2015 13:17:00 +0200 Subject: [PATCH 023/118] Use v0.7 releases of erusev/parsedown-extra --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2ec4593..faf8901 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "require": { "php": ">=5.3.2", "twig/twig": "1.18.*", - "erusev/parsedown-extra": "dev-master@dev", + "erusev/parsedown-extra": "0.7.*", "symfony/yaml" : "2.3" }, "autoload": { From 5533b0753125754b2725193ce07f626ca44e327c Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 15 Sep 2015 13:20:52 +0200 Subject: [PATCH 024/118] Re-add lost PicoPluginInterface --- lib/PicoPluginInterface.php | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lib/PicoPluginInterface.php diff --git a/lib/PicoPluginInterface.php b/lib/PicoPluginInterface.php new file mode 100644 index 0000000..a4dc5e1 --- /dev/null +++ b/lib/PicoPluginInterface.php @@ -0,0 +1,97 @@ + required plugins + */ + public function getDependencies(); + + /** + * Returns a list of plugins which depend on this plugin + * + * @return array dependant plugins + */ + public function getDependants(); + + /** + * Returns the plugins instance of Pico + * + * @return Pico the plugins instance of Pico + */ + public function getPico(); +} From a7fd853f8f722403c0314ab06b64c0b68df8f9aa Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 17 Sep 2015 23:44:05 +0200 Subject: [PATCH 025/118] Add striptags filter to description meta header Thanks @PontusHorn --- themes/default/index.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/index.twig b/themes/default/index.twig index 5d9d371..25d8b19 100644 --- a/themes/default/index.twig +++ b/themes/default/index.twig @@ -5,7 +5,7 @@ {% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }} {% if meta.description %} - + {% endif %}{% if meta.robots %} {% endif %} From a5755b0d3956a72ec4a21d859181647db9574a32 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 28 Sep 2015 17:13:26 +0200 Subject: [PATCH 026/118] Fix deprecated get_page_data and get_pages events --- plugins/00-PicoDeprecated.php | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 98a8859..7058763 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -242,13 +242,7 @@ class PicoDeprecated extends AbstractPicoPlugin */ public function onSinglePageLoaded(&$pageData) { - // remove array keys - $pages = array(); - foreach ($pageData as &$page) { - $pages[] = &$page; - } - - $this->triggerEvent('get_page_data', array(&$pages, $pageData['meta'])); + $this->triggerEvent('get_page_data', array(&$pageData, $pageData['meta'])); } /** @@ -258,7 +252,30 @@ class PicoDeprecated extends AbstractPicoPlugin */ public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage) { - $this->triggerEvent('get_pages', array(&$pages, &$currentPage, &$previousPage, &$nextPage)); + // remove keys of pages array + $plainPages = array(); + foreach ($pages as &$pageData) { + $plainPages[] = &$pageData; + } + unset($pageData); + + $this->triggerEvent('get_pages', array(&$plainPages, &$currentPage, &$previousPage, &$nextPage)); + + // re-index pages array + $pages = array(); + foreach ($plainPages as &$pageData) { + if (!isset($pageData['id'])) { + $urlPrefixLength = strlen($this->getBaseUrl()) + intval(!$this->isUrlRewritingEnabled()); + $pageData['id'] = substr($pageData['url'], $urlPrefixLength); + } + + // prevent duplicates + for ($i = 1, $origId = $pageData['id']; isset($pages[$pageData['id']]); $i++) { + $pageData['id'] = $origId . '~dup' . $i; + } + + $pages[$pageData['id']] = &$pageData; + } } /** From 13a50c7f2f40b23a78405d553c5c4de814f4869b Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 28 Sep 2015 17:23:47 +0200 Subject: [PATCH 027/118] Don't change $pageData['id'], add ~dup1 to the array key only --- plugins/00-PicoDeprecated.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 7058763..1a7b058 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -270,11 +270,12 @@ class PicoDeprecated extends AbstractPicoPlugin } // prevent duplicates - for ($i = 1, $origId = $pageData['id']; isset($pages[$pageData['id']]); $i++) { - $pageData['id'] = $origId . '~dup' . $i; + $id = $pageData['id']; + for ($i = 1; isset($pages[$id]); $i++) { + $id = $pageData['id'] . '~dup' . $i; } - $pages[$pageData['id']] = &$pageData; + $pages[$id] = &$pageData; } } From 8d888638057f830a7b1f378a802e47457ed452e7 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 28 Sep 2015 17:42:47 +0200 Subject: [PATCH 028/118] Fix whitespace --- content-sample/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content-sample/index.md b/content-sample/index.md index 511b338..8ea4d1a 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -113,7 +113,7 @@ have a trailing slash. * `{{ content }}` - The content of the current page (after it has been processed through Markdown) * `{{ pages }}` - A collection of all the content pages in your site - * `{{ page.id }}` + * `{{ page.id }}` * `{{ page.url }}` * `{{ page.title }}` * `{{ page.description }}` From 5e77d862bf420abbb8df9524f38ce2e59adde2a0 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 29 Sep 2015 00:42:04 +0200 Subject: [PATCH 029/118] Remove twig cache dir --- config/config.php.template | 2 +- global.php | 1 - lib/cache/.gitignore | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 lib/cache/.gitignore diff --git a/config/config.php.template b/config/config.php.template index 000a60d..cea5967 100644 --- a/config/config.php.template +++ b/config/config.php.template @@ -30,7 +30,7 @@ $config = array(); */ // $config['theme'] = 'default'; // Set the theme (defaults to "default") // $config['twig_config'] = array( // Twig settings -// 'cache' => false, // To enable Twig caching change this to CACHE_DIR +// 'cache' => false, // To enable Twig caching change this to a path to a writable directory // 'autoescape' => false, // Autoescape Twig vars // 'debug' => false // Enable Twig debug // ); diff --git a/global.php b/global.php index 02bb91c..9145623 100644 --- a/global.php +++ b/global.php @@ -5,7 +5,6 @@ define('VENDOR_DIR', ROOT_DIR . 'vendor/'); define('PLUGINS_DIR', ROOT_DIR . 'plugins/'); define('THEMES_DIR', ROOT_DIR . 'themes/'); define('CONFIG_DIR', ROOT_DIR . 'config/'); -define('CACHE_DIR', LIB_DIR . 'cache/'); require_once(VENDOR_DIR . 'autoload.php'); diff --git a/lib/cache/.gitignore b/lib/cache/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/lib/cache/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore From fc7632b0acb8e4410652b41a8f726f0b3b28e423 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 15:05:50 +0200 Subject: [PATCH 030/118] Overhaul init of Pico This may break BC if you're using one of the now deprecated constants (e.g. ROOT_DIR) --- global.php | 10 --- index.php | 10 ++- lib/Pico.php | 111 ++++++++++++++++++++++++++++++---- plugins/00-PicoDeprecated.php | 24 +++++++- 4 files changed, 127 insertions(+), 28 deletions(-) delete mode 100644 global.php diff --git a/global.php b/global.php deleted file mode 100644 index 9145623..0000000 --- a/global.php +++ /dev/null @@ -1,10 +0,0 @@ -run(); diff --git a/lib/Pico.php b/lib/Pico.php index 35d66cd..544fb4e 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -26,6 +26,34 @@ */ class Pico { + /** + * Root directory of this Pico instance + * + * @var string + */ + protected $rootDir; + + /** + * Config directory of this Pico instance + * + * @var string + */ + protected $configDir; + + /** + * Plugins directory of this Pico instance + * + * @var string + */ + protected $pluginsDir; + + /** + * Themes directory of this Pico instance + * + * @var string + */ + protected $themesDir; + /** * List of loaded plugins * @@ -133,10 +161,66 @@ class Pico /** * Constructs a new Pico instance * - * The constructor carries out all the processing in Pico. - * Does URL routing, Markdown processing and Twig processing. + * To carry out all the processing in Pico, call the run() method. */ - public function __construct() + public function __construct($rootDir, $configDir, $pluginsDir, $themesDir) + { + $this->rootDir = rtrim($rootDir, '/') . '/'; + $this->configDir = rtrim($configDir, '/') . '/'; + $this->pluginsDir = rtrim($pluginsDir, '/') . '/'; + $this->themesDir = rtrim($themesDir, '/') . '/'; + } + + /** + * Returns the root directory of this Pico instance + * + * @return string root directory path + */ + public function getRootDir() + { + return $this->rootDir; + } + + /** + * Returns the config directory of this Pico instance + * + * @return string config directory path + */ + public function getConfigDir() + { + return $this->configDir; + } + + /** + * Returns the plugins directory of this Pico instance + * + * @return string plugins directory path + */ + public function getPluginsDir() + { + return $this->pluginsDir; + } + + /** + * Returns the themes directory of this Pico instance + * + * @return string themes directory path + */ + public function getThemesDir() + { + return $this->themesDir; + } + + /** + * Runs this Pico instance + * + * Loads plugins, evaluates the config file, does URL routing, parses + * meta headers, processes Markdown, does Twig processing and returns + * the rendered contents. + * + * @return string rendered Pico contents + */ + public function run() { // load plugins $this->loadPlugins(); @@ -210,7 +294,7 @@ class Pico } else { $templateName = 'index'; } - if (file_exists(THEMES_DIR . $this->getConfig('theme') . '/' . $templateName . '.twig')) { + if (file_exists($this->getThemesDir() . $this->getConfig('theme') . '/' . $templateName . '.twig')) { $templateName .= '.twig'; } else { $templateName .= '.html'; @@ -221,7 +305,7 @@ class Pico $output = $this->twig->render($templateName, $this->twigVariables); $this->triggerEvent('onPageRendered', array(&$output)); - echo $output; + return $output; } /** @@ -237,7 +321,7 @@ class Pico protected function loadPlugins() { $this->plugins = array(); - $pluginFiles = $this->getFiles(PLUGINS_DIR, '.php'); + $pluginFiles = $this->getFiles($this->getPluginsDir(), '.php'); foreach ($pluginFiles as $pluginFile) { require_once($pluginFile); @@ -302,12 +386,13 @@ class Pico 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), 'pages_order_by' => 'alpha', 'pages_order' => 'asc', - 'content_dir' => ROOT_DIR . 'content-sample/', + 'content_dir' => $this->getRootDir() . 'content-sample/', 'content_ext' => '.md', 'timezone' => '' ); - $config = file_exists(CONFIG_DIR . 'config.php') ? require(CONFIG_DIR . 'config.php') : null; + $configFile = $this->getConfigDir() . 'config.php'; + $config = file_exists($configFile) ? require($configFile) : null; $this->config = is_array($config) ? $config + $defaultConfig : $defaultConfig; if (empty($this->config['base_url'])) { @@ -579,7 +664,7 @@ class Pico $content = str_replace('%base_url%', rtrim($this->getBaseUrl(), '/'), $content); // replace %theme_url% - $themeUrl = $this->getBaseUrl() . basename(THEMES_DIR) . '/' . $this->getConfig('theme'); + $themeUrl = $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'); $content = str_replace('%theme_url%', $themeUrl, $content); // replace %meta.*% @@ -783,7 +868,7 @@ class Pico */ protected function registerTwig() { - $twigLoader = new Twig_Loader_Filesystem(THEMES_DIR . $this->getConfig('theme')); + $twigLoader = new Twig_Loader_Filesystem($this->getThemesDir() . $this->getConfig('theme')); $this->twig = new Twig_Environment($twigLoader, $this->getConfig('twig_config')); $this->twig->addExtension(new Twig_Extension_Debug()); $this->twig->addFilter(new Twig_SimpleFilter('link', array($this, 'getPageUrl'))); @@ -812,10 +897,10 @@ class Pico $frontPage = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); return array( 'config' => $this->getConfig(), - 'base_dir' => rtrim(ROOT_DIR, '/'), + 'base_dir' => rtrim($this->getRootDir(), '/'), 'base_url' => rtrim($this->getBaseUrl(), '/'), - 'theme_dir' => THEMES_DIR . $this->getConfig('theme'), - 'theme_url' => $this->getBaseUrl() . basename(THEMES_DIR) . '/' . $this->getConfig('theme'), + 'theme_dir' => $this->getThemesDir() . $this->getConfig('theme'), + 'theme_url' => $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'), 'rewrite_url' => $this->isUrlRewritingEnabled(), 'site_title' => $this->getConfig('site_title'), 'meta' => $this->meta, diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 1a7b058..39f17eb 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -93,9 +93,9 @@ class PicoDeprecated extends AbstractPicoPlugin */ public function onConfigLoaded(&$config) { - if (file_exists(ROOT_DIR . 'config.php')) { + if (file_exists($this->getRootDir() . 'config.php')) { // config.php in ROOT_DIR is deprecated; use CONFIG_DIR instead - $newConfig = require(ROOT_DIR . 'config.php'); + $newConfig = require($this->getRootDir() . 'config.php'); if (is_array($newConfig)) { $config = $newConfig + $config; } @@ -119,7 +119,25 @@ class PicoDeprecated extends AbstractPicoPlugin } // CONTENT_DIR constant is deprecated since v0.9, - // CONTENT_EXT constant since v1.0 + // ROOT_DIR, LIB_DIR, PLUGINS_DIR, THEMES_DIR and CONTENT_EXT constants since v1.0, + // CONFIG_DIR constant existed just for a short time between v0.9 and v1.0, + // CACHE_DIR constant was dropped with v1.0 without a replacement + if (!defined('ROOT_DIR')) { + define('ROOT_DIR', $this->getRootDir()); + } + if (!defined('CONFIG_DIR')) { + define('CONFIG_DIR', $this->getConfigDir()); + } + if (!defined('LIB_DIR')) { + $picoReflector = new ReflectionClass('Pico'); + define('LIB_DIR', dirname($picoReflector->getFileName() . '/')); + } + if (!defined('PLUGINS_DIR')) { + define('PLUGINS_DIR', $this->getPluginsDir()); + } + if (!defined('THEMES_DIR')) { + define('THEMES_DIR', $this->getThemesDir()); + } if (!defined('CONTENT_DIR')) { define('CONTENT_DIR', $config['content_dir']); } From cdef7a6324a7309fa03994237b6649545b09047c Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 15:14:45 +0200 Subject: [PATCH 031/118] Explicitly treat relative paths to be relative to Picos root dir This tempers the BC break, we can now recommend to simply remove the ROOT_DIR part --- index.php | 6 +++--- lib/Pico.php | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/index.php b/index.php index c459a00..02f9444 100644 --- a/index.php +++ b/index.php @@ -2,8 +2,8 @@ require_once(__DIR__ . '/vendor/autoload.php'); $pico = new Pico( __DIR__, - __DIR__ . '/config/', - __DIR__ . '/plugins/', - __DIR__ . '/themes/' + 'config/', + 'plugins/', + 'themes/' ); echo $pico->run(); diff --git a/lib/Pico.php b/lib/Pico.php index 544fb4e..e9aae70 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -166,9 +166,9 @@ class Pico public function __construct($rootDir, $configDir, $pluginsDir, $themesDir) { $this->rootDir = rtrim($rootDir, '/') . '/'; - $this->configDir = rtrim($configDir, '/') . '/'; - $this->pluginsDir = rtrim($pluginsDir, '/') . '/'; - $this->themesDir = rtrim($themesDir, '/') . '/'; + $this->configDir = $this->getAbsolutePath($configDir); + $this->pluginsDir = $this->getAbsolutePath($pluginsDir); + $this->themesDir = $this->getAbsolutePath($themesDir); } /** @@ -399,7 +399,7 @@ class Pico $this->config['base_url'] = $this->getBaseUrl(); } if (!empty($this->config['content_dir'])) { - $this->config['content_dir'] = rtrim($this->config['content_dir'], '/') . '/'; + $this->config['content_dir'] = $this->getAbsolutePath($this->config['content_dir']); } if (!empty($this->config['timezone'])) { date_default_timezone_set($this->config['timezone']); @@ -1006,6 +1006,22 @@ class Pico return $result; } + /** + * Makes a relative path absolute to Picos root dir + * + * This method also guarantees a trailing slash. + * + * @param string $path relative or absolute path + * @return string absolute path + */ + protected function getAbsolutePath($path) + { + if (substr($path, 0, 1) !== '/') { + $path = $this->getRootDir() . $path; + } + return rtrim($path, '/') . '/'; + } + /** * Triggers events on plugins which implement {@link PicoPluginInterface} * From 95db5ba1a14601aa0169d26df15844be9b519311 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 15:59:47 +0200 Subject: [PATCH 032/118] Drop inaccessible pages e.g. drop sub.md if sub/index.md exists --- lib/Pico.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Pico.php b/lib/Pico.php index e9aae70..4420e9e 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -726,6 +726,13 @@ class Pico $meta = &$this->meta; } + // drop inaccessible pages (e.g. drop "sub.md" if "sub/index.md" exists) + if (substr($id, -6) === '/index') { + unset($pages[dirname($id)]); + } elseif (isset($pages[$id . '/index'])) { + continue; + } + // build page data // title, description, author and date are assumed to be pretty basic data // everything else is accessible through $page['meta'] From 45815e2c6d2d1611b1615d74283541a20afb58f5 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 17:22:14 +0200 Subject: [PATCH 033/118] Don't read file contents of inaccessible pages --- lib/Pico.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 4420e9e..bd2f72d 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -717,6 +717,13 @@ class Pico } $id = substr($file, strlen($this->getConfig('content_dir')), -strlen($this->getConfig('content_ext'))); + + // drop inaccessible pages (e.g. drop "sub.md" if "sub/index.md" exists) + $conflictFile = $this->getConfig('content_dir') . $id . '/index' . $this->getConfig('content_ext'); + if (in_array($conflictFile, $files, true)) { + continue; + } + $url = $this->getPageUrl($id); if ($file != $this->requestFile) { $rawContent = file_get_contents($file); @@ -726,13 +733,6 @@ class Pico $meta = &$this->meta; } - // drop inaccessible pages (e.g. drop "sub.md" if "sub/index.md" exists) - if (substr($id, -6) === '/index') { - unset($pages[dirname($id)]); - } elseif (isset($pages[$id . '/index'])) { - continue; - } - // build page data // title, description, author and date are assumed to be pretty basic data // everything else is accessible through $page['meta'] From fd64e4fa5ec0b5cf62a529a965bad04db3cd8b6e Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 21:54:30 +0200 Subject: [PATCH 034/118] Add a exception to alpha sorting: List index files first This guarantees that e.g. sub/index.md is listed before sub/foo.md --- lib/Pico.php | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index bd2f72d..ee65e7f 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -658,7 +658,7 @@ class Pico // we'll replace the links accordingly, depending on enabled rewriting $content = str_replace('%base_url%?', $this->getBaseUrl(), $content); } else { - // actually not necessary, but makes the URLs look a little nicer + // actually not necessary, but makes the URL look a little nicer $content = str_replace('%base_url%?', $this->getBaseUrl() . '?', $content); } $content = str_replace('%base_url%', rtrim($this->getBaseUrl(), '/'), $content); @@ -707,8 +707,8 @@ class Pico */ protected function readPages() { - $pages = array(); - $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext')); + $this->pages = array(); + $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext'), SCANDIR_SORT_NONE); foreach ($files as $i => $file) { // skip 404 page if (basename($file) == '404' . $this->getConfig('content_ext')) { @@ -758,17 +758,22 @@ class Pico // trigger event $this->triggerEvent('onSinglePageLoaded', array(&$page)); - $pages[$id] = $page; + $this->pages[$id] = $page; } - // sort pages by date - // Pico::getFiles() already sorts alphabetically - $this->pages = $pages; - if ($this->getConfig('pages_order_by') == 'date') { - $pageIds = array_keys($this->pages); - $order = $this->getConfig('pages_order'); + // sort pages + $order = $this->getConfig('pages_order'); + $alphaSortClosure = function ($a, $b) use ($order) { + $aSortKey = (basename($a['id']) === 'index') ? dirname($a['id']) : $a['id']; + $bSortKey = (basename($b['id']) === 'index') ? dirname($b['id']) : $b['id']; - uasort($this->pages, function ($a, $b) use ($pageIds, $order) { + $cmp = strcmp($aSortKey, $bSortKey); + return $cmp * (($order == 'desc') ? -1 : 1); + }; + + if ($this->getConfig('pages_order_by') == 'date') { + // sort by date + uasort($this->pages, function ($a, $b) use ($alphaSortClosure, $order) { if (empty($a['time']) || empty($b['time'])) { $cmp = (empty($a['time']) - empty($b['time'])); } else { @@ -776,14 +781,15 @@ class Pico } if ($cmp === 0) { - // never assume equality; fallback to the original order (= alphabetical) - $cmp = (array_search($b['id'], $pageIds) - array_search($a['id'], $pageIds)); + // never assume equality; fallback to alphabetical order + return $alphaSortClosure($a, $b); } return $cmp * (($order == 'desc') ? 1 : -1); }); - } elseif ($this->getConfig('pages_order') == 'desc') { - $this->pages = array_reverse($this->pages); + } else { + // sort alphabetically + uasort($this->pages, $alphaSortClosure); } } @@ -978,20 +984,24 @@ class Pico /** * Recursively walks through a directory and returns all containing files - * matching the specified file extension in alphabetical order + * matching the specified file extension * * @param string $directory start directory * @param string $fileExtension return files with the given file extension * only (optional) + * @param int $order specify whether and how files should be + * sorted; use SCANDIR_SORT_ASCENDING for a alphabetical ascending + * order (default), SCANDIR_SORT_DESCENDING for a descending order or + * SCANDIR_SORT_NONE to leave the result unsorted * @return array list of found files */ - protected function getFiles($directory, $fileExtension = '') + protected function getFiles($directory, $fileExtension = '', $order = SCANDIR_SORT_ASCENDING) { $directory = rtrim($directory, '/'); $result = array(); // scandir() reads files in alphabetical order - $files = scandir($directory); + $files = scandir($directory, $order); $fileExtensionLength = strlen($fileExtension); if ($files !== false) { foreach ($files as $file) { From 012dffa85608b7ac936c747cba674ae8227fa0d7 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 21:59:03 +0200 Subject: [PATCH 035/118] Fix #257 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06e5653..81925b1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Run The easiest way to Pico is using [the built-in web server on PHP](). - $ php -S 0.0.0.0:8080 ./ + $ php -S 0.0.0.0:8080 Pico will be accessible from . From fbb744dd43e92560027ad480ce9c88d24d0b8aa5 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 22:42:23 +0200 Subject: [PATCH 036/118] Deny access to config, content, content-sample, lib and vendor dirs Send 404 Not Found instead of 403 Forbidden --- .htaccess | 1 + 1 file changed, 1 insertion(+) diff --git a/.htaccess b/.htaccess index b12e916..389bb6d 100644 --- a/.htaccess +++ b/.htaccess @@ -5,6 +5,7 @@ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?$1 [L,QSA] + RewriteRule ^(config|content|content-sample|lib|vendor)/.* - [R=404,L] SetEnv PICO_URL_REWRITING 1 From 241a52907e82ef5e570050482a2897488c78fc18 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 1 Oct 2015 22:52:10 +0200 Subject: [PATCH 037/118] Update inline code comments --- lib/Pico.php | 4 ++-- plugins/00-PicoDeprecated.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index ee65e7f..67962c2 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -309,7 +309,7 @@ class Pico } /** - * Loads plugins from PLUGINS_DIR in alphabetical order + * Loads plugins from Pico::$pluginsDir in alphabetical order * * Plugin files may be prefixed by a number (e.g. 00-PicoDeprecated.php) * to indicate their processing order. You MUST NOT use prefixes between @@ -371,7 +371,7 @@ class Pico } /** - * Loads the config.php from CONFIG_DIR + * Loads the config.php from Pico::$configDir * * @return void */ diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 39f17eb..65f09ea 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -86,15 +86,15 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Triggers the deprecated event config_loaded($config), tries to read * {@path "config.php"} in Picos root dir, enables the plugins - * {@link PicoParsePagesContent} and {@link PicoExcerpt} and defines the - * deprecated constants CONTENT_DIR and CONTENT_EXT + * {@link PicoParsePagesContent} and {@link PicoExcerpt} and defines some + * deprecated constants (ROOT_DIR, CONTENT_DIR etc.) * * @see DummyPlugin::onConfigLoaded() */ public function onConfigLoaded(&$config) { if (file_exists($this->getRootDir() . 'config.php')) { - // config.php in ROOT_DIR is deprecated; use CONFIG_DIR instead + // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead $newConfig = require($this->getRootDir() . 'config.php'); if (is_array($newConfig)) { $config = $newConfig + $config; From 9be23de8979839d3af5bcf1fa7824ad0b4bf8649 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 2 Oct 2015 16:53:29 +0200 Subject: [PATCH 038/118] Require PHP >= 5.3.6 until erusev/parsedown-extra#75 is solved --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index faf8901..926e41d 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": ">=5.3.2", + "php": ">=5.3.6", "twig/twig": "1.18.*", "erusev/parsedown-extra": "0.7.*", "symfony/yaml" : "2.3" From 00f76d10830893077d5cf184e55fa312414eded4 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 3 Oct 2015 17:36:43 +0200 Subject: [PATCH 039/118] Update README.md --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 81925b1..778a958 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,61 @@ Pico ==== Pico is a stupidly simple, blazing fast, flat file CMS. See http://picocms.org/ for more info. + -[![I Love Open Source](http://www.iloveopensource.io/images/logo-lightbg.png)](http://www.iloveopensource.io/projects/524c55dcca7964c617000756) +[![I Love Open Source](http://www.iloveopensource.io/images/logo-lightbg.png)][iloveopensource] Install ------- -Requires PHP 5.3+ -Download [composer]() and run it with install option. +You can install Pico either using a pre-bundled release or with composer. Pico requires PHP 5.3+ - $ curl -sS https://getcomposer.org/installer | php - $ php composer.phar install +#### Using a pre-bundled release + +Just [download the latest Pico release][LatestRelease] and upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server. + +#### Composer + +**Step 1** +[Download the *source code* of Picos latest release][LatestRelease] and upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server. + +**Step 2** +Navigate to the upload directory using a shell. + +**Step 3** +Download [composer][] and run it with the `install` option: +```bash +$ curl -sS https://getcomposer.org/installer | php +$ php composer.phar install +``` Run --- -The easiest way to Pico is using [the built-in web server on PHP](). +You have nothing to consider specially, simply navigate to your Pico install using your favourite web browser. Picos default contents will explain how to use your brand new, stupidly simple, blazing fast, flat file CMS. - $ php -S 0.0.0.0:8080 +**You don't have a web server?** -Pico will be accessible from . +The easiest way to Pico is using [the built-in web server of PHP][PHPServer]. Please note that PHPs built-in web server is for development and testing purposes only! + +**Step 1** +Navigate to Picos installation directory using a shell. + +**Step 2** +Start PHPs built-in web server: +```bash +$ php -S 0.0.0.0:8080 +``` + +**Step 3** +Access Pico from . Wiki ---- -Visit the [Pico Wiki](https://github.com/picocms/Pico/wiki) for plugins, themes, etc... +---- + +Visit the [Pico Wiki](https://github.com/picocms/Pico/wiki) for plugins, themes, etc. + +[iloveopensource]: http://www.iloveopensource.io/projects/524c55dcca7964c617000756 +[LatestRelease]: https://github.com/picocms/Pico/releases/latest +[composer]: https://getcomposer.org/ +[PHPServer]: From fa024ce8493c9a6bf85809e389e2cf6bfc162972 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 3 Oct 2015 18:35:17 +0200 Subject: [PATCH 040/118] Fix README.md formatting --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f33e2ae..a279696 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,17 @@ Just [download the latest Pico release][LatestRelease] and upload all files to t #### Composer -**Step 1 - for users** +###### Step 1 - for users [Download the *source code* of Picos latest release][LatestRelease], upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server and navigate to the upload directory using a shell. -**Step 1 - for developers** +###### Step 1 - for developers Open a shell and navigate to the desired install directory of Pico within the `httpdocs` directory (e.g. `/var/www/html`) of your server. You can now clone Picos Git repository as follows: ```shell $ git clone https://github.com/picocms/Pico.git . ``` Please note that this gives you the current development version of Pico, what is likely unstable and not ready for production use! -**Step 2** +###### Step 2 Download [composer][] and run it with the `install` option: ```shell $ curl -sS https://getcomposer.org/installer | php @@ -43,20 +43,20 @@ Run You have nothing to consider specially, simply navigate to your Pico install using your favourite web browser. Picos default contents will explain how to use your brand new, stupidly simple, blazing fast, flat file CMS. -**You don't have a web server?** +#### You don't have a web server? The easiest way to Pico is using [the built-in web server of PHP][PHPServer]. Please note that PHPs built-in web server is for development and testing purposes only! -**Step 1** +###### Step 1 Navigate to Picos installation directory using a shell. -**Step 2** +###### Step 2 Start PHPs built-in web server: ```shell $ php -S 0.0.0.0:8080 ``` -**Step 3** +###### Step 3 Access Pico from . Getting Help From ab8994593f7488671d93eaeb07b138ab9d40efb3 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 3 Oct 2015 18:38:16 +0200 Subject: [PATCH 041/118] Improve README.md formatting; Run PHP web server on localhost only --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a279696..3c63b3f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Open a shell and navigate to the desired install directory of Pico within the `h ```shell $ git clone https://github.com/picocms/Pico.git . ``` -Please note that this gives you the current development version of Pico, what is likely unstable and not ready for production use! +Please note that this gives you the current development version of Pico, what is likely *unstable* and *not ready for production use*! ###### Step 2 Download [composer][] and run it with the `install` option: @@ -53,7 +53,7 @@ Navigate to Picos installation directory using a shell. ###### Step 2 Start PHPs built-in web server: ```shell -$ php -S 0.0.0.0:8080 +$ php -S 127.0.0.1:8080 ``` ###### Step 3 From 3a4721a20c56ac831c2847355d343eedfc5c8552 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 14:15:11 +0200 Subject: [PATCH 042/118] SCANDIR_SORT_* constants are available since PHP 5.4 Thanks @Lomanic --- lib/Pico.php | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 67962c2..22551bb 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -26,6 +26,30 @@ */ class Pico { + /** + * Sort files in alphabetical ascending order + * + * @see Pico::getFiles() + * @var int + */ + const SORT_ASC = 0; + + /** + * Sort files in alphabetical descending order + * + * @see Pico::getFiles() + * @var int + */ + const SORT_DESC = 1; + + /** + * Don't sort files + * + * @see Pico::getFiles() + * @var int + */ + const SORT_NONE = 2; + /** * Root directory of this Pico instance * @@ -708,7 +732,7 @@ class Pico protected function readPages() { $this->pages = array(); - $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext'), SCANDIR_SORT_NONE); + $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext'), Pico::SORT_NONE); foreach ($files as $i => $file) { // skip 404 page if (basename($file) == '404' . $this->getConfig('content_ext')) { @@ -990,12 +1014,12 @@ class Pico * @param string $fileExtension return files with the given file extension * only (optional) * @param int $order specify whether and how files should be - * sorted; use SCANDIR_SORT_ASCENDING for a alphabetical ascending - * order (default), SCANDIR_SORT_DESCENDING for a descending order or - * SCANDIR_SORT_NONE to leave the result unsorted + * sorted; use Pico::SORT_ASC for a alphabetical ascending order (this + * is the default behaviour), Pico::SORT_DESC for a descending order + * or Pico::SORT_NONE to leave the result unsorted * @return array list of found files */ - protected function getFiles($directory, $fileExtension = '', $order = SCANDIR_SORT_ASCENDING) + protected function getFiles($directory, $fileExtension = '', $order = self::SORT_ASC) { $directory = rtrim($directory, '/'); $result = array(); @@ -1013,7 +1037,7 @@ class Pico if (is_dir($directory . '/' . $file)) { // get files recursively - $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension)); + $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension, $order)); } elseif (empty($fileExtension) || (substr($file, -$fileExtensionLength) === $fileExtension)) { $result[] = $directory . '/' . $file; } From 3f7b0998c627759db78269e69225156c45dc1547 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 14:30:19 +0200 Subject: [PATCH 043/118] Declare undefined $plugins variable Thanks @Lomanic --- plugins/00-PicoDeprecated.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 65f09ea..7edf739 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -104,6 +104,7 @@ class PicoDeprecated extends AbstractPicoPlugin // enable PicoParsePagesContent and PicoExcerpt // we can't enable them during onPluginsLoaded because we can't know // if the user disabled us (PicoDeprecated) manually in the config + $plugins = $this->getPlugins(); if (isset($plugins['PicoParsePagesContent'])) { // parse all pages content if this plugin hasn't // be explicitly enabled/disabled yet From 77f939028c08f79f059b154510986de3c57bacec Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 15:24:38 +0200 Subject: [PATCH 044/118] Support per-directory 404.md files --- lib/Pico.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 22551bb..7221a60 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -271,7 +271,7 @@ class Pico $this->triggerEvent('on404ContentLoading', array(&$this->requestFile)); header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); - $this->rawContent = $this->load404Content(); + $this->rawContent = $this->load404Content($this->requestFile); $this->triggerEvent('on404ContentLoaded', array(&$this->rawContent)); } @@ -555,11 +555,23 @@ class Pico /** * Returns the raw contents of the 404 file if the requested file wasn't found * - * @return string raw contents of the 404 file + * @param string $file path to requested (but not existing) file + * @return string raw contents of the 404 file */ - public function load404Content() + public function load404Content($file) { - return $this->loadFileContent($this->getConfig('content_dir') . '404' . $this->getConfig('content_ext')); + $errorFileDir = substr($file, strlen($this->getConfig('content_dir'))); + do { + $errorFileDir = dirname($errorFileDir); + $errorFile = $errorFileDir . '/404' . $this->getConfig('content_ext'); + } while (!file_exists($this->getConfig('content_dir') . $errorFile) && ($errorFileDir !== '.')); + + if (!file_exists($this->getConfig('content_dir') . $errorFile)) { + $errorFile = ($errorFileDir === '.') ? '404' . $this->getConfig('content_ext') : $errorFile; + throw new RuntimeException('Required "' . $errorFile .'" not found'); + } + + return $this->loadFileContent($this->getConfig('content_dir') . $errorFile); } /** From 9aa62b4b44dde27500a0ec6d967f9841cf8edbbc Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 16:57:57 +0200 Subject: [PATCH 045/118] Improve method docs of Pico::load404Content() --- lib/Pico.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 7221a60..2b1efbc 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -553,10 +553,12 @@ class Pico } /** - * Returns the raw contents of the 404 file if the requested file wasn't found + * Returns the raw contents of the first found 404 file when traversing + * up from the directory the requested file is in * - * @param string $file path to requested (but not existing) file - * @return string raw contents of the 404 file + * @param string $file path to requested (but not existing) file + * @return string raw contents of the 404 file + * @throws RuntimeException thrown when no suitable 404 file is found */ public function load404Content($file) { From ef1a9e0c33f0d98775e5cc67c397263e05580f64 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 16:59:38 +0200 Subject: [PATCH 046/118] Improve README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c63b3f..2f31af3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Pico ==== [![License](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://scrutinizer-ci.com/g/theshka/Pico/build-status/LICENSE) -[![Version](https://img.shields.io/badge/version-0.9-lightgrey.svg)]() +[![Version](https://img.shields.io/badge/version-1.0-lightgrey.svg)]() [![Build Status](https://scrutinizer-ci.com/g/theshka/Pico/badges/build.png?b=master)](https://scrutinizer-ci.com/g/theshka/Pico/build-status/master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/theshka/Pico/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/theshka/Pico/?branch=master) Pico is a stupidly simple, blazing fast, flat file CMS. See http://picocms.org/ for more info. @@ -45,7 +45,7 @@ You have nothing to consider specially, simply navigate to your Pico install usi #### You don't have a web server? -The easiest way to Pico is using [the built-in web server of PHP][PHPServer]. Please note that PHPs built-in web server is for development and testing purposes only! +Starting with PHP 5.4 the easiest way to try Pico is using [the built-in web server of PHP][PHPServer]. Please note that PHPs built-in web server is for development and testing purposes only! ###### Step 1 Navigate to Picos installation directory using a shell. @@ -62,7 +62,7 @@ Access Pico from . Getting Help ------------ -You can read the wiki if you are looking for examples and read the inline-docs for more development information. +You can read the [wiki][Wiki] if you are looking for examples and read the inline-docs for more development information. If you find a bug please report it on the issues page, but remember to include as much detail as possible, and what someone can do to re-create the issue. From 2e15e112f731034db32fbd6895f0f432e41cc77f Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 18:50:16 +0200 Subject: [PATCH 047/118] Define deprecated constants before evaluating the config.php in Picos root dir This prevents E_NOTICEs when using e.g. ROOT_DIR in a old config.php, so upgrading users are usually not bothered with this BC break --- plugins/00-PicoDeprecated.php | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 7edf739..d39b229 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -93,32 +93,6 @@ class PicoDeprecated extends AbstractPicoPlugin */ public function onConfigLoaded(&$config) { - if (file_exists($this->getRootDir() . 'config.php')) { - // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead - $newConfig = require($this->getRootDir() . 'config.php'); - if (is_array($newConfig)) { - $config = $newConfig + $config; - } - } - - // enable PicoParsePagesContent and PicoExcerpt - // we can't enable them during onPluginsLoaded because we can't know - // if the user disabled us (PicoDeprecated) manually in the config - $plugins = $this->getPlugins(); - if (isset($plugins['PicoParsePagesContent'])) { - // parse all pages content if this plugin hasn't - // be explicitly enabled/disabled yet - if (!$plugins['PicoParsePagesContent']->isStatusChanged()) { - $plugins['PicoParsePagesContent']->setEnabled(true, true, true); - } - } - if (isset($plugins['PicoExcerpt'])) { - // enable excerpt plugin if it hasn't be explicitly enabled/disabled yet - if (!$plugins['PicoExcerpt']->isStatusChanged()) { - $plugins['PicoExcerpt']->setEnabled(true, true, true); - } - } - // CONTENT_DIR constant is deprecated since v0.9, // ROOT_DIR, LIB_DIR, PLUGINS_DIR, THEMES_DIR and CONTENT_EXT constants since v1.0, // CONFIG_DIR constant existed just for a short time between v0.9 and v1.0, @@ -146,6 +120,32 @@ class PicoDeprecated extends AbstractPicoPlugin define('CONTENT_EXT', $config['content_ext']); } + if (file_exists($this->getRootDir() . 'config.php')) { + // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead + $newConfig = require($this->getRootDir() . 'config.php'); + if (is_array($newConfig)) { + $config = $newConfig + $config; + } + } + + // enable PicoParsePagesContent and PicoExcerpt + // we can't enable them during onPluginsLoaded because we can't know + // if the user disabled us (PicoDeprecated) manually in the config + $plugins = $this->getPlugins(); + if (isset($plugins['PicoParsePagesContent'])) { + // parse all pages content if this plugin hasn't + // be explicitly enabled/disabled yet + if (!$plugins['PicoParsePagesContent']->isStatusChanged()) { + $plugins['PicoParsePagesContent']->setEnabled(true, true, true); + } + } + if (isset($plugins['PicoExcerpt'])) { + // enable excerpt plugin if it hasn't be explicitly enabled/disabled yet + if (!$plugins['PicoExcerpt']->isStatusChanged()) { + $plugins['PicoExcerpt']->setEnabled(true, true, true); + } + } + $this->triggerEvent('config_loaded', array(&$config)); } From 006afa5774db21c106864941a091c6155db2f1bc Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 19:57:20 +0200 Subject: [PATCH 048/118] Update changelog.txt The changelog only provides basic information about the enormous changes introduced with Pico 1.0-beta. Please refer to the (not yet written... :smile:) UPGRADE section of the docs for details. --- changelog.txt | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index f8035ad..1292835 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,63 @@ *** Pico Changelog *** +2015.10.XX - version 1.0-beta + * NOTE: This changelog only provides basic information about the enormous + changes introduced with Pico 1.0-beta. Please refer to the UPGRADE + section of the docs for details. + * [New] Pico is on its way to its first stable release! + * [New] Provide pre-bundled releases + * [New] Heavily expanded documentation (inline code docs, user docs, dev docs) + * [New] New routing system using the QUERY_STRING method; Pico now works + out-of-the-box with any webserver and without URL rewriting; use + `%base_url%?sub/page` in markdown files and `{{ "sub/page"|link }}` + in Twig templates to declare internal links + * [New] Brand new plugin system with dependencies (see `PicoPluginInterface` + and `AbstractPicoPlugin`); if you're plugin developer, you really + should take a look at the UPGRADE section of the docs! + * [New] Introducing the `PicoDeprecated` plugin to maintain full backward + compatibility with Pico 0.9 and older + * [New] Support YAML-style meta header comments (`---`) + * [New] Various new placeholders to use in content files (e.g. `%site_title%`) + * [New] Provide access to all meta headers in content files (`%meta.*%`) + * [New] Provide access to meta headers in `$page` arrays (`$page['meta']`) + * [New] The file extension of content files is now configurable + * [New] Supporting per-directory `404.md` files + * [New] #103: Providing access to `sub.md` even when the `sub` directory + exists, provided that there is no `sub/index.md` + * [New] #249: Support the `.twig` file extension for templates + * [Changed] Complete code refactoring + * [Changed] Source code now follows PSR code styling + * [Changed] Replacing constants (e.g. `ROOT_DIR`) with constructor parameters + * [Changed] Paths (e.g. `content_dir`) are now relative to Picos root dir + * [Changed] Adding `Pico::run()` method that performs Picos processing and + returns the rendered contents + * [Changed] Renaming all plugin events; adding some new events + * [Changed] `Pico_Plugin` is now the fully documented `DummyPlugin` + * [Changed] Meta data must start on the first line of the file now + * [Changed] Exclude inaccessible files from pages list + * [Changed] With alphabetical order, index files (e.g. `sub/index.md`) are + now always placed before their sub pages (e.g. `sub/foo.md`) + * [Changed] Pico requires PHP >= 5.3.6 (due to `erusev/parsedown-extra`) + * [Changed] Composer: Require a v0.7 release of `erusev/parsedown-extra` + * [Changed] #93, #158: Pico doesn't parse all content files anymore; moved to + `PicoParsePagesContent` plugin, but still impacts performance; + Note: This means `$page['content']` isn't available anymore, but + usually the new `$page['raw_content']` is suitable as replacement. + * [Changed] #116: Parse meta headers using the Symfony YAML component + * [Changed] #244: Replace opendir() with scandir() + * [Changed] #246: Move `config.php` to `config/` directory + * [Changed] #253: Assume HTTPS if page is requested through port 443 + * [Changed] A vast number of small improvements and changes... + * [Fixed] Sorting by date now uses timestamps and works as expected + * [Fixed] Fixing `$currentPage`, `$nextPage` and `$previousPage` + * [Fixed] #99: Support content filenames with spaces + * [Fixed] #140, #241: Use file paths as page identifiers rather than titles + * [Fixed] #248: Always set a timezone; adding `$config['timezone']` option + * [Fixed] A vast number of small bugs... + * [Removed] Removing the default Twig cache dir + * [Removed] Removing various empty `index.html` files + * [Removed] Moving Picos excerpt feature to `PicoExcerpt` plugin + 2015.04.28 - version 0.9 * [New] Default theme is now mobile-friendly * [New] Description meta now available in content areas @@ -8,13 +66,13 @@ * [Changed] Removed Composer, Twig files in /vendor, you must run composer install now * [Changed] Localized date format; strftime() instead of date() * [Changed] Added ignore for tmp file extensions in the get_files() method - * [Fixed] Pico now only removes the 1st comment block in .md file + * [Fixed] Pico now only removes the 1st comment block in .md files * [Fixed] Issue wherein the alphabetical sorting of pages did not happen 2013.10.23 - version 0.8 * [New] Added ability to set template in content meta * [New] Added before_parse_content and after_parse_content hooks - * [Changed] content_parsed hook is now depreciated + * [Changed] content_parsed hook is now deprecated * [Changed] Moved loading the config to nearer the beginning of the class * [Changed] Only append ellipsis in limit_words() when word count exceeds max * [Changed] Made private methods protected for better inheritance From 27d694697f4d549539e8cbd94017dc5ff3a1126d Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 21:34:37 +0200 Subject: [PATCH 049/118] Fix code formatting --- lib/AbstractPicoPlugin.php | 6 +++--- lib/Pico.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 07905d1..09e8eb2 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -71,7 +71,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface { // plugins can be enabled/disabled using the config if ($eventName === 'onConfigLoaded') { - $pluginEnabled = $this->getConfig(get_called_class().'.enabled'); + $pluginEnabled = $this->getConfig(get_called_class() . '.enabled'); if ($pluginEnabled !== null) { $this->setEnabled($pluginEnabled); } @@ -137,8 +137,8 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface } throw new BadMethodCallException( - 'Call to undefined method '.get_class($this->getPico()).'::'.$methodName.'() ' - . 'through '.get_called_class().'::__call()' + 'Call to undefined method ' . get_class($this->getPico()) . '::' . $methodName . '() ' + . 'through ' . get_called_class() . '::__call()' ); } diff --git a/lib/Pico.php b/lib/Pico.php index 2b1efbc..5c11e2e 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -380,7 +380,7 @@ class Pico return $this->plugins[$pluginName]; } - throw new RuntimeException("Missing plugin '".$pluginName."'"); + throw new RuntimeException("Missing plugin '" . $pluginName . "'"); } /** @@ -570,7 +570,7 @@ class Pico if (!file_exists($this->getConfig('content_dir') . $errorFile)) { $errorFile = ($errorFileDir === '.') ? '404' . $this->getConfig('content_ext') : $errorFile; - throw new RuntimeException('Required "' . $errorFile .'" not found'); + throw new RuntimeException('Required "' . $errorFile . '" not found'); } return $this->loadFileContent($this->getConfig('content_dir') . $errorFile); From 79e2dacdb2a47dd9abb7327aac74016d3117c6ea Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 22:08:04 +0200 Subject: [PATCH 050/118] Fix method docs typo --- plugins/DummyPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 70133c7..bf1d538 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -44,7 +44,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico readed its configuration + * Triggered after Pico read its configuration * * @see Pico::getConfig() * @param array &$config array of config variables From 4f1e8667c37dc527edaeece313fc67851b3b63ed Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 22:30:35 +0200 Subject: [PATCH 051/118] Cast AbstractPicoPlugin::$dependsOn to array Plugin devs could come up with the idea of setting AbstractPicoPlugin::$dependsOn to a string (single dependency) or null (no dependencies) --- lib/AbstractPicoPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 09e8eb2..e3ee2dc 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -187,7 +187,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface */ public function getDependencies() { - return $this->dependsOn; + return (array) $this->dependsOn; } /** From 9d518fd7223740eda576c2f50ab5f870939734d9 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 22:39:38 +0200 Subject: [PATCH 052/118] Move sorting of $pages from Pico::getPages() to Pico::sortPages() --- lib/Pico.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Pico.php b/lib/Pico.php index 5c11e2e..408d4ae 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -298,6 +298,7 @@ class Pico $this->triggerEvent('onPagesLoading'); $this->readPages(); + $this->sortPages(); $this->discoverCurrentPage(); $this->triggerEvent('onPagesLoaded', array( @@ -798,7 +799,15 @@ class Pico $this->pages[$id] = $page; } + } + /** + * Sorts all pages known to Pico + * + * @return void + */ + protected function sortPages() + { // sort pages $order = $this->getConfig('pages_order'); $alphaSortClosure = function ($a, $b) use ($order) { From 46ef63186acc22b48a9480917dd6002290165b92 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 22:52:08 +0200 Subject: [PATCH 053/118] Support $config['']['enabled'] option ... as a alternative to $config['.enabled']; Thanks @theshka for giving this hint --- lib/AbstractPicoPlugin.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index e3ee2dc..804a6be 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -74,6 +74,11 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface $pluginEnabled = $this->getConfig(get_called_class() . '.enabled'); if ($pluginEnabled !== null) { $this->setEnabled($pluginEnabled); + } else { + $pluginConfig = $this->getConfig(get_called_class()); + if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) { + $this->setEnabled($pluginConfig['enabled']); + } } } From 1cbf48af794d7e3d835cb1c65f69ad113d0acd0b Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 4 Oct 2015 23:25:32 +0200 Subject: [PATCH 054/118] Split PicoDeprecated::onConfigLoaded() into multiple methods --- plugins/00-PicoDeprecated.php | 54 +++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index d39b229..edf05ea 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -84,19 +84,34 @@ class PicoDeprecated extends AbstractPicoPlugin } /** - * Triggers the deprecated event config_loaded($config), tries to read - * {@path "config.php"} in Picos root dir, enables the plugins - * {@link PicoParsePagesContent} and {@link PicoExcerpt} and defines some - * deprecated constants (ROOT_DIR, CONTENT_DIR etc.) + * Triggers the deprecated event config_loaded($config) * + * @see PicoDeprecated::defineConstants() + * @see PicoDeprecated::loadRootDirConfig() + * @see PicoDeprecated::enablePlugins() * @see DummyPlugin::onConfigLoaded() */ public function onConfigLoaded(&$config) { - // CONTENT_DIR constant is deprecated since v0.9, - // ROOT_DIR, LIB_DIR, PLUGINS_DIR, THEMES_DIR and CONTENT_EXT constants since v1.0, - // CONFIG_DIR constant existed just for a short time between v0.9 and v1.0, - // CACHE_DIR constant was dropped with v1.0 without a replacement + $this->defineConstants(); + $this->loadRootDirConfig($config); + $this->enablePlugins(); + + $this->triggerEvent('config_loaded', array(&$config)); + } + + /** + * Defines deprecated constants + * + * CONTENT_DIR is deprecated since v0.9, ROOT_DIR, LIB_DIR, PLUGINS_DIR, + * THEMES_DIR and CONTENT_EXT since v1.0, CONFIG_DIR existed just for a + * short time between v0.9 and v1.0 and CACHE_DIR was dropped with v1.0 + * without a replacement. + * + * @return void + */ + protected function defineConstants() + { if (!defined('ROOT_DIR')) { define('ROOT_DIR', $this->getRootDir()); } @@ -114,12 +129,21 @@ class PicoDeprecated extends AbstractPicoPlugin define('THEMES_DIR', $this->getThemesDir()); } if (!defined('CONTENT_DIR')) { - define('CONTENT_DIR', $config['content_dir']); + define('CONTENT_DIR', $this->getConfig('content_dir')); } if (!defined('CONTENT_EXT')) { - define('CONTENT_EXT', $config['content_ext']); + define('CONTENT_EXT', $this->getConfig('content_ext')); } + } + /** + * Read {@path "config.php"} in Picos root dir + * + * @param array &$config array of config variables + * @return void + */ + protected function loadRootDirConfig(&$config) + { if (file_exists($this->getRootDir() . 'config.php')) { // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead $newConfig = require($this->getRootDir() . 'config.php'); @@ -127,7 +151,15 @@ class PicoDeprecated extends AbstractPicoPlugin $config = $newConfig + $config; } } + } + /** + * Enables the plugins {@link PicoParsePagesContent} and {@link PicoExcerpt} + * + * @return void + */ + protected function enablePlugins() + { // enable PicoParsePagesContent and PicoExcerpt // we can't enable them during onPluginsLoaded because we can't know // if the user disabled us (PicoDeprecated) manually in the config @@ -145,8 +177,6 @@ class PicoDeprecated extends AbstractPicoPlugin $plugins['PicoExcerpt']->setEnabled(true, true, true); } } - - $this->triggerEvent('config_loaded', array(&$config)); } /** From 7537159868b6c3b4430004de02235328375d4eae Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 5 Oct 2015 01:50:55 +0200 Subject: [PATCH 055/118] Remove the need to register headers during onMetaHeaders() Why? I'm currently writing the user docs and I really have no idea how to explain this whole process in a non-technical way... It is very likely that a normal user wants to use custom tags and it would be absurd to tell him,that he should learn a programming language to do so. On the other hand, providing a copy-and-paste template makes the whole idea of explicitly registering headers worthless. The only reasonable solution is to remove the need to register headers. Anyway, I think @PontusHorn is right to say that registering headers makes the whole system more predictable. So plugin developers are still instructed to register their meta headers during . We actually can't check and ensure this, but that's imho the best solution. --- lib/Pico.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 408d4ae..11e0cd2 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -616,9 +616,10 @@ class Pico * * Meta data MUST start on the first line of the file, either opened and * closed by --- or C-style block comments (deprecated). The headers are - * parsed by the YAML component of the Symfony project. You MUST register - * new headers during the `onMetaHeaders` event first, otherwise they are - * ignored and won't be returned. + * parsed by the YAML component of the Symfony project, keys are lowered. + * If you're a plugin developer, you MUST register new headers during the + * `onMetaHeaders` event first. The implicit availability of headers is + * for users and pure (!) theme developers ONLY. * * @see * @param string $rawContent the raw file contents @@ -632,16 +633,19 @@ class Pico . "(.*?)(?:\r)?\n(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; if (preg_match($pattern, $rawContent, $rawMetaMatches)) { $yamlParser = new \Symfony\Component\Yaml\Parser(); - $rawMeta = $yamlParser->parse($rawMetaMatches[3]); - $rawMeta = array_change_key_case($rawMeta, CASE_LOWER); + $meta = $yamlParser->parse($rawMetaMatches[3]); + $meta = array_change_key_case($meta, CASE_LOWER); - // TODO: maybe we should change this to pass all headers, no matter - // they are registered during the `onMetaHeaders` event or not... foreach ($headers as $fieldId => $fieldName) { $fieldName = strtolower($fieldName); - if (isset($rawMeta[$fieldName])) { - $meta[$fieldId] = $rawMeta[$fieldName]; + if (isset($meta[$fieldName])) { + // rename field (e.g. remove whitespaces) + if ($fieldId != $fieldName) { + $meta[$fieldId] = $meta[$fieldName]; + unset($meta[$fieldName]); + } } else { + // guarantee array key existance $meta[$fieldId] = ''; } } @@ -653,6 +657,7 @@ class Pico $meta['time'] = $meta['date_formatted'] = ''; } } else { + // guarantee array key existance foreach ($headers as $id => $field) { $meta[$id] = ''; } From 7aa199d77aae8ca3f02a2ef043f722e18c24d4f1 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 5 Oct 2015 01:58:11 +0200 Subject: [PATCH 056/118] Update changelog.txt for 7537159 --- changelog.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 1292835..ecda215 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,8 +12,8 @@ `%base_url%?sub/page` in markdown files and `{{ "sub/page"|link }}` in Twig templates to declare internal links * [New] Brand new plugin system with dependencies (see `PicoPluginInterface` - and `AbstractPicoPlugin`); if you're plugin developer, you really - should take a look at the UPGRADE section of the docs! + and `AbstractPicoPlugin`); if you're plugin dev, you really should + take a look at the UPGRADE section of the docs! * [New] Introducing the `PicoDeprecated` plugin to maintain full backward compatibility with Pico 0.9 and older * [New] Support YAML-style meta header comments (`---`) @@ -34,6 +34,9 @@ * [Changed] Renaming all plugin events; adding some new events * [Changed] `Pico_Plugin` is now the fully documented `DummyPlugin` * [Changed] Meta data must start on the first line of the file now + * [Changed] Dropping the need to register meta headers for the convenience of + users and pure (!) theme devs; plugin devs are still REQUIRED to + register their meta headers during `onMetaHeaders` * [Changed] Exclude inaccessible files from pages list * [Changed] With alphabetical order, index files (e.g. `sub/index.md`) are now always placed before their sub pages (e.g. `sub/foo.md`) From 1419cf163689bcb88d1851adbf505632195c1881 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 6 Oct 2015 20:23:28 +0200 Subject: [PATCH 057/118] Add Pico::setConfig() method Thanks @dav-m85 --- lib/Pico.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Pico.php b/lib/Pico.php index 11e0cd2..61399e5 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -246,6 +246,9 @@ class Pico */ public function run() { + // lock config + $this->config = is_array($this->config) ? $this->config : array(); + // load plugins $this->loadPlugins(); $this->triggerEvent('onPluginsLoaded', array(&$this->plugins)); @@ -418,7 +421,7 @@ class Pico $configFile = $this->getConfigDir() . 'config.php'; $config = file_exists($configFile) ? require($configFile) : null; - $this->config = is_array($config) ? $config + $defaultConfig : $defaultConfig; + $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig; if (empty($this->config['base_url'])) { $this->config['base_url'] = $this->getBaseUrl(); @@ -437,6 +440,29 @@ class Pico } } + /** + * Sets Picos config before calling Pico::run() + * + * This method allows you to modify Picos config without creating a + * {@path "config/config.php"} or changing some of its variables before + * Pico starts processing. It can only be called between the constructor + * call and Pico::run(). Options set with this method cannot be overwritten + * by {@path "config/config.php"}. + * + * @param array $config array with configuration variables, like + * $config in {@path "config/config.php"} + * @return void + * @throws RuntimeException thrown if Pico already started processing + */ + public function setConfig(array $config) + { + if ($this->config !== null) { + throw new RuntimeException('You cannot modify Picos config after processing has started'); + } + + $this->config = $config; + } + /** * Returns either the value of the specified config variable or * the config array From 04a1c603d04c5325ae58d3790d43e7e02eead425 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 6 Oct 2015 20:33:31 +0200 Subject: [PATCH 058/118] Update changelog.txt for 1419cf1 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index ecda215..4aebd43 100644 --- a/changelog.txt +++ b/changelog.txt @@ -21,6 +21,7 @@ * [New] Provide access to all meta headers in content files (`%meta.*%`) * [New] Provide access to meta headers in `$page` arrays (`$page['meta']`) * [New] The file extension of content files is now configurable + * [New] Add `Pico::setConfig()` method to predefine config variables * [New] Supporting per-directory `404.md` files * [New] #103: Providing access to `sub.md` even when the `sub` directory exists, provided that there is no `sub/index.md` From b09433a37b6b4f1813ce322894496df01c0706a0 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 6 Oct 2015 20:38:34 +0200 Subject: [PATCH 059/118] Allow multiple calls to Pico::setConfig() --- lib/Pico.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 61399e5..5070471 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -78,6 +78,13 @@ class Pico */ protected $themesDir; + /** + * Boolean indicating whether Picos processing started yet + * + * @var boolean + */ + protected $locked = false; + /** * List of loaded plugins * @@ -246,8 +253,8 @@ class Pico */ public function run() { - // lock config - $this->config = is_array($this->config) ? $this->config : array(); + // lock Pico + $this->locked = true; // load plugins $this->loadPlugins(); @@ -421,6 +428,8 @@ class Pico $configFile = $this->getConfigDir() . 'config.php'; $config = file_exists($configFile) ? require($configFile) : null; + + $this->config = is_array($this->config) ? $this->config : array(); $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig; if (empty($this->config['base_url'])) { @@ -456,7 +465,7 @@ class Pico */ public function setConfig(array $config) { - if ($this->config !== null) { + if ($this->locked) { throw new RuntimeException('You cannot modify Picos config after processing has started'); } From 7c5f371b9ac875ccacf605bcfa2843821fb5c339 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 8 Oct 2015 14:19:59 +0200 Subject: [PATCH 060/118] Use PSR-0 autoload Makes no big difference... Using PSR-4 breaks BC. --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 926e41d..b0d75e1 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ "symfony/yaml" : "2.3" }, "autoload": { - "files": [ - "lib/Pico.php", - "lib/PicoPluginInterface.php", - "lib/AbstractPicoPlugin.php" - ] + "psr-0": { + "Pico": "lib/", + "PicoPluginInterface": "lib/", + "AbstractPicoPlugin": "lib/" + } } } From 40dbd0ee373931a11709bdb00f56bae16715278f Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 8 Oct 2015 21:01:30 +0200 Subject: [PATCH 061/118] Update Picos inline user docs Adding a Blogging and URL Rewriting section, splitting the Plugins section into "for users" and "for devs", extend all sections and fix some typos --- content-sample/index.md | 145 ++++++++++++++++++++++++++++++++++------ 1 file changed, 124 insertions(+), 21 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index 8ea4d1a..dd2fd2b 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -6,7 +6,7 @@ Description: Pico is a stupidly simple, blazing fast, flat file CMS. ## Welcome to Pico Congratulations, you have successfully installed [Pico](http://picocms.org/). -%meta.description% +%meta.description% ### Creating Content @@ -54,7 +54,9 @@ and their corresponing URLs: -If a file cannot be found, the file `content-sample/404.md` will be shown. +If a file cannot be found, the file `content-sample/404.md` will be shown. You +can add `404.md` files to any directory, so if you want to use a special error +page for your blog, simply create `content-sample/blog/404.md`. ### Text File Markup @@ -81,26 +83,68 @@ There are also certain variables that you can use in your text files: can be specified using %base_url%?sub/page * %theme_url% - The URL to the currently used theme * %meta.*% - Access any meta variable of the current page, - e.g. %meta.author% returns `Joe Bloggs` + e.g. %meta.author% is replaced with `Joe Bloggs` + +### Blogging + +Pico is no blogging software - but makes it very easy for you to use it as a +blogging software. You can find many plugins out there implementing typical +blogging features like authentication, tagging, pagination and social plugins. +See the below Plugins section for details. + +If you want to use Pico as a blogging software, you probably want to do +something like the following: +1. Put all your blog articles in a separate `blog` folder in your `content` + directory. All these articles should have both a `Date` and `Template` meta + header, the latter with e.g. `blog-post` as value (see Step 2). +2. Create a new Twig template called `blog-post.twig` (this must match the + `Template` meta header from Step 1) in your theme directory. This template + probably isn't very different from your default `index.twig`, it specifies + how your article pages will look like. +3. Create a `blog.md` in your `content` folder and set its `Template` meta + header to e.g. `blog`. Also create a `blog.twig` in your theme directory. + This template will show a list of your articles, so you probably want to + do something like this: + ``` + {% for page in pages %} + {% if page.id starts with "blog/" %} +
+

{{ page.title }}

+

{{ page.date_formatted }}

+

{{ page.description }}

+
+ {% endif %} + {% endfor %} + ``` +4. Let Pico sort pages by date by setting `$config['pages_order_by'] = 'date';` + in your `config/config.php`. To use a descending order (newest articles + first), also add `$config['pages_order'] = 'desc';`. The former won't affect + pages without a `Date` meta header, but the latter does. To use ascending + order for your page navigation again, add Twigs `reverse` filter to the + navigation loop (`{% for page in pages|reverse %}...{% endfor %}`) in your + themes `index.twig`. +5. Make sure to exclude the blog articles from your page navigation. You can + achieve this by adding `{% if not page starts with "blog/" %}...{% endif %}` + to the navigation loop. ### Themes You can create themes for your Pico installation in the `themes` folder. Check -out the default theme for an example of a theme. Pico uses [Twig][] for -template rendering. You can select your theme by setting the `$config['theme']` -variable in `config/config.php` to your theme folder. +out the default theme for an example. Pico uses [Twig][] for template +rendering. You can select your theme by setting the `$config['theme']` option +in `config/config.php` to the name of your theme folder. -All themes must include an `index.twig` file to define the HTML structure of -the theme. Below are the Twig variables that are available to use in your -theme. Paths (e.g. `{{ base_dir }}``) and URLs (e.g. `{{ base_url }}`) don't -have a trailing slash. +All themes must include an `index.twig` (or `index.html`) file to define the +HTML structure of the theme. Below are the Twig variables that are available +to use in your theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs +(e.g. `{{ base_url }}`) don't have a trailing slash. * `{{ config }}` - Conatins the values you set in `config/config.php` - (e.g. `{{ config.theme }}` = "default") + (e.g. `{{ config.theme }}` becomes `default`) * `{{ base_dir }}` - The path to your Pico root directory * `{{ base_url }}` - The URL to your Pico site -* `{{ theme_dir }}` - The path to the Pico active theme directory -* `{{ theme_url }}` - The URL to the Pico active theme directory +* `{{ theme_dir }}` - The path to the currently active theme +* `{{ theme_url }}` - The URL to the currently active theme * `{{ rewrite_url }}` - A boolean flag indicating enabled/disabled URL rewriting * `{{ site_title }}` - Shortcut to the site title (see `config/config.php`) * `{{ meta }}` - Contains the meta values from the current page @@ -109,7 +153,9 @@ have a trailing slash. * `{{ meta.author }}` * `{{ meta.date }}` * `{{ meta.date_formatted }}` + * `{{ meta.time }}` * `{{ meta.robots }}` + * ... * `{{ content }}` - The content of the current page (after it has been processed through Markdown) * `{{ pages }}` - A collection of all the content pages in your site @@ -128,24 +174,81 @@ have a trailing slash. * `{{ next_page }}` - The data of the next page (relative to `current_page`) * `{{ is_front_page }}` - A boolean flag for the front page -Pages can be used like: +Pages can be used like the following: -
<ul class="nav">
-    {% for page in pages %}
-    <li><a href="{{ page.url }}">{{ page.title }}</a></li>
-    {% endfor %}
-</ul>
+ + +You can use different templates for different content files by specifing the +`Template` meta header. Simply add e.g. `Template: blog-post` to a content file +and Pico will use the `blog-post.twig` file in your theme folder to render +the page. + +You don't have to create your own theme if Picos default theme isn't sufficient +for you, you can use one of the great themes third-party developers and +designers created in the past. As with plugins, you can find themes in +[our Wiki](https://github.com/picocms/Pico/wiki/Pico-Themes). ### Plugins -See [http://pico.dev7studios.com/plugins](http://picocms.org/plugins) +#### Plugins for users + +Officially tested plugins can be found at http://pico.dev7studios.com/plugins, +but there are many awesome third-party plugins out there! A good start point +for discovery is [our Wiki](https://github.com/picocms/Pico/wiki/Pico-Plugins). + +Pico makes it very easy for you to add new features to your website. Simply +upload the files of the plugin to the `plugins/` directory and you're done. +Depending on the plugin you've installed, you may have to go through some more +steps (e.g. specifing config variables), the plugin docs or `README` file will +explain what to do. + +Plugins which were written to work with Pico 1.0 can be enabled and disabled +through your `config/config.php`. If you want to e.g. disable the `PicoExcerpt` +plugin, add the following line to your `config/config.php`: +`$config['PicoExcerpt.enabled'] = false;`. To force the plugin to be enabled +replace `false` with `true`. + +#### Plugins for developers + +You're a plugin developer? We love you guys! You can find tons of information +about how to develop plugins at http://picocms.org/plugin-dev.html. If you'd +developed a plugin for Pico 0.9 and older, you probably want to upgrade it +to the brand new plugin system introduced with Pico 1.0. Please refer to the +[Upgrade section of the docs](http://picocms.org/plugin-dev.html#upgrade). + +### URL Rewriting + +Picos default URLs (e.g. %base_url%/?sub/page) already are very user friendly. +Pico anyway offers you an URL rewrite feature to make URLs even more user +friendly (e.g. %base_url%/sub/page). + +If you're using the Apache web server, URL rewriting probably already is +enabled - try it yourself, click on the [second URL](%base_url%/sub/page). If +you get an error message from your web server, please make sure to enable the +`mod_rewrite` module. Assumed the second URL works, but Pico still shows no +rewritten URLs, force URL rewriting by setting `$config['rewrite_url'] = true;` +in your `config/config.php`. + +If you're using Nginx, you can use the following configuration to enable +URL rewriting. Don't forget to adjust the path (`/pico/`; line `1` and `4`) +to match your installation directory. You can then enable URL rewriting by +setting `$config['rewrite_url'] = true;` in your `config/config.php`. + + location /pico/ { + index index.php; + try_files $uri $uri/ /pico/?$uri&$args; + } ### Config You can override the default Pico settings (and add your own custom settings) by editing `config/config.php` in the Pico directory. For a brief overview of the available settings and their defaults see `config/config.php.template`. To -override a setting copy `config/config.php.template` to `config/config.php`, +override a setting, copy `config/config.php.template` to `config/config.php`, uncomment the setting and set your custom value. ### Documentation From b46ed0535c15e2f2fe126f04b9e10eadfbddc34e Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 9 Oct 2015 14:27:49 +0200 Subject: [PATCH 062/118] Let users know about Twigs link filter --- content-sample/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content-sample/index.md b/content-sample/index.md index dd2fd2b..d5a48e1 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -142,7 +142,10 @@ to use in your theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs * `{{ config }}` - Conatins the values you set in `config/config.php` (e.g. `{{ config.theme }}` becomes `default`) * `{{ base_dir }}` - The path to your Pico root directory -* `{{ base_url }}` - The URL to your Pico site +* `{{ base_url }}` - The URL to your Pico site; use Twigs `link` filter to + specify internal links (e.g. `{{ "sub/page"|link }}`), + this guarantees that your link works whether URL rewriting + is enabled or not * `{{ theme_dir }}` - The path to the currently active theme * `{{ theme_url }}` - The URL to the currently active theme * `{{ rewrite_url }}` - A boolean flag indicating enabled/disabled URL rewriting From 3336fb717e0b99a27f63d85b53c92fdf216ea288 Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:08:02 -0600 Subject: [PATCH 063/118] fix spelling --- config/config.php.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.php.template b/config/config.php.template index cea5967..69cd7b1 100644 --- a/config/config.php.template +++ b/config/config.php.template @@ -7,7 +7,7 @@ * {@path "lib/Pico.php"}). * * To override any of the default settings below, copy this file to - * {@path "config/config.php"}, uncomment the line and make and + * {@path "config/config.php"}, uncomment the line, then make and * save your changes. * * @author Gilbert Pellegrom @@ -31,7 +31,7 @@ $config = array(); // $config['theme'] = 'default'; // Set the theme (defaults to "default") // $config['twig_config'] = array( // Twig settings // 'cache' => false, // To enable Twig caching change this to a path to a writable directory -// 'autoescape' => false, // Autoescape Twig vars +// 'autoescape' => false, // Auto-escape Twig vars // 'debug' => false // Enable Twig debug // ); From fe83d1fa7f99350910c0562409ab49ad8b29bc82 Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:11:21 -0600 Subject: [PATCH 064/118] fix spelling --- lib/AbstractPicoPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 804a6be..ef96838 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -39,7 +39,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface protected $statusChanged = false; /** - * List of plugins this plugin depends on + * List of plugins which this plugin depends on * * @var array * @see PicoPluginInterface::getDependencies() @@ -148,7 +148,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface } /** - * Enables all plugins on which this plugin depends + * Enables all plugins which this plugin depends on * * @param boolean $recursive enable required plugins automatically * @return void From 01122f2901cba3b9608ca9e0e2050cd2593737ef Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:19:18 -0600 Subject: [PATCH 065/118] fix spelling --- lib/Pico.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 5070471..681cc34 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -504,13 +504,13 @@ class Pico * * Pico 0.9 and older required Apache with mod_rewrite enabled, thus old * plugins, templates and contents may require you to enable URL rewriting - * to work. If you're upgrading from Pico 0.9, you probably have to update - * your rewriting rules. + * to work. If you're upgrading from Pico 0.9, you will probably have to + * update your rewriting rules. * * We recommend you to use the `link` filter in templates to create * internal links, e.g. `{{ "sub/page"|link }}` is equivalent to * `{{ base_url }}sub/page`. In content files you can still use the - * `%base_url%` variable; e.g. `%base_url%?sub/page` is automatically + * `%base_url%` variable; e.g. `%base_url%?sub/page` will be automatically * replaced accordingly. * * @return void @@ -531,7 +531,7 @@ class Pico } /** - * Returns the URL with which the user requested the page + * Returns the URL where a user requested the page * * @see Pico::evaluateRequestUrl() * @return string request URL From 8380b54142f6436c6b87169f90c9a2393d1bea90 Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:35:15 -0600 Subject: [PATCH 066/118] fix spelling --- plugins/DummyPlugin.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index bf1d538..b9441b8 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -14,7 +14,7 @@ class DummyPlugin extends AbstractPicoPlugin { /** - * This plugin is disabled by default + * This plugin is enabled by default? * * @see AbstractPicoPlugin::$enabled */ @@ -28,7 +28,7 @@ class DummyPlugin extends AbstractPicoPlugin protected $dependsOn = array(); /** - * Triggered after Pico loaded all available plugins + * Triggered after has Pico loaded all available plugins * * This event is triggered nevertheless the plugin is enabled or not. * It is NOT guaranteed that plugin dependencies are fulfilled! @@ -44,7 +44,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico read its configuration + * Triggered after Pico has read its configuration * * @see Pico::getConfig() * @param array &$config array of config variables @@ -56,7 +56,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico evaluated the request URL + * Triggered after Pico has evaluated the request URL * * @see Pico::getBaseUrl() * @see Pico::getRequestUrl() @@ -69,7 +69,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico discovered the content file to serve + * Triggered after Pico has discovered the content file to serve * * @see Pico::getRequestFile() * @param string &$file path to the content file to serve @@ -93,7 +93,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico read the contents of the file to serve + * Triggered after Pico has read the contents of the file to serve * * @see Pico::getRawContent() * @param string &$rawContent raw file contents @@ -117,7 +117,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico read the contents of the 404 file + * Triggered after Pico has read the contents of the 404 file * * @see Pico::getRawContent() * @param string &$rawContent raw file contents @@ -129,7 +129,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered when Pico reads its known meta header fields + * Triggered when Pico reads its known meta/header fields * * @see Pico::getMetaHeaders() * @param array &$headers list of known meta header fields @@ -154,7 +154,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico parsed the meta header + * Triggered after Pico has parsed the meta header * * @see Pico::getFileMeta() * @param array &$meta parsed meta data @@ -178,7 +178,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico prepared the raw file contents for parsing + * Triggered after Pico has prepared the raw file contents for parsing * * @see Pico::parseFileContent() * @param string &$content prepared file contents for parsing @@ -190,7 +190,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico parsed the contents of the file to serve + * Triggered after Pico has parsed the contents of the file to serve * * @see Pico::getFileContent() * @param string &$content parsed contents @@ -202,7 +202,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered when Pico reads a single page for the list of all known pages + * Triggered when Pico reads a single page from the list of all known pages * * @param array &$pageData data of the loaded page * @return void @@ -213,7 +213,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico read all known pages + * Triggered after Pico has read all known pages * * @see Pico::getPages() * @see Pico::getCurrentPage() @@ -255,7 +255,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered after Pico rendered the page + * Triggered after Pico has rendered the page * * @param string &$output contents which will be sent to the user * @return void From 132399f3059e7a0e1b51f10cabad2f520ee458f3 Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:39:56 -0600 Subject: [PATCH 067/118] add comments/in-line docs --- index.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/index.php b/index.php index 02f9444..40b811a 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,17 @@ setConfig(array()); + +// run application echo $pico->run(); From aec024a7930be7c1a017467b43a9a6996f5bb5ef Mon Sep 17 00:00:00 2001 From: theshka Date: Fri, 9 Oct 2015 09:49:07 -0600 Subject: [PATCH 068/118] fix spelling --- content-sample/index.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index d5a48e1..1918b81 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -10,7 +10,7 @@ Congratulations, you have successfully installed [Pico](http://picocms.org/). ### Creating Content -Pico is a flat file CMS, this means there is no administration backend and +Pico is a flat file CMS, this means there is no administration backend or database to deal with. You simply create `.md` files in the `content-sample` folder and that becomes a page. For example, this file is called `index.md` and is shown as the main landing page. @@ -71,6 +71,7 @@ attributes of the page. For example: Author: Joe Bloggs Date: 2013/01/01 Robots: noindex,nofollow + Template: index --- These values will be contained in the `{{ meta }}` variable in themes @@ -82,13 +83,13 @@ There are also certain variables that you can use in your text files: * %base_url% - The URL to your Pico site; internal links can be specified using %base_url%?sub/page * %theme_url% - The URL to the currently used theme -* %meta.*% - Access any meta variable of the current page, +* %meta.* % - Access any meta variable of the current page, e.g. %meta.author% is replaced with `Joe Bloggs` ### Blogging -Pico is no blogging software - but makes it very easy for you to use it as a -blogging software. You can find many plugins out there implementing typical +Pico is not blogging software - but makes it very easy for you to use it as a +blog. You can find many plugins out there implementing typical blogging features like authentication, tagging, pagination and social plugins. See the below Plugins section for details. From 15515ff3fdb2ad62307bd3db17a258b2f1f6fe7e Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 9 Oct 2015 20:51:07 +0200 Subject: [PATCH 069/118] Fix typo/formatting --- content-sample/index.md | 8 ++++---- plugins/DummyPlugin.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index 1918b81..3718b58 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -83,15 +83,15 @@ There are also certain variables that you can use in your text files: * %base_url% - The URL to your Pico site; internal links can be specified using %base_url%?sub/page * %theme_url% - The URL to the currently used theme -* %meta.* % - Access any meta variable of the current page, +* %meta.*% - Access any meta variable of the current page, e.g. %meta.author% is replaced with `Joe Bloggs` ### Blogging Pico is not blogging software - but makes it very easy for you to use it as a -blog. You can find many plugins out there implementing typical -blogging features like authentication, tagging, pagination and social plugins. -See the below Plugins section for details. +blog. You can find many plugins out there implementing typical blogging +features like authentication, tagging, pagination and social plugins. See the +below Plugins section for details. If you want to use Pico as a blogging software, you probably want to do something like the following: diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index b9441b8..30771b1 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -28,7 +28,7 @@ class DummyPlugin extends AbstractPicoPlugin protected $dependsOn = array(); /** - * Triggered after has Pico loaded all available plugins + * Triggered after Pico has loaded all available plugins * * This event is triggered nevertheless the plugin is enabled or not. * It is NOT guaranteed that plugin dependencies are fulfilled! @@ -129,7 +129,7 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered when Pico reads its known meta/header fields + * Triggered when Pico reads its known meta header fields * * @see Pico::getMetaHeaders() * @param array &$headers list of known meta header fields From 9cdd34edcc96c5a5fd8bbd7c21f50bafe316b6c2 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 10 Oct 2015 00:08:38 +0200 Subject: [PATCH 070/118] Change content order, add Customization section --- content-sample/index.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index 3718b58..fec4430 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -8,7 +8,7 @@ Description: Pico is a stupidly simple, blazing fast, flat file CMS. Congratulations, you have successfully installed [Pico](http://picocms.org/). %meta.description% -### Creating Content +## Creating Content Pico is a flat file CMS, this means there is no administration backend or database to deal with. You simply create `.md` files in the `content-sample` @@ -128,6 +128,18 @@ something like the following: achieve this by adding `{% if not page starts with "blog/" %}...{% endif %}` to the navigation loop. +## Customization + +Pico is highly customizable in two different ways: On the one hand you can +change Picos apperance by using themes, on the other hand you can add new +functionality by using plugins. Doing the former includes changing Picos HTML, +CSS and JavaScript, the latter mostly consists of PHP programming. + +This is all Greek to you? Don't worry, you don't have to spend time on these +techie talk - it's very easy to use one of the great themes or plugins others +developed and released to the public. Please refer to the next sections for +details. + ### Themes You can create themes for your Pico installation in the `themes` folder. Check @@ -247,7 +259,7 @@ setting `$config['rewrite_url'] = true;` in your `config/config.php`. try_files $uri $uri/ /pico/?$uri&$args; } -### Config +## Config You can override the default Pico settings (and add your own custom settings) by editing `config/config.php` in the Pico directory. For a brief overview of @@ -255,7 +267,7 @@ the available settings and their defaults see `config/config.php.template`. To override a setting, copy `config/config.php.template` to `config/config.php`, uncomment the setting and set your custom value. -### Documentation +## Documentation For more help have a look at the Pico documentation at [http://picocms.org/docs](http://picocms.org/docs) From 4171f9a0312e23353f2f1c61dd90e0c9c8961512 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 10 Oct 2015 00:11:56 +0200 Subject: [PATCH 071/118] Move URL Rewriting to Config section --- content-sample/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index fec4430..b76d749 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -236,6 +236,14 @@ developed a plugin for Pico 0.9 and older, you probably want to upgrade it to the brand new plugin system introduced with Pico 1.0. Please refer to the [Upgrade section of the docs](http://picocms.org/plugin-dev.html#upgrade). +## Config + +You can override the default Pico settings (and add your own custom settings) +by editing `config/config.php` in the Pico directory. For a brief overview of +the available settings and their defaults see `config/config.php.template`. To +override a setting, copy `config/config.php.template` to `config/config.php`, +uncomment the setting and set your custom value. + ### URL Rewriting Picos default URLs (e.g. %base_url%/?sub/page) already are very user friendly. @@ -259,14 +267,6 @@ setting `$config['rewrite_url'] = true;` in your `config/config.php`. try_files $uri $uri/ /pico/?$uri&$args; } -## Config - -You can override the default Pico settings (and add your own custom settings) -by editing `config/config.php` in the Pico directory. For a brief overview of -the available settings and their defaults see `config/config.php.template`. To -override a setting, copy `config/config.php.template` to `config/config.php`, -uncomment the setting and set your custom value. - ## Documentation For more help have a look at the Pico documentation at From 92af554d148aa6fe48c02b7a40d5671e941cbd64 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 27 Oct 2015 01:39:28 +0100 Subject: [PATCH 072/118] Improve inline code comments; preparing use of phpDocumentor --- lib/AbstractPicoPlugin.php | 2 +- lib/Pico.php | 94 ++++++++++++++++++++++------------- plugins/00-PicoDeprecated.php | 7 +-- plugins/DummyPlugin.php | 42 ++++++++++++---- 4 files changed, 95 insertions(+), 50 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index ef96838..cb19d44 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -50,7 +50,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * List of plugin which depend on this plugin * - * @var array + * @var array * @see PicoPluginInterface::getDependants() * @see AbstractPicoPlugin::checkDependants() */ diff --git a/lib/Pico.php b/lib/Pico.php index 681cc34..ee15044 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -89,7 +89,7 @@ class Pico * List of loaded plugins * * @see Pico::loadPlugins() - * @var array + * @var array|null */ protected $plugins; @@ -97,23 +97,23 @@ class Pico * Current configuration of this Pico instance * * @see Pico::loadConfig() - * @var array + * @var array|null */ protected $config; /** - * URL with which the user requested the page + * Part of the URL describing the requested contents * * @see Pico::evaluateRequestUrl() - * @var string + * @var string|null */ protected $requestUrl; /** - * Path to the content file being served + * Absolute path to the content file being served * * @see Pico::discoverRequestFile() - * @var string + * @var string|null */ protected $requestFile; @@ -121,7 +121,7 @@ class Pico * Raw, not yet parsed contents to serve * * @see Pico::loadFileContent() - * @var string + * @var string|null */ protected $rawContent; @@ -129,7 +129,7 @@ class Pico * Meta data of the page to serve * * @see Pico::parseFileMeta() - * @var array + * @var array|null */ protected $meta; @@ -138,7 +138,7 @@ class Pico * * @see Pico::prepareFileContent() * @see Pico::parseFileContent() - * @var string + * @var string|null */ protected $content; @@ -146,7 +146,7 @@ class Pico * List of known pages * * @see Pico::readPages() - * @var array + * @var array|null */ protected $pages; @@ -154,7 +154,7 @@ class Pico * Data of the page being served * * @see Pico::discoverCurrentPage() - * @var array + * @var array|null */ protected $currentPage; @@ -162,7 +162,7 @@ class Pico * Data of the previous page relative to the page being served * * @see Pico::discoverCurrentPage() - * @var array + * @var array|null */ protected $previousPage; @@ -170,7 +170,7 @@ class Pico * Data of the next page relative to the page being served * * @see Pico::discoverCurrentPage() - * @var array + * @var array|null */ protected $nextPage; @@ -178,14 +178,14 @@ class Pico * Twig instance used for template parsing * * @see Pico::registerTwig() - * @var Twig_Environment + * @var Twig_Environment|null */ protected $twig; /** * Variables passed to the twig template * - * @var array + * @var array|null */ protected $twigVariables; @@ -193,6 +193,11 @@ class Pico * Constructs a new Pico instance * * To carry out all the processing in Pico, call the run() method. + * + * @param string $rootDir root directory of this Pico instance + * @param string $configDir config directory of this Pico instance + * @param string $pluginsDir plugins directory of this Pico instance + * @param string $themesDir themes directory of this Pico instance */ public function __construct($rootDir, $configDir, $pluginsDir, $themesDir) { @@ -398,7 +403,7 @@ class Pico * Returns all loaded plugins * * @see Pico::loadPlugins() - * @return array + * @return array|null */ public function getPlugins() { @@ -458,10 +463,11 @@ class Pico * call and Pico::run(). Options set with this method cannot be overwritten * by {@path "config/config.php"}. * - * @param array $config array with configuration variables, like - * $config in {@path "config/config.php"} + * @param array $config array with configuration variables, + * like $config in {@path "config/config.php"} * @return void - * @throws RuntimeException thrown if Pico already started processing + * @throws RuntimeException thrown if Pico already started + * processing */ public function setConfig(array $config) { @@ -534,7 +540,7 @@ class Pico * Returns the URL where a user requested the page * * @see Pico::evaluateRequestUrl() - * @return string request URL + * @return string|null request URL */ public function getRequestUrl() { @@ -567,10 +573,10 @@ class Pico } /** - * Returns the path to the content file to serve + * Returns the absolute path to the content file to serve * * @see Pico::discoverRequestFile() - * @return string file path + * @return string|null file path */ public function getRequestFile() { @@ -616,7 +622,7 @@ class Pico * Returns the cached raw contents, either of the requested or the 404 file * * @see Pico::loadFileContent() - * @return string raw contents + * @return string|null raw contents */ public function getRawContent() { @@ -629,7 +635,9 @@ class Pico * Heads up! Calling this method triggers the `onMetaHeaders` event. * Keep this in mind to prevent a infinite loop! * - * @return array known meta headers + * @return array known meta headers; the array value + * specifies the YAML key to search for, the array key is later used + * to access the found value */ public function getMetaHeaders() { @@ -657,9 +665,9 @@ class Pico * for users and pure (!) theme developers ONLY. * * @see - * @param string $rawContent the raw file contents - * @param array $headers a array containing the known headers - * @return array parsed meta data + * @param string $rawContent the raw file contents + * @param array $headers known meta headers + * @return array parsed meta data */ public function parseFileMeta($rawContent, array $headers) { @@ -707,7 +715,7 @@ class Pico * Returns the parsed meta data of the requested page * * @see Pico::parseFileMeta() - * @return array parsed meta data + * @return array|null parsed meta data */ public function getFileMeta() { @@ -772,7 +780,7 @@ class Pico * Returns the cached contents of the requested page * * @see Pico::parseFileContent() - * @return string parsed contents + * @return string|null parsed contents */ public function getFileContent() { @@ -782,6 +790,22 @@ class Pico /** * Reads the data of all pages known to Pico * + * The page data will be an array containing the following values: + * +----------------+------------------------------------------+ + * | Array key | Description | + * +----------------+------------------------------------------+ + * | id | relative path to the content file | + * | url | URL to the page | + * | title | title of the page (YAML header) | + * | description | description of the page (YAML header) | + * | author | author of the page (YAML header) | + * | time | timestamp derived from the Date header | + * | date | date of the page (YAML header) | + * | date_formatted | formatted date of the page | + * | raw_content | raw, not yet parsed contents of the page | + * | meta | parsed meta data of the page) | + * +----------------+------------------------------------------+ + * * @return void */ protected function readPages() @@ -884,7 +908,7 @@ class Pico * Returns the list of known pages * * @see Pico::readPages() - * @return array the data of all pages + * @return array|null the data of all pages */ public function getPages() { @@ -932,7 +956,7 @@ class Pico * Returns the data of the requested page * * @see Pico::discoverCurrentPage() - * @return array page data + * @return array|null page data */ public function getCurrentPage() { @@ -943,7 +967,7 @@ class Pico * Returns the data of the previous page relative to the page being served * * @see Pico::discoverCurrentPage() - * @return array page data + * @return array|null page data */ public function getPreviousPage() { @@ -954,7 +978,7 @@ class Pico * Returns the data of the next page relative to the page being served * * @see Pico::discoverCurrentPage() - * @return array page data + * @return array|null page data */ public function getNextPage() { @@ -977,7 +1001,7 @@ class Pico /** * Returns the twig template engine * - * @return Twig_Environment twig template engine + * @return Twig_Environment|null twig template engine */ public function getTwig() { @@ -990,7 +1014,7 @@ class Pico * URLs and paths (namely base_dir, base_url, theme_dir and theme_url) * don't add a trailing slash for historic reasons. * - * @return array template variables + * @return array template variables */ protected function getTwigVariables() { diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index edf05ea..f448abe 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -54,7 +54,7 @@ class PicoDeprecated extends AbstractPicoPlugin /** * The requested file * - * @var string + * @var string|null * @see PicoDeprecated::onRequestFile() */ protected $requestFile; @@ -139,7 +139,7 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Read {@path "config.php"} in Picos root dir * - * @param array &$config array of config variables + * @param array &$config array of config variables * @return void */ protected function loadRootDirConfig(&$config) @@ -295,7 +295,8 @@ class PicoDeprecated extends AbstractPicoPlugin } /** - * Triggers the deprecated event get_pages($pages, $currentPage, $previousPage, $nextPage) + * Triggers the deprecated event + * get_pages($pages, $currentPage, $previousPage, $nextPage) * * @see DummyPlugin::onPagesLoaded() */ diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 30771b1..588721d 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -16,6 +16,7 @@ class DummyPlugin extends AbstractPicoPlugin /** * This plugin is enabled by default? * + * @var boolean * @see AbstractPicoPlugin::$enabled */ protected $enabled = false; @@ -23,6 +24,7 @@ class DummyPlugin extends AbstractPicoPlugin /** * This plugin depends on {@link ...} * + * @var array * @see AbstractPicoPlugin::$dependsOn */ protected $dependsOn = array(); @@ -47,7 +49,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has read its configuration * * @see Pico::getConfig() - * @param array &$config array of config variables + * @param array &$config array of config variables * @return void */ public function onConfigLoaded(&$config) @@ -60,7 +62,7 @@ class DummyPlugin extends AbstractPicoPlugin * * @see Pico::getBaseUrl() * @see Pico::getRequestUrl() - * @param string &$url request URL + * @param string &$url part of the URL describing the requested contents * @return void */ public function onRequestUrl(&$url) @@ -72,7 +74,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has discovered the content file to serve * * @see Pico::getRequestFile() - * @param string &$file path to the content file to serve + * @param string &$file absolute path to the content file to serve * @return void */ public function onRequestFile(&$file) @@ -132,7 +134,9 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered when Pico reads its known meta header fields * * @see Pico::getMetaHeaders() - * @param array &$headers list of known meta header fields + * @param array &$headers list of known meta header + * fields; the array value specifies the YAML key to search for, the + * array key is later used to access the found value * @return void */ public function onMetaHeaders(&$headers) @@ -144,8 +148,8 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico parses the meta header * * @see Pico::parseFileMeta() - * @param string &$rawContent raw file contents - * @param array &$headers known meta header fields + * @param string &$rawContent raw file contents + * @param array &$headers known meta header fields * @return void */ public function onMetaParsing(&$rawContent, &$headers) @@ -157,7 +161,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has parsed the meta header * * @see Pico::getFileMeta() - * @param array &$meta parsed meta data + * @param array &$meta parsed meta data * @return void */ public function onMetaParsed(&$meta) @@ -204,7 +208,20 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered when Pico reads a single page from the list of all known pages * - * @param array &$pageData data of the loaded page + * @param array &$pageData { + * data of the loaded page + * + * @var string $id relative path to the content file + * @var string $url URL to the page + * @var string $title title of the page (YAML header) + * @var string $description description of the page (YAML header) + * @var string $author author of the page (YAML header) + * @var string $time timestamp derived from the Date header + * @var string $date date of the page (YAML header) + * @var string $date_formatted formatted date of the page + * @var string $raw_content raw, not yet parsed contents of the page + * @var string $meta parsed meta data of the page + * } * @return void */ public function onSinglePageLoaded(&$pageData) @@ -215,6 +232,9 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered after Pico has read all known pages * + * See {@link DummyPlugin::onSinglePageLoaded()} for details about the + * structure of the page data. + * * @see Pico::getPages() * @see Pico::getCurrentPage() * @see Pico::getPreviousPage() @@ -244,9 +264,9 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico renders the page * * @see Pico::getTwig() - * @param Twig_Environment &$twig twig template engine - * @param array &$twigVariables variables passed to the template - * @param string &$templateName name of the template to render + * @param Twig_Environment &$twig twig template engine + * @param array &$twigVariables template variables + * @param string &$templateName file name of the template * @return void */ public function onPageRendering(&$twig, &$twigVariables, &$templateName) From a654b1585bf7b20a508aee8c54a55d104f7a0c52 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Tue, 27 Oct 2015 01:48:58 +0100 Subject: [PATCH 073/118] phpDocumentor 2.8.5 currently doesn't support the Generic notations This will likely be implemented as soon as the proposed PSR-5: PHPDoc is accepted --- lib/AbstractPicoPlugin.php | 4 ++-- lib/Pico.php | 33 ++++++++++++++++----------------- lib/PicoPluginInterface.php | 4 ++-- plugins/00-PicoDeprecated.php | 2 +- plugins/DummyPlugin.php | 20 ++++++++++---------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index cb19d44..63c36b0 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -41,7 +41,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * List of plugins which this plugin depends on * - * @var array + * @var string[] * @see PicoPluginInterface::getDependencies() * @see AbstractPicoPlugin::checkDependencies() */ @@ -50,7 +50,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * List of plugin which depend on this plugin * - * @var array + * @var object[] * @see PicoPluginInterface::getDependants() * @see AbstractPicoPlugin::checkDependants() */ diff --git a/lib/Pico.php b/lib/Pico.php index ee15044..0a83929 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -89,7 +89,7 @@ class Pico * List of loaded plugins * * @see Pico::loadPlugins() - * @var array|null + * @var object[]|null */ protected $plugins; @@ -97,7 +97,7 @@ class Pico * Current configuration of this Pico instance * * @see Pico::loadConfig() - * @var array|null + * @var mixed[]|null */ protected $config; @@ -129,7 +129,7 @@ class Pico * Meta data of the page to serve * * @see Pico::parseFileMeta() - * @var array|null + * @var string[]|null */ protected $meta; @@ -146,7 +146,7 @@ class Pico * List of known pages * * @see Pico::readPages() - * @var array|null + * @var array[]|null */ protected $pages; @@ -185,7 +185,7 @@ class Pico /** * Variables passed to the twig template * - * @var array|null + * @var mixed[]|null */ protected $twigVariables; @@ -403,7 +403,7 @@ class Pico * Returns all loaded plugins * * @see Pico::loadPlugins() - * @return array|null + * @return object[]|null */ public function getPlugins() { @@ -463,11 +463,10 @@ class Pico * call and Pico::run(). Options set with this method cannot be overwritten * by {@path "config/config.php"}. * - * @param array $config array with configuration variables, - * like $config in {@path "config/config.php"} + * @param mixed[] $config array with configuration variables, like + * $config in {@path "config/config.php"} * @return void - * @throws RuntimeException thrown if Pico already started - * processing + * @throws RuntimeException thrown if Pico already started processing */ public function setConfig(array $config) { @@ -635,9 +634,9 @@ class Pico * Heads up! Calling this method triggers the `onMetaHeaders` event. * Keep this in mind to prevent a infinite loop! * - * @return array known meta headers; the array value - * specifies the YAML key to search for, the array key is later used - * to access the found value + * @return string[] known meta headers; the array value specifies the + * YAML key to search for, the array key is later used to access the + * found value */ public function getMetaHeaders() { @@ -665,9 +664,9 @@ class Pico * for users and pure (!) theme developers ONLY. * * @see - * @param string $rawContent the raw file contents - * @param array $headers known meta headers - * @return array parsed meta data + * @param string $rawContent the raw file contents + * @param string[] $headers known meta headers + * @return array parsed meta data */ public function parseFileMeta($rawContent, array $headers) { @@ -1014,7 +1013,7 @@ class Pico * URLs and paths (namely base_dir, base_url, theme_dir and theme_url) * don't add a trailing slash for historic reasons. * - * @return array template variables + * @return mixed[] template variables */ protected function getTwigVariables() { diff --git a/lib/PicoPluginInterface.php b/lib/PicoPluginInterface.php index a4dc5e1..081b280 100644 --- a/lib/PicoPluginInterface.php +++ b/lib/PicoPluginInterface.php @@ -77,14 +77,14 @@ interface PicoPluginInterface /** * Returns a list of names of plugins required by this plugin * - * @return array required plugins + * @return string[] required plugins */ public function getDependencies(); /** * Returns a list of plugins which depend on this plugin * - * @return array dependant plugins + * @return object[] dependant plugins */ public function getDependants(); diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index f448abe..076a726 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -139,7 +139,7 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Read {@path "config.php"} in Picos root dir * - * @param array &$config array of config variables + * @param mixed[] &$config array of config variables * @return void */ protected function loadRootDirConfig(&$config) diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 588721d..059b188 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -24,7 +24,7 @@ class DummyPlugin extends AbstractPicoPlugin /** * This plugin depends on {@link ...} * - * @var array + * @var string[] * @see AbstractPicoPlugin::$dependsOn */ protected $dependsOn = array(); @@ -37,7 +37,7 @@ class DummyPlugin extends AbstractPicoPlugin * * @see Pico::getPlugin() * @see Pico::getPlugins() - * @param array &$plugins loaded plugin instances + * @param object[] &$plugins loaded plugin instances * @return void */ public function onPluginsLoaded(&$plugins) @@ -49,7 +49,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has read its configuration * * @see Pico::getConfig() - * @param array &$config array of config variables + * @param mixed[] &$config array of config variables * @return void */ public function onConfigLoaded(&$config) @@ -134,7 +134,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered when Pico reads its known meta header fields * * @see Pico::getMetaHeaders() - * @param array &$headers list of known meta header + * @param string[] &$headers list of known meta header * fields; the array value specifies the YAML key to search for, the * array key is later used to access the found value * @return void @@ -148,8 +148,8 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico parses the meta header * * @see Pico::parseFileMeta() - * @param string &$rawContent raw file contents - * @param array &$headers known meta header fields + * @param string &$rawContent raw file contents + * @param string[] &$headers known meta header fields * @return void */ public function onMetaParsing(&$rawContent, &$headers) @@ -161,7 +161,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has parsed the meta header * * @see Pico::getFileMeta() - * @param array &$meta parsed meta data + * @param string[] &$meta parsed meta data * @return void */ public function onMetaParsed(&$meta) @@ -264,9 +264,9 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico renders the page * * @see Pico::getTwig() - * @param Twig_Environment &$twig twig template engine - * @param array &$twigVariables template variables - * @param string &$templateName file name of the template + * @param Twig_Environment &$twig twig template engine + * @param mixed[] &$twigVariables template variables + * @param string &$templateName file name of the template * @return void */ public function onPageRendering(&$twig, &$twigVariables, &$templateName) From de6b3a7c280544bc56b68efe6b96b54d7e2ba651 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 01:08:45 +0100 Subject: [PATCH 074/118] Fix Markdown %meta.*% replacement Don't even try to use arrays here... --- lib/Pico.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index 0a83929..85e3f0a 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -754,10 +754,13 @@ class Pico $content = str_replace('%theme_url%', $themeUrl, $content); // replace %meta.*% - $metaKeys = array_map(function ($metaKey) { - return '%meta.' . $metaKey . '%'; - }, array_keys($this->meta)); - $metaValues = array_values($this->meta); + $metaKeys = $metaValues = array(); + foreach ($this->meta as $metaKey => $metaValue) { + if (is_scalar($metaValue) || ($metaValue === null)) { + $metaKeys[] = '%meta.' . $metaKey . '%'; + $metaValues[] = strval($metaValue); + } + } $content = str_replace($metaKeys, $metaValues, $content); return $content; From 9e2604af85a90267186c19d121206e1cc25be1d5 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 01:36:28 +0100 Subject: [PATCH 075/118] Prevent content_dir breakouts using malicious request URLs It's appalling that nobody (including me!) thought about that! --- lib/Pico.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/Pico.php b/lib/Pico.php index 85e3f0a..4d78507 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -556,7 +556,28 @@ class Pico if (empty($this->requestUrl)) { $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); } else { - $this->requestFile = $this->getConfig('content_dir') . $this->requestUrl; + // prevent content_dir breakouts using malicious request URLs + // we don't use realpath() here because we neither want to check for file existance + // nor prohibit symlinks which intentionally point to somewhere outside the content_dir + // it is STRONGLY RECOMMENDED to use open_basedir - always, not just with Pico! + $requestUrl = str_replace('\\', '/', $this->requestUrl); + $requestUrlParts = explode('/', $requestUrl); + + $requestFileParts = array(); + foreach ($requestUrlParts as $requestUrlPart) { + if (($requestUrlPart === '') || ($requestUrlPart === '.')) { + continue; + } elseif ($requestUrlPart === '..') { + array_pop($requestFileParts); + continue; + } + + $requestFileParts[] = $requestUrlPart; + } + + // discover the content file to serve + // Note: $requestFileParts neither contains a trailing nor a leading slash + $this->requestFile = $this->getConfig('content_dir') . implode('/', $requestFileParts); if (is_dir($this->requestFile)) { // if no index file is found, try a accordingly named file in the previous dir // if this file doesn't exist either, show the 404 page, but assume the index From 647a7b5bb77b0d6e4d6432dc0309fd57c795574a Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 01:41:26 +0100 Subject: [PATCH 076/118] Trap empty $requestFileParts --- lib/Pico.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Pico.php b/lib/Pico.php index 4d78507..827c89b 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -575,6 +575,11 @@ class Pico $requestFileParts[] = $requestUrlPart; } + if (empty($requestFileParts)) { + $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); + return; + } + // discover the content file to serve // Note: $requestFileParts neither contains a trailing nor a leading slash $this->requestFile = $this->getConfig('content_dir') . implode('/', $requestFileParts); From 3e0161b51ad0a7bf14cff4c66a3ede56ada9ab87 Mon Sep 17 00:00:00 2001 From: "David \"Paztek\" Moreau" Date: Tue, 27 Oct 2015 22:20:37 -0400 Subject: [PATCH 077/118] Create .travis.yml --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e6109a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: php +php: + - 5.4 + +script: + - composer install + - tar -czf pico.tar.gz .htaccess README.md changelog.txt composer.json composer.lock config content-sample index.php lib license.txt plugins themes vendor + +deploy: + provider: releases + api_key: ${GITHUB_OAUTH_TOKEN} + file: pico.tar.gz + skip_cleanup: true + on: + tags: true From d3a1308556f22a60cd14d1151b88365b624cd836 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 12:53:56 +0100 Subject: [PATCH 078/118] Update .travis.yml Just adding some features inspired by other projects using Travis, e.g. a simple PHP syntax checker with various PHP versions. A short peak into @dav-m85 link leads me to think that running composer and creating the archive should be done with before_deploy rather than script. --- .travis.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e6109a6..55a2a7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,19 @@ language: php php: + - 5.3 - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + - nightly script: + - find . -type f -name '*.php' -print0 | xargs -0 -I file php -l file > /dev/null + +before_deploy: - composer install - - tar -czf pico.tar.gz .htaccess README.md changelog.txt composer.json composer.lock config content-sample index.php lib license.txt plugins themes vendor + - tar -czf pico.tar.gz .htaccess README.md changelog.txt composer.json composer.lock license.txt config content-sample lib plugins themes vendor index.php deploy: provider: releases @@ -13,3 +22,4 @@ deploy: skip_cleanup: true on: tags: true + php: 5.3 From efcbbb8ce0c628b408eacd88f8fbbf7215b343c7 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 14:38:02 +0100 Subject: [PATCH 079/118] Update .travis.yml Build on picocms/Pico only --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 55a2a7a..a2d5f55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,5 +21,7 @@ deploy: file: pico.tar.gz skip_cleanup: true on: + repo: picocms/Pico tags: true php: 5.3 + From a068a1f9d759ae3fdd86e06a89bdad4362d57849 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 15:11:34 +0100 Subject: [PATCH 080/118] Update changelog.txt - Add security section - Add Travis CI --- changelog.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4aebd43..aa2276d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,9 +1,10 @@ *** Pico Changelog *** -2015.10.XX - version 1.0-beta +2015.11.XX - version 1.0.0-beta.1 * NOTE: This changelog only provides basic information about the enormous - changes introduced with Pico 1.0-beta. Please refer to the UPGRADE - section of the docs for details. + changes introduced with Pico 1.0.0-beta.1. Please refer to the + UGPRADE section of the docs for details. + * [Security] (9e2604a) Prevent content_dir breakouts using malicious URLs * [New] Pico is on its way to its first stable release! * [New] Provide pre-bundled releases * [New] Heavily expanded documentation (inline code docs, user docs, dev docs) @@ -26,6 +27,8 @@ * [New] #103: Providing access to `sub.md` even when the `sub` directory exists, provided that there is no `sub/index.md` * [New] #249: Support the `.twig` file extension for templates + * [New] #268, 269: Now using Travis CI; performing basic code tests and + implementing an automatic release process * [Changed] Complete code refactoring * [Changed] Source code now follows PSR code styling * [Changed] Replacing constants (e.g. `ROOT_DIR`) with constructor parameters From 360e7ab91f1d024f91afd7751d020b62c4ff92ab Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 17:26:43 +0100 Subject: [PATCH 081/118] Update .travis.yml Use $TRAVIS_TAG for the "binary" filename --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2d5f55..85e7693 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,12 @@ script: before_deploy: - composer install - - tar -czf pico.tar.gz .htaccess README.md changelog.txt composer.json composer.lock license.txt config content-sample lib plugins themes vendor index.php + - tar -czf "pico-$TRAVIS_TAG.tar.gz" .htaccess README.md changelog.txt composer.json composer.lock license.txt config content-sample lib plugins themes vendor index.php deploy: provider: releases api_key: ${GITHUB_OAUTH_TOKEN} - file: pico.tar.gz + file: pico-$TRAVIS_TAG.tar.gz skip_cleanup: true on: repo: picocms/Pico From 43f9590d5e028394e1aa096d66c59a9c15f33705 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Oct 2015 17:34:10 +0100 Subject: [PATCH 082/118] Update .travis.yml Use Travis container-based infrastructure --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 85e7693..8ff8b12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,3 +25,5 @@ deploy: tags: true php: 5.3 +sudo: false + From a068850578cb969d8e747b893518142304359fc3 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 00:37:59 +0100 Subject: [PATCH 083/118] Update .travis.yml Let Travis build branches master and gh-pages only --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8ff8b12..6862e64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,11 @@ php: - hhvm - nightly +branches: + only: + - master + - gh-pages + script: - find . -type f -name '*.php' -print0 | xargs -0 -I file php -l file > /dev/null From 38081b3d52a823fa1e8f90954a831d91de5907fa Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 00:54:01 +0100 Subject: [PATCH 084/118] Sync docs with website --- content-sample/index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index b76d749..318a23d 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -175,16 +175,16 @@ to use in your theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs * `{{ content }}` - The content of the current page (after it has been processed through Markdown) * `{{ pages }}` - A collection of all the content pages in your site - * `{{ page.id }}` - * `{{ page.url }}` - * `{{ page.title }}` - * `{{ page.description }}` - * `{{ page.author }}` - * `{{ page.time }}` - * `{{ page.date }}` - * `{{ page.date_formatted }}` - * `{{ page.raw_content }}` - * `{{ page.meta }}` + * `{{ page.id }}` - The relative path to the content file + * `{{ page.url }}` - The URL to the page + * `{{ page.title }}` - The title of the page (YAML header) + * `{{ page.description }}` - The description of the page (YAML header) + * `{{ page.author }}` - The author of the page (YAML header) + * `{{ page.time }}` - The timestamp derived from the `Date` header + * `{{ page.date }}` - The date of the page (YAML header) + * `{{ page.date_formatted }}` - The formatted date of the page + * `{{ page.raw_content }}` - The raw, not yet parsed contents of the page + * `{{ page.meta }}`- The meta values of the page * `{{ prev_page }}` - The data of the previous page (relative to `current_page`) * `{{ current_page }}` - The data of the current page * `{{ next_page }}` - The data of the next page (relative to `current_page`) From 638638f5a49af6da3dfc4c10b97a12b04dcf1d30 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 00:57:54 +0100 Subject: [PATCH 085/118] Add UPGRADE section to docs This is still work in progress --- content-sample/index.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/content-sample/index.md b/content-sample/index.md index 318a23d..b451260 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -234,7 +234,7 @@ You're a plugin developer? We love you guys! You can find tons of information about how to develop plugins at http://picocms.org/plugin-dev.html. If you'd developed a plugin for Pico 0.9 and older, you probably want to upgrade it to the brand new plugin system introduced with Pico 1.0. Please refer to the -[Upgrade section of the docs](http://picocms.org/plugin-dev.html#upgrade). +[upgrade section of the docs][PluginUpgrade]. ## Config @@ -267,6 +267,44 @@ setting `$config['rewrite_url'] = true;` in your `config/config.php`. try_files $uri $uri/ /pico/?$uri&$args; } +## Upgrading to Pico 1.0 + +We worked hard to make the upgrade process to Pico 1.0 as easy as possible - +and we think we made the grade. Usually you don't have to consider anything +special, nevertheless you should create a backup of your Pico installation +before upgrading. + +The first step is to delete all of Picos files except for your `content` +directory, `config.php` (or `config/config.php`) and, if applicable, the +directory of your custom theme. Provided that you're using plugins, also keep +the `plugins` directory. You can then upload Pico 1.0 to your installation +directory. Please refer to the websites of the plugins you're using to get +updates for them. + +The new `PicoDeprecated` plugin ensures backward compatibility to Pico 0.9 and +older. The plugin is disabled by default, but gets automatically enabled as +soon as a old plugin is loaded. We will maintain backward compatibility for +a long time, however, we recommend you to take the following steps to confine +the neccessity of `PicoDeprecated` to old plugins. If you don't use plugins or +upgraded all plugins to be compatible to Pico 1.0, you must take these steps. + +If you're a plugin developer, please refer to the new development docs, +particularly the [plugin upgrade section][PluginUpgrade]. + +* Move your `config.php` to the new `config/` directory. +* URL Rewriting became optional in Pico 1.0. If you don't use the `.htaccess` + file provided by Pico, you must update your rewriting rules to let the + webserver rewrite internal links (e.g. `index.php?sub/page`) correctly. + You musn't update your markdown files or custom Twig templates if you keep + URL rewriting enabled. Otherwise you have to rectify all internal links in + markdown files (e.g. %base_url%?sub/page) and your + custom Twig templates (e.g. (e.g. `{{ "sub/page"|link }}`)). +* Pico 1.0 doesn't parse the contents of all pages anymore. This can be put + down to the massive performance impact, but leads to the removal of the + generation of auto-generated excerpts. + TODO: describe how to force enable/disable `PicoExcerpt` and `PicoParsePagesContent` +* TODO: Removing various empty `index.html` files; check accessibility! + ## Documentation For more help have a look at the Pico documentation at @@ -274,3 +312,4 @@ For more help have a look at the Pico documentation at [Twig]: http://twig.sensiolabs.org/documentation [Markdown]: http://daringfireball.net/projects/markdown/syntax +[PluginUpgrade]: http://picocms.org/plugin-dev.html#upgrade From f1fc4c979e67c1d107cc24d91893eb59ea85b87a Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 01:19:43 +0100 Subject: [PATCH 086/118] Revert commit a068850 According to travis-ci/travis-ci#2111 and some own testing, it isn't possible to combine branch whitelists with tag-based auto deployment. Unfortunately it is necessary to whitelist the gh-pages branch, because Travis implicitly blacklists it. --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6862e64..8ff8b12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,6 @@ php: - hhvm - nightly -branches: - only: - - master - - gh-pages - script: - find . -type f -name '*.php' -print0 | xargs -0 -I file php -l file > /dev/null From 54ce5b96994ff82c49a1e2ad1a7ae9fb81e34aad Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 02:55:30 +0100 Subject: [PATCH 087/118] Various small improvements - Improve class docs for phpDocumentor - Add missing onPagesLoading() event to DummyPlugin - Add some TODOs to the UPGRADE section of the docs --- lib/AbstractPicoPlugin.php | 20 +++---- lib/Pico.php | 98 ++++++++++++++++++++++++----------- lib/PicoPluginInterface.php | 9 +++- plugins/00-PicoDeprecated.php | 35 ++++++++++--- plugins/02-PicoExcerpt.php | 6 ++- plugins/DummyPlugin.php | 31 +++++++++-- 6 files changed, 143 insertions(+), 56 deletions(-) diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 63c36b0..55660d2 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -15,44 +15,43 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * Current instance of Pico * - * @var Pico - * @see PicoPluginInterface::__construct() * @see PicoPluginInterface::getPico() + * @var Pico */ private $pico; /** * Boolean indicating if this plugin is enabled (true) or disabled (false) * - * @var boolean * @see PicoPluginInterface::isEnabled() * @see PicoPluginInterface::setEnabled() + * @var boolean */ protected $enabled = true; /** * Boolean indicating if this plugin was ever enabled/disabled manually * - * @var boolean * @see PicoPluginInterface::isStatusChanged() + * @var boolean */ protected $statusChanged = false; /** * List of plugins which this plugin depends on * - * @var string[] - * @see PicoPluginInterface::getDependencies() * @see AbstractPicoPlugin::checkDependencies() + * @see PicoPluginInterface::getDependencies() + * @var string[] */ protected $dependsOn = array(); /** * List of plugin which depend on this plugin * - * @var object[] - * @see PicoPluginInterface::getDependants() * @see AbstractPicoPlugin::checkDependants() + * @see PicoPluginInterface::getDependants() + * @var object[] */ private $dependants; @@ -129,8 +128,9 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface } /** - * Passes all not satisfiable method calls to {@link Pico} + * Passes all not satisfiable method calls to Pico * + * @see Pico * @param string $methodName name of the method to call * @param array $params parameters to pass * @return mixed return value of the called method @@ -150,6 +150,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * Enables all plugins which this plugin depends on * + * @see PicoPluginInterface::getDependencies() * @param boolean $recursive enable required plugins automatically * @return void * @throws RuntimeException thrown when a dependency fails @@ -198,6 +199,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface /** * Disables all plugins which depend on this plugin * + * @see PicoPluginInterface::getDependants() * @param boolean $recursive disabled dependant plugins automatically * @return void * @throws RuntimeException thrown when a dependency fails diff --git a/lib/Pico.php b/lib/Pico.php index 827c89b..4fcb1fc 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -53,6 +53,7 @@ class Pico /** * Root directory of this Pico instance * + * @see Pico::getRootDir() * @var string */ protected $rootDir; @@ -60,6 +61,7 @@ class Pico /** * Config directory of this Pico instance * + * @see Pico::getConfigDir() * @var string */ protected $configDir; @@ -67,6 +69,7 @@ class Pico /** * Plugins directory of this Pico instance * + * @see Pico::getPluginsDir() * @var string */ protected $pluginsDir; @@ -74,6 +77,7 @@ class Pico /** * Themes directory of this Pico instance * + * @see Pico::getThemesDir() * @var string */ protected $themesDir; @@ -88,7 +92,7 @@ class Pico /** * List of loaded plugins * - * @see Pico::loadPlugins() + * @see Pico::getPlugins() * @var object[]|null */ protected $plugins; @@ -96,7 +100,7 @@ class Pico /** * Current configuration of this Pico instance * - * @see Pico::loadConfig() + * @see Pico::getConfig() * @var mixed[]|null */ protected $config; @@ -104,7 +108,7 @@ class Pico /** * Part of the URL describing the requested contents * - * @see Pico::evaluateRequestUrl() + * @see Pico::getRequestUrl() * @var string|null */ protected $requestUrl; @@ -112,7 +116,7 @@ class Pico /** * Absolute path to the content file being served * - * @see Pico::discoverRequestFile() + * @see Pico::getRequestFile() * @var string|null */ protected $requestFile; @@ -120,7 +124,7 @@ class Pico /** * Raw, not yet parsed contents to serve * - * @see Pico::loadFileContent() + * @see Pico::getRawContent() * @var string|null */ protected $rawContent; @@ -128,7 +132,7 @@ class Pico /** * Meta data of the page to serve * - * @see Pico::parseFileMeta() + * @see Pico::getFileMeta() * @var string[]|null */ protected $meta; @@ -136,8 +140,7 @@ class Pico /** * Parsed content being served * - * @see Pico::prepareFileContent() - * @see Pico::parseFileContent() + * @see Pico::getFileContent() * @var string|null */ protected $content; @@ -145,7 +148,7 @@ class Pico /** * List of known pages * - * @see Pico::readPages() + * @see Pico::getPages() * @var array[]|null */ protected $pages; @@ -153,7 +156,7 @@ class Pico /** * Data of the page being served * - * @see Pico::discoverCurrentPage() + * @see Pico::getCurrentPage() * @var array|null */ protected $currentPage; @@ -161,7 +164,7 @@ class Pico /** * Data of the previous page relative to the page being served * - * @see Pico::discoverCurrentPage() + * @see Pico::getPreviousPage() * @var array|null */ protected $previousPage; @@ -169,7 +172,7 @@ class Pico /** * Data of the next page relative to the page being served * - * @see Pico::discoverCurrentPage() + * @see Pico::getNextPage() * @var array|null */ protected $nextPage; @@ -177,7 +180,7 @@ class Pico /** * Twig instance used for template parsing * - * @see Pico::registerTwig() + * @see Pico::getTwig() * @var Twig_Environment|null */ protected $twig; @@ -185,6 +188,7 @@ class Pico /** * Variables passed to the twig template * + * @see Pico::getTwigVariables * @var mixed[]|null */ protected $twigVariables; @@ -192,7 +196,7 @@ class Pico /** * Constructs a new Pico instance * - * To carry out all the processing in Pico, call the run() method. + * To carry out all the processing in Pico, call {@link Pico::run()}. * * @param string $rootDir root directory of this Pico instance * @param string $configDir config directory of this Pico instance @@ -355,6 +359,8 @@ class Pico * to indicate their processing order. You MUST NOT use prefixes between * 00 and 19 (reserved for built-in plugins). * + * @see Pico::getPlugin() + * @see Pico::getPlugins() * @return void * @throws RuntimeException thrown when a plugin couldn't be loaded */ @@ -386,6 +392,7 @@ class Pico * rely on it. For more information see {@link PicoPluginInterface}. * * @see Pico::loadPlugins() + * @see Pico::getPlugins() * @param string $pluginName name of the plugin * @return object instance of the plugin * @throws RuntimeException thrown when the plugin wasn't found @@ -403,6 +410,7 @@ class Pico * Returns all loaded plugins * * @see Pico::loadPlugins() + * @see Pico::getPlugin() * @return object[]|null */ public function getPlugins() @@ -413,6 +421,8 @@ class Pico /** * Loads the config.php from Pico::$configDir * + * @see Pico::setConfig() + * @see Pico::getConfig() * @return void */ protected function loadConfig() @@ -459,12 +469,15 @@ class Pico * * This method allows you to modify Picos config without creating a * {@path "config/config.php"} or changing some of its variables before - * Pico starts processing. It can only be called between the constructor - * call and Pico::run(). Options set with this method cannot be overwritten - * by {@path "config/config.php"}. + * Pico starts processing. * - * @param mixed[] $config array with configuration variables, like - * $config in {@path "config/config.php"} + * You can call this method between {@link Pico::__construct()} and + * {@link Pico::run()} only. Options set with this method cannot be + * overwritten by {@path "config/config.php"}. + * + * @see Pico::loadConfig() + * @see Pico::getConfig() + * @param mixed[] $config array with config variables * @return void * @throws RuntimeException thrown if Pico already started processing */ @@ -481,6 +494,7 @@ class Pico * Returns either the value of the specified config variable or * the config array * + * @see Pico::setConfig() * @see Pico::loadConfig() * @param string $configName optional name of a config variable * @return mixed returns either the value of the named config @@ -499,15 +513,15 @@ class Pico /** * Evaluates the requested URL * - * Pico 1.0 uses the QUERY_STRING routing method (e.g. /pico/?sub/page) to - * support SEO-like URLs out-of-the-box with any webserver. You can still - * setup URL rewriting (e.g. using mod_rewrite on Apache) to basically - * remove the `?` from URLs, but your rewritten URLs must follow the - * new QUERY_STRING principles. URL rewriting requires some special + * Pico 1.0 uses the `QUERY_STRING` routing method (e.g. `/pico/?sub/page`) + * to support SEO-like URLs out-of-the-box with any webserver. You can + * still setup URL rewriting (e.g. using `mod_rewrite` on Apache) to + * basically remove the `?` from URLs, but your rewritten URLs must follow + * the new `QUERY_STRING` principles. URL rewriting requires some special * configuration on your webserver, but this should be "basic work" for * any webmaster... * - * Pico 0.9 and older required Apache with mod_rewrite enabled, thus old + * Pico 0.9 and older required Apache with `mod_rewrite` enabled, thus old * plugins, templates and contents may require you to enable URL rewriting * to work. If you're upgrading from Pico 0.9, you will probably have to * update your rewriting rules. @@ -518,6 +532,7 @@ class Pico * `%base_url%` variable; e.g. `%base_url%?sub/page` will be automatically * replaced accordingly. * + * @see Pico::getRequestUrl() * @return void */ protected function evaluateRequestUrl() @@ -549,6 +564,7 @@ class Pico /** * Uses the request URL to discover the content file to serve * + * @see Pico::getRequestFile() * @return void */ protected function discoverRequestFile() @@ -611,6 +627,7 @@ class Pico /** * Returns the raw contents of a file * + * @see Pico::getRawContent() * @param string $file file path * @return string raw contents of the file */ @@ -623,6 +640,7 @@ class Pico * Returns the raw contents of the first found 404 file when traversing * up from the directory the requested file is in * + * @see Pico::getRawContent() * @param string $file path to requested (but not existing) file * @return string raw contents of the 404 file * @throws RuntimeException thrown when no suitable 404 file is found @@ -644,9 +662,10 @@ class Pico } /** - * Returns the cached raw contents, either of the requested or the 404 file + * Returns the raw contents, either of the requested or the 404 file * * @see Pico::loadFileContent() + * @see Pico::load404Content() * @return string|null raw contents */ public function getRawContent() @@ -683,12 +702,13 @@ class Pico * Parses the file meta from raw file contents * * Meta data MUST start on the first line of the file, either opened and - * closed by --- or C-style block comments (deprecated). The headers are + * closed by `---` or C-style block comments (deprecated). The headers are * parsed by the YAML component of the Symfony project, keys are lowered. * If you're a plugin developer, you MUST register new headers during the * `onMetaHeaders` event first. The implicit availability of headers is * for users and pure (!) theme developers ONLY. * + * @see Pico::getFileMeta() * @see * @param string $rawContent the raw file contents * @param string[] $headers known meta headers @@ -751,6 +771,8 @@ class Pico * Applies some static preparations to the raw contents of a page, * e.g. removing the meta header and replacing %base_url% * + * @see Pico::parseFileContent() + * @see Pico::getFileContent() * @param string $rawContent raw contents of a page * @return string contents prepared for parsing */ @@ -795,6 +817,8 @@ class Pico /** * Parses the contents of a page using ParsedownExtra * + * @see Pico::prepareFileContent() + * @see Pico::getFileContent() * @param string $content raw contents of a page (Markdown) * @return string parsed contents (HTML) */ @@ -807,6 +831,7 @@ class Pico /** * Returns the cached contents of the requested page * + * @see Pico::prepareFileContent() * @see Pico::parseFileContent() * @return string|null parsed contents */ @@ -834,6 +859,8 @@ class Pico * | meta | parsed meta data of the page) | * +----------------+------------------------------------------+ * + * @see Pico::sortPages() + * @see Pico::getPages() * @return void */ protected function readPages() @@ -896,6 +923,8 @@ class Pico /** * Sorts all pages known to Pico * + * @see Pico::readPages() + * @see Pico::getPages() * @return void */ protected function sortPages() @@ -936,6 +965,7 @@ class Pico * Returns the list of known pages * * @see Pico::readPages() + * @see Pico::sortPages() * @return array|null the data of all pages */ public function getPages() @@ -947,6 +977,9 @@ class Pico * Walks through the list of known pages and discovers the requested page * as well as the previous and next page relative to it * + * @see Pico::getCurrentPage() + * @see Pico::getPreviousPage() + * @see Pico::getNextPage() * @return void */ protected function discoverCurrentPage() @@ -1016,6 +1049,7 @@ class Pico /** * Registers the twig template engine * + * @see Pico::getTwig() * @return void */ protected function registerTwig() @@ -1029,6 +1063,7 @@ class Pico /** * Returns the twig template engine * + * @see Pico::registerTwig() * @return Twig_Environment|null twig template engine */ public function getTwig() @@ -1039,8 +1074,8 @@ class Pico /** * Returns the variables passed to the template * - * URLs and paths (namely base_dir, base_url, theme_dir and theme_url) - * don't add a trailing slash for historic reasons. + * URLs and paths (namely `base_dir`, `base_url`, `theme_dir` and + * `theme_url`) don't add a trailing slash for historic reasons. * * @return mixed[] template variables */ @@ -1179,11 +1214,14 @@ class Pico } /** - * Triggers events on plugins which implement {@link PicoPluginInterface} + * Triggers events on plugins which implement PicoPluginInterface * * Deprecated events (as used by plugins not implementing * {@link IPocPlugin}) are triggered by {@link PicoDeprecated}. * + * @see PicoPluginInterface + * @see AbstractPicoPlugin + * @see DummyPlugin * @param string $eventName name of the event to trigger * @param array $params optional parameters to pass * @return void diff --git a/lib/PicoPluginInterface.php b/lib/PicoPluginInterface.php index 081b280..8ea3ab6 100644 --- a/lib/PicoPluginInterface.php +++ b/lib/PicoPluginInterface.php @@ -14,11 +14,11 @@ * plugins are loaded. Consequently the old events are never triggered when * your plugin is implementing this interface and no old plugins are present. * - * If you're developing a new plugin, you MUST implement PicoPluginInterface. If + * If you're developing a new plugin, you MUST implement this interface. If * you're the developer of an old plugin, it is STRONGLY RECOMMENDED to use * the events introduced in Pico 1.0 when releasing a new version of your * plugin. If you want to use any of the new events, you MUST implement - * PicoPluginInterface and update all other events you use. + * this interface and update all other events you use. * * @author Daniel Rudolf * @link http://picocms.org @@ -46,6 +46,8 @@ interface PicoPluginInterface /** * Enables or disables this plugin * + * @see PicoPluginInterface::isEnabled() + * @see PicoPluginInterface::isStatusChanged() * @param boolean $enabled enable (true) or disable (false) this plugin * @param boolean $recursive when true, enable or disable recursively * In other words, if you enable a plugin, all required plugins are @@ -63,6 +65,7 @@ interface PicoPluginInterface /** * Returns true if this plugin is enabled, false otherwise * + * @see PicoPluginInterface::setEnabled() * @return boolean plugin is enabled (true) or disabled (false) */ public function isEnabled(); @@ -70,6 +73,7 @@ interface PicoPluginInterface /** * Returns true if the plugin was ever enabled/disabled manually * + * @see PicoPluginInterface::setEnabled() * @return boolean plugin is in its default state (true), false otherwise */ public function isStatusChanged(); @@ -91,6 +95,7 @@ interface PicoPluginInterface /** * Returns the plugins instance of Pico * + * @see Pico * @return Pico the plugins instance of Pico */ public function getPico(); diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 076a726..5c2cec1 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -54,8 +54,8 @@ class PicoDeprecated extends AbstractPicoPlugin /** * The requested file * + * @see PicoDeprecated::getRequestFile() * @var string|null - * @see PicoDeprecated::onRequestFile() */ protected $requestFile; @@ -103,11 +103,12 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Defines deprecated constants * - * CONTENT_DIR is deprecated since v0.9, ROOT_DIR, LIB_DIR, PLUGINS_DIR, - * THEMES_DIR and CONTENT_EXT since v1.0, CONFIG_DIR existed just for a - * short time between v0.9 and v1.0 and CACHE_DIR was dropped with v1.0 - * without a replacement. + * `CONTENT_DIR` is deprecated since v0.9, `ROOT_DIR`, `LIB_DIR`, + * `PLUGINS_DIR`, `THEMES_DIR` and `CONTENT_EXT` since v1.0, `CONFIG_DIR` + * existed just for a short time between v0.9 and v1.0 and `CACHE_DIR` + * was dropped with v1.0 without a replacement. * + * @see PicoDeprecated::onConfigLoaded() * @return void */ protected function defineConstants() @@ -137,8 +138,10 @@ class PicoDeprecated extends AbstractPicoPlugin } /** - * Read {@path "config.php"} in Picos root dir + * Read config.php in Picos root dir * + * @see PicoDeprecated::onConfigLoaded() + * @see Pico::loadConfig() * @param mixed[] &$config array of config variables * @return void */ @@ -154,8 +157,10 @@ class PicoDeprecated extends AbstractPicoPlugin } /** - * Enables the plugins {@link PicoParsePagesContent} and {@link PicoExcerpt} + * Enables the plugins PicoParsePagesContent and PicoExcerpt * + * @see PicoParsePagesContent + * @see PicoExcerpt * @return void */ protected function enablePlugins() @@ -190,9 +195,11 @@ class PicoDeprecated extends AbstractPicoPlugin } /** - * Sets {@link PicoDeprecated::$requestFile} to trigger the deprecated + * Sets PicoDeprecated::$requestFile to trigger the deprecated * events after_load_content() and after_404_load_content() * + * @see PicoDeprecated::onContentLoaded() + * @see PicoDeprecated::on404ContentLoaded() * @see DummyPlugin::onRequestFile() */ public function onRequestFile(&$file) @@ -298,6 +305,11 @@ class PicoDeprecated extends AbstractPicoPlugin * Triggers the deprecated event * get_pages($pages, $currentPage, $previousPage, $nextPage) * + * Please note that the `get_pages()` event gets `$pages` passed without a + * array index. The index is rebuild later using either the `id` array key + * or is derived from the `url` array key. Duplicates are prevented by + * adding `~dup` when necessary. + * * @see DummyPlugin::onPagesLoaded() */ public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage) @@ -342,6 +354,9 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Triggers the deprecated event before_render($twigVariables, $twig, $templateName) * + * Please note that the `before_render()` event gets `$templateName` passed + * without its file extension. The file extension is later added again. + * * @see DummyPlugin::onPageRendering() */ public function onPageRendering(&$twig, &$twigVariables, &$templateName) @@ -372,6 +387,10 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Triggers a deprecated event on all plugins * + * Deprecated events are also triggered on plugins which implement + * {@link PicoPluginInterface}. Please note that the methods are called + * directly and not through {@link PicoPluginInterface::handleEvent()}. + * * @param string $eventName event to trigger * @param array $params parameters to pass * @return void diff --git a/plugins/02-PicoExcerpt.php b/plugins/02-PicoExcerpt.php index 3767cc7..1488670 100644 --- a/plugins/02-PicoExcerpt.php +++ b/plugins/02-PicoExcerpt.php @@ -11,7 +11,7 @@ * {@link PicoParsePagesContent}, what heavily impacts Picos performance. You * should either use the Description meta header field or write something own. * Best solution seems to be a filter for twig, see e.g. - * . + * {@link https://gist.github.com/james2doyle/6629712}. * * @author Daniel Rudolf * @link http://picocms.org @@ -28,8 +28,9 @@ class PicoExcerpt extends AbstractPicoPlugin protected $enabled = false; /** - * This plugin depends on {@link PicoParsePagesContent} + * This plugin depends on PicoParsePagesContent * + * @see PicoParsePagesContent * @see AbstractPicoPlugin::$dependsOn */ protected $dependsOn = array('PicoParsePagesContent'); @@ -49,6 +50,7 @@ class PicoExcerpt extends AbstractPicoPlugin /** * Creates a excerpt for the contents of each page * + * @see PicoExcerpt::createExcerpt() * @see DummyPlugin::onSinglePageLoaded() */ public function onSinglePageLoaded(&$pageData) diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 059b188..510af8e 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -16,16 +16,16 @@ class DummyPlugin extends AbstractPicoPlugin /** * This plugin is enabled by default? * - * @var boolean * @see AbstractPicoPlugin::$enabled + * @var boolean */ protected $enabled = false; /** - * This plugin depends on {@link ...} + * This plugin depends on ... * - * @var string[] * @see AbstractPicoPlugin::$dependsOn + * @var string[] */ protected $dependsOn = array(); @@ -60,7 +60,6 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered after Pico has evaluated the request URL * - * @see Pico::getBaseUrl() * @see Pico::getRequestUrl() * @param string &$url part of the URL describing the requested contents * @return void @@ -73,6 +72,7 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered after Pico has discovered the content file to serve * + * @see Pico::getBaseUrl() * @see Pico::getRequestFile() * @param string &$file absolute path to the content file to serve * @return void @@ -86,6 +86,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico reads the contents of the file to serve * * @see Pico::loadFileContent() + * @see DummyPlugin::onContentLoaded() * @param string &$file path to the file which contents will be read * @return void */ @@ -107,9 +108,10 @@ class DummyPlugin extends AbstractPicoPlugin } /** - * Triggered before Pico reads the contents of the 404 file + * Triggered before Pico reads the contents of a 404 file * * @see Pico::load404Content() + * @see DummyPlugin::on404ContentLoaded() * @param string &$file path to the file which contents were requested * @return void */ @@ -148,6 +150,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico parses the meta header * * @see Pico::parseFileMeta() + * @see DummyPlugin::onMetaParsed() * @param string &$rawContent raw file contents * @param string[] &$headers known meta header fields * @return void @@ -173,6 +176,8 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico parses the pages content * * @see Pico::prepareFileContent() + * @see DummyPlugin::prepareFileContent() + * @see DummyPlugin::onContentParsed() * @param string &$rawContent raw file contents * @return void */ @@ -185,6 +190,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered after Pico has prepared the raw file contents for parsing * * @see Pico::parseFileContent() + * @see DummyPlugin::onContentParsed() * @param string &$content prepared file contents for parsing * @return void */ @@ -205,9 +211,23 @@ class DummyPlugin extends AbstractPicoPlugin // your code } + /** + * Triggered before Pico reads all known pages + * + * @see Pico::readPages() + * @see DummyPlugin::onSinglePageLoaded() + * @see DummyPlugin::onPagesLoaded() + * @return void + */ + public function onPagesLoading() + { + // your code + } + /** * Triggered when Pico reads a single page from the list of all known pages * + * @see DummyPlugin::onPagesLoaded() * @param array &$pageData { * data of the loaded page * @@ -264,6 +284,7 @@ class DummyPlugin extends AbstractPicoPlugin * Triggered before Pico renders the page * * @see Pico::getTwig() + * @see DummyPlugin::onPageRendered() * @param Twig_Environment &$twig twig template engine * @param mixed[] &$twigVariables template variables * @param string &$templateName file name of the template From d29e2c1f95da9219b598218077acd81d8867bba6 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 16:57:25 +0100 Subject: [PATCH 088/118] Add CONTRIBUTING.md The file mostly contains adapted contents @theska wrote for our website and the great CONTRIBUTING.md of phpDocumentor (https://github.com/phpDocumentor/phpDocumentor2/blob/v2.8.5/CONTRIBUTING.md) --- CONTRIBUTING.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2cd1598 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +Contributing to Pico +==================== + +Pico aims to be a high quality Content Management System (CMS) but at the same time wants to give contributors freedom when submitting fixes or improvements. + +As such we want to *encourage* but not obligate you, the contributor, to follow these guidelines. The only exception to this are the guidelines regarding *Contributing code* to prevent `merge-hell`. + +Having said that: we really appreciate it when you apply the guidelines in part or wholly as that will save us time which, in turn, we can spend on bugfixes and new features. + +Once you decide you want to contribute to *Picos core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/plugin-dev.html) of our website. + +Issues +------ + +If you want to report an *issue* with Picos core, please create a new [Issue](https://github.com/picocms/Pico/issues) on GitHub. Concerning problems with plugins or themes, please refer to the website of the developer of this plugin or theme. + +Before creating a [new Issue on GitHub](https://github.com/picocms/Pico/issues/new), please make sure the problem wasn't reported yet using [GitHubs search engine](https://github.com/picocms/Pico/search?type=Issues). Please describe your issue as clear as possible and always include steps to reproduce the problem. + +Contributing code +----------------- + +Please do *not* develop your contribution on the `master` branch of your fork, but create a separate feature branch, that is based off the `master` branch, for each feature that you want to contribute. + +> Not doing so means that if you decide to work on two separate features and place a pull request for one of them, that the changes of the other issue that you are working on is also submitted. Even if it is not completely finished. + +To get more information about the usage of Git, please refer to the [Pro Git book](https://git-scm.com/book) written by Scott Chacon and/or [this help page of GitHub](https://help.github.com/articles/using-pull-requests). + +Please keep in mind that pull requests should be small (i.e. one feature per request), stick to existing coding conventions and documentation should be updated if required. It's encouraged to make commits of logical units and check for unnecessary whitespaces before commiting (try `git diff --check`). Please reference issue numbers in your commit messages where appropriate. + +Versioning +---------- + +Pico follows [Semantic Versioning 2.0](http://semver.org) and uses version numbers like `MAJOR`.`MINOR`.`PATCH`. We will increment the: + +- `MAJOR` version when we make incompatible API changes, +- `MINOR` version when we add functionality in a backwards-compatible manner, and +- `PATCH` version when we make backwards-compatible bug fixes. + +For more information please refer to the http://semver.org website. + +Branching +--------- + +The `master` branch contains the current development version of Pico. It is likely *unstable* and *not ready for production use*. However, the `master` branch always consists of a deployable version of Pico. + +Picos actual development happens in separate development branches. Development branches are prefixed by: + +- `feature/` for bigger features, +- `enhancement/` for smaller improvements, and +- `bugfix/` for bug fixes. + +As soon as development reaches a point where feedback is appreciated, a [pull request](https://github.com/picocms/Pico/pulls) is opened. After some time (very soon for bug fixes, and other improvements should have a reasonable feedback phase) the pull request is merged into `master` and the development branch will be deleted. + +Coding Standards +---------------- + +Pico uses the [PSR-2 Coding Standard](http://www.php-fig.org/psr/psr-2/) as defined by the [PHP Framework Interoperability Group (PHP-FIG)](http://www.php-fig.org/). + +For historical reasons we don't use formal namespaces. Markdown files in the `content-sample` folder (the inline documentation) must follow a hard limit of 80 characters line length. + +It is recommended to check your code using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) using the `PSR2` standard using the following command: + + $ ./bin/phpcs --standard=PSR2 [file(s)] + +With this command you can specify a file or folder to limit which files it will check or omit that argument altogether, in which case the current directory is checked. + +Build & Release process +----------------------- + +This is work in progress. Please refer to #268 for details. + + From 7a69fdf66dd9fd47385b8d227352d45f00a3aaf6 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 17:16:57 +0100 Subject: [PATCH 089/118] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cd1598..a5e44a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,6 @@ As such we want to *encourage* but not obligate you, the contributor, to follow Having said that: we really appreciate it when you apply the guidelines in part or wholly as that will save us time which, in turn, we can spend on bugfixes and new features. -Once you decide you want to contribute to *Picos core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/plugin-dev.html) of our website. - Issues ------ @@ -19,6 +17,8 @@ Before creating a [new Issue on GitHub](https://github.com/picocms/Pico/issues/n Contributing code ----------------- +Once you decide you want to contribute to *Picos core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/plugin-dev.html) of our website. + Please do *not* develop your contribution on the `master` branch of your fork, but create a separate feature branch, that is based off the `master` branch, for each feature that you want to contribute. > Not doing so means that if you decide to work on two separate features and place a pull request for one of them, that the changes of the other issue that you are working on is also submitted. Even if it is not completely finished. From e6681ea903f5947edc19797765dc8423cd35a709 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 18:07:45 +0100 Subject: [PATCH 090/118] Improve class docs Also add some ToDos to inline docs --- content-sample/index.md | 2 ++ lib/Pico.php | 2 ++ plugins/00-PicoDeprecated.php | 2 ++ plugins/DummyPlugin.php | 33 +++++++++++++++++++-------------- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/content-sample/index.md b/content-sample/index.md index b451260..3adb7c8 100644 --- a/content-sample/index.md +++ b/content-sample/index.md @@ -303,7 +303,9 @@ particularly the [plugin upgrade section][PluginUpgrade]. down to the massive performance impact, but leads to the removal of the generation of auto-generated excerpts. TODO: describe how to force enable/disable `PicoExcerpt` and `PicoParsePagesContent` + TODO: describe how to replace `PicoExcerpt` * TODO: Removing various empty `index.html` files; check accessibility! +* TODO: Describe new features that are important for users... e.g. `%meta.*%` ## Documentation diff --git a/lib/Pico.php b/lib/Pico.php index 4fcb1fc..aaa032d 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -844,6 +844,7 @@ class Pico * Reads the data of all pages known to Pico * * The page data will be an array containing the following values: + *
      * +----------------+------------------------------------------+
      * | Array key      | Description                              |
      * +----------------+------------------------------------------+
@@ -858,6 +859,7 @@ class Pico
      * | raw_content    | raw, not yet parsed contents of the page |
      * | meta           | parsed meta data of the page)            |
      * +----------------+------------------------------------------+
+     * 
* * @see Pico::sortPages() * @see Pico::getPages() diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 5c2cec1..2c818f8 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -11,6 +11,7 @@ * can disable this plugin by calling {@link PicoDeprecated::setEnabled()}. * * The following deprecated events are triggered by this plugin: + *
  * +---------------------+-----------------------------------------------------------+
  * | Event               | ... triggers the deprecated event                         |
  * +---------------------+-----------------------------------------------------------+
@@ -32,6 +33,7 @@
  * | onPageRendering     | before_render($twigVariables, $twig, $templateName)       |
  * | onPageRendered      | after_render($output)                                     |
  * +---------------------+-----------------------------------------------------------+
+ * 
* * Since Pico 1.0 the config is stored in {@path "config/config.php"}. This * plugin tries to read {@path "config.php"} in Picos root dir and overwrites diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 510af8e..023e70b 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -227,21 +227,26 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered when Pico reads a single page from the list of all known pages * - * @see DummyPlugin::onPagesLoaded() - * @param array &$pageData { - * data of the loaded page + * The $pageData variable consits of the following values: + *
+     * +-----------------+--------+------------------------------------------+
+     * | Array key       | Type   | Description                              |
+     * +-----------------+--------+------------------------------------------+
+     * | $id             | string | relative path to the content file        |
+     * | $url            | string | URL to the page                          |
+     * | $title          | string | title of the page (YAML header)          |
+     * | $description    | string | description of the page (YAML header)    |
+     * | $author         | string | author of the page (YAML header)         |
+     * | $time           | string | timestamp derived from the Date header   |
+     * | $date           | string | date of the page (YAML header)           |
+     * | $date_formatted | string | formatted date of the page               |
+     * | $raw_content    | string | raw, not yet parsed contents of the page |
+     * | $meta           | string | parsed meta data of the page             |
+     * +-----------------+--------+------------------------------------------+
+     * 
* - * @var string $id relative path to the content file - * @var string $url URL to the page - * @var string $title title of the page (YAML header) - * @var string $description description of the page (YAML header) - * @var string $author author of the page (YAML header) - * @var string $time timestamp derived from the Date header - * @var string $date date of the page (YAML header) - * @var string $date_formatted formatted date of the page - * @var string $raw_content raw, not yet parsed contents of the page - * @var string $meta parsed meta data of the page - * } + * @see DummyPlugin::onPagesLoaded() + * @param array &$pageData data of the loaded page * @return void */ public function onSinglePageLoaded(&$pageData) From afb55b9cb613572454bd9e64d1e3141d68dbef02 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 29 Oct 2015 18:13:35 +0100 Subject: [PATCH 091/118] Improve class docs --- lib/Pico.php | 28 ++++++++++++++-------------- plugins/DummyPlugin.php | 30 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/Pico.php b/lib/Pico.php index aaa032d..a75d6ad 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -845,20 +845,20 @@ class Pico * * The page data will be an array containing the following values: *
-     * +----------------+------------------------------------------+
-     * | Array key      | Description                              |
-     * +----------------+------------------------------------------+
-     * | id             | relative path to the content file        |
-     * | url            | URL to the page                          |
-     * | title          | title of the page (YAML header)          |
-     * | description    | description of the page (YAML header)    |
-     * | author         | author of the page (YAML header)         |
-     * | time           | timestamp derived from the Date header   |
-     * | date           | date of the page (YAML header)           |
-     * | date_formatted | formatted date of the page               |
-     * | raw_content    | raw, not yet parsed contents of the page |
-     * | meta           | parsed meta data of the page)            |
-     * +----------------+------------------------------------------+
+     * +----------------+--------+------------------------------------------+
+     * | Array key      | Type   | Description                              |
+     * +----------------+--------+------------------------------------------+
+     * | id             | string | relative path to the content file        |
+     * | url            | string | URL to the page                          |
+     * | title          | string | title of the page (YAML header)          |
+     * | description    | string | description of the page (YAML header)    |
+     * | author         | string | author of the page (YAML header)         |
+     * | time           | string | timestamp derived from the Date header   |
+     * | date           | string | date of the page (YAML header)           |
+     * | date_formatted | string | formatted date of the page               |
+     * | raw_content    | string | raw, not yet parsed contents of the page |
+     * | meta           | string | parsed meta data of the page             |
+     * +----------------+--------+------------------------------------------+
      * 
* * @see Pico::sortPages() diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 023e70b..597237d 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -227,22 +227,22 @@ class DummyPlugin extends AbstractPicoPlugin /** * Triggered when Pico reads a single page from the list of all known pages * - * The $pageData variable consits of the following values: + * The `$pageData` parameter consists of the following values: *
-     * +-----------------+--------+------------------------------------------+
-     * | Array key       | Type   | Description                              |
-     * +-----------------+--------+------------------------------------------+
-     * | $id             | string | relative path to the content file        |
-     * | $url            | string | URL to the page                          |
-     * | $title          | string | title of the page (YAML header)          |
-     * | $description    | string | description of the page (YAML header)    |
-     * | $author         | string | author of the page (YAML header)         |
-     * | $time           | string | timestamp derived from the Date header   |
-     * | $date           | string | date of the page (YAML header)           |
-     * | $date_formatted | string | formatted date of the page               |
-     * | $raw_content    | string | raw, not yet parsed contents of the page |
-     * | $meta           | string | parsed meta data of the page             |
-     * +-----------------+--------+------------------------------------------+
+     * +----------------+--------+------------------------------------------+
+     * | Array key      | Type   | Description                              |
+     * +----------------+--------+------------------------------------------+
+     * | id             | string | relative path to the content file        |
+     * | url            | string | URL to the page                          |
+     * | title          | string | title of the page (YAML header)          |
+     * | description    | string | description of the page (YAML header)    |
+     * | author         | string | author of the page (YAML header)         |
+     * | time           | string | timestamp derived from the Date header   |
+     * | date           | string | date of the page (YAML header)           |
+     * | date_formatted | string | formatted date of the page               |
+     * | raw_content    | string | raw, not yet parsed contents of the page |
+     * | meta           | string | parsed meta data of the page             |
+     * +----------------+--------+------------------------------------------+
      * 
* * @see DummyPlugin::onPagesLoaded() From 9a702415fb4e1b5a2092c95bb003d56bd88fa7ff Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 31 Oct 2015 00:32:08 +0100 Subject: [PATCH 092/118] Remove `return $config` in `config/config.php` I always thought that doing this is pretty unusual... But now it simply breaks BC - please refer to @Lomanic's [comment](https://github.com/picocms/Pico/pull/260#issuecomment-152610857). Using a return statement has no advantages, but increases the probability that something goes wrong (e.g. a clueless user removes the return statement). It was introduced with 23b90e2, but we never released it ([v0.9.1](https://github.com/picocms/Pico/blob/4cb2b24fae6bda83c7a3327cfb806370a90a60ac/lib/pico.php#L188-L189)). Removing the return statement shouldn't cause any problems even for users which installed Pico in the meantime. As a result we don't break BC and moreover remove a prior BC break :smiley: --- config/config.php.template | 5 ----- lib/Pico.php | 5 ++++- plugins/00-PicoDeprecated.php | 12 +++++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/config.php.template b/config/config.php.template index 69cd7b1..568bd00 100644 --- a/config/config.php.template +++ b/config/config.php.template @@ -16,8 +16,6 @@ * @version 0.9 */ -$config = array(); - /* * BASIC */ @@ -58,6 +56,3 @@ $config = array(); * CUSTOM */ // $config['custom_setting'] = 'Hello'; // Can be accessed by {{ config.custom_setting }} in a theme - -// DO NOT REMOVE THIS LINE -return $config; diff --git a/lib/Pico.php b/lib/Pico.php index a75d6ad..13194c4 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -427,6 +427,7 @@ class Pico */ protected function loadConfig() { + $config = null; $defaultConfig = array( 'site_title' => 'Pico', 'base_url' => '', @@ -442,7 +443,9 @@ class Pico ); $configFile = $this->getConfigDir() . 'config.php'; - $config = file_exists($configFile) ? require($configFile) : null; + if (file_exists($configFile)) { + require $configFile; + } $this->config = is_array($this->config) ? $this->config : array(); $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig; diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 2c818f8..d719d20 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -144,16 +144,18 @@ class PicoDeprecated extends AbstractPicoPlugin * * @see PicoDeprecated::onConfigLoaded() * @see Pico::loadConfig() - * @param mixed[] &$config array of config variables + * @param mixed[] &$realConfig array of config variables * @return void */ - protected function loadRootDirConfig(&$config) + protected function loadRootDirConfig(&$realConfig) { if (file_exists($this->getRootDir() . 'config.php')) { // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead - $newConfig = require($this->getRootDir() . 'config.php'); - if (is_array($newConfig)) { - $config = $newConfig + $config; + $config = null; + require($this->getRootDir() . 'config.php'); + + if (is_array($config)) { + $realConfig = $config + $realConfig; } } } From 8da62f4aad48b26c6726c7b7a8ad1c5b6bed50cd Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sat, 31 Oct 2015 01:03:24 +0100 Subject: [PATCH 093/118] PicoDeprecated: Making $config globally accessible again This was dropped without a replacement with Pico 0.9. I checked all changes since Pico 0.8 manually, as far as I can tell there should be no more surprises regarding BC... Thanks @Lomanic for rubbing our nose in the fact that we should check this! I also added the missing changes of Pico 0.9 to changelog.txt --- changelog.txt | 6 +++++- plugins/00-PicoDeprecated.php | 37 +++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/changelog.txt b/changelog.txt index aa2276d..6eb52b0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -16,7 +16,7 @@ and `AbstractPicoPlugin`); if you're plugin dev, you really should take a look at the UPGRADE section of the docs! * [New] Introducing the `PicoDeprecated` plugin to maintain full backward - compatibility with Pico 0.9 and older + compatibility with Pico 0.9 and Pico 0.8 * [New] Support YAML-style meta header comments (`---`) * [New] Various new placeholders to use in content files (e.g. `%site_title%`) * [New] Provide access to all meta headers in content files (`%meta.*%`) @@ -68,11 +68,15 @@ 2015.04.28 - version 0.9 * [New] Default theme is now mobile-friendly * [New] Description meta now available in content areas + * [New] Add description to composer.json * [Changed] content folder is now content-sample + * [Changed] config.php moved to config.php.template * [Changed] Updated documentation & wiki * [Changed] Removed Composer, Twig files in /vendor, you must run composer install now * [Changed] Localized date format; strftime() instead of date() * [Changed] Added ignore for tmp file extensions in the get_files() method + * [Changed] michelf/php-markdown is replaced with erusev/parsedown-extra + * [Changed] $config is no global variable anymore * [Fixed] Pico now only removes the 1st comment block in .md files * [Fixed] Issue wherein the alphabetical sorting of pages did not happen diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index d719d20..496b9a7 100644 --- a/plugins/00-PicoDeprecated.php +++ b/plugins/00-PicoDeprecated.php @@ -88,27 +88,38 @@ class PicoDeprecated extends AbstractPicoPlugin /** * Triggers the deprecated event config_loaded($config) * - * @see PicoDeprecated::defineConstants() - * @see PicoDeprecated::loadRootDirConfig() - * @see PicoDeprecated::enablePlugins() - * @see DummyPlugin::onConfigLoaded() + * This method also defines deprecated constants, reads the `config.php` + * in Picos root dir, enables the plugins {@link PicoParsePagesContent} + * and {@link PicoExcerpt} and makes `$config` globally accessible (the + * latter was removed with Pico 0.9 and was added again as deprecated + * feature with Pico 1.0) + * + * @see PicoDeprecated::defineConstants() + * @see PicoDeprecated::loadRootDirConfig() + * @see PicoDeprecated::enablePlugins() + * @see DummyPlugin::onConfigLoaded() + * @param mixed[] &$realConfig array of config variables + * @return void */ - public function onConfigLoaded(&$config) + public function onConfigLoaded(&$realConfig) { - $this->defineConstants(); - $this->loadRootDirConfig($config); - $this->enablePlugins(); + global $config; - $this->triggerEvent('config_loaded', array(&$config)); + $this->defineConstants(); + $this->loadRootDirConfig($realConfig); + $this->enablePlugins(); + $config = &$realConfig; + + $this->triggerEvent('config_loaded', array(&$realConfig)); } /** * Defines deprecated constants * - * `CONTENT_DIR` is deprecated since v0.9, `ROOT_DIR`, `LIB_DIR`, - * `PLUGINS_DIR`, `THEMES_DIR` and `CONTENT_EXT` since v1.0, `CONFIG_DIR` - * existed just for a short time between v0.9 and v1.0 and `CACHE_DIR` - * was dropped with v1.0 without a replacement. + * `ROOT_DIR`, `LIB_DIR`, `PLUGINS_DIR`, `THEMES_DIR` and `CONTENT_EXT` + * are deprecated since v1.0, `CONTENT_DIR` existed just in v0.9, + * `CONFIG_DIR` just for a short time between v0.9 and v1.0 and + * `CACHE_DIR` was dropped with v1.0 without a replacement. * * @see PicoDeprecated::onConfigLoaded() * @return void From 85d7c510f93069a970a271330071d6c3eba6f3a5 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 2 Nov 2015 03:16:37 +0100 Subject: [PATCH 094/118] Update CONTRIBUTING.md --- CONTRIBUTING.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5e44a8..8b136f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Contributing to Pico Pico aims to be a high quality Content Management System (CMS) but at the same time wants to give contributors freedom when submitting fixes or improvements. -As such we want to *encourage* but not obligate you, the contributor, to follow these guidelines. The only exception to this are the guidelines regarding *Contributing code* to prevent `merge-hell`. +As such we want to *encourage* but not obligate you, the contributor, to follow these guidelines. The only exception to this are the guidelines elucidated in the *Prevent `merge-hell`* section. Having said that: we really appreciate it when you apply the guidelines in part or wholly as that will save us time which, in turn, we can spend on bugfixes and new features. @@ -19,14 +19,30 @@ Contributing code Once you decide you want to contribute to *Picos core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/plugin-dev.html) of our website. +### Prevent `merge-hell` + Please do *not* develop your contribution on the `master` branch of your fork, but create a separate feature branch, that is based off the `master` branch, for each feature that you want to contribute. > Not doing so means that if you decide to work on two separate features and place a pull request for one of them, that the changes of the other issue that you are working on is also submitted. Even if it is not completely finished. To get more information about the usage of Git, please refer to the [Pro Git book](https://git-scm.com/book) written by Scott Chacon and/or [this help page of GitHub](https://help.github.com/articles/using-pull-requests). +### Pull Requests + Please keep in mind that pull requests should be small (i.e. one feature per request), stick to existing coding conventions and documentation should be updated if required. It's encouraged to make commits of logical units and check for unnecessary whitespaces before commiting (try `git diff --check`). Please reference issue numbers in your commit messages where appropriate. +### Coding Standards + +Pico uses the [PSR-2 Coding Standard](http://www.php-fig.org/psr/psr-2/) as defined by the [PHP Framework Interoperability Group (PHP-FIG)](http://www.php-fig.org/). + +For historical reasons we don't use formal namespaces. Markdown files in the `content-sample` folder (the inline documentation) must follow a hard limit of 80 characters line length. + +It is recommended to check your code using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) using the `PSR2` standard using the following command: + + $ ./bin/phpcs --standard=PSR2 [file(s)] + +With this command you can specify a file or folder to limit which files it will check or omit that argument altogether, in which case the current directory is checked. + Versioning ---------- @@ -51,23 +67,10 @@ Picos actual development happens in separate development branches. Development b As soon as development reaches a point where feedback is appreciated, a [pull request](https://github.com/picocms/Pico/pulls) is opened. After some time (very soon for bug fixes, and other improvements should have a reasonable feedback phase) the pull request is merged into `master` and the development branch will be deleted. -Coding Standards ----------------- - -Pico uses the [PSR-2 Coding Standard](http://www.php-fig.org/psr/psr-2/) as defined by the [PHP Framework Interoperability Group (PHP-FIG)](http://www.php-fig.org/). - -For historical reasons we don't use formal namespaces. Markdown files in the `content-sample` folder (the inline documentation) must follow a hard limit of 80 characters line length. - -It is recommended to check your code using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) using the `PSR2` standard using the following command: - - $ ./bin/phpcs --standard=PSR2 [file(s)] - -With this command you can specify a file or folder to limit which files it will check or omit that argument altogether, in which case the current directory is checked. - Build & Release process ----------------------- -This is work in progress. Please refer to #268 for details. +This is work in progress. Please refer to [#268](https://github.com/picocms/Pico/issues/268) for details.