Merge pull request #334 from picocms/pico-1.1

Pico 2.0
This commit is contained in:
Daniel Rudolf 2018-07-01 17:45:28 +02:00 committed by GitHub
commit 6a7494a54d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 3128 additions and 2519 deletions

77
.build/create-release.sh Executable file
View file

@ -0,0 +1,77 @@
#!/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 'config/config.yml.template'..."
cp vendor/picocms/pico/config/config.yml.template config/config.yml.template
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

44
.build/deploy-branch.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -e
export PATH="$PICO_TOOLS_DIR:$PATH"
# get current Pico milestone
VERSION="$(php -r "require_once('$PICO_PROJECT_DIR/lib/Pico.php'); echo Pico::VERSION;")"
MILESTONE="Pico$([[ "$VERSION" =~ ^([0-9]+\.[0-9]+)\. ]] && echo " ${BASH_REMATCH[1]}")"
echo "Deploying $PROJECT_REPO_BRANCH branch ($MILESTONE)..."
echo
# clone repo
github-clone.sh "$PICO_DEPLOY_DIR" "https://github.com/$DEPLOY_REPO_SLUG.git" "$DEPLOY_REPO_BRANCH"
cd "$PICO_DEPLOY_DIR"
# setup repo
github-setup.sh
# generate phpDocs
generate-phpdoc.sh \
"$PICO_PROJECT_DIR/.phpdoc.xml" \
"$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT.cache" "$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT" \
"$MILESTONE API Documentation ($PROJECT_REPO_BRANCH branch)"
if [ -z "$(git status --porcelain "$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT.cache")" ]; then
# nothing to do
exit 0
fi
# update phpDoc list
update-phpdoc-list.sh \
"$PICO_DEPLOY_DIR/_data/phpDoc.yml" \
"$PICO_DEPLOYMENT" "branch" "<code>$PROJECT_REPO_BRANCH</code> branch" "$(date +%s)"
# commit phpDocs
github-commit.sh \
"Update phpDocumentor class docs for $PROJECT_REPO_BRANCH branch @ $PROJECT_REPO_COMMIT" \
"$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT.cache" "$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT" \
"$PICO_DEPLOY_DIR/_data/phpDoc.yml"
# deploy phpDocs
github-deploy.sh "$PROJECT_REPO_SLUG" "heads/$PROJECT_REPO_BRANCH" "$PROJECT_REPO_COMMIT"

116
.build/deploy-release.sh Executable file
View file

@ -0,0 +1,116 @@
#!/usr/bin/env bash
set -e
DEPLOY_FULL="true"
if [ "$DEPLOY_PHPDOC_RELEASES" != "true" ]; then
echo "Skipping phpDoc release deployment because it has been disabled"
DEPLOY_FULL="false"
fi
if [ "$DEPLOY_VERSION_BADGE" != "true" ]; then
echo "Skipping version badge deployment because it has been disabled"
DEPLOY_FULL="false"
fi
if [ "$DEPLOY_VERSION_FILE" != "true" ]; then
echo "Skipping version file deployment because it has been disabled"
DEPLOY_FULL="false"
fi
if [ "$DEPLOY_CLOC_STATS" != "true" ]; then
echo "Skipping cloc statistics deployment because it has been disabled"
DEPLOY_FULL="false"
fi
if [ "$DEPLOY_FULL" != "true" ]; then
if [ "$DEPLOY_PHPDOC_RELEASES" != "true" ] \
&& [ "$DEPLOY_VERSION_BADGE" != "true" ] \
&& [ "$DEPLOY_VERSION_FILE" != "true" ] \
&& [ "$DEPLOY_CLOC_STATS" != "true" ]
then
# nothing to do
exit 0
fi
echo
fi
export PATH="$PICO_TOOLS_DIR:$PATH"
. "$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
fi
echo "Deploying Pico $VERSION_MILESTONE ($VERSION_STABILITY)..."
printf 'VERSION_FULL="%s"\n' "$VERSION_FULL"
printf 'VERSION_NAME="%s"\n' "$VERSION_NAME"
printf 'VERSION_ID="%s"\n' "$VERSION_ID"
echo
# clone repo
github-clone.sh "$PICO_DEPLOY_DIR" "https://github.com/$DEPLOY_REPO_SLUG.git" "$DEPLOY_REPO_BRANCH"
cd "$PICO_DEPLOY_DIR"
# setup repo
github-setup.sh
# generate phpDocs
if [ "$DEPLOY_PHPDOC_RELEASES" == "true" ]; then
# generate phpDocs
generate-phpdoc.sh \
"$PICO_PROJECT_DIR/.phpdoc.xml" \
"-" "$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT" \
"Pico $VERSION_MILESTONE API Documentation (v$VERSION_FULL)"
if [ -n "$(git status --porcelain "$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT")" ]; then
# update phpDoc list
update-phpdoc-list.sh \
"$PICO_DEPLOY_DIR/_data/phpDoc.yml" \
"$PICO_DEPLOYMENT" "version" "Pico $VERSION_FULL" "$(date +%s)"
# commit phpDocs
github-commit.sh \
"Update phpDocumentor class docs for v$VERSION_FULL" \
"$PICO_DEPLOY_DIR/phpDoc/$PICO_DEPLOYMENT" "$PICO_DEPLOY_DIR/_data/phpDoc.yml"
fi
fi
# don't update version badge, version file and cloc statistics for pre-releases
if [ "$VERSION_STABILITY" == "stable" ]; then
# update version badge
if [ "$DEPLOY_VERSION_BADGE" == "true" ]; then
generate-badge.sh \
"$PICO_DEPLOY_DIR/badges/pico-version.svg" \
"release" "$VERSION_FULL" "blue"
# commit version badge
github-commit.sh \
"Update version badge for v$VERSION_FULL" \
"$PICO_DEPLOY_DIR/badges/pico-version.svg"
fi
# update version file
if [ "$DEPLOY_VERSION_FILE" == "true" ]; then
update-version-file.sh \
"$PICO_DEPLOY_DIR/_data/version.yml" \
"$VERSION_FULL"
# commit version file
github-commit.sh \
"Update version file for v$VERSION_FULL" \
"$PICO_DEPLOY_DIR/_data/version.yml"
fi
# update cloc statistics
if [ "$DEPLOY_CLOC_STATS" == "true" ]; then
update-cloc-stats.sh "$PICO_DEPLOY_DIR/_data/cloc.yml"
# commit cloc statistics
github-commit.sh \
"Update cloc statistics for v$VERSION_FULL" \
"$PICO_DEPLOY_DIR/_data/cloc.yml"
fi
fi
# deploy
github-deploy.sh "$PROJECT_REPO_SLUG" "tags/$PROJECT_REPO_TAG" "$PROJECT_REPO_COMMIT"

6
.build/deploy.sh Executable file
View file

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

54
.build/install.sh Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -e
# 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
# set COMPOSER_ROOT_VERSION when necessary
if [ -z "$COMPOSER_ROOT_VERSION" ] && [ -n "$PROJECT_REPO_BRANCH" ]; then
PICO_VERSION_PATTERN="$(php -r "
\$json = json_decode(file_get_contents('$PICO_PROJECT_DIR/composer.json'), true);
if (\$json !== null) {
if (isset(\$json['extra']['branch-alias']['dev-$PROJECT_REPO_BRANCH'])) {
echo 'dev-$PROJECT_REPO_BRANCH';
}
}
")"
if [ -z "$PICO_VERSION_PATTERN" ]; then
PICO_VERSION_PATTERN="$(php -r "
require_once('$PICO_PROJECT_DIR/lib/Pico.php');
echo preg_replace('/\.[0-9]+-dev$/', '.x-dev', Pico::VERSION);
")"
fi
if [ -n "$PICO_VERSION_PATTERN" ]; then
export COMPOSER_ROOT_VERSION="$PICO_VERSION_PATTERN"
fi
fi
echo
# install dependencies
echo "Running \`composer install\`$([ -n "$COMPOSER_ROOT_VERSION" ] && echo -n " ($COMPOSER_ROOT_VERSION)")..."
composer install --no-suggest

20
.gitattributes vendored
View file

@ -1,8 +1,12 @@
/.github export-ignore /.github export-ignore
/_build export-ignore /.build export-ignore
/.gitattributes export-ignore /assets/.gitignore export-ignore
/.gitignore export-ignore /config/.gitignore export-ignore
/.phpcs.xml export-ignore /content/.gitignore export-ignore
/.phpdoc.xml export-ignore /plugins/.gitignore export-ignore
/.travis.yml export-ignore /themes/.gitignore export-ignore
/index.php.dist export-ignore /.gitattributes export-ignore
/.gitignore export-ignore
/.phpcs.xml export-ignore
/.phpdoc.xml export-ignore
/.travis.yml export-ignore

48
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,48 @@
<!--
Developer Certificate of Origin
===============================
By contributing to Pico, you accept and agree to the following terms and conditions (the *Developer Certificate of Origin*) for your present and future contributions submitted to Pico. Please refer to the *Developer Certificate of Origin* section in Pico's [`CONTRIBUTING.md`](https://github.com/picocms/Pico/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) for details.
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
-->

26
.gitignore vendored
View file

