Browse Source

Merge branch 'master' into feature/PicoTwigExtension

Conflicts:
	CHANGELOG.md
Daniel Rudolf 9 years ago
parent
commit
be46e19677

+ 15 - 11
.gitignore

@@ -10,23 +10,27 @@ desktop.ini
 .DS_Store
 ._*
 
+# Travis
+/build/phpdoc-*/
+/build/phpdoc-*.git/
+
 # Composer
-composer.lock
-composer.phar
-vendor/*
+/composer.lock
+/composer.phar
+/vendor
 
 # User config
-config/config.php
+/config/config.php
 
 # User themes
-themes/*
-!themes/default/*
+/themes/*
+!/themes/default
 
 # User plugins
-plugins/*
-!plugins/0?-*
-!plugins/1?-*
-!plugins/DummyPlugin.php
+/plugins/*
+!/plugins/0?-*
+!/plugins/1?-*
+!/plugins/DummyPlugin.php
 
 # User content
-content/*
+/content

+ 20 - 5
.travis.yml

@@ -8,12 +8,29 @@ php:
   - hhvm
   - nightly
 
+matrix:
+  allow_failures:
+    - php: nightly
+  fast-finish: true
+
+install:
+  - composer install
+
+before_script:
+  - export PATH="$TRAVIS_BUILD_DIR/build:$TRAVIS_BUILD_DIR/vendor/bin:$PATH"
+
 script:
-  - find . -type f -name '*.php' -print0 | xargs -0 -I file php -l file > /dev/null
+  - phpcs --standard=phpcs.xml "$TRAVIS_BUILD_DIR"
+
+after_success:
+  - deploy-phpdoc-branch.sh
 
 before_deploy:
-  - composer install
-  - tar -czf "pico-release-$TRAVIS_TAG.tar.gz" .htaccess README.md CHANGELOG.md CONTRIBUTING.md composer.json composer.lock LICENSE config content-sample lib plugins themes vendor index.php
+  - deploy-phpdoc-release.sh
+  - composer install --no-dev --optimize-autoloader
+  - find vendor/ -type d -path 'vendor/*/*/.git' -print0 | xargs -0 rm -rf
+  - mv index.php.dist index.php
+  - tar -czf "pico-release-$TRAVIS_TAG.tar.gz" README.md LICENSE CONTRIBUTING.md CHANGELOG.md composer.json composer.lock config content-sample lib plugins themes vendor .htaccess index.php
 
 deploy:
   provider: releases
@@ -21,9 +38,7 @@ deploy:
   file: pico-release-$TRAVIS_TAG.tar.gz
   skip_cleanup: true
   on:
-    repo: picocms/Pico
     tags: true
     php: 5.3
 
 sudo: false
-

+ 14 - 0
CHANGELOG.md

@@ -12,10 +12,24 @@ Released: -
         want to parse the contents of a page, use the `content` filter instead
 * [New] New `sort_by` filter to sort a array by a specified key or key path
 * [New] New `map` filter to get the values of the given key or key path
+* [New] New PHP version check in `index.php`
+* [Changed] Improve documentation
+* [Changed] Improve table styling in default theme
+* [Changed] Update composer version constraints; almost all dependencies will
+            have pending updates, run `composer update`
+* [Changed] Throw a RuntimeException when the `content` dir isn't accessible
 * [Changed] Reuse `ParsedownExtra` object; new `onParsedownRegistration` event
+* [Changed] `$config['rewrite_url']` is now always available
+* [Changed] `DummyPlugin` class is now final
+* [Changed] Various small improvements and changes...
+* [Fixed] `PicoDeprecated`: Sanitize `content_dir` and `base_url` options when
+          reading `config.php` in Picos root dir
 * [Fixed] Replace `urldecode()` (deprecated RFC 1738) with `rawurldecode()`
           (RFC 3986) in `Page::evaluateRequestUrl()`
 * [Fixed] #272: Encode URLs using `rawurlencode()` in `Pico::getPageUrl()`
+* [Fixed] #274: Prevent double slashes in `base_url`
+* [Fixed] #285: Make `index.php` work when installed as a composer dependency
+* [Fixed] #291: Force `Pico::$requestUrl` to have no leading/trailing slash
 ```
 
 ### Version 1.0.0-beta.1

+ 15 - 39
CONTRIBUTING.md

@@ -12,7 +12,9 @@ Issues
 
 If you want to report an *issue* with Pico's core, please create a new [Issue](https://github.com/picocms/Pico/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 on GitHub](https://github.com/picocms/Pico/issues/new), please make sure the problem wasn't reported yet using [GitHubs search engine](https://github.com/picocms/Pico/search?type=Issues). Please describe your issue as clear as possible and always include steps to reproduce the problem.
+Before creating a [new Issue on GitHub](https://github.com/picocms/Pico/issues/new), please make sure the problem wasn't reported yet using [GitHubs search engine](https://github.com/picocms/Pico/search?type=Issues).
+
+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 have you taken to resolve the problem by yourself (i.e. *your own troubleshooting*)?
 
 Contributing code
 -----------------
@@ -45,7 +47,7 @@ With this command you can specify a file or folder to limit which files it will
 
 ### Keep documentation 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 the 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/Pico/tree/gh-pages/_docs/) folder of the `gh-pages` branch (i.e. [Pico's website](http://picocms.org/docs.html)) 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 [Redcarpet](https://github.com/vmg/redcarpet) by yourself, otherwise please address the issues in your pull request message and we'll take care of it.
 
@@ -78,44 +80,18 @@ As soon as development reaches a point where feedback is appreciated, a pull req
 Build & Release process
 -----------------------
 
-This is work in progress. Please refer to [#268](https://github.com/picocms/Pico/issues/268) for details.
-
-<!--
-
-Defined below is a specification to which the Build and Release process of Pico should follow. We use `travis-ci` to automate the process, and each commit to `master` should be releasable.
-
-#### Commit phase
-- Commit changes
-- Create & Push Git tag
-- Trigger automatic build process...
-
-Example commit message:
-
-    Pico 1.0.1
-    * [New] ...
-    * [Changed] ...
-
-*Please submit pull-requests with a properly
-formatted commit message/SemVer increase to avoid the need for manual amendments.*
-
-#### Analysis phase
-- Run through `scrutinizer-ci`?
+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 and uploading them to GitHub. Please refer to [our `.travis.yml`](https://github.com/picocms/Pico/blob/master/.travis.yml) for details.
 
-#### Packaging phase
-- Run composer locally
-- Create a ZIP archive (so vendor/ is included)
-- Build documentation, output goes to a new folder in the `gh-pages` branch
+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:
 
-#### Release phase
-- Create new Git release at tag
-- Upload ZIP archive
-- Upload documentation to the `gh-pages` branch
-- Set Symlink for latest documentation (http://picocms.org/docs/latest)
-- Update release information on GitHub with:
-    - Release title (taken from changelog)
-    - Changelog
+```
+Version 1.0.0
 
