diff --git a/.gitignore b/.gitignore index 01cdfee..8e11af4 100644 --- a/.gitignore +++ b/.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 diff --git a/.travis.yml b/.travis.yml index 01b5c42..aa0af02 100644 --- a/.travis.yml +++ b/.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 - diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b920e9..d02dafe 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2aa4241..b849472 100644 --- a/CONTRIBUTING.md +++ b/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. +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. - +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. diff --git a/README.md b/README.md index 520d937..1f32a64 100644 --- a/README.md +++ b/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 diff --git a/build/deploy-phpdoc-branch.sh b/build/deploy-phpdoc-branch.sh new file mode 100755 index 0000000..f08063d --- /dev/null +++ b/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 diff --git a/build/deploy-phpdoc-release.sh b/build/deploy-phpdoc-release.sh new file mode 100755 index 0000000..721f3a4 --- /dev/null +++ b/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 diff --git a/build/deploy-phpdoc.sh b/build/deploy-phpdoc.sh new file mode 100755 index 0000000..26d06b8 --- /dev/null +++ b/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 diff --git a/build/generate-phpdoc.sh b/build/generate-phpdoc.sh new file mode 100755 index 0000000..c2fe334 --- /dev/null +++ b/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 diff --git a/composer.json b/composer.json index b0d75e1..5894970 100644 --- a/composer.json +++ b/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": { diff --git a/content-sample/index.md b/content-sample/index.md index 389a09a..218646a 100644 --- a/content-sample/index.md +++ b/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: !\[Image Title\](%base_url%/assets/image.png) + ### 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 diff --git a/index.php b/index.php index 40b811a..7e7cdf0 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,15 @@ -setConfig(array()); +//$pico->setConfig(array()); // run application echo $pico->run(); diff --git a/index.php.dist b/index.php.dist new file mode 100644 index 0000000..2186072 --- /dev/null +++ b/index.php.dist @@ -0,0 +1,20 @@ +run(); diff --git a/lib/Pico.php b/lib/Pico.php index 42454e2..334d792 100644 --- a/lib/Pico.php +++ b/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); } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..93b9ebb --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,39 @@ + + + + Pico's coding standards mainly base on the PHP-FIG PSR-2 standard, + but without the MissingNamespace sniff. + + + + ^build/ + ^vendor/ + *.min.js + + + + + + + + + + + + + + + + diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php index 53bfff2..5240160 100644 --- a/plugins/00-PicoDeprecated.php +++ b/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 = ''; diff --git a/plugins/01-PicoParsePagesContent.php b/plugins/01-PicoParsePagesContent.php index fd36f0a..db4ed10 100644 --- a/plugins/01-PicoParsePagesContent.php +++ b/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']); diff --git a/plugins/02-PicoExcerpt.php b/plugins/02-PicoExcerpt.php index 4ada382..7d0c449 100644 --- a/plugins/02-PicoExcerpt.php +++ b/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( diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 9ecd411..f8ebb01 100644 --- a/plugins/DummyPlugin.php +++ b/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 } diff --git a/themes/default/style.css b/themes/default/style.css index b1f3ca9..d26f65a 100644 --- a/themes/default/style.css +++ b/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; - } -} \ No newline at end of file +/*=================================*/ +/* 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; + } +}