@ -10,28 +10,12 @@ desktop.ini
.DS_Store .DS_Store
._* ._*
# Composer # composer
/composer.lock /composer.lock
/composer.phar /composer.phar
/vendor /vendor
# Build system # phpDocumentor
/_build/phpdoc /.build/phpdoc
/_build/phpdoc.cache /.build/phpdoc.cache
/_build/deploy-*.git /phpDocumentor.phar
# User config
/config/config.php
# User themes
/themes/*
!/themes/default
# User plugins
/plugins/*
!/plugins/0?-*
!/plugins/1?-*
!/plugins/DummyPlugin.php
# User content
/content

View file

@ -2,13 +2,19 @@
RewriteEngine On RewriteEngine On
# May be required to access sub directories # May be required to access sub directories
#RewriteBase / #RewriteBase /
# Deny access to internal dirs and files by passing the URL to Pico
RewriteRule ^(config|content|content-sample|lib|vendor)(/|$) index.php [L]
RewriteRule ^(CHANGELOG\.md|composer\.(json|lock|phar))(/|$) index.php [L]
RewriteRule (^\.|/\.)(?!well-known(/|$)) index.php [L]
# Enable URL rewriting
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?$1 [L,QSA] RewriteRule ^ index.php [L]
RewriteRule ^(\.git|config|content|content-sample|lib|vendor)(/.*)?$ index.php?$0 [L,QSA]
RewriteRule ^(CHANGELOG\.md|composer\.(json|lock)) index.php?404 [L]
<IfModule mod_env.c> <IfModule mod_env.c>
# Let Pico know about available URL rewriting
SetEnv PICO_URL_REWRITING 1 SetEnv PICO_URL_REWRITING 1
</IfModule> </IfModule>
</IfModule> </IfModule>

View file

@ -11,9 +11,10 @@
<file>.</file> <file>.</file>
<!-- <!--
Exclude _build/ and vendor/ dirs as well as minified JavaScript files Exclude .build/, .github/ and vendor/ dirs as well as minified JavaScript files
--> -->
<exclude-pattern type="relative">^_build/</exclude-pattern> <exclude-pattern type="relative">^.build/</exclude-pattern>
<exclude-pattern type="relative">^.github/</exclude-pattern>
<exclude-pattern type="relative">^vendor/</exclude-pattern> <exclude-pattern type="relative">^vendor/</exclude-pattern>
<exclude-pattern>*.min.js</exclude-pattern> <exclude-pattern>*.min.js</exclude-pattern>
@ -41,4 +42,15 @@
<rule ref="PSR2"> <rule ref="PSR2">
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/> <exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/>
</rule> </rule>
<!--
The PHP-FIG PSR-2 Coding Style doesn't fully apply to JavaScript files
Furthermore, some sniffs aren't able to handle JavaScript peculiarities
-->
<rule ref="Generic.ControlStructures.InlineControlStructure">
<exclude-pattern>*.js</exclude-pattern>
</rule>
<rule ref="Squiz.Functions.MultiLineFunctionDeclaration">
<exclude-pattern>*.js</exclude-pattern>
</rule>
</ruleset> </ruleset>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<phpdoc> <phpdoc>
<title><![CDATA[Pico 1.0 API Documentation]]></title> <title><![CDATA[Pico API Documentation]]></title>
<parser> <parser>
<target>_build/phpdoc.cache</target> <target>.build/phpdoc.cache</target>
</parser> </parser>
<transformer> <transformer>
<target>_build/phpdoc</target> <target>.build/phpdoc</target>
</transformer> </transformer>
<transformations> <transformations>
<template name="clean"/> <template name="clean"/>
@ -15,12 +15,13 @@
<file>index.php</file> <file>index.php</file>
<file>index.php.dist</file> <file>index.php.dist</file>
<!-- exclude build environment --> <!-- exclude .build and .github directories -->
<ignore>_build/*</ignore> <ignore>.build/*</ignore>
<ignore>.github/*</ignore>
<!-- exclude user config --> <!-- exclude user config -->
<ignore>config/*</ignore> <ignore>config/*</ignore>
<file>config/config.php.template</file> <file>config/config.yml.template</file>
<!-- exclude all plugins --> <!-- exclude all plugins -->
<ignore>plugins/*</ignore> <ignore>plugins/*</ignore>

View file

@ -1,50 +1,68 @@
language: php dist: trusty
php: sudo: false
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- nightly
- hhvm
- hhvm-nightly
matrix: language: php
cache:
directories:
- $HOME/.composer/cache/files
jobs:
include: include:
# Test stage
- php: 5.3 - php: 5.3
dist: precise dist: precise
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
- php: nightly
- php: hhvm
- php: hhvm-nightly
# Deployment stage
- stage: deploy
sudo: required
install:
- '[ "$TRAVIS_PULL_REQUEST" == "false" ] || travis_terminate 0'
- '[ -n "$TRAVIS_TAG" ] || [[ ",$DEPLOY_PHPDOC_BRANCHES," == *,"$TRAVIS_BRANCH",* ]] || travis_terminate 0'
- install.sh --deploy
script:
- deploy.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"
deploy:
provider: releases
api_key: ${GITHUB_OAUTH_TOKEN}
file: pico-release-$TRAVIS_TAG.tar.gz
skip_cleanup: true
tag_name: $TRAVIS_TAG
target_commitish: $TRAVIS_COMMIT
name: Version $TRAVIS_TAG
draft: true
on:
tags: true
# Ignore nightly build failures
allow_failures: allow_failures:
- php: nightly - php: nightly
- php: hhvm-nightly - php: hhvm-nightly
fast-finish: true fast-finish: true
notifications: before_install:
irc: "chat.freenode.net#picocms" - 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"
install: install:
- composer install - install.sh
before_script: before_script:
- export PATH="$TRAVIS_BUILD_DIR/_build:$TRAVIS_BUILD_DIR/vendor/bin:$PATH" - export PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH"
script: script:
- phpcs --standard=.phpcs.xml "$TRAVIS_BUILD_DIR" - phpcs --standard=.phpcs.xml "$TRAVIS_BUILD_DIR"
after_success:
- deploy-phpdoc-branch.sh
before_deploy:
- deploy-phpdoc-release.sh
- create-release-archive.sh "$TRAVIS_TAG"
deploy:
provider: releases
api_key: ${GITHUB_OAUTH_TOKEN}
file: pico-release-$TRAVIS_TAG.tar.gz
skip_cleanup: true
on:
tags: true
php: 5.3
dist: trusty
sudo: false

View file

@ -1,6 +1,194 @@
Pico Changelog Pico Changelog
============== ==============
**Note:** This changelog only provides technical information about the changes
introduced with a particular Pico version, and is meant to supplement
the actual code changes. The information in this changelog are often
insufficient to understand the implications of larger changes. Please
refer to both the UPGRADE and NEWS sections of the docs for more
details.
**Note:** Changes breaking backwards compatibility (BC) are marked with an `!`
(exclamation mark). This doesn't include changes for which BC is
preserved by Pico's official `PicoDeprecated` plugin. If a previously
deprecated feature is later removed in `PicoDeprecated`, this change
is going to be marked as BC-breaking change in both Pico's and
`PicoDeprecated`'s changelog. Please note that BC-breaking changes
are only possible with a new major version.
### Version 2.0.0
Released: 2018-07-01
```
* [New] Add Bountysource
* [Changed] Improve documentation
* [Changed] Improve release & build process
* [Fixed] Don't load `config/config.yml` multiple times
```
### Version 2.0.0-beta.3
Released: 2018-04-07
```
* [Changed] Add `README.md`, `CONTRIBUTING.md` and `CHANGELOG.md` of main repo
to pre-bundled releases, keep `.gitignore`
* [Changed] Deny access to a possibly existing `composer.phar` in `.htaccess`
* [Changed] Disallow the use of the `callback` filter for the `url_param` and
`form_param` Twig functions
* [Changed] Improve documentation
* [Fixed] Fix page tree when sorting pages by arbitrary values
* [Fixed] Fix sorting of `Pico::$nativePlugins`
```
### Version 2.0.0-beta.2
Released: 2018-01-21
```
* [New] Improve release & build process and move most build tools to the new
`picocms/ci-tools` repo, allowing them to be used by other projects
* [New] Add page tree; refer to the `Pico::buildPageTree()` method for more
details; also see the `onPageTreeBuilt` event
* [Changed] Update dependencies: Twig 1.35
* [Changed] ! Improve `.htaccess` and deny access to all dot files by default
* [Changed] ! Throw a `RuntimeException` when non-native plugins are loaded,
but Pico's `PicoDeprecated` plugin is not loaded
* [Changed] ! Change `AbstractPicoPlugin::$enabled`'s behavior: setting it to
TRUE now leads to throwing a `RuntimeException` when the plugin's
dependencies aren't fulfilled; use NULL to maintain old behavior
* [Changed] ! Force themes to use `.twig` as file extension for Twig templates
* [Changed] Improve PHP class docs
* [Changed] Various small improvements
```
### Version 2.0.0-beta.1
Released: 2017-11-05
```
* [New] Pico is on its way to its second major release!
* [New] Improve Pico's release & build process
* [New] Add "Developer Certificate of Origin" to `CONTRIBUTING.md`
* [New] Add license & copyright header to all relevant files
* [New] Add Pico version constants (`Pico::VERSION` and `Pico::VERSION_ID`),
and add a `version` Twig variable and `%version%` Markdown placeholder
* [New] Add Pico API versioning for plugins (see `Pico::API_VERSION` constant);
Pico now triggers events on plugins using the latest API version only
("native" plugins), `PicoDeprecated` takes care of all other plugins;
as a result, old plugin's always depend on `PicoDeprecated` now
* [New] Add a theme and plugin installer for composer; Pico now additionally
uses the new `vendor/pico-plugin.php` file to discover plugins
installed by composer and loads them using composer's autoloader;
see the `picocms/composer-installer` repo for more details; Pico
loads plugins installed by composer first and ignores conflicting
plugins in Pico's `plugins/` dir
* [New] Add `$enableLocalPlugins` parameter to `Pico::__construct()` to allow
website developers to disable local plugin discovery by scanning the
`plugins/` dir (i.e. load plugins from `vendor/pico-plugin.php` only)
* [New] Add public `AbstractPicoPlugin::getPluginConfig()` method
* [New] Add public `Pico::loadPlugin()` method and the corresponding
`onPluginManuallyLoaded` event
* [New] Add public `Pico::resolveFilePath()` method (replaces the protected
`Pico::discoverRequestFile()` method)
* [New] Add public `Pico::is404Content()` method
* [New] Add public `Pico::getYamlParser()` method and the corresponding
`onYamlParserRegistered` event
* [New] Add public `Pico::substituteFileContent()` method
* [New] Add public `Pico::getPageId()` method
* [New] Add public `Pico::getFilesGlob()` method
* [New] Add public `Pico::getVendorDir()` method, returning Pico's installation
directory (i.e. `/var/www/pico/vendor/picocms/pico`); don't confuse
this with composer's `vendor/` dir!
* [New] Add `$default` parameter to `Pico::getConfig()` method
* [New] Add empty `assets/` and `content/` dirs
* [New] #305: Add `url_param` and `form_param` Twig functions, and the public
`Pico::getUrlParameter()` and `Pico::getFormParameter()` methods,
allowing theme developers to access URL GET and HTTP POST parameters
* [New] Add `$meta` parameter to `markdown` Twig filter
* [New] Add `remove` fallback to `sort_by` Twig filter
* [New] Add `theme_url` config parameter
* [New] Add public `Pico::getBaseThemeUrl()` method
* [New] Add `REQUEST_URI` routing method, allowing one to simply rewrite all
requests to `index.php` (e.g. use `FallbackResource` or `mod_rewrite`
in your `.htaccess` for Apache, or use `try_files` for nginx)
* [New] #299: Add built-in 404 page as fallback when no `404.md` is found
* [New] Allow sorting pages by arbitrary meta values
* [New] Add `onSinglePageLoading` event, allowing one to skip a page
* [New] Add `onSinglePageContent` event
* [New] Add some config parameters to change Parsedown's behavior
* [Changed] ! Disallow running the same Pico instance multiple times by
throwing a `RuntimeException` when calling `Pico::run()`
* [Changed] ! #203: Load plugins from `plugins/<plugin name>/<plugin name>.php`
and `plugins/<plugin name>.php` only (directory and file name must
match case-sensitive), and throw a `RuntimeException` when Pico is
unable to load a plugin; also throw a `RuntimeException` when
superfluous files or directories in `plugins/` are found; use a
scope-isolated `require()` to include plugin files
* [Changed] ! Use a plugin dependency topology to sort `Pico::$plugins`,
changing the execution order of plugins so that plugins, on which
other plugins depend, are always executed before their dependants
* [Changed] ! Don't pass `$plugins` parameter to `onPluginsLoaded` event by
reference anymore; use `Pico::loadPlugin()` instead
* [Changed] ! Leave `Pico::$pages` unsorted when a unknown sort method was
configured; this usually means that a plugin wants to sort it
* [Changed] Overhaul page discovery events: add `onPagesDiscovered` event which
is triggered right before `Pico::$pages` is sorted and move the
`$currentPage`, `$previousPage` and `$nextPage` parameters of the
`onPagesLoaded` event to the new `onCurrentPageDiscovered` event
* [Changed] Move the `$twig` parameter of the `onPageRendering` event to the
`onTwigRegistered` event, replacing the `onTwigRegistration` event
* [Changed] Unify the `onParsedownRegistration` event by renaming it to
`onParsedownRegistered` and add the `$parsedown` parameter
* [Changed] #330: Replace `config/config.php` by a modular YAML-based approach;
you can now use a arbitrary number of `config/*.yml` files to
configure Pico
* [Changed] ! When trying to auto-detect Pico's `content` dir, Pico no longer
searches just for a (possibly empty) directory, but rather checks
whether a `index.md` exists in this directory
* [Changed] ! Use the relative path between `index.php` and `Pico::$themesDir`
for Pico's theme URL (also refer to the new `theme_url` config and
the public `Pico::getBaseThemeUrl()` method for more details)
* [Changed] #347: Drop the superfluous trailing "/index" from Pico's URLs
* [Changed] Flip registered meta headers array, so that the array key is used
to search for a meta value and the array value is used to store the
found meta value (previously it was the other way round)
* [Changed] ! Add lazy loading for `Pico::$yamlParser`, `Pico::$parsedown` and
`Pico::$twig`; the corresponding events are no longer part of
Pico's event flow and are triggered on demand
* [Changed] ! Trigger the `onMetaHeaders` event just once; the event is no
longer part of Pico's event flow and is triggered on demand
* [Changed] Don't lower meta headers on the first level of a page's meta data
(i.e. `SomeKey: value` is accessible using `$meta['SomeKey']`)
* [Changed] Don't compare registered meta headers case-insensitive, require
matching case
* [Changed] Allow users to explicitly set values for the `date_formatted` and
`time` meta headers in a page's YAML front matter
* [Changed] Add page siblings for all pages
* [Changed] ! Treat pages or directories that are prefixed by `_` as hidden;
when requesting a hidden page, Pico responds with a 404 page;
hidden pages are still in `Pico::$pages`, but are moved to the end
of the pages array when sorted alphabetically or by date
* [Changed] ! Don't treat explicit requests to a 404 page as successful request
* [Changed] Change method visibility of `Pico::getFiles()` to public
* [Changed] Change method visibility of `Pico::triggerEvent()` to public;
at first glance this method triggers events on native plugins only,
however, `PicoDeprecated` takes care of triggering events for other
plugins, thus you can use this method to trigger (custom) events on
all plugins; never use it to trigger Pico core events!
* [Changed] Move Pico's default theme to the new `picocms/pico-theme` repo; the
theme was completely rewritten from scratch and is a much better
starting point for creating your own theme; refer to the theme's
`CHANGELOG.md` for more details
* [Changed] Move `PicoDeprecated` plugin to the new `picocms/pico-deprecated`
repo; refer to the plugin's `CHANGELOG.md` for more details
* [Changed] Update dependencies: Twig 1.34, Symfony YAML 2.8, Parsedown 1.6
* [Changed] Improve Pico docs and PHP class docs
* [Changed] A vast number of small improvements and changes...
* [Removed] ! Remove `PicoParsePagesContent` plugin
* [Removed] ! Remove `PicoExcerpt` plugin
* [Removed] Remove `rewrite_url` and `is_front_page` Twig variables
* [Removed] Remove superfluous parameters of various events to reduce Pico's
error-proneness (plugins hopefully interfere with each other less)
```
### Version 1.0.6 ### Version 1.0.6
Released: 2017-07-25 Released: 2017-07-25
@ -124,10 +312,6 @@ Released: 2015-11-30
### Version 1.0.0-beta.1 ### Version 1.0.0-beta.1
Released: 2015-11-06 Released: 2015-11-06
**Note:** This changelog only provides basic information about the enormous
changes introduced with Pico 1.0.0-beta.1. Please refer to the
UGPRADE section of the docs for details.
``` ```
* [Security] (9e2604a) Prevent content_dir breakouts using malicious URLs * [Security] (9e2604a) Prevent content_dir breakouts using malicious URLs
* [New] Pico is on its way to its first stable release! * [New] Pico is on its way to its first stable release!

View file

@ -3,9 +3,9 @@ Contributing to Pico
Pico aims to be a high quality Content Management System (CMS) but at the same time wants to give contributors freedom when submitting fixes or improvements. Pico aims to be a high quality Content Management System (CMS) but at the same time wants to give contributors freedom when submitting fixes or improvements.
As such we want to *encourage* but not obligate you, the contributor, to follow these guidelines. The only exception to this are the guidelines elucidated in the *Prevent `merge-hell`* section. By contributing to Pico, you accept and agree to the *Developer Certificate of Origin* for your present and future contributions submitted to Pico. Please refer to the *Developer Certificate of Origin* section below.
Having said that: we really appreciate it when you apply the guidelines in part or wholly as that will save us time which, in turn, we can spend on bugfixes and new features. Aside from this, we want to *encourage*, but not obligate you, the contributor, to follow the following guidelines. The only exception to this are the guidelines elucidated in the *Prevent `merge-hell`* section. Having said that: we really appreciate it when you apply the guidelines in part or wholly as that will save us time which, in turn, we can spend on bugfixes and new features.
Issues Issues
------ ------
@ -16,11 +16,84 @@ Before creating a [new Issue on GitHub](https://github.com/picocms/Pico/issues/n
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*). 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*).
Contributing code Contributing
----------------- ------------
Once you decide you want to contribute to *Pico's core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/development/) of our website. Once you decide you want to contribute to *Pico's core* (which we really appreciate!) you can fork the project from https://github.com/picocms/Pico. If you're interested in developing a *plugin* or *theme* for Pico, please refer to the [development section](http://picocms.org/development/) of our website.
### Developer Certificate of Origin
By contributing to Pico, you accept and agree to the following terms and conditions for your present and future contributions submitted to Pico. Except for the license granted herein to Pico and recipients of software distributed by Pico, you reserve all right, title, and interest in and to your contributions. All contributions are subject to the following DCO + license terms.
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
All contributions to this project are licensed under the following MIT License:
```
Copyright (c) <YEAR> <COPYRIGHT HOLDER>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
```
Please note that developing a *plugin* or *theme* for Pico is *not* assumed to be a contribution to Pico itself. By developing a plugin or theme you rather create a 3rd-party project that just uses Pico. Following the spirit of open source, we want to *encourage* you to release your plugin or theme under the terms of a [OSI-approved open source license](https://opensource.org/licenses). After all, Pico is open source, too!
### Prevent `merge-hell` ### Prevent `merge-hell`
Please do *not* develop your contribution on the `master` branch of your fork, but create a separate feature branch, that is based off the `master` branch, for each feature that you want to contribute. Please do *not* develop your contribution on the `master` branch of your fork, but create a separate feature branch, that is based off the `master` branch, for each feature that you want to contribute.
@ -49,7 +122,7 @@ With this command you can specify a file or folder to limit which files it will
Pico accepts the problems of having redundant documentation on different places (concretely Pico's inline user docs, the `README.md` and the website) for the sake of a better user experience. When updating the docs, please make sure to keep them in sync. Pico accepts the problems of having redundant documentation on different places (concretely Pico's inline user docs, the `README.md` and the website) for the sake of a better user experience. When updating the docs, please make sure to keep them in sync.
If you update the [`README.md`](https://github.com/picocms/Pico/blob/master/README.md) or [`content-sample/index.md`](https://github.com/picocms/Pico/blob/master/content-sample/index.md), please make sure to update the corresponding files in the [`_docs`](https://github.com/picocms/picocms.github.io/tree/master/_docs/) folder of the `picocms.github.io` repo (i.e. [Pico's website](http://picocms.org/docs/)) and vice versa. Unfortunately this involves three (!) different markdown parsers. If you're experiencing problems, use Pico's [`erusev/parsedown-extra`](https://github.com/erusev/parsedown-extra) as a reference. You can try to make the contents compatible to [Kramdown](http://kramdown.gettalong.org/) (Pico's website) and [Redcarpet](https://github.com/vmg/redcarpet) (`README.md`) by yourself, otherwise please address the issues in your pull request message and we'll take care of it. If you update the [`README.md`](https://github.com/picocms/Pico/blob/master/README.md) or [`content-sample/index.md`](https://github.com/picocms/Pico/blob/master/content-sample/index.md), please make sure to update the corresponding files in the [`_docs`](https://github.com/picocms/picocms.github.io/tree/master/_docs/) folder of the `picocms.github.io` repo (i.e. [Pico's website](http://picocms.org/docs/)) and vice versa. Unfortunately this involves three (!) different markdown parsers. If you're experiencing problems, use Pico's [`erusev/parsedown-extra`](https://github.com/erusev/parsedown-extra) as a reference. You can try to make the contents compatible to [Kramdown](http://kramdown.gettalong.org/) (Pico's website) and [CommonMarker](https://github.com/gjtorikian/commonmarker) (`README.md`) by yourself, otherwise please address the issues in your pull request message and we'll take care of it.
Versioning Versioning
---------- ----------
@ -75,12 +148,12 @@ Pico's actual development happens in separate development branches. Development
- `enhancement/` for smaller improvements, and - `enhancement/` for smaller improvements, and
- `bugfix/` for non-trivial bug fixes. - `bugfix/` for non-trivial bug fixes.
As soon as development reaches a point where feedback is appreciated, a pull request is opened. After some time (very soon for bug fixes, and other improvements should have a reasonable feedback phase) the pull request is merged and the development branch will be deleted. Trivial bug fixes which will be part of the next `PATCH` version will be merged directly into `master`. As soon as development reaches a point where feedback is appreciated, a pull request is opened. After some time (very soon for bug fixes, and other improvements should have a reasonable feedback phase) the pull request is merged and the development branch will be deleted. Trivial bug fixes that will be part of the next `PATCH` version will be merged directly into `master`.
Build & Release process Build & Release process
----------------------- -----------------------
We're using [Travis CI](https://travis-ci.com) to automate the build & release process of Pico. It generates and deploys [phpDoc](http://phpdoc.org) class docs for new releases and on every commit to the `master` branch. Travis also prepares new releases by generating Pico's pre-built packages, a version badge and pushing both to our website (the [`picocms.github.io` repo](https://github.com/picocms/picocms.github.io)). Please refer to our [`.travis.yml`](https://github.com/picocms/Pico/blob/master/.travis.yml) and the [`_build` directory](https://github.com/picocms/Pico/tree/master/_build) for details. We're using [Travis CI](https://travis-ci.com) to automate the build & release process of Pico. It generates and deploys a [PHP class documentation](http://picocms.org/phpDoc/) (powered by [phpDoc](http://phpdoc.org)) for new releases and on every commit to the `master` branch. Travis also prepares new releases by generating Pico's pre-built release packages, a version badge, code statistics (powered by [cloc](https://github.com/AlDanial/cloc)) and updates our website (the [`picocms.github.io` repo](https://github.com/picocms/picocms.github.io)). Please refer to our [`.travis.yml`](https://github.com/picocms/Pico/blob/master/.travis.yml), the [`picocms/ci-tools` repo](https://github.com/picocms/ci-tools) and the [`.build` directory](https://github.com/picocms/Pico/tree/master/.build) for details.
As insinuated above, it is important that each commit to `master` is deployable. Once development of a new Pico release is finished, trigger Pico's build & release process by pushing a new Git tag. This tag should reference a (usually empty) commit on `master`, which message should adhere to the following template: As insinuated above, it is important that each commit to `master` is deployable. Once development of a new Pico release is finished, trigger Pico's build & release process by pushing a new Git tag. This tag should reference a (usually empty) commit on `master`, which message should adhere to the following template:
@ -94,6 +167,10 @@ Version 1.0.0
* [Removed] ... * [Removed] ...
``` ```
Before pushing a new Git tag, make sure to update the `Pico::VERSION` and `Pico::VERSION_ID` constants. The versions of Pico's official [default theme](https://github.com/picocms/pico-theme) and the [`PicoDeprecated` plugin](https://github.com/picocms/pico-deprecated) both strictly follow Pico's version. Since Pico's pre-built release package contains them, you must always create a new release of them (even though nothing has changed) before creating a new Pico release.
If you're pushing a new major or minor release of Pico, you should also update Pico's `composer.json` to require the latest minor releases of Pico's dependencies. Besides, don't forget to update the `@version` tags in the PHP class docs.
Travis CI will draft the new [release on GitHub](https://github.com/picocms/Pico/releases) automatically, but will require you to manually amend the descriptions formatting. The latest Pico version is always available at https://github.com/picocms/Pico/releases/latest, so please make sure to publish this URL rather than version-specific URLs. [Packagist](http://packagist.org/packages/picocms/pico) will be updated automatically. Travis CI will draft the new [release on GitHub](https://github.com/picocms/Pico/releases) automatically, but will require you to manually amend the descriptions formatting. The latest Pico version is always available at https://github.com/picocms/Pico/releases/latest, so please make sure to publish this URL rather than version-specific URLs. [Packagist](http://packagist.org/packages/picocms/pico) will be updated automatically.
Labeling of Issues & Pull Requests Labeling of Issues & Pull Requests

134
README.md
View file

@ -5,88 +5,114 @@ Pico
[![Version](https://picocms.github.io/badges/pico-version.svg)](https://github.com/picocms/Pico/releases/latest) [![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) [![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) [![Freenode IRC Webchat](https://picocms.github.io/badges/pico-chat.svg)](https://webchat.freenode.net/?channels=%23picocms)
[![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. 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. Visit us at http://picocms.org/ and see http://picocms.org/about/ for more info.
Screenshot Screenshot
------- ----------
![Pico Screenshot](https://cloud.githubusercontent.com/assets/640217/11488596/70b39396-978d-11e5-885e-01341ad9dd75.gif)
![Pico Screenshot](https://picocms.github.io/screenshots/pico-20.png)
Install Install
------- -------
You can install Pico either using a pre-bundled release or with composer. Pico is also available on [Packagist.org][] and may be included in other projects via `composer require picocms/pico`. Pico requires PHP 5.3.6+ 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. 😇
#### Using a pre-bundled release Pico requires PHP 5.3.6+
Just [download the latest Pico release][LatestRelease] and upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server. ### I want to use Composer
#### Composer Starting with Pico 2.0 we recommend installing Pico using Composer whenever possible. Trust us, you won't regret it when it comes to upgrading Pico! Anyway, if you don't want to use Composer, or if you simply can't use Composer because you don't have access to a shell on your server, don't despair, installing Pico using a pre-bundled release is still easier than everything you know!
###### Step 1 - for users ###### Step 1
[Download the *source code* of Pico's latest release][LatestRelease], upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server and navigate to the upload directory using a shell.
Open a shell and navigate to the desired install directory of Pico within the `httpdocs` directory (e.g. `/var/www/html`) of your server. Download Composer and run it with the `create-project` option:
###### Step 1 - for developers
Open a shell and navigate to the desired install directory of Pico within the `httpdocs` directory (e.g. `/var/www/html`) of your server. You can now clone Pico's Git repository as follows:
```shell ```shell
$ git clone https://github.com/picocms/Pico.git . $ curl -sSL https://getcomposer.org/installer | php
$ php composer.phar create-project picocms/pico-composer .
``` ```
Please note that this gives you the current development version of Pico, what is likely *unstable* and *not ready for production use*!
###### Step 2 ###### Step 2
Download [composer][] and run it with the `install` option:
What second step? There's no second step. That's it! Open your favorite web browser and navigate to your brand new, stupidly simple, blazing fast, flat file CMS! Pico's default contents will explain how to create your own contents. 😊
### I want to use a pre-bundled release
Do you know the feeling: You want to install a new website, so you upload all files of your favorite CMS and run the setup script - just to find out that you forgot about creating the SQL database first? Later the setup script tells you that the file permissions are wrong. Heck, what does this even mean? Forget about it, Pico is different!
###### Step 1
[Download the latest Pico release][LatestRelease] and upload all files to the `httpdocs` directory (e.g. `/var/www/html`) of your server.
###### Step 2
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 default contents will explain how to create your own contents. 😊
### 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] and use Composer to install its dependencies. You can find both [Pico][PicoPackagist] and [Pico's Composer starter project][PicoComposerPackagist] on [Packagist.org][Packagist]. Using Pico's Git repository is different from using one of the installation methods elucidated above, because it uses Pico as the Composer root package. Furthermore it gives you the current development version of Pico, what is likely *unstable* and *not ready for production use*!
Open a shell and navigate to the desired install directory of Pico within the `httpdocs` directory (e.g. `/var/www/html`) of your server. You can now clone Pico's Git repository, download Composer and install Pico's dependencies as follows:
```shell ```shell
$ curl -sS https://getcomposer.org/installer | php $ git clone https://github.com/picocms/Pico.git .
$ curl -sSL https://getcomposer.org/installer | php
$ php composer.phar install $ php composer.phar install
``` ```
Upgrade Upgrade
------- -------
Upgrading Pico is very easy: You just have to replace all of Pico's files - that's it! Nevertheless you should *always* create a backup of your Pico installation before upgrading. Do you remember when you installed Pico? It was ingeniously simple, wasn't it? Upgrading Pico is no difference! The upgrade process differs depending on whether you used [Composer][] or a pre-bundled release to install Pico. Please note that you should *always* create a backup of your Pico installation before upgrading!
Pico follows [Semantic Versioning 2.0][SemVer] and uses version numbers like `MAJOR`.`MINOR`.`PATCH`. When we update... Pico follows [Semantic Versioning 2.0][SemVer] and uses version numbers like `MAJOR`.`MINOR`.`PATCH`. When we update the `PATCH` version (e.g. `2.0.0` to `2.0.1`), we made backwards-compatible bug fixes. If we change the `MINOR` version (e.g. `2.0` to `2.1`), we added functionality in a backwards-compatible manner. Upgrading Pico is dead simple in both cases. Simply head over to the appropiate Upgrade sections below.
- the `PATCH` version (e.g. `1.0.0` to `1.0.1`), we made backwards-compatible bug fixes. It's then sufficient to extract [Pico's latest release][LatestRelease] to your existing installation directory and overwriting all files. Alternatively you can either use the [*source code* of Pico's latest release][LatestRelease] or pull from Pico's Git repository, but are then required to update Pico's [composer][] dependencies manually by running `php composer.phar update`. But wait, we forgot to mention what happens when we update the `MAJOR` version (e.g. `2.0` to `3.0`). In this case we made incompatible API changes. We will then provide a appropriate upgrade tutorial, so please head over to the ["Upgrade" page on our website][HelpUpgrade].
- the `MINOR` version (e.g. `1.0` to `1.1`), we added functionality in a backwards-compatible manner, but anyway recommend you to "install" Pico newly. Backup all of your files, empty your installation directory and install Pico as elucidated above. You can then copy your `config/config.php` and `content` directory without any change. If applicable, you can also copy the folder of your custom theme within the `themes` directory. Provided that you're using plugins, also copy all of your plugins from the `plugins` directory. ### I've used Composer to install Pico
- the `MAJOR` version (e.g. `1.0` to `2.0`), we made incompatible API changes. We will then provide a appropriate upgrade tutorial. Upgrading Pico is dead simple if you've used Composer to install Pico. Simply open a shell and navigate to Pico's install directory within the `httpdocs` directory (e.g. `/var/www/html/pico`) of your server. You can now upgrade Pico using just a single command:
Upgrading Pico 0.8 or 0.9 to Pico 1.0 is a special case. The new `PicoDeprecated` plugin ensures backwards compatibility, so you basically can follow the above upgrade instructions as if we updated the `MINOR` version. However, we recommend you to take some further steps to confine the necessity of `PicoDeprecated` as far as possible. For more information about what has changed with Pico 1.0 and a step-by-step upgrade tutorial, please refer to the [upgrade page of our website][HelpUpgrade].
Run
---
You have nothing to consider specially, simply navigate to your Pico install using your favorite web browser. Pico's default contents will explain how to use your brand new, stupidly simple, blazing fast, flat file CMS.
#### You don't have a web server?
Starting with PHP 5.4 the easiest way to try Pico is using [the built-in web server of PHP][PHPServer]. Please note that PHPs built-in web server is for development and testing purposes only!
###### Step 1
Navigate to Pico's installation directory using a shell.
###### Step 2
Start PHPs built-in web server:
```shell ```shell
$ php -S 127.0.0.1:8080 $ php composer.phar update
``` ```
###### Step 3 That's it! Composer will automatically update Pico and all plugins and themes you've installed using Composer. Please make sure to manually update all plugins and themes you've installed manually.
Access Pico from http://localhost:8080.
### 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!
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".
That's it! Now that Pico is up-to-date, you need to update all plugins and themes you've installed.
### I'm a developer
As a developer you should be up-to-date already... 😉 For the sake of completeness, if you want to upgrade Pico, simply open a shell and navigate to Pico's install directory within the `httpdocs` directory (e.g. `/var/www/html/pico`) of your server. Then pull the latest commits from [Pico's Git repository][PicoGit] and let Composer update your dependencies:
```shell
$ git pull
$ php composer.phar update
```
Getting Help Getting Help
------------ ------------
#### Getting Help as a user #### Getting Help as a user
If you want to get started using Pico, please refer to our [user docs][HelpUserDocs]. Please read the [upgrade notes][HelpUpgrade] if you want to upgrade from Pico 0.8 or 0.9 to Pico 1.0. You can find officially supported [plugins][OfficialPlugins] and [themes][OfficialThemes] on our website. A greater choice of third-party plugins and themes can be found in our [Wiki][] on the [plugins][WikiPlugins] or [themes][WikiThemes] pages respectively. If you want to create your own plugin or theme, please refer to the "Getting Help as a developer" section below.
If you want to get started using Pico, please refer to our [user docs][HelpUserDocs]. Please read the [upgrade notes][HelpUpgrade] if you want to upgrade from Pico 1.0 to Pico 2.0. You can find officially supported [plugins][OfficialPlugins] and [themes][OfficialThemes] on our website. A greater choice of third-party plugins and themes can be found in our [Wiki][] on the [plugins][WikiPlugins] or [themes][WikiThemes] pages respectively. If you want to create your own plugin or theme, please refer to the "Getting Help as a developer" section below.
#### Getting Help as a developer #### Getting Help as a developer
If you're a developer, please refer to the "Contributing" section below and our [contribution guidelines][ContributionGuidelines]. To get you started with creating a plugin or theme, please read the [dev docs on our website][HelpDevDocs].
If you're a developer, please refer to the "Contributing" section below and our [contribution guidelines][ContributionGuidelines]. To get you started with creating a plugin or theme, please read the [developer docs on our website][HelpDevDocs].
#### You still need help or experience a problem with Pico? #### 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 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.
**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*). **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*).
@ -98,15 +124,34 @@ You want to contribute to Pico? We really appreciate that! You can help make Pic
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 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!
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 Pico from https://github.com/picocms/Pico, change the Markdown files and open a [pull request][PullRequests]. 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].
3. Pico's Core: The supreme discipline is to work on Pico's Core. Your contribution should help *every* Pico user to have a better experience with Pico. If this is the case, fork Pico from https://github.com/picocms/Pico and open a [pull request][PullRequests]. We look forward to your contribution! 3. Pico's Core: The supreme discipline is to work on Pico's Core. Your contribution should help *every* Pico user to have a better experience with Pico. If this is the case, fork Pico from https://github.com/picocms/Pico and open a [pull request][PullRequests]. We look forward to your contribution!
[Packagist.org]: http://packagist.org/packages/picocms/pico By contributing to Pico, you accept and agree to the *Developer Certificate of Origin* for your present and future contributions submitted to Pico. Please refer to the ["Developer Certificate of Origin" section in our `CONTRIBUTING.md`][ContributionGuidelinesDCO].
You don't have time to contribute code to Pico, but still want to "stand a coffee" for the ones who do? You can contribute monetary to Pico using [Bountysource][], a crowd funding website that focuses on individual issues and feature requests. Just refer to the "Bounties and Fundraisers" section below for more info.
Bounties and Fundraisers
------------------------
Pico uses [Bountysource][] to allow monetary contributions to the project. Bountysource is a crowd funding website that focuses on individual issues and feature requests in Open Source projects using micropayment. Users, or "Backers", can pledge money for fixing a specific issue, implementing new features, or developing a new plugin or theme. Open source software developers, or "Bounty Hunters", can then pick up and solve these tasks to earn the money.
Obviously this won't allow a developer to replace a full time job, it's rather aiming to "stand a coffee". However, it helps bringing users and developers closer together, and shows developers what users want and how much they care about certain things. Nevertheless you can still donate money to the project itself, as an easy way to say "Thank You" and to support Pico.
If you want to encourage developers to [fix a specific issue][Issues] or implement a feature, simply [pledge a new bounty][Bountysource] or back an existing one.
As a developer you can pick up a bounty by simply contributing to Pico (please refer to the "Contributing" section above). You don't have to be a official Pico Contributor! Pico is a Open Source project, anyone can open [pull requests][PullRequests] and claim bounties.
Official Pico Contributors won't claim bounties on their own behalf, Pico will never take any money out of Bountysource. All money collected by Pico is used to pledge new bounties or to support projects Pico depends on.
[Composer]: https://getcomposer.org/
[LatestRelease]: https://github.com/picocms/Pico/releases/latest [LatestRelease]: https://github.com/picocms/Pico/releases/latest
[composer]: https://getcomposer.org/ [PicoGit]: https://github.com/picocms/Pico
[PicoPackagist]: http://packagist.org/packages/picocms/pico
[PicoComposerPackagist]: http://packagist.org/packages/picocms/pico-composer
[Packagist]: http://packagist.org/
[SemVer]: http://semver.org [SemVer]: http://semver.org
[PHPServer]: http://php.net/manual/en/features.commandline.webserver.php
[HelpUpgrade]: http://picocms.org/in-depth/upgrade/ [HelpUpgrade]: http://picocms.org/in-depth/upgrade/
[HelpUserDocs]: http://picocms.org/docs/ [HelpUserDocs]: http://picocms.org/docs/
[HelpDevDocs]: http://picocms.org/development/ [HelpDevDocs]: http://picocms.org/development/
@ -119,7 +164,10 @@ You want to contribute to Pico? We really appreciate that! You can help make Pic
[Issues]: https://github.com/picocms/Pico/issues [Issues]: https://github.com/picocms/Pico/issues
[IssuesSearch]: https://github.com/picocms/Pico/search?type=Issues [IssuesSearch]: https://github.com/picocms/Pico/search?type=Issues
[PullRequests]: https://github.com/picocms/Pico/pulls [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 [ContributionGuidelines]: https://github.com/picocms/Pico/blob/master/CONTRIBUTING.md
[ContributionGuidelinesDCO]: https://github.com/picocms/Pico/blob/master/CONTRIBUTING.md#developer-certificate-of-origin
[EditInlineDocs]: https://github.com/picocms/Pico/edit/master/content-sample/index.md [EditInlineDocs]: https://github.com/picocms/Pico/edit/master/content-sample/index.md
[EditUserDocs]: https://github.com/picocms/picocms.github.io/tree/master/_docs [EditUserDocs]: https://github.com/picocms/picocms.github.io/tree/master/_docs
[EditDevDocs]: https://github.com/picocms/picocms.github.io/tree/master/_development [EditDevDocs]: https://github.com/picocms/picocms.github.io/tree/master/_development
[Bountysource]: https://www.bountysource.com/teams/picocms

View file

@ -1,42 +0,0 @@
#!/usr/bin/env bash
RELEASE="$1"
ARCHIVE="pico-release.tar.gz"
[ -n "$RELEASE" ] && ARCHIVE="pico-release-$RELEASE.tar.gz"
# install dependencies
echo "Running \`composer install\`..."
composer install --no-dev --optimize-autoloader
[ $? -eq 0 ] || exit 1
echo
# remove .git dirs
echo "Removing '.git' directories of dependencies..."
find vendor/ -type d -path 'vendor/*/*/.git' -print0 | xargs -0 rm -rf
echo
# create release archive
echo "Creating release archive '$ARCHIVE'..."
if [ -e "$ARCHIVE" ]; then
echo "Unable to create archive: File exists" >&2
exit 1
fi
INDEX_BACKUP="$(mktemp -u)"
mv index.php "$INDEX_BACKUP"
mv index.php.dist index.php
tar -czf "$ARCHIVE" \
README.md LICENSE.md CONTRIBUTING.md CHANGELOG.md \
composer.json composer.lock \
config content-sample lib plugins themes vendor \
.htaccess index.php
EXIT=$?
mv index.php index.php.dist
mv "$INDEX_BACKUP" index.php
echo
[ $EXIT -eq 0 ] || exit 1

