VERSION
VERSION
Pico version
Pico
Pico is a stupidly simple, blazing fast, flat file CMS.
See http://picocms.org/ for more info.
$nativePlugins : array<mixed,\PicoPluginInterface>
List of loaded plugins using the current API version
__construct(string $rootDir, string $configDir, string $pluginsDir, string $themesDir, boolean $enableLocalPlugins = true)
Constructs a new Pico instance
To carry out all the processing in Pico, call \Pico::run().
string | $rootDir | root dir of this Pico instance |
string | $configDir | config dir of this Pico instance |
string | $pluginsDir | plugins dir of this Pico instance |
string | $themesDir | themes dir of this Pico instance |
boolean | $enableLocalPlugins | enables (TRUE; default) or disables (FALSE) loading plugins from the filesystem |
run() : string
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.
thrown when a irrecoverable error occurs
rendered Pico contents
loadPlugin(\PicoPluginInterface|string $plugin) : \PicoPluginInterface
Manually loads a plugin
Manually loaded plugins MUST implement \PicoPluginInterface. They are simply appended to the plugins array without any additional checks, so you might get unexpected results, depending on when you're loading a plugin. You SHOULD NOT load plugins after a event has been triggered by Pico. In-depth knowledge of Pico's inner workings is strongly advised otherwise, and you MUST NOT rely on \PicoDeprecated to maintain backward compatibility in such cases.
If you e.g. load a plugin after the onPluginsLoaded
event, Pico
doesn't guarantee the plugin's order (\Pico::sortPlugins()).
Already triggered events won't get triggered on the manually loaded
plugin. Thus you SHOULD load plugins either before \Pico::run()
is called, or via the constructor of another plugin (i.e. the plugin's
__construct()
method; plugins are instanced in
\Pico::loadPlugins()).
This method triggers the onPluginManuallyLoaded
event.
\PicoPluginInterface|string | $plugin | either the class name of a plugin to instantiate or a plugin instance |
thrown when a plugin couldn't be loaded
instance of the loaded plugin
getPlugin(string $pluginName) : object
Returns the instance of a named plugin
Plugins SHOULD implement \PicoPluginInterface, but you MUST NOT rely on it. For more information see \PicoPluginInterface.
string | $pluginName | name of the plugin |
thrown when the plugin wasn't found
instance of the plugin
setConfig(array $config) : void
Sets Pico's config before calling Pico::run()
This method allows you to modify Pico's config without creating a {@path "config/config.yml"} or changing some of its variables before Pico starts processing.
You can call this method between \Pico::__construct() and \Pico::run() only. Options set with this method cannot be overwritten by {@path "config/config.yml"}.
array | $config | array with config variables |
thrown if Pico already started processing
getConfig(string $configName = null, mixed $default = null) : mixed
Returns either the value of the specified config variable or the config array
string | $configName | optional name of a config variable |
mixed | $default | optional default value to return when the named config variable doesn't exist |
if no name of a config variable has been supplied, the config array is returned; otherwise it returns either the value of the named config variable, or, if the named config variable doesn't exist, the provided default value or NULL
resolveFilePath(string $requestUrl) : string
Resolves a given file path to its corresponding content file
This method also prevents content_dir
breakouts using malicious
request URLs. We don't use realpath()
, because we neither want to
check for file existance, nor prohibit symlinks which intentionally
point to somewhere outside the content_dir
folder. It is STRONGLY
RECOMMENDED to use PHP's open_basedir
feature - always, not just
with Pico!
string | $requestUrl | path name (likely from a URL) to resolve |
path to the resolved content file
load404Content(string $file) : string
Returns the raw contents of the first found 404 file when traversing up from the directory the requested file is in
If no suitable 404.md
is found, fallback to a built-in error message.
string | $file | path to requested (but not existing) file |
raw contents of the 404 file
getMetaHeaders() : array<mixed,string>
Returns known meta headers
This method triggers the onMetaHeaders
event when the known meta
headers weren't assembled yet.
known meta headers; the array key specifies the YAML key to search for, the array value is later used to access the found value
parseFileMeta(string $rawContent, array<mixed,string> $headers) : array
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
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.
string | $rawContent | the raw file contents |
array<mixed,string> | $headers | known meta headers |
thrown when the meta data is invalid
parsed meta data
prepareFileContent(string $rawContent, array $meta = array()) : string
Applies some static preparations to the raw contents of a page
This method removes the meta header and replaces %...%
placeholders
by calling the \Pico::substituteFileContent() method.
string | $rawContent | raw contents of a page |
array | $meta | meta data to use for %meta.*% replacement |
prepared Markdown contents
substituteFileContent(string $markdown, array $meta = array()) : string
Replaces all %.
..% placeholders in a page's contents
string | $markdown | Markdown contents of a page |
array | $meta | meta data to use for %meta.*% replacement |
substituted Markdown contents
getTwig() : \Twig_Environment|null
Returns the Twig template engine
This method triggers the onTwigRegistered
event when the Twig template
engine wasn't initiated yet. When initiating Twig, this method also
registers Pico's core Twig filter content
as well as Pico's
\PicoTwigExtension Twig extension.
Twig template engine
getBaseUrl() : string
Returns the base URL of this Pico instance
Security Notice: You MUST configure Pico's base URL explicitly when using the base URL in contexts that are potentially vulnerable to HTTP Host Header Injection attacks (e.g. when generating emails).
the base url
getPageUrl(string $page, array|string $queryData = null, boolean $dropIndex = true) : string
Returns the URL to a given page
This method can be used in Twig templates by applying the link
filter
to a string representing a page ID.
string | $page | ID of the page to link to |
array|string | $queryData | either an array containing properties to create a URL-encoded query string from, or a already encoded string |
boolean | $dropIndex | if the last path component is "index", passing TRUE (default) leads to removing this path component |
URL
getBaseThemeUrl() : string
Returns the URL of the themes folder of this Pico instance
We assume that the themes folder is a arbitrary deep sub folder of the
script's base path (i.e. the directory {@path "index.php"} is in resp.
the httpdocs
directory). Usually the script's base path is identical
to \Pico::$rootDir, but this may aberrate when Pico got installed
as a composer dependency. However, ultimately it allows us to use
\Pico::getBaseUrl() as origin of the theme URL. Otherwise Pico
falls back to the basename of \Pico::$themesDir (i.e. assuming
that Pico::$themesDir
is foo/bar/baz
, the base URL of the themes
folder will be baz/
; this ensures BC to Pico < 2.0). Pico's base URL
always gets prepended appropriately.
the URL of the themes folder
getUrlParameter(string $name, integer|string $filter = '', mixed|array $options = null, integer|string|array<mixed,integer>|array<mixed,string> $flags = null) : mixed
Filters a URL GET parameter with a specified filter
This method is just an alias for \Pico::filterVariable(), see
\Pico::filterVariable() for a detailed description. It can be
used in Twig templates by calling the url_param
function.
string | $name | name of the URL GET parameter to filter |
integer|string | $filter | the filter to apply |
mixed|array | $options | either a associative options array to be used by the filter or a scalar default value |
integer|string|array<mixed,integer>|array<mixed,string> | $flags | flags and flag strings to be used by the filter |
either the filtered data, FALSE if the filter fails, or NULL if the URL GET parameter doesn't exist and no default value is given
getFormParameter(string $name, integer|string $filter = '', mixed|array $options = null, integer|string|array<mixed,integer>|array<mixed,string> $flags = null) : mixed
Filters a HTTP POST parameter with a specified filter
This method is just an alias for \Pico::filterVariable(), see
\Pico::filterVariable() for a detailed description. It can be
used in Twig templates by calling the form_param
function.
string | $name | name of the HTTP POST parameter to filter |
integer|string | $filter | the filter to apply |
mixed|array | $options | either a associative options array to be used by the filter or a scalar default value |
integer|string|array<mixed,integer>|array<mixed,string> | $flags | flags and flag strings to be used by the filter |
either the filtered data, FALSE if the filter fails, or NULL if the HTTP POST parameter doesn't exist and no default value is given
getFiles(string $directory, string $fileExtension = '', integer $order = self::SORT_ASC) : array
Recursively walks through a directory and returns all containing files matching the specified file extension
string | $directory | start directory |
string | $fileExtension | return files with the given file extension only (optional) |
integer | $order | specify whether and how files should be 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 |
list of found files
getFilesGlob(string $pattern, integer $order = self::SORT_ASC) : array
Returns all files in a directory matching a libc glob() pattern
string | $pattern | the pattern to search for; see PHP's glob() function for details |
integer | $order | specify whether and how files should be 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 |
list of found files
triggerEvent(string $eventName, array $params = array()) : void
Triggers events on plugins using the current API version
Plugins using older API versions are handled by \PicoDeprecated. Please note that \PicoDeprecated also triggers custom events on plugins using older API versions, thus you can safely use this method to trigger custom events on all loaded plugins, no matter what API version - the event will be triggered in any case.
You MUST NOT trigger events of Pico's core with a plugin!
string | $eventName | name of the event to trigger |
array | $params | optional parameters to pass |
loadPlugins() : void
Loads plugins from vendor/pico-plugin.php and Pico::$pluginsDir
See \Pico::loadComposerPlugins() for details about plugins loaded
from vendor/pico-plugin.php
(i.e. plugins that were installed using
composer), and \Pico::loadLocalPlugins() for details about plugins
installed to \Pico::$pluginsDir. Pico loads plugins from the
filesystem only if \Pico::$enableLocalPlugins is set to TRUE
(this is the default).
Pico always loads plugins from vendor/pico-plugin.php
first and
ignores conflicting plugins in \Pico::$pluginsDir.
The official PicoDeprecated plugin must be loaded when plugins that use an older API version than Pico's API version (\Pico::API_VERSION) are loaded.
Please note that Pico will change the processing order when needed to incorporate plugin dependencies. See \Pico::sortPlugins() for details.
thrown when a plugin couldn't be loaded
loadComposerPlugins(array<mixed,string> $pluginBlacklist = array()) : array<mixed,string>
Loads plugins from vendor/pico-plugin.php
This method loads all plugins installed using composer and Pico's
picocms/composer-installer
installer by reading the pico-plugin.php
in composer's vendor
dir.
array<mixed,string> | $pluginBlacklist | class names of plugins not to load |
installer names of the loaded plugins
loadLocalPlugins(array<mixed,string> $pluginBlacklist = array()) : void
Loads plugins from Pico::$pluginsDir in alphabetical order
Pico tries to load plugins from <plugin name>/<plugin name>.php
and
<plugin name>.php
only. Plugin names are treated case insensitive.
Pico will throw a RuntimeException if it can't load a plugin.
Plugin files MAY be prefixed by a number (e.g. 00-PicoDeprecated.php
)
to indicate their processing order. Plugins without a prefix will be
loaded last. If you want to use a prefix, you MUST NOT use the reserved
prefixes 00
to 09
. Prefixes are completely optional, however, you
SHOULD take the following prefix classification into consideration:
onPageRendered
eventarray<mixed,string> | $pluginBlacklist | class names of plugins not to load |
thrown when a plugin couldn't be loaded
sortPlugins() : void
Sorts all loaded plugins using a plugin dependency topology
Execution order matters: if plugin A depends on plugin B, it usually means that plugin B does stuff which plugin A requires. However, Pico loads plugins in alphabetical order, so events might get fired on plugin A before plugin B.
Hence plugins need to be sorted. Pico sorts plugins using a dependency topology, this means that it moves all plugins, on which a plugin depends, in front of that plugin. The order isn't touched apart from that, so they are still sorted alphabetically, as long as this doesn't interfere with the dependency topology. Circular dependencies are being ignored; their behavior is undefiend. Missing dependencies are being ignored until you try to enable the dependant plugin.
This method bases on Marc J. Schmidt's Topological Sort library in
version 1.1.0, licensed under the MIT license. It uses the ArraySort
implementation (class \MJS\TopSort\Implementations\ArraySort
).
loadConfig() : void
Loads the config.yml and any other *.yml from Pico::$configDir
After loading {@path "config/config.yml"}, Pico proceeds with any other
existing config/*.yml
file in alphabetical order. The file order is
crucial: Config values which have been set already, cannot be
overwritten by a succeeding file. This is also true for arrays, i.e.
when specifying test: { foo: bar }
in config/a.yml
and
test: { baz: 42 }
in config/b.yml
, {{ config.test.baz }}
will be
undefined!
evaluateRequestUrl() : void
Evaluates the requested URL
Pico 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 to basically remove the ?
from URLs.
However, URL rewriting requires some special configuration of your
webserver, but this should be "basic work" for any webmaster...
With Pico 1.0 you had to setup URL rewriting (e.g. using mod_rewrite
on Apache) in a way that rewritten URLs follow the QUERY_STRING
principles. Starting with version 2.0, Pico additionally supports the
REQUEST_URI
routing method, what allows you to simply rewrite all
requests to just index.php
. Pico then reads the requested page from
the REQUEST_URI
environment variable provided by the webserver.
Please note that QUERY_STRING
takes precedence over REQUEST_URI
.
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.
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
and {{ base_url }}?sub/page
, depending on
enabled URL rewriting. In content files you can use the %base_url%
variable; e.g. %base_url%?sub/page
will be replaced accordingly.
Heads up! Pico always interprets the first parameter as name of the
requested page (provided that the parameter has no value). According to
that you MUST NOT call Pico with a parameter without value as first
parameter (e.g. http://example.com/pico/?someBooleanParam), otherwise
Pico interprets someBooleanParam
as name of the requested page. Use
/pico/?someBooleanParam=
or /pico/?index&someBooleanParam
instead.
readPages() : void
Reads the data of all pages known to Pico
The page data will be an array containing 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 | int | timestamp derived from the Date header |
date | string | date of the page (YAML header) |
date_formatted | string | formatted date of the page |
hidden | bool | this page shouldn't be visible |
raw_content | string | raw, not yet parsed contents of the page |
meta | string[] | parsed meta data of the page |
previous_page | &array[] | reference to the previous page |
next_page | &array[] | reference to the next page |
tree_node | &array[] | reference to the page's tree node |
Please note that the previous_page
and next_page
keys don't
exist until the onCurrentPageDiscovered
event is triggered
(\Pico::discoverPageSiblings()). The tree_node
key doesn't
exit until the onPageTreeBuilt
event is triggered
(\Pico::buildPageTree()).
buildPageTree() : void
Builds a tree structure containing all known pages
Pico's page tree is a list of all the tree's branches (no matter the
depth). Thus, by iterating a array element, you get the nodes of a given
branch. All leaf nodes do represent a page, but inner nodes may or may
not represent a page (e.g. if there's a sub/page.md
, but neither a
sub/index.md
nor a sub.md
, the inner node sub
, that is the parent
of the sub/page
node, represents no page itself).
A page's file path describes its node's path in the tree (e.g. the page
sub/page.md
is represented by the sub/page
node, thus a child of the
sub
node and a element of the sub
branch). However, the index page
of a folder (e.g. sub/index.md
), is not a node of the sub
branch,
but rather of the /
branch. The page's node is not sub/index
, but
sub
. If two pages are described by the same node (e.g. if both a
sub/index.md
and a sub.md
exist), the index page takes precedence.
Pico's main index page (i.e. index.md
) is represented by the tree's
root node /
and a special case: it is the only node of the `` (i.e.
the empty string) branch.
A node is represented by an array with the keys id
, page
and
children
. The id
key contains a string with the node's name. If the
node represents a page, the page
key is a reference to the page's
data array. If the node is a inner node, the children
key is a
reference to its matching branch (i.e. a list of the node's children).
The order of a node's children matches the order in Pico's pages array.
If you want to walk the whole page tree, start with the tree's root node
at $pageTree[""]["/"]
, or rather, use $pages["index"]["tree_node"]
.
The root node's children
key is a reference to the /
branch at
$pageTree["/"]
, that is a list of the root node's direct child nodes
and their siblings.
You MUST NOT iterate the page tree itself (i.e. the list of the tree's branches), its order is undefined and the array will be replaced by a non-iterable data structure with Pico 3.0.
filterVariable(mixed $variable, integer|string $filter = '', mixed|array $options = null, integer|string|array<mixed,integer>|array<mixed,string> $flags = null) : mixed
Filters a variable with a specified filter
This method basically wraps around PHP's filter_var()
function. It
filters data by either validating or sanitizing it. This is especially
useful when the data source contains unknown (or foreign) data, like
user supplied input. Validation is used to validate or check if the data
meets certain qualifications, but will not change the data itself.
Sanitization will sanitize the data, so it may alter it by removing
undesired characters. It doesn't actually validate the data! The
behaviour of most filters can optionally be tweaked by flags.
Heads up! Input validation is hard! Always validate your input data the most paranoid way you can imagine. Always prefer validation filters over sanitization filters; be very careful with sanitization filters, you might create cross-site scripting vulnerabilities!
mixed | $variable | value to filter |
integer|string | $filter | ID (int) or name (string) of the filter to apply; if omitted, the method will return FALSE |
mixed|array | $options | either a associative array
of options to be used by the filter (e.g. |
integer|string|array<mixed,integer>|array<mixed,string> | $flags | either a bitwise disjunction of flags or a string with the significant part of a flag constant (the constant name is the result of "FILTERFLAG" and the given string in ASCII-only uppercase); you may also pass an array of flags and flag strings (optional) |
with a validation filter, the method either returns the validated value or, provided that the value wasn't valid, the given default value or FALSE; with a sanitization filter, the method returns the sanitized value; if no value (i.e. NULL) was given, the method always returns either the provided default value or NULL