Merge branch 'pico1.0' of https://github.com/PhrozenByte/Pico into PhrozenByte-pico1.0
This commit is contained in:
commit
3b223e8e1d
6 changed files with 214 additions and 65 deletions
|
@ -2,7 +2,7 @@ Pico
|
|||
====
|
||||
|
||||
[](https://scrutinizer-ci.com/g/theshka/Pico/build-status/LICENSE)
|
||||
[]()
|
||||
[]()
|
||||
[](https://scrutinizer-ci.com/g/theshka/Pico/build-status/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 <http://localhost:8080>.
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,5 +1,66 @@
|
|||
*** 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 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 (`---`)
|
||||
* [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] 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`)
|
||||
* [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 +69,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
|
||||
|
|
|
@ -71,9 +71,14 @@ 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);
|
||||
} else {
|
||||
$pluginConfig = $this->getConfig(get_called_class());
|
||||
if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) {
|
||||
$this->setEnabled($pluginConfig['enabled']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,8 +142,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()'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -187,7 +192,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
|
|||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
return $this->dependsOn;
|
||||
return (array) $this->dependsOn;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
94
lib/Pico.php
94
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
|
||||
*
|
||||
|
@ -247,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));
|
||||
}
|
||||
|
@ -274,6 +298,7 @@ class Pico
|
|||
$this->triggerEvent('onPagesLoading');
|
||||
|
||||
$this->readPages();
|
||||
$this->sortPages();
|
||||
$this->discoverCurrentPage();
|
||||
|
||||
$this->triggerEvent('onPagesLoaded', array(
|
||||
|
@ -356,7 +381,7 @@ class Pico
|
|||
return $this->plugins[$pluginName];
|
||||
}
|
||||
|
||||
throw new RuntimeException("Missing plugin '".$pluginName."'");
|
||||
throw new RuntimeException("Missing plugin '" . $pluginName . "'");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -529,13 +554,27 @@ 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
|
||||
*
|
||||
* @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()
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -577,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 <http://symfony.com/doc/current/components/yaml/introduction.html>
|
||||
* @param string $rawContent the raw file contents
|
||||
|
@ -593,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] = '';
|
||||
}
|
||||
}
|
||||
|
@ -614,6 +657,7 @@ class Pico
|
|||
$meta['time'] = $meta['date_formatted'] = '';
|
||||
}
|
||||
} else {
|
||||
// guarantee array key existance
|
||||
foreach ($headers as $id => $field) {
|
||||
$meta[$id] = '';
|
||||
}
|
||||
|
@ -708,7 +752,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')) {
|
||||
|
@ -760,7 +804,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) {
|
||||
|
@ -990,12 +1042,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 +1065,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;
|
||||
}
|
||||
|
|
|
@ -84,44 +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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
$this->defineConstants();
|
||||
$this->loadRootDirConfig($config);
|
||||
$this->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
|
||||
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));
|
||||
}
|
||||
|
||||
// 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
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
@ -139,13 +129,54 @@ 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'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->triggerEvent('config_loaded', array(&$config));
|
||||
/**
|
||||
* 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');
|
||||
if (is_array($newConfig)) {
|
||||
$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
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue