Compare commits

...

125 commits

Author SHA1 Message Date
Maya McDougall
09aa825787
Update Twig URL 2022-07-27 15:17:31 -04:00
Maya McDougall
ff5e345e58
Added Temporary PHP 8.0 Notice 2022-02-06 16:34:18 -05:00
Daniel Rudolf
7e3c6a7b3e
Merge pull request #599 from dipohl/patch-1
Update web links in index.md
2021-08-15 13:38:01 +02:00
dipohl
2e425dc8ef
Update index.md
Checked all web links and updated them where necessary especially eliminating some 404 errors.
2021-08-15 11:02:43 +02:00
Daniel Rudolf
09fbaaaf33
IRC: Switch from Freenode to Libera.Chat 2021-08-13 17:19:24 +02:00
Daniel Rudolf
d0f32c0a2b
Create SECURITY.md 2021-06-21 20:45:31 +02:00
Daniel Rudolf
d8470f9f5c
Replace Probot Stale with GitHub Action
Looks like Probot Stale is abandoned... Too bad. 😒
2021-03-09 13:40:16 +01:00
Daniel Rudolf
221625efac
CONTRIBUTING.md: Rename 'type: Question' label to 'type: Support' 2020-11-11 16:10:46 +01:00
Daniel Rudolf
7228129cad
Version 2.1.4
```
* [Changed] Silence PHP errors in Parsedown
* [Fixed] #560: Improve charset guessing for formatted date strings using
          `strftime()` (Pico always uses UTF-8, but `strftime()` might not)
```
2020-08-29 16:15:52 +02:00
Daniel Rudolf
b95cfe0a0e
Update Pico::VERSION and Pico::VERSION_ID 2020-08-29 16:15:37 +02:00
Daniel Rudolf
e4741d0fde
Update CHANGELOG.md 2020-08-29 16:14:52 +02:00
Daniel Rudolf
1916dc5645
Silence PHP notices in upstream Parsedown
Parsedown is a still unresolved issue in whole, this at least ensures that we don't fill up logfiles with useless errors...
2020-08-29 16:07:10 +02:00
Daniel Rudolf
4be1f6ae90
Try to proberly encode formatted date strings returned by strftime()
Fixes #560
2020-08-29 16:04:10 +02:00
Daniel Rudolf
0e2b124b8e
Version 2.1.3
```
* [New] Add `locale` option to `config/config.yml`
* [Changed] Improve Pico docs
```
2020-07-10 18:51:38 +02:00
Daniel Rudolf
590d467347
Update Pico::VERSION and Pico::VERSION_ID 2020-07-10 18:51:32 +02:00
Daniel Rudolf
649feed2f6
Update CHANGELOG.md 2020-07-10 18:50:35 +02:00
Dimitri Merejkowsky
1345d6ac82 More inclusive README
Replace 'you guys' by something more gender neutral.
2020-06-29 21:40:47 +02:00
Daniel Rudolf
60f27dfe85
Merge pull request #546 from asdfuser/master
Add option to set a locale
2020-06-14 19:03:08 +02:00
Daniel Willmann
b2df860546 Add option to set a locale
As mentioned in https://www.php.net/manual/en/function.basename.php both
basename() and dirname() are locale aware.
An incorrect locale can cause the page tree to behave in strange ways.

For example the structure

/über-uns
 |-index.md
 |-impressum.md

could lead to the page impressum not being a child of über-uns.
2020-06-14 17:34:46 +02:00
Daniel Rudolf
1aca13e83d
Version 2.1.2
```
* [Fixed] Fix DummyPlugin declaring API version 3
```
2020-04-10 23:18:00 +02:00
Daniel Rudolf
b892d6bb11
Update Pico::VERSION and Pico::VERSION_ID 2020-04-10 23:17:45 +02:00
Daniel Rudolf
0ddde859e8
Update CHANGELOG.md 2020-04-10 23:17:01 +02:00
Daniel Rudolf
6c746fabb6
Fix DummyPlugin declaring API version 3 2020-04-10 23:14:33 +02:00
Daniel Rudolf
af9c09e440
Add composer.json branch alias for Pico 3.0 2020-03-29 16:04:38 +02:00
Daniel Rudolf
5d6257feac
Update composer.json branch aliases 2020-03-29 14:35:52 +02:00
Daniel Rudolf
b49936fb3a
Version 2.1.1
```
* [Fixed] Require Parsedown 1.8.0-beta-7 and Parsedown Extra 0.8.0-beta-1 due
          to changes in Parsedown and Parsedown Extra breaking BC beyond repair
* [Changed] #523: Check for hidden pages based on page ID instead of full paths
* [Changed] Improve Pico docs
```
2019-12-31 16:33:55 +01:00
Daniel Rudolf
6672fb2277
Update Pico::VERSION and Pico::VERSION_ID 2019-12-31 16:33:45 +01:00
Daniel Rudolf
4c97f69ef8
Update CHANGELOG.md 2019-12-31 16:32:22 +01:00
Daniel Rudolf
8a44584291
Composer: Force Parsedown 1.8.0-beta-7 and Parsedown Extra 0.8.0-beta-1
A few days ago Parsedown released new ("stable") versions of Parsedown and Parsedown Extra: Parsedown 1.7.4 and Parsedown Extra 0.8.1. Parsedown 1.7.4 backports some features of Parsedown 1.8.0-beta-7 and is compatible with Parsedown Extra 0.8.1. However, due to these changes Parsedown Extra 0.8.1 now breaks compatibility with Parsedown 1.8.0-beta-7. Parsedown's release process is messed up beyond repair... Not sure what we're going to do now, this needs some research whether we can downgrade to Parsedown 1.7.4 without breaking things (what would require us to release a new major release, i.e. Pico 3.0). For now we're pinning Parsedown 1.8.0-beta-7 and Parsedown Extra 0.8.0-beta-1 as of Pico 2.1.0.
2019-12-31 16:25:52 +01:00
Daniel Rudolf
447479d973
Test hidden page requests on page ID
Fixes #523
2019-12-31 00:36:22 +01:00
Daniel Rudolf
1d250a2f7c
Travis CI: Switch to PHP 5.3 for branch deployment (just like release deployment) 2019-12-02 19:28:16 +01:00
Daniel Rudolf
86f82b930d
Travis CI: Update to Ubuntu Bionic, add HHVM 3.24 LTS 2019-12-02 19:22:57 +01:00
Daniel Rudolf
95146669f7
README.md: Add instructions for using a Git repo 2019-12-02 18:37:56 +01:00
Daniel Rudolf
ae1225f725
Travis CI: Add PHP 7.4 2019-11-30 16:43:00 +01:00
Daniel Rudolf
2661730ef5
CONTRIBUTING.md: Add `info: Pico CMS for Nextcloud\' label 2019-11-29 15:42:33 +01:00
Daniel Rudolf
41a4da5229
Merge pull request #515 from picocms/pico-2.1
This is **Pico 2.1 - small, but mighty!**

If you want to upgrade to Pico 2.1, simply follow the usual upgrade instructions for minor releases. Installing Pico is as easy as before. You can find more extensive upgrade instructions as well as a complete list of all additions and changes in Pico's [upgrade docs](http://picocms.org/in-depth/upgrade-pico-21/) - even though this is a minor release, it's still a lot of new and improved stuff!

You might also want to give [Pico CMS for Nextcloud `v1.0.0`](https://apps.nextcloud.com/apps/cms_pico) a try - it's now an official part of Pico. You can find more info at [picocms.org](http://picocms.org/plugins/#pico-cms-for-nextcloud)
2019-11-25 01:11:59 +01:00
Daniel Rudolf
232a90bdf8
Version 2.1.0
```
* [Changed] Add Pico's official logo and tagline to `content-sample/_meta.md`
* [Changed] Improve `content-sample/theme.md` to show Pico's official logo and
            the usage of the new image utility classes of Pico's default theme
* [Changed] Improve Pico docs and PHPDoc class docs
```
2019-11-25 00:43:58 +01:00
Daniel Rudolf
a87e40fbf0
Update Pico::VERSION 2019-11-25 00:43:58 +01:00
Daniel Rudolf
e4e249ae27
Update CHANGELOG.md 2019-11-25 00:43:58 +01:00
Daniel Rudolf
636e8d2a8a
Travis CI: Force Composer minimum stability <= beta
Unfortunately we must force Composer minimum stability <= beta due to Parsedown 1.8 currently being in beta. Composer AFAIK can't decide this on a per-dependency basis...
2019-11-25 00:43:53 +01:00
Daniel Rudolf
a5ff37e380
Travis CI: Remove 'sudo' requirement for branch deployment 2019-11-25 00:41:08 +01:00
Daniel Rudolf
b25225bbf4
README.md: Update screenshot 2019-11-24 23:40:51 +01:00
Daniel Rudolf
35bfdf51b7
Sample contents: Add usage of Pico's pages variable 2019-11-24 22:20:48 +01:00
Daniel Rudolf
bd0a257784
Update CHANGELOG.md 2019-11-23 19:45:11 +01:00
Daniel Rudolf
47d533982d
Replace Placeholder logo in content-sample/theme.md by Pico's logo
Also shows the new image utility classes of Pico's default theme

Co-Authored-By: type76 <osmanjaro@gmail.com>
2019-11-23 19:39:16 +01:00
Daniel Rudolf
558840643b
Add Pico's logo and tagline to the sample contents 2019-11-23 19:38:45 +01:00
Daniel Rudolf
cc6a478939
README: Mention requirement of PHP extensions 'dom' and 'mbstring'
Resolves #517
2019-11-18 16:49:08 +01:00
Daniel Rudolf
c02e3fa7a4
Various small improvements 2019-11-18 09:55:36 +01:00
Daniel Rudolf
03d466c117
Fix Pico's sample contents 2019-11-13 00:23:43 +01:00
Daniel Rudolf
c99f3cbbdf
Update @version phpDoc class docs 2019-11-11 19:02:11 +01:00
Daniel Rudolf
3b8d89fa30
composer.json: Adding myself as lead dev 2019-11-11 18:33:22 +01:00
Daniel Rudolf
420ede0daa
composer.json: Remove 'ext-dom' extension
Pico doesn't require 'ext-dom' itself, Parsedown Extra does; Parsedown Extra didn't declare this dependency before, but Parsedown Extra 0.8 finally does.
2019-11-10 17:24:25 +01:00
Daniel Rudolf
a04bc6c6b8
Version 2.1.0-beta.1
```
* [New] Add `assets_dir`, `assets_url` and `plugins_url` config params
* [New] Add `%config.*%` Markdown placeholders for scalar config params and the
        `%assets_url%`, `%themes_url%` and `%plugins_url%` placeholders
* [New] Add `content-sample/theme.md` for theme testing purposes
* [New] Introduce API versioning for themes and support theme-specific configs
        using the new `pico-theme.yml` in a theme's directory; `pico-theme.yml`
        allows a theme to influence Pico's Twig config, to register known meta
        headers and to provide defaults for theme config params
* [New] Add `assets_url`, `themes_url` and `plugins_url` Twig variables
* [New] Add `pages` Twig function to deal with Pico's page tree; this function
        replaces the raw usage of Pico's `pages` array in themes
* [New] Add `url` Twig filter to replace URL placeholders (e.g. `%base_url%`)
        in strings using the new `Pico::substituteUrl()` method
* [New] Add `onThemeLoading` and `onThemeLoaded` events
* [New] Add `debug` config param and the `Pico::isDebugModeEnabled()` method,
        cehcking the `PICO_DEBUG` environment variable, to enable debugging
* [New] Add new `Pico::getNormalizedPath()` method to normalize a path; this
        method should be used to prevent content dir breakouts when dealing
        with paths provided by user input
* [New] Add new `Pico::getUrlFromPath()` method to guess a URL from a file path
* [New] Add new `Pico::getAbsoluteUrl()` method to make a relative URL absolute
* [New] #505: Create pre-built `.zip` release archives
* [Fixed] #461: Proberly handle content files with a UTF-8 BOM
* [Changed] Introduce API version 3
* [Changed] Rename `theme_url` config param to `themes_url`; the `theme_url`
            Twig variable and Markdown placeholder are kept unchanged
* [Changed] Update to Parsedown Extra 0.8 and Parsedown 1.8 (both still beta)
* [Changed] Enable Twig's `autoescape` feature by default; outputting a
            variable now causes Twig to escape HTML markup; Pico's `content`
            variable is a notable exception, as it is marked as being HTML safe
* [Changed] Rename `prev_page` Twig variable to `previous_page`
* [Changed] Mark `markdown` and `content` Twig filters as being HTML safe
* [Changed] Add `$singleLine` param to `markdown` Twig filter as well as the
            `Pico::parseFileContent()` method to parse just a single line of
            Markdown input
* [Changed] Add `AbstractPicoPlugin::configEnabled()` method to check whether
            a plugin should be enabled or disabled based on Pico's config
* [Changed] Deprecate the use of `AbstractPicoPlugin::__call()`, use
            `PicoPluginInterface::getPico()` instead
* [Changed] Update to Twig 1.36 as last version supporting PHP 5.3, use a
            Composer-based installation to use a newer Twig version