View file

@ -1,62 +0,0 @@
#!/usr/bin/env bash
if [ "$TRAVIS_PHP_VERSION" != "5.3" ]; then
echo "Skipping branch deployment because this is not on the required runtime"
exit 0
fi
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
echo "Skipping branch deployment because this pull request (#$TRAVIS_PULL_REQUEST) is not permitted to deploy"
exit 0
fi
if [[ ",$DEPLOY_PHPDOC_BRANCHES," != *,"$TRAVIS_BRANCH",* ]]; then
echo "Skipping phpDoc branch deployment because this branch ($TRAVIS_BRANCH) is not permitted to deploy"
exit 0
fi
DEPLOYMENT_ID="${TRAVIS_BRANCH//\//_}"
DEPLOYMENT_DIR="$TRAVIS_BUILD_DIR/_build/deploy-$DEPLOYMENT_ID.git"
[ -n "$DEPLOY_REPO_SLUG" ] || export DEPLOY_REPO_SLUG="$TRAVIS_REPO_SLUG"
[ -n "$DEPLOY_REPO_BRANCH" ] || export DEPLOY_REPO_BRANCH="gh-pages"
# clone repo
github-clone.sh "$DEPLOYMENT_DIR" "https://github.com/$DEPLOY_REPO_SLUG.git" "$DEPLOY_REPO_BRANCH"
[ $? -eq 0 ] || exit 1
cd "$DEPLOYMENT_DIR"
# setup repo
github-setup.sh
[ $? -eq 0 ] || exit 1
# generate phpDocs
generate-phpdoc.sh \
"$TRAVIS_BUILD_DIR/.phpdoc.xml" \
"$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID.cache" "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" \
"Pico 1.0 API Documentation ($TRAVIS_BRANCH branch)"
[ $? -eq 0 ] || exit 1
[ -n "$(git status --porcelain "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID.cache")" ] || exit 0
# update phpDoc list
update-phpdoc-list.sh \
"$DEPLOYMENT_DIR/_data/phpDoc.yml" \
"$TRAVIS_BRANCH" "branch" "<code>$TRAVIS_BRANCH</code> branch" "$(date +%s)"
[ $? -eq 0 ] || exit 1
# commit phpDocs
echo "Committing changes..."
git add \
"$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID.cache" "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" \
"$DEPLOYMENT_DIR/_data/phpDoc.yml"
git commit \
--message="Update phpDocumentor class docs for $TRAVIS_BRANCH branch @ $TRAVIS_COMMIT" \
"$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID.cache" "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" \
"$DEPLOYMENT_DIR/_data/phpDoc.yml"
[ $? -eq 0 ] || exit 1
echo
# deploy phpDocs
github-deploy.sh "$TRAVIS_REPO_SLUG" "heads/$TRAVIS_BRANCH" "$TRAVIS_COMMIT"
[ $? -eq 0 ] || exit 1

View file

@ -1,94 +0,0 @@
#!/usr/bin/env bash
if [ "$DEPLOY_PHPDOC_RELEASES" != "true" ]; then
echo "Skipping phpDoc release deployment because it has been disabled"
fi
if [ "$DEPLOY_VERSION_BADGE" != "true" ]; then
echo "Skipping version badge deployment because it has been disabled"
fi
if [ "$DEPLOY_VERSION_FILE" != "true" ]; then
echo "Skipping version file deployment because it has been disabled"
fi
if [ "$DEPLOY_PHPDOC_RELEASES" != "true" ] || [ "$DEPLOY_VERSION_BADGE" != "true" ] || [ "$DEPLOY_VERSION_FILE" != "true" ]; then
[ "$DEPLOY_PHPDOC_RELEASES" != "true" ] && [ "$DEPLOY_VERSION_BADGE" != "true" ] && [ "$DEPLOY_VERSION_FILE" != "true" ] && exit 0 || echo
fi
DEPLOYMENT_ID="${TRAVIS_BRANCH//\//_}"
DEPLOYMENT_DIR="$TRAVIS_BUILD_DIR/_build/deploy-$DEPLOYMENT_ID.git"
[ -n "$DEPLOY_REPO_SLUG" ] || export DEPLOY_REPO_SLUG="$TRAVIS_REPO_SLUG"
[ -n "$DEPLOY_REPO_BRANCH" ] || export DEPLOY_REPO_BRANCH="gh-pages"
# clone repo
github-clone.sh "$DEPLOYMENT_DIR" "https://github.com/$DEPLOY_REPO_SLUG.git" "$DEPLOY_REPO_BRANCH"
[ $? -eq 0 ] || exit 1
cd "$DEPLOYMENT_DIR"
# setup repo
github-setup.sh
[ $? -eq 0 ] || exit 1
# generate phpDocs
if [ "$DEPLOY_PHPDOC_RELEASES" == "true" ]; then
generate-phpdoc.sh \
"$TRAVIS_BUILD_DIR/.phpdoc.xml" \
"-" "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" \
"Pico 1.0 API Documentation ($TRAVIS_TAG)"
[ $? -eq 0 ] || exit 1
if [ -n "$(git status --porcelain "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID")" ]; then
# update phpDoc list
update-phpdoc-list.sh \
"$DEPLOYMENT_DIR/_data/phpDoc.yml" \
"$TRAVIS_TAG" "version" "Pico ${TRAVIS_TAG#v}" "$(date +%s)"
[ $? -eq 0 ] || exit 1
# commit phpDocs
echo "Committing phpDoc changes..."
git add "$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" "$DEPLOYMENT_DIR/_data/phpDoc.yml"
git commit \
--message="Update phpDocumentor class docs for $TRAVIS_TAG" \
"$DEPLOYMENT_DIR/phpDoc/$DEPLOYMENT_ID" "$DEPLOYMENT_DIR/_data/phpDoc.yml"
[ $? -eq 0 ] || exit 1
echo
fi
fi
# update version badge
if [ "$DEPLOY_VERSION_BADGE" == "true" ]; then
generate-badge.sh \
"$DEPLOYMENT_DIR/badges/pico-version.svg" \
"release" "$TRAVIS_TAG" "blue"
[ $? -eq 0 ] || exit 1
# commit version badge
echo "Committing version badge..."
git add "$DEPLOYMENT_DIR/badges/pico-version.svg"
git commit \
--message="Update version badge for $TRAVIS_TAG" \
"$DEPLOYMENT_DIR/badges/pico-version.svg"
[ $? -eq 0 ] || exit 1
echo
fi
# update version file
if [ "$DEPLOY_VERSION_FILE" == "true" ]; then
update-version-file.sh \
"$DEPLOYMENT_DIR/_data/version.yml" \
"${TRAVIS_TAG#v}"
[ $? -eq 0 ] || exit 1
# commit version file
echo "Committing version file..."
git add "$DEPLOYMENT_DIR/_data/version.yml"
git commit \
--message="Update version file for $TRAVIS_TAG" \
"$DEPLOYMENT_DIR/_data/version.yml"
[ $? -eq 0 ] || exit 1
echo
fi
# deploy
github-deploy.sh "$TRAVIS_REPO_SLUG" "tags/$TRAVIS_TAG" "$TRAVIS_COMMIT"
[ $? -eq 0 ] || exit 1

View file

@ -1,55 +0,0 @@
#!/usr/bin/env bash
##
# Downloads a custom badge from shields.io
#
# All credit goes to the awesome guys at shields.io!
#
# @see http://shields.io/
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
BADGE_FILE_PATH="$1" # target file path
BADGE_SUBJECT="$2" # subject (left half) of the badge
BADGE_STATUS="$3" # status (right half) of the badge
BADGE_COLOR="$4" # color of the badge
# print parameters
echo "Generating badge..."
printf 'BADGE_FILE_PATH="%s"\n' "$BADGE_FILE_PATH"
printf 'BADGE_SUBJECT="%s"\n' "$BADGE_SUBJECT"
printf 'BADGE_STATUS="%s"\n' "$BADGE_STATUS"
printf 'BADGE_COLOR="%s"\n' "$BADGE_COLOR"
echo
# download badge from shields.io
printf 'Downloading badge...\n'
TMP_BADGE="$(mktemp -u)"
curl --location --output "$TMP_BADGE" \
"https://img.shields.io/badge/$BADGE_SUBJECT-$BADGE_STATUS-$BADGE_COLOR.svg"
# validate badge
if [ ! -f "$TMP_BADGE" ] || [ ! -s "$TMP_BADGE" ]; then
echo "Unable to generate badge; skipping..." >&2
exit 1
fi
# MIME type image/svg+xml isn't supported at the moment
#
#TMP_BADGE_MIME="$(file --mime-type "$TMP_BADGE" | cut -d ' ' -f 2)"
#if [ "$TMP_BADGE_MIME" != "image/svg+xml" ]; then
# echo "Generated badge should be of type 'image/svg+xml', '$TMP_BADGE_MIME' given; aborting...\n" >&2
# exit 1
#fi
# deploy badge
mv "$TMP_BADGE" "$BADGE_FILE_PATH"
echo

View file

@ -1,57 +0,0 @@
#!/usr/bin/env bash
##
# Generates phpDoc class documentation
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
PHPDOC_CONFIG="$1" # phpDoc config file
PHPDOC_CACHE_DIR="$2" # phpDoc cache dir
PHPDOC_TARGET_DIR="$3" # phpDoc output dir
PHPDOC_TITLE="$4" # API docs title
# print parameters
echo "Generating phpDocs..."
printf 'PHPDOC_CONFIG="%s"\n' "$PHPDOC_CONFIG"
printf 'PHPDOC_CACHE_DIR="%s"\n' "$PHPDOC_CACHE_DIR"
printf 'PHPDOC_TARGET_DIR="%s"\n' "$PHPDOC_TARGET_DIR"
printf 'PHPDOC_TITLE="%s"\n' "$PHPDOC_TITLE"
echo
# update a separate phpDoc cache
if [ "$PHPDOC_CACHE_DIR" != "-" ]; then
# parse phpDoc files (i.e. update cache)
printf "Update phpDoc cache...\n"
phpdoc project:parse --config "$PHPDOC_CONFIG" \
--target "$PHPDOC_CACHE_DIR"
# check for changes
printf '\nCheck for phpDoc cache changes...\n'
if [ -z "$(git status --porcelain "$PHPDOC_CACHE_DIR")" ]; then
printf 'No changes detected; skipping phpDocs renewal...\n\n'
exit 0
fi
# NOTE: actually the following command should be `phpdoc project:transform`
# instead of `phpdoc project:run`, but the command seems to be broken...
echo
else
# create temporary cache files in PHPDOC_TARGET_DIR
PHPDOC_CACHE_DIR="$PHPDOC_TARGET_DIR"
fi
# transform phpDoc files (i.e. rewrite API docs)
printf 'Rewrite phpDocs...\n'
rm -rf "$PHPDOC_TARGET_DIR"
phpdoc project:run --config "$PHPDOC_CONFIG" \
--cache-folder "$PHPDOC_CACHE_DIR" \
--target "$PHPDOC_TARGET_DIR" \
--title "$PHPDOC_TITLE"
echo

View file