-#### Announcements
-- Where to announce new Pico release?
+* [Security] ...
+* [New] ...
+* [Changed] ...
+* [Fixed] ...
+* [Removed] ...
+```
 
--->
+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.

+ 3 - 3
README.md

@@ -46,11 +46,11 @@ Upgrading Pico is very easy: You just have to replace all of Pico's files - that
 
 Pico follows [Semantic Versioning 2.0][SemVer] and uses version numbers like `MAJOR`.`MINOR`.`PATCH`. When we update...
 
-- 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.
+- 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`.
 
 - 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.
 
-- the `MAJOR` version (e.g. `1.0` to `2.0`), a appropriate upgrade tutorial will be provided.
+- 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 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].
 
@@ -115,6 +115,6 @@ You want to contribute to Pico? We really appreciate that! You can help make Pic
 [IssuesSearch]: https://github.com/picocms/Pico/search?type=Issues
 [PullRequests]: https://github.com/picocms/Pico/pulls
 [ContributionGuidelines]: https://github.com/picocms/Pico/blob/master/CONTRIBUTING.md
-[EditInlineDocs]: https://github.com/picocms/Pico/blob/master/content-sample/index.md
+[EditInlineDocs]: https://github.com/picocms/Pico/edit/master/content-sample/index.md
 [EditUserDocs]: https://github.com/picocms/Pico/tree/gh-pages/_docs
 [EditDevDocs]: https://github.com/picocms/Pico/tree/gh-pages/_plugin-dev

+ 28 - 0
build/deploy-phpdoc-branch.sh

@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+if [ "$TRAVIS_PHP_VERSION" != "5.3" ]; then
+    echo "Skipping phpDoc deployment because this is not on the required runtime"
+    exit
+fi
+
+if [[ ",$DEPLOY_PHPDOC_BRANCHES," != *,"$TRAVIS_BRANCH",* ]]; then
+    echo "Skipping phpDoc deployment because this branch ($TRAVIS_BRANCH) is not permitted to deploy"
+    exit
+fi
+
+if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
+    echo "Skipping phpDoc deployment because this pull request (#$TRAVIS_PULL_REQUEST) is not permitted to deploy"
+    exit
+fi
+
+PHPDOC_ID="${TRAVIS_BRANCH//\//_}"
+
+generate-phpdoc.sh \
+    "$TRAVIS_BUILD_DIR" "$TRAVIS_BUILD_DIR/build/phpdoc-$PHPDOC_ID" \
+    "Pico 1.0 API Documentation ($TRAVIS_BRANCH branch)"
+[ $? -eq 0 ] || exit 1
+
+deploy-phpdoc.sh \
+    "$TRAVIS_REPO_SLUG" "heads/$TRAVIS_BRANCH @ $TRAVIS_COMMIT" "$TRAVIS_BUILD_DIR/build/phpdoc-$PHPDOC_ID" \
+    "$TRAVIS_REPO_SLUG" "gh-pages" "phpDoc/$PHPDOC_ID"
+[ $? -eq 0 ] || exit 1

+ 15 - 0
build/deploy-phpdoc-release.sh

@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+[ "$DEPLOY_PHPDOC_RELEASES" == "true" ] || exit
+
+PHPDOC_ID="${TRAVIS_BRANCH//\//_}"
+
+generate-phpdoc.sh \
+    "$TRAVIS_BUILD_DIR" "$TRAVIS_BUILD_DIR/build/phpdoc-$PHPDOC_ID" \
+    "Pico 1.0 API Documentation ($TRAVIS_TAG)"
+[ $? -eq 0 ] || exit 1
+
+deploy-phpdoc.sh \
+    "$TRAVIS_REPO_SLUG" "tags/$TRAVIS_TAG" "$TRAVIS_BUILD_DIR/build/phpdoc-$PHPDOC_ID" \
+    "$TRAVIS_REPO_SLUG" "gh-pages" "phpDoc/$PHPDOC_ID"
+[ $? -eq 0 ] || exit 1

+ 109 - 0
build/deploy-phpdoc.sh