* [Changed] Add `$basePath` and `$endSlash` params to `Pico::getAbsolutePath()`
* [Changed] Deprecate `Pico::getBaseThemeUrl()`
* [Changed] Replace various `file_exists` calls with proper `is_file` calls
* [Changed] Refactor release & build system
* [Changed] Improve PHP class docs
* [Changed] Various small improvements
* [Removed] Remove superfluous `base_dir` and `theme_dir` Twig variables
* [Removed] Remove `PicoPluginInterface::__construct()`
```
2019-11-04 01:49:11 +01:00
Daniel Rudolf
29f2e95160
Update Pico::VERSION 2019-11-04 01:49:11 +01:00
Daniel Rudolf
58ec760f04
Update CHANGELOG.md 2019-11-04 01:49:11 +01:00
Daniel Rudolf
db1c6301e6
Build system: Fix release package deployment 2019-11-04 01:49:03 +01:00
Daniel Rudolf
03d475c31c
Update Pico's sample contents to reflect the changes of Pico 2.1 2019-11-04 00:36:05 +01:00
Daniel Rudolf
dec44817a5
Build system: Add $PROJECT_REPO_TAG param to release.sh call 2019-11-03 21:18:28 +01:00
Daniel Rudolf
58f615403e
Refactor Build system
- Separate Travis branch deployment and release deployment stages (also makes `deploy.sh` obsolete)
- Add `clean.sh` and `release.sh` scripts to allow users to create "release" packages locally
- Use `setup/*.sh` scripts to check and install build dependencies (like PHP_CodeSniffer, phpDocumentor and cloc)
- Use `create-release.sh` of `picocms/ci-tools` to create release archives
- Streamline script usage

Use the following to test Pico and to create a "release" package locally:

```sh
cd ~/My-Pico-Workspace/Components/pico

ln -rs ../ci-tools .build/ci-tools

. ./.build/ci-tools/init/local.sh.inc
. ./.build/init.sh.inc

phpcs --standard=.phpcs.xml "$PICO_PROJECT_DIR"

clean.sh
release.sh
```
2019-11-03 20:03:48 +01:00
Daniel Rudolf
7bbd8736d1
Update CHANGELOG.md 2019-10-26 14:09:58 +02:00
Daniel Rudolf
e0415c8c1d
Mark Pico's content Twig variable as being safe
This no longer requires themes to output the `content` variable using `{{ content|raw }}`, theme developers can use `{{ content }}` as before.
2019-10-26 14:02:11 +02:00
Daniel Rudolf
3480a520d9
Build system: Use PHP 5.3 to create pre-built release packages
Otherwise Composer downloads a newer version of Twig which isn't compatible with PHP 5.3. Since we don't pin down specific versions of our dependencies, Composer-based installations might use newer versions of Twig which aren't compatible with PHP 5.3. Raising the PHP requirements requires a new major version, something that will definitly happen with Pico 3.0.
2019-10-26 14:00:45 +02:00
Daniel Rudolf
2a23edde4e
Fix code formatting 2019-10-26 11:34:58 +02:00
Daniel Rudolf
23000af64e
Add $singleLine param to Twig markdown parser
This allows you to parse just a single line of Markdown, i.e. the parsed output won't include a HTML paragraph element.
2019-10-24 13:03:38 +02:00
Daniel Rudolf
be0812fb55
Mark Twig content filter as HTML safe 2019-10-24 12:05:08 +02:00
Daniel Rudolf
cb3bdd149f
Mark Twig markdown filter as HTML safe 2019-10-24 12:02:23 +02:00
Daniel Rudolf
d2573c5df5
Fix $this->config['twig_config'] handling in Pico::loadTheme() 2019-10-20 19:17:42 +02:00
Daniel Rudolf
9c00ac4191
Travis CI: Remove not-yet-released PHP 7.4 2019-10-20 15:54:31 +02:00
Daniel Rudolf
b088555d17
Create .github/FUNDING.yml 2019-10-20 15:52:41 +02:00
Daniel Rudolf
1d5aba46af
Support content files with UTF-8 BOM
Resolves #461
2019-10-20 15:16:40 +02:00
Daniel Rudolf
d95c9d3708
Add content-sample/theme.md
The purpose of `theme.md` is to aid theme development - on this page you'll find basically every format that is possible with Markdown. If you develop a theme, you should make sure that all examples below show decent. The page doesn't show up in the website's menu due to `hidden: true` in the page's YAML header.
2019-10-12 19:54:06 +02:00
Daniel Rudolf
87bcff1654
Various small improvements 2019-10-12 15:54:05 +02:00
Daniel Rudolf
e4e6f0f5f0
Merge branch 'master' into pico-2.1
Conflicts:
	config/config.yml.template
2019-10-12 15:05:02 +02:00
Daniel Rudolf
6ffbbec689
Improve theme API version retrieval 2019-10-12 15:01:16 +02:00
Daniel Rudolf
87ced8c8bd
Improve phpDoc class docs 2019-10-01 13:29:16 +02:00
Daniel Rudolf
7684fc455a
Improve index.php error message for a missing 'vendor/autoload.php' 2019-10-01 13:29:06 +02:00
Daniel Rudolf
92a8a299f8
Travis CI: Add PHP 7.4, improve deployment logging 2019-10-01 13:28:21 +02:00
Daniel Rudolf
681ad27158
🎉 Add Twig pages function
This function should be used most of the time when dealing with Pico's pages array, as it allows one to easily traverse Pico's pages tree (see `Pico::getPageTree()`) to retrieve a subset of Pico's pages array in a very convenient and performant way.
2019-09-22 18:55:35 +02:00
Daniel Rudolf
b27b4f388a
🎉 Add Pico theme API versioning and add pico-theme.yml
- Add pico-theme.yml with a theme's API version, theme-specific default Twig config, registering theme-specific custom meta headers and defaults for Pico's `theme_config` config
- Add new `onThemeLoading(&$theme)` and `onThemeLoaded($theme, $themeApiVersion, &$themeConfig)` events
- Enable Twig autoescaping by default
2019-09-22 18:49:37 +02:00
Robbert
c99e3e639d Fixed typo in config.yml (#510) 2019-09-14 22:18:23 +02:00
Daniel Rudolf
c1113a780c
Fix @deprecated notice for Pico::getBaseThemeUrl() and AbstractPicoPlugin::__call() 2019-09-12 14:00:58 +02:00
Daniel Rudolf
d0b637f686
Build system: Use PHP 5.6 for deployment due to broken phpDocumentor v2.9
phpDocumentor v3.0 (currently in alpha) is broken, too, generating class docs without a single working link
2019-09-12 13:46:22 +02:00
Daniel Rudolf
cd3d3dcec5
Bump API version
Due to ad729a99c4 and 33117be981
2019-09-12 12:45:35 +02:00
Daniel Rudolf
17aba01513
Various small improvements 2019-09-12 12:42:01 +02:00
Daniel Rudolf
fd97c70502
Deprecated AbstractPicoPlugin::__call() in favour of PicoPluginInterface::getPico() 2019-09-12 12:41:47 +02:00
Daniel Rudolf
581a3a0609
Add AbstractPicoPlugin::configEnabled() 2019-09-12 12:38:59 +02:00
Daniel Rudolf
bbccb374de
phpDoc class docs: Use {@inheritDoc} in AbstractPicoPlugin 2019-09-12 12:38:59 +02:00
Daniel Rudolf
d72bc24ab3
Remove PicoPluginInterface::__construct()
It doesn't really matter how the current Pico instance is injected into the plugin unless PicoPluginInterface::getPico() is implemented
2019-09-12 12:38:59 +02:00
Daniel Rudolf
eca06a38a9
phpDoc class docs: Remove superflous @return void 2019-09-12 12:38:59 +02:00
Daniel Rudolf
56659ab95d
Build system: Don't use Composer for dev dependencies
PHP_CodeSniffer and phpDocumentor are external tools which should never affect Pico's environment, thus we rather use PHARs in the future.
2019-09-12 12:38:56 +02:00
TheTechRobo
b1ec1e55e1 Update README.md (#507) 2019-09-03 09:53:45 +02:00
Daniel Rudolf
6e6d80c044
Add Pico::substituteUrl() and url Twig filter
Allows theme developers and users to use URL placeholders like `%base_url%` in meta headers, e.g. to include images.
2019-08-30 16:37:01 +02:00
Daniel Rudolf
715cb83431
Improve class docs of Pico::getBaseThemeUrl() and Pico::getUrlFromPath() 2019-08-29 23:07:16 +02:00
Daniel Rudolf
8d6e9ac31e
Replace file_exists() by is_file() 2019-08-29 23:06:27 +02:00
Daniel Rudolf
641cae849b
Travis CI: Additionally create .zip release archives 2019-08-29 16:42:18 +02:00
Daniel Rudolf
c9a3f84673
Sync config/config.yml.template with Pico::loadConfig() 2019-07-14 16:16:57 +02:00
Daniel Rudolf
8c8c6e33f7
composer.json: Remove 3.0.x-dev alias 2019-07-14 15:36:02 +02:00
Daniel Rudolf
6476d6507d
Re-add deprecated Pico::getBaseThemeUrl() to maintain BC 2019-07-14 15:21:12 +02:00
Daniel Rudolf
ae82c16369
composer.json: Add 2.1.x-dev alias for pico-2.1 branch 2019-07-14 15:20:39 +02:00
Daniel Rudolf
3eab6c58d0
Bump version to 2.1.0-nightly 2019-07-14 15:19:59 +02:00
Daniel Rudolf
f3b5a92247
Fix directory separator in Pico::getUrlFromPath() 2019-07-14 14:56:14 +02:00
Daniel Rudolf
ad729a99c4
Remove base_dir and theme_dir Twig variables
These variables aren't really needed in Twig and can still be accessed using $config
2019-07-14 14:56:14 +02:00
Daniel Rudolf
33117be981
Config: Rename theme_url to themes_url, add plugins_url, assets_url and assets_dir 2019-07-14 14:56:14 +02:00
Daniel Rudolf
38bb0a4ac7
Various small improvements 2019-04-30 15:30:00 +02:00
Daniel Rudolf
bb1b8639bd
Add Pico::getUrlFromPath() and Pico::getAbsoluteUrl(), replacing Pico::getBaseThemeUrl() 2019-04-30 15:26:31 +02:00
Daniel Rudolf
f016c8a937
Add Pico::getNormalizedPath() 2019-04-30 15:22:49 +02:00
Daniel Rudolf
8ce3b0c224
Add debug mode
You can enable Pico's debug mode by setting the PICO_DEBUG environment variable. At the moment this just enables Twig's debug mode.
2019-03-28 20:12:46 +01:00
Daniel Rudolf
edf849725d
Config template: Add more Twig config options 2019-03-28 20:11:46 +01:00
Daniel Rudolf
008ca6f41b
Pico::loadConfig(): Make twig cache path absolute 2019-03-28 19:14:23 +01:00
Daniel Rudolf
c91518a7c8
composer.json: Update Parsedown Extra 0.8 and Parsedown 1.8 version constraints 2019-03-11 23:28:09 +01:00
Daniel Rudolf
787344a526
Rename prev_page Twig variable to previous_page 2019-03-11 23:02:57 +01:00
Daniel Rudolf
34ae8e8812
Support %config.*% Markdown placeholders 2019-03-11 23:02:45 +01:00
Daniel Rudolf
8cb37d38ba
composer.json: Add 3.0.x-dev alias for pico-3.0 branch 2019-03-11 23:02:02 +01:00
Daniel Rudolf
2cf60e25af
Build system: Small improvement 2019-02-21 10:35:54 +01:00
Daniel Rudolf
7c1e889717
Travis CI: Remove hhvm master and nightly, add HHVM 3.30 and 3.27
HHVM 4 no longer supports execution of PHP code. HHVM 3.30 and 3.27 are the only remaining still supported HHVM versions with PHP support. They will reach end-of-life in the course of 2019.
2019-02-21 10:35:11 +01:00
Daniel Rudolf
e697f56d10
Version 2.0.5-beta.1
```
* [New] Add PHP 7.3 tests
* [New] Add `2.0.x-dev` alias for master branch to `composer.json`
* [Changed] Update to Parsedown Extra 0.8 and Parsedown 1.8 (both still beta)
* [Changed] Improve release & build process
```
2019-01-03 13:15:49 +01:00
Daniel Rudolf
38b6aef40a
Update CHANGELOG.md 2019-01-03 13:15:44 +01:00
Daniel Rudolf
6dd6da655f
Build system: Allow pre-release dependencies when creating pre-releases 2019-01-03 13:15:18 +01:00
Daniel Rudolf
feba0b32df
Update Pico::VERSION and Pico::VERSION_ID 2019-01-03 01:34:16 +01:00
Daniel Rudolf
1c904b5215
Update CHANGELOG.md 2019-01-03 01:12:35 +01:00
Daniel Rudolf
a327977696
Update to Parsedown Extra 0.8 and Parsedown 1.8 (both currently in beta) 2019-01-03 01:00:25 +01:00
Daniel Rudolf
d663553c34
composer.json: Add 2.0.x-dev alias for master branch 2019-01-03 00:23:26 +01:00
Daniel Rudolf
5955c8c322
Update CHANGELOG.md 2018-12-31 16:20:28 +01:00
Daniel Rudolf
061a11636f
Travis CI: Add PHP 7.3 2018-12-23 23:43:13 +01:00
Daniel Rudolf
c889f5b475
Add IRC Logger
All credit goes to François Beerten from https://colabti.org and his awesome IRC Logger 2 project (https://colabti.org/ilogger2/IrcLogger2.html)
2018-12-23 23:43:02 +01:00
30 changed files with 1494 additions and 546 deletions

25
.build/clean.sh Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
[ -n "$PICO_BUILD_ENV" ] || { echo "No Pico build environment specified" >&2; exit 1; }
# parameters
ARCHIVE_DIR="${1:-$PICO_PROJECT_DIR}" # directory to create release archives in
# print parameters
echo "Cleaning up build environment..."
printf 'PICO_DEPLOY_DIR="%s"\n' "$PICO_DEPLOY_DIR"
printf 'PICO_BUILD_DIR="%s"\n' "$PICO_BUILD_DIR"
printf 'ARCHIVE_DIR="%s"\n' "$ARCHIVE_DIR"
echo
echo "Removing deployment directory..."
[ ! -d "$PICO_DEPLOY_DIR" ] || rm -rf "$PICO_DEPLOY_DIR"
echo "Removing build directory..."
[ ! -d "$PICO_BUILD_DIR" ] || rm -rf "$PICO_BUILD_DIR"
echo "Removing release archives..."
find "$ARCHIVE_DIR" -mindepth 1 -maxdepth 1 \
\( -name 'pico-release-*.tar.gz' -o -name 'pico-release-*.zip' \) \
-delete

View file

@ -1,74 +0,0 @@
#!/usr/bin/env bash
set -e
export PATH="$PICO_TOOLS_DIR:$PATH"
. "$PICO_TOOLS_DIR/functions/parse-version.sh.inc"
# parameters
ARCHIVE="$1" # release archive file name
if [ -z "$ARCHIVE" ]; then
echo "Unable to create release archive: No file name specified" >&2
exit 1
fi
# parse version
if ! parse_version "$PROJECT_REPO_TAG"; then
echo "Unable to create release archive: Invalid version '$PROJECT_REPO_TAG'" >&2
exit 1
fi
# clone repo
github-clone.sh "$PICO_BUILD_DIR" "https://github.com/$RELEASE_REPO_SLUG.git" "$RELEASE_REPO_BRANCH"
cd "$PICO_BUILD_DIR"
# force Pico version
echo "Updating composer dependencies..."
composer require --no-update \
"picocms/pico $VERSION_FULL@$VERSION_STABILITY" \
"picocms/pico-theme $VERSION_FULL@$VERSION_STABILITY" \
"picocms/pico-deprecated $VERSION_FULL@$VERSION_STABILITY"
echo
# install dependencies
echo "Running \`composer install\`..."
composer install --no-suggest --prefer-dist --no-dev --optimize-autoloader
echo
# prepare release
echo "Replacing 'index.php'..."
cp vendor/picocms/pico/index.php.dist index.php
echo "Adding 'README.md', 'CONTRIBUTING.md', 'CHANGELOG.md'..."
cp vendor/picocms/pico/README.md README.md
cp vendor/picocms/pico/CONTRIBUTING.md CONTRIBUTING.md
cp vendor/picocms/pico/CHANGELOG.md CHANGELOG.md
echo "Preparing 'composer.json' for release..."
composer require --no-update \
"picocms/pico ^$VERSION_MILESTONE" \
"picocms/pico-theme ^$VERSION_MILESTONE" \
"picocms/pico-deprecated ^$VERSION_MILESTONE"
echo "Removing '.git' directory..."
rm -rf .git
echo "Removing '.git' directories of dependencies..."
find vendor/ -type d -path 'vendor/*/*/.git' -print0 | xargs -0 rm -rf
find themes/ -type d -path 'themes/*/.git' -print0 | xargs -0 rm -rf
find plugins/ -type d -path 'plugins/*/.git' -print0 | xargs -0 rm -rf
echo
# create release archive
echo "Creating release archive '$ARCHIVE'..."
if [ -e "$ARCHIVE" ]; then
echo "Unable to create release archive: File exists" >&2
exit 1
fi
find . -mindepth 1 -maxdepth 1 -printf '%f\0' \
| xargs -0 -- tar -czf "$ARCHIVE" --
echo

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
export PATH="$PICO_TOOLS_DIR:$PATH"
[ -n "$PICO_BUILD_ENV" ] || { echo "No Pico build environment specified" >&2; exit 1; }
# get current Pico milestone
VERSION="$(php -r "require_once('$PICO_PROJECT_DIR/lib/Pico.php'); echo Pico::VERSION;")"

View file

@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -e
[ -n "$PICO_BUILD_ENV" ] || { echo "No Pico build environment specified" >&2; exit 1; }
DEPLOY_FULL="true"
if [ "$DEPLOY_PHPDOC_RELEASES" != "true" ]; then
echo "Skipping phpDoc release deployment because it has been disabled"
@ -31,10 +33,9 @@ if [ "$DEPLOY_FULL" != "true" ]; then
echo
fi
export PATH="$PICO_TOOLS_DIR:$PATH"
# parse version
. "$PICO_TOOLS_DIR/functions/parse-version.sh.inc"
# parse version
if ! parse_version "$PROJECT_REPO_TAG"; then
echo "Invalid version '$PROJECT_REPO_TAG'; aborting..." >&2
exit 1

View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
if [ -n "$PROJECT_REPO_TAG" ]; then
exec "$(dirname "$0")/deploy-release.sh"
else
exec "$(dirname "$0")/deploy-branch.sh"
fi

19
.build/init.sh.inc Normal file
View file

@ -0,0 +1,19 @@
if [ -z "$PICO_BUILD_ENV" ]; then
echo "No Pico build environment specified" >&2
exit 1
fi
# add project build dir to $PATH
export PATH="$PICO_PROJECT_DIR/.build:$PATH"
# set environment variables
__picocms_cmd export RELEASE_REPO_SLUG="${RELEASE_REPO_SLUG:-picocms/pico-composer}"
__picocms_cmd export RELEASE_REPO_BRANCH="${RELEASE_REPO_BRANCH:-master}"
if [ "$PROJECT_REPO_SLUG" != "picocms/Pico" ]; then
__picocms_cmd export DEPLOY_REPO_SLUG="${DEPLOY_REPO_SLUG:-$PROJECT_REPO_SLUG}"
__picocms_cmd export DEPLOY_REPO_BRANCH="${DEPLOY_REPO_BRANCH:-gh-pages}"
else
__picocms_cmd export DEPLOY_REPO_SLUG="${DEPLOY_REPO_SLUG:-picocms.github.io}"
__picocms_cmd export DEPLOY_REPO_BRANCH="${DEPLOY_REPO_BRANCH:-master}"
fi

View file