@ -1,29 +0,0 @@
#!/usr/bin/env bash
##
# Clones a Git repo
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
CLONE_TARGET_DIR="$1" # target directory
CLONE_REPO_URL="$2" # URL of the git repo to clone
CLONE_REPO_BRANCH="$3" # optional branch to checkout
# print parameters
echo "Cloning repo..."
printf 'CLONE_TARGET_DIR="%s"\n' "$CLONE_TARGET_DIR"
printf 'CLONE_REPO_URL="%s"\n' "$CLONE_REPO_URL"
printf 'CLONE_REPO_BRANCH="%s"\n' "$CLONE_REPO_BRANCH"
echo
# clone repo
[ -n "$CLONE_REPO_BRANCH" ] || CLONE_REPO_BRANCH="master"
git clone --branch="$CLONE_REPO_BRANCH" "$CLONE_REPO_URL" "$CLONE_TARGET_DIR"
echo

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
##
# Pushes commits to a GitHub repo
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
CHECK_REPO_SLUG="$1" # optional GitHub repo (e.g. picocms/Pico) to check
# its latest commit as basic race condition protection
CHECK_REMOTE_REF="$2" # optional remote Git reference (e.g. heads/master)
CHECK_LOCAL_COMMIT="$3" # optional local commit SHA1
# print parameters
echo "Deploying repo..."
printf 'CHECK_REPO_SLUG="%s"\n' "$CHECK_REPO_SLUG"
printf 'CHECK_REMOTE_REF="%s"\n' "$CHECK_REMOTE_REF"
printf 'CHECK_LOCAL_COMMIT="%s"\n' "$CHECK_LOCAL_COMMIT"
echo
# check for changes
if [ -z "$(git log --oneline '@{upstream}..')" ]; then
printf 'Nothing to deploy; skipping...\n\n'
exit 0
fi
# race condition protection for concurrent Travis builds
# this is no definite protection (race conditions are still possible during `git push`),
# but it should give a basic protection without disabling concurrent builds completely
if [ -n "$CHECK_REPO_SLUG" ] && [ -n "$CHECK_REMOTE_REF" ] && [ -n "$CHECK_LOCAL_COMMIT" ]; then
# retrieve information using GitHub APIv3
printf 'Checking latest commit...\n'
CHECK_API_URL="https://api.github.com/repos/$CHECK_REPO_SLUG/git/refs/$CHECK_REMOTE_REF"
if [ -n "$GITHUB_OAUTH_TOKEN" ]; then
CHECK_API_RESPONSE="$(wget -O- --header="Authorization: token $GITHUB_OAUTH_TOKEN" "$CHECK_API_URL" 2> /dev/null)"
else
CHECK_API_RESPONSE="$(wget -O- "$CHECK_API_URL" 2> /dev/null)"
fi
# evaluate JSON response
CHECK_REMOTE_COMMIT="$(echo "$CHECK_API_RESPONSE" | php -r "
\$json = json_decode(stream_get_contents(STDIN), true);
if (\$json !== null) {
if (isset(\$json['ref']) && (\$json['ref'] === 'refs/$CHECK_REMOTE_REF')) {
if (isset(\$json['object']) && isset(\$json['object']['sha'])) {
echo \$json['object']['sha'];
}
}
}
")"
# compare source reference against the latest commit
if [ "$CHECK_REMOTE_COMMIT" != "$CHECK_LOCAL_COMMIT" ]; then
echo "Latest local commit '$CHECK_LOCAL_COMMIT' doesn't match latest remote commit '$CHECK_REMOTE_COMMIT'; aborting..." >&2
exit 1
fi
echo
fi
# push changes
printf 'Pushing changes...\n'
git push
echo

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
##
# Prepares a GitHub repo for deployment
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# environment variables
# GITHUB_OAUTH_TOKEN GitHub authentication token, see https://github.com/settings/tokens
# print "parameters" (we don't have any)
echo "Setup repo..."
echo
# check for git repo
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Not a git repo; aborting..." >&2
exit 1
fi
# setup git
printf 'Preparing repo...\n'
git config push.default simple
git config user.name "Travis CI"
git config user.email "travis-ci@picocms.org"
if [ -n "$GITHUB_OAUTH_TOKEN" ]; then
git config credential.helper 'store --file=.git/credentials'
(umask 077 && echo "https://GitHub:$GITHUB_OAUTH_TOKEN@github.com" > .git/credentials)
fi
echo

View file

@ -1,78 +0,0 @@
#!/usr/bin/env bash
##
# Updates the phpDoc list
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
LIST_FILE_PATH="$1" # target file path
LIST_ID="$2" # phpDoc ID
LIST_TYPE="$3" # phpDoc type
LIST_TITLE="$4" # phpDoc title
LIST_LAST_UPDATE="$5" # phpDoc last update
# print parameters
echo "Updating phpDoc list..."
printf 'LIST_FILE_PATH="%s"\n' "$LIST_FILE_PATH"
printf 'LIST_ID="%s"\n' "$LIST_ID"
printf 'LIST_TYPE="%s"\n' "$LIST_TYPE"
printf 'LIST_TITLE="%s"\n' "$LIST_TITLE"
printf 'LIST_LAST_UPDATE="%s"\n' "$LIST_LAST_UPDATE"
echo
# create temporary file
printf 'Creating temporary file...\n'
LIST_TMP_FILE="$(mktemp)"
[ -n "$LIST_TMP_FILE" ] || exit 1
exec 3> "$LIST_TMP_FILE"
# walk through phpDoc list
printf 'Walking through phpDoc list...\n'
DO_REPLACE="no"
DID_REPLACE="no"
while IFS='' read -r LINE || [[ -n "$LINE" ]]; do
if [ "$DO_REPLACE" == "yes" ]; then
# skip lines until next entry is reached
[ "${LINE:0:2}" == " " ] && continue
DO_REPLACE="no"
elif [ "$LINE" == "- id: $LIST_ID" ]; then
# update existing entry
printf 'Updating existing entry...\n'
printf -- '- id: %s\n' "$LIST_ID" >&3
printf -- ' type: %s\n' "$LIST_TYPE" >&3
printf -- ' title: %s\n' "$LIST_TITLE" >&3
printf -- ' last_update: %s\n' "$LIST_LAST_UPDATE" >&3
DO_REPLACE="yes"
DID_REPLACE="yes"
continue
fi
echo "$LINE" >&3
done < "$LIST_FILE_PATH"
# add new entry
if [ "$DID_REPLACE" == "no" ]; then
printf 'Adding new entry...\n'
printf -- '- id: %s\n' "$LIST_ID" >&3
printf -- ' type: %s\n' "$LIST_TYPE" >&3
printf -- ' title: %s\n' "$LIST_TITLE" >&3
printf -- ' last_update: %s\n' "$LIST_LAST_UPDATE" >&3
fi
exec 3>&-
# move temporary file
printf 'Replacing phpDoc list...\n'
mv "$LIST_TMP_FILE" "$LIST_FILE_PATH"
echo

View file

@ -1,57 +0,0 @@
#!/usr/bin/env bash
##
# Updates the version file
#
# @author Daniel Rudolf
# @link http://picocms.org
# @license http://opensource.org/licenses/MIT
#
set -e
# parameters
VERSION_FILE_PATH="$1" # target file path
VERSION_FULL="$2" # full version string (e.g. 1.0.0-beta.1+7b4ad7f)
# print parameters
echo "Generating version file..."
printf 'VERSION_FILE_PATH="%s"\n' "$VERSION_FILE_PATH"
printf 'VERSION_FULL="%s"\n' "$VERSION_FULL"
echo
# evaluate version constraint (see http://semver.org/)
printf 'Evaluating version constraint...\n'
if [[ "$VERSION_FULL" =~ ^([0-9]+)\.([0-9]{1,2})\.([0-9]{1,2})(-([0-9A-Za-z\.\-]+))?(\+([0-9A-Za-z\.\-]+))?$ ]]; then
VERSION_MAJOR="${BASH_REMATCH[1]}"
VERSION_MINOR="${BASH_REMATCH[2]}"
VERSION_PATCH="${BASH_REMATCH[3]}"
VERSION_SUFFIX="${BASH_REMATCH[5]}"
VERSION_BUILD="${BASH_REMATCH[7]}"
VERSION_MILESTONE="$VERSION_MAJOR.$VERSION_MINOR"
VERSION_NAME="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH"
VERSION_ID="$VERSION_MAJOR$(printf '%02d' "$VERSION_MINOR")$(printf '%02d' "$VERSION_PATCH")"
else
echo "Invalid version constraint; skipping..." >&2
exit 1
fi
# generate version file
printf 'Updating version file...\n'
echo -n "" > "$VERSION_FILE_PATH"
exec 3> "$VERSION_FILE_PATH"
printf 'full: %s\n' "$VERSION_FULL" >&3
printf 'name: %s\n' "$VERSION_NAME" >&3
printf 'milestone: %s\n' "$VERSION_MILESTONE" >&3
printf 'id: %d\n' "$VERSION_ID" >&3
printf 'major: %d\n' "$VERSION_MAJOR" >&3
printf 'minor: %d\n' "$VERSION_MINOR" >&3
printf 'patch: %d\n' "$VERSION_PATCH" >&3
printf 'suffix: %s\n' "$VERSION_SUFFIX" >&3
printf 'build: %s\n' "$VERSION_BUILD" >&3
exec 3>&-
echo

2
assets/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# This directory is meant to be empty
*

View file

@ -2,7 +2,7 @@
"name": "picocms/pico", "name": "picocms/pico",
"type": "library", "type": "library",
"description": "Pico is a flat file CMS, this means there is no administration backend and database to deal with. You simply create .md files in the \"content\" folder and that becomes a page.", "description": "Pico is a flat file CMS, this means there is no administration backend and database to deal with. You simply create .md files in the \"content\" folder and that becomes a page.",
"keywords": ["flat-file","cms","php","twig","markdown"], "keywords": ["pico", "picocms", "pico-cms", "simple", "flat-file", "cms", "content-management", "website", "markdown-to-html", "php", "markdown", "yaml", "twig" ],
"homepage": "http://picocms.org/", "homepage": "http://picocms.org/",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
@ -13,8 +13,11 @@
}, },
{ {
"name": "The Pico Community", "name": "The Pico Community",
"homepage": "https://github.com/picocms/Pico/graphs/contributors", "homepage": "http://picocms.org/"
"role": "Contributors" },
{
"name": "Contributors",
"homepage": "https://github.com/picocms/Pico/graphs/contributors"
} }
], ],
"support": { "support": {
@ -25,10 +28,15 @@
"require": { "require": {
"php": ">=5.3.6", "php": ">=5.3.6",
"ext-dom": "*", "ext-dom": "*",
"ext-mbstring": "*", "twig/twig": "^1.35",
"twig/twig": "^1.18", "symfony/yaml" : "^2.8",
"erusev/parsedown-extra": "^0.7", "erusev/parsedown": "^1.7",
"symfony/yaml" : "^2.3" "erusev/parsedown-extra": "^0.7"
},
"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" : { "require-dev" : {
"phpdocumentor/phpdocumentor": "^2.8", "phpdocumentor/phpdocumentor": "^2.8",
@ -40,5 +48,10 @@
"PicoPluginInterface": "lib/", "PicoPluginInterface": "lib/",
"AbstractPicoPlugin": "lib/" "AbstractPicoPlugin": "lib/"
} }
},
"extra": {
"branch-alias": {
"dev-pico-1.1": "2.0-dev"
}
} }
} }

3
config/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# This directory is meant to be empty, except for config.yml.template
*
!config.yml.template

View file

@ -1,58 +0,0 @@
<?php
/**
* Pico configuration
*
* This is the configuration file for {@link Pico}. It comes loaded with the
* default values, which can be found in {@link Pico::getConfig()} (see
* {@path "lib/Pico.php"}).
*
* To override any of the default settings below, copy this file to
* {@path "config/config.php"}, uncomment the line, then make and
* save your changes.
*
* @author Gilbert Pellegrom
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0
*/
/*
* BASIC
*/
// $config['site_title'] = 'Pico'; // Site title
// $config['base_url'] = ''; // Override base URL (e.g. http://example.com)
// $config['rewrite_url'] = null; // A boolean indicating forced URL rewriting
/*
* THEME
*/
// $config['theme'] = 'default'; // Set the theme (defaults to "default")
// $config['twig_config'] = array( // Twig settings
// 'cache' => false, // To enable Twig caching change this to a path to a writable directory
// 'autoescape' => false, // Auto-escape Twig vars
// 'debug' => false // Enable Twig debug
// );
/*
* CONTENT
*/
// $config['date_format'] = '%D %T'; // Set the PHP date format as described here: http://php.net/manual/en/function.strftime.php
// $config['pages_order_by'] = 'alpha'; // Order pages by "alpha" or "date"
// $config['pages_order'] = 'asc'; // Order pages "asc" or "desc"
// $config['content_dir'] = 'content-sample/'; // Content directory
// $config['content_ext'] = '.md'; // File extension of content files to serve
/*
* TIMEZONE
*/
// $config['timezone'] = 'UTC'; // Timezone may be required by your php install
/*
* PLUGINS
*/
// $config['DummyPlugin.enabled'] = false; // Force DummyPlugin to be disabled
/*
* CUSTOM
*/
// $config['custom_setting'] = 'Hello'; // Can be accessed by {{ config.custom_setting }} in a theme

View file

@ -0,0 +1,50 @@
##
# 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/
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
##
# 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:
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
##
# Content
#
date_format: %D %T # Pico's default date format
# See http://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_ext: .md # The file extension of your Markdown files
content_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
##
# Plugins
#
DummyPlugin.enabled: false # Force the plugin "DummyPlugin" to be disabled
##
# Custom
#
my_custom_setting: Hello World! # You can access custom settings in themes using {{ config.my_custom_setting }}

12
content-sample/_meta.md Normal file
View file

@ -0,0 +1,12 @@
---
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
icon: chat
- title: Help us by creating/collecting bounties and pledging to fundraisers
url: https://www.bountysource.com/teams/picocms
icon: dollar
---

View file

