-
Notifications
You must be signed in to change notification settings - Fork 617
Open
Description
This issue is intended to collect and discuss ideas about Pico 2.0 3.0 4.0, especially on how we can make Pico's plugin system more flexible than ever. The changes are very substantial and far reaching, therefore they can't make it into the soon-to-be-released Pico 2.0 3.0. The changes break BC, therefore they can't make it into Pico 1.1 2.1 3.1, but Pico 2.0 3.0 4.0.Features planned for Pico 2.0 can be found in #270. Feedback is appreciated! 😃
General
- Move Pico to the
\picocms\Pico
namespace and use a PSR-4 autoloader. UsePicoDeprecated
to also provide all classes in the root namespace (usingclass_alias()
), otherwise this would break all existing plugins. - Add Unit Tests.
- Split
Pico::run()
into multiple public methods ("phases"), but still call them throughPico::run()
(init, request URL, loading contents, evaluating contents (YAML + Markdown), page discovery, page rendering (Twig)). - Use
PicoMarkdownParser
,PicoYamlParser
andPicoTemplateEngine
instead of\Parsedown
,\Symfony\Component\Yaml\Parser
and\Twig_Environment
as type hints and add appropriate wrapper classes for Parsedown, Symfony YAML and Twig, allowing one to extend/replace them completely.- Enable some useful Symfony YAML flags by default (like converting keys to string automatically).
- Also see cancelled "replace YAML/Parsedown/Twig" ToDo below.
- Drop all remaining pre-v1.0 behavior from
PicoDeprecated
, also remove v1.0+ behaviour with a notable performance impact - Think about how users can easily install plugins with dependencies when they aren't using
composer
, but a pre-bundled release - Think about refactoring Pico's plugin discovery process in general: having a lot of (partially duplicate) code for filesystem-based and composer-based plugins is bad, furthermore there are issues with the current system like Error running Pico on Synology Diskstation #538; maybe use extensible iterators?
Allow theme developers to register meta headers and change Twig's default config (maybe using aImplemented with Pico 2.1config.yml
in the theme's dir?)- Allow theme developers to register static pages in Pico's
pico-theme.yml
(e.g. a virtualcontact
page is added to the pages list not requiring acontact.md
with e.g.Template: contact
to exist) - Support relative page URLs in Markdown files
- We must likely hook into Parsedown and make them absolute
- Relative paths to other files pages (e.g.
assets/image.jpg
) should be supported, too - Relative paths must be interpreted so that
content
dir breakouts are stripped, i.e.../page
fromcontent/sub/page.md
ends up ashttps://example.com/pico/page
, whereas../../assets/index.jpg
ends up ashttps://example.com/pico/assets/index.jpg
(instead ofhttps://example.com/assets/index.jpg
)
Page management
- Implement lazy loading using page objects (i.e. use simple objects instead of
$page
arrays)- Performance, Performance, Performance
- Implement
ArrayAccess
; not only to maintain BC, but also to make the object's data easier to use (especially for non-experienced developers)- Also switch a page's meta data to
ArrayAccess
objects and allow case insensitive keys (i.e. it no longer matters whether a meta value isTitle
ortitle
, one can always access it withTitle
,title
, or eventItLe
) - Only propagate
title
,date
,template
andhidden
as page data; everything else (e.g.description
,author
,robots
) gets ordinary YAML metadata and can be accessed viapage.meta
- Also switch a page's meta data to
- Pico initially loads no file contents, only
page.id
andpage.url
are available at first; as soon as something else is accessed, the file's contents are read; depending on what has been accessed, Pico processes either the YAML frontmatter or the Markdown contents (i.e.$page['content']
won't be deprecated anymore). - Plugin developers should be able to implement dynamic values with callbacks (e.g.
PicoPage::addDynamicValue(string $key, callable $callback)
). This also allows plugin developers to implement lazy loading for custom values. - Open question: Should we still allow regular page arrays in the
$pages
array? Probably not: plugin developers will have to differentiate both cases otherwise, what makes the whole feature a pain in the ass for them. Thus we need a conversion method, otherwise we would break all existing plugins... - Page objects don't know their respective next and previous pages, but parent and child pages (see below)
- Implement a lazy page tree (using iterable objects) as better performing alternative to the regular pages array
- Performance, Performance, Performance!
- Rather than always loading the whole page tree (a
Traversable
,ArrayAccess
object like the pages array, see above), nothing is loaded by default. - When accessing the page tree for the first time, Pico discovers only pages directly inside
content/
(with the second level of lazy loading as elucidated above). Pages in sub-directories are accessible through achildren
key. However, the contents of directories aren't discovered until they are explicitly requested by accessing saidchildren
key. - Most themes build their page menu by iterating over pages on the first level anyway. This allows Pico to discover only the pages it needs to know, i.e. just the pages directly inside the
content/
dir and without any sub directory. This should heavily improve performance when a Pico instance is supposed to serve hundreds or thousands of pages. - Even the regular pages array is actually empty in the beginning. However, by accessing the variable (i.e. by iterating over it or by accessing a key) the whole page tree is being loaded (what shatters our efforts). We should encourage plugin/theme developers to use the page tree instead.
- Rather than always loading the whole page tree (a
- Pico manages a global sorted page tree
- Drop support for sorting the pages list globally and rather use concomitant alphabetical sorting; i.e. Pico first discovers
content/index.md
only, then all other pages incontent
(e.g.content/foo.md
) and sorts them alphabetically, then checks for acontent/foo/
directory and discovers all pages in it (e.g.content/foo/bar.md
), etc. and slices the just discovered pages in
- Drop support for sorting the pages list globally and rather use concomitant alphabetical sorting; i.e. Pico first discovers
- Drop support for the
$pages
variable and force users to use thepages()
function- Open question:
PicoDeprecated
should implement it as some magic variable to still support lazy loading (is this even possible?) - The
pages()
function should return an object instead of an array with sortable page objects wrapping the requested page objects; the pages list object can then be sorted and set a page's respective previous and next page to the page wrapper- It should be possible to perform both shallow and recursive sorting
- Open question:
- Open question: How do we make this backwards-compatible with
PicoDeprecated
?- Use
ArrayObject
orArrayIterator
instead of a regular$pages
array. This allows us to convert page arrays to objects as soon as they are added. - However, PHP's built-in array functions (e.g.
array_keys()
) won't work anymore... According to that we must pass a regular array to theonPagesLoaded
event for older plugins, otherwise we would pretty likely break many of them.PicoDeprecated
could then iterate through the$pages
array and convert them appropriately. - Use event system versioning?
- Use
- Refactor the
pages()
function to useoffset
andlength
parameters instead ofdepth
,depthOffset
andoffset
; see Show (grand)parent-page title #627 (comment) and earlier comments- Utilize theme API versioning to actually drop
depth
anddepthOffset
, but usePicoDeprecated
to inject the old function for old themes - Thinking about using API versioning anyway, we could also rename
length
todepth
- it better reflects what it does... But it's not to confuse with the "old"depth
- Taking page objects into consideration we should use those in most cases, i.e. there should be keys to specifically not just access a page's child pages, but ancestors until a user-defined depth
- Utilize theme API versioning to actually drop
- Performance, Performance, Performance!
Plugin event system
- Don't trigger all events on all plugins. Let plugins register the events they want to use instead. This heavily increases performance with a large number of plugins, because
method_exists
calls are comparatively expensive compared to a simpleforeach
per event.- Open question: Either introduce a new
onSinglePluginLoaded
event or a newPicoPluginInterface::getEvents()
method.- The latter breaks BC, but with this new approach, we must refactor
AbstractPicoPlugin::handleEvent()
anyway, therefore we probably can't implement it without breaking BC one way or the other. - We can circumvent this by letting
\AbstractPicoPlugin
and\picocms\Pico\AbstractPlugin
differ in functionality (i.e. the first mentioned implements thegetEvents()
method in a BC way by examining aReflectionClass
of the plugin, or by letting\PicoPluginInterface
lack thegetEvents()
method entirely).
- The latter breaks BC, but with this new approach, we must refactor
- Open question: Either introduce a new
- Allow plugins to return
false
on preliminary events (e.g.onContentLoading
) to prevent Pico from performing a specific processing step (Pico::run()
skipsPico::loadFileContent()
). Returningtrue
ornull
works as with Pico 1.0 and changes nothing. The subsequent event is still triggered (onContentLoaded
), but the payload variable ($rawContent
) is empty. The event is triggered with special priority on this plugin (regardless of the regular processing order), so it can set the variable before any other plugin receives the event.- Example:
- A markdown cache plugin returns
false
duringonMetaParsing
andonContentParsing
to load both meta data and the parsed contents from its cache.
- A markdown cache plugin returns
- Affected Events:
onContentLoading
(completely skipson404Content…
events) andonContentLoaded
on404ContentLoading
andon404ContentLoaded
onMetaParsing
andonMetaParsed
onContentParsing
(simulatesonContentPrepared
),onContentPrepared
andonContentParsed
onPagesLoading
(onSinglePage…
events will be simulated) andonPagesLoaded
onSinglePageLoading
(new event) andonSinglePageLoaded
onPageRendering
andonPageRendered
- Example:
- Allow plugins to return
false
on theonRequestUrl
oronRequestFile
events to completely skip Pico's processing. The only remaining event to trigger isonOutput
(new event that is only triggered when Pico's processing is skipped) right before Pico returns$output
.- Example:
- A static HTML cache plugin returns
false
duringonRequestFile
, bypasses Pico's processing completely and returns the cached contents duringonOutput
.
- A static HTML cache plugin returns
- Example:
New official plugins
- Markdown cache
- Static HTML cache
- Save rendered output of pages to static HTML files
- Rely on OS to detect file changes (last modification time of
.md
files) - Add
Cache: No
meta header to prevent pages from being cached - Add a event to let plugins "register" non-content pages for caching (should be triggered right after
onConfigLoaded
to allow plugins to change their behavior when caching is requested) - Explicitly allow combining statically cached and dynamic pages
- Open questions
- What happens when a page is added (i.e. page navigation changes)?
- What happens when a plugin or theme is added/updated/removed?
- How to determine all URLs that need to be parsed? Markdown files don't necessarily have a 1:1 relation to pages, just think of collections or hidden meta files
- Ignore files and directories starting with a
_
- Allow users to explicitly specify the URL of a page
- Ignore files and directories starting with a
- Use this feature to allow Pico to act as a static website generator (allow plugins to distinct between "static HTML cache" and "static website generator" mode)
- Use Pico (with Travis,
PHP's development server and) for our website rather than Jekyllwget -r
- Use Pico (with Travis,
- Plugin plugins (:smile:):
- Add a
URLs
sequence meta header to support alternative URLs (like Jekyll's Redirect From plugin
- Add a
- Search (using Lucene?)
- Problem: How to determine the URL of a found Markdown file? Markdown files don't necessarily have a 1:1 relation to pages, just think of collections or hidden meta files
- Possible solution: Use a static HTML cache and search in the HTML files
- Possible solution: Do the exact same things as the static HTML cache (see above)
- Multilanguage (
i18n
)- Contents: use language-specific content directories or file extensions?
- Themes: use PHP
intl
andTwig_Extensions_Extension_Intl
- References: How to create multilingual web site? #335
- Performance statistics: See Pico 4.0 and beyond #317 (comment)
- Import plugins to import contents of other CMS (e.g. WordPress, see https://github.com/gilbitron/WordPress-to-Pico-Exporter)
- Use a HTML to Markdown converter?
- Data Files
- Support independent (meta) data files (e.g.
content/catalog.yml
) - The files are accessed similar to pages (e.g.
{{ data.catalog }}
). - A data file named after a markdown file (e.g. both
content/catalog.yml
andcontent/catalog.md
exist) is non-recursively merged into the page's meta data (i.e. into{{ pages.catalog.meta }}
). However, the YAML frontmatter takes preference and the data file can still be accessed via{{ data.catalog }}
. The same happens for all pages (non-recursive) in a directory if there's a data file with the same name as the directory (e.g._collection.yml
and_collection/
directory). You can enforce recursion for e.g._collection/subdir/
by creating_collection/subdir.yml
.
- Support independent (meta) data files (e.g.
- Redirect pages (like Jekyll's Redirect From plugin)
Not planned anymore
Allow a single plugin to hook into Pico to basically replace YAML/Parsedown/Twig with something different. Rather than hooking into the instantiation of\Symfony\Component\Yaml\Parser
inPico::parseFileMeta()
,Pico::registerParsedown()
and/orPico::registerTwig()
, it should be possible to replace thePico::parseFileMeta()
method, thePico::prepareFileContent()
/Pico::parseFileContent()
methods (+ themarkdown
Twig filter inPicoTwigExtension
) and/or the call ofPico::$twig->render()
. Otherwise the plugin needs to re-implement the internal structures and workings of the YAML parser/Parsedown/Twig, what isn't desirable. I'm currently not sure about how this interacts with the$twig
parameter of theonPageRendering
event (maybe drop the parameter and add a newonTwigRegistered
event?). The plugin needs to be registered explicitly inconfig/config.php
to work.Example (quite a stretch):Instead of parsing Markdown, parse MediaWiki syntax.
icro and Croydon