@ -1,31 +1,17 @@
#!/usr/bin/env bash
set -e
[ -n "$PICO_BUILD_ENV" ] || { echo "No Pico build environment specified" >&2; exit 1; }
# setup build system
echo "Installing build dependencies..."
case "$1" in
"--deploy")
echo "Synchronizing package index files..."
sudo apt-get update
echo "Installing packages..."
sudo apt-get install -y cloc
;;
esac
echo
# setup composer
echo "Setup Composer..."
# let composer use our GITHUB_OAUTH_TOKEN
if [ -n "$GITHUB_OAUTH_TOKEN" ]; then
composer config --global github-oauth.github.com "$GITHUB_OAUTH_TOKEN"
fi
BUILD_REQUIREMENTS=( --phpcs )
[ "$1" != "--deploy" ] || BUILD_REQUIREMENTS+=( --cloc --phpdoc )
"$PICO_TOOLS_DIR/setup/$PICO_BUILD_ENV.sh" "${BUILD_REQUIREMENTS[@]}"
# set COMPOSER_ROOT_VERSION when necessary
if [ -z "$COMPOSER_ROOT_VERSION" ] && [ -n "$PROJECT_REPO_BRANCH" ]; then
echo "Setting up Composer..."
PICO_VERSION_PATTERN="$(php -r "
\$json = json_decode(file_get_contents('$PICO_PROJECT_DIR/composer.json'), true);
if (\$json !== null) {
@ -45,9 +31,9 @@ if [ -z "$COMPOSER_ROOT_VERSION" ] && [ -n "$PROJECT_REPO_BRANCH" ]; then
if [ -n "$PICO_VERSION_PATTERN" ]; then
export COMPOSER_ROOT_VERSION="$PICO_VERSION_PATTERN"
fi
fi
echo
echo
fi
# install dependencies
echo "Running \`composer install\`$([ -n "$COMPOSER_ROOT_VERSION" ] && echo -n " ($COMPOSER_ROOT_VERSION)")..."

91
.build/release.sh Executable file
View file

@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -e
[ -n "$PICO_BUILD_ENV" ] || { echo "No Pico build environment specified" >&2; exit 1; }
# parameters
VERSION="${1:-$PROJECT_REPO_TAG}" # version to create a release for
ARCHIVE_DIR="${2:-$PICO_PROJECT_DIR}" # directory to create release archives in
# print parameters
echo "Creating new release..."
printf 'VERSION="%s"\n' "$VERSION"
echo
# guess version string
if [ -z "$VERSION" ]; then
PICO_VERSION="$(php -r "
require_once('$PICO_PROJECT_DIR/lib/Pico.php');
echo preg_replace('/-(?:dev|n|nightly)(?:[.-]?[0-9]+)?(?:[.-]dev)?$/', '', Pico::VERSION);
")"
VERSION="v$PICO_VERSION-dev+${PROJECT_REPO_BRANCH:-master}"
echo "Creating development release of Pico v$PICO_VERSION ($VERSION)..."
echo
fi
# parse version
. "$PICO_TOOLS_DIR/functions/parse-version.sh.inc"
if ! parse_version "$VERSION"; then
echo "Unable to create release archive: Invalid version '$VERSION'" >&2
exit 1
fi
DEPENDENCY_VERSION="$VERSION_FULL@$VERSION_STABILITY"
if [ "$VERSION_STABILITY" == "dev" ] && [ -n "$VERSION_BUILD" ]; then
DEPENDENCY_VERSION="dev-$VERSION_BUILD"
fi
# clone repo
github-clone.sh "$PICO_BUILD_DIR" "https://github.com/$RELEASE_REPO_SLUG.git" "$RELEASE_REPO_BRANCH"
cd "$PICO_BUILD_DIR"
# force Pico version
echo "Updating composer dependencies..."
composer require --no-update \
"picocms/pico $DEPENDENCY_VERSION" \
"picocms/pico-theme $DEPENDENCY_VERSION" \
"picocms/pico-deprecated $DEPENDENCY_VERSION"
echo
# force minimum stability <= beta due to Parsedown 1.8 currently being in beta
if [ "$VERSION_STABILITY" == "stable" ] || [ "$VERSION_STABILITY" == "rc" ]; then
VERSION_STABILITY="beta"
fi
# set minimum stability
if [ "$VERSION_STABILITY" != "stable" ]; then
echo "Setting minimum stability to '$VERSION_STABILITY'..."
composer config "minimum-stability" "$VERSION_STABILITY"
composer config "prefer-stable" "true"
echo
fi
# install dependencies
echo "Running \`composer install\`..."
composer install --no-suggest --prefer-dist --no-dev --optimize-autoloader
echo
# prepare release
echo "Replacing 'index.php'..."
cp vendor/picocms/pico/index.php.dist index.php
echo "Adding 'README.md', 'CONTRIBUTING.md', 'CHANGELOG.md'..."
cp vendor/picocms/pico/README.md README.md
cp vendor/picocms/pico/CONTRIBUTING.md CONTRIBUTING.md
cp vendor/picocms/pico/CHANGELOG.md CHANGELOG.md
echo "Removing '.git' directories of plugins and themes..."
find themes/ -type d -path 'themes/*/.git' -print0 | xargs -0 rm -rf
find plugins/ -type d -path 'plugins/*/.git' -print0 | xargs -0 rm -rf
echo "Preparing 'composer.json' for release..."
composer require --no-update \
"picocms/pico ^$VERSION_MILESTONE" \
"picocms/pico-theme ^$VERSION_MILESTONE" \
"picocms/pico-deprecated ^$VERSION_MILESTONE"
# create release archives
create-release.sh "$PICO_BUILD_DIR" "$ARCHIVE_DIR" "pico-release-v$VERSION_FULL"

2
.gitattributes vendored
View file

@ -1,5 +1,5 @@
/.github export-ignore
/.build export-ignore
/.github export-ignore
/assets/.gitignore export-ignore
/config/.gitignore export-ignore
/content/.gitignore export-ignore

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
custom: https://www.bountysource.com/teams/picocms

17
.github/stale.yml vendored
View file

@ -1,17 +0,0 @@
daysUntilStale: 7
daysUntilClose: 2
exemptLabels:
- "type: Bug"
- "type: Enhancement"
- "type: Feature"
- "type: Idea"
- "type: Release"
- "info: Pinned"
staleLabel: "info: Stale"
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in two days if no further activity
occurs. Thank you for your contributions! :+1:
unmarkComment: false
closeComment: false
only: issues

28
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: "Mark or close stale issues and PRs"
on:
schedule:
- cron: "0 12 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 2
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in two days if no further activity
occurs. Thank you for your contributions! :+1:
stale-pr-message: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed in two days if no further activity
occurs. Thank you for your contributions! :+1:
stale-pr-label: "info: Stale"
stale-issue-label: "info: Stale"
exempt-issue-labels: "type: Bug,type: Enhancement,type: Feature,type: Idea,type: Release,info: Pinned"
exempt-pr-labels: "type: Bug,type: Enhancement,type: Feature,type: Idea,type: Release,info: Pinned"
remove-stale-when-updated: true

11
.gitignore vendored
View file

@ -10,12 +10,17 @@ desktop.ini
.DS_Store
._*
# composer
# Composer
/composer.lock
/composer.phar
/vendor
# Build system
/.build/build
/.build/deploy
/.build/ci-tools
/pico-release-*.tar.gz
/pico-release-*.zip
# phpDocumentor
/.build/phpdoc
/.build/phpdoc.cache
/phpDocumentor.phar

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<phpdocumentor>
<title><![CDATA[Pico API Documentation]]></title>
<parser>
<target>.build/phpdoc.cache</target>
@ -30,4 +30,4 @@
<!-- exclude vendor dir -->
<ignore>vendor/*</ignore>
</files>
</phpdoc>
</phpdocumentor>

View file

@ -1,4 +1,4 @@
dist: trusty
dist: bionic
sudo: false
language: php
@ -13,33 +13,53 @@ jobs:
- php: 5.3
dist: precise
- php: 5.4
dist: trusty
- php: 5.5
dist: trusty
- php: 5.6
dist: xenial
- php: 7.0
dist: xenial
- php: 7.1
- php: 7.2
- php: 7.3
- php: 7.4
- php: nightly
- php: hhvm
- php: hhvm-nightly
- php: hhvm-3.24 # until Dec 2018
- php: hhvm-3.27 # until Sep 2019
- php: hhvm-3.30 # until Nov 2019
# Deployment stage
- stage: deploy
sudo: required
# Branch deployment stage
- stage: deploy-branch
if: type == "push" && tag IS blank
php: 5.3
dist: precise
install:
- '[ "$TRAVIS_PULL_REQUEST" == "false" ] || travis_terminate 0'
- '[ -n "$TRAVIS_TAG" ] || [[ ",$DEPLOY_PHPDOC_BRANCHES," == *,"$TRAVIS_BRANCH",* ]] || travis_terminate 0'
- '[[ ",$DEPLOY_PHPDOC_BRANCHES," == *,"$TRAVIS_BRANCH",* ]] || travis_terminate 0'
- install.sh --deploy
script:
- deploy.sh
- deploy-branch.sh
# Release deployment stage
- stage: deploy-release
if: tag IS present
php: 5.3
dist: precise
install:
- install.sh --deploy
script:
- '[ "$PROJECT_REPO_TAG" == "v$(php -r "require_once(\"lib/Pico.php\"); echo Pico::VERSION;")" ]'
- deploy-release.sh
before_deploy:
- '[ "$TRAVIS_TAG" == "v$(php -r "require_once(\"lib/Pico.php\"); echo Pico::VERSION;")" ]'
- create-release.sh "$TRAVIS_BUILD_DIR/pico-release-$TRAVIS_TAG.tar.gz"
- release.sh "$PROJECT_REPO_TAG"
deploy:
provider: releases
api_key: ${GITHUB_OAUTH_TOKEN}
file: pico-release-$TRAVIS_TAG.tar.gz
file:
- pico-release-$PROJECT_REPO_TAG.tar.gz
- pico-release-$PROJECT_REPO_TAG.zip
skip_cleanup: true
name: Version ${TRAVIS_TAG:1}
name: Version ${PROJECT_REPO_TAG:1}
draft: true
on:
tags: true
@ -47,20 +67,16 @@ jobs:
# Ignore nightly build failures
allow_failures:
- php: nightly
- php: hhvm-nightly
fast-finish: true
fast_finish: true
before_install:
- export PATH="$TRAVIS_BUILD_DIR/.build:$PATH"
- export PICO_TOOLS_DIR="$HOME/__picocms_tools"
- git clone --branch="$TOOLS_REPO_BRANCH" "https://github.com/$TOOLS_REPO_SLUG.git" "$PICO_TOOLS_DIR"
- . "$PICO_TOOLS_DIR/init/travis.sh.inc"
- . "$PICO_PROJECT_DIR/.build/init.sh.inc"
install:
- install.sh
before_script:
- export PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH"
script:
- phpcs --standard=.phpcs.xml "$TRAVIS_BUILD_DIR"
- phpcs --standard=.phpcs.xml "$PICO_PROJECT_DIR"

View file

@ -16,6 +16,116 @@ Pico Changelog
`PicoDeprecated`'s changelog. Please note that BC-breaking changes
are only possible with a new major version.
### Version 2.1.4
Released: 2020-08-29
```
* [Changed] Silence PHP errors in Parsedown
* [Fixed] #560: Improve charset guessing for formatted date strings using
`strftime()` (Pico always uses UTF-8, but `strftime()` might not)
```
### Version 2.1.3
Released: 2020-07-10
```
* [New] Add `locale` option to `config/config.yml`
* [Changed] Improve Pico docs
```
### Version 2.1.2
Released: 2020-04-10
```
* [Fixed] Fix DummyPlugin declaring API version 3
```
### Version 2.1.1
Released: 2019-12-31
```
* [Fixed] Require Parsedown 1.8.0-beta-7 and Parsedown Extra 0.8.0-beta-1 due
to changes in Parsedown and Parsedown Extra breaking BC beyond repair
* [Changed] #523: Check for hidden pages based on page ID instead of full paths
* [Changed] Improve Pico docs
```
### Version 2.1.0
Released: 2019-11-24
```
* [Changed] Add Pico's official logo and tagline to `content-sample/_meta.md`
* [Changed] Improve `content-sample/theme.md` to show Pico's official logo and
the usage of the new image utility classes of Pico's default theme
* [Changed] Improve Pico docs and PHPDoc class docs
```
### Version 2.1.0-beta.1
Released: 2019-11-03
```
* [New] Introduce API version 3
* [New] Add `assets_dir`, `assets_url` and `plugins_url` config params
* [New] Add `%config.*%` Markdown placeholders for scalar config params and the
`%assets_url%`, `%themes_url%` and `%plugins_url%` placeholders
* [New] Add `content-sample/theme.md` for theme testing purposes
* [New] Introduce API versioning for themes and support theme-specific configs
using the new `pico-theme.yml` in a theme's directory; `pico-theme.yml`
allows a theme to influence Pico's Twig config, to register known meta
headers and to provide defaults for theme config params
* [New] Add `assets_url`, `themes_url` and `plugins_url` Twig variables
* [New] Add `pages` Twig function to deal with Pico's page tree; this function
replaces the raw usage of Pico's `pages` array in themes
* [New] Add `url` Twig filter to replace URL placeholders (e.g. `%base_url%`)
in strings using the new `Pico::substituteUrl()` method
* [New] Add `onThemeLoading` and `onThemeLoaded` events
* [New] Add `debug` config param and the `Pico::isDebugModeEnabled()` method,
checking the `PICO_DEBUG` environment variable, to enable debugging
* [New] Add new `Pico::getNormalizedPath()` method to normalize a path; this
method should be used to prevent content dir breakouts when dealing
with paths provided by user input
* [New] Add new `Pico::getUrlFromPath()` method to guess a URL from a file path
* [New] Add new `Pico::getAbsoluteUrl()` method to make a relative URL absolute
* [New] #505: Create pre-built `.zip` release archives
* [Fixed] #461: Proberly handle content files with a UTF-8 BOM
* [Changed] Rename `theme_url` config param to `themes_url`; the `theme_url`
Twig variable and Markdown placeholder are kept unchanged
* [Changed] Update to Parsedown Extra 0.8 and Parsedown 1.8 (both still beta)
* [Changed] Enable Twig's `autoescape` feature by default; outputting a
variable now causes Twig to escape HTML markup; Pico's `content`
variable is a notable exception, as it is marked as being HTML safe
* [Changed] Rename `prev_page` Twig variable to `previous_page`
* [Changed] Mark `markdown` and `content` Twig filters as well as the `content`
variable as being HTML safe
* [Changed] Add `$singleLine` param to `markdown` Twig filter as well as the
`Pico::parseFileContent()` method to parse just a single line of
Markdown input
* [Changed] Add `AbstractPicoPlugin::configEnabled()` method to check whether
a plugin should be enabled or disabled based on Pico's config
* [Changed] Deprecate the use of `AbstractPicoPlugin::__call()`, use
`PicoPluginInterface::getPico()` instead
* [Changed] Update to Twig 1.36 as last version supporting PHP 5.3, use a
Composer-based installation to use a newer Twig version
* [Changed] Add `$basePath` and `$endSlash` params to `Pico::getAbsolutePath()`
* [Changed] Deprecate `Pico::getBaseThemeUrl()`
* [Changed] Replace various `file_exists` calls with proper `is_file` calls
* [Changed] Refactor release & build system
* [Changed] Improve Pico docs and PHPDoc class docs
* [Changed] Various small improvements
* [Removed] Remove superfluous `base_dir` and `theme_dir` Twig variables
* [Removed] Remove `PicoPluginInterface::__construct()`
```
### Version 2.0.5-beta.1
Released: 2019-01-03
```
* [New] Add PHP 7.3 tests
* [New] Add `2.0.x-dev` alias for master branch to `composer.json`
* [Changed] Update to Parsedown Extra 0.8 and Parsedown 1.8 (both still beta)
* [Changed] Improve release & build process
```
### Version 2.0.4
Released: 2018-12-17

View file

@ -192,7 +192,7 @@ Issues and pull requests labeled with `info: Feedback Needed` indicate that feed
- The `type: Release` label is used in the exact same way as `type: Feature` and indicates the primary pull request of a new Pico release (please refer to the *Branching* and *Build & Release process* sections above).
- The `type: Notice`, `type: Question` and `type: Discussion` labels are used to indicate "fyi" issues, issues opened by users or developers asking questions, and issues with disucssions about arbitrary topics related to Pico. They are neither combined with `pri` labels, nor with `status` labels.
- The `type: Notice`, `type: Support` and `type: Discussion` labels are used to indicate "fyi" issues, support-related issues (e.g. issues opened by users or developers asking questions), and issues with disucssions about arbitrary topics related to Pico. They are neither combined with `pri` labels, nor with `status` labels.
- The `type: Duplicate` label is used when there is already another issue or pull request related to this problem or feature request. Issues labeled with `type: Duplicate` are immediately closed.
@ -200,10 +200,10 @@ Issues and pull requests labeled with `info: Feedback Needed` indicate that feed
The `status: Deferred` label might get added to any open issue or pull request to indicate that it is still unresolved and will be resolved later. This is also true for the `info: Pinned` label: It indicates a important issue or pull request that remains open on purpose.
After resolving a issue, we usually keep it open for about a week to give users some more time for feedback and further questions. This is especially true for issues with the `type: Notice`, `type: Question`, `type: Discussion` and `type: Invalid` labels. After 7 days with no interaction, [Probot](https://probot.github.io/)'s [Stale](https://github.com/apps/stale) bot adds the `info: Stale` label to the issue to ask the participants whether the issue has been resolved. If no more activity occurs, the issue will be automatically closed by Stale bot 2 days later.
After resolving a issue, we usually keep it open for about a week to give users some more time for feedback and further questions. This is especially true for issues with the `type: Notice`, `type: Support`, `type: Discussion` and `type: Invalid` labels. After 7 days with no interaction, [Probot](https://probot.github.io/)'s [Stale](https://github.com/apps/stale) bot adds the `info: Stale` label to the issue to ask the participants whether the issue has been resolved. If no more activity occurs, the issue will be automatically closed by Stale bot 2 days later.
Issues and pull requests labeled with `info: Information Needed` indicate that we have asked one of the participants for further information and didn't receive any feedback yet. It is usually added after Stale bot adds the `info: Stale` label to give the participants some more days to give the necessary information.
Issues and pull requests, which are rather related to upstream projects (i.e. projects Pico depends on, like Twig), are additionally labeled with `info: Upstream`.
When a issue or pull request isn't directly related to Pico's core, but the project as a whole, it is labeled with `info: Meta`. The same applies to the `info: Website` label, however, in this case it is usually expedient to move the issue to the [`picocms.github.io` repo](https://github.com/picocms/picocms.github.io) containing Pico's website.
When a issue or pull request isn't directly related to Pico's core, but the project as a whole, it is labeled with `info: Meta`. Issues labeled with `info: Website` are related to [Pico's website](http://picocms.org), however, in this case it is usually expedient to move the issue to the [`picocms.github.io` repo](https://github.com/picocms/picocms.github.io) instead. The same applies to the `info: Pico CMS for Nextcloud` label; these issues are related to [Pico CMS for Nextcloud](https://apps.nextcloud.com/apps/cms_pico).

View file

@ -4,24 +4,40 @@ Pico
[![License](https://picocms.github.io/badges/pico-license.svg)](https://github.com/picocms/Pico/blob/master/LICENSE.md)
[![Version](https://picocms.github.io/badges/pico-version.svg)](https://github.com/picocms/Pico/releases/latest)
[![Build Status](https://api.travis-ci.org/picocms/Pico.svg?branch=master)](https://travis-ci.org/picocms/Pico)
[![Freenode IRC Webchat](https://picocms.github.io/badges/pico-chat.svg)](https://webchat.freenode.net/?channels=%23picocms)
[![Libera.Chat](https://picocms.github.io/badges/pico-chat.svg)](https://web.libera.chat/#picocms)
[![Open Bounties on Bountysource](https://www.bountysource.com/badge/team?team_id=198139&style=bounties_received)](https://www.bountysource.com/teams/picocms)
Pico is a stupidly simple, blazing fast, flat file CMS.
Visit us at http://picocms.org/ and see http://picocms.org/about/ for more info.
---
### PHP 8.0+ Users
Seeing an `Unparenthesized a ? b : c ? d : e is not supported.` error?
Pico currently has issues with PHP versions newer than 8.0. This is due to Pico's dependencies, and not Pico itself. There's currently an "alpha" build of Pico you can download as a [Pre-Bundled Release](https://github.com/picocms/Pico/releases/tag/v3.0.0-alpha.2) that solves this issue.
This "alpha" is **perfectly safe** to use in production, as the *only* changes are **updated dependencies and version number strings**. If you're curious, you can confirm this by [comparing the changes](https://github.com/picocms/Pico/compare/pico-3.0-alpha) between branches.
More work was intended to be done on this branch, hence the "3.0" label, but it hasn't happened yet. There's an [on-going discussion](https://github.com/picocms/Pico/issues/608) about getting just these updated dependencies merged in as an official update (either Pico 2.2 or 3.0) as soon as possible.
Sorry for the inconvenience, and thanks to all Pico users for your patience on the matter. ❤️
---
Screenshot
----------
![Pico Screenshot](https://picocms.github.io/screenshots/pico-20.png)
![Pico Screenshot](https://picocms.github.io/screenshots/pico-21.png)
Install
-------
Installing Pico is dead simple - and done in seconds! If you have access to a shell on your server (i.e. SSH access), we recommend using [Composer][]. If not, use a pre-bundled release. If you don't know what "SSH access" is, head over to the pre-bundled release. 😇
Pico requires PHP 5.3.6+
Pico requires PHP 5.3.6+ and the PHP extensions `dom` and `mbstring` to be enabled.
### I want to use Composer
@ -52,9 +68,32 @@ Do you know the feeling: You want to install a new website, so you upload all fi
Okay, here's the catch: There's no catch. That's it! Open your favorite web browser and navigate to your brand new, stupidly simple, blazing fast, flat file CMS! Pico's sample contents will explain how to create your own contents. 😊
### I want to manage my website using a Git repository
Git is a very powerful distributed version-control system - and it can be used to establish a nice workflow around your Pico website. Using a Git repository for your website aids content creation and deployment, including collaborative editing and version control. If you want to manage your website in a Git repository, you use a Composer-based installation.
1. Fork [Pico's Composer starter project][PicoComposerGit] using [GitHub's fork button][HelpFork]. If you don't want to use GitHub you aren't required to, you can choose whatever Git server you want. Forking manually just requires some extra steps: First clone the Git repository locally, add your Git server as a remote and push the repository to this new remote.
2. Clone your fork locally and add your contents and assets. You can edit Pico's `composer.json` to include 3rd-party plugins and themes, or simply add your own plugins and themes to Pico's `plugins` resp. `themes` directories. Don't forget to commit your changes and push them to your Git server.
3. Open a shell on your webserver and navigate to the `httpdocs` directory (e.g. `/var/www/html`). Download Composer, clone your Git repository to the desired directory (e.g. `/var/www/html/pico`) and install Pico's dependencies using Composer's `install` option:
```shell
$ curl -sSL https://getcomposer.org/installer | php
$ git clone https://github.com/<YOUR_USERNAME>/<YOUR_REPOSITORY> pico
$ php composer.phar --working-dir=pico install
```
4. If you update your website's contents, simply commit your changes and push them to your Git server. Open a shell on your webserver and navigate to Pico's install directory within the `httpdocs` directory (e.g. `/var/www/html/pico`) of your server. Pull all changes from your Git server and update Pico's dependencies using Composer's `update` option:
```shell
$ git pull
$ php composer.phar update
```
### I'm a developer
So, you're one of these amazing folks making all of this possible? We love you guys! As a developer we recommend you to clone [Pico's Git repository][PicoGit] as well as the Git repositories of [Pico's default theme][PicoThemeGit] and the [`PicoDeprecated` plugin][PicoDeprecatedGit]. You can set up your workspace using [Pico's Composer starter project][PicoComposerGit] and include all of Pico's components using local packages.
So, you're one of these amazing people making all of this possible? We love you folks! As a developer we recommend you to clone [Pico's Git repository][PicoGit] as well as the Git repositories of [Pico's default theme][PicoThemeGit] and the [`PicoDeprecated` plugin][PicoDeprecatedGit]. You can set up your workspace using [Pico's Composer starter project][PicoComposerGit] and include all of Pico's components using local packages.
Using Pico's Git repositories is different from using one of the installation methods elucidated above. It gives you the current development version of Pico, what is likely *unstable* and *not ready for production use*!
@ -136,7 +175,7 @@ That's it! Composer will automatically update Pico and all plugins and themes yo
### I've used a pre-bundled release to install Pico
Okay, installing Pico was easy, but upgrading Pico is going to be hard, isn't it? I'm affraid I have to disappoint you... It's just as simple as installing Pico!
Okay, installing Pico was easy, but upgrading Pico is going to be hard, isn't it? I'm afraid I have to disappoint you. It's just as simple as installing Pico!
First you'll have to delete the `vendor` directory of your Pico installation (e.g. if you've installed Pico to `/var/www/html/pico`, delete `/var/www/html/pico/vendor`). Then [download the latest Pico release][LatestRelease] and upload all files to your existing Pico installation directory. You will be prompted whether you want to overwrite files like `index.php`, `.htaccess`, ... - simply hit "Yes".
@ -166,7 +205,7 @@ If you're a developer, please refer to the "Contributing" section below and our
#### You still need help or experience a problem with Pico?
When the docs can't answer your question, you can get help by joining us on [#picocms on Freenode IRC](https://webchat.freenode.net/?channels=%23picocms). When you're experiencing problems with Pico, please don't hesitate to create a new [Issue][Issues] on GitHub. Concerning problems with plugins or themes, please refer to the website of the developer of this plugin or theme.
When the docs can't answer your question, you can get help by joining us on [#picocms on Libera.Chat][LiberaChat] ([logs][LiberaChatLogs]). When you're experiencing problems with Pico, please don't hesitate to create a new [Issue][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,** please make sure the problem wasn't reported yet using [GitHubs search engine][IssuesSearch]. Please describe your issue as clear as possible and always include the *Pico version* you're using. Provided that you're using *plugins*, include a list of them too. We need information about the *actual and expected behavior*, the *steps to reproduce* the problem, and what steps you have taken to resolve the problem by yourself (i.e. *your own troubleshooting*).
@ -175,7 +214,7 @@ Contributing
You want to contribute to Pico? We really appreciate that! You can help make Pico better by [contributing code][PullRequests] or [reporting issues][Issues], but please take note of our [contribution guidelines][ContributionGuidelines]. In general you can contribute in three different areas:
1. Plugins & Themes: You're a plugin developer or theme designer? We love you guys! You can find tons of information about how to develop plugins and themes at http://picocms.org/development/. If you have created a plugin or theme, please add it to our [Wiki][], either on the [plugins][WikiPlugins] or [themes][WikiThemes] page. You may also [Submit][] it to our website, where it'll be displayed on the official [plugin][OfficialPlugins] or [theme][OfficialThemes] pages!
1. Plugins & Themes: You're a plugin developer or theme designer? We love you folks! You can find tons of information about how to develop plugins and themes at http://picocms.org/development/. If you have created a plugin or theme, please add it to our [Wiki][], either on the [plugins][WikiPlugins] or [themes][WikiThemes] page. You may also [Submit][] it to our website, where it'll be displayed on the official [plugin][OfficialPlugins] or [theme][OfficialThemes] pages!
2. Documentation: We always appreciate people improving our documentation. You can either improve the [inline user docs][EditInlineDocs] or the more extensive [user docs on our website][EditUserDocs]. You can also improve the [docs for plugin and theme developers][EditDevDocs]. Simply fork our website's Git repository from https://github.com/picocms/picocms.github.io, change the Markdown files and open a [pull request][PullRequestsWebsite].
@ -210,6 +249,7 @@ Official Pico Contributors won't claim bounties on their own behalf, Pico will n
[PicoDeprecatedPackagist]: https://packagist.org/packages/picocms/pico-deprecated
[PicoComposerPackagist]: https://packagist.org/packages/picocms/pico-composer
[SemVer]: http://semver.org
[HelpFork]: https://help.github.com/en/github/getting-started-with-github/fork-a-repo
[HelpUpgrade]: http://picocms.org/in-depth/upgrade/
[HelpUserDocs]: http://picocms.org/docs/
[HelpDevDocs]: http://picocms.org/development/
@ -221,6 +261,8 @@ Official Pico Contributors won't claim bounties on their own behalf, Pico will n
[WikiThemes]: https://github.com/picocms/Pico/wiki/Pico-Themes
[Issues]: https://github.com/picocms/Pico/issues
[IssuesSearch]: https://github.com/picocms/Pico/search?type=Issues
[LiberaChat]: https://web.libera.chat/#picocms
[LiberaChatLogs]: http://picocms.org/irc-logs
[PullRequests]: https://github.com/picocms/Pico/pulls
[PullRequestsWebsite]: https://github.com/picocms/picocms.github.io/pulls
[ContributionGuidelines]: https://github.com/picocms/Pico/blob/master/CONTRIBUTING.md

15
SECURITY.md Normal file
View file

@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
Only the most recent stable version of Pico is supported.
## Reporting a Vulnerability
To mitigate the impact of possible security issues we ask you to disclose any security issues with Pico privately first ("responsible disclosure"). To do so please send an email to Pico's lead developer:
> Daniel Rudolf \<picocms.org@daniel-rudolf.de\>
You should receive an answer within 48 hours.
All messages with valid security reports will be puslished on GitHub in full text.

View file

@ -11,6 +11,11 @@
"email": "gilbert@pellegrom.me",
"role": "Project Founder"
},
{
"name": "Daniel Rudolf",
"email": "picocms.org@daniel-rudolf.de",
"role": "Lead Developer"
},
{
"name": "The Pico Community",
"homepage": "http://picocms.org/"
@ -27,26 +32,28 @@
},
"require": {
"php": ">=5.3.6",
"ext-dom": "*",
"twig/twig": "^1.35",
"ext-mbstring": "*",
"twig/twig": "^1.36",
"symfony/yaml" : "^2.8",
"erusev/parsedown": "^1.7",
"erusev/parsedown-extra": "^0.7"
"erusev/parsedown": "1.8.0-beta-7",
"erusev/parsedown-extra": "0.8.0-beta-1"
},
"suggest": {
"picocms/pico-theme": "Pico requires a theme to actually display the contents of your website. This is Pico's official default theme.",
"picocms/pico-deprecated": "PicoDeprecated's purpose is to maintain backward compatibility to older versions of Pico.",
"picocms/composer-installer": "This Composer plugin is responsible for installing Pico plugins and themes using the Composer package manager."
},
"require-dev" : {
"phpdocumentor/phpdocumentor": "^2.8",
"squizlabs/php_codesniffer": "^2.4"
},
"autoload": {
"psr-0": {
"Pico": "lib/",
"PicoPluginInterface": "lib/",
"AbstractPicoPlugin": "lib/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev",
"dev-pico-3.0": "3.0.x-dev"
}
}
}

View file

@ -2,46 +2,56 @@
# Basic
#
site_title: Pico # The title of your website
base_url: ~ # Pico will try to guess its base URL, if this fails, override it here
# Example: http://example.com/pico/
base_url: ~ # Pico will try to guess its base URL, if this fails, override it here;
# Example: https://example.com/pico/
rewrite_url: ~ # A boolean (true or false) indicating whether URL rewriting is forced
timezone: UTC # Your PHP installation might require you to manually specify a timezone
debug: ~ # Set this to true to enable Pico's debug mode
timezone: ~ # Your PHP installation might require you to manually specify a timezone
locale: ~ # Your PHP installation might require you to manually specify a locale to use
##
# Theme
#
theme: default # The name of your custom theme
theme_url: ~ # Pico will try to guess the URL to the themes dir of your installation
# If this fails, override it here. Example: http://example.com/pico/themes/
theme_config:
widescreen: false # Default theme: Use more horicontal space (i.e. make the site container wider)
twig_config:
themes_url: ~ # Pico will try to guess the URL to the themes dir of your installation;
# If this fails, override it here. Example: https://example.com/pico/themes/
theme_config: # Additional theme-specific config
widescreen: false # Default theme: Use more horizontal space (i.e. make the site container wider)
twig_config: # Twig template engine config
autoescape: html # Let Twig escape variables by default
strict_variables: false # If set to true, Twig will bail out when unset variables are being used
charset: utf-8 # The charset used by Twig templates
debug: ~ # Enable Twig's debug mode
cache: false # Enable Twig template caching by specifying a path to a writable directory
autoescape: false # Let Twig escape variables by default
debug: false # Enable Twig's debugging mode
auto_reload: ~ # Recompile Twig templates whenever the source code changes
##
# Content
#
date_format: %D %T # Pico's default date format
# See http://php.net/manual/en/function.strftime.php for more info
date_format: %D %T # Pico's default date format;
# See https://php.net/manual/en/function.strftime.php for more info
pages_order_by_meta: author # Sort pages by meta value "author" (set "pages_order_by" to "meta")
pages_order_by: alpha # Change how Pico sorts pages ("alpha" for alphabetical order, "date", or "meta")
pages_order: asc # Sort pages in ascending ("asc") or descending ("desc") order
content_dir: content/ # The path to Pico's content directory
content_dir: ~ # The path to Pico's content directory
content_ext: .md # The file extension of your Markdown files
content_config:
extra: true # Use the Parsedown Extra parser to support extended markup
content_config: # Parsedown Markdown parser config
extra: true # Use the Parsedown Extra parser to support extended markup;
# See https://michelf.ca/projects/php-markdown/extra/ for more info
breaks: false # A boolean indicating whether breaks in the markup should be reflected in the
# parsed contents of the page
escape: false # Escape HTML markup in your content files; don't confuse this with some sort of
# safe mode, enabling this doesn't allow you to process untrusted user input!
auto_urls: true # Automatically link URLs found in your markup
assets_dir: assets/ # The path to Pico's assets directory
assets_url: ~ # Pico will try to guess the URL to the assets dir of your installation;
# If this fails, override it here. Example: https://example.com/pico/assets/
##
# Plugins
#
plugins_url: ~ # Pico will try to guess the URL to the plugins dir of your installation;
# If this fails, override it here. Example: https://example.com/pico/plugins/
DummyPlugin.enabled: false # Force the plugin "DummyPlugin" to be disabled
##

View file

@ -1,10 +1,12 @@
---
social:
Logo: %theme_url%/img/pico-white.svg
Tagline: Making the web easy.
Social:
- title: Visit us on GitHub
url: https://github.com/picocms/Pico
icon: octocat
- title: Join us on Freenode IRC Webchat
url: https://webchat.freenode.net/?channels=%23picocms
- title: Join us on Libera.Chat
url: https://web.libera.chat/#picocms
icon: chat
- title: Help us by creating/collecting bounties and pledging to fundraisers
url: https://www.bountysource.com/teams/picocms

View file

@ -53,6 +53,10 @@ Below we've shown some examples of locations and their corresponding URLs:
<td>content/sub/page.md</td>
<td><a href="%base_url%?sub/page">?sub/page</a></td>
</tr>
<tr>
<td>content/theme.md</td>
<td><a href="%base_url%?theme">?theme</a> (hidden in menu)</td>
</tr>
<tr>
<td>content/a/very/long/url.md</td>
<td>
@ -81,10 +85,9 @@ page instead.
As a common practice, we recommend you to separate your contents and assets
(like images, downloads, etc.). We even deny access to your `content` directory
by default. If you want to use some assets (e.g. a image) in one of your content
files, you should create an `assets` folder in Pico's root directory and upload
your assets there. You can then access them in your Markdown using
<code>&#37;base_url&#37;/assets/</code> for example:
<code>!\[Image Title\](&#37;base_url&#37;/assets/image.png)</code>
files, use Pico's `assets` folder. You can then access them in your Markdown
using the <code>&#37;assets_url&#37;</code> placeholder, for example:
<code>!\[Image Title\](&#37;assets_url&#37;/image.png)</code>
### Text File Markup
@ -117,7 +120,7 @@ classes to your theme. For example, you might want to add some CSS classes to
your theme to rule how much of the available space a image should use (e.g.
`img.small { width: 80%; }`). You can then use these CSS classes in your
Markdown files, for example:
<code>!\[Image Title\](&#37;base_url&#37;/assets/image.png) {.small}</code>
<code>!\[Image Title\](&#37;assets_url&#37;/image.png) {.small}</code>
There are also certain variables that you can use in your text files:
@ -125,9 +128,15 @@ There are also certain variables that you can use in your text files:
* <code>&#37;base_url&#37;</code> - The URL to your Pico site; internal links
can be specified using <code>&#37;base_url&#37;?sub/page</code>
* <code>&#37;theme_url&#37;</code> - The URL to the currently used theme
* <code>&#37;assets_url&#37;</code> - The URL to Pico's `assets` directory
* <code>&#37;themes_url&#37;</code> - The URL to Pico's `themes` directory;
don't confuse this with <code>&#37;theme_url&#37;</code>
* <code>&#37;plugins_url&#37;</code> - The URL to Pico's `plugins` directory
* <code>&#37;version&#37;</code> - Pico's current version string (e.g. `2.0.0`)
* <code>&#37;meta.&#42;&#37;</code> - Access any meta variable of the current
page, e.g. <code>&#37;meta.author&#37;</code> is replaced with `Joe Bloggs`
* <code>&#37;config.&#42;&#37;</code> - Access any scalar config variable,
e.g. <code>&#37;config.theme&#37;</code> is replaced with `default`
### Blogging
@ -150,14 +159,12 @@ something like the following:
`index.twig`), it will create a list of all your blog articles. Add the
following Twig snippet to `blog-index.twig` near `{{ content }}`:
```
{% for page in pages|sort_by("time")|reverse %}
{% if page.id starts with "blog/" and not page.hidden %}
<div class="post">
<h3><a href="{{ page.url }}">{{ page.title }}</a></h3>
<p class="date">{{ page.date_formatted }}</p>
<p class="excerpt">{{ page.description }}</p>
</div>
{% endif %}
{% for page in pages("blog")|sort_by("time")|reverse if not page.hidden %}
<div class="post">
<h3><a href="{{ page.url }}">{{ page.title }}</a></h3>
<p class="date">{{ page.date_formatted }}</p>
<p class="excerpt">{{ page.description }}</p>
</div>
{% endfor %}
```
@ -175,75 +182,58 @@ details.
### Themes
You can create themes for your Pico installation in the `themes` folder. Check
out the default theme for an example. Pico uses [Twig][] for template
rendering. You can select your theme by setting the `theme` option in
`config/config.yml` to the name of your theme folder.
You can create themes for your Pico installation in the `themes` folder. Pico
uses [Twig][] for template rendering. You can select your theme by setting the
`theme` option in `config/config.yml` to the name of your theme folder.
[Pico's default theme][PicoTheme] isn't really intended to be used for a
productive website, it's rather a starting point for creating your own theme.
If the default theme isn't sufficient for you, and you don't want to create
your own theme, you can use one of the great themes third-party developers and
designers created in the past. As with plugins, you can find themes on [our website][OfficialThemes].
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. Please note that paths (e.g. `{{ base_dir }}`) and URLs
(e.g. `{{ base_url }}`) don't have a trailing slash.
the theme, and a `pico-theme.yml` to set the necessary config parameters. Just
refer to Pico's default theme as an example. You can use different templates
for different content files by specifying the `Template` meta header. Simply
add e.g. `Template: blog` to the YAML header of a content file and Pico will
use the `blog.twig` template in your theme folder to display the page.
Below are the Twig variables that are available to use in themes. Please note
that URLs (e.g. `{{ base_url }}`) never include a trailing slash.
* `{{ site_title }}` - Shortcut to the site title (see `config/config.yml`)
* `{{ config }}` - Contains the values you set in `config/config.yml`
(e.g. `{{ config.theme }}` becomes `default`)
* `{{ base_dir }}` - The path to your Pico root directory
* `{{ base_url }}` - The URL to your Pico site; use Twig's `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
* `{{ version }}` - Pico's current version string (e.g. `2.0.0`)
* `{{ assets_url }}` - The URL to Pico's `assets` directory
* `{{ themes_url }}` - The URL to Pico's `themes` directory; don't confuse this
with `{{ theme_url }}`
* `{{ plugins_url }}` - The URL to Pico's `plugins` directory
* `{{ version }}` - Pico's current version string (e.g. `%version%`)
* `{{ meta }}` - Contains the meta values of the current page
* `{{ meta.title }}`
* `{{ meta.description }}`
* `{{ meta.author }}`
* `{{ meta.date }}`
* `{{ meta.date_formatted }}`
* `{{ meta.time }}`
* `{{ meta.robots }}`
* `{{ meta.title }}` - The `Title` YAML header
* `{{ meta.description }}` - The `Description` YAML header
* `{{ meta.author }}` - The `Author` YAML header
* `{{ meta.date }}` - The `Date` YAML header
* `{{ meta.date_formatted }}` - The formatted date of the page as specified
by the `date_format` parameter in your
`config/config.yml`
* `{{ meta.time }}` - The [Unix timestamp][UnixTimestamp] derived from the
`Date` YAML header
* `{{ meta.robots }}` - The `Robots` YAML header
* ...
* `{{ 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 }}` - The relative path to the content file (unique ID)
* `{{ 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 [Unix timestamp][UnixTimestamp] derived from
the `Date` header
* `{{ page.date }}` - The date of the page (YAML header)
* `{{ page.date_formatted }}` - The formatted date of the page as specified
by the `date_format` parameter in your
`config/config.yml`
* `{{ page.raw_content }}` - The raw, not yet parsed contents of the page;
use Twig's `content` filter to get the parsed
contents of a page by passing its unique ID
(e.g. `{{ "sub/page"|content }}`)
* `{{ page.meta }}`- The meta values of the page (see `{{ meta }}` above)
* `{{ page.previous_page }}` - The data of the respective previous page
* `{{ page.next_page }}` - The data of the respective next page
* `{{ page.tree_node }}` - The page's node in Pico's page tree
* `{{ prev_page }}` - The data of the previous page (relative to `current_page`)
* `{{ current_page }}` - The data of the current page (see `{{ pages }}` above)
* `{{ next_page }}` - The data of the next page (relative to `current_page`)
Pages can be used like the following:
<ul class="nav">
{% for page in pages if not page.hidden %}
<li><a href="{{ page.url }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
Besides using the `{{ pages }}` list, you can also access pages using Pico's
page tree. The page tree allows you to iterate through Pico's pages using a tree
structure, so you can e.g. iterate just a page's direct children. It allows you
to build recursive menus (like dropdowns) and to filter pages more easily. Just
head over to Pico's [page tree documentation][FeaturesPageTree] for details.
* `{{ previous_page }}` - The data of the previous page, relative to
`current_page`
* `{{ current_page }}` - The data of the current page; refer to the "Pages"
section below for details
* `{{ next_page }}` - The data of the next page, relative to `current_page`
To call assets from your theme, use `{{ theme_url }}`. For instance, to include
the CSS file `themes/my_theme/example.css`, add
@ -251,19 +241,104 @@ the CSS file `themes/my_theme/example.css`, add
to your `index.twig`. This works for arbitrary files in your theme's folder,
including images and JavaScript files.
Additional to Twigs extensive list of filters, functions and tags, Pico also
provides some useful additional filters to make theming easier.
Please note that Twig escapes HTML in all strings before outputting them. So
for example, if you add `headline: My <strong>favorite</strong> color` to the
YAML header of a page and output it using `{{ meta.headline }}`, you'll end up
seeing `My <strong>favorite</strong> color` - yes, including the markup! To
actually get it parsed, you must use `{{ meta.headline|raw }}` (resulting in
the expected <code>My **favorite** color</code>). Notable exceptions to this
are Pico's `content` variable (e.g. `{{ content }}`), Pico's `content` filter
(e.g. `{{ "sub/page"|content }}`), and Pico's `markdown` filter, they all are
marked as HTML safe.
#### Dealing with pages
There are several ways to access Pico's pages list. You can access the current
page's data using the `current_page` variable, or use the `prev_page` and/or
`next_page` variables to access the respective previous/next page in Pico's
pages list. But more importantly there's the `pages()` function. No matter how
you access a page, it will always consist of the following data:
* `{{ id }}` - The relative path to the content file (unique ID)
* `{{ url }}` - The URL to the page
* `{{ title }}` - The title of the page (`Title` YAML header)
* `{{ description }}` - The description of the page (`Description` YAML header)
* `{{ author }}` - The author of the page (`Author` YAML header)
* `{{ date }}` - The date of the page (`Date` YAML header)
* `{{ date_formatted }}` - The formatted date of the page as specified by the
`date_format` parameter in your `config/config.yml`
* `{{ time }}` - The [Unix timestamp][UnixTimestamp] derived from the page's
date
* `{{ raw_content }}` - The raw, not yet parsed contents of the page; use the
filter to get the parsed contents of a page by passing
its unique ID (e.g. `{{ "sub/page"|content }}`)
* `{{ meta }}` - The meta values of the page (see global `{{ meta }}` above)
* `{{ prev_page }}` - The data of the respective previous page
* `{{ next_page }}` - The data of the respective next page
* `{{ tree_node }}` - The page's node in Pico's page tree; check out Pico's
[page tree documentation][FeaturesPageTree] for details
Pico's `pages()` function is the best way to access all of your site's pages.
It uses Pico's page tree to easily traverse a subset of Pico's pages list. It
allows you to filter pages and to build recursive menus (like dropdowns). By
default, `pages()` returns a list of all main pages (e.g. `content/page.md` and
`content/sub/index.md`, but not `content/sub/page.md` or `content/index.md`).
If you want to return all pages below a specific folder (e.g. `content/blog/`),
pass the folder name as first parameter to the function (e.g. `pages("blog")`).
Naturally you can also pass variables to the function. For example, to return a
list of all child pages of the current page, use `pages(current_page.id)`.
Check out the following code snippet:
<section class="articles">
{% for page in pages(current_page.id) if not page.hidden %}
<article>
<h2><a href="{{ page.url }}">{{ page.title }}</a></h2>
{{ page.id|content }}
</article>
{% endfor %}
</section>
The `pages()` function is very powerful and also allows you to return not just
a page's child pages by passing the `depth` and `depthOffset` params. For
example, if you pass `pages(depthOffset=-1)`, the list will also include Pico's
main index page (i.e. `content/index.md`). This one is commonly used to create
a theme's main navigation. If you want to learn more, head over to Pico's
complete [`pages()` function documentation][FeaturesPagesFunction].
If you want to access the data of a particular page, use Pico's `pages`
variable. Just take `content/_meta.md` in Pico's sample contents for an
example: `content/_meta.md` contains some meta data you might want to use in
your theme. If you want to output the page's `tagline` meta value, use
`{{ pages["_meta"].meta.logo }}`. Don't ever try to use Pico's `pages` variable
as an replacement for Pico's `pages()` function. Its usage looks very similar,
it will kinda work and you might even see it being used in old themes, but be
warned: It slows down Pico. Always use Pico's `pages()` function when iterating
Pico's page list (e.g. `{% for page in pages() %}…{% endfor %}`).
#### Twig filters and functions
Additional to [Twig][]'s extensive list of filters, functions and tags, Pico
also provides some useful additional filters and functions to make theming
even easier.
* Pass the unique ID of a page to the `link` filter to return the page's URL
(e.g. `{{ "sub/page"|link }}` gets `%base_url%?sub/page`).
* You can replace URL placeholders (like <code>&#37;base_url&#37;</code>) in
arbitrary strings using the `url` filter. This is helpful together with meta
variables, e.g. if you add <code>image: &#37;assets_url&#37;/stock.jpg</code>
to the YAML header of a page, `{{ meta.image|url }}` will return
`%assets_url%/stock.jpg`.
* To get the parsed contents of a page, pass its unique ID to the `content`
filter (e.g. `{{ "sub/page"|content }}`).
* You can parse any Markdown string using the `markdown` filter (e.g. you can
use Markdown in the `description` meta variable and later parse it in your
theme using `{{ meta.description|markdown }}`). You can pass meta data as
parameter to replace <code>&#37;meta.&#42;&#37;</code> placeholders (e.g.
`{{ "Written *by %meta.author%*"|markdown(meta) }}` yields "Written by
*John Doe*").
* You can parse any Markdown string using the `markdown` filter. For example,
you might use Markdown in the `description` meta variable and later parse it
in your theme using `{{ meta.description|markdown }}`. You can also pass meta
data as parameter to replace <code>&#37;meta.&#42;&#37;</code> placeholders
(e.g. `{{ "Written by *%meta.author%*"|markdown(meta) }}` yields "Written by
*John Doe*"). However, please note that all contents will be wrapped inside
HTML paragraph elements (i.e. `<p>…</p>`). If you want to parse just a single
line of Markdown markup, pass the `singleLine` param to the `markdown` filter
(e.g. `{{ "This really is a *single* line"|markdown(singleLine=true) }}`).
* Arrays can be sorted by one of its keys using the `sort_by` filter
(e.g. `{% for page in pages|sort_by([ 'meta', 'nav' ]) %}...{% endfor %}`
iterates through all pages, ordered by the `nav` meta header; please note the
@ -281,18 +356,6 @@ provides some useful additional filters to make theming easier.
Twig! Simply head over to our [introductory page for accessing HTTP
parameters][FeaturesHttpParams] for details.
You can use different templates for different content files by specifying the
`Template` meta header. Simply add e.g. `Template: blog` to the YAML header of
a content file and Pico will use the `blog.twig` template in your theme folder
to display the page.
Pico's default theme isn't really intended to be used for a productive website,
it's rather a starting point for creating your own theme. If the default theme
isn't sufficient for you, and you don't want to create your own theme, 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][WikiThemes] and on
[our website][OfficialThemes].
### Plugins
#### Plugins for users
@ -422,22 +485,23 @@ url.rewrite-if-not-file = (
## Documentation
For more help have a look at the Pico documentation at http://picocms.org/docs.
For more help have a look at the Pico documentation at https://picocms.org/docs/.
[Pico]: http://picocms.org/
[Pico]: https://picocms.org/
[PicoTheme]: https://github.com/picocms/pico-theme
[SampleContents]: https://github.com/picocms/Pico/tree/master/content-sample
[Markdown]: http://daringfireball.net/projects/markdown/syntax
[Markdown]: https://daringfireball.net/projects/markdown/syntax
[MarkdownExtra]: https://michelf.ca/projects/php-markdown/extra/
[YAML]: https://en.wikipedia.org/wiki/YAML
[Twig]: http://twig.sensiolabs.org/documentation
[UnixTimestamp]: https://en.wikipedia.org/wiki/Unix_timestamp
[Twig]: https://twig.symfony.com/doc/
[UnixTimestamp]: https://en.wikipedia.org/wiki/Unix_time
[Composer]: https://getcomposer.org/
[FeaturesHttpParams]: http://picocms.org/in-depth/features/http-params/
[FeaturesPageTree]: http://picocms.org/in-depth/features/page-tree/
[WikiThemes]: https://github.com/picocms/Pico/wiki/Pico-Themes
[FeaturesHttpParams]: https://picocms.org/in-depth/features/http-params/
[FeaturesPageTree]: https://picocms.org/in-depth/features/page-tree/
[FeaturesPagesFunction]: https://picocms.org/in-depth/features/pages-function/
[WikiPlugins]: https://github.com/picocms/Pico/wiki/Pico-Plugins
[OfficialThemes]: http://picocms.org/themes/
[PluginUpgrade]: http://picocms.org/development/#upgrade
[OfficialThemes]: https://picocms.org/themes/
[PluginUpgrade]: https://picocms.org/development/#migrating-plugins
[ModRewrite]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html
[AllowOverride]: https://httpd.apache.org/docs/current/mod/core.html#allowoverride
[NginxConfig]: http://picocms.org/in-depth/nginx/
[NginxConfig]: https://picocms.org/in-depth/nginx/

195
content-sample/theme.md Normal file
View file

@ -0,0 +1,195 @@
---
title: Theme Styling Test
hidden: true
---
Theme Styling Test
==================
This is `theme.md` in Pico's content directory. This page doesn't show up in the website's menu due to `hidden: true` in the page's YAML header. The purpose of this page is to aid theme development - below you'll find basically every format that is possible with Markdown. If you develop a theme, you should make sure that all examples below show decent.
Text
----
**Lorem ipsum dolor sit amet,** consectetur adipisici elit, *sed eiusmod tempor* incidunt ut labore et dolore magna aliqua.[^1] ~~Ut enim ad minim veniam,~~ quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.[^2] [Quis aute iure reprehenderit][Link] in voluptate velit esse cillum dolore eu fugiat nulla pariatur. `Excepteur` sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
[![Pico Logo](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIiB2aWV3Qm94PSIwIDAgNDAwIDQwMCI+PHBhdGggZD0ibTI5OC40IDE5NC43cTAtMTQuMTUtLjgtMzEuMmwtLjg1LTE0LjI1aC01MS4wNXY4OS45NWw4IDEuMXE5LjYgMS4wNSAxNy42IDEuMDUgNy45NSAwIDE3LjUtMS4wNSA0LjgtLjU1IDcuOTUtMS4xIDEuNjUtMjIuMiAxLjY1LTQ0LjVtLTY5Ljc1LTQ1LjhoLTQ5LjN2MTgyLjQ1bDcuNy44NXE5LjMuOCAxNyAuOCAxMi4zIDAgMjQuNi0xLjY1eiIgZmlsbD0iIzJlYWU5YiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM4Ljg1IC00MC45NSkiLz48L3N2Zz4K)](%base_url% "Pico Logo") {.image .small .float-right}
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
Headings
--------
# h1
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
## h2
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
### h3
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
#### h4
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
##### h5
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
###### h6
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Horizontal line
---------------
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
---
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
List
----
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
* Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
1. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
2. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
3. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
* Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
- Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
1. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
2. At vero eos et accusam et justo duo dolores et ea rebum.
1. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet
2. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
3. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
Definition list
---------------
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Duis autem
: Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Lorem ipsum dolor sit amet
: Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam
: Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
: Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
Blockquote
----------
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
> Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse
> molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero
> eros et accumsan et iusto odio dignissim qui blandit praesent luptatum
> zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum
> dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod
> tincidunt ut laoreet dolore magna aliquam erat volutpat.
>
> > Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
> > lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
> > dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
> > dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
> > dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
> > feugait nulla facilisi.
>
> Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet
> doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet,
> consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
> laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam,
> quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex
> ea commodo consequat.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
Code block
----------
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
```
<!DOCTYPE html>
<html>
<head>
<title>This is a title</title>
</head>
<body>
<p>Hello world!</p>
</body>
</html>
```
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Table
-----
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum | Duis autem vel eum | Ut wisi enim ad minim veniam
----------- | ------------------ | ----------------------------
**Duis autem vel eum iriure dolor** in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. | *Lorem ipsum dolor sit amet,* consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. | ~~Ut wisi enim ad minim veniam,~~ quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
[Duis autem vel eum iriure dolor][Link] in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. | `Nam liber tempor` cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. | | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Forms
-----
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<fieldset>
<legend>Legend</legend>
<label>Label</label>
<input type="checkbox"/>
<input type="checkbox" checked="checked"/>
<input type="radio"/>
<input type="radio" checked="checked"/><br/>
<input type="text" value="Lorem ipsum"/>
<input type="password" value="Ut enim"/><br/>
<input type="submit" value="Submit"/>
<input type="reset" value="Reset"/>
<input type="button" value="Button (Input)"/>
<button>Button</button><br/>
<textarea>Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua<br/>.</textarea><br/>
<select>
<option>Lorem ipsum</option>
<option>Ut enim</option>
</select><br/>
<select multiple="multiple">
<option>Lorem ipsum</option>
<option selected="selected">Ut enim</option>
<option>Quis aute iure</option>
<option>Excepteur sint</option>
</select>
</fieldset>
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
*[Lorem ipsum]: Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua.
[Link]: %base_url% "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat."
[^1]: Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
[^2]: Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

View file

@ -18,7 +18,11 @@ if (is_file(__DIR__ . '/vendor/autoload.php')) {
// composer dependency package
require_once(__DIR__ . '/../../../vendor/autoload.php');
} else {
die("Cannot find 'vendor/autoload.php'. Run `composer install`.");
die(
"Cannot find 'vendor/autoload.php'. If you're using a composer-based Pico install, run `composer install`. "
. "If you're rather trying to use one of Pico's pre-built release packages, make sure to download Pico's "
. "latest release package named 'pico-release-v*.tar.gz' (don't download a source code package)."
);
}
// instance Pico

View file

@ -21,7 +21,7 @@
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 2.0
* @version 2.1
*/
abstract class AbstractPicoPlugin implements PicoPluginInterface
{
@ -31,7 +31,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
* @see PicoPluginInterface::getPico()
* @var Pico
*/
private $pico;
protected $pico;
/**
* Boolean indicating if this plugin is enabled (TRUE) or disabled (FALSE)
@ -74,10 +74,12 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
* @see PicoPluginInterface::getDependants()
* @var object[]|null
*/
private $dependants;
protected $dependants;
/**
* @see PicoPluginInterface::__construct()
* Constructs a new instance of a Pico plugin
*
* @param Pico $pico current instance of Pico
*/
public function __construct(Pico $pico)
{
@ -85,31 +87,13 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::handleEvent()
* {@inheritDoc}
*/
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);
} else {
$pluginEnabled = $this->getPluginConfig('enabled');
if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled);
} elseif ($this->enabled) {
$this->setEnabled($this->enabled, true, true);
} elseif ($this->enabled === null) {
// make sure dependencies are already fulfilled,
// otherwise the plugin needs to be enabled manually
try {
$this->setEnabled(true, false, true);
} catch (RuntimeException $e) {
$this->enabled = false;
}
}
}
$this->configEnabled();
}
if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) {
@ -120,7 +104,33 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::setEnabled()
* Enables or disables this plugin depending on Pico's config
*/
protected function configEnabled()
{
$pluginEnabled = $this->getPico()->getConfig(get_called_class() . '.enabled');
if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled);
} else {
$pluginEnabled = $this->getPluginConfig('enabled');
if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled);
} elseif ($this->enabled) {
$this->setEnabled(true, true, true);
} elseif ($this->enabled === null) {
// make sure dependencies are already fulfilled,
// otherwise the plugin needs to be enabled manually
try {
$this->setEnabled(true, false, true);
} catch (RuntimeException $e) {
$this->enabled = false;
}
}
}
}
/**
* {@inheritDoc}
*/
public function setEnabled($enabled, $recursive = true, $auto = false)
{
@ -136,7 +146,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::isEnabled()
* {@inheritDoc}
*/
public function isEnabled()
{
@ -144,7 +154,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::isStatusChanged()
* {@inheritDoc}
*/
public function isStatusChanged()
{
@ -152,7 +162,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::getPico()
* {@inheritDoc}
*/
public function getPico()
{
@ -174,7 +184,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
*/
public function getPluginConfig($configName = null, $default = null)
{
$pluginConfig = $this->getConfig(get_called_class(), array());
$pluginConfig = $this->getPico()->getConfig(get_called_class(), array());
if ($configName === null) {
return $pluginConfig;
@ -186,7 +196,9 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
/**
* Passes all not satisfiable method calls to Pico
*
* @see Pico
* @see PicoPluginInterface::getPico()
*
* @deprecated 2.1.0
*
* @param string $methodName name of the method to call
* @param array $params parameters to pass
@ -212,15 +224,13 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
*
* @param bool $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);
$plugin = $this->getPico()->getPlugin($pluginName);
} catch (RuntimeException $e) {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
@ -250,7 +260,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::getDependencies()
* {@inheritDoc}
*/
public function getDependencies()
{
@ -264,8 +274,6 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
*
* @param bool $recursive disabled dependant plugins automatically
*
* @return void
*
* @throws RuntimeException thrown when a dependency fails
*/
protected function checkDependants($recursive)
@ -297,13 +305,13 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
}
/**
* @see PicoPluginInterface::getDependants()
* {@inheritDoc}
*/
public function getDependants()
{
if ($this->dependants === null) {
$this->dependants = array();
foreach ($this->getPlugins() as $pluginName => $plugin) {
foreach ($this->getPico()->getPlugins() as $pluginName => $plugin) {
// only plugins which implement PicoPluginInterface support dependencies
if ($plugin instanceof PicoPluginInterface) {
$dependencies = $plugin->getDependencies();
@ -322,13 +330,11 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
*
* Pico automatically adds a dependency to {@see PicoDeprecated} when the
* plugin's API is older than Pico's API. {@see PicoDeprecated} furthermore
* throws a exception when it can't provide compatibility in such cases.
* throws a exception if it can't provide compatibility in such cases.
* However, we still have to decide whether this plugin is compatible to
* newer API versions, what requires some special (version specific)
* precaution and is therefore usually not the case.
*
* @return void
*
* @throws RuntimeException thrown when the plugin's and Pico's API aren't
* compatible
*/

View file

@ -40,7 +40,7 @@
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 2.0
* @version 2.1
*/
class Pico
{
@ -49,21 +49,21 @@ class Pico
*
* @var string
*/
const VERSION = '2.0.4';
const VERSION = '2.1.4';
/**
* Pico version ID
*
* @var int
*/
const VERSION_ID = 20004;
const VERSION_ID = 20104;
/**
* Pico API version
*
* @var int
*/
const API_VERSION = 2;
const API_VERSION = 3;
/**
* Sort files in alphabetical ascending order
@ -168,6 +168,29 @@ class Pico
*/
protected $config;
/**
* Theme in use
*
* @see Pico::getTheme()
* @var string
*/
protected $theme;
/**
* API version of the current theme
*
* @see Pico::getThemeApiVersion()
* @var int
*/
protected $themeApiVersion;
/**
* Additional meta headers of the current theme
*
* @var array<string,string>|null
*/
protected $themeMetaHeaders;
/**
* Part of the URL describing the requested contents
*
@ -212,7 +235,7 @@ class Pico
* List of known meta headers
*
* @see Pico::getMetaHeaders()
* @var string[]|null
* @var array<string,string>|null
*/
protected $metaHeaders;
@ -411,6 +434,16 @@ class Pico
throw new RuntimeException('Invalid content directory "' . $this->getConfig('content_dir') . '"');
}
// load theme
$this->theme = $this->config['theme'];
$this->triggerEvent('onThemeLoading', array(&$this->theme));
$this->loadTheme();
$this->triggerEvent(
'onThemeLoaded',
array($this->theme, $this->themeApiVersion, &$this->config['theme_config'])
);
// evaluate request url
$this->evaluateRequestUrl();
$this->triggerEvent('onRequestUrl', array(&$this->requestUrl));
@ -422,8 +455,9 @@ class Pico
// load raw file content
$this->triggerEvent('onContentLoading');
$requestedPageId = $this->getPageId($this->requestFile) ?: $this->requestFile;
$hiddenFileRegex = '/(?:^|\/)(?:_|404' . preg_quote($this->getConfig('content_ext'), '/') . '$)/';
if (file_exists($this->requestFile) && !preg_match($hiddenFileRegex, $this->requestFile)) {
if (is_file($this->requestFile) && !preg_match($hiddenFileRegex, $requestedPageId)) {
$this->rawContent = $this->loadFileContent($this->requestFile);
} else {
$this->triggerEvent('on404ContentLoading');
@ -508,8 +542,6 @@ class Pico
* @see Pico::getPlugin()
* @see Pico::getPlugins()
*
* @return void
*
* @throws RuntimeException thrown when a plugin couldn't be loaded
*/
protected function loadPlugins()
@ -541,14 +573,16 @@ class Pico
* @param string[] $pluginBlacklist class names of plugins not to load
*
* @return string[] installer names of the loaded plugins
*
* @throws RuntimeException thrown when a plugin couldn't be loaded
*/
protected function loadComposerPlugins(array $pluginBlacklist = array())
{
$composerPlugins = array();
if (file_exists($this->getVendorDir() . 'vendor/pico-plugin.php')) {
if (is_file($this->getVendorDir() . 'vendor/pico-plugin.php')) {
// composer root package
$composerPlugins = require($this->getVendorDir() . 'vendor/pico-plugin.php') ?: array();
} elseif (file_exists($this->getVendorDir() . '../../../vendor/pico-plugin.php')) {
} elseif (is_file($this->getVendorDir() . '../../../vendor/pico-plugin.php')) {
// composer dependency package
$composerPlugins = require($this->getVendorDir() . '../../../vendor/pico-plugin.php') ?: array();
}
@ -608,8 +642,6 @@ class Pico
*
* @param string[] $pluginBlacklist class names of plugins not to load
*
* @return void
*
* @throws RuntimeException thrown when a plugin couldn't be loaded
*/
protected function loadLocalPlugins(array $pluginBlacklist = array())
@ -635,7 +667,7 @@ class Pico
$className = preg_replace('/^[0-9]+-/', '', $file);
$pluginFile = $file . '/' . $className . '.php';
if (!file_exists($this->getPluginsDir() . $pluginFile)) {
if (!is_file($this->getPluginsDir() . $pluginFile)) {
throw new RuntimeException(
"Unable to load plugin '" . $className . "' from '" . $pluginFile . "': File not found"
);
@ -703,7 +735,7 @@ class Pico
*
* @return PicoPluginInterface instance of the loaded plugin
*
* @throws RuntimeException thrown when a plugin couldn't be loaded
* @throws RuntimeException thrown when the plugin couldn't be loaded
*/
public function loadPlugin($plugin)
{
@ -712,7 +744,7 @@ class Pico
if (class_exists($className)) {
$plugin = new $className($this);
} else {
throw new RuntimeException("Unable to load plugin '" . $className . "': Class not found");
throw new RuntimeException("Unable to load plugin '" . $className . "': Class not found");
}
}
@ -764,8 +796,6 @@ class Pico
* Marc J. Schmidt's Topological Sort / Dependency resolver in PHP
* @see https://github.com/marcj/topsort.php/blob/1.1.0/src/Implementations/ArraySort.php
* \MJS\TopSort\Implementations\ArraySort class
*
* @return void
*/
protected function sortPlugins()
{
@ -862,7 +892,7 @@ class Pico
}
/**
* Loads the config.yml and any other *.yml from Pico::$configDir
* Loads 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
@ -874,8 +904,6 @@ class Pico
*
* @see Pico::setConfig()
* @see Pico::getConfig()
*
* @return void
*/
protected function loadConfig()
{
@ -889,7 +917,7 @@ class Pico
// load main config file (config/config.yml)
$this->config = is_array($this->config) ? $this->config : array();
if (file_exists($this->getConfigDir() . 'config.yml')) {
if (is_file($this->getConfigDir() . 'config.yml')) {
$this->config += $loadConfigClosure($this->getConfigDir() . 'config.yml');
}
@ -904,18 +932,26 @@ class Pico
// merge default config
$this->config += array(
'site_title' => 'Pico',
'base_url' => '',
'base_url' => null,
'rewrite_url' => null,
'debug' => null,
'timezone' => null,
'locale' => null,
'theme' => 'default',
'theme_url' => null,
'theme_config' => null,
'theme_meta' => null,
'themes_url' => null,
'twig_config' => null,
'date_format' => '%D %T',
'pages_order_by_meta' => 'author',
'pages_order_by' => 'alpha',
'pages_order' => 'asc',
'content_dir' => null,
'content_ext' => '.md',
'content_config' => null
'content_config' => null,
'assets_dir' => 'assets/',
'assets_url' => null,
'plugins_url' => null
);
if (!$this->config['base_url']) {
@ -928,6 +964,10 @@ class Pico
$this->config['rewrite_url'] = $this->isUrlRewritingEnabled();
}
if ($this->config['debug'] === null) {
$this->config['debug'] = $this->isDebugModeEnabled();
}
if (!$this->config['timezone']) {
// 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
@ -935,19 +975,20 @@ class Pico
}
date_default_timezone_set($this->config['timezone']);
if (!$this->config['theme_url']) {
$this->config['theme_url'] = $this->getBaseThemeUrl();
} elseif (preg_match('#^[A-Za-z][A-Za-z0-9+\-.]*://#', $this->config['theme_url'])) {
$this->config['theme_url'] = rtrim($this->config['theme_url'], '/') . '/';
} else {
$this->config['theme_url'] = $this->getBaseUrl() . rtrim($this->config['theme_url'], '/') . '/';
if ($this->config['locale'] !== null) {
setlocale(LC_ALL, $this->config['locale']);
}
$defaultTwigConfig = array('cache' => false, 'autoescape' => false, 'debug' => false);
if (!is_array($this->config['twig_config'])) {
$this->config['twig_config'] = $defaultTwigConfig;
if (!$this->config['plugins_url']) {
$this->config['plugins_url'] = $this->getUrlFromPath($this->getPluginsDir());
} else {
$this->config['twig_config'] += $defaultTwigConfig;
$this->config['plugins_url'] = $this->getAbsoluteUrl($this->config['plugins_url']);
}
if (!$this->config['themes_url']) {
$this->config['themes_url'] = $this->getUrlFromPath($this->getThemesDir());
} else {
$this->config['themes_url'] = $this->getAbsoluteUrl($this->config['themes_url']);
}
if (!$this->config['content_dir']) {
@ -969,6 +1010,18 @@ class Pico
} else {
$this->config['content_config'] += $defaultContentConfig;
}
if (!$this->config['assets_dir']) {
$this->config['assets_dir'] = $this->getRootDir() . 'assets/';
} else {
$this->config['assets_dir'] = $this->getAbsolutePath($this->config['assets_dir']);
}
if (!$this->config['assets_url']) {
$this->config['assets_url'] = $this->getUrlFromPath($this->config['assets_dir']);
} else {
$this->config['assets_url'] = $this->getAbsoluteUrl($this->config['assets_url']);
}
}
/**
@ -987,8 +1040,6 @@ class Pico
*
* @param array $config array with config variables
*
* @return void
*
* @throws LogicException thrown if Pico already started processing
*/
public function setConfig(array $config)
@ -1025,6 +1076,109 @@ class Pico
}
}
/**
* Loads a theme's config file (pico-theme.yml)
*
* @see Pico::getTheme()
* @see Pico::getThemeApiVersion()
*/
protected function loadTheme()
{
$themeConfig = array();
// load theme config from pico-theme.yml
$themeConfigFile = $this->getThemesDir() . $this->getTheme() . '/pico-theme.yml';
if (is_file($themeConfigFile)) {
$themeConfigYaml = file_get_contents($themeConfigFile);
$themeConfig = $this->getYamlParser()->parse($themeConfigYaml);
$themeConfig = is_array($themeConfig) ? $themeConfig : array();
}
$themeConfig += array(
'api_version' => null,
'meta' => array(),
'twig_config' => array()
);
// theme API version
if (is_int($themeConfig['api_version']) || preg_match('/^[0-9]+$/', $themeConfig['api_version'])) {
$this->themeApiVersion = (int) $themeConfig['api_version'];
} else {
$this->themeApiVersion = 0;
}
unset($themeConfig['api_version']);
// twig config
$themeTwigConfig = array('autoescape' => 'html', 'strict_variables' => false, 'charset' => 'utf-8');
foreach ($themeTwigConfig as $key => $_) {
if (isset($themeConfig['twig_config'][$key])) {
$themeTwigConfig[$key] = $themeConfig['twig_config'][$key];
}
}
unset($themeConfig['twig_config']);
$defaultTwigConfig = array('debug' => null, 'cache' => false, 'auto_reload' => null);
$this->config['twig_config'] = is_array($this->config['twig_config']) ? $this->config['twig_config'] : array();
$this->config['twig_config'] = array_merge($defaultTwigConfig, $themeTwigConfig, $this->config['twig_config']);
if ($this->config['twig_config']['autoescape'] === true) {
$this->config['twig_config']['autoescape'] = 'html';
}
if ($this->config['twig_config']['cache']) {
$this->config['twig_config']['cache'] = $this->getAbsolutePath($this->config['twig_config']['cache']);
}
if ($this->config['twig_config']['debug'] === null) {
$this->config['twig_config']['debug'] = $this->isDebugModeEnabled();
}
// meta headers
$this->themeMetaHeaders = is_array($themeConfig['meta']) ? $themeConfig['meta'] : array();
unset($themeConfig['meta']);
// theme config
if (!is_array($this->config['theme_config'])) {
$this->config['theme_config'] = $themeConfig;
} else {
$this->config['theme_config'] += $themeConfig;
}
// check for theme compatibility
if (!isset($this->plugins['PicoDeprecated']) && ($this->themeApiVersion < static::API_VERSION)) {
throw new RuntimeException(
'Current theme "' . $this->theme . '" uses API version ' . $this->themeApiVersion . ', but Pico '
. 'provides API version ' . static::API_VERSION . ' and PicoDeprecated isn\'t loaded'
);
}
}
/**
* Returns the name of the current theme
*
* @see Pico::loadTheme()
* @see Pico::getThemeApiVersion()
*
* @return string
*/
public function getTheme()
{
return $this->theme;
}
/**
* Returns the API version of the current theme
*
* @see Pico::loadTheme()
* @see Pico::getTheme()
*
* @return int
*/
public function getThemeApiVersion()
{
return $this->themeApiVersion;
}
/**
* Evaluates the requested URL
*
@ -1061,8 +1215,6 @@ class Pico
* `/pico/?someBooleanParam=` or `/pico/?index&someBooleanParam` instead.
*
* @see Pico::getRequestUrl()
*
* @return void
*/
protected function evaluateRequestUrl()
{
@ -1136,39 +1288,22 @@ class Pico
if (!$requestUrl) {
return $contentDir . 'index' . $contentExt;
} else {
// prevent content_dir breakouts
$requestUrl = str_replace('\\', '/', $requestUrl);
$requestUrlParts = explode('/', $requestUrl);
$requestFileParts = array();
foreach ($requestUrlParts as $requestUrlPart) {
if (($requestUrlPart === '') || ($requestUrlPart === '.')) {
continue;
} elseif ($requestUrlPart === '..') {
array_pop($requestFileParts);
continue;
}
$requestFileParts[] = $requestUrlPart;
}
if (!$requestFileParts) {
return $contentDir . 'index' . $contentExt;
}
// normalize path and prevent content_dir breakouts
$requestFile = $this->getNormalizedPath($requestUrl, false, false);
// discover the content file to serve
// Note: $requestFileParts neither contains a trailing nor a leading slash
$requestFile = $contentDir . implode('/', $requestFileParts);
if (is_dir($requestFile)) {
if (!$requestFile) {
return $contentDir . 'index' . $contentExt;
} elseif (is_dir($contentDir . $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 = $requestFile . '/index' . $contentExt;
if (file_exists($indexFile) || !file_exists($requestFile . $contentExt)) {
$indexFile = $contentDir . $requestFile . '/index' . $contentExt;
if (is_file($indexFile) || !is_file($contentDir . $requestFile . $contentExt)) {
return $indexFile;
}
}
return $requestFile . $contentExt;
return $contentDir . $requestFile . $contentExt;
}
}
@ -1223,11 +1358,11 @@ class Pico
$errorFileDir = dirname($errorFileDir);
$errorFile = $errorFileDir . '/404' . $contentExt;
if (file_exists($contentDir . $errorFile)) {
if (is_file($contentDir . $errorFile)) {
return $this->loadFileContent($contentDir . $errorFile);
}
}
} elseif (file_exists($contentDir . '404' . $contentExt)) {
} elseif (is_file($contentDir . '404' . $contentExt)) {
// provided that the requested file is not in the regular
// content directory, fallback to Pico's global `404.md`
return $this->loadFileContent($contentDir . '404' . $contentExt);
@ -1293,6 +1428,10 @@ class Pico
'Hidden' => 'hidden'
);
if ($this->themeMetaHeaders) {
$this->metaHeaders += $this->themeMetaHeaders;
}
$this->triggerEvent('onMetaHeaders', array(&$this->metaHeaders));
}
@ -1340,7 +1479,7 @@ class Pico
public function parseFileMeta($rawContent, array $headers)
{
$meta = array();
$pattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n"
$pattern = "/^(?:\xEF\xBB\xBF)?(\/(\*)|---)[[:blank:]]*(?:\r)?\n"
. "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s";
if (preg_match($pattern, $rawContent, $rawMetaMatches) && isset($rawMetaMatches[3])) {
$meta = $this->getYamlParser()->parse($rawMetaMatches[3]) ?: array();
@ -1379,8 +1518,17 @@ class Pico
}
if (empty($meta['date_formatted'])) {
$dateFormat = $this->getConfig('date_format');
$meta['date_formatted'] = $meta['time'] ? utf8_encode(strftime($dateFormat, $meta['time'])) : '';
if ($meta['time']) {
$encodingList = mb_detect_order();
if ($encodingList === array('ASCII', 'UTF-8')) {
$encodingList[] = 'Windows-1252';
}
$rawFormattedDate = strftime($this->getConfig('date_format'), $meta['time']);
$meta['date_formatted'] = mb_convert_encoding($rawFormattedDate, 'UTF-8', $encodingList);
} else {
$meta['date_formatted'] = '';
}
}
} else {
// guarantee array key existance
@ -1444,7 +1592,7 @@ class Pico
public function prepareFileContent($rawContent, array $meta = array())
{
// remove meta header
$metaHeaderPattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n"
$metaHeaderPattern = "/^(?:\xEF\xBB\xBF)?(\/(\*)|---)[[:blank:]]*(?:\r)?\n"
. "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s";
$markdown = preg_replace($metaHeaderPattern, '', $rawContent, 1);
@ -1483,8 +1631,13 @@ class Pico
}
$variables['%base_url%'] = rtrim($this->getBaseUrl(), '/');
// replace %plugins_url%, %themes_url% and %assets_url%
$variables['%plugins_url%'] = rtrim($this->getConfig('plugins_url'), '/');
$variables['%themes_url%'] = rtrim($this->getConfig('themes_url'), '/');
$variables['%assets_url%'] = rtrim($this->getConfig('assets_url'), '/');
// replace %theme_url%
$variables['%theme_url%'] = $this->getBaseThemeUrl() . $this->getConfig('theme');
$variables['%theme_url%'] = $this->getConfig('themes_url') . $this->getTheme();
// replace %meta.*%
if ($meta) {
@ -1495,6 +1648,13 @@ class Pico
}
}
// replace %config.*%
foreach ($this->config as $configKey => $configValue) {
if (is_scalar($configValue) || ($configValue === null)) {
$variables['%config.' . $configKey . '%'] = (string) $configValue;
}
}
return str_replace(array_keys($variables), $variables, $markdown);
}
@ -1505,13 +1665,15 @@ class Pico
* @see Pico::substituteFileContent()
* @see Pico::getFileContent()
*
* @param string $markdown Markdown contents of a page
* @param string $markdown Markdown contents of a page
* @param bool $singleLine whether to parse just a single line of markup
*
* @return string parsed contents (HTML)
*/
public function parseFileContent($markdown)
public function parseFileContent($markdown, $singleLine = false)
{
return $this->getParsedown()->text($markdown);
$markdownParser = $this->getParsedown();
return !$singleLine ? @$markdownParser->text($markdown) : @$markdownParser->line($markdown);
}
/**
@ -1559,8 +1721,6 @@ class Pico
* @see Pico::sortPages()
* @see Pico::discoverPageSiblings()
* @see Pico::getPages()
*
* @return void
*/
protected function readPages()
{
@ -1619,7 +1779,7 @@ class Pico
'time' => &$meta['time'],
'date' => &$meta['date'],
'date_formatted' => &$meta['date_formatted'],
'hidden' => (preg_match('/(?:^|\/)_/', $id) || $meta['hidden']),
'hidden' => ($meta['hidden'] || preg_match('/(?:^|\/)_/', $id)),
'raw_content' => &$rawContent,
'meta' => &$meta
);
@ -1644,8 +1804,6 @@ class Pico
*
* @see Pico::readPages()
* @see Pico::getPages()
*
* @return void
*/
protected function sortPages()
{
@ -1725,8 +1883,6 @@ class Pico
*
* @see Pico::readPages()
* @see Pico::getPages()
*
* @return void
*/
protected function discoverPageSiblings()
{
@ -1776,8 +1932,6 @@ class Pico
* @see Pico::getCurrentPage()
* @see Pico::getPreviousPage()
* @see Pico::getNextPage()
*
* @return void
*/
protected function discoverCurrentPage()
{
@ -1864,8 +2018,6 @@ class Pico
* non-iterable data structure with Pico 3.0.
*
* @see Pico::getPageTree()
*
* @return void
*/
protected function buildPageTree()
{
@ -1940,7 +2092,7 @@ class Pico
* {@see PicoTwigExtension} Twig extension.
*
* @see Pico::getTwig()
* @see http://twig.sensiolabs.org/ Twig website
* @see https://twig.symfony.com/ Twig website
* @see https://github.com/twigphp/Twig Twig on GitHub
*
* @return Twig_Environment|null Twig template engine
@ -1950,7 +2102,7 @@ class Pico
if ($this->twig === null) {
$twigConfig = $this->getConfig('twig_config');
$twigLoader = new Twig_Loader_Filesystem($this->getThemesDir() . $this->getConfig('theme'));
$twigLoader = new Twig_Loader_Filesystem($this->getThemesDir() . $this->getTheme());
$this->twig = new Twig_Environment($twigLoader, $twigConfig);
$this->twig->addExtension(new PicoTwigExtension($this));
@ -1963,17 +2115,21 @@ class Pico
// this is the reason why we can't register this filter as part of PicoTwigExtension
$pico = $this;
$pages = &$this->pages;
$this->twig->addFilter(new Twig_SimpleFilter('content', function ($page) use ($pico, &$pages) {
if (isset($pages[$page])) {
$pageData = &$pages[$page];
if (!isset($pageData['content'])) {
$pageData['content'] = $pico->prepareFileContent($pageData['raw_content'], $pageData['meta']);
$pageData['content'] = $pico->parseFileContent($pageData['content']);
$this->twig->addFilter(new Twig_SimpleFilter(
'content',
function ($page) use ($pico, &$pages) {
if (isset($pages[$page])) {
$pageData = &$pages[$page];
if (!isset($pageData['content'])) {
$markdown = $pico->prepareFileContent($pageData['raw_content'], $pageData['meta']);
$pageData['content'] = $pico->parseFileContent($markdown);
}
return $pageData['content'];
}
return $pageData['content'];
}
return null;
}));
return null;
},
array('is_safe' => array('html'))
));
// trigger onTwigRegistration event
$this->triggerEvent('onTwigRegistered', array(&$this->twig));
@ -1985,8 +2141,7 @@ 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 don't add a trailing slash for historic reasons.
*
* @return array template variables
*/
@ -1994,15 +2149,16 @@ class Pico
{
return array(
'config' => $this->getConfig(),
'base_dir' => rtrim($this->getRootDir(), '/'),
'base_url' => rtrim($this->getBaseUrl(), '/'),
'theme_dir' => $this->getThemesDir() . $this->getConfig('theme'),
'theme_url' => $this->getBaseThemeUrl() . $this->getConfig('theme'),
'plugins_url' => rtrim($this->getConfig('plugins_url'), '/'),
'themes_url' => rtrim($this->getConfig('themes_url'), '/'),
'assets_url' => rtrim($this->getConfig('assets_url'), '/'),
'theme_url' => $this->getConfig('themes_url') . $this->getTheme(),
'site_title' => $this->getConfig('site_title'),
'meta' => $this->meta,
'content' => $this->content,
'content' => new Twig_Markup($this->content, 'UTF-8'),
'pages' => $this->pages,
'prev_page' => $this->previousPage,
'previous_page' => $this->previousPage,
'current_page' => $this->currentPage,
'next_page' => $this->nextPage,
'version' => static::VERSION
@ -2102,6 +2258,29 @@ class Pico
return $this->config['rewrite_url'];
}
/**
* Returns TRUE if Pico's debug mode is enabled
*
* @return bool TRUE if Pico's debug mode is enabled, FALSE otherwise
*/
public function isDebugModeEnabled()
{
$debugModeEnabled = $this->getConfig('debug');
if ($debugModeEnabled !== null) {
return $debugModeEnabled;
}
if (isset($_SERVER['PICO_DEBUG'])) {
$this->config['debug'] = (bool) $_SERVER['PICO_DEBUG'];
} elseif (isset($_SERVER['REDIRECT_PICO_DEBUG'])) {
$this->config['debug'] = (bool) $_SERVER['REDIRECT_PICO_DEBUG'];
} else {
$this->config['debug'] = false;
}
return $this->config['debug'];
}
/**
* Returns the URL to a given page
*
@ -2115,6 +2294,8 @@ class Pico
* "index", passing TRUE (default) will remove this path component
*
* @return string URL
*
* @throws InvalidArgumentException thrown when invalid arguments got passed
*/
public function getPageUrl($page, $queryData = null, $dropIndex = true)
{
@ -2122,7 +2303,7 @@ class Pico
$queryData = http_build_query($queryData, '', '&');
} elseif (($queryData !== null) && !is_string($queryData)) {
throw new InvalidArgumentException(
'Argument 2 passed to ' . get_called_class() . '::getPageUrl() must be of the type array or string, '
'Argument 2 passed to ' . __METHOD__ . ' must be of the type array or string, '
. (is_object($queryData) ? get_class($queryData) : gettype($queryData)) . ' given'
);
}
@ -2164,14 +2345,12 @@ class Pico
{
$contentDir = $this->getConfig('content_dir');
$contentDirLength = strlen($contentDir);
if (substr($path, 0, $contentDirLength) !== $contentDir) {
return null;
}
$contentExt = $this->getConfig('content_ext');
$contentExtLength = strlen($contentExt);
if (substr($path, -$contentExtLength) !== $contentExt) {
return null;
}
@ -2179,42 +2358,93 @@ class Pico
return substr($path, $contentDirLength, -$contentExtLength) ?: null;
}
/**
* Substitutes URL placeholders (e.g. %base_url%)
*
* This method is registered as the `url` Twig filter and often used to
* allow users to specify absolute URLs in meta data utilizing the known
* URL placeholders `%base_url%`, `%plugins_url%`, `%themes_url%`,
* `%assets_url%` and `%theme_url%`.
*
* Don't confuse this with the `link` Twig filter, which takes a page ID as
* parameter. However, you can indeed use this method to create page URLs,
* e.g. `{{ "%base_url%?sub/page"|url }}`.
*
* @param string $url URL with placeholders
*
* @return string URL with replaced placeholders
*/
public function substituteUrl($url)
{
$variables = array(
'%base_url%?' => $this->getBaseUrl() . (!$this->isUrlRewritingEnabled() ? '?' : ''),
'%base_url%' => rtrim($this->getBaseUrl(), '/'),
'%plugins_url%' => rtrim($this->getConfig('plugins_url'), '/'),
'%themes_url%' => rtrim($this->getConfig('themes_url'), '/'),
'%assets_url%' => rtrim($this->getConfig('assets_url'), '/'),
'%theme_url%' => $this->getConfig('themes_url') . $this->getTheme()
);
return str_replace(array_keys($variables), $variables, $url);
}
/**
* 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 {@see Pico::$rootDir}, but this may aberrate when Pico got installed
* as a composer dependency. However, ultimately it allows us to use
* {@see Pico::getBaseUrl()} as origin of the theme URL. Otherwise Pico
* falls back to the basename of {@see 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.
* @see Pico::getUrlFromPath()
*
* @return string the URL of the themes folder
* @deprecated 2.1.0
*
* @return string
*/
public function getBaseThemeUrl()
{
$themeUrl = $this->getConfig('theme_url');
if ($themeUrl) {
return $themeUrl;
}
return $this->getConfig('themes_url');
}
if (isset($_SERVER['SCRIPT_FILENAME']) && ($_SERVER['SCRIPT_FILENAME'] !== 'index.php')) {
/**
* Returns the URL of a given absolute path within this Pico instance
*
* We assume that the given path 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). If this isn't the case, we check whether it's
* a sub folder of {@see Pico::$rootDir} (what is often identical to the
* script's base path). If this isn't the case either, we fall back to
* the basename of the given folder. This whole process ultimately allows
* us to use {@see Pico::getBaseUrl()} as origin for the URL.
*
* This method is used to guess Pico's `plugins_url`, `themes_url` and
* `assets_url`. However, guessing might fail, requiring a manual config.
*
* @param string $absolutePath the absolute path to interpret
*
* @return string the URL of the given folder
*/
public function getUrlFromPath($absolutePath)
{
$absolutePath = str_replace('\\', '/', $absolutePath);
$basePath = '';
if (isset($_SERVER['SCRIPT_FILENAME']) && strrpos($_SERVER['SCRIPT_FILENAME'], '/')) {
$basePath = dirname($_SERVER['SCRIPT_FILENAME']);
$basePath = !in_array($basePath, array('.', '/', '\\'), true) ? $basePath . '/' : '/';
$basePathLength = strlen($basePath);
if (substr($this->getThemesDir(), 0, $basePathLength) === $basePath) {
$this->config['theme_url'] = $this->getBaseUrl() . substr($this->getThemesDir(), $basePathLength);
return $this->config['theme_url'];
if ((substr($absolutePath, 0, $basePathLength) === $basePath) && ($basePath !== '/')) {
return $this->getBaseUrl() . substr($absolutePath, $basePathLength);
}
}
$this->config['theme_url'] = $this->getBaseUrl() . basename($this->getThemesDir()) . '/';
return $this->config['theme_url'];
if ($basePath !== $this->getRootDir()) {
$basePath = $this->getRootDir();
$basePathLength = strlen($basePath);
if (substr($absolutePath, 0, $basePathLength) === $basePath) {
return $this->getBaseUrl() . substr($absolutePath, $basePathLength);
}
}
return $this->getBaseUrl() . basename($absolutePath) . '/';
}
/**
@ -2367,11 +2597,10 @@ class Pico
public function getFiles($directory, $fileExtension = '', $order = self::SORT_ASC)
{
$directory = rtrim($directory, '/');
$fileExtensionLength = strlen($fileExtension);
$result = array();
// scandir() reads files in alphabetical order
$files = scandir($directory, $order);
$fileExtensionLength = strlen($fileExtension);
if ($files !== false) {
foreach ($files as $file) {
// exclude hidden files/dirs starting with a .; this also excludes the special dirs . and ..
@ -2430,24 +2659,109 @@ class Pico
/**
* Makes a relative path absolute to Pico's root dir
*
* This method also guarantees a trailing slash.
*
* @param string $path relative or absolute path
* @param string $path relative or absolute path
* @param string $basePath treat relative paths relative to the given path;
* defaults to Pico::$rootDir
* @param bool $endSlash whether to add a trailing slash to the absolute
* path or not (defaults to TRUE)
*
* @return string absolute path
*/
public function getAbsolutePath($path)
public function getAbsolutePath($path, $basePath = null, $endSlash = true)
{
if ($basePath === null) {
$basePath = $this->getRootDir();
}
if (DIRECTORY_SEPARATOR === '\\') {
if (preg_match('/^(?>[a-zA-Z]:\\\\|\\\\\\\\)/', $path) !== 1) {
$path = $this->getRootDir() . $path;
$path = $basePath . $path;
}
} else {
if ($path[0] !== '/') {
$path = $this->getRootDir() . $path;
$path = $basePath . $path;
}
}
return rtrim($path, '/\\') . '/';
return rtrim($path, '/\\') . ($endSlash ? '/' : '');
}
/**
* Normalizes a path by taking care of '', '.' and '..' parts
*
* @param string $path path to normalize
* @param bool $allowAbsolutePath whether absolute paths are allowed
* @param bool $endSlash whether to add a trailing slash to the
* normalized path or not (defaults to TRUE)
*
* @return string normalized path
*
* @throws UnexpectedValueException thrown when a absolute path is passed
* although absolute paths aren't allowed
*/
public function getNormalizedPath($path, $allowAbsolutePath = false, $endSlash = true)
{
$absolutePath = '';
if (DIRECTORY_SEPARATOR === '\\') {
if (preg_match('/^(?>[a-zA-Z]:\\\\|\\\\\\\\)/', $path, $pathMatches) === 1) {
$absolutePath = $pathMatches[0];
$path = substr($path, strlen($absolutePath));
}
} else {
if ($path[0] === '/') {
$absolutePath = '/';
$path = substr($path, 1);
}
}
if ($absolutePath && !$allowAbsolutePath) {
throw new UnexpectedValueException(
'Argument 1 passed to ' . __METHOD__ . ' must be a relative path, absolute path "' . $path . '" given'
);
}
$path = str_replace('\\', '/', $path);
$pathParts = explode('/', $path);
$resultParts = array();
foreach ($pathParts as $pathPart) {
if (($pathPart === '') || ($pathPart === '.')) {
continue;
} elseif ($pathPart === '..') {
array_pop($resultParts);
continue;
}
$resultParts[] = $pathPart;
}
if (!$resultParts) {
return $absolutePath ?: '/';
}
return $absolutePath . implode('/', $resultParts) . ($endSlash ? '/' : '');
}
/**
* Makes a relative URL absolute to Pico's base URL
*
* Please note that URLs starting with a slash are considered absolute URLs
* even though they don't include a scheme and host.
*
* @param string $url relative or absolute URL
* @param string $baseUrl treat relative URLs relative to the given URL;
* defaults to Pico::getBaseUrl()
* @param bool $endSlash whether to add a trailing slash to the absolute
* URL or not (defaults to TRUE)
*
* @return string absolute URL
*/
public function getAbsoluteUrl($url, $baseUrl = null, $endSlash = true)
{
if (($url[0] !== '/') && !preg_match('#^[A-Za-z][A-Za-z0-9+\-.]*://#', $url)) {
$url = (($baseUrl !== null) ? $baseUrl : $this->getBaseUrl()) . $url;
}
return rtrim($url, '/') . ($endSlash ? '/' : '');
}
/**
@ -2467,8 +2781,6 @@ class Pico
*
* @param string $eventName name of the event to trigger
* @param array $params optional parameters to pass
*
* @return void
*/
public function triggerEvent($eventName, array $params = array())
{

View file

@ -26,24 +26,15 @@
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 2.0
* @version 2.1
*/
interface PicoPluginInterface
{
/**
* Constructs a new instance of a Pico plugin
*
* @param Pico $pico current instance of Pico
*/
public function __construct(Pico $pico);
/**
* Handles a event that was triggered by Pico
*
* @param string $eventName name of the triggered event
* @param array $params passed parameters
*
* @return void
*/
public function handleEvent($eventName, array $params);
@ -63,8 +54,6 @@ interface PicoPluginInterface
* @param bool $auto enable or disable to fulfill a dependency. This
* parameter is optional and defaults to FALSE.
*
* @return void
*
* @throws RuntimeException thrown when a dependency fails
*/
public function setEnabled($enabled, $recursive = true, $auto = false);
@ -106,11 +95,11 @@ interface PicoPluginInterface
public function getDependants();
/**
* Returns the plugins instance of Pico
* Returns the plugin's instance of Pico
*
* @see Pico
*
* @return Pico the plugins instance of Pico
* @return Pico the plugin's instance of Pico
*/
public function getPico();
}

View file

@ -16,7 +16,7 @@
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 2.0
* @version 2.1
*/
class PicoTwigExtension extends Twig_Extension
{
@ -72,10 +72,15 @@ class PicoTwigExtension extends Twig_Extension
public function getFilters()
{
return array(
'markdown' => new Twig_SimpleFilter('markdown', array($this, 'markdownFilter')),
'markdown' => new Twig_SimpleFilter(
'markdown',
array($this, 'markdownFilter'),
array('is_safe' => array('html'))
),
'map' => new Twig_SimpleFilter('map', array($this, 'mapFilter')),
'sort_by' => new Twig_SimpleFilter('sort_by', array($this, 'sortByFilter')),
'link' => new Twig_SimpleFilter('link', array($this->pico, 'getPageUrl'))
'link' => new Twig_SimpleFilter('link', array($this->pico, 'getPageUrl')),
'url' => new Twig_SimpleFilter('url', array($this->pico, 'substituteUrl'))
);
}
@ -90,7 +95,8 @@ class PicoTwigExtension extends Twig_Extension
{
return array(
'url_param' => new Twig_SimpleFunction('url_param', array($this, 'urlParamFunction')),
'form_param' => new Twig_SimpleFunction('form_param', array($this, 'formParamFunction'))
'form_param' => new Twig_SimpleFunction('form_param', array($this, 'formParamFunction')),
'pages' => new Twig_SimpleFunction('pages', array($this, 'pagesFunction'))
);
}
@ -105,15 +111,16 @@ class PicoTwigExtension extends Twig_Extension
* @see Pico::substituteFileContent()
* @see Pico::parseFileContent()
*
* @param string $markdown markdown to parse
* @param array $meta meta data to use for %meta.*% replacement
* @param string $markdown markdown to parse
* @param array $meta meta data to use for %meta.*% replacement
* @param bool $singleLine whether to parse just a single line of markup
*
* @return string parsed HTML
*/
public function markdownFilter($markdown, array $meta = array())
public function markdownFilter($markdown, array $meta = array(), $singleLine = false)
{
$markdown = $this->getPico()->substituteFileContent($markdown, $meta);
return $this->getPico()->parseFileContent($markdown);
return $this->getPico()->parseFileContent($markdown, $singleLine);
}
/**
@ -128,6 +135,8 @@ class PicoTwigExtension extends Twig_Extension
* $item['foo']['bar'] values)
*
* @return array mapped values
*
* @throws Twig_Error_Runtime
*/
public function mapFilter($var, $mapKeyPath)
{
@ -168,6 +177,8 @@ class PicoTwigExtension extends Twig_Extension
* these items
*
* @return array sorted array
*
* @throws Twig_Error_Runtime
*/
public function sortByFilter($var, $sortKeyPath, $fallback = 'bottom')
{
@ -339,4 +350,138 @@ class PicoTwigExtension extends Twig_Extension
return $this->pico->getFormParameter($name, $filter, $options, $flags);
}
/**
* Returns all pages within a particular branch of Pico's page tree
*
* This function should be used most of the time when dealing with Pico's
* pages array, as it allows one to easily traverse Pico's pages tree
* ({@see Pico::getPageTree()}) to retrieve a subset of Pico's pages array
* in a very convenient and performant way.
*
* The function's default parameters are `$start = ""`, `$depth = 0`,
* `$depthOffset = 0` and `$offset = 1`. A positive `$offset` is equivalent
* to `$depth = $depth + $offset`, `$depthOffset = $depthOffset + $offset`
* and `$offset = 0`.
*
* Consequently the default `$start = ""`, `$depth = 0`, `$depthOffset = 0`
* and `$offset = 1` is equivalent to `$depth = 1`, `$depthOffset = 1` and
* `$offset = 0`. `$start = ""` instruct the function to start from the
* root node (i.e. the node of Pico's main index page at `index.md`).
* `$depth` tells the function what pages to return. In this example,
* `$depth = 1` matches the start node (i.e. the zeroth generation) and all
* its descendant pages until the first generation (i.e. the start node's
* children). `$depthOffset` instructs the function to exclude some of the
* older generations. `$depthOffset = 1` specifically tells the function
* to exclude the zeroth generation, so that the function returns all of
* Pico's main index page's direct child pages (like `sub/index.md` and
* `page.md`, but not `sub/page.md`) only.
*
* Passing `$depthOffset = -1` only is the same as passing `$start = ""`,
* `$depth = 1`, `$depthOffset = 0` and `$offset = 0`. The only difference
* is that `$depthOffset` won't exclude the zeroth generation, so that the
* function returns Pico's main index page as well as all of its direct
* child pages.
*
* Passing `$depth = 0`, `$depthOffset = -2` and `$offset = 2` is the same
* as passing `$depth = 2`, `$depthOffset = 0` and `$offset = 0`. Both will
* return the zeroth, first and second generation of pages. For Pico's main
* index page this would be `index.md` (0th gen), `sub/index.md` (1st gen),
* `sub/page.md` (2nd gen) and `page.md` (1st gen). If you want to return
* 2nd gen pages only, pass `$offset = 2` only (with implicit `$depth = 0`
* and `$depthOffset = 0` it's the same as `$depth = 2`, `$depthOffset = 2`
* and `$offset = 0`).
*
* Instead of an integer you can also pass `$depth = null`. This is the
* same as passing an infinitely large number as `$depth`, so that this
* function simply returns all descendant pages. Consequently passing
* `$start = ""`, `$depth = null`, `$depthOffset = 0` and `$offset = 0`
* returns Pico's full pages array.
*
* If `$depth` is negative after taking `$offset` into consideration, the
* function will throw a {@see Twig_Error_Runtime} exception, since this
* would simply make no sense and is likely an error. Passing a negative
* `$depthOffset` is equivalent to passing `$depthOffset = 0`.
*
* But what about a negative `$offset`? Passing `$offset = -1` instructs
* the function not to start from the given `$start` node, but its parent
* node. Consequently `$offset = -2` instructs the function to use the
* `$start` node's grandparent node. Obviously this won't make any sense
* for Pico's root node, but just image `$start = "sub/index"`. Passing
* this together with `$offset = -1` is equivalent to `$start = ""` and
* `$offset = 0`.
*
* @param string $start name of the node to start from
* @param int|null $depth return pages until the given maximum depth;
* pass NULL to return all descendant pages; defaults to 0
* @param int $depthOffset start returning pages from the given
* minimum depth; defaults to 0
* @param int $offset ascend (positive) or descend (negative) the
* given number of branches before returning pages; defaults to 1
*
* @return array[] the data of the matched pages
*
* @throws Twig_Error_Runtime
*/
public function pagesFunction($start = '', $depth = 0, $depthOffset = 0, $offset = 1)
{
$start = (string) $start;
if (basename($start) === 'index') {
$start = dirname($start);
}
for (; $offset < 0; $offset++) {
if (in_array($start, array('', '.', '/'), true)) {
$offset = 0;
break;
}
$start = dirname($start);
}
$depth = ($depth !== null) ? $depth + $offset : null;
$depthOffset = $depthOffset + $offset;
if (($depth !== null) && ($depth < 0)) {
throw new Twig_Error_Runtime('The pages function doesn\'t support negative depths');
}
$pageTree = $this->getPico()->getPageTree();
if (in_array($start, array('', '.', '/'), true)) {
if (($depth === null) && ($depthOffset <= 0)) {
return $this->getPico()->getPages();
}
$startNode = isset($pageTree['']['/']) ? $pageTree['']['/'] : null;
} else {
$branch = dirname($start);
$branch = ($branch !== '.') ? $branch : '/';
$node = (($branch !== '/') ? $branch . '/' : '') . basename($start);
$startNode = isset($pageTree[$branch][$node]) ? $pageTree[$branch][$node] : null;
}
if (!$startNode) {
return array();
}
$getPagesClosure = function ($nodes, $depth, $depthOffset) use (&$getPagesClosure) {
$pages = array();
foreach ($nodes as $node) {
if (isset($node['page']) && ($depthOffset <= 0)) {
$pages[$node['page']['id']] = &$node['page'];
}
if (isset($node['children']) && ($depth > 0)) {
$pages += $getPagesClosure($node['children'], $depth - 1, $depthOffset - 1);
}
}
return $pages;
};
return $getPagesClosure(
array($startNode),
($depth !== null) ? $depth : INF,
$depthOffset
);
}
}

View file

@ -19,7 +19,7 @@
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 2.0
* @version 2.1
*/
class DummyPlugin extends AbstractPicoPlugin
{
@ -28,7 +28,7 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @var int
*/
const API_VERSION = 2;
const API_VERSION = 3;
/**
* This plugin is disabled by default
@ -36,8 +36,8 @@ class DummyPlugin extends AbstractPicoPlugin
* Usually you should remove this class property (or set it to NULL) to
* leave the decision whether this plugin should be enabled or disabled by
* default up to Pico. If all the plugin's dependenies are fulfilled (see
* {@see self::$dependsOn}), Pico enables the plugin by default. Otherwise
* the plugin is silently disabled.
* {@see DummyPlugin::$dependsOn}), Pico enables the plugin by default.
* Otherwise the plugin is silently disabled.
*
* If this plugin should never be disabled *silently* (e.g. when dealing
* with security-relevant stuff like access control, or similar), set this
@ -79,8 +79,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getPlugins()
*
* @param object[] $plugins loaded plugin instances
*
* @return void
*/
public function onPluginsLoaded(array $plugins)
{
@ -95,8 +93,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getPlugins()
*
* @param object $plugin loaded plugin instance
*
* @return void
*/
public function onPluginManuallyLoaded($plugin)
{
@ -108,26 +104,50 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @see Pico::getConfig()
* @see Pico::getBaseUrl()
* @see Pico::getBaseThemeUrl()
* @see Pico::isUrlRewritingEnabled()
*
* @param array &$config array of config variables
*
* @return void
*/
public function onConfigLoaded(array &$config)
{
// your code
}
/**
* Triggered before Pico loads its theme
*
* @see Pico::loadTheme()
* @see DummyPlugin::onThemeLoaded()
*
* @param string &$theme name of current theme
*/
public function onThemeLoading(&$theme)
{
// your code
}
/**
* Triggered after Pico loaded its theme
*
* @see DummyPlugin::onThemeLoading()
* @see Pico::getTheme()
* @see Pico::getThemeApiVersion()
*
* @param string $theme name of current theme
* @param int $themeApiVersion API version of the theme
* @param array &$themeConfig config array of the theme
*/
public function onThemeLoaded($theme, $themeApiVersion, array &$themeConfig)
{
// your code
}
/**
* Triggered after Pico has evaluated the request URL
*
* @see Pico::getRequestUrl()
*
* @param string &$url part of the URL describing the requested contents
*
* @return void
*/
public function onRequestUrl(&$url)
{
@ -141,8 +161,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getRequestFile()
*
* @param string &$file absolute path to the content file to serve
*
* @return void
*/
public function onRequestFile(&$file)
{
@ -154,8 +172,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @see Pico::loadFileContent()
* @see DummyPlugin::onContentLoaded()
*
* @return void
*/
public function onContentLoading()
{
@ -167,8 +183,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @see Pico::load404Content()
* @see DummyPlugin::on404ContentLoaded()
*
* @return void
*/
public function on404ContentLoading()
{
@ -183,8 +197,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::is404Content()
*
* @param string &$rawContent raw file contents
*
* @return void
*/
public function on404ContentLoaded(&$rawContent)
{
@ -203,8 +215,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::is404Content()
*
* @param string &$rawContent raw file contents
*
* @return void
*/
public function onContentLoaded(&$rawContent)
{
@ -216,8 +226,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @see Pico::parseFileMeta()
* @see DummyPlugin::onMetaParsed()
*
* @return void
*/
public function onMetaParsing()
{
@ -231,8 +239,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getFileMeta()
*
* @param string[] &$meta parsed meta data
*
* @return void
*/
public function onMetaParsed(array &$meta)
{
@ -246,8 +252,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::substituteFileContent()
* @see DummyPlugin::onContentPrepared()
* @see DummyPlugin::onContentParsed()
*
* @return void
*/
public function onContentParsing()
{
@ -262,8 +266,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see DummyPlugin::onContentParsed()
*
* @param string &$markdown Markdown contents of the requested page
*
* @return void
*/
public function onContentPrepared(&$markdown)
{
@ -278,8 +280,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getFileContent()
*
* @param string &$content parsed contents (HTML) of the requested page
*
* @return void
*/
public function onContentParsed(&$content)
{
@ -291,8 +291,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @see DummyPlugin::onPagesDiscovered()
* @see DummyPlugin::onPagesLoaded()
*
* @return void
*/
public function onPagesLoading()
{
@ -314,8 +312,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @param string $id relative path to the content file
* @param bool|null $skipPage set this to TRUE to remove this page from the
* pages array, otherwise leave it unchanged
*
* @return void
*/
public function onSinglePageLoading($id, &$skipPage)
{
@ -334,8 +330,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @param string $id relative path to the content file
* @param string &$rawContent raw file contents
*
* @return void
*/
public function onSinglePageContent($id, &$rawContent)
{
@ -352,8 +346,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see DummyPlugin::onSinglePageContent()
*
* @param array &$pageData data of the loaded page
*
* @return void
*/
public function onSinglePageLoaded(array &$pageData)
{
@ -372,8 +364,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see DummyPlugin::onPagesLoaded()
*
* @param array[] &$pages list of all known pages
*
* @return void
*/
public function onPagesDiscovered(array &$pages)
{
@ -392,8 +382,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getPages()
*
* @param array[] &$pages sorted list of all known pages
*
* @return void
*/
public function onPagesLoaded(array &$pages)
{
@ -415,8 +403,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @param array|null &$currentPage data of the page being served
* @param array|null &$previousPage data of the previous page
* @param array|null &$nextPage data of the next page
*
* @return void
*/
public function onCurrentPageDiscovered(
array &$currentPage = null,
@ -435,8 +421,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getPageTree()
*
* @param array &$pageTree page tree
*
* @return void
*/
public function onPageTreeBuilt(array &$pageTree)
{
@ -450,8 +434,6 @@ class DummyPlugin extends AbstractPicoPlugin
*
* @param string &$templateName file name of the template
* @param array &$twigVariables template variables
*
* @return void
*/
public function onPageRendering(&$templateName, array &$twigVariables)
{
@ -464,8 +446,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see DummyPlugin::onPageRendering()
*
* @param string &$output contents which will be sent to the user
*
* @return void
*/
public function onPageRendered(&$output)
{
@ -480,8 +460,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @param string[] &$headers list of known meta header fields; the array
* key specifies the YAML key to search for, the array value is later
* used to access the found value
*
* @return void
*/
public function onMetaHeaders(array &$headers)
{
@ -494,8 +472,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getYamlParser()
*
* @param \Symfony\Component\Yaml\Parser &$yamlParser YAML parser instance
*
* @return void
*/
public function onYamlParserRegistered(\Symfony\Component\Yaml\Parser &$yamlParser)
{
@ -508,8 +484,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getParsedown()
*
* @param Parsedown &$parsedown Parsedown instance
*
* @return void
*/
public function onParsedownRegistered(Parsedown &$parsedown)
{
@ -522,8 +496,6 @@ class DummyPlugin extends AbstractPicoPlugin
* @see Pico::getTwig()
*
* @param Twig_Environment &$twig Twig instance
*
* @return void
*/
public function onTwigRegistered(Twig_Environment &$twig)
{