@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+set -e
+
+# base variables
+APP_NAME="$(basename "$0")"
+BASE_PWD="$PWD"
+
+# environment variables
+# GITHUB_OAUTH_TOKEN    GitHub authentication token, see https://github.com/settings/tokens
+
+# parameters
+SOURCE_REPO_SLUG="$1"       # source GitHub repo (e.g. picocms/Pico)
+SOURCE_REF="$2"             # source reference (either "[ref] @ [commit]" or "[ref]",
+                            #                   [ref] can be e.g. heads/master or tags/v1.0.0)
+SOURCE_DIR="$3"             # absolute source path
+TARGET_REPO_SLUG="$4"       # target GitHub repo (e.g. picocms/Pico)
+TARGET_BRANCH="$5"          # target branch (e.g. gh-pages)
+TARGET_DIR="$6"             # relative target path
+
+# print parameters
+echo "Deploying phpDocs..."
+printf 'SOURCE_REPO_SLUG="%s"\n' "$SOURCE_REPO_SLUG"
+printf 'SOURCE_REF="%s"\n' "$SOURCE_REF"
+printf 'SOURCE_DIR="%s"\n' "$SOURCE_DIR"
+printf 'TARGET_REPO_SLUG="%s"\n' "$TARGET_REPO_SLUG"
+printf 'TARGET_BRANCH="%s"\n' "$TARGET_BRANCH"
+printf 'TARGET_DIR="%s"\n' "$TARGET_DIR"
+echo
+
+# evaluate source reference
+if [[ "$SOURCE_REF" == *" @ "* ]]; then
+    SOURCE_REF_TYPE="commit"
+    SOURCE_REF_HEAD="${SOURCE_REF% @ *}"
+    SOURCE_REF_COMMIT="${SOURCE_REF##* @ }"
+
+    if ! git check-ref-format "$SOURCE_REF_HEAD" || ! git rev-parse --verify "$SOURCE_REF_COMMIT" > /dev/null; then
+        echo "FATAL: $APP_NAME source reference '$SOURCE_REF' is invalid" >&2
+        exit 1
+    fi
+elif git check-ref-format "$SOURCE_REF"; then
+    SOURCE_REF_TYPE="ref"
+else
+    echo "FATAL: $APP_NAME source reference '$SOURCE_REF' is invalid" >&2
+    exit 1
+fi
+
+# clone repo
+printf 'Cloning repo...\n'
+GIT_DIR="$SOURCE_DIR.git"
+git clone --branch="$TARGET_BRANCH" "https://github.com/$TARGET_REPO_SLUG.git" "$GIT_DIR"
+
+# setup git
+cd "$GIT_DIR"
+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
+
+# copy phpdoc
+printf '\nCopying phpDocs...\n'
+[ ! -d "$TARGET_DIR" ] || rm -rf "$TARGET_DIR"
+[ "${SOURCE_DIR:0:1}" == "/" ] || SOURCE_DIR="$BASE_PWD/$SOURCE_DIR"
+cp -R "$SOURCE_DIR" "$TARGET_DIR"
+
+# commit changes
+printf '\nCommiting changes...\n'
+git add --all "$TARGET_DIR"
+git commit --message="Update phpDocumentor class docs for $SOURCE_REF"
+
+# very simple 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 [ "$SOURCE_REF_TYPE" == "commit" ]; then
+    # load branch data via GitHub APIv3
+    printf '\nRetrieving latest commit...\n'
+    LATEST_COMMIT_URL="https://api.github.com/repos/$SOURCE_REPO_SLUG/git/refs/$SOURCE_REF_HEAD"
+    if [ -n "$GITHUB_OAUTH_TOKEN" ]; then
+        LATEST_COMMIT_RESPONSE="$(wget -O- --header="Authorization: token $GITHUB_OAUTH_TOKEN" "$LATEST_COMMIT_URL" 2> /dev/null)"
+    else
+        LATEST_COMMIT_RESPONSE="$(wget -O- "$LATEST_COMMIT_URL" 2> /dev/null)"
+    fi
+
+    # evaluate JSON response
+    LATEST_COMMIT="$(echo "$LATEST_COMMIT_RESPONSE" | php -r "
+        \$json = json_decode(stream_get_contents(STDIN), true);
+        if (\$json !== null) {
+            if (isset(\$json['ref']) && (\$json['ref'] === 'refs/$SOURCE_REF_HEAD')) {
+                if (isset(\$json['object']) && isset(\$json['object']['sha'])) {
+                    echo \$json['object']['sha'];
+                }
+            }
+        }
+    ")"
+
+    # compare source reference against the latest commit
+    if [ "$LATEST_COMMIT" != "$SOURCE_REF_COMMIT" ]; then
+        echo "WARNING: $APP_NAME source reference '$SOURCE_REF' doesn't match the latest commit '$LATEST_COMMIT'" >&2
+        exit 0
+    fi
+fi
+
+# push changes
+printf '\nPushing changes...\n'
+git push "https://github.com/$TARGET_REPO_SLUG.git" "$TARGET_BRANCH:$TARGET_BRANCH"
+
+echo

+ 24 - 0
build/generate-phpdoc.sh

@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+set -e
+
+# parameters
+PHPDOC_SOURCE_DIR="$1"
+PHPDOC_TARGET_DIR="$2"
+PHPDOC_TITLE="$3"
+
+# print parameters
+echo "Generating phpDocs..."
+printf 'PHPDOC_SOURCE_DIR="%s"\n' "$PHPDOC_SOURCE_DIR"
+printf 'PHPDOC_TARGET_DIR="%s"\n' "$PHPDOC_TARGET_DIR"
+printf 'PHPDOC_TITLE="%s"\n' "$PHPDOC_TITLE"
+echo
+
+# generate phpdoc
+phpdoc -d "$PHPDOC_SOURCE_DIR" \
+    -i "$PHPDOC_SOURCE_DIR/build/" \
+    -i "$PHPDOC_SOURCE_DIR/vendor/" \
+    -i "$PHPDOC_SOURCE_DIR/plugins/" -f "$PHPDOC_SOURCE_DIR/plugins/DummyPlugin.php" \
+    -t "$PHPDOC_TARGET_DIR" \
+    --title "$PHPDOC_TITLE"
+
+echo