@ -1,19 +1,12 @@
--- ---
Title: Welcome Title: Welcome
Description: Pico is a stupidly simple, blazing fast, flat file CMS. Description: Pico is a stupidly simple, blazing fast, flat file CMS.
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
icon: chat
--- ---
## Welcome to Pico ## Welcome to Pico
Congratulations, you have successfully installed [Pico](http://picocms.org/). Congratulations, you have successfully installed [Pico](http://picocms.org/)
%meta.description% <!-- replaced by the above Description meta header --> %version%. %meta.description% <!-- replaced by the above Description header -->
## Creating Content ## Creating Content
@ -24,17 +17,16 @@ and is shown as the main landing page.
When you install Pico, it comes with a `content-sample` folder. Inside this When you install Pico, it comes with a `content-sample` folder. Inside this
folder is a sample website that will display until you add your own content. folder is a sample website that will display until you add your own content.
You should create your own `content` folder in Pico's root directory and place Simply add some `.md` files to your `content` folder in Pico's root directory.
your files there. No configuration is required, Pico will automatically use the No configuration is required, Pico will automatically use the `content` folder
`content` folder if it exists. as soon as you create your own `index.md`.
If you create a folder within the content folder (e.g. `content/sub`) and put If you create a folder within the content directory (e.g. `content/sub`) and
an `index.md` inside it, you can access that folder at the URL put an `index.md` inside it, you can access that folder at the URL
`http://example.com/?sub`. If you want another page within the sub folder, `%base_url%?sub`. If you want another page within the sub folder, simply create
simply create a text file with the corresponding name and you will be able to a text file with the corresponding name and you will be able to access it
access it (e.g. `content/sub/page.md` is accessible from the URL (e.g. `content/sub/page.md` is accessible from the URL `%base_url%?sub/page`).
`http://example.com/?sub/page`). Below we've shown some examples of locations Below we've shown some examples of locations and their corresponding URLs:
and their corresponding URLs:
<table style="width: 100%; max-width: 40em;"> <table style="width: 100%; max-width: 40em;">
<thead> <thead>
@ -78,7 +70,7 @@ 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 (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 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 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 your assets there. You can then access them in your Markdown using
<code>&#37;base_url&#37;/assets/</code> for example: <code>&#37;base_url&#37;/assets/</code> for example:
<code>!\[Image Title\](&#37;base_url&#37;/assets/image.png)</code> <code>!\[Image Title\](&#37;base_url&#37;/assets/image.png)</code>
@ -94,13 +86,18 @@ attributes of the page using [YAML][] (the "YAML header"). For example:
Title: Welcome Title: Welcome
Description: This description will go in the meta description tag Description: This description will go in the meta description tag
Author: Joe Bloggs Author: Joe Bloggs
Date: 2013/01/01 Date: 2001-04-25
Robots: noindex,nofollow Robots: noindex,nofollow
Template: index Template: index
--- ---
These values will be contained in the `{{ meta }}` variable in themes These values will be contained in the `{{ meta }}` variable in themes (see
(see below). below). Meta headers sometimes have a special meaning: For instance, Pico not
only passes through the `Date` meta header, but rather evaluates it to really
"understand" when this page was created. This comes into play when you want to
sort your pages not just alphabetically, but by date. Another example is the
`Template` meta header: It controls what Twig template Pico uses to display
this page (e.g. if you add `Template: blog`, Pico uses `blog.twig`).
There are also certain variables that you can use in your text files: There are also certain variables that you can use in your text files:
@ -120,6 +117,7 @@ below Plugins section for details.
If you want to use Pico as a blogging software, you probably want to do If you want to use Pico as a blogging software, you probably want to do
something like the following: something like the following:
1. Put all your blog articles in a separate `blog` folder in your `content` 1. Put all your blog articles in a separate `blog` folder in your `content`
directory. All these articles should have both a `Date` and `Template` meta directory. All these articles should have both a `Date` and `Template` meta
header, the latter with e.g. `blog-post` as value (see Step 2). header, the latter with e.g. `blog-post` as value (see Step 2).
@ -133,7 +131,7 @@ something like the following:
do something like this: do something like this:
``` ```
{% for page in pages|sort_by("time")|reverse %} {% for page in pages|sort_by("time")|reverse %}
{% if page.id starts with "blog/" %} {% if page.id starts with "blog/" and not page.hidden %}
<div class="post"> <div class="post">
<h3><a href="{{ page.url }}">{{ page.title }}</a></h3> <h3><a href="{{ page.url }}">{{ page.title }}</a></h3>
<p class="date">{{ page.date_formatted }}</p> <p class="date">{{ page.date_formatted }}</p>
@ -163,26 +161,25 @@ details.
You can create themes for your Pico installation in the `themes` folder. Check 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 out the default theme for an example. Pico uses [Twig][] for template
rendering. You can select your theme by setting the `$config['theme']` option rendering. You can select your theme by setting the `theme` option in
in `config/config.php` to the name of your theme folder. `config/config.yml` to the name of your theme folder.
All themes must include an `index.twig` (or `index.html`) file to define the All themes must include an `index.twig` file to define the HTML structure of
HTML structure of the theme. Below are the Twig variables that are available the theme. Below are the Twig variables that are available to use in your
to use in your theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs
(e.g. `{{ base_url }}`) don't have a trailing slash. (e.g. `{{ base_url }}`) don't have a trailing slash.
* `{{ config }}` - Contains the values you set in `config/config.php` * `{{ config }}` - Contains the values you set in `config/config.yml`
(e.g. `{{ config.theme }}` becomes `default`) (e.g. `{{ config.theme }}` becomes `default`)
* `{{ base_dir }}` - The path to your Pico root directory * `{{ base_dir }}` - The path to your Pico root directory
* `{{ base_url }}` - The URL to your Pico site; use Twigs `link` filter to * `{{ base_url }}` - The URL to your Pico site; use Twig's `link` filter to
specify internal links (e.g. `{{ "sub/page"|link }}`), specify internal links (e.g. `{{ "sub/page"|link }}`),
this guarantees that your link works whether URL rewriting this guarantees that your link works whether URL rewriting
is enabled or not is enabled or not
* `{{ theme_dir }}` - The path to the currently active theme * `{{ theme_dir }}` - The path to the currently active theme
* `{{ theme_url }}` - The URL to the currently active theme * `{{ theme_url }}` - The URL to the currently active theme
* `{{ rewrite_url }}` - A boolean flag indicating enabled/disabled URL rewriting * `{{ site_title }}` - Shortcut to the site title (see `config/config.yml`)
* `{{ site_title }}` - Shortcut to the site title (see `config/config.php`) * `{{ meta }}` - Contains the meta values of the current page
* `{{ meta }}` - Contains the meta values from the current page
* `{{ meta.title }}` * `{{ meta.title }}`
* `{{ meta.description }}` * `{{ meta.description }}`
* `{{ meta.author }}` * `{{ meta.author }}`
@ -191,55 +188,66 @@ to use in your theme. Please note that paths (e.g. `{{ base_dir }}`) and URLs
* `{{ meta.time }}` * `{{ meta.time }}`
* `{{ meta.robots }}` * `{{ meta.robots }}`
* ... * ...
* `{{ content }}` - The content of the current page * `{{ content }}` - The content of the current page after it has been processed
(after it has been processed through Markdown) through Markdown
* `{{ pages }}` - A collection of all the content pages in your site * `{{ pages }}` - A collection of all the content pages in your site
* `{{ page.id }}` - The relative path to the content file (unique ID) * `{{ page.id }}` - The relative path to the content file (unique ID)
* `{{ page.url }}` - The URL to the page * `{{ page.url }}` - The URL to the page
* `{{ page.title }}` - The title of the page (YAML header) * `{{ page.title }}` - The title of the page (YAML header)
* `{{ page.description }}` - The description of the page (YAML header) * `{{ page.description }}` - The description of the page (YAML header)
* `{{ page.author }}` - The author of the page (YAML header) * `{{ page.author }}` - The author of the page (YAML header)
* `{{ page.time }}` - The timestamp derived from the `Date` header * `{{ page.time }}` - The [Unix timestamp][UnixTimestamp] derived from
the `Date` header
* `{{ page.date }}` - The date of the page (YAML header) * `{{ page.date }}` - The date of the page (YAML header)
* `{{ page.date_formatted }}` - The formatted date of the page * `{{ 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; * `{{ page.raw_content }}` - The raw, not yet parsed contents of the page;
use Twigs `content` filter to get the parsed use Twig's `content` filter to get the parsed
contents of a page by passing its unique ID contents of a page by passing its unique ID
(e.g. `{{ "sub/page"|content }}`) (e.g. `{{ "sub/page"|content }}`)
* `{{ page.meta }}`- The meta values of the page * `{{ page.meta }}`- The meta values of the page
* `{{ prev_page }}` - The data of the previous page (relative to `current_page`) * `{{ prev_page }}` - The data of the previous page (relative to `current_page`)
* `{{ current_page }}` - The data of the current page * `{{ current_page }}` - The data of the current page
* `{{ next_page }}` - The data of the next page (relative to `current_page`) * `{{ next_page }}` - The data of the next page (relative to `current_page`)
* `{{ is_front_page }}` - A boolean flag for the front page
Pages can be used like the following: Pages can be used like the following:
<ul class="nav"> <ul class="nav">
{% for page in pages %} {% for page in pages if not page.hidden %}
<li><a href="{{ page.url }}">{{ page.title }}</a></li> <li><a href="{{ page.url }}">{{ page.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
Additional to Twigs extensive list of filters, functions and tags, Pico also Additional to Twigs extensive list of filters, functions and tags, Pico also
provides some useful additional filters to make theming easier. You can parse provides some useful additional filters to make theming easier.
any Markdown string to HTML using the `markdown` filter. Arrays can be sorted
by one of its keys or a arbitrary deep sub-key using the `sort_by` filter * Pass the unique ID of a page to the `link` filter to return the page's URL
(e.g. `{% for page in pages|sort_by([ 'meta', 'nav' ]) %}...{% endfor %}` (e.g. `{{ "sub/page"|link }}` gets `%base_url%?sub/page`).
iterates through all pages, ordered by the `nav` meta header; please note the * To get the parsed contents of a page, pass its unique ID to the `content`
`[ 'meta', 'nav' ]` part of the example, it instructs Pico to sort by filter (e.g. `{{ "sub/page"|content }}`).
`page.meta.nav`). You can return all values of a given key or key path of an * You can parse any Markdown string using the `markdown` filter (e.g. you can
array using the `map` filter (e.g. `{{ pages|map("title") }}` returns all use Markdown in the `description` meta variable and later parse it in your
page titles). theme using `{{ meta.description|markdown }}`).
* 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
`[ 'meta', 'nav' ]` part of the example, it instructs Pico to sort by
`page.meta.nav`).
* You can return all values of a given array key using the `map` filter
(e.g. `{{ pages|map("title") }}` returns all page titles).
You can use different templates for different content files by specifying the You can use different templates for different content files by specifying the
`Template` meta header. Simply add e.g. `Template: blog-post` to a content file `Template` meta header. Simply add e.g. `Template: blog` to the YAML header of
and Pico will use the `blog-post.twig` file in your theme folder to render a content file and Pico will use the `blog.twig` template in your theme folder
the page. to display the page.
You don't have to create your own theme if Pico's default theme isn't Pico's default theme isn't really intended to be used for a productive website,
sufficient for you, you can use one of the great themes third-party developers it's rather a starting point for creating your own theme. If the default theme
and designers created in the past. As with plugins, you can find themes in isn't sufficient for you, and you don't want to create your own theme, you can
[our Wiki][WikiThemes] and on [our website][OfficialThemes]. 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
@ -255,61 +263,113 @@ Depending on the plugin you've installed, you may have to go through some more
steps (e.g. specifying config variables), the plugin docs or `README` file will steps (e.g. specifying config variables), the plugin docs or `README` file will
explain what to do. explain what to do.
Plugins which were written to work with Pico 1.0 can be enabled and disabled Plugins which were written to work with Pico 1.0 and later can be enabled and
through your `config/config.php`. If you want to e.g. disable the `PicoExcerpt` disabled through your `config/config.yml`. If you want to e.g. disable the
plugin, add the following line to your `config/config.php`: `PicoDeprecated` plugin, add the following line to your `config/config.yml`:
`$config['PicoExcerpt.enabled'] = false;`. To force the plugin to be enabled `PicoDeprecated.enabled: false`. To force the plugin to be enabled, replace
replace `false` with `true`. `false` by `true`.
#### Plugins for developers #### Plugins for developers
You're a plugin developer? We love you guys! You can find tons of information You're a plugin developer? We love you guys! You can find tons of information
about how to develop plugins at http://picocms.org/development/. If you've about how to develop plugins at http://picocms.org/development/. If you've
developed a plugin for Pico 0.9 or older, you probably want to upgrade it developed a plugin before and want to upgrade it to Pico 2.0, refer to the
to the brand new plugin system introduced with Pico 1.0. Please refer to the
[upgrade section of the docs][PluginUpgrade]. [upgrade section of the docs][PluginUpgrade].
## Config ## Config
You can override the default Pico settings (and add your own custom settings) Configuring Pico really is stupidly simple: Just create a `config/config.yml`
by editing `config/config.php` in the Pico directory. For a brief overview of to override the default Pico settings (and add your own custom settings). Take
the available settings and their defaults see `config/config.php.template`. To a look at the `config/config.yml.template` for a brief overview of the
override a setting, copy `config/config.php.template` to `config/config.php`, available settings and their defaults. To override a setting, simply copy the
uncomment the setting and set your custom value. line from `config/config.yml.template` to `config/config.yml` and set your
custom value.
But we didn't stop there. Rather than having just a single config file, you can
use a arbitrary number of config files. Simply create a `.yml` file in Pico's
`config` dir and you're good to go. This allows you to add some structure to
your config, like a separate config file for your theme (`config/my_theme.yml`).
Please note that Pico loads config files in a special way you should be aware
of. First of all it loads the main config file `config/config.yml`, and then
any other `*.yml` file in Pico's `config` dir in alphabetical order. The file
order is crucial: Configiguration values which have been set already, cannot be
overwritten by a succeeding file. For example, if you set `site_title: Pico` in
`config/a.yml` and `site_title: My awesome site!` in `config/b.yml`, your site
title will be "Pico".
Since YAML files are plain text files, users might read your Pico config by
navigating to `%base_url%/config/config.yml`. This is no problem in the first
place, but might get a problem if you use plugins that require you to store
security-relevant data in the config (like credentials). Thus you should
*always* make sure to configure your webserver to deny access to Pico's
`config` dir. Just refer to the "URL Rewriting" section below. By following the
instructions, you will not just enable URL rewriting, but also deny access to
Pico's `config` dir.
### URL Rewriting ### URL Rewriting
Pico's default URLs (e.g. %base_url%/?sub/page) already are very user-friendly. Pico's default URLs (e.g. %base_url%/?sub/page) already are very user-friendly.
Additionally, Pico offers you a URL rewrite feature to make URLs even more Additionally, Pico offers you a URL rewrite feature to make URLs even more
user-friendly (e.g. %base_url%/sub/page). user-friendly (e.g. %base_url%/sub/page). Below you'll find some basic info
about how to configure your webserver proberly to enable URL rewriting.
#### Apache
If you're using the Apache web server, URL rewriting probably already is If you're using the Apache web server, URL rewriting probably already is
enabled - try it yourself, click on the [second URL](%base_url%/sub/page). If enabled - try it yourself, click on the [second URL](%base_url%/sub/page). If
you get an error message from your web server, please make sure to enable the URL rewriting doesn't work (you're getting `404 Not Found` error messages from
[`mod_rewrite` module][ModRewrite]. Assuming the second URL works, but Pico Apache), please make sure to enable the [`mod_rewrite` module][ModRewrite] and
still shows no rewritten URLs, force URL rewriting by setting to enable `.htaccess` overrides. You might have to set the
`$config['rewrite_url'] = true;` in your `config/config.php`. [`AllowOverride` directive][AllowOverride] to `AllowOverride All` in your
virtual host config file or global `httpd.conf`/`apache.conf`. Assuming
rewritten URLs work, but Pico still shows no rewritten URLs, force URL
rewriting by setting `rewrite_url: true` in your `config/config.yml`. If you
rather get a `500 Internal Server Error` no matter what you do, try removing
the `Options` directive from Pico's `.htaccess` file (it's the last line).
If you're using Nginx, you can use the following configuration to enable URL #### Nginx
rewriting (lines `5` to `8`) and denying access to Pico's internal files
(lines `1` to `3`). You'll need to adjust the path (`/pico` on lines `1`, `5` If you're using Nginx, you can use the following config to enable URL rewriting
and `7`) to match your installation directory. Additionally, you'll need to (lines `5` to `8`) and denying access to Pico's internal files (lines `1` to
enable URL rewriting by setting `$config['rewrite_url'] = true;` in your `3`). You'll need to adjust the path (`/pico` on lines `1`, `2`, `5` and `7`)
`config/config.php`. The Nginx configuration should provide the *bare minimum* to match your installation directory. Additionally, you'll need to enable URL
you need for Pico. Nginx is a very extensive subject. If you have any trouble, rewriting by setting `rewrite_url: true` in your `config/config.yml`. The Nginx
please read through our [Nginx configuration docs][NginxConfig]. config should provide the *bare minimum* you need for Pico. Nginx is a very
extensive subject. If you have any trouble, please read through our
[Nginx config docs][NginxConfig].
``` ```
location ~ /pico/(\.htaccess|\.git|config|content|content-sample|lib|vendor|CHANGELOG\.md|composer\.(json|lock)) { location ~ ^/pico/((config|content|vendor|composer\.(json|lock|phar))(/|$)|(.+/)?\.(?!well-known(/|$))) {
return 404; try_files /pico/index.php$is_args$args;
} }
location ~ ^/pico(.*) { location /pico/ {
index index.php; index index.php;
try_files $uri $uri/ /pico/index.php?$1&$args; try_files $uri $uri/ /pico/index.php$is_args$args;
} }
``` ```
#### Lighttpd
Pico runs smoothly on Lighttpd. You can use the following config to enable URL
rewriting (lines `6` to `9`) and denying access to Pico's internal files (lines
`1` to `4`). Make sure to adjust the path (`/pico` on lines `2`, `3` and `7`)
to match your installation directory, and let Pico know about available URL
rewriting by setting `rewrite_url: true` in your `config/config.yml`. The
config below should provide the *bare minimum* you need for Pico.
```
url.rewrite-once = (
"^/pico/(config|content|vendor|composer\.(json|lock|phar))(/|$)" => "/pico/index.php",
"^/pico/(.+/)?\.(?!well-known(/|$))" => "/pico/index.php"
)
url.rewrite-if-not-file = (
"^/pico(/|$)" => "/pico/index.php"
)
```
## Documentation ## 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 http://picocms.org/docs.
@ -318,9 +378,11 @@ For more help have a look at the Pico documentation at http://picocms.org/docs.
[MarkdownExtra]: https://michelf.ca/projects/php-markdown/extra/ [MarkdownExtra]: https://michelf.ca/projects/php-markdown/extra/
[YAML]: https://en.wikipedia.org/wiki/YAML [YAML]: https://en.wikipedia.org/wiki/YAML
[Twig]: http://twig.sensiolabs.org/documentation [Twig]: http://twig.sensiolabs.org/documentation
[UnixTimestamp]: https://en.wikipedia.org/wiki/Unix_timestamp
[WikiThemes]: https://github.com/picocms/Pico/wiki/Pico-Themes [WikiThemes]: https://github.com/picocms/Pico/wiki/Pico-Themes
[WikiPlugins]: https://github.com/picocms/Pico/wiki/Pico-Plugins [WikiPlugins]: https://github.com/picocms/Pico/wiki/Pico-Plugins
[OfficialThemes]: http://picocms.org/themes/ [OfficialThemes]: http://picocms.org/themes/
[PluginUpgrade]: http://picocms.org/development/#upgrade [PluginUpgrade]: http://picocms.org/development/#upgrade
[ModRewrite]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html [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]: http://picocms.org/in-depth/nginx/

2
content/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# This directory is meant to be empty
*

View file

@ -1,4 +1,14 @@
<?php // @codingStandardsIgnoreFile <?php // @codingStandardsIgnoreFile
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/index.php>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
// load dependencies // load dependencies
if (is_file(__DIR__ . '/vendor/autoload.php')) { if (is_file(__DIR__ . '/vendor/autoload.php')) {
@ -8,7 +18,7 @@ if (is_file(__DIR__ . '/vendor/autoload.php')) {
// composer dependency package // composer dependency package
require_once(__DIR__ . '/../../../vendor/autoload.php'); require_once(__DIR__ . '/../../../vendor/autoload.php');
} else { } else {
die("Cannot find `vendor/autoload.php`. Run `composer install`."); die("Cannot find 'vendor/autoload.php'. Run `composer install`.");
} }
// instance Pico // instance Pico

View file

@ -1,14 +1,24 @@
<?php // @codingStandardsIgnoreFile <?php // @codingStandardsIgnoreFile
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/index.php.dist>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
// check PHP platform requirements // check PHP platform requirements
if (PHP_VERSION_ID < 50306) { if (PHP_VERSION_ID < 50306) {
die('Pico requires PHP 5.3.6 or above to run'); die('Pico requires PHP 5.3.6 or above to run');
} }
if (!extension_loaded('dom')) { if (!extension_loaded('dom')) {
die('Pico requires the PHP extension "dom" to run'); die("Pico requires the PHP extension 'dom' to run");
} }
if (!extension_loaded('mbstring')) { if (!extension_loaded('mbstring')) {
die('Pico requires the PHP extension "mbstring" to run'); die("Pico requires the PHP extension 'mbstring' to run");
} }
// load dependencies // load dependencies
@ -22,5 +32,8 @@ $pico = new Pico(
'themes/' // themes dir 'themes/' // themes dir
); );
// override configuration?
//$pico->setConfig(array());
// run application // run application
echo $pico->run(); echo $pico->run();

View file

@ -1,14 +1,27 @@
<?php <?php
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/lib/AbstractPicoPlugin.php>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
/** /**
* Abstract class to extend from when implementing a Pico plugin * Abstract class to extend from when implementing a Pico plugin
* *
* Please refer to {@see PicoPluginInterface} for more information about how
* to develop a plugin for Pico.
*
* @see PicoPluginInterface * @see PicoPluginInterface
* *
* @author Daniel Rudolf * @author Daniel Rudolf
* @link http://picocms.org * @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License * @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0 * @version 2.0
*/ */
abstract class AbstractPicoPlugin implements PicoPluginInterface abstract class AbstractPicoPlugin implements PicoPluginInterface
{ {
@ -21,22 +34,30 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
private $pico; private $pico;
/** /**
* Boolean indicating if this plugin is enabled (true) or disabled (false) * Boolean indicating if this plugin is enabled (TRUE) or disabled (FALSE)
* *
* @see PicoPluginInterface::isEnabled() * @see PicoPluginInterface::isEnabled()
* @see PicoPluginInterface::setEnabled() * @see PicoPluginInterface::setEnabled()
* @var boolean * @var bool|null
*/ */
protected $enabled = true; protected $enabled;
/** /**
* Boolean indicating if this plugin was ever enabled/disabled manually * Boolean indicating if this plugin was ever enabled/disabled manually
* *
* @see PicoPluginInterface::isStatusChanged() * @see PicoPluginInterface::isStatusChanged()
* @var boolean * @var bool
*/ */
protected $statusChanged = false; protected $statusChanged = false;
/**
* Boolean indicating whether this plugin matches Pico's API version
*
* @see AbstractPicoPlugin::checkCompatibility()
* @var bool|null
*/
protected $nativePlugin;
/** /**
* List of plugins which this plugin depends on * List of plugins which this plugin depends on
* *
@ -74,14 +95,16 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
if ($pluginEnabled !== null) { if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled); $this->setEnabled($pluginEnabled);
} else { } else {
$pluginConfig = $this->getConfig(get_called_class()); $pluginEnabled = $this->getPluginConfig('enabled');
if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) { if ($pluginEnabled !== null) {
$this->setEnabled($pluginConfig['enabled']); $this->setEnabled($pluginEnabled);
} elseif ($this->enabled) { } elseif ($this->enabled) {
$this->setEnabled($this->enabled, true, true);
} elseif ($this->enabled === null) {
// make sure dependencies are already fulfilled, // make sure dependencies are already fulfilled,
// otherwise the plugin needs to be enabled manually // otherwise the plugin needs to be enabled manually
try { try {
$this->checkDependencies(false); $this->setEnabled(true, false, true);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
$this->enabled = false; $this->enabled = false;
} }
@ -105,6 +128,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
$this->enabled = (bool) $enabled; $this->enabled = (bool) $enabled;
if ($enabled) { if ($enabled) {
$this->checkCompatibility();
$this->checkDependencies($recursive); $this->checkDependencies($recursive);
} else { } else {
$this->checkDependants($recursive); $this->checkDependants($recursive);
@ -135,13 +159,39 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
return $this->pico; return $this->pico;
} }
/**
* Returns either the value of the specified plugin config variable or
* the config array
*
* @param string $configName optional name of a config variable
* @param mixed $default optional default value to return when the
* named config variable doesn't exist
*
* @return mixed if no name of a config variable has been supplied, the
* plugin's config array is returned; otherwise it returns either the
* value of the named config variable, or, if the named config variable
* doesn't exist, the provided default value or NULL
*/
public function getPluginConfig($configName = null, $default = null)
{
$pluginConfig = $this->getConfig(get_called_class(), array());
if ($configName === null) {
return $pluginConfig;
}
return isset($pluginConfig[$configName]) ? $pluginConfig[$configName] : $default;
}
/** /**
* Passes all not satisfiable method calls to Pico * Passes all not satisfiable method calls to Pico
* *
* @see Pico * @see Pico
* @param string $methodName name of the method to call *
* @param array $params parameters to pass * @param string $methodName name of the method to call
* @return mixed return value of the called method * @param array $params parameters to pass
*
* @return mixed return value of the called method
*/ */
public function __call($methodName, array $params) public function __call($methodName, array $params)
{ {
@ -158,10 +208,13 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
/** /**
* Enables all plugins which this plugin depends on * Enables all plugins which this plugin depends on
* *
* @see PicoPluginInterface::getDependencies() * @see PicoPluginInterface::getDependencies()
* @param boolean $recursive enable required plugins automatically *
* @param bool $recursive enable required plugins automatically
*
* @return void * @return void
* @throws RuntimeException thrown when a dependency fails *
* @throws RuntimeException thrown when a dependency fails
*/ */
protected function checkDependencies($recursive) protected function checkDependencies($recursive)
{ {
@ -176,7 +229,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
} }
// plugins which don't implement PicoPluginInterface are always enabled // plugins which don't implement PicoPluginInterface are always enabled
if (is_a($plugin, 'PicoPluginInterface') && !$plugin->isEnabled()) { if (($plugin instanceof PicoPluginInterface) && !$plugin->isEnabled()) {
if ($recursive) { if ($recursive) {
if (!$plugin->isStatusChanged()) { if (!$plugin->isStatusChanged()) {
$plugin->setEnabled(true, true, true); $plugin->setEnabled(true, true, true);
@ -207,15 +260,18 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
/** /**
* Disables all plugins which depend on this plugin * Disables all plugins which depend on this plugin
* *
* @see PicoPluginInterface::getDependants() * @see PicoPluginInterface::getDependants()
* @param boolean $recursive disabled dependant plugins automatically *
* @param bool $recursive disabled dependant plugins automatically
*
* @return void * @return void
* @throws RuntimeException thrown when a dependency fails *
* @throws RuntimeException thrown when a dependency fails
*/ */
protected function checkDependants($recursive) protected function checkDependants($recursive)
{ {
$dependants = $this->getDependants(); $dependants = $this->getDependants();
if (!empty($dependants)) { if ($dependants) {
if ($recursive) { if ($recursive) {
foreach ($this->getDependants() as $pluginName => $plugin) { foreach ($this->getDependants() as $pluginName => $plugin) {
if ($plugin->isEnabled()) { if ($plugin->isEnabled()) {
@ -230,8 +286,8 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
} }
} }
} else { } else {
$dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' '; $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' '
$dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'"; . "'" . implode("', '", array_keys($dependants)) . "'";
throw new RuntimeException( throw new RuntimeException(
"Unable to disable plugin '" . get_called_class() . "': " "Unable to disable plugin '" . get_called_class() . "': "
. "Required by " . $dependantsList . "Required by " . $dependantsList
@ -249,7 +305,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
$this->dependants = array(); $this->dependants = array();
foreach ($this->getPlugins() as $pluginName => $plugin) { foreach ($this->getPlugins() as $pluginName => $plugin) {
// only plugins which implement PicoPluginInterface support dependencies // only plugins which implement PicoPluginInterface support dependencies
if (is_a($plugin, 'PicoPluginInterface')) { if ($plugin instanceof PicoPluginInterface) {
$dependencies = $plugin->getDependencies(); $dependencies = $plugin->getDependencies();
if (in_array(get_called_class(), $dependencies)) { if (in_array(get_called_class(), $dependencies)) {
$this->dependants[$pluginName] = $plugin; $this->dependants[$pluginName] = $plugin;
@ -260,4 +316,37 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface
return $this->dependants; return $this->dependants;
} }
/**
* Checks compatibility with Pico's API version
*
* 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.
* 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
*/
protected function checkCompatibility()
{
if ($this->nativePlugin === null) {
$picoClassName = get_class($this->pico);
$picoApiVersion = defined($picoClassName . '::API_VERSION') ? $picoClassName::API_VERSION : 1;
$pluginApiVersion = defined('static::API_VERSION') ? static::API_VERSION : 1;
$this->nativePlugin = ($pluginApiVersion === $picoApiVersion);
if (!$this->nativePlugin && ($pluginApiVersion > $picoApiVersion)) {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': The plugin's API (version "
. $pluginApiVersion . ") isn't compatible with Pico's API (version " . $picoApiVersion . ")"
);
}
}
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,32 @@
<?php <?php
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/lib/PicoPluginInterface.php>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
/** /**
* Common interface for Pico plugins * Common interface for Pico plugins
* *
* For a list of supported events see {@link DummyPlugin}; you can use * For a list of supported events see {@see DummyPlugin}; you can use
* {@link DummyPlugin} as template for new plugins. For a list of deprecated * {@see DummyPlugin} as template for new plugins. For a list of deprecated
* events see {@link PicoDeprecated}. * events see {@see PicoDeprecated}.
* *
* You SHOULD NOT use deprecated events when implementing this interface. * If you're developing a new plugin, you MUST both implement this interface
* Deprecated events are triggered by the {@link PicoDeprecated} plugin, if * and define the class constant `API_VERSION`. You SHOULD always use the
* plugins which don't implement this interface are loaded. You can take * API version of Pico's latest milestone when releasing a plugin. If you're
* advantage from this behaviour if you want to do something only when old * developing a new version of an existing plugin, it is strongly recommended
* plugins are loaded. Consequently the old events are never triggered when * to update your plugin to use Pico's latest API version.
* your plugin is implementing this interface and no old plugins are present.
*
* If you're developing a new plugin, you MUST implement this interface. If
* you're the developer of an old plugin, it is STRONGLY RECOMMENDED to use
* the events introduced in Pico 1.0 when releasing a new version of your
* plugin. If you want to use any of the new events, you MUST implement
* this interface and update all other events you use.
* *
* @author Daniel Rudolf * @author Daniel Rudolf
* @link http://picocms.org * @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License * @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0 * @version 2.0
*/ */
interface PicoPluginInterface interface PicoPluginInterface
{ {
@ -37,8 +40,9 @@ interface PicoPluginInterface
/** /**
* Handles a event that was triggered by Pico * Handles a event that was triggered by Pico
* *
* @param string $eventName name of the triggered event * @param string $eventName name of the triggered event
* @param array $params passed parameters * @param array $params passed parameters
*
* @return void * @return void
*/ */
public function handleEvent($eventName, array $params); public function handleEvent($eventName, array $params);
@ -46,35 +50,44 @@ interface PicoPluginInterface
/** /**
* Enables or disables this plugin * Enables or disables this plugin
* *
* @see PicoPluginInterface::isEnabled() * @see PicoPluginInterface::isEnabled()
* @see PicoPluginInterface::isStatusChanged() * @see PicoPluginInterface::isStatusChanged()
* @param boolean $enabled enable (true) or disable (false) this plugin *
* @param boolean $recursive when true, enable or disable recursively * @param bool $enabled enable (TRUE) or disable (FALSE) this plugin
* @param bool $recursive when TRUE, enable or disable recursively.
* In other words, if you enable a plugin, all required plugins are * In other words, if you enable a plugin, all required plugins are
* enabled, too. When disabling a plugin, all depending plugins are * enabled, too. When disabling a plugin, all depending plugins are
* disabled likewise. Recursive operations are only performed as long * disabled likewise. Recursive operations are only performed as long
* as a plugin wasn't enabled/disabled manually. This parameter is * as a plugin wasn't enabled/disabled manually. This parameter is
* optional and defaults to true. * optional and defaults to TRUE.
* @param boolean $auto enable or disable to fulfill a dependency * @param bool $auto enable or disable to fulfill a dependency. This
* This parameter is optional and defaults to false. * parameter is optional and defaults to FALSE.
*
* @return void * @return void
* @throws RuntimeException thrown when a dependency fails *
* @throws RuntimeException thrown when a dependency fails
*/ */
public function setEnabled($enabled, $recursive = true, $auto = false); public function setEnabled($enabled, $recursive = true, $auto = false);
/** /**
* Returns true if this plugin is enabled, false otherwise * Returns a boolean indicating whether this plugin is enabled or not
* *
* @see PicoPluginInterface::setEnabled() * You musn't rely on the return value when Pico's `onConfigLoaded` event
* @return boolean plugin is enabled (true) or disabled (false) * wasn't triggered on all plugins yet. This method might even return NULL
* then. The plugin's status might change later.
*
* @see PicoPluginInterface::setEnabled()
*
* @return bool|null plugin is enabled (TRUE) or disabled (FALSE)
*/ */
public function isEnabled(); public function isEnabled();
/** /**
* Returns true if the plugin was ever enabled/disabled manually * Returns TRUE if the plugin was ever enabled/disabled manually
* *
* @see PicoPluginInterface::setEnabled() * @see PicoPluginInterface::setEnabled()
* @return boolean plugin is in its default state (true), false otherwise *
* @return bool plugin is in its default state (TRUE), FALSE otherwise
*/ */
public function isStatusChanged(); public function isStatusChanged();
@ -95,7 +108,8 @@ interface PicoPluginInterface
/** /**
* Returns the plugins instance of Pico * Returns the plugins instance of Pico
* *
* @see Pico * @see Pico
*
* @return Pico the plugins instance of Pico * @return Pico the plugins instance of Pico
*/ */
public function getPico(); public function getPico();

View file

@ -1,4 +1,14 @@
<?php <?php
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/lib/PicoTwigExtension.php>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
/** /**
* Pico's Twig extension to implement additional filters * Pico's Twig extension to implement additional filters
@ -6,7 +16,7 @@
* @author Daniel Rudolf * @author Daniel Rudolf
* @link http://picocms.org * @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License * @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0 * @version 2.0
*/ */
class PicoTwigExtension extends Twig_Extension class PicoTwigExtension extends Twig_Extension
{ {
@ -31,8 +41,9 @@ class PicoTwigExtension extends Twig_Extension
/** /**
* Returns the extensions instance of Pico * Returns the extensions instance of Pico
* *
* @see Pico * @see Pico
* @return Pico the extensions instance of Pico *
* @return Pico the extension's instance of Pico
*/ */
public function getPico() public function getPico()
{ {
@ -42,7 +53,8 @@ class PicoTwigExtension extends Twig_Extension
/** /**
* Returns the name of the extension * Returns the name of the extension
* *
* @see Twig_ExtensionInterface::getName() * @see Twig_ExtensionInterface::getName()
*
* @return string the extension name * @return string the extension name
*/ */
public function getName() public function getName()
@ -51,9 +63,10 @@ class PicoTwigExtension extends Twig_Extension
} }
/** /**
* Returns the Twig filters markdown, map and sort_by * Returns a list of Pico-specific Twig filters
*
* @see Twig_ExtensionInterface::getFilters()
* *
* @see Twig_ExtensionInterface::getFilters()
* @return Twig_SimpleFilter[] array of Pico's Twig filters * @return Twig_SimpleFilter[] array of Pico's Twig filters
*/ */
public function getFilters() public function getFilters()
@ -62,6 +75,22 @@ class PicoTwigExtension extends Twig_Extension
'markdown' => new Twig_SimpleFilter('markdown', array($this, 'markdownFilter')), 'markdown' => new Twig_SimpleFilter('markdown', array($this, 'markdownFilter')),
'map' => new Twig_SimpleFilter('map', array($this, 'mapFilter')), 'map' => new Twig_SimpleFilter('map', array($this, 'mapFilter')),
'sort_by' => new Twig_SimpleFilter('sort_by', array($this, 'sortByFilter')), 'sort_by' => new Twig_SimpleFilter('sort_by', array($this, 'sortByFilter')),
'link' => new Twig_SimpleFilter('link', array($this->pico, 'getPageUrl'))
);
}
/**
* Returns a list of Pico-specific Twig functions
*
* @see Twig_ExtensionInterface::getFunctions()
*
* @return Twig_SimpleFunction[] array of Pico's Twig functions
*/
public function getFunctions()
{
return array(
'url_param' => new Twig_SimpleFunction('url_param', array($this, 'urlParamFunction')),
'form_param' => new Twig_SimpleFunction('form_param', array($this, 'formParamFunction'))
); );
} }
@ -73,19 +102,18 @@ class PicoTwigExtension extends Twig_Extension
* Don't use it to parse the contents of a page, use the `content` filter * Don't use it to parse the contents of a page, use the `content` filter
* instead, what ensures the proper preparation of the contents. * instead, what ensures the proper preparation of the contents.
* *
* @param string $markdown markdown to parse * @see Pico::substituteFileContent()
* @return string parsed HTML * @see Pico::parseFileContent()
*
* @param string $markdown markdown to parse
* @param array $meta meta data to use for %meta.*% replacement
*
* @return string parsed HTML
*/ */
public function markdownFilter($markdown) public function markdownFilter($markdown, array $meta = array())
{ {
if ($this->getPico()->getParsedown() === null) { $markdown = $this->getPico()->substituteFileContent($markdown, $meta);
throw new LogicException( return $this->getPico()->parseFileContent($markdown);
'Unable to apply Twig "markdown" filter: '
. 'Parsedown instance wasn\'t registered yet'
);
}
return $this->getPico()->getParsedown()->text($markdown);
} }
/** /**
@ -94,15 +122,16 @@ class PicoTwigExtension extends Twig_Extension
* This method is registered as the Twig `map` filter. You can use this * This method is registered as the Twig `map` filter. You can use this
* filter to e.g. get all page titles (`{{ pages|map("title") }}`). * filter to e.g. get all page titles (`{{ pages|map("title") }}`).
* *
* @param array|Traversable $var variable to map * @param array|Traversable $var variable to map
* @param mixed $mapKeyPath key to map; either a scalar or a * @param mixed $mapKeyPath key to map; either a scalar or a
* array interpreted as key path (i.e. ['foo', 'bar'] will return all * array interpreted as key path (i.e. ['foo', 'bar'] will return all
* $item['foo']['bar'] values) * $item['foo']['bar'] values)
* @return array mapped values *
* @return array mapped values
*/ */
public function mapFilter($var, $mapKeyPath) public function mapFilter($var, $mapKeyPath)
{ {
if (!is_array($var) && (!is_object($var) || !is_a($var, 'Traversable'))) { if (!is_array($var) && (!is_object($var) || !($var instanceof Traversable))) {
throw new Twig_Error_Runtime(sprintf( throw new Twig_Error_Runtime(sprintf(
'The map filter only works with arrays or "Traversable", got "%s"', 'The map filter only works with arrays or "Traversable", got "%s"',
is_object($var) ? get_class($var) : gettype($var) is_object($var) ? get_class($var) : gettype($var)
@ -122,26 +151,27 @@ class PicoTwigExtension extends Twig_Extension
* *
* This method is registered as the Twig `sort_by` filter. You can use this * This method is registered as the Twig `sort_by` filter. You can use this
* filter to e.g. sort the pages array by a arbitrary meta value. Calling * filter to e.g. sort the pages array by a arbitrary meta value. Calling
* `{{ pages|sort_by("meta:nav"|split(":")) }}` returns all pages sorted by * `{{ pages|sort_by([ "meta", "nav" ]) }}` returns all pages sorted by the
* the meta value `nav`. Please note the `"meta:nav"|split(":")` part of * meta value `nav`. The sorting algorithm will never assume equality of
* the example. The sorting algorithm will never assume equality of two * two values, it will then fall back to the original order. The result is
* values, it will then fall back to the original order. The result is
* always sorted in ascending order, apply Twigs `reverse` filter to * always sorted in ascending order, apply Twigs `reverse` filter to
* achieve a descending order. * achieve a descending order.
* *
* @param array|Traversable $var variable to sort * @param array|Traversable $var variable to sort
* @param mixed $sortKeyPath key to use for sorting; either * @param mixed $sortKeyPath key to use for sorting; either
* a scalar or a array interpreted as key path (i.e. ['foo', 'bar'] * a scalar or a array interpreted as key path (i.e. ['foo', 'bar']
* will sort $var by $item['foo']['bar']) * will sort $var by $item['foo']['bar'])
* @param string $fallback specify what to do with items * @param string $fallback specify what to do with items
* which don't contain the specified sort key; use "bottom" (default) * which don't contain the specified sort key; use "bottom" (default)
* to move those items to the end of the sorted array, "top" to rank * to move these items to the end of the sorted array, "top" to rank
* them first, or "keep" to keep the original order of those items * them first, "keep" to keep the original order, or "remove" to remove
* @return array sorted array * these items
*
* @return array sorted array
*/ */
public function sortByFilter($var, $sortKeyPath, $fallback = 'bottom') public function sortByFilter($var, $sortKeyPath, $fallback = 'bottom')
{ {
if (is_object($var) && is_a($var, 'Traversable')) { if (is_object($var) && ($var instanceof Traversable)) {
$var = iterator_to_array($var, true); $var = iterator_to_array($var, true);
} elseif (!is_array($var)) { } elseif (!is_array($var)) {
throw new Twig_Error_Runtime(sprintf( throw new Twig_Error_Runtime(sprintf(
@ -149,12 +179,15 @@ class PicoTwigExtension extends Twig_Extension
is_object($var) ? get_class($var) : gettype($var) is_object($var) ? get_class($var) : gettype($var)
)); ));
} }
if (($fallback !== 'top') && ($fallback !== 'bottom') && ($fallback !== 'keep')) { if (($fallback !== 'top') && ($fallback !== 'bottom') && ($fallback !== 'keep') && ($fallback !== "remove")) {
throw new Twig_Error_Runtime('The sort_by filter only supports the "top", "bottom" and "keep" fallbacks'); throw new Twig_Error_Runtime(
'The sort_by filter only supports the "top", "bottom", "keep" and "remove" fallbacks'
);
} }
$twigExtension = $this; $twigExtension = $this;
$varKeys = array_keys($var); $varKeys = array_keys($var);
$removeItems = array();
uksort($var, function ($a, $b) use ($twigExtension, $var, $varKeys, $sortKeyPath, $fallback, &$removeItems) { uksort($var, function ($a, $b) use ($twigExtension, $var, $varKeys, $sortKeyPath, $fallback, &$removeItems) {
$aSortValue = $twigExtension->getKeyOfVar($var[$a], $sortKeyPath); $aSortValue = $twigExtension->getKeyOfVar($var[$a], $sortKeyPath);
$aSortValueNull = ($aSortValue === null); $aSortValueNull = ($aSortValue === null);
@ -162,7 +195,15 @@ class PicoTwigExtension extends Twig_Extension
$bSortValue = $twigExtension->getKeyOfVar($var[$b], $sortKeyPath); $bSortValue = $twigExtension->getKeyOfVar($var[$b], $sortKeyPath);
$bSortValueNull = ($bSortValue === null); $bSortValueNull = ($bSortValue === null);
if ($aSortValueNull xor $bSortValueNull) { if (($fallback === 'remove') && ($aSortValueNull || $bSortValueNull)) {
if ($aSortValueNull) {
$removeItems[$a] = $var[$a];
}
if ($bSortValueNull) {
$removeItems[$b] = $var[$b];
}
return ($aSortValueNull - $bSortValueNull);
} elseif ($aSortValueNull xor $bSortValueNull) {
if ($fallback === 'top') { if ($fallback === 'top') {
return ($aSortValueNull - $bSortValueNull) * -1; return ($aSortValueNull - $bSortValueNull) * -1;
} elseif ($fallback === 'bottom') { } elseif ($fallback === 'bottom') {
@ -180,6 +221,10 @@ class PicoTwigExtension extends Twig_Extension
return ($aIndex > $bIndex) ? 1 : -1; return ($aIndex > $bIndex) ? 1 : -1;
}); });
if ($removeItems) {
$var = array_diff_key($var, $removeItems);
}
return $var; return $var;
} }
@ -187,16 +232,17 @@ class PicoTwigExtension extends Twig_Extension
* Returns the value of a variable item specified by a scalar key or a * Returns the value of a variable item specified by a scalar key or a
* arbitrary deep sub-key using a key path * arbitrary deep sub-key using a key path
* *
* @param array|Traversable|ArrayAccess|object $var base variable * @param array|Traversable|ArrayAccess|object $var base variable
* @param mixed $keyPath scalar key or a * @param mixed $keyPath scalar key or a
* array interpreted as key path (when passing e.g. ['foo', 'bar'], * array interpreted as key path (when passing e.g. ['foo', 'bar'],
* the method will return $var['foo']['bar']) specifying the value * the method will return $var['foo']['bar']) specifying the value
* @return mixed the requested *
* value or NULL when the given key or key path didn't match * @return mixed the requested value or NULL when the given key or key path
* didn't match
*/ */
public static function getKeyOfVar($var, $keyPath) public static function getKeyOfVar($var, $keyPath)
{ {
if (empty($keyPath)) { if (!$keyPath) {
return null; return null;
} elseif (!is_array($keyPath)) { } elseif (!is_array($keyPath)) {
$keyPath = array($keyPath); $keyPath = array($keyPath);
@ -204,9 +250,9 @@ class PicoTwigExtension extends Twig_Extension
foreach ($keyPath as $key) { foreach ($keyPath as $key) {
if (is_object($var)) { if (is_object($var)) {
if (is_a($var, 'ArrayAccess')) { if ($var instanceof ArrayAccess) {
// use ArrayAccess, see below // use ArrayAccess, see below
} elseif (is_a($var, 'Traversable')) { } elseif ($var instanceof Traversable) {
$var = iterator_to_array($var); $var = iterator_to_array($var);
} elseif (isset($var->{$key})) { } elseif (isset($var->{$key})) {
$var = $var->{$key}; $var = $var->{$key};
@ -235,4 +281,62 @@ class PicoTwigExtension extends Twig_Extension
return $var; return $var;
} }
/**
* Filters a URL GET parameter with a specified filter
*
* The Twig function disallows the use of the `callback` filter.
*
* @see Pico::getUrlParameter()
*
* @param string $name name of the URL GET parameter
* to filter
* @param int|string $filter the filter to apply
* @param mixed|array $options either a associative options
* array to be used by the filter or a scalar default value
* @param int|string|int[]|string[] $flags flags and flag strings to be
* used by the filter
*
* @return mixed either the filtered data, FALSE if the filter fails, or
* NULL if the URL GET parameter doesn't exist and no default value is
* given
*/
public function urlParamFunction($name, $filter = '', $options = null, $flags = null)
{
$filter = $filter ? (is_string($filter) ? filter_id($filter) : (int) $filter) : false;
if (!$filter || ($filter === FILTER_CALLBACK)) {
return false;
}
return $this->pico->getUrlParameter($name, $filter, $options, $flags);
}
/**
* Filters a HTTP POST parameter with a specified filter
*
* The Twig function disallows the use of the `callback` filter.
*
* @see Pico::getFormParameter()
*
* @param string $name name of the HTTP POST
* parameter to filter
* @param int|string $filter the filter to apply
* @param mixed|array $options either a associative options
* array to be used by the filter or a scalar default value
* @param int|string|int[]|string[] $flags flags and flag strings to be
* used by the filter
*
* @return mixed either the filtered data, FALSE if the filter fails, or
* NULL if the HTTP POST parameter doesn't exist and no default value
* is given
*/
public function formParamFunction($name, $filter = '', $options = null, $flags = null)
{
$filter = $filter ? (is_string($filter) ? filter_id($filter) : (int) $filter) : false;
if (!$filter || ($filter === FILTER_CALLBACK)) {
return false;
}
return $this->pico->getFormParameter($name, $filter, $options, $flags);
}
} }

3
plugins/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# This directory is meant to be empty, except for DummyPlugin.php
*
!DummyPlugin.php

View file

@ -1,437 +0,0 @@
<?php
/**
* Serve features of Pico deprecated since v1.0
*
* This plugin exists for backward compatibility and is disabled by default.
* It gets automatically enabled when a plugin which doesn't implement
* {@link PicoPluginInterface} is loaded. This plugin triggers deprecated
* events and automatically enables {@link PicoParsePagesContent} and
* {@link PicoExcerpt}. These plugins heavily impact Pico's performance! You
* can disable this plugin by calling {@link PicoDeprecated::setEnabled()}.
*
* The following deprecated events are triggered by this plugin:
*
* | Event | ... triggers the deprecated event |
* | ------------------- | --------------------------------------------------------- |
* | onPluginsLoaded | plugins_loaded() |
* | onConfigLoaded | config_loaded($config) |
* | onRequestUrl | request_url($url) |
* | onContentLoading | before_load_content($file) |
* | onContentLoaded | after_load_content($file, $rawContent) |
* | on404ContentLoading | before_404_load_content($file) |
* | on404ContentLoaded | after_404_load_content($file, $rawContent) |
* | onMetaHeaders | before_read_file_meta($headers) |
* | onMetaParsed | file_meta($meta) |
* | onContentParsing | before_parse_content($rawContent) |
* | onContentParsed | after_parse_content($content) |
* | onContentParsed | content_parsed($content) |
* | onSinglePageLoaded | get_page_data($pages, $meta) |
* | onPagesLoaded | get_pages($pages, $currentPage, $previousPage, $nextPage) |
* | onTwigRegistration | before_twig_register() |
* | onPageRendering | before_render($twigVariables, $twig, $templateName) |
* | onPageRendered | after_render($output) |
*
* Since Pico 1.0 the config is stored in {@path "config/config.php"}. This
* plugin tries to read {@path "config.php"} in Pico's root dir and overwrites
* all settings previously specified in {@path "config/config.php"}.
*
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0
*/
class PicoDeprecated extends AbstractPicoPlugin
{
/**
* This plugin is disabled by default
*
* @see AbstractPicoPlugin::$enabled
*/
protected $enabled = false;
/**
* The requested file
*
* @see PicoDeprecated::getRequestFile()
* @var string|null
*/
protected $requestFile;
/**
* Enables this plugin on demand and triggers the deprecated event
* plugins_loaded()
*
* @see DummyPlugin::onPluginsLoaded()
*/
public function onPluginsLoaded(array &$plugins)
{
if (!empty($plugins)) {
foreach ($plugins as $plugin) {
if (!is_a($plugin, 'PicoPluginInterface')) {
// the plugin doesn't implement PicoPluginInterface; it uses deprecated events
// enable PicoDeprecated if it hasn't be explicitly enabled/disabled yet
if (!$this->isStatusChanged()) {
$this->setEnabled(true, true, true);
}
break;
}
}
} else {
// no plugins were found, so it actually isn't necessary to call deprecated events
// anyway, this plugin also ensures compatibility apart from events used by old plugins,
// so enable PicoDeprecated if it hasn't be explicitly enabled/disabled yet
if (!$this->isStatusChanged()) {
$this->setEnabled(true, true, true);
}
}
if ($this->isEnabled()) {
$this->triggerEvent('plugins_loaded');
}
}
/**
* Triggers the deprecated event config_loaded($config)
*
* This method also defines deprecated constants, reads the `config.php`
* in Pico's root dir, enables the plugins {@link PicoParsePagesContent}
* and {@link PicoExcerpt} and makes `$config` globally accessible (the
* latter was removed with Pico 0.9 and was added again as deprecated
* feature with Pico 1.0)
*
* @see PicoDeprecated::defineConstants()
* @see PicoDeprecated::loadRootDirConfig()
* @see PicoDeprecated::enablePlugins()
* @see DummyPlugin::onConfigLoaded()
* @param array &$config array of config variables
* @return void
*/
public function onConfigLoaded(array &$config)
{
$this->defineConstants();
$this->loadRootDirConfig($config);
$this->enablePlugins();
$GLOBALS['config'] = &$config;
$this->triggerEvent('config_loaded', array(&$config));
}
/**
* Defines deprecated constants
*
* `ROOT_DIR`, `LIB_DIR`, `PLUGINS_DIR`, `THEMES_DIR` and `CONTENT_EXT`
* are deprecated since v1.0, `CONTENT_DIR` existed just in v0.9,
* `CONFIG_DIR` just for a short time between v0.9 and v1.0 and
* `CACHE_DIR` was dropped with v1.0 without a replacement.
*
* @see PicoDeprecated::onConfigLoaded()
* @return void
*/
protected function defineConstants()
{
if (!defined('ROOT_DIR')) {
define('ROOT_DIR', $this->getRootDir());
}
if (!defined('CONFIG_DIR')) {
define('CONFIG_DIR', $this->getConfigDir());
}
if (!defined('LIB_DIR')) {
$picoReflector = new ReflectionClass('Pico');
define('LIB_DIR', dirname($picoReflector->getFileName()) . '/');
}
if (!defined('PLUGINS_DIR')) {
define('PLUGINS_DIR', $this->getPluginsDir());
}
if (!defined('THEMES_DIR')) {
define('THEMES_DIR', $this->getThemesDir());
}
if (!defined('CONTENT_DIR')) {
define('CONTENT_DIR', $this->getConfig('content_dir'));
}
if (!defined('CONTENT_EXT')) {
define('CONTENT_EXT', $this->getConfig('content_ext'));
}
}
/**
* Read config.php in Pico's root dir
*
* @see PicoDeprecated::onConfigLoaded()
* @see Pico::loadConfig()
* @param array &$realConfig array of config variables
* @return void
*/
protected function loadRootDirConfig(array &$realConfig)
{
if (file_exists($this->getRootDir() . 'config.php')) {
// config.php in Pico::$rootDir is deprecated
// use config.php in Pico::$configDir instead
$config = null;
require($this->getRootDir() . 'config.php');
if (is_array($config)) {
if (isset($config['base_url'])) {
$config['base_url'] = rtrim($config['base_url'], '/') . '/';
}
if (isset($config['content_dir'])) {
$config['content_dir'] = rtrim($config['content_dir'], '/\\') . '/';
}
$realConfig = $config + $realConfig;
}
}
}
/**
* Enables the plugins PicoParsePagesContent and PicoExcerpt
*
* @see PicoParsePagesContent
* @see PicoExcerpt
* @return void
*/
protected function enablePlugins()
{
// enable PicoParsePagesContent and PicoExcerpt
// we can't enable them during onPluginsLoaded because we can't know
// if the user disabled us (PicoDeprecated) manually in the config
$plugins = $this->getPlugins();
if (isset($plugins['PicoParsePagesContent'])) {
// parse all pages content if this plugin hasn't
// be explicitly enabled/disabled yet
if (!$plugins['PicoParsePagesContent']->isStatusChanged()) {
$plugins['PicoParsePagesContent']->setEnabled(true, true, true);
}
}
if (isset($plugins['PicoExcerpt'])) {
// enable excerpt plugin if it hasn't be explicitly enabled/disabled yet
if (!$plugins['PicoExcerpt']->isStatusChanged()) {
$plugins['PicoExcerpt']->setEnabled(true, true, true);
}
}
}
/**
* Triggers the deprecated event request_url($url)
*
* @see DummyPlugin::onRequestUrl()
*/
public function onRequestUrl(&$url)
{
$this->triggerEvent('request_url', array(&$url));
}
/**
* Sets PicoDeprecated::$requestFile to trigger the deprecated
* events after_load_content() and after_404_load_content()
*
* @see PicoDeprecated::onContentLoaded()
* @see PicoDeprecated::on404ContentLoaded()
* @see DummyPlugin::onRequestFile()
*/
public function onRequestFile(&$file)
{
$this->requestFile = &$file;
}
/**
* Triggers the deprecated before_load_content($file)
*
* @see DummyPlugin::onContentLoading()
*/
public function onContentLoading(&$file)
{
$this->triggerEvent('before_load_content', array(&$file));
}
/**
* Triggers the deprecated event after_load_content($file, $rawContent)
*
* @see DummyPlugin::onContentLoaded()
*/
public function onContentLoaded(&$rawContent)
{
$this->triggerEvent('after_load_content', array(&$this->requestFile, &$rawContent));
}
/**
* Triggers the deprecated before_404_load_content($file)
*
* @see DummyPlugin::on404ContentLoading()
*/
public function on404ContentLoading(&$file)
{
$this->triggerEvent('before_404_load_content', array(&$file));
}
/**
* Triggers the deprecated event after_404_load_content($file, $rawContent)
*
* @see DummyPlugin::on404ContentLoaded()
*/
public function on404ContentLoaded(&$rawContent)
{
$this->triggerEvent('after_404_load_content', array(&$this->requestFile, &$rawContent));
}
/**
* Triggers the deprecated event before_read_file_meta($headers)
*
* @see DummyPlugin::onMetaHeaders()
*/
public function onMetaHeaders(array &$headers)
{
$this->triggerEvent('before_read_file_meta', array(&$headers));
}
/**
* Triggers the deprecated event file_meta($meta)
*
* @see DummyPlugin::onMetaParsed()
*/
public function onMetaParsed(array &$meta)
{
$this->triggerEvent('file_meta', array(&$meta));
}
/**
* Triggers the deprecated event before_parse_content($rawContent)
*
* @see DummyPlugin::onContentParsing()
*/
public function onContentParsing(&$rawContent)
{
$this->triggerEvent('before_parse_content', array(&$rawContent));
}
/**
* Triggers the deprecated events after_parse_content($content) and
* content_parsed($content)
*
* @see DummyPlugin::onContentParsed()
*/
public function onContentParsed(&$content)
{
$this->triggerEvent('after_parse_content', array(&$content));
// deprecated since v0.8
$this->triggerEvent('content_parsed', array(&$content));
}
/**
* Triggers the deprecated event get_page_data($pages, $meta)
*
* @see DummyPlugin::onSinglePageLoaded()
*/
public function onSinglePageLoaded(array &$pageData)
{
$this->triggerEvent('get_page_data', array(&$pageData, $pageData['meta']));
}
/**
* Triggers the deprecated event
* get_pages($pages, $currentPage, $previousPage, $nextPage)
*
* Please note that the `get_pages()` event gets `$pages` passed without a
* array index. The index is rebuild later using either the `id` array key
* or is derived from the `url` array key. Duplicates are prevented by
* adding `~dup` when necessary.
*
* @see DummyPlugin::onPagesLoaded()
*/
public function onPagesLoaded(
array &$pages,
array &$currentPage = null,
array &$previousPage = null,
array &$nextPage = null
) {
// remove keys of pages array
$plainPages = array();
foreach ($pages as &$pageData) {
$plainPages[] = &$pageData;
}
unset($pageData);
$this->triggerEvent('get_pages', array(&$plainPages, &$currentPage, &$previousPage, &$nextPage));
// re-index pages array
$pages = array();
foreach ($plainPages as &$pageData) {
if (!isset($pageData['id'])) {
$urlPrefixLength = strlen($this->getBaseUrl()) + intval(!$this->isUrlRewritingEnabled());
$pageData['id'] = substr($pageData['url'], $urlPrefixLength);
}
// prevent duplicates
$id = $pageData['id'];
for ($i = 1; isset($pages[$id]); $i++) {
$id = $pageData['id'] . '~dup' . $i;
}
$pages[$id] = &$pageData;
}
}
/**
* Triggers the deprecated event before_twig_register()
*
* @see DummyPlugin::onTwigRegistration()
*/
public function onTwigRegistration()
{
$this->triggerEvent('before_twig_register');
}
/**
* Triggers the deprecated event before_render($twigVariables, $twig, $templateName)
*
* Please note that the `before_render()` event gets `$templateName` passed
* without its file extension. The file extension is later added again.
*
* @see DummyPlugin::onPageRendering()
*/
public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName)
{
// template name contains file extension since Pico 1.0
$fileExtension = '';
if (($fileExtensionPos = strrpos($templateName, '.')) !== false) {
$fileExtension = substr($templateName, $fileExtensionPos);
$templateName = substr($templateName, 0, $fileExtensionPos);
}
$this->triggerEvent('before_render', array(&$twigVariables, &$twig, &$templateName));
// add original file extension
$templateName = $templateName . $fileExtension;
}
/**
* Triggers the deprecated event after_render($output)
*
* @see DummyPlugin::onPageRendered()
*/
public function onPageRendered(&$output)
{
$this->triggerEvent('after_render', array(&$output));
}
/**
* Triggers a deprecated event on all plugins
*
* Deprecated events are also triggered on plugins which implement
* {@link PicoPluginInterface}. Please note that the methods are called
* directly and not through {@link PicoPluginInterface::handleEvent()}.
*
* @param string $eventName event to trigger
* @param array $params parameters to pass
* @return void
*/
protected function triggerEvent($eventName, array $params = array())
{
foreach ($this->getPlugins() as $plugin) {
if (method_exists($plugin, $eventName)) {
call_user_func_array(array($plugin, $eventName), $params);
}
}
}
}

View file

@ -1,40 +0,0 @@
<?php
/**
* Parses the contents of all pages
*
* This plugin exists for backward compatibility and is disabled by default.
* It gets automatically enabled when {@link PicoDeprecated} is enabled. You
* can avoid this by calling {@link PicoParsePagesContent::setEnabled()}.
*
* This plugin heavily impacts Pico's performance, you should avoid to enable
* it whenever possible! If you must parse the contents of a page, do this
* selectively and only for pages you really need to.
*
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0
*/
class PicoParsePagesContent extends AbstractPicoPlugin
{
/**
* This plugin is disabled by default
*
* @see AbstractPicoPlugin::$enabled
*/
protected $enabled = false;
/**
* Parses the contents of all pages
*
* @see DummyPlugin::onSinglePageLoaded()
*/
public function onSinglePageLoaded(array &$pageData)
{
if (!isset($pageData['content'])) {
$pageData['content'] = $this->prepareFileContent($pageData['raw_content'], $pageData['meta']);
$pageData['content'] = $this->parseFileContent($pageData['content']);
}
}
}

View file

@ -1,81 +0,0 @@
<?php
/**
* Creates a excerpt for the contents of each page (as of Pico v0.9 and older)
*
* This plugin exists for backward compatibility and is disabled by default.
* It gets automatically enabled when {@link PicoDeprecated} is enabled. You
* can avoid this by calling {@link PicoExcerpt::setEnabled()}.
*
* This plugin doesn't do its job very well and depends on
* {@link PicoParsePagesContent}, what heavily impacts Pico's performance. You
* should either use the Description meta header field or write something own.
* Best solution seems to be a filter for twig, see e.g.
* {@link https://gist.github.com/james2doyle/6629712}.
*
* @author Daniel Rudolf
* @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0
*/
class PicoExcerpt extends AbstractPicoPlugin
{
/**
* This plugin is disabled by default
*
* @see AbstractPicoPlugin::$enabled
*/
protected $enabled = false;
/**
* This plugin depends on PicoParsePagesContent
*
* @see PicoParsePagesContent
* @see AbstractPicoPlugin::$dependsOn
*/
protected $dependsOn = array('PicoParsePagesContent');
/**
* Adds the default excerpt length of 50 words to the config
*
* @see DummyPlugin::onConfigLoaded()
*/
public function onConfigLoaded(array &$config)
{
if (!isset($config['excerpt_length'])) {
$config['excerpt_length'] = 50;
}
}
/**
* Creates a excerpt for the contents of each page
*
* @see PicoExcerpt::createExcerpt()
* @see DummyPlugin::onSinglePageLoaded()
*/
public function onSinglePageLoaded(array &$pageData)
{
if (!isset($pageData['excerpt'])) {
$pageData['excerpt'] = $this->createExcerpt(
strip_tags($pageData['content']),
$this->getConfig('excerpt_length')
);
}
}
/**
* Helper function to create a excerpt of a string
*
* @param string $string the string to create a excerpt from
* @param int $wordLimit the maximum number of words the excerpt should be long
* @return string excerpt of $string
*/
protected function createExcerpt($string, $wordLimit)
{
$words = explode(' ', $string);
if (count($words) > $wordLimit) {
return trim(implode(' ', array_slice($words, 0, $wordLimit))) . '&hellip;';
}
return $string;
}
}

View file

@ -1,4 +1,14 @@
<?php <?php
/**
* This file is part of Pico. It's copyrighted by the contributors recorded
* in the version control history of the file, available from the following
* original location:
*
* <https://github.com/picocms/Pico/blob/master/plugins/DummyPlugin.php>
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/
/** /**
* Pico dummy plugin - a template for plugins * Pico dummy plugin - a template for plugins
@ -9,21 +19,50 @@
* @author Daniel Rudolf * @author Daniel Rudolf
* @link http://picocms.org * @link http://picocms.org
* @license http://opensource.org/licenses/MIT The MIT License * @license http://opensource.org/licenses/MIT The MIT License
* @version 1.0 * @version 2.0
*/ */
final class DummyPlugin extends AbstractPicoPlugin class DummyPlugin extends AbstractPicoPlugin
{ {
/** /**
* This plugin is enabled by default? * API version used by this plugin
*
* @var int
*/
const API_VERSION = 2;
/**
* This plugin is disabled by default
*
* 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.
*
* If this plugin should never be disabled *silently* (e.g. when dealing
* with security-relevant stuff like access control, or similar), set this
* to TRUE. If Pico can't fulfill all the plugin's dependencies, it will
* throw an RuntimeException.
*
* If this plugin rather does some "crazy stuff" a user should really be
* aware of before using it, you can set this to FALSE. The user will then
* have to enable the plugin manually. However, if another plugin depends
* on this plugin, it might get enabled silently nevertheless.
*
* No matter what, the user can always explicitly enable or disable this
* plugin in Pico's config.
* *
* @see AbstractPicoPlugin::$enabled * @see AbstractPicoPlugin::$enabled
* @var boolean * @var bool|null
*/ */
protected $enabled = false; protected $enabled = false;
/** /**
* This plugin depends on ... * This plugin depends on ...
* *
* If your plugin doesn't depend on any other plugin, remove this class
* property.
*
* @see AbstractPicoPlugin::$dependsOn * @see AbstractPicoPlugin::$dependsOn
* @var string[] * @var string[]
*/ */
@ -35,12 +74,31 @@ final class DummyPlugin extends AbstractPicoPlugin
* This event is triggered nevertheless the plugin is enabled or not. * This event is triggered nevertheless the plugin is enabled or not.
* It is NOT guaranteed that plugin dependencies are fulfilled! * It is NOT guaranteed that plugin dependencies are fulfilled!
* *
* @see Pico::getPlugin() * @see Pico::loadPlugin()
* @see Pico::getPlugins() * @see Pico::getPlugin()
* @param object[] &$plugins loaded plugin instances * @see Pico::getPlugins()
*
* @param object[] $plugins loaded plugin instances
*
* @return void * @return void
*/ */
public function onPluginsLoaded(array &$plugins) public function onPluginsLoaded(array $plugins)
{
// your code
}
/**
* Triggered when Pico manually loads a plugin
*
* @see Pico::loadPlugin()
* @see Pico::getPlugin()
* @see Pico::getPlugins()
*
* @param object $plugin loaded plugin instance
*
* @return void
*/
public function onPluginManuallyLoaded($plugin)
{ {
// your code // your code
} }
@ -48,8 +106,13 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has read its configuration * Triggered after Pico has read its configuration
* *
* @see Pico::getConfig() * @see Pico::getConfig()
* @param array &$config array of config variables * @see Pico::getBaseUrl()
* @see Pico::getBaseThemeUrl()
* @see Pico::isUrlRewritingEnabled()
*
* @param array &$config array of config variables
*
* @return void * @return void
*/ */
public function onConfigLoaded(array &$config) public function onConfigLoaded(array &$config)
@ -60,8 +123,10 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has evaluated the request URL * Triggered after Pico has evaluated the request URL
* *
* @see Pico::getRequestUrl() * @see Pico::getRequestUrl()
* @param string &$url part of the URL describing the requested contents *
* @param string &$url part of the URL describing the requested contents
*
* @return void * @return void
*/ */
public function onRequestUrl(&$url) public function onRequestUrl(&$url)
@ -72,9 +137,11 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has discovered the content file to serve * Triggered after Pico has discovered the content file to serve
* *
* @see Pico::getBaseUrl() * @see Pico::resolveFilePath()
* @see Pico::getRequestFile() * @see Pico::getRequestFile()
* @param string &$file absolute path to the content file to serve *
* @param string &$file absolute path to the content file to serve
*
* @return void * @return void
*/ */
public function onRequestFile(&$file) public function onRequestFile(&$file)
@ -85,24 +152,12 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico reads the contents of the file to serve * Triggered before Pico reads the contents of the file to serve
* *
* @see Pico::loadFileContent() * @see Pico::loadFileContent()
* @see DummyPlugin::onContentLoaded() * @see DummyPlugin::onContentLoaded()
* @param string &$file path to the file which contents will be read
* @return void
*/
public function onContentLoading(&$file)
{
// your code
}
/**
* Triggered after Pico has read the contents of the file to serve
* *
* @see Pico::getRawContent()
* @param string &$rawContent raw file contents
* @return void * @return void
*/ */
public function onContentLoaded(&$rawContent) public function onContentLoading()
{ {
// your code // your code
} }
@ -110,12 +165,12 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico reads the contents of a 404 file * Triggered before Pico reads the contents of a 404 file
* *
* @see Pico::load404Content() * @see Pico::load404Content()
* @see DummyPlugin::on404ContentLoaded() * @see DummyPlugin::on404ContentLoaded()
* @param string &$file path to the file which contents were requested *
* @return void * @return void
*/ */
public function on404ContentLoading(&$file) public function on404ContentLoading()
{ {
// your code // your code
} }
@ -123,8 +178,12 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has read the contents of the 404 file * Triggered after Pico has read the contents of the 404 file
* *
* @see Pico::getRawContent() * @see DummyPlugin::on404ContentLoading()
* @param string &$rawContent raw file contents * @see Pico::getRawContent()
* @see Pico::is404Content()
*
* @param string &$rawContent raw file contents
*
* @return void * @return void
*/ */
public function on404ContentLoaded(&$rawContent) public function on404ContentLoaded(&$rawContent)
@ -133,15 +192,21 @@ final class DummyPlugin extends AbstractPicoPlugin
} }
/** /**
* Triggered when Pico reads its known meta header fields * Triggered after Pico has read the contents of the file to serve
*
* If Pico serves a 404 file, this event is triggered with the raw contents
* of said 404 file. Use {@see Pico::is404Content()} to check for this
* case when necessary.
*
* @see DummyPlugin::onContentLoading()
* @see Pico::getRawContent()
* @see Pico::is404Content()
*
* @param string &$rawContent raw file contents
* *
* @see Pico::getMetaHeaders()
* @param string[] &$headers list of known meta header
* fields; the array value specifies the YAML key to search for, the
* array key is later used to access the found value
* @return void * @return void
*/ */
public function onMetaHeaders(array &$headers) public function onContentLoaded(&$rawContent)
{ {
// your code // your code
} }
@ -149,13 +214,12 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico parses the meta header * Triggered before Pico parses the meta header
* *
* @see Pico::parseFileMeta() * @see Pico::parseFileMeta()
* @see DummyPlugin::onMetaParsed() * @see DummyPlugin::onMetaParsed()
* @param string &$rawContent raw file contents *
* @param string[] &$headers known meta header fields
* @return void * @return void
*/ */
public function onMetaParsing(&$rawContent, array &$headers) public function onMetaParsing()
{ {
// your code // your code
} }
@ -163,8 +227,11 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has parsed the meta header * Triggered after Pico has parsed the meta header
* *
* @see Pico::getFileMeta() * @see DummyPlugin::onMetaParsing()
* @param string[] &$meta parsed meta data * @see Pico::getFileMeta()
*
* @param string[] &$meta parsed meta data
*
* @return void * @return void
*/ */
public function onMetaParsed(array &$meta) public function onMetaParsed(array &$meta)
@ -175,13 +242,14 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico parses the pages content * Triggered before Pico parses the pages content
* *
* @see Pico::prepareFileContent() * @see Pico::prepareFileContent()
* @see DummyPlugin::prepareFileContent() * @see Pico::substituteFileContent()
* @see DummyPlugin::onContentParsed() * @see DummyPlugin::onContentPrepared()
* @param string &$rawContent raw file contents * @see DummyPlugin::onContentParsed()
*
* @return void * @return void
*/ */
public function onContentParsing(&$rawContent) public function onContentParsing()
{ {
// your code // your code
} }
@ -189,12 +257,15 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has prepared the raw file contents for parsing * Triggered after Pico has prepared the raw file contents for parsing
* *
* @see Pico::parseFileContent() * @see DummyPlugin::onContentParsing()
* @see DummyPlugin::onContentParsed() * @see Pico::parseFileContent()
* @param string &$content prepared file contents for parsing * @see DummyPlugin::onContentParsed()
*
* @param string &$markdown Markdown contents of the requested page
*
* @return void * @return void
*/ */
public function onContentPrepared(&$content) public function onContentPrepared(&$markdown)
{ {
// your code // your code
} }
@ -202,8 +273,12 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has parsed the contents of the file to serve * Triggered after Pico has parsed the contents of the file to serve
* *
* @see Pico::getFileContent() * @see DummyPlugin::onContentParsing()
* @param string &$content parsed contents * @see DummyPlugin::onContentPrepared()
* @see Pico::getFileContent()
*
* @param string &$content parsed contents (HTML) of the requested page
*
* @return void * @return void
*/ */
public function onContentParsed(&$content) public function onContentParsed(&$content)
@ -214,9 +289,9 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico reads all known pages * Triggered before Pico reads all known pages
* *
* @see Pico::readPages() * @see DummyPlugin::onPagesDiscovered()
* @see DummyPlugin::onSinglePageLoaded() * @see DummyPlugin::onPagesLoaded()
* @see DummyPlugin::onPagesLoaded() *
* @return void * @return void
*/ */
public function onPagesLoading() public function onPagesLoading()
@ -225,25 +300,59 @@ final class DummyPlugin extends AbstractPicoPlugin
} }
/** /**
* Triggered when Pico reads a single page from the list of all known pages * Triggered before Pico loads a single page
* *
* The `$pageData` parameter consists of the following values: * Set the `$skipFile` parameter to TRUE to remove this page from the pages
* array. Pico usually passes NULL by default, unless it is a conflicting
* page (i.e. `content/sub.md`, but there's also a `content/sub/index.md`),
* then it passes TRUE. Don't change this value incautiously if it isn't
* NULL! Someone likely set it to TRUE or FALSE on purpose...
* *
* | Array key | Type | Description | * @see DummyPlugin::onSinglePageContent()
* | -------------- | ------ | ---------------------------------------- | * @see DummyPlugin::onSinglePageLoaded()
* | id | string | relative path to the content file | *
* | url | string | URL to the page | * @param string $id relative path to the content file
* | title | string | title of the page (YAML header) | * @param bool|null $skipPage set this to TRUE to remove this page from the
* | description | string | description of the page (YAML header) | * pages array, otherwise leave it unchanged
* | author | string | author of the page (YAML header) | *
* | time | string | timestamp derived from the Date header | * @return void
* | date | string | date of the page (YAML header) | */
* | date_formatted | string | formatted date of the page | public function onSinglePageLoading($id, &$skipPage)
* | raw_content | string | raw, not yet parsed contents of the page | {
* | meta | string | parsed meta data of the page | // your code
}
/**
* Triggered when Pico loads the raw contents of a single page
*
* Please note that this event isn't triggered when the currently processed
* page is the requested page. The reason for this exception is that the
* raw contents of this page were loaded already.
*
* @see DummyPlugin::onSinglePageLoading()
* @see DummyPlugin::onSinglePageLoaded()
*
* @param string $id relative path to the content file
* @param string &$rawContent raw file contents
*
* @return void
*/
public function onSinglePageContent($id, &$rawContent)
{
// your code
}
/**
* Triggered when Pico loads a single page
*
* Please refer to {@see Pico::readPages()} for information about the
* structure of a single page's data.
*
* @see DummyPlugin::onSinglePageLoading()
* @see DummyPlugin::onSinglePageContent()
*
* @param array &$pageData data of the loaded page
* *
* @see DummyPlugin::onPagesLoaded()
* @param array &$pageData data of the loaded page
* @return void * @return void
*/ */
public function onSinglePageLoaded(array &$pageData) public function onSinglePageLoaded(array &$pageData)
@ -252,23 +361,64 @@ final class DummyPlugin extends AbstractPicoPlugin
} }
/** /**
* Triggered after Pico has read all known pages * Triggered after Pico has discovered all known pages
* *
* See {@link DummyPlugin::onSinglePageLoaded()} for details about the * Pico's pages array isn't sorted until the `onPagesLoaded` event is
* structure of the page data. * triggered. Please refer to {@see Pico::readPages()} for information
* about the structure of Pico's pages array and the structure of a single
* page's data.
*
* @see DummyPlugin::onPagesLoading()
* @see DummyPlugin::onPagesLoaded()
*
* @param array[] &$pages list of all known pages
* *
* @see Pico::getPages()
* @see Pico::getCurrentPage()
* @see Pico::getPreviousPage()
* @see Pico::getNextPage()
* @param array[] &$pages data of all known pages
* @param array|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 * @return void
*/ */
public function onPagesLoaded( public function onPagesDiscovered(array &$pages)
array &$pages, {
// your code
}
/**
* Triggered after Pico has sorted the pages array
*
* Please refer to {@see Pico::readPages()} for information about the
* structure of Pico's pages array and the structure of a single page's
* data.
*
* @see DummyPlugin::onPagesLoading()
* @see DummyPlugin::onPagesDiscovered()
* @see Pico::getPages()
*
* @param array[] &$pages sorted list of all known pages
*
* @return void
*/
public function onPagesLoaded(array &$pages)
{
// your code
}
/**
* Triggered when Pico discovered the current, previous and next pages
*
* If Pico isn't serving a regular page, but a plugin's virtual page, there
* will neither be a current, nor previous or next pages. Please refer to
* {@see Pico::readPages()} for information about the structure of a single
* page's data.
*
* @see Pico::getCurrentPage()
* @see Pico::getPreviousPage()
* @see Pico::getNextPage()
*
* @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, array &$currentPage = null,
array &$previousPage = null, array &$previousPage = null,
array &$nextPage = null array &$nextPage = null
@ -277,11 +427,18 @@ final class DummyPlugin extends AbstractPicoPlugin
} }
/** /**
* Triggered before Pico registers the twig template engine * Triggered after Pico built the page tree
*
* Please refer to {@see Pico::buildPageTree()} for information about
* the structure of Pico's page tree array.
*
* @see Pico::getPageTree()
*
* @param array &$pageTree page tree
* *
* @return void * @return void
*/ */
public function onTwigRegistration() public function onPageTreeBuilt(array &$pageTree)
{ {
// your code // your code
} }
@ -289,14 +446,14 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered before Pico renders the page * Triggered before Pico renders the page
* *
* @see Pico::getTwig() * @see DummyPlugin::onPageRendered()
* @see DummyPlugin::onPageRendered() *
* @param Twig_Environment &$twig twig template engine * @param string &$templateName file name of the template
* @param array &$twigVariables template variables * @param array &$twigVariables template variables
* @param string &$templateName file name of the template *
* @return void * @return void
*/ */
public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName) public function onPageRendering(&$templateName, array &$twigVariables)
{ {
// your code // your code
} }
@ -304,11 +461,72 @@ final class DummyPlugin extends AbstractPicoPlugin
/** /**
* Triggered after Pico has rendered the page * Triggered after Pico has rendered the page
* *
* @param string &$output contents which will be sent to the user * @see DummyPlugin::onPageRendering()
*
* @param string &$output contents which will be sent to the user
*
* @return void * @return void
*/ */
public function onPageRendered(&$output) public function onPageRendered(&$output)
{ {
// your code // your code
} }
/**
* Triggered when Pico reads its known meta header fields
*
* @see Pico::getMetaHeaders()
*
* @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)
{
// your code
}
/**
* Triggered when Pico registers the YAML parser
*
* @see Pico::getYamlParser()
*
* @param \Symfony\Component\Yaml\Parser &$yamlParser YAML parser instance
*
* @return void
*/
public function onYamlParserRegistered(\Symfony\Component\Yaml\Parser &$yamlParser)
{
// your code
}
/**
* Triggered when Pico registers the Parsedown parser
*
* @see Pico::getParsedown()
*
* @param Parsedown &$parsedown Parsedown instance
*
* @return void
*/
public function onParsedownRegistered(Parsedown &$parsedown)
{
// your code
}
/**
* Triggered when Pico registers the twig template engine
*
* @see Pico::getTwig()
*
* @param Twig_Environment &$twig Twig instance
*
* @return void
*/
public function onTwigRegistered(Twig_Environment &$twig)
{
// your code
}
} }

2
themes/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# This directory is meant to be empty
*

View file

@ -1,22 +0,0 @@
Icon fonts generator
## Fontello
Copyright (C) 2011 by Vitaly Puzrin
Author: Vitaly Puzrin
License: The MIT License <https://github.com/fontello/fontello/blob/master/LICENSE>
Homepage: http://fontello.com/
Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Author: Dave Gandy
License: SIL OFL 1.1 <http://scripts.sil.org/OFL>
Homepage: http://fortawesome.github.com/Font-Awesome/

Binary file not shown.

View file

@ -1,14 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2017 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="chat" unicode="&#xe800;" d="M786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
<glyph glyph-name="octocat" unicode="&#xf09b;" d="M429 779q116 0 215-58t156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17q0 1 0 43t0 75q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 67-44 115 21 51-4 114-16 5-46-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-47 7q-25-63-5-114-44-48-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-21-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l6-13q7-21 24-34t37-17 39-3 31 1l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156 216 58z m-267-616q2 4-3 7-6 1-8-1-1-4 4-7 5-3 7 1z m18-19q4 3-1 9-6 5-9 2-4-3 1-9 5-6 9-2z m16-25q6 4 0 11-4 7-9 3-5-3 0-10t9-4z m24-23q4 4-2 10-7 7-11 2-5-5 2-11 6-6 11-1z m32-14q1 6-8 9-8 2-10-4t7-9q8-3 11 4z m35-3q0 7-10 6-9 0-9-6 0-7 10-6 9 0 9 6z m32 5q-1 7-10 5-9-1-8-8t10-4 8 7z" horiz-adv-x="857.1" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,59 +0,0 @@
@font-face {
font-family: 'fontello';
src: url('font/fontello.eot?13793670');
src: url('font/fontello.eot?13793670#iefix') format('embedded-opentype'),
url('font/fontello.woff2?13793670') format('woff2'),
url('font/fontello.woff?13793670') format('woff'),
url('font/fontello.ttf?13793670') format('truetype'),
url('font/fontello.svg?13793670#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?13793670#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-chat:before { content: '\e800'; } /* '' */
.icon-octocat:before { content: '\f09b'; } /* '' */

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8" />
<title>{% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }}</title>
{% if meta.description %}
<meta name="description" content="{{ meta.description|striptags }}">
{% endif %}{% if meta.robots %}
<meta name="robots" content="{{ meta.robots }}">
{% endif %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" type="text/css" />
<link rel="stylesheet" href="{{ theme_url }}/style.css" type="text/css" />
<link rel="stylesheet" href="{{ theme_url }}/fontello.css" type="text/css" />
<script src="{{ theme_url }}/scripts/modernizr-2.6.1.min.js"></script>
</head>
<body>
<header id="header">
<div class="inner clearfix">
<h1><a href="{{ "index"|link }}" id="logo">{{ site_title }}</a></h1>
<nav>
<ul>
{% for page in pages if page.title %}
<li{% if page.id == current_page.id %} class="active"{% endif %}>
<a href="{{ page.url }}">{{ page.title }}</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
</header>
<section id="content">
<div class="inner">
{{ content }}
</div>
</section>
<footer id="footer">
<div class="inner">
<div class="social">
{% for social in meta.social %}
<a href="{{ social.url }}" title="{{ social.title }}"><span class="icon-{{ social.icon }}"></span></a>
{% endfor %}
</div>
<a href="http://picocms.org/">Pico</a> was made by <a href="http://gilbert.pellegrom.me">Gilbert Pellegrom</a>
and is maintained by <a href="https://github.com/picocms/Pico/graphs/contributors">The Pico Community</a>.
Released under the <a href="https://github.com/picocms/Pico/blob/master/LICENSE.md">MIT license</a>.
</div>
</footer>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,332 +0,0 @@
/*=================================*/
/* Pico Default Theme
/* By: Gilbert Pellegrom
/* http: //dev7studios.com
/*=================================*/
/* Reset Styles
/*---------------------------------------------*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
color: black;
background: white;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
caption, th, td {
text-align: left;
font-weight: normal;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: "";
}
blockquote, q {
quotes: "" "";
}
/* HTML5 tags */
header, section, footer,
aside, nav, article, figure {
display: block;
}
/* hand cursor on clickable input elements */
label, input[type=button], input[type=submit], button {
cursor: pointer;
}
/* make buttons play nice in IE:
www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */
button {
width: auto;
overflow: visible;
}
/* Sharper Thumbnails */
img {
-ms-interpolation-mode: bicubic;
}
/* Input Styles
/*---------------------------------------------*/
input,
textarea,
select {
padding: 5px;
font: 400 1em Verdana, Sans-serif;
color: #666;
background: #fff;
border: 1px solid #999999;
margin: 0 0 1em 0;
}
input:focus,
textarea:focus,
select:focus {
color: #000;
background: #fff;
border: 1px solid #666666;
}
/* Main Styles
/*---------------------------------------------*/
html { height: 100%; }
body {
font: 14px/1.8em 'Open Sans', Helvetica, Arial, Helvetica, sans-serif;
color: #444;
background: #fff;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
height: 100%;
}
a, a:visited {
color: #2EAE9B;
text-decoration: none;
-webkit-transition: all 0.2s linear;
-moz-transition: all 0.2s linear;
-ms-transition: all 0.2s linear;
-o-transition: all 0.2s linear;
transition: all 0.2s linear;
}
a:hover, a:active {
color: #000;
text-decoration: none;
}
h1, h2, h3, h4, h5, h6 {
color: #000;
line-height: 1.2em;
margin-bottom: 0.6em;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.7em;
}
h3 {
font-size: 1.5em;
margin-top: 2em;
}
p, table, ol, ul, pre, blockquote, dl {
margin-bottom: 1em;
}
ol, ul {
padding-left: 30px;
}
b, strong {
font-weight: bold;
}
i, em {
font-style: italic;
}
u {
text-decoration: underline;
}
abbr, acronym {
cursor: help;
border-bottom: 0.1em dotted;
}
td, td img {
vertical-align: top;
}
td, th {
border: solid 1px #999;
padding: 0.25em 0.5em;
}
th {
font-weight: bold;
text-align: center;
background: #eee;
}
sub {
vertical-align: sub;
font-size: smaller;
}
sup {
vertical-align: super;
font-size: smaller;
}
code {
font-family: Courier, "Courier New", Monaco, Tahoma;
background: #eee;
color: #333;
padding: 0px 2px;
}
pre {
background: #eee;
padding: 20px;
overflow: auto;
}
blockquote {
font-style: italic;
margin-left: 15px;
padding-left: 10px;
border-left: 5px solid #dddddd;
}
dd {
margin-left: 2em;
}
/* Structure Styles
/*---------------------------------------------*/
.inner {
width: 100%;
max-width: 850px;
margin: 0 auto;
}
#header {
background: #2EAE9B;
padding: 60px 0;
margin-bottom: 80px;
color: #afe1da;
}
#header a {
color: #afe1da;
}
#header h1 a,
#header a:hover,
#header .active a {
color: #fff;
}
#header h1 {
font-weight: bold;
margin: 0;
float: left;
}
#header nav {
float: right;
list-style: none;
margin: 0;
padding: 0;
}
#header nav a {
font-weight: bold;
margin-left: 20px;
}
#header ul {
list-style: none;
}
#header li {
display: inline-block;
float: left;
}
#content { flex: 1 1 auto; }
#footer {
background: #707070;
padding: 60px 0;
margin-top: 80px;
color: #C0C0C0;
}
#footer .social {
float: right;
margin: 0 0 0.5em 1em;
font-size: 200%;
}
#footer a { color: #ddd; }
#footer a:hover { color: #fff; }
/* Misc Styles
/*---------------------------------------------*/
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}
/* Media Queries
/*---------------------------------------------*/
/* Small Devices, Tablets */
@media only screen and (max-width : 768px) {
.inner {
width: 85%;
}
.inner img {
width:100%;
}
#header {
margin-bottom: 40px;
padding: 30px 0;
}
#header h1 {
float: none;
}
#header nav {
float: none;
width: 100%;
}
#header nav ul {
padding: 0;
}
#header nav li {
display: block;
float: left;
width: 50%;
text-align: center;
}
#header nav li a {
display: block;
margin: 0;
padding: 10px 0;
}
#footer {
margin-top: 40px;
padding: 30px 0;
}
}