+ 20 - 5
composer.json

@@ -2,20 +2,35 @@
     "name": "picocms/pico",
     "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.",
-    "keywords": ["cms"],
+    "keywords": ["flat-file","cms","php","twig","markdown"],
     "homepage": "http://picocms.org/",
     "license": "MIT",
     "authors": [
         {
             "name": "Gilbert Pellegrom",
-            "email": "gilbert@pellegrom.me"
+            "email": "gilbert@pellegrom.me",
+            "role": "Project Founder"
+        },
+        {
+            "name": "The Pico Community",
+            "homepage": "https://github.com/picocms/Pico/graphs/contributors",
+            "role": "Contributors"
         }
     ],
+    "support": {
+        "docs": "http://picocms.org/docs",
+        "issues": "https://github.com/picocms/Pico/issues",
+        "source": "https://github.com/picocms/Pico"
+    },
     "require": {
         "php": ">=5.3.6",
-        "twig/twig": "1.18.*",
-        "erusev/parsedown-extra": "0.7.*",
-        "symfony/yaml" : "2.3"
+        "twig/twig": "^1.18",
+        "erusev/parsedown-extra": "^0.7",
+        "symfony/yaml" : "^2.3"
+    },
+    "require-dev" : {
+        "phpdocumentor/phpdocumentor": "^2.8",
+        "squizlabs/php_codesniffer": "^2.4"
     },
     "autoload": {
         "psr-0": {

+ 9 - 2
content-sample/index.md

@@ -62,12 +62,18 @@ Instead of adding your own content to the `content-sample` folder, you should
 create your own `content` directory in Pico's root directory. You can then add
 and access your contents as described above.
 
+As a common practice, we recommend you to separate your contents and assets
+(like images, downloads etc.). We even deny access to your `content` directory
+by default. So if you want to use a asset (e.g. a image) in one of your content
+files, upload it to the (to be created) directory `assets` and use it as
+follows: <code>!\[Image Title\](&#37;base_url&#37;/assets/image.png)</code>
+
 ### Text File Markup
 
 Text files are marked up using [Markdown][]. They can also contain regular HTML.
 
-At the top of text files you can place a block comment and specify certain
-attributes of the page. For example:
+At the top of text files you can place a block comment and specify certain meta
+attributes of the page using [YAML][] (the "YAML header"). For example:
 
     ---
     Title: Welcome
@@ -290,6 +296,7 @@ setting `$config['rewrite_url'] = true;` in your `config/config.php`.
 For more help have a look at the Pico documentation at http://picocms.org/docs.
 
 [Markdown]: http://daringfireball.net/projects/markdown/syntax
+[YAML]: https://en.wikipedia.org/wiki/YAML
 [Twig]: http://twig.sensiolabs.org/documentation
 [WikiThemes]: https://github.com/picocms/Pico/wiki/Pico-Themes
 [WikiPlugins]: https://github.com/picocms/Pico/wiki/Pico-Plugins

+ 12 - 3
index.php

@@ -1,6 +1,15 @@
-<?php
+<?php // @codingStandardsIgnoreFile
+
 // load dependencies
-require_once(__DIR__ . '/vendor/autoload.php');
+if(is_file(__DIR__ . '/vendor/autoload.php')) {
+    // composer root package
+    require_once(__DIR__ . '/vendor/autoload.php');
+} elseif(is_file(__DIR__ . '/../../../vendor/autoload.php')) {
+    // composer dependency package
+    require_once(__DIR__ . '/../../../vendor/autoload.php');
+} else {
+    die("Cannot find `vendor/autoload.php`. Run `composer install`.");
+}
 
 // instance Pico
 $pico = new Pico(
@@ -11,7 +20,7 @@ $pico = new Pico(
 );
 
 // override configuration?
-// $pico->setConfig(array());
+//$pico->setConfig(array());
 
 // run application
 echo $pico->run();

+ 20 - 0
index.php.dist

@@ -0,0 +1,20 @@
+<?php // @codingStandardsIgnoreFile
+
+// check PHP version
+if (version_compare(PHP_VERSION, '5.3.6', '<')) {
+    die('Pico requires PHP 5.3.6 or above to run');
+}
+
+// load dependencies
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// instance Pico
+$pico = new Pico(
+    __DIR__,    // root dir
+    'config/',  // config dir
+    'plugins/', // plugins dir
+    'themes/'   // themes dir
+);
+
+// run application
+echo $pico->run();

+ 43 - 38
lib/Pico.php

@@ -266,7 +266,8 @@ class Pico
      * meta headers, processes Markdown, does Twig processing and returns
      * the rendered contents.
      *
-     * @return string rendered Pico contents
+     * @return string           rendered Pico contents
+     * @throws RuntimeException thrown when a not recoverable error occurs
      */
     public function run()
     {
@@ -281,6 +282,11 @@ class Pico
         $this->loadConfig();
         $this->triggerEvent('onConfigLoaded', array(&$this->config));
 
+        // check content dir
+        if (!is_dir($this->getConfig('content_dir'))) {
+            throw new RuntimeException('Invalid content directory "' . $this->getConfig('content_dir') . '"');
+        }
+
         // evaluate request url
         $this->evaluateRequestUrl();
         $this->triggerEvent('onRequestUrl', array(&$this->requestUrl));
@@ -446,6 +452,10 @@ class Pico
     protected function loadConfig()
     {
         $config = null;
+        if (file_exists($this->getConfigDir() . 'config.php')) {
+            require($this->getConfigDir() . 'config.php');
+        }
+
         $defaultConfig = array(
             'site_title' => 'Pico',
             'base_url' => '',
@@ -460,11 +470,6 @@ class Pico
             'timezone' => ''
         );
 
-        $configFile = $this->getConfigDir() . 'config.php';
-        if (file_exists($configFile)) {
-            require $configFile;
-        }
-
         $this->config = is_array($this->config) ? $this->config : array();
         $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig;
 
@@ -474,6 +479,10 @@ class Pico
             $this->config['base_url'] = rtrim($this->config['base_url'], '/') . '/';
         }
 
+        if ($this->config['rewrite_url'] === null) {
+            $this->config['rewrite_url'] = $this->isUrlRewritingEnabled();
+        }
+
         if (empty($this->config['content_dir'])) {
             // try to guess the content directory
             if (is_dir($this->getRootDir() . 'content')) {
@@ -558,9 +567,9 @@ class Pico
      *
      * We recommend you to use the `link` filter in templates to create
      * internal links, e.g. `{{ "sub/page"|link }}` is equivalent to
-     * `{{ base_url }}sub/page`. In content files you can still use the
-     * `%base_url%` variable; e.g. `%base_url%?sub/page` will be automatically
-     * replaced accordingly.
+     * `{{ base_url }}/sub/page` and `{{ base_url }}?sub/page`, depending on
+     * enabled URL rewriting. In content files you can use the `%base_url%`
+     * variable; e.g. `%base_url%?sub/page` will be replaced accordingly.
      *
      * @see    Pico::getRequestUrl()
      * @return void
@@ -578,6 +587,7 @@ class Pico
             $pathComponent = substr($pathComponent, 0, $pathComponentLength);
         }
         $this->requestUrl = (strpos($pathComponent, '=') === false) ? rawurldecode($pathComponent) : '';
+        $this->requestUrl = trim($this->requestUrl, '/');
     }
 
     /**
@@ -762,7 +772,7 @@ class Pico
                         $meta[$fieldId] = $meta[$fieldName];
                         unset($meta[$fieldName]);
                     }
-                } else {
+                } elseif (!isset($meta[$fieldId])) {
                     // guarantee array key existance
                     $meta[$fieldId] = '';
                 }
@@ -776,10 +786,7 @@ class Pico
             }
         } else {
             // guarantee array key existance
-            foreach ($headers as $id => $field) {
-                $meta[$id] = '';
-            }
-
+            $meta = array_fill_keys(array_keys($headers), '');
             $meta['time'] = $meta['date_formatted'] = '';
         }
 
@@ -929,7 +936,7 @@ class Pico
         $files = $this->getFiles($this->getConfig('content_dir'), $this->getConfig('content_ext'), Pico::SORT_NONE);
         foreach ($files as $i => $file) {
             // skip 404 page
-            if (basename($file) == '404' . $this->getConfig('content_ext')) {
+            if (basename($file) === '404' . $this->getConfig('content_ext')) {
                 unset($files[$i]);
                 continue;
             }
@@ -967,7 +974,7 @@ class Pico
                 'meta' => &$meta
             );
 
-            if ($file == $this->requestFile) {
+            if ($file === $this->requestFile) {
                 $page['content'] = &$this->content;
             }
 
@@ -996,10 +1003,10 @@ class Pico
             $bSortKey = (basename($b['id']) === 'index') ? dirname($b['id']) : $b['id'];
 
             $cmp = strcmp($aSortKey, $bSortKey);
-            return $cmp * (($order == 'desc') ? -1 : 1);
+            return $cmp * (($order === 'desc') ? -1 : 1);
         };
 
-        if ($this->getConfig('pages_order_by') == 'date') {
+        if ($this->getConfig('pages_order_by') === 'date') {
             // sort by date
             uasort($this->pages, function ($a, $b) use ($alphaSortClosure, $order) {
                 if (empty($a['time']) || empty($b['time'])) {
@@ -1013,7 +1020,7 @@ class Pico
                     return $alphaSortClosure($a, $b);
                 }
 
-                return $cmp * (($order == 'desc') ? 1 : -1);
+                return $cmp * (($order === 'desc') ? 1 : -1);
             });
         } else {
             // sort alphabetically
@@ -1026,7 +1033,7 @@ class Pico
      *
      * @see    Pico::readPages()
      * @see    Pico::sortPages()
-     * @return array|null the data of all pages
+     * @return array[]|null the data of all pages
      */
     public function getPages()
     {
@@ -1053,7 +1060,7 @@ class Pico
         if ($currentPageIndex !== false) {
             $this->currentPage = &$this->pages[$currentPageId];
 
-            if (($this->getConfig('order_by') == 'date') && ($this->getConfig('order') == 'desc')) {
+            if (($this->getConfig('order_by') === 'date') && ($this->getConfig('order') === 'desc')) {
                 $previousPageOffset = 1;
                 $nextPageOffset = -1;
             } else {
@@ -1179,7 +1186,7 @@ class Pico
             'prev_page' => $this->previousPage,
             'current_page' => $this->currentPage,
             'next_page' => $this->nextPage,
-            'is_front_page' => ($this->requestFile == $frontPage),
+            'is_front_page' => ($this->requestFile === $frontPage),
         );
     }
 
@@ -1195,19 +1202,18 @@ class Pico
             return $baseUrl;
         }
 
-        if (
-            (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
-            || ($_SERVER['SERVER_PORT'] == 443)
-            || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
-        ) {
+        $protocol = 'http';
+        if (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] !== 'off')) {
+            $protocol = 'https';
+        } elseif ($_SERVER['SERVER_PORT'] == 443) {
+            $protocol = 'https';
+        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) {
             $protocol = 'https';
-        } else {
-            $protocol = 'http';
         }
 
         $this->config['base_url'] =
             $protocol . "://" . $_SERVER['HTTP_HOST']
-            . dirname($_SERVER['SCRIPT_NAME']) . '/';
+            . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/') . '/';
 
         return $this->getConfig('base_url');
     }
@@ -1219,13 +1225,13 @@ class Pico
      */
     public function isUrlRewritingEnabled()
     {
-        if (($this->getConfig('rewrite_url') === null) && isset($_SERVER['PICO_URL_REWRITING'])) {
-            return (bool) $_SERVER['PICO_URL_REWRITING'];
-        } elseif ($this->getConfig('rewrite_url')) {
-            return true;
+        $urlRewritingEnabled = $this->getConfig('rewrite_url');
+        if ($urlRewritingEnabled !== null) {
+            return $urlRewritingEnabled;
         }
 
-        return false;
+        $this->config['rewrite_url'] = (isset($_SERVER['PICO_URL_REWRITING']) && $_SERVER['PICO_URL_REWRITING']);
+        return $this->getConfig('rewrite_url');
     }
 
     /**
@@ -1294,7 +1300,7 @@ class Pico
      * @param  string $path relative or absolute path
      * @return string       absolute path
      */
-    protected function getAbsolutePath($path)
+    public function getAbsolutePath($path)
     {
         if (substr($path, 0, 1) !== '/') {
             $path = $this->getRootDir() . $path;
@@ -1320,8 +1326,7 @@ class Pico
         if (!empty($this->plugins)) {
             foreach ($this->plugins as $plugin) {
                 // only trigger events for plugins that implement PicoPluginInterface
-                // deprecated events (plugins for Pico 0.9 and older) will be
-                // triggered by the `PicoPluginDeprecated` plugin
+                // deprecated events (plugins for Pico 0.9 and older) will be triggered by `PicoDeprecated`
                 if (is_a($plugin, 'PicoPluginInterface')) {
                     $plugin->handleEvent($eventName, $params);
                 }

+ 39 - 0
phpcs.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<ruleset name="Pico">
+    <description>
+        Pico's coding standards mainly base on the PHP-FIG PSR-2 standard,
+        but without the MissingNamespace sniff.
+    </description>
+
+    <!--
+        Exclude build/ and vendor/ dirs as well as minified JavaScript files
+    -->
+    <exclude-pattern type="relative">^build/</exclude-pattern>
+    <exclude-pattern type="relative">^vendor/</exclude-pattern>
+    <exclude-pattern>*.min.js</exclude-pattern>
+
+    <!--
+        Check files for PHP syntax errors
+    -->
+    <config name="php_path" value="php"/>
+    <rule ref="Generic.PHP.Syntax"/>
+
+    <!--
+        Never use deprecated functions,
+        as they will be removed in future PHP releases
+    -->
+    <rule ref="Generic.PHP.DeprecatedFunctions"/>
+
+    <!--
+        Warn about structures which affect performance negatively
+    -->
+    <rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
+
+    <!--
+        Pico follows PHP-FIG PSR-2 Coding Style,
+        but doesn't use formal namespaces for historic reasons
+    -->
+    <rule ref="PSR2">
+        <exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/>
+    </rule>
+</ruleset>

+ 26 - 16
plugins/00-PicoDeprecated.php

@@ -67,7 +67,7 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onPluginsLoaded()
      */
-    public function onPluginsLoaded(&$plugins)
+    public function onPluginsLoaded(array &$plugins)
     {
         if (!empty($plugins)) {
             foreach ($plugins as $plugin) {
@@ -107,19 +107,17 @@ class PicoDeprecated extends AbstractPicoPlugin
      * @see    PicoDeprecated::loadRootDirConfig()
      * @see    PicoDeprecated::enablePlugins()
      * @see    DummyPlugin::onConfigLoaded()
-     * @param  mixed[] &$realConfig array of config variables
+     * @param  mixed[] &$config array of config variables
      * @return void
      */
-    public function onConfigLoaded(&$realConfig)
+    public function onConfigLoaded(array &$config)
     {
-        global $config;
-
         $this->defineConstants();
-        $this->loadRootDirConfig($realConfig);
+        $this->loadRootDirConfig($config);
         $this->enablePlugins();
-        $config = &$realConfig;
+        $GLOBALS['config'] = &$config;
 
-        $this->triggerEvent('config_loaded', array(&$realConfig));
+        $this->triggerEvent('config_loaded', array(&$config));
     }
 
     /**
@@ -167,14 +165,22 @@ class PicoDeprecated extends AbstractPicoPlugin
      * @param  mixed[] &$realConfig array of config variables
      * @return void
      */
-    protected function loadRootDirConfig(&$realConfig)
+    protected function loadRootDirConfig(array &$realConfig)
     {
         if (file_exists($this->getRootDir() . 'config.php')) {
-            // config.php in Pico::$rootDir is deprecated; use Pico::$configDir instead
+            // 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;
             }
         }
@@ -276,7 +282,7 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onMetaHeaders()
      */
-    public function onMetaHeaders(&$headers)
+    public function onMetaHeaders(array &$headers)
     {
         $this->triggerEvent('before_read_file_meta', array(&$headers));
     }
@@ -286,7 +292,7 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onMetaParsed()
      */
-    public function onMetaParsed(&$meta)
+    public function onMetaParsed(array &$meta)
     {
         $this->triggerEvent('file_meta', array(&$meta));
     }
@@ -320,7 +326,7 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onSinglePageLoaded()
      */
-    public function onSinglePageLoaded(&$pageData)
+    public function onSinglePageLoaded(array &$pageData)
     {
         $this->triggerEvent('get_page_data', array(&$pageData, $pageData['meta']));
     }
@@ -336,8 +342,12 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onPagesLoaded()
      */
-    public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage)
-    {
+    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) {
@@ -383,7 +393,7 @@ class PicoDeprecated extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onPageRendering()
      */
-    public function onPageRendering(&$twig, &$twigVariables, &$templateName)
+    public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName)
     {
         // template name contains file extension since Pico 1.0
         $fileExtension = '';

+ 1 - 1
plugins/01-PicoParsePagesContent.php

@@ -30,7 +30,7 @@ class PicoParsePagesContent extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onSinglePageLoaded()
      */
-    public function onSinglePageLoaded(&$pageData)
+    public function onSinglePageLoaded(array &$pageData)
     {
         if (!isset($pageData['content'])) {
             $pageData['content'] = $this->prepareFileContent($pageData['raw_content'], $pageData['meta']);

+ 2 - 2
plugins/02-PicoExcerpt.php

@@ -40,7 +40,7 @@ class PicoExcerpt extends AbstractPicoPlugin
      *
      * @see DummyPlugin::onConfigLoaded()
      */
-    public function onConfigLoaded(&$config)
+    public function onConfigLoaded(array &$config)
     {
         if (!isset($config['excerpt_length'])) {
             $config['excerpt_length'] = 50;
@@ -53,7 +53,7 @@ class PicoExcerpt extends AbstractPicoPlugin
      * @see PicoExcerpt::createExcerpt()
      * @see DummyPlugin::onSinglePageLoaded()
      */
-    public function onSinglePageLoaded(&$pageData)
+    public function onSinglePageLoaded(array &$pageData)
     {
         if (!isset($pageData['excerpt'])) {
             $pageData['excerpt'] = $this->createExcerpt(

+ 18 - 14
plugins/DummyPlugin.php

@@ -11,7 +11,7 @@
  * @license http://opensource.org/licenses/MIT
  * @version 1.0
  */
-class DummyPlugin extends AbstractPicoPlugin
+final class DummyPlugin extends AbstractPicoPlugin
 {
     /**
      * This plugin is enabled by default?
@@ -40,7 +40,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  object[] &$plugins loaded plugin instances
      * @return void
      */
-    public function onPluginsLoaded(&$plugins)
+    public function onPluginsLoaded(array &$plugins)
     {
         // your code
     }
@@ -52,7 +52,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  mixed[] &$config array of config variables
      * @return void
      */
-    public function onConfigLoaded(&$config)
+    public function onConfigLoaded(array &$config)
     {
         // your code
     }
@@ -141,7 +141,7 @@ class DummyPlugin extends AbstractPicoPlugin
      *     array key is later used to access the found value
      * @return void
      */
-    public function onMetaHeaders(&$headers)
+    public function onMetaHeaders(array &$headers)
     {
         // your code
     }
@@ -155,7 +155,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  string[] &$headers    known meta header fields
      * @return void
      */
-    public function onMetaParsing(&$rawContent, &$headers)
+    public function onMetaParsing(&$rawContent, array &$headers)
     {
         // your code
     }
@@ -167,7 +167,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  string[] &$meta parsed meta data
      * @return void
      */
-    public function onMetaParsed(&$meta)
+    public function onMetaParsed(array &$meta)
     {
         // your code
     }
@@ -249,7 +249,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  array &$pageData data of the loaded page
      * @return void
      */
-    public function onSinglePageLoaded(&$pageData)
+    public function onSinglePageLoaded(array &$pageData)
     {
         // your code
     }
@@ -264,14 +264,18 @@ class DummyPlugin extends AbstractPicoPlugin
      * @see    Pico::getCurrentPage()
      * @see    Pico::getPreviousPage()
      * @see    Pico::getNextPage()
-     * @param  array &$pages        data of all known pages
-     * @param  array &$currentPage  data of the page being served
-     * @param  array &$previousPage data of the previous page
-     * @param  array &$nextPage     data of the next page
+     * @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
      */
-    public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage)
-    {
+    public function onPagesLoaded(
+        array &$pages,
+        array &$currentPage = null,
+        array &$previousPage = null,
+        array &$nextPage = null
+    ) {
         // your code
     }
 
@@ -295,7 +299,7 @@ class DummyPlugin extends AbstractPicoPlugin
      * @param  string           &$templateName  file name of the template
      * @return void
      */
-    public function onPageRendering(&$twig, &$twigVariables, &$templateName)
+    public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName)
     {
         // your code
     }

+ 367 - 359
themes/default/style.css

@@ -1,359 +1,367 @@
-/*=================================*/
-/* 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: separate;
-	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
-/*---------------------------------------------*/
-body {
-	font: 14px/1.8em 'Open Sans', Helvetica, Arial, Helvetica, sans-serif;
-	color: #444;
-	background: #fff;
-	-webkit-font-smoothing: antialiased;
-}
-
-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 {
-	margin-bottom: 1em;
-}
-
-ol, ul {
-	padding-left: 30px;
-	margin-bottom: 1em;
-}
-
-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;
-}
-
-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;
-	margin-bottom: 1em;
-	overflow: auto;
-}
-
-blockquote {
-	font-style: italic;
-	margin: 0 0 1em 15px;
-	padding-left: 10px;
-	border-left: 5px solid #dddddd;
-}
-
-/* Structure Styles
-/*---------------------------------------------*/
-.inner {
-	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 { color: #fff; }
-#header h1 { 
-	font-weight: bold;
-	margin: 0; 
-	float: left;
-}
-#header .menu-icon {
-	display: none;
-	width: 35px;
-	height: 35px;
-	background: #afe1da url(menu-icon.png) center;
-}
-#header nav {
-	float: right;
-	list-style: none;
-	margin: 0;
-	padding: 0;
-}
-#header nav a {
-	font-weight: bold;
-	margin-left: 20px;
-}
-#header a:hover#menu-icon {
-	background-color: #444;
-	border-radius: 4px 4px 0 0;
-}
-#header ul {
-	list-style: none;
-}
-#header li {
-	display: inline-block;
-	float: left;
-}
-#footer {
-	background: #707070;
-	padding: 60px 0;
-	margin-top: 80px;
-	color: #C0C0C0;
-}
-#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;
-	}
-	#header h1 a {
-		font-size:1em;
-	}
-	#header .menu-icon {
-		display:inline-block;
-	}
-	#header nav a { color: #000; }
-	#header nav a:hover { color: #afe1da; }
-	#header nav ul, nav:active ul { 
-		display: none;
-		position: absolute;
-		padding: 20px;
-		background: #fff;
-		border: 5px solid #444;
-		right: 2.7em;
-		top: 100px;
-		width: 80%;
-		border-radius: 4px 0 4px 4px;
-		z-index: 9999;
-	}
-	#header nav li {
-		text-align: center;
-		width: 100%;
-		padding: 10px 0;
-		margin: 0;
-	}
-	#header nav:hover ul {
-		display: block;
-	}
-	
-}
-
-/* Extra Small Devices, Phones */ 
-@media only screen and (max-width : 480px) {
-	
-	.inner {
-		width: 85%;
-	}
-	.inner img {
-		width:100%;
-	}
-	#header {
-		margin-bottom: 30px;
-	}
-	#header h1 a {
-		font-size:1em;
-	}
-	#header .menu-icon {
-		display:inline-block;
-	}
-	#header nav a { color: #000; }
-	#header nav a:hover { color: #afe1da; }
-	#header nav ul, nav:active ul { 
-		display: none;
-		position: absolute;
-		padding: 20px;
-		background: #fff;
-		border: 5px solid #444;
-		right: 0em;
-		top: 100px;
-		width: auto;
-		border-radius: 4px 0 4px 4px;
-	}
-	#header nav li {
-		text-align: center;
-		width: 100%;
-		padding: 10px 0;
-		margin: 0;
-	}
-	#header nav:hover ul {
-		display: block;
-	}
-}
+/*=================================*/
+/* 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
+/*---------------------------------------------*/
+body {
+    font: 14px/1.8em 'Open Sans', Helvetica, Arial, Helvetica, sans-serif;
+    color: #444;
+    background: #fff;
+    -webkit-font-smoothing: antialiased;
+}
+
+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 {
+    margin-bottom: 1em;
+}
+
+ol, ul {
+    padding-left: 30px;
+    margin-bottom: 1em;
+}
+
+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;
+    margin-bottom: 1em;
+    overflow: auto;
+}
+
+blockquote {
+    font-style: italic;
+    margin: 0 0 1em 15px;
+    padding-left: 10px;
+    border-left: 5px solid #dddddd;
+}
+
+/* Structure Styles
+/*---------------------------------------------*/
+.inner {
+    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 { color: #fff; }
+#header h1 {
+    font-weight: bold;
+    margin: 0;
+    float: left;
+}
+#header .menu-icon {
+    display: none;
+    width: 35px;
+    height: 35px;
+    background: #afe1da url(menu-icon.png) center;
+}
+#header nav {
+    float: right;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+#header nav a {
+    font-weight: bold;
+    margin-left: 20px;
+}
+#header a:hover#menu-icon {
+    background-color: #444;
+    border-radius: 4px 4px 0 0;
+}
+#header ul {
+    list-style: none;
+}
+#header li {
+    display: inline-block;
+    float: left;
+}
+#footer {
+    background: #707070;
+    padding: 60px 0;
+    margin-top: 80px;
+    color: #C0C0C0;
+}
+#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;
+    }
+    #header h1 a {
+        font-size:1em;
+    }
+    #header .menu-icon {
+        display:inline-block;
+    }
+    #header nav a { color: #000; }
+    #header nav a:hover { color: #afe1da; }
+    #header nav ul, nav:active ul {
+        display: none;
+        position: absolute;
+        padding: 20px;
+        background: #fff;
+        border: 5px solid #444;
+        right: 2.7em;
+        top: 100px;
+        width: 80%;
+        border-radius: 4px 0 4px 4px;
+        z-index: 9999;
+    }
+    #header nav li {
+        text-align: center;
+        width: 100%;
+        padding: 10px 0;
+        margin: 0;
+    }
+    #header nav:hover ul {
+        display: block;
+    }
+}
+
+/* Extra Small Devices, Phones */
+@media only screen and (max-width : 480px) {
+    .inner {
+        width: 85%;
+    }
+    .inner img {
+        width:100%;
+    }
+    #header {
+        margin-bottom: 30px;
+    }
+    #header h1 a {
+        font-size:1em;
+    }
+    #header .menu-icon {
+        display:inline-block;
+    }
+    #header nav a { color: #000; }
+    #header nav a:hover { color: #afe1da; }
+    #header nav ul, nav:active ul {
+        display: none;
+        position: absolute;
+        padding: 20px;
+        background: #fff;
+        border: 5px solid #444;
+        right: 0em;
+        top: 100px;
+        width: auto;
+        border-radius: 4px 0 4px 4px;
+    }
+    #header nav li {
+        text-align: center;
+        width: 100%;
+        padding: 10px 0;
+        margin: 0;
+    }
+    #header nav:hover ul {
+        display: block;
+    }
+}