beardyjay.co.uk

My main website source code, uses Hugo.
Log | Files | Refs

commit de51c4a4d2f9b13ca71e00e01ef6b6af0a76c3ce
parent 2e8ced7b3a9235bad86001f4785cf68b688f3a14
Author: Jay Scott <jay@jayscott.co.uk>
Date:   Fri,  4 Mar 2016 21:36:14 +0000

.

Diffstat:
DLICENSE.md | 23-----------------------
DREADME | 0
Dcomposer.json | 42------------------------------------------
Dconfig/config.php | 20--------------------
Dconfig/config.php.template | 58----------------------------------------------------------
Dcontent/100food-keto-shipping-packaging.md | 75---------------------------------------------------------------------------
Dcontent/404.md | 6------
Dcontent/about.md | 49-------------------------------------------------
Dcontent/continuous-integration-using-jenkins.md | 209-------------------------------------------------------------------------------
Dcontent/dotfiles-with-make.md | 40----------------------------------------
Dcontent/index.html | 0
Dcontent/lfcs-exam.md | 22----------------------
Dcontent/security.md | 39---------------------------------------
Dcontent/trawling-gliffy-for-sensitive-data.md | 80-------------------------------------------------------------------------------
Dcontent/zines/2600.md | 7-------
Dcontent/zines/index.md | 38--------------------------------------
Dcontent/zines/phuk.md | 7-------
Dcontent/zines/tap.md | 7-------
Ddeploy.sh | 5-----
Dfavicon.ico | 0
Dfiles/images/2600van.png | 0
Dfiles/security/Big-Lick-File-Manager.txt | 48------------------------------------------------
Dfiles/security/Big-Lick-Mailing-List.txt | 23-----------------------
Dfiles/security/Big-Lick-Website-Backup.txt | 50--------------------------------------------------
Dfiles/security/Caught in a Honeypot - The Analysis.pdf | 0
Dfiles/security/Million-Dollar-Text-Links-exploit.txt | 63---------------------------------------------------------------
Dfiles/security/PHP-SiteLock-exploit.txt | 73-------------------------------------------------------------------------
Dfiles/security/arcade-trade-script-exploit.txt | 68--------------------------------------------------------------------
Dfiles/security/aterr-exploits.txt | 101-------------------------------------------------------------------------------
Dfiles/security/filecopa-exploit.txt | 65-----------------------------------------------------------------
Dfiles/security/star-articles-exploit.txt | 66------------------------------------------------------------------
Dfiles/security/trawling gliffy for sensitive data.pdf | 0
Dindex.php | 26--------------------------
Dlib/AbstractPicoPlugin.php | 255-------------------------------------------------------------------------------
Dlib/Pico.php | 1363-------------------------------------------------------------------------------
Dlib/PicoPluginInterface.php | 102-------------------------------------------------------------------------------
Dlib/PicoTwigExtension.php | 238-------------------------------------------------------------------------------
Dlib/cache/.gitignore | 2--
Dlib/index.html | 0
Dlib/pico.php | 363-------------------------------------------------------------------------------
Dplugins/00-PicoDeprecated.php | 437-------------------------------------------------------------------------------
Dplugins/01-PicoParsePagesContent.php | 40----------------------------------------
Dplugins/02-PicoExcerpt.php | 81-------------------------------------------------------------------------------
Dplugins/DummyPlugin.php | 314-------------------------------------------------------------------------------
Dplugins/index.html | 0
Dplugins/pico_pages_images.php | 87-------------------------------------------------------------------------------
Dplugins/pico_plugin.php | 94-------------------------------------------------------------------------------
Dplugins/pico_sitemap.php | 90-------------------------------------------------------------------------------
Dserver | 3---
Dthemes/beardyjay/css/style.css | 199-------------------------------------------------------------------------------
Dthemes/beardyjay/img/logo-orig.png | 0
Dthemes/beardyjay/img/logo.png | 0
Dthemes/beardyjay/index.html | 72------------------------------------------------------------------------
Dthemes/beardyjay/scripts/modernizr-2.6.1.min.js | 5-----
Dthemes/index.html | 0
55 files changed, 0 insertions(+), 5055 deletions(-)

diff --git a/LICENSE.md b/LICENSE.md @@ -1,22 +0,0 @@ - Copyright (c) 2012 Dev7studios Ltd - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE.- \ No newline at end of file diff --git a/README b/README diff --git a/composer.json b/composer.json @@ -1,42 +0,0 @@ -{ - "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": ["flat-file","cms","php","twig","markdown"], - "homepage": "http://picocms.org/", - "license": "MIT", - "authors": [ - { - "name": "Gilbert Pellegrom", - "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" - }, - "require-dev" : { - "phpdocumentor/phpdocumentor": "^2.8", - "squizlabs/php_codesniffer": "^2.4" - }, - "autoload": { - "psr-0": { - "Pico": "lib/", - "PicoPluginInterface": "lib/", - "AbstractPicoPlugin": "lib/" - } - } -} diff --git a/config/config.php b/config/config.php @@ -1,20 +0,0 @@ -<?php - -$config['site_title'] = '.: beardyjay :.'; // Site title -$config['base_url'] = 'https://beardyjay.co.uk'; // Override base URL (e.g. http://example.com) -$config['theme'] = 'beardyjay'; // Set the theme (defaults to "default") -$config['date_format'] = 'jS M Y'; // Set the PHP date format -$config['twig_config'] = array( // Twig settings - 'cache' => false, // To enable Twig caching change this to CACHE_DIR - 'autoescape' => false, // Autoescape Twig vars - 'debug' => false // Enable Twig debug -); -$config['pages_order_by'] = 'date'; // Order pages by "alpha" or "date" -$config['pages_order'] = 'desc'; // Order pages "asc" or "desc" -$config['excerpt_length'] = 100; // The pages excerpt length (in words) -$config['content_dir'] = 'content/'; // Content directory. - -$config['images_path'] = 'files/'; - -date_default_timezone_set('UTC'); - diff --git a/config/config.php.template b/config/config.php.template @@ -1,58 +0,0 @@ -<?php -/** - * Pico configuration - * - * This is the configuration file for {@link Pico}. It comes loaded with the - * default values, which can be found in {@link Pico::getConfig()} (see - * {@path "lib/Pico.php"}). - * - * To override any of the default settings below, copy this file to - * {@path "config/config.php"}, uncomment the line, then make and - * save your changes. - * - * @author Gilbert Pellegrom - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ - -/* - * BASIC - */ -// $config['site_title'] = 'Pico'; // Site title -// $config['base_url'] = ''; // Override base URL (e.g. http://example.com) -// $config['rewrite_url'] = null; // A boolean indicating forced URL rewriting - -/* - * THEME - */ -// $config['theme'] = 'default'; // Set the theme (defaults to "default") -// $config['twig_config'] = array( // Twig settings -// 'cache' => false, // To enable Twig caching change this to a path to a writable directory -// 'autoescape' => false, // Auto-escape Twig vars -// 'debug' => false // Enable Twig debug -// ); - -/* - * CONTENT - */ -// $config['date_format'] = '%D %T'; // Set the PHP date format as described here: http://php.net/manual/en/function.strftime.php -// $config['pages_order_by'] = 'alpha'; // Order pages by "alpha" or "date" -// $config['pages_order'] = 'asc'; // Order pages "asc" or "desc" -// $config['content_dir'] = 'content-sample/'; // Content directory -// $config['content_ext'] = '.md'; // File extension of content files to serve - -/* - * TIMEZONE - */ -// $config['timezone'] = 'UTC'; // Timezone may be required by your php install - -/* - * PLUGINS - */ -// $config['DummyPlugin.enabled'] = false; // Force DummyPlugin to be disabled - -/* - * CUSTOM - */ -// $config['custom_setting'] = 'Hello'; // Can be accessed by {{ config.custom_setting }} in a theme diff --git a/content/100food-keto-shipping-packaging.md b/content/100food-keto-shipping-packaging.md @@ -1,75 +0,0 @@ -/* -date: 2015-04-26 -author: Jay Scott -title: 100%Food Keto - Delivery, Cost and Packaging -slug: vegan -*/ - -I am a vegan and also a type 1 diabetic so I wanted to find a soylent version that would cover the following: - - - Vegan - - Very low carb (Keto) - - Ships to the U.K - -I could only find one brand/version that matches this (outwith making my own) and that was the 100%Food Keto (If anyone else knows of a soylent that covers the above then please let me know). So this is my review of 100%FOOD Keto, this post, as you guessed :p, is just about the delivery side of things and then the product its self in a week once I have given it a good try. - -##### Delivery ##### - -I first sent a query to 100%Food on the 3rd April and after a few questions being answered I was sent the invoice via paypal which was paid on the 6th April. However, on the 11th April I noticed that package wasn't going any where so contacted 100%Food, they came back to me on the same day saying they will contact UPS. The reason I was given for the delay was: - -> seems that we faced a system failure - when the label was marked in the system as printed though actually have been not. No label - no parcel to go. ->It happens rarely. We've been changing the platform in order to escape this types mistakes as well. - -I am happy that this may of been an one off, as soon as the parcel was picked up it arrived in the UK quickly so thats a plus. - -Once in the UK it was a nightmare, they kept delivering it to the wrong address etc but again this had nothing to do with 100%FOOD and everything to do with how crap Parcelforce are. In general if you don't count the UPS mix up that delayed the package being sent then delivery to the UK should be 6-8 days. This is the complete delivery log: - - -Date | Note | Location ----------|----------|---------- -April 23, 2015 , 10:05 am | Addressee cannot be located | UNITED KINGDOM -April 22, 2015 , 6:26 pm | Attempted Delivery - Item being held | UNITED KINGDOM -April 22, 2015 , 10:20 am | Addressee cannot be located | UNITED KINGDOM -April 18, 2015 , 7:34 am | Payment of charges - Item being held | UNITED KINGDOM | -April 18, 2015 , 12:15 am | Arrival at Post Office | UNITED KINGDOM | -April 16, 2015 , 1:48 pm | Customs clearance processing complete | UNITED KINGDOM -April 16, 2015 , 9:42 am | Customs Clearance | UNITED KINGDOM -April 16, 2015 , 9:41 am | Customs Clearance | UNITED KINGDOM -April 16, 2015 , 9:36 am | Processed Through Sort Facility | UNITED KINGDOM -April 15, 2015 , 11:38 am | Departed | London, UNITED KINGDOM -April 14, 2015 , 4:40 pm | Departed | San Francisco, UNITED STATES -April 14, 2015 , 11:11 am | Arrived | San Francisco, UNITED STATES -April 14, 2015 , 3:36 am | Processed Through Sort Facility | ISC SAN FRANCISCO (USPS) -April 14, 2015 , 3:36 am | Arrived at Sort Facility | ISC SAN FRANCISCO (USPS) -April 13, 2015 , 6:26 pm | Departed Post Office | SARATOGA, CA 95070 -April 13, 2015 , 4:39 pm | Picked Up | SARATOGA, CA 95070 -April 6, 2015 , 11:45 pm | Shipping Label Created | KENOSHA, WI 53142 - -##### Packaging ##### - -Packaging was in a bit of a mess to say the least but the contents were fine, I suspect this was due to UK parcelforce however. - -**Some Images**: - - [Package as received](http://i.imgur.com/9YocXJ0.jpg) - - [Package contents](http://i.imgur.com/FiNd2Wb.jpg) - - [Keto Cake Nutrition](http://i.imgur.com/G7ZLdk0.png) - - [Keto Choco Nutrition](http://i.imgur.com/cw9ObIv.png) - -##### Cost ##### - -As expected the delivery and customs (VAT) make this quite expensive to do :) So not really a problem with 100%FOOD this is more to do with the fact its getting shipped to the UK. Just to note shipping 1 weeks worth of 100%FOOD is the exact same as 2 weeks as it fits into the same parcel, this is the only reason that I done 2 weeks. - -Item | USD | UK ------|-----|---- -100%FOOD Keto (1 week) | $85 | £57 | -100%FOOD Keto (1 week) | $85 | £57 | -Shipping to UK | $62 | £41 | -UK Customs Charge | $47 | £32 | -Total Paid | $279 | £187 | -Price per meal | $6.65 | £4.45 | - -The costs could be reduced further if you subscribe as postage will then be free and also getting 4 weeks at a time too. I plan on taking this 3 times a day for 5 days and will post a review on how I get on :) diff --git a/content/404.md b/content/404.md @@ -1,6 +0,0 @@ -/* -Title: Error 404 -Robots: noindex,nofollow -*/ - -Woops. Looks like this page doesn't exist. diff --git a/content/about.md b/content/about.md @@ -1,49 +0,0 @@ -/* -title: about -slug: about -*/ - -You just can't beat a bullet point list! - -### What I Like: - - - Archinux and Slackware - - DWM tiling manager - - Python, C and Bash - - VIM - - Ansible and Automation in general - - RTLSDR - -### What I Hate: - - - Bloat - - Desktops - - Apple - - Over-engineering - - -### In a nutshell: - - - Got a 386, installed Slackware. - - The world of hacking began. - - Left high school. - - Got into phones. - - Worked as a ASP developer - - Went to college. - - Played with a lot of crackmes - - Website named UK hacker book, [A Complete Hackers Handbook](http://www.amazon.co.uk/Complete-Hackers-Handbook-Everything-Hacking/dp/1842227246/) - - Wrote an exam paper for college, no joke. - - Got a HND: software development. - - Website removed from hacker book (lol) - - Went to Uni and got a Computer Science Degree. - - Created a honeypot network. - - Worked at an ISP as a linux sysadmin. - - Went Vegan. - - Got Married. - - Worked in a datacentre as as NOC/Linux admin. - - Learned automation chef/puppet etc. - - Switched over to a new job doing DevOps. - - First conference, LinuxCon. - - Tracked a hacker group with some aussie feds via my honeypot network. - - Tool I made named in a security book [Open Source Intelligence Techniques](http://www.amazon.co.uk/Open-Source-Intelligence-Techniques-Information/dp/149427535X). - - Wrote this stuff. diff --git a/content/continuous-integration-using-jenkins.md b/content/continuous-integration-using-jenkins.md @@ -1,209 +0,0 @@ -/*date: 2014-08-05 -author: Jay Scott -title: Continuous Integration using Jenkins -slug:sysadmin -*/ - -This post is a rough guide on getting up and running with Continuous Integration using [Jenkins](http://pkg.jenkins-ci.org/). - - * Build on commits to a git repo. - * Check for security issues with the code base using <span class="radius secondary label">[Brakeman](http://brakemanscanner.org/)</span>. - * Run the code base tests, in this case <span class="radius secondary label">[RSpec](http://rspec.info/)</span> tests. - * Deploy to <span class="radius secondary label">[Engineyard](https://www.engineyard.com/)</span>, AWS instances. - * Rollback if the deploy has failed. - -I will add a [Chef](http://www.opscode.com/chef/) cookbook and a [SaltStack](http://saltstack.com/) state to my [git](http://github.com/beardyjay/) to automate the -install - the way it should be! - -### Jenkins setup on CentOS/Redhat - -Installing Jenkins can be as easy as downloading the Jenkins WAR file and then running it from the command line. I prefer to use Jenkins -own repo as it allows you to update to the latest version with easy and in the case of redhat based distributions you get an INIT script too. - -To install Jenkins via YUM then run these commands as root or sudo under a user account and you will be good to go! - -<pre><code class="bash"> -sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo -sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key -sudo yum install jenkins -</code> -</pre> - -Now that Jenkins is installed running the init script via Service or directly will start -it up on localhost:8080 by default. - -<pre><code class="bash"> - service jenkins start -</code> -</pre> - -Also if you want Jenkins to start on boot then remember to add Jenkins to init 3, I -hope your not running a server under init 5. - -<pre><code class="bash"> - chkconfig jenkins on --level 3 -</code> -</pre> - -#### Nginx Configuration - -I normally like to setup Jenkins under a domain name to eliminate the need to -specify port numbers and for added control over the service. If you would like -to do this then install [Nginx](http://nginx.org/). - -<pre><code class="bash"> - yum install nginx -</code> -</pre> - -Copy the configuration to /etc/nginx/conf.d/mydomain.conf and restart nginx - -<pre><code class="bash"> - service nginx restart or - /etc/init.d/nginx restart -</code> -</pre> - -<pre><code class="bash"> - server { - listen 80; - server_name ci.mydomain.com; - root /var/run/jenkins/war/; - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - location @jenkins { - sendfile off; - proxy_pass http://127.0.0.1:8080; - proxy_redirect default; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_max_temp_file_size 0; - - #this is the maximum upload size - client_max_body_size 10m; - client_body_buffer_size 128k; - - proxy_connect_timeout 90; - proxy_send_timeout 90; - proxy_read_timeout 90; - proxy_buffer_size 4k; - proxy_buffers 4 32k; - proxy_busy_buffers_size 64k; - proxy_temp_file_write_size 64k; - } - - location / { - try_files $uri @jenkins; - } -} -</code> -</pre> - -### Jenkins Plugins - -To install Jenkins plug-ins from the main page go to Manage Jenkins -> Plugin Manager then -click available. From here you can search for the plug-ins listed below. - -On a side note, checkout the Chuck Norris and Green ball plug-ins. - -#### Jenkins Git Plugin - -The main plug-in that you will need is [this one](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin), it allows us to pull down from a Git repo. - -#### Brakeman Plugin - -[Brakeman plugin](https://wiki.jenkins-ci.org/display/JENKINS/Brakeman+Plugin) detects security vulnerabilities in RoR applications and as such is useful to run -against builds for production environments. To run Brakeman during a build add the following -line to your build step. - -<pre class="brush:bash gutter: false"> - brakeman -o brakeman-output.tabs -</pre> -This is included in my build script below. - -#### ThinBackup Plugin - -[ThinBackup Plugin](https://wiki.jenkins-ci.org/display/JENKINS/thinBackup) takes a backup of your current Jenkins Home in TAR GZ format, clearly very handy! Once you -have it installed then go to Manage Jenkins -> ThinBackup -> Settings and fill out the required -information. I suggest you use something like Rsnapshot to pull the backups offsite nightly. - -#### Dashboard View - -[Dashboard View](https://wiki.jenkins-ci.org/display/JENKINS/Dashboard+View) allows you to make a custom view that looks a lot snappier than the default. You can use this -to remove any un-needed information such as build times etc. - -#### Workspace Cleanup Plugin - -[I love this plugin](https://wiki.jenkins-ci.org/display/JENKINS/Workspace+Cleanup+Plugin), before each build it will clear out the workspace. Such a simple thing to -do but not clearing out the workspace has caused me a few headaches in the past! - -When you create a new build make sure you tick the box saying clear out workspace before build! - -### Jenkins Build Script - -Once I have created a new Job, selected a free-style project and then filled in the git -account information (remember the delete workspace checkbox). As a post-build action -select Publish Brakeman warnings and as the File output enter - -<pre class="brush:bash gutter: false"> -brakeman-output.tabs -</pre> - -Add the script below in as a Build step as a execute shell, it will do the following -steps: - - * Run a bundle install for and GEM updates. - * Create the DBs and migrate and changes. - * Run Brakeman - * Run the RSpec tests, if they fail Jenkins will fail the build. - * Deploy the code base to EngineYard - * Check the URL for a 200 response, if it fails rollback to the last good version - -You can easily modify this script to use Capistrano instead of the Engineyard Gem. The -results of the Brakeman scan will be shown on the Job main page. - -My Build Script: - -<pre><code class="bash"> - set +x - - ### CONFIG ### - - APPNAME='EngineYard Application Name' - EY_ENV='EngineYard Environment Name' - BRANCH='Git Branch' - URL='URL FOR SMOKE TEST' - - ### - - BUNDLE=~/.rbenv/shims/bundle - BRAKEMAN=~/.rbenv/shims/brakeman - EY_GEM=~/.rbenv/shims/ey - - cd "$WORKSPACE" - $BUNDLE install - $BUNDLE exec rake db:create db:migrate db:test:prepare - - $BRAKEMAN -o brakeman-output.tabs --no-progress --separate-models - - if ! $BUNDLE exec rspec; then - echo "RSpec Tests have failed" >&2 - else - $EY_GEM deploy --app $APPNAME --environment $EY_ENV --branch $BRANCH -m - fi - - if ! curl -s -w "%{http_code}" $URL -o /dev/null | grep 200; then - echo "Server returned a failed Response Code, Rolling back." - # Should be a production only thing I think, comment out if - # not needed! - $EY_GEM rollback --app $APPNAME --environment $EY_ENV - else - echo "Server returned a 200 Response Code." - fi - - set -x -</code> -</pre> diff --git a/content/dotfiles-with-make.md b/content/dotfiles-with-make.md @@ -1,40 +0,0 @@ -/* -date: 2015-03-16 -title: dotfiles with Make -slug: archlinux, slackware, linux -*/ -Everyone knows about pushing your dotfiles to [github][1]/[bitbucket][2] or whatever -version control provider you like best but not many people implement a easy way to -copy or link the dotfiles. Lots of people use [bloated][3] [gems][4] to manage the -linking or even write there own but to me these are just over engineered for my -needs. My solution is to use a tool that will be already on everyone's -machine [GNU/Make][6] - nice and simple! - -Below is a copy of my Makefile which I used to keep my desktop (archlinux) and - my laptop (Slackware) in sync. You can also find it on my [git account][5]. - -##### Makefile ##### - -<pre><code class="bash"> - -DOTFILES = $(shell pwd) - -all : linkfiles linkfolders linkmisc - -linkfiles:: bashrc vimrc xinitrc Xresources - for file in $^; do ln -fs $(DOTFILES)/$$file ${HOME}/.$$file; done - -linkfolders:: vim ncmpcpp - for folder in $^; do ln -fns $(DOTFILES)/$$folder ${HOME}/.$$folder; done - -linkmisc:: bin dwm - for folder in $^; do ln -fns $(DOTFILES)/$$folder ${HOME}/$$folder; done -</code> -</pre> - -[1]: http://github.com -[2]: http://bitbucket.com -[3]: https://rubygems.org/gems/dotfiles -[4]: https://github.com/technicalpickles/homesick -[5]: http://github.com/beardyjay/dotfiles -[6]: http://www.gnu.org/software/make/ diff --git a/content/index.html b/content/index.html diff --git a/content/lfcs-exam.md b/content/lfcs-exam.md @@ -1,22 +0,0 @@ -/* -date: 2015-06-01 -author: Jay Scott -title: LFCS - Passed! -slug: linux -*/ - -I recently decided that I should get some official certifications to backup the -experience that I have over the years working with Linux. I am not normally a -fan of certifications as anyone can really [brain dump][1] however I read about -the Linux Foundations exams, they require you to connect to a VM and preform a -series of objectives. - -This really appealed to me, it shows that the person taking the exam actually -knows what they are doing - as a plus its the home of Linus Torvolds himself -and also not distributions specific, you have a choice on what OS you sit the -exam with. - -I passed with a score of 93% - Result! - -[1]: http://whatis.techtarget.com/definition/brain-dump "Whats a Brain Dump" -[2]: http://www.linuxfoundation.org/ "The Linux Foundation" diff --git a/content/security.md b/content/security.md @@ -1,39 +0,0 @@ -/* -title: secuirty -slug: security -*/ - -Details of various security related projects I have done. - -Name | Description | Details | ----- | ----------- | ------- | -**Gliffy** | Trawling Gliffy for Sensitive Data | [Research][14] | -**Gliffy** | Vulnerability not disclosed to public | N/A | -**Commando.io** | Vulnerability not disclosed to public | [Acknowledgement][1] | -**Honeypot Network** | Results of my Honeypot Network | [White Paper][11] | -**domRecon DNS Enumeration** | Published Book containing tool I wrote | [Amazon Link][12] | -**VOIP Honeypot** | Source for VOIP honeypot network I created | [Github Repo][13] | -**Arcade Trade** | Insecure Cookie Handling | [Advisory][2] -**Aterr Forums** | User Disclosure, System Disclosure, Forum Admin Access | [Advisory][3] -**Big Lick File Manager** | System Files Download | [Advisory][4] -**Big Lick Mailing List** | System Files Download | [Advisory][5] -**Big Lick Website Backup** | System Files Download | [Advisory][6] -**FileCOPA FTP Server** | Buffer Overflow leading to shell access | [Advisory][7] -**Million Dollar Text Links** | Authentication Bypass | [Advisory][8] -**PHP SiteLock** | Insecure Cookie Handling | [Advisory][9] -**Star Articles** | Insecure Cookie Handling | [Advisory][10] - -[1]: https://commando.io/security.html -[2]: http://beardyjay.co.uk/files/security/arcade-trade-script-exploit.txt -[3]: http://beardyjay.co.uk/files/security/aterr-exploits.txt -[4]: http://beardyjay.co.uk/files/security/Big-Lick-File-Manager.txt -[5]: http://beardyjay.co.uk/files/security/Big-Lick-Mailing-List.txt -[6]: http://beardyjay.co.uk/files/security/Big-Lick-Website-Backup.txt -[7]: http://beardyjay.co.uk/files/security/filecopa-exploit.txt -[8]: http://beardyjay.co.uk/files/security/Million-Dollar-Text-Links-exploit.txt -[9]: http://beardyjay.co.uk/files/security/PHP-SiteLock-exploit.txt -[10]: http://beardyjay.co.uk/files/security/star-articles-exploit.txt -[11]: http://beardyjay.co.uk/files/security/Caught%20in%20a%20Honeypot%20-%20The%20Analysis.pdf -[12]: http://www.amazon.co.uk/Open-Source-Intelligence-Techniques-Information/dp/149427535X -[13]: https://github.com/beardyjay/projectvoip -[14]: http://beardyjay.co.uk/trawling-gliffy-for-sensitive-data diff --git a/content/trawling-gliffy-for-sensitive-data.md b/content/trawling-gliffy-for-sensitive-data.md @@ -1,80 +0,0 @@ -/* -date: 2015-08-03 -author: Jay Scott -title: Trawling Gliffy for Sensitive Data -slug: linux -*/ - - -Gliffy.com is a tool that allows you to draw various diagrams ranging from flowcharts to network diagrams. Gliffy has various tiers of membership, the one that we are interested in is the free tier - the limitation of this tier is that your diagrams are marked as read only to the public. - - -**The Issue** --------------------------- - -When you create a new diagram a unique identifier (ID) is assigned to that diagram, you would think that the ID would be randomly generated, however, this is not the case. All that Gliffy seem to do is increment the previously generated ID by 1, no matter if its a private or public diagram. - -If you come across a diagram which is private you get an "Unauthorized" message with a 401 HTTP status code. - -![enter image description here](http://i.imgur.com/nCkfRGm.png) - -Also, if the user has removed the diagram and you then try to access the ID, you will get an "Not Found" error and a 404 HTTP status code. - -![404 Not Found](http://i.imgur.com/Lqfx02d.png) - -Using these helpful error codes, it is a trivial process to [create a script](http://fpaste.org/251177/38639697/) to download any diagram that has not been set to private or hasn't been removed by the user. Relying on human error, and with the help of Gliffy's ID generation, let's see what we can find... - -**The Results** ------------------ - -After a I looked at a few random ID's, it seemed to be that any diagrams created with a 8XXXXXX ID were first created in late 2014 until 2015, so that's the range I've stuck with. After creating a bash script, running it over a 4 hour period I managed to find and download **3,252** public diagrams from a total of **26,000** ID's scanned. Initially I cherry picked a few diagrams and the results were eye opening, ranging from full network diagrams to user authentication processes containing username / passwords. - -**Example 1**: - -This redacted diagram showed a wealth of information such as: - - - Public IP Address - - Private IP Address - - Company Name - - Company Remote Locations - - Line Numbers (Ref) - -![Example 1](http://i.imgur.com/7vTNH1I.png) - -**Example 2**: - -I had to heavily redacted this particular diagram as it was one of most technically rich diagrams from the sample I downloaded. - - - Public IP Address - - Private IP Address - - Firewall Rules - - IPSec Tunnel Information - - Company branding - - Company Name - - Company Remote Locations - - -![Example 2](http://i.imgur.com/IK8xAXr.png) - - -**The Fix** -------- - -Don't use the free account for real world diagrams. - -Gliffy could help the situation by not making the IDs so linear. I did contact Gliffy to ask if they had any intention on fixing the way the IDs are generated to reduce this risk, I received this reply: - -> Hi Jay, -> -> Thanks for using Gliffy. We unfortunately do not have any current -> plans to change this. -> -> We have voted for it on your behalf in our public forum located here: -> http://support.gliffy.com/entries/20133138-Make-public-document-URLs-much-harder-to-guess-or-brute-force-attack -> -> We take these requests very seriously. The votes and comments these -> receive help us to gauge public interest and assist us in allocating -> resources for future development. -> - -So it appears that Gliffy have known about this issue since 2011 when it was first brought up on there forums. If they haven't "fixed" it in 4 years I suspect they never will... diff --git a/content/zines/2600.md b/content/zines/2600.md @@ -1,7 +0,0 @@ -/* -title: Zines - 2600 -slug: zines -tmp: <img style="float: left; margin: 0px 15px 15px 0px;" src="f" width="270" height="367"> -images: true -*/ - diff --git a/content/zines/index.md b/content/zines/index.md @@ -1,38 +0,0 @@ -/* -title: Zines -slug: zines -*/ - - -I **love** reading zines, new and old! A lot of them have articles that are copy -and pasted from other places (looking at you 2600!) but some of the content can -be really informative. - -I will try and upload the rare and better ones I have here for all to enjoy, learn -and laugh at :) - ------- - -<h1><a href="/zines/2600">2600 Hacker Quarterly</a></h1> - -Well where to you start... - -I kinda started off with 2600 and going to a few meetings a longgg time ago -but these days it just feels like a cash cow tbh - -The older version of the mag use to be quite good but fuck me its terrible -now, how to hack walmarts PA system... full of adverts and quite expensive -concidering you can now get it as a ebook from amazon etc. - - -<h1><a href="/zines/tap">TAP</a></h1> - -Pre 2600 magazine and originally called YIPL then renamed to TAP to refect -the fact it was now a tech heavy newletter. Really interesting to read. - -<h1><a href="/zines/phuk">PHUK</a></h1> - -Zine from the UK which seemed to die after the second issue, however it -was a great read. They even release the BT secuirty manual covering such -things as OBASS. Would be good to find out more about what happened with -this one! diff --git a/content/zines/phuk.md b/content/zines/phuk.md @@ -1,7 +0,0 @@ -/* -title: Zines - PHUK -slug: zines -tmp: <img style="float: left; margin: 0px 15px 15px 0px;" src="f" width="270" height="367"> -images: true -*/ - diff --git a/content/zines/tap.md b/content/zines/tap.md @@ -1,7 +0,0 @@ -/* -title: Zines - TAP -slug: zines -tmp: <img style="float: left; margin: 0px 15px 15px 0px;" src="f" width="270" height="367"> -images: true -*/ - diff --git a/deploy.sh b/deploy.sh @@ -1,5 +0,0 @@ -#!/bin/bash - -HOST="jay@beardyjay:/var/www/vhosts/beardyjay.co.uk" - -rsync -az --exclude 'files/zines' --exclude '.git' --size-only --force --progress -e "ssh -p22" ./ $HOST diff --git a/favicon.ico b/favicon.ico Binary files differ. diff --git a/files/images/2600van.png b/files/images/2600van.png Binary files differ. diff --git a/files/security/Big-Lick-File-Manager.txt b/files/security/Big-Lick-File-Manager.txt @@ -1,48 +0,0 @@ - - -Name Big Lick Media: FIle Manager -Severity High -Vendor www.biglickmedia.com -Authors jay@jayscott.co.uk -Date 10th Jan 2009 -Status Vendor has NOT been informed - - -DESCRIPTION - -Multiple vulnerabilities affect this web application. On a side note, BLM: File -Manager seems to using the code from the GPL licenced web app PHPFM -tsk tsk... These vulnerabilities should also be present on PHPFM but this -has been untested. The vulnerabilities work in both Multi-User and -Single-User versions. - - -EXPLOIT 1 - -View any file on the server when permissions allow the web server to open them: - -To view the applications configuration file and file manager source code: - -index.php?&path=&filename=./config.php&action=edit -index.php?&path=./inc/&filename=filebrowser.php&action=edit - -Change path variable to "path" and "filename" to the file you wish -to edit ( if permissions allow) or view. - - -EXPLOIT 2 - -Delete any file on the server with poorly configured permissions: - -Delete the index.php file: - -/index.php?&path=&filename=index.php&action=delete - -Change path variable to "path" and "filename" to the file you wish -to remove. - - -EXPLOIT 3 - -Creating a user, set the default folder to /etc for example and you can view the -contents of this folder. Not so good for shared hosting environment. diff --git a/files/security/Big-Lick-Mailing-List.txt b/files/security/Big-Lick-Mailing-List.txt @@ -1,23 +0,0 @@ - - -Name Big Lick Media: Mailing List -Severity High -Vendor www.biglickmedia.com -Authors jay@jayscott.co.uk -Date 10th Jan 2009 -Status Vendor has NOT been informed - - -DESCRIPTION - -Poor coding allows anyone to download a file on the host without -requiring authentication. - - -EXPLOIT - -Simply go to the following address in a web browser. Change the file -variable to the file you wish to download. - -<path to application>/dl.php?file=/etc/fstab - diff --git a/files/security/Big-Lick-Website-Backup.txt b/files/security/Big-Lick-Website-Backup.txt @@ -1,50 +0,0 @@ - - -Name Big Lick Media: Website Backup -Severity High -Vendor www.biglickmedia.com -Authors jay@jayscott.co.uk -Date 10th Jan 2009 -Status Vendor has NOT been informed - - -DESCRIPTION - -Poor coding allows anyone to download a file on the host without -requiring authentication. - - -EXPLOIT - -Simply go to the following address in a web browser. Change the file -variable to the file you wish to download. - -<path to application>/download.php?file=/etc/fstab - - -VULNERABLE CODE - -$filename = $_GET['file']; - -// required for IE, otherwise Content-disposition is ignored -if(ini_get('zlib.output_compression')) - ini_set('zlib.output_compression', 'Off'); - -$file_extension = strtolower(substr(strrchr($filename,"."),1)); - -switch( $file_extension ) -{ - case "gz": $ctype="application/x-gzip"; break; - case "zip": $ctype="application/zip"; break; - default: $ctype="application/download"; -} -header("Pragma: public"); // required -header("Expires: 0"); -header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); -header("Cache-Control: private",false); // required for certain browsers -header("Content-Type: $ctype"); -header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" ); -header("Content-Transfer-Encoding: binary"); -header("Content-Length: ".filesize($filename)); -readfile("$filename"); -exit(); diff --git a/files/security/Caught in a Honeypot - The Analysis.pdf b/files/security/Caught in a Honeypot - The Analysis.pdf Binary files differ. diff --git a/files/security/Million-Dollar-Text-Links-exploit.txt b/files/security/Million-Dollar-Text-Links-exploit.txt @@ -1,63 +0,0 @@ - - - Million Dollar Text Links - Authentication bypass - =========================== - - - - - APP SUMMARY - ____________ - - Now that the market is overcrowded with million dollar graphic - pages where the users get links back to their site, here is how - you can add your "twist" to encash the million dollar craze. - Use this script to generate adsense revenue, promote your - links, get backward links to your site or simply to manage your - link exchange. - - - - IMPACT - _______ - - Leads to full administration rights of the admin panel. - - - - VERSIONS - _________ - - Vulnerable systems: All versions - - Immune systems: None - - - - DESCRIPTION #1 - ______________ - - No authentication checks on the admin home page allows anyone to - just browse to the admin contol panel and bypass the login - procedure. - - - Proof of Concept: - -> http://www.kalptarudemos.com/demo/million/admin.home.php - - Fix: - -> None given. - - - - ADDITIONAL INFO - _______________ - - - Vendor URL - http://www.cmsnx.com/product.about.php?id=12 - Underlying OS - Linux (Any), UNIX (Any), Windows (Any) - Credit - Jay Scott <jay@jayscott.co.uk - Message History - Vendor Contacted. - No reply after 30 days - diff --git a/files/security/PHP-SiteLock-exploit.txt b/files/security/PHP-SiteLock-exploit.txt @@ -1,73 +0,0 @@ - - - PHP SiteLock - Insecure Cookie Handling - =========================== - - - - - SUMMARY - ________ - - PHP Site Lock: A highly secure website login script which has - features like User Authentication & Management, Website - Password Protection , protection of pdf , images , etc. - - - - IMPACT - _______ - - Leads to full administration rights of the admin panel. - - - - VERSIONS - _________ - - Vulnerable systems: All versions - - Immune systems: None - - - - DESCRIPTION #1 - ______________ - - Insecure cookie handling allows anyone to simply create a custom cookie - with the values below. This will allow full access to the admin panel. - - Name - user_type - Content - admin - Path - / - - Name - login_name - Content - admin - Path - / - - Name - login_id - Content - 0 - Path - / - - - Proof of Concept: - -> javascript:document.cookie="user_type=admin; path=/" - -> javascript:document.cookie="login_name=admin; path=/" - -> javascript:document.cookie="login_id=0; path=/" - - Fix: - -> None given. - - - - ADDITIONAL INFO - _______________ - - - Vendor URL - www.phpsitelock.com - Underlying OS - Linux (Any), UNIX (Any), Windows (Any) - Credit - Jay Scott <jay@jayscott.co.uk - Message History - Vendor Contacted. - No reply after 30 days - diff --git a/files/security/arcade-trade-script-exploit.txt b/files/security/arcade-trade-script-exploit.txt @@ -1,68 +0,0 @@ - - - Arcade Trade Script - Insecure Cookie Handling - =========================== - - - - - SUMMARY - ________ - - Arcade Trade Script is a full arcade site CMS (Content Management System) - with easy customization and advanced traffic trading system built in. - With ATS you will hardly ever have to FTP anything. Almost all files, - pages, and meta tags can be edited from the admin panel. ATS is extremely - easy to use and works for both regular arcades and full blown traffic - trading arcades. - - Please note that this issue has now been fixed! - - - - IMPACT - _______ - - Leads to full administration rights on the CMS admin panel. - - - - VERSIONS - _________ - - Vulnerable systems: ATS versions prior to 1.0 - - Immune systems: None - - - - DESCRIPTION #1 - ______________ - - Insecure cookie handling allows anyone to simply create a custom cookie - with the values below. This will allow full access to the admin panel. - - Name - adminLoggedIn - Content - true - Path - / - - - Proof of Concept: - -> javascript:document.cookie="adminLoggedIn=true; path=/" - - Fix: - -> None given. - - - - ADDITIONAL INFO - _______________ - - - Vendor URL - www.arcadetradescript.com - Underlying OS - Linux (Any), UNIX (Any), Windows (Any) - Credit - Jay Scott <jay@jayscott.co.uk - Message History - Vendor notifyied and problem fixed - the following day. - diff --git a/files/security/aterr-exploits.txt b/files/security/aterr-exploits.txt @@ -1,101 +0,0 @@ - -Aterr Forums Multiple Vulnerabilities - - - -SUMMARY --------- - -Aterr is a threaded forum system allowing registered visitors to express -their opinions, discuss topics, and debate with other visitors. A threaded -forum system differs from regular, flat forum systems in that once posted, -a thread can fork, allowing visitors to reply directly to other posts. aterr -also provides a customisable permissions system, the ability to nest forums, -and moderation tools. - - - -IMPACT -------- - -Can lead to Disclosure of system information, Disclosure of user information -and Modification of forum setup. - - - -VERSIONS ---------- - -Vulnerable systems: - * Aterr versions prior to 0.4 - -Immune systems: - * Aterr version 0.5 - - - -DESCRIPTION #1 - Modification of Forum Setup --------------- - -The file forums.php fails to check that an administrator has the correct -privileges to log into the admin panel and edit the forum setup such as -changing the logo, title etc. - - -Proof of Concept: - - www.yoursite.com/forums/forums.php?op=admin&sub=config - -Fix: - -Add the following too forums.php starting at line 1393 : - - 1393 : if (!permission::has_flag('forums', F_FORUM_EDIT)) - 1394 : { - 1395 : redirect('http://' . $config['domain_name'] . $config['install_path'] . forums::furl('admin')); - 1396 : } - - - -DESCRIPTION #2 - Disclosure of User Information --------------- - -Not filtering HTML of the Topic header allows XSS exploits to be added to -any forum post. - - -Proof of Concept: - -Enter the following as a topic header: - <script>alert(document.cookie); </script> - -FIX: - -None given, upgrade to new version. - - - -DESCRIPTION #3 - Disclosure of System Information --------------- - -No check is made to see if a vaild profile has been selected. When a invaild -profile has been requested the forum discloses full path information to the -user. - - -Proof of Concept: - - www.yoursite.com/forums/accounts.php?op=viewprofile&u= - -FIX: - -None given, upgrade to new version. - - -ADDITIONAL INFORMATION ------------------------ - -Vendor URL - http://chimaera.starglade.org -Underlying OS - Linux (Any), UNIX (Any), Windows (Any) -Credit - Jay Scott <jay@jayscott.co.uk -Message History - None diff --git a/files/security/filecopa-exploit.txt b/files/security/filecopa-exploit.txt @@ -1,65 +0,0 @@ - -FileCOPA FTP Server - - - -SUMMARY --------- - -FileCOPA takes the hard work out of running an FTP Server. The FileCOPA -FTP Server Software installs on any version of the Microsoft Windows -operating system with just a few clicks of the mouse and automatically -configures itself for anonymous operation. - - - -IMPACT -------- - -Can lead to Denial of Service Attack and remote system access. - - - -VERSIONS ---------- - -Vulnerable systems: - * Unknown version number. - * Version released 10/11/2005 - -Immune systems: - * Version released after 28/11/2005 - - - -DESCRIPTION ------------- - -FileCOPA fails to check the CWD buffer the length of the input in -the CMD FTP command. If you pass 1036 characters to CWD it will crash -the FTP server allowing no more connections to the service. - - -Proof of Concept: - - POC C code for a DOS attack and remote access exploit was given - to the vendor. The POC is not for public release. - - -Fix: - - Upgrade to latest version. - - - - -ADDITIONAL INFORMATION ------------------------ - -Vendor URL - http://www.filecopa.com/ -Underlying OS - Windows (Any) -Credit - Jay Scott <jay@jayscott.co.uk> - -History - 18/11/05 - Vendor Contacted - - 19/11/05 - Vendor Acknowledged - - 21/11/05 - New version released diff --git a/files/security/star-articles-exploit.txt b/files/security/star-articles-exploit.txt @@ -1,66 +0,0 @@ - - - Star Articles - Insecure Cookie Handling - =========================== - - - - - SUMMARY - ________ - - Ready to use article, news, joke, tutorial site script with - more features than you can think of . . . Manage a large - collection of articles, jokes , tutorials and anything else - for your niche and get features like automatic RSS - generation , easy contents syndication , automated link - exchange and everything else (Including inbuilt 13 POWERFUL - SEO TOOLS)that MAKES YOUR LIFE EASY. - - - IMPACT - _______ - - Leads to full administration rights on the CMS admin panel. - - - - VERSIONS - _________ - - Vulnerable systems: Versions prior to 5.0 - - Immune systems: None - - - - DESCRIPTION #1 - ______________ - - Insecure cookie handling allows anyone to simply create a custom cookie - with the values below. This will allow full access to the admin panel. - - Name - admin_user - Content - admin - Path - / - - - Proof of Concept: - -> javascript:document.cookie="admin_user=admin; path=/" - - Fix: - -> None given. - - - - ADDITIONAL INFO - _______________ - - - Vendor URL - www.stararticles.com - Underlying OS - Linux (Any), UNIX (Any), Windows (Any) - Credit - Jay Scott <jay@jayscott.co.uk - Message History - No response from vendor after - 30 days. - diff --git a/files/security/trawling gliffy for sensitive data.pdf b/files/security/trawling gliffy for sensitive data.pdf Binary files differ. diff --git a/index.php b/index.php @@ -1,26 +0,0 @@ -<?php // @codingStandardsIgnoreFile - -// load dependencies -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( - __DIR__, // root dir - 'config/', // config dir - 'plugins/', // plugins dir - 'themes/' // themes dir -); - -// override configuration? -//$pico->setConfig(array()); - -// run application -echo $pico->run(); diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php @@ -1,255 +0,0 @@ -<?php - -/** - * Abstract class to extend from when implementing a Pico plugin - * - * @see PicoPluginInterface - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -abstract class AbstractPicoPlugin implements PicoPluginInterface -{ - /** - * Current instance of Pico - * - * @see PicoPluginInterface::getPico() - * @var Pico - */ - private $pico; - - /** - * Boolean indicating if this plugin is enabled (true) or disabled (false) - * - * @see PicoPluginInterface::isEnabled() - * @see PicoPluginInterface::setEnabled() - * @var boolean - */ - protected $enabled = true; - - /** - * Boolean indicating if this plugin was ever enabled/disabled manually - * - * @see PicoPluginInterface::isStatusChanged() - * @var boolean - */ - protected $statusChanged = false; - - /** - * List of plugins which this plugin depends on - * - * @see AbstractPicoPlugin::checkDependencies() - * @see PicoPluginInterface::getDependencies() - * @var string[] - */ - protected $dependsOn = array(); - - /** - * List of plugin which depend on this plugin - * - * @see AbstractPicoPlugin::checkDependants() - * @see PicoPluginInterface::getDependants() - * @var object[] - */ - private $dependants; - - /** - * @see PicoPluginInterface::__construct() - */ - public function __construct(Pico $pico) - { - $this->pico = $pico; - } - - /** - * @see PicoPluginInterface::handleEvent() - */ - public function handleEvent($eventName, array $params) - { - // plugins can be enabled/disabled using the config - if ($eventName === 'onConfigLoaded') { - $pluginEnabled = $this->getConfig(get_called_class() . '.enabled'); - if ($pluginEnabled !== null) { - $this->setEnabled($pluginEnabled); - } else { - $pluginConfig = $this->getConfig(get_called_class()); - if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) { - $this->setEnabled($pluginConfig['enabled']); - } - } - } - - if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) { - if (method_exists($this, $eventName)) { - call_user_func_array(array($this, $eventName), $params); - } - } - } - - /** - * @see PicoPluginInterface::setEnabled() - */ - public function setEnabled($enabled, $recursive = true, $auto = false) - { - $this->statusChanged = (!$this->statusChanged) ? !$auto : true; - $this->enabled = (bool) $enabled; - - if ($enabled) { - $this->checkDependencies($recursive); - } else { - $this->checkDependants($recursive); - } - } - - /** - * @see PicoPluginInterface::isEnabled() - */ - public function isEnabled() - { - return $this->enabled; - } - - /** - * @see PicoPluginInterface::isStatusChanged() - */ - public function isStatusChanged() - { - return $this->statusChanged; - } - - /** - * @see PicoPluginInterface::getPico() - */ - public function getPico() - { - return $this->pico; - } - - /** - * Passes all not satisfiable method calls to Pico - * - * @see Pico - * @param string $methodName name of the method to call - * @param array $params parameters to pass - * @return mixed return value of the called method - */ - public function __call($methodName, array $params) - { - if (method_exists($this->getPico(), $methodName)) { - return call_user_func_array(array($this->getPico(), $methodName), $params); - } - - throw new BadMethodCallException( - 'Call to undefined method ' . get_class($this->getPico()) . '::' . $methodName . '() ' - . 'through ' . get_called_class() . '::__call()' - ); - } - - /** - * Enables all plugins which this plugin depends on - * - * @see PicoPluginInterface::getDependencies() - * @param boolean $recursive enable required plugins automatically - * @return void - * @throws RuntimeException thrown when a dependency fails - */ - protected function checkDependencies($recursive) - { - foreach ($this->getDependencies() as $pluginName) { - try { - $plugin = $this->getPlugin($pluginName); - } catch (RuntimeException $e) { - throw new RuntimeException( - "Unable to enable plugin '" . get_called_class() . "':" - . "Required plugin '" . $pluginName . "' not found" - ); - } - - // plugins which don't implement PicoPluginInterface are always enabled - if (is_a($plugin, 'PicoPluginInterface') && !$plugin->isEnabled()) { - if ($recursive) { - if (!$plugin->isStatusChanged()) { - $plugin->setEnabled(true, true, true); - } else { - throw new RuntimeException( - "Unable to enable plugin '" . get_called_class() . "':" - . "Required plugin '" . $pluginName . "' was disabled manually" - ); - } - } else { - throw new RuntimeException( - "Unable to enable plugin '" . get_called_class() . "':" - . "Required plugin '" . $pluginName . "' is disabled" - ); - } - } - } - } - - /** - * @see PicoPluginInterface::getDependencies() - */ - public function getDependencies() - { - return (array) $this->dependsOn; - } - - /** - * Disables all plugins which depend on this plugin - * - * @see PicoPluginInterface::getDependants() - * @param boolean $recursive disabled dependant plugins automatically - * @return void - * @throws RuntimeException thrown when a dependency fails - */ - protected function checkDependants($recursive) - { - $dependants = $this->getDependants(); - if (!empty($dependants)) { - if ($recursive) { - foreach ($this->getDependants() as $pluginName => $plugin) { - if ($plugin->isEnabled()) { - if (!$plugin->isStatusChanged()) { - $plugin->setEnabled(false, true, true); - } else { - throw new RuntimeException( - "Unable to disable plugin '" . get_called_class() . "': " - . "Required by manually enabled plugin '" . $pluginName . "'" - ); - } - } - } - } else { - $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' '; - $dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'"; - throw new RuntimeException( - "Unable to disable plugin '" . get_called_class() . "': " - . "Required by " . $dependantsList - ); - } - } - } - - /** - * @see PicoPluginInterface::getDependants() - */ - public function getDependants() - { - if ($this->dependants === null) { - $this->dependants = array(); - foreach ($this->getPlugins() as $pluginName => $plugin) { - // only plugins which implement PicoPluginInterface support dependencies - if (is_a($plugin, 'PicoPluginInterface')) { - $dependencies = $plugin->getDependencies(); - if (in_array(get_called_class(), $dependencies)) { - $this->dependants[$pluginName] = $plugin; - } - } - } - } - - return $this->dependants; - } -} diff --git a/lib/Pico.php b/lib/Pico.php @@ -1,1363 +0,0 @@ -<?php - -/** - * Pico - * - * Pico is a stupidly simple, blazing fast, flat file CMS. - * - Stupidly Simple: Pico makes creating and maintaining a - * website as simple as editing text files. - * - Blazing Fast: Pico is seriously lightweight and doesn't - * use a database, making it super fast. - * - No Database: Pico is a "flat file" CMS, meaning no - * database woes, no MySQL queries, nothing. - * - Markdown Formatting: Edit your website in your favourite - * text editor using simple Markdown formatting. - * - Twig Templates: Pico uses the Twig templating engine, - * for powerful and flexible themes. - * - Open Source: Pico is completely free and open source, - * released under the MIT license. - * See <http://picocms.org/> for more info. - * - * @author Gilbert Pellegrom - * @author Daniel Rudolf - * @link <http://picocms.org> - * @license The MIT License <http://opensource.org/licenses/MIT> - * @version 1.0 - */ -class Pico -{ - /** - * Sort files in alphabetical ascending order - * - * @see Pico::getFiles() - * @var int - */ - const SORT_ASC = 0; - - /** - * Sort files in alphabetical descending order - * - * @see Pico::getFiles() - * @var int - */ - const SORT_DESC = 1; - - /** - * Don't sort files - * - * @see Pico::getFiles() - * @var int - */ - const SORT_NONE = 2; - - /** - * Root directory of this Pico instance - * - * @see Pico::getRootDir() - * @var string - */ - protected $rootDir; - - /** - * Config directory of this Pico instance - * - * @see Pico::getConfigDir() - * @var string - */ - protected $configDir; - - /** - * Plugins directory of this Pico instance - * - * @see Pico::getPluginsDir() - * @var string - */ - protected $pluginsDir; - - /** - * Themes directory of this Pico instance - * - * @see Pico::getThemesDir() - * @var string - */ - protected $themesDir; - - /** - * Boolean indicating whether Pico started processing yet - * - * @var boolean - */ - protected $locked = false; - - /** - * List of loaded plugins - * - * @see Pico::getPlugins() - * @var object[]|null - */ - protected $plugins; - - /** - * Current configuration of this Pico instance - * - * @see Pico::getConfig() - * @var array|null - */ - protected $config; - - /** - * Part of the URL describing the requested contents - * - * @see Pico::getRequestUrl() - * @var string|null - */ - protected $requestUrl; - - /** - * Absolute path to the content file being served - * - * @see Pico::getRequestFile() - * @var string|null - */ - protected $requestFile; - - /** - * Raw, not yet parsed contents to serve - * - * @see Pico::getRawContent() - * @var string|null - */ - protected $rawContent; - - /** - * Meta data of the page to serve - * - * @see Pico::getFileMeta() - * @var array|null - */ - protected $meta; - - /** - * Parsedown Extra instance used for markdown parsing - * - * @see Pico::getParsedown() - * @var ParsedownExtra|null - */ - protected $parsedown; - - /** - * Parsed content being served - * - * @see Pico::getFileContent() - * @var string|null - */ - protected $content; - - /** - * List of known pages - * - * @see Pico::getPages() - * @var array[]|null - */ - protected $pages; - - /** - * Data of the page being served - * - * @see Pico::getCurrentPage() - * @var array|null - */ - protected $currentPage; - - /** - * Data of the previous page relative to the page being served - * - * @see Pico::getPreviousPage() - * @var array|null - */ - protected $previousPage; - - /** - * Data of the next page relative to the page being served - * - * @see Pico::getNextPage() - * @var array|null - */ - protected $nextPage; - - /** - * Twig instance used for template parsing - * - * @see Pico::getTwig() - * @var Twig_Environment|null - */ - protected $twig; - - /** - * Variables passed to the twig template - * - * @see Pico::getTwigVariables - * @var array|null - */ - protected $twigVariables; - - /** - * Constructs a new Pico instance - * - * To carry out all the processing in Pico, call {@link Pico::run()}. - * - * @param string $rootDir root directory of this Pico instance - * @param string $configDir config directory of this Pico instance - * @param string $pluginsDir plugins directory of this Pico instance - * @param string $themesDir themes directory of this Pico instance - */ - public function __construct($rootDir, $configDir, $pluginsDir, $themesDir) - { - $this->rootDir = rtrim($rootDir, '/\\') . '/'; - $this->configDir = $this->getAbsolutePath($configDir); - $this->pluginsDir = $this->getAbsolutePath($pluginsDir); - $this->themesDir = $this->getAbsolutePath($themesDir); - } - - /** - * Returns the root directory of this Pico instance - * - * @return string root directory path - */ - public function getRootDir() - { - return $this->rootDir; - } - - /** - * Returns the config directory of this Pico instance - * - * @return string config directory path - */ - public function getConfigDir() - { - return $this->configDir; - } - - /** - * Returns the plugins directory of this Pico instance - * - * @return string plugins directory path - */ - public function getPluginsDir() - { - return $this->pluginsDir; - } - - /** - * Returns the themes directory of this Pico instance - * - * @return string themes directory path - */ - public function getThemesDir() - { - return $this->themesDir; - } - - /** - * Runs this Pico instance - * - * Loads plugins, evaluates the config file, does URL routing, parses - * meta headers, processes Markdown, does Twig processing and returns - * the rendered contents. - * - * @return string rendered Pico contents - * @throws Exception thrown when a not recoverable error occurs - */ - public function run() - { - // lock Pico - $this->locked = true; - - // load plugins - $this->loadPlugins(); - $this->triggerEvent('onPluginsLoaded', array(&$this->plugins)); - - // load config - $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)); - - // discover requested file - $this->discoverRequestFile(); - $this->triggerEvent('onRequestFile', array(&$this->requestFile)); - - // load raw file content - $this->triggerEvent('onContentLoading', array(&$this->requestFile)); - - if (file_exists($this->requestFile)) { - $this->rawContent = $this->loadFileContent($this->requestFile); - } else { - $this->triggerEvent('on404ContentLoading', array(&$this->requestFile)); - - header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); - $this->rawContent = $this->load404Content($this->requestFile); - - $this->triggerEvent('on404ContentLoaded', array(&$this->rawContent)); - } - - $this->triggerEvent('onContentLoaded', array(&$this->rawContent)); - - // parse file meta - $headers = $this->getMetaHeaders(); - - $this->triggerEvent('onMetaParsing', array(&$this->rawContent, &$headers)); - $this->meta = $this->parseFileMeta($this->rawContent, $headers); - $this->triggerEvent('onMetaParsed', array(&$this->meta)); - - // register parsedown - $this->triggerEvent('onParsedownRegistration'); - $this->registerParsedown(); - - // parse file content - $this->triggerEvent('onContentParsing', array(&$this->rawContent)); - - $this->content = $this->prepareFileContent($this->rawContent, $this->meta); - $this->triggerEvent('onContentPrepared', array(&$this->content)); - - $this->content = $this->parseFileContent($this->content); - $this->triggerEvent('onContentParsed', array(&$this->content)); - - // read pages - $this->triggerEvent('onPagesLoading'); - - $this->readPages(); - $this->sortPages(); - $this->discoverCurrentPage(); - - $this->triggerEvent('onPagesLoaded', array( - &$this->pages, - &$this->currentPage, - &$this->previousPage, - &$this->nextPage - )); - - // register twig - $this->triggerEvent('onTwigRegistration'); - $this->registerTwig(); - - // render template - $this->twigVariables = $this->getTwigVariables(); - if (isset($this->meta['template']) && $this->meta['template']) { - $templateName = $this->meta['template']; - } else { - $templateName = 'index'; - } - if (file_exists($this->getThemesDir() . $this->getConfig('theme') . '/' . $templateName . '.twig')) { - $templateName .= '.twig'; - } else { - $templateName .= '.html'; - } - - $this->triggerEvent('onPageRendering', array(&$this->twig, &$this->twigVariables, &$templateName)); - - $output = $this->twig->render($templateName, $this->twigVariables); - $this->triggerEvent('onPageRendered', array(&$output)); - - return $output; - } - - /** - * Loads plugins from Pico::$pluginsDir in alphabetical order - * - * Plugin files MAY be prefixed by a number (e.g. 00-PicoDeprecated.php) - * to indicate their processing order. Plugins without a prefix will be - * loaded last. If you want to use a prefix, you MUST consider the - * following directives: - * - 00 to 19: Reserved - * - 20 to 39: Low level code helper plugins - * - 40 to 59: Plugins manipulating routing or the pages array - * - 60 to 79: Plugins hooking into template or markdown parsing - * - 80 to 99: Plugins using the `onPageRendered` event - * - * @see Pico::getPlugin() - * @see Pico::getPlugins() - * @return void - * @throws RuntimeException thrown when a plugin couldn't be loaded - */ - protected function loadPlugins() - { - $this->plugins = array(); - $pluginFiles = $this->getFiles($this->getPluginsDir(), '.php'); - foreach ($pluginFiles as $pluginFile) { - require_once($pluginFile); - - $className = preg_replace('/^[0-9]+-/', '', basename($pluginFile, '.php')); - if (class_exists($className)) { - // class name and file name can differ regarding case sensitivity - $plugin = new $className($this); - $className = get_class($plugin); - - $this->plugins[$className] = $plugin; - } else { - // TODO: breaks backward compatibility - //throw new RuntimeException("Unable to load plugin '".$className."'"); - } - } - } - - /** - * Returns the instance of a named plugin - * - * Plugins SHOULD implement {@link PicoPluginInterface}, but you MUST NOT - * rely on it. For more information see {@link PicoPluginInterface}. - * - * @see Pico::loadPlugins() - * @see Pico::getPlugins() - * @param string $pluginName name of the plugin - * @return object instance of the plugin - * @throws RuntimeException thrown when the plugin wasn't found - */ - public function getPlugin($pluginName) - { - if (isset($this->plugins[$pluginName])) { - return $this->plugins[$pluginName]; - } - - throw new RuntimeException("Missing plugin '" . $pluginName . "'"); - } - - /** - * Returns all loaded plugins - * - * @see Pico::loadPlugins() - * @see Pico::getPlugin() - * @return object[]|null - */ - public function getPlugins() - { - return $this->plugins; - } - - /** - * Loads the config.php from Pico::$configDir - * - * @see Pico::setConfig() - * @see Pico::getConfig() - * @return void - */ - protected function loadConfig() - { - $config = null; - if (file_exists($this->getConfigDir() . 'config.php')) { - require($this->getConfigDir() . 'config.php'); - } - - $defaultConfig = array( - 'site_title' => 'Pico', - 'base_url' => '', - 'rewrite_url' => null, - 'theme' => 'default', - 'date_format' => '%D %T', - 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), - 'pages_order_by' => 'alpha', - 'pages_order' => 'asc', - 'content_dir' => null, - 'content_ext' => '.md', - 'timezone' => '' - ); - - $this->config = is_array($this->config) ? $this->config : array(); - $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig; - - if (empty($this->config['base_url'])) { - $this->config['base_url'] = $this->getBaseUrl(); - } else { - $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')) { - $this->config['content_dir'] = $this->getRootDir() . 'content/'; - } else { - $this->config['content_dir'] = $this->getRootDir() . 'content-sample/'; - } - } else { - $this->config['content_dir'] = $this->getAbsolutePath($this->config['content_dir']); - } - - if (empty($this->config['timezone'])) { - // explicitly set a default timezone to prevent a E_NOTICE - // when no timezone is set; the `date_default_timezone_get()` - // function always returns a timezone, at least UTC - $this->config['timezone'] = @date_default_timezone_get(); - } - date_default_timezone_set($this->config['timezone']); - } - - /** - * Sets Pico's config before calling Pico::run() - * - * This method allows you to modify Pico's config without creating a - * {@path "config/config.php"} or changing some of its variables before - * Pico starts processing. - * - * You can call this method between {@link Pico::__construct()} and - * {@link Pico::run()} only. Options set with this method cannot be - * overwritten by {@path "config/config.php"}. - * - * @see Pico::loadConfig() - * @see Pico::getConfig() - * @param array $config array with config variables - * @return void - * @throws LogicException thrown if Pico already started processing - */ - public function setConfig(array $config) - { - if ($this->locked) { - throw new LogicException("You cannot modify Pico's config after processing has started"); - } - - $this->config = $config; - } - - /** - * Returns either the value of the specified config variable or - * the config array - * - * @see Pico::setConfig() - * @see Pico::loadConfig() - * @param string $configName optional name of a config variable - * @return mixed returns either the value of the named config - * variable, null if the config variable doesn't exist or the config - * array if no config name was supplied - */ - public function getConfig($configName = null) - { - if ($configName !== null) { - return isset($this->config[$configName]) ? $this->config[$configName] : null; - } else { - return $this->config; - } - } - - /** - * Evaluates the requested URL - * - * Pico 1.0 uses the `QUERY_STRING` routing method (e.g. `/pico/?sub/page`) - * to support SEO-like URLs out-of-the-box with any webserver. You can - * still setup URL rewriting (e.g. using `mod_rewrite` on Apache) to - * basically remove the `?` from URLs, but your rewritten URLs must follow - * the new `QUERY_STRING` principles. URL rewriting requires some special - * configuration on your webserver, but this should be "basic work" for - * any webmaster... - * - * Pico 0.9 and older required Apache with `mod_rewrite` enabled, thus old - * plugins, templates and contents may require you to enable URL rewriting - * to work. If you're upgrading from Pico 0.9, you will probably have to - * update your rewriting rules. - * - * 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` 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 - */ - protected function evaluateRequestUrl() - { - // use QUERY_STRING; e.g. /pico/?sub/page - // if you want to use rewriting, you MUST make your rules to - // rewrite the URLs to follow the QUERY_STRING method - // - // Note: you MUST NOT call the index page with /pico/?someBooleanParameter; - // use /pico/?someBooleanParameter= or /pico/?index&someBooleanParameter instead - $pathComponent = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; - if (($pathComponentLength = strpos($pathComponent, '&')) !== false) { - $pathComponent = substr($pathComponent, 0, $pathComponentLength); - } - $this->requestUrl = (strpos($pathComponent, '=') === false) ? rawurldecode($pathComponent) : ''; - $this->requestUrl = trim($this->requestUrl, '/'); - } - - /** - * Returns the URL where a user requested the page - * - * @see Pico::evaluateRequestUrl() - * @return string|null request URL - */ - public function getRequestUrl() - { - return $this->requestUrl; - } - - /** - * Uses the request URL to discover the content file to serve - * - * @see Pico::getRequestFile() - * @return void - */ - protected function discoverRequestFile() - { - if (empty($this->requestUrl)) { - $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); - } else { - // prevent content_dir breakouts using malicious request URLs - // we don't use realpath() here because we neither want to check for file existance - // nor prohibit symlinks which intentionally point to somewhere outside the content_dir - // it is STRONGLY RECOMMENDED to use open_basedir - always, not just with Pico! - $requestUrl = str_replace('\\', '/', $this->requestUrl); - $requestUrlParts = explode('/', $requestUrl); - - $requestFileParts = array(); - foreach ($requestUrlParts as $requestUrlPart) { - if (($requestUrlPart === '') || ($requestUrlPart === '.')) { - continue; - } elseif ($requestUrlPart === '..') { - array_pop($requestFileParts); - continue; - } - - $requestFileParts[] = $requestUrlPart; - } - - if (empty($requestFileParts)) { - $this->requestFile = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); - return; - } - - // discover the content file to serve - // Note: $requestFileParts neither contains a trailing nor a leading slash - $this->requestFile = $this->getConfig('content_dir') . implode('/', $requestFileParts); - if (is_dir($this->requestFile)) { - // if no index file is found, try a accordingly named file in the previous dir - // if this file doesn't exist either, show the 404 page, but assume the index - // file as being requested (maintains backward compatibility to Pico < 1.0) - $indexFile = $this->requestFile . '/index' . $this->getConfig('content_ext'); - if (file_exists($indexFile) || !file_exists($this->requestFile . $this->getConfig('content_ext'))) { - $this->requestFile = $indexFile; - return; - } - } - $this->requestFile .= $this->getConfig('content_ext'); - } - } - - /** - * Returns the absolute path to the content file to serve - * - * @see Pico::discoverRequestFile() - * @return string|null file path - */ - public function getRequestFile() - { - return $this->requestFile; - } - - /** - * Returns the raw contents of a file - * - * @see Pico::getRawContent() - * @param string $file file path - * @return string raw contents of the file - */ - public function loadFileContent($file) - { - return file_get_contents($file); - } - - /** - * Returns the raw contents of the first found 404 file when traversing - * up from the directory the requested file is in - * - * @see Pico::getRawContent() - * @param string $file path to requested (but not existing) file - * @return string raw contents of the 404 file - * @throws RuntimeException thrown when no suitable 404 file is found - */ - public function load404Content($file) - { - $errorFileDir = substr($file, strlen($this->getConfig('content_dir'))); - do { - $errorFileDir = dirname($errorFileDir); - $errorFile = $errorFileDir . '/404' . $this->getConfig('content_ext'); - } while (!file_exists($this->getConfig('content_dir') . $errorFile) && ($errorFileDir !== '.')); - - if (!file_exists($this->getConfig('content_dir') . $errorFile)) { - $errorFile = ($errorFileDir === '.') ? '404' . $this->getConfig('content_ext') : $errorFile; - throw new RuntimeException('Required "' . $this->getConfig('content_dir') . $errorFile . '" not found'); - } - - return $this->loadFileContent($this->getConfig('content_dir') . $errorFile); - } - - /** - * Returns the raw contents, either of the requested or the 404 file - * - * @see Pico::loadFileContent() - * @see Pico::load404Content() - * @return string|null raw contents - */ - public function getRawContent() - { - return $this->rawContent; - } - - /** - * Returns known meta headers and triggers the onMetaHeaders event - * - * Heads up! Calling this method triggers the `onMetaHeaders` event. - * Keep this in mind to prevent a infinite loop! - * - * @return string[] known meta headers; the array value specifies the - * YAML key to search for, the array key is later used to access the - * found value - */ - public function getMetaHeaders() - { - $headers = array( - 'title' => 'Title', - 'description' => 'Description', - 'author' => 'Author', - 'date' => 'Date', - 'robots' => 'Robots', - 'template' => 'Template' - ); - - $this->triggerEvent('onMetaHeaders', array(&$headers)); - return $headers; - } - - /** - * Parses the file meta from raw file contents - * - * Meta data MUST start on the first line of the file, either opened and - * closed by `---` or C-style block comments (deprecated). The headers are - * parsed by the YAML component of the Symfony project, keys are lowered. - * If you're a plugin developer, you MUST register new headers during the - * `onMetaHeaders` event first. The implicit availability of headers is - * for users and pure (!) theme developers ONLY. - * - * @see Pico::getFileMeta() - * @see <http://symfony.com/doc/current/components/yaml/introduction.html> - * @param string $rawContent the raw file contents - * @param string[] $headers known meta headers - * @return array parsed meta data - * @throws \Symfony\Component\Yaml\Exception\ParseException thrown when the - * meta data is invalid - */ - public function parseFileMeta($rawContent, array $headers) - { - $meta = array(); - $pattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" - . "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; - if (preg_match($pattern, $rawContent, $rawMetaMatches) && isset($rawMetaMatches[3])) { - $yamlParser = new \Symfony\Component\Yaml\Parser(); - $meta = $yamlParser->parse($rawMetaMatches[3]); - $meta = ($meta !== null) ? array_change_key_case($meta, CASE_LOWER) : array(); - - foreach ($headers as $fieldId => $fieldName) { - $fieldName = strtolower($fieldName); - if (isset($meta[$fieldName])) { - // rename field (e.g. remove whitespaces) - if ($fieldId != $fieldName) { - $meta[$fieldId] = $meta[$fieldName]; - unset($meta[$fieldName]); - } - } elseif (!isset($meta[$fieldId])) { - // guarantee array key existance - $meta[$fieldId] = ''; - } - } - - if (!empty($meta['date'])) { - $meta['time'] = strtotime($meta['date']); - $meta['date_formatted'] = utf8_encode(strftime($this->getConfig('date_format'), $meta['time'])); - } else { - $meta['time'] = $meta['date_formatted'] = ''; - } - } else { - // guarantee array key existance - $meta = array_fill_keys(array_keys($headers), ''); - $meta['time'] = $meta['date_formatted'] = ''; - } - - return $meta; - } - - /** - * Returns the parsed meta data of the requested page - * - * @see Pico::parseFileMeta() - * @return array|null parsed meta data - */ - public function getFileMeta() - { - return $this->meta; - } - - /** - * Registers the Parsedown Extra markdown parser - * - * @see Pico::getParsedown() - * @return void - */ - protected function registerParsedown() - { - $this->parsedown = new ParsedownExtra(); - } - - /** - * Returns the Parsedown Extra markdown parser - * - * @see Pico::registerParsedown() - * @return ParsedownExtra|null Parsedown Extra markdown parser - */ - public function getParsedown() - { - return $this->parsedown; - } - - /** - * Applies some static preparations to the raw contents of a page, - * e.g. removing the meta header and replacing %base_url% - * - * @see Pico::parseFileContent() - * @see Pico::getFileContent() - * @param string $rawContent raw contents of a page - * @param array $meta meta data to use for %meta.*% replacement - * @return string contents prepared for parsing - */ - public function prepareFileContent($rawContent, array $meta) - { - // remove meta header - $metaHeaderPattern = "/^(\/(\*)|---)[[:blank:]]*(?:\r)?\n" - . "(?:(.*?)(?:\r)?\n)?(?(2)\*\/|---)[[:blank:]]*(?:(?:\r)?\n|$)/s"; - $content = preg_replace($metaHeaderPattern, '', $rawContent, 1); - - // replace %site_title% - $content = str_replace('%site_title%', $this->getConfig('site_title'), $content); - - // replace %base_url% - if ($this->isUrlRewritingEnabled()) { - // always use `%base_url%?sub/page` syntax for internal links - // we'll replace the links accordingly, depending on enabled rewriting - $content = str_replace('%base_url%?', $this->getBaseUrl(), $content); - } else { - // actually not necessary, but makes the URL look a little nicer - $content = str_replace('%base_url%?', $this->getBaseUrl() . '?', $content); - } - $content = str_replace('%base_url%', rtrim($this->getBaseUrl(), '/'), $content); - - // replace %theme_url% - $themeUrl = $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'); - $content = str_replace('%theme_url%', $themeUrl, $content); - - // replace %meta.*% - if (!empty($meta)) { - $metaKeys = $metaValues = array(); - foreach ($meta as $metaKey => $metaValue) { - if (is_scalar($metaValue) || ($metaValue === null)) { - $metaKeys[] = '%meta.' . $metaKey . '%'; - $metaValues[] = strval($metaValue); - } - } - $content = str_replace($metaKeys, $metaValues, $content); - } - - return $content; - } - - /** - * Parses the contents of a page using ParsedownExtra - * - * @see Pico::prepareFileContent() - * @see Pico::getFileContent() - * @param string $content raw contents of a page (Markdown) - * @return string parsed contents (HTML) - */ - public function parseFileContent($content) - { - if ($this->parsedown === null) { - throw new LogicException("Unable to parse file contents: Parsedown instance wasn't registered yet"); - } - - return $this->parsedown->text($content); - } - - /** - * Returns the cached contents of the requested page - * - * @see Pico::prepareFileContent() - * @see Pico::parseFileContent() - * @return string|null parsed contents - */ - public function getFileContent() - { - return $this->content; - } - - /** - * Reads the data of all pages known to Pico - * - * The page data will be an array containing the following values: - * - * | Array key | Type | Description | - * | -------------- | ------ | ---------------------------------------- | - * | id | string | relative path to the content file | - * | url | string | URL to the page | - * | title | string | title of the page (YAML header) | - * | description | string | description of the page (YAML header) | - * | author | string | author of the page (YAML header) | - * | time | string | timestamp derived from the Date header | - * | date | string | date of the page (YAML header) | - * | date_formatted | string | formatted date of the page | - * | raw_content | string | raw, not yet parsed contents of the page | - * | meta | string | parsed meta data of the page | - * - * @see Pico::sortPages() - * @see Pico::getPages() - * @return void - */ - protected function readPages() - { - $this->pages = array(); - $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')) { - unset($files[$i]); - continue; - } - - $id = substr($file, strlen($this->getConfig('content_dir')), -strlen($this->getConfig('content_ext'))); - - // drop inaccessible pages (e.g. drop "sub.md" if "sub/index.md" exists) - $conflictFile = $this->getConfig('content_dir') . $id . '/index' . $this->getConfig('content_ext'); - if (in_array($conflictFile, $files, true)) { - continue; - } - - $url = $this->getPageUrl($id); - if ($file != $this->requestFile) { - $rawContent = file_get_contents($file); - - $headers = $this->getMetaHeaders(); - try { - $meta = $this->parseFileMeta($rawContent, $headers); - } catch (\Symfony\Component\Yaml\Exception\ParseException $e) { - $meta = $this->parseFileMeta('', $headers); - $meta['YAML_ParseError'] = $e->getMessage(); - } - } else { - $rawContent = &$this->rawContent; - $meta = &$this->meta; - } - - // build page data - // title, description, author and date are assumed to be pretty basic data - // everything else is accessible through $page['meta'] - $page = array( - 'id' => $id, - 'url' => $url, - 'title' => &$meta['title'], - 'description' => &$meta['description'], - 'author' => &$meta['author'], - 'time' => &$meta['time'], - 'date' => &$meta['date'], - 'date_formatted' => &$meta['date_formatted'], - 'raw_content' => &$rawContent, - 'meta' => &$meta - ); - - if ($file === $this->requestFile) { - $page['content'] = &$this->content; - } - - unset($rawContent, $meta); - - // trigger event - $this->triggerEvent('onSinglePageLoaded', array(&$page)); - - $this->pages[$id] = $page; - } - } - - /** - * Sorts all pages known to Pico - * - * @see Pico::readPages() - * @see Pico::getPages() - * @return void - */ - protected function sortPages() - { - // sort pages - $order = $this->getConfig('pages_order'); - $alphaSortClosure = function ($a, $b) use ($order) { - $aSortKey = (basename($a['id']) === 'index') ? dirname($a['id']) : $a['id']; - $bSortKey = (basename($b['id']) === 'index') ? dirname($b['id']) : $b['id']; - - $cmp = strcmp($aSortKey, $bSortKey); - return $cmp * (($order === 'desc') ? -1 : 1); - }; - - 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'])) { - $cmp = (empty($a['time']) - empty($b['time'])); - } else { - $cmp = ($b['time'] - $a['time']); - } - - if ($cmp === 0) { - // never assume equality; fallback to alphabetical order - return $alphaSortClosure($a, $b); - } - - return $cmp * (($order === 'desc') ? 1 : -1); - }); - } else { - // sort alphabetically - uasort($this->pages, $alphaSortClosure); - } - } - - /** - * Returns the list of known pages - * - * @see Pico::readPages() - * @see Pico::sortPages() - * @return array[]|null the data of all pages - */ - public function getPages() - { - return $this->pages; - } - - /** - * Walks through the list of known pages and discovers the requested page - * as well as the previous and next page relative to it - * - * @see Pico::getCurrentPage() - * @see Pico::getPreviousPage() - * @see Pico::getNextPage() - * @return void - */ - protected function discoverCurrentPage() - { - $pageIds = array_keys($this->pages); - - $contentDir = $this->getConfig('content_dir'); - $contentExt = $this->getConfig('content_ext'); - $currentPageId = substr($this->requestFile, strlen($contentDir), -strlen($contentExt)); - $currentPageIndex = array_search($currentPageId, $pageIds); - if ($currentPageIndex !== false) { - $this->currentPage = &$this->pages[$currentPageId]; - - if (($this->getConfig('order_by') === 'date') && ($this->getConfig('order') === 'desc')) { - $previousPageOffset = 1; - $nextPageOffset = -1; - } else { - $previousPageOffset = -1; - $nextPageOffset = 1; - } - - if (isset($pageIds[$currentPageIndex + $previousPageOffset])) { - $previousPageId = $pageIds[$currentPageIndex + $previousPageOffset]; - $this->previousPage = &$this->pages[$previousPageId]; - } - - if (isset($pageIds[$currentPageIndex + $nextPageOffset])) { - $nextPageId = $pageIds[$currentPageIndex + $nextPageOffset]; - $this->nextPage = &$this->pages[$nextPageId]; - } - } - } - - /** - * Returns the data of the requested page - * - * @see Pico::discoverCurrentPage() - * @return array|null page data - */ - public function getCurrentPage() - { - return $this->currentPage; - } - - /** - * Returns the data of the previous page relative to the page being served - * - * @see Pico::discoverCurrentPage() - * @return array|null page data - */ - public function getPreviousPage() - { - return $this->previousPage; - } - - /** - * Returns the data of the next page relative to the page being served - * - * @see Pico::discoverCurrentPage() - * @return array|null page data - */ - public function getNextPage() - { - return $this->nextPage; - } - - /** - * Registers the twig template engine - * - * This method also registers Picos core Twig filters `link` and `content` - * as well as Picos {@link PicoTwigExtension} Twig extension. - * - * @see Pico::getTwig() - * @return void - */ - protected function registerTwig() - { - $twigLoader = new Twig_Loader_Filesystem($this->getThemesDir() . $this->getConfig('theme')); - $this->twig = new Twig_Environment($twigLoader, $this->getConfig('twig_config')); - $this->twig->addExtension(new Twig_Extension_Debug()); - $this->twig->addExtension(new PicoTwigExtension($this)); - - // register link filter - $this->twig->addFilter(new Twig_SimpleFilter('link', array($this, 'getPageUrl'))); - - // register content filter - // we pass the $pages array by reference to prevent multiple parser runs for the same page - // this is the reason why we can't register this filter as part of PicoTwigExtension - $pico = $this; - $pages = &$this->pages; - $this->twig->addFilter(new Twig_SimpleFilter('content', function ($page) use ($pico, &$pages) { - if (isset($pages[$page])) { - $pageData = &$pages[$page]; - if (!isset($pageData['content'])) { - $pageData['content'] = $pico->prepareFileContent($pageData['raw_content'], $pageData['meta']); - $pageData['content'] = $pico->parseFileContent($pageData['content']); - } - return $pageData['content']; - } - return null; - })); - } - - /** - * Returns the twig template engine - * - * @see Pico::registerTwig() - * @return Twig_Environment|null Twig template engine - */ - public function getTwig() - { - return $this->twig; - } - - /** - * Returns the variables passed to the template - * - * URLs and paths (namely `base_dir`, `base_url`, `theme_dir` and - * `theme_url`) don't add a trailing slash for historic reasons. - * - * @return array template variables - */ - protected function getTwigVariables() - { - $frontPage = $this->getConfig('content_dir') . 'index' . $this->getConfig('content_ext'); - return array( - 'config' => $this->getConfig(), - 'base_dir' => rtrim($this->getRootDir(), '/'), - 'base_url' => rtrim($this->getBaseUrl(), '/'), - 'theme_dir' => $this->getThemesDir() . $this->getConfig('theme'), - 'theme_url' => $this->getBaseUrl() . basename($this->getThemesDir()) . '/' . $this->getConfig('theme'), - 'rewrite_url' => $this->isUrlRewritingEnabled(), - 'site_title' => $this->getConfig('site_title'), - 'meta' => $this->meta, - 'content' => $this->content, - 'pages' => $this->pages, - 'prev_page' => $this->previousPage, - 'current_page' => $this->currentPage, - 'next_page' => $this->nextPage, - 'is_front_page' => ($this->requestFile === $frontPage), - ); - } - - /** - * Returns the base URL of this Pico instance - * - * @return string the base url - */ - public function getBaseUrl() - { - $baseUrl = $this->getConfig('base_url'); - if (!empty($baseUrl)) { - return $baseUrl; - } - - $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'; - } - - $this->config['base_url'] = - $protocol . "://" . $_SERVER['HTTP_HOST'] - . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/'; - - return $this->getConfig('base_url'); - } - - /** - * Returns true if URL rewriting is enabled - * - * @return boolean true if URL rewriting is enabled, false otherwise - */ - public function isUrlRewritingEnabled() - { - $urlRewritingEnabled = $this->getConfig('rewrite_url'); - if ($urlRewritingEnabled !== null) { - return $urlRewritingEnabled; - } - - $this->config['rewrite_url'] = (isset($_SERVER['PICO_URL_REWRITING']) && $_SERVER['PICO_URL_REWRITING']); - return $this->getConfig('rewrite_url'); - } - - /** - * Returns the URL to a given page - * - * @param string $page identifier of the page to link to - * @param array|string $queryData either an array containing properties to - * create a URL-encoded query string from, or a already encoded string - * @return string URL - */ - public function getPageUrl($page, $queryData = null) - { - if (is_array($queryData)) { - $queryData = http_build_query($queryData, '', '&'); - } elseif (($queryData !== null) && !is_string($queryData)) { - throw new InvalidArgumentException( - 'Argument 2 passed to ' . get_called_class() . '::getPageUrl() must be of the type array or string, ' - . (is_object($queryData) ? get_class($queryData) : gettype($queryData)) . ' given' - ); - } - if (!empty($queryData)) { - $page = !empty($page) ? $page : 'index'; - $queryData = $this->isUrlRewritingEnabled() ? '?' . $queryData : '&' . $queryData; - } - - if (empty($page)) { - return $this->getBaseUrl() . $queryData; - } elseif (!$this->isUrlRewritingEnabled()) { - return $this->getBaseUrl() . '?' . rawurlencode($page) . $queryData; - } else { - return $this->getBaseUrl() . implode('/', array_map('rawurlencode', explode('/', $page))) . $queryData; - } - } - - /** - * Recursively walks through a directory and returns all containing files - * matching the specified file extension - * - * @param string $directory start directory - * @param string $fileExtension return files with the given file extension - * only (optional) - * @param int $order specify whether and how files should be - * sorted; use Pico::SORT_ASC for a alphabetical ascending order (this - * is the default behaviour), Pico::SORT_DESC for a descending order - * or Pico::SORT_NONE to leave the result unsorted - * @return array list of found files - */ - protected function getFiles($directory, $fileExtension = '', $order = self::SORT_ASC) - { - $directory = rtrim($directory, '/'); - $result = array(); - - // scandir() reads files in alphabetical order - $files = scandir($directory, $order); - $fileExtensionLength = strlen($fileExtension); - if ($files !== false) { - foreach ($files as $file) { - // exclude hidden files/dirs starting with a .; this also excludes the special dirs . and .. - // exclude files ending with a ~ (vim/nano backup) or # (emacs backup) - if ((substr($file, 0, 1) === '.') || in_array(substr($file, -1), array('~', '#'))) { - continue; - } - - if (is_dir($directory . '/' . $file)) { - // get files recursively - $result = array_merge($result, $this->getFiles($directory . '/' . $file, $fileExtension, $order)); - } elseif (empty($fileExtension) || (substr($file, -$fileExtensionLength) === $fileExtension)) { - $result[] = $directory . '/' . $file; - } - } - } - - return $result; - } - - /** - * Makes a relative path absolute to Pico's root dir - * - * This method also guarantees a trailing slash. - * - * @param string $path relative or absolute path - * @return string absolute path - */ - public function getAbsolutePath($path) - { - if (strncasecmp(PHP_OS, 'WIN', 3) === 0) { - if (preg_match('/^([a-zA-Z]:\\\\|\\\\\\\\)/', $path) !== 1) { - $path = $this->getRootDir() . $path; - } - } else { - if (substr($path, 0, 1) !== '/') { - $path = $this->getRootDir() . $path; - } - } - return rtrim($path, '/\\') . '/'; - } - - /** - * Triggers events on plugins which implement PicoPluginInterface - * - * Deprecated events (as used by plugins not implementing - * {@link PicoPluginInterface}) are triggered by {@link PicoDeprecated}. - * - * @see PicoPluginInterface - * @see AbstractPicoPlugin - * @see DummyPlugin - * @param string $eventName name of the event to trigger - * @param array $params optional parameters to pass - * @return void - */ - protected function triggerEvent($eventName, array $params = array()) - { - 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 `PicoDeprecated` - if (is_a($plugin, 'PicoPluginInterface')) { - $plugin->handleEvent($eventName, $params); - } - } - } - } -} diff --git a/lib/PicoPluginInterface.php b/lib/PicoPluginInterface.php @@ -1,102 +0,0 @@ -<?php - -/** - * Common interface for Pico plugins - * - * For a list of supported events see {@link DummyPlugin}; you can use - * {@link DummyPlugin} as template for new plugins. For a list of deprecated - * events see {@link PicoDeprecated}. - * - * You SHOULD NOT use deprecated events when implementing this interface. - * Deprecated events are triggered by the {@link PicoDeprecated} plugin, if - * plugins which don't implement this interface are loaded. You can take - * advantage from this behaviour if you want to do something only when old - * plugins are loaded. Consequently the old events are never triggered when - * your plugin is implementing this interface and no old plugins are present. - * - * If you're developing a new plugin, you MUST implement this interface. If - * you're the developer of an old plugin, it is STRONGLY RECOMMENDED to use - * the events introduced in Pico 1.0 when releasing a new version of your - * plugin. If you want to use any of the new events, you MUST implement - * this interface and update all other events you use. - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -interface PicoPluginInterface -{ - /** - * Constructs a new instance of a Pico plugin - * - * @param Pico $pico current instance of Pico - */ - public function __construct(Pico $pico); - - /** - * Handles a event that was triggered by Pico - * - * @param string $eventName name of the triggered event - * @param array $params passed parameters - * @return void - */ - public function handleEvent($eventName, array $params); - - /** - * Enables or disables this plugin - * - * @see PicoPluginInterface::isEnabled() - * @see PicoPluginInterface::isStatusChanged() - * @param boolean $enabled enable (true) or disable (false) this plugin - * @param boolean $recursive when true, enable or disable recursively - * In other words, if you enable a plugin, all required plugins are - * enabled, too. When disabling a plugin, all depending plugins are - * disabled likewise. Recursive operations are only performed as long - * as a plugin wasn't enabled/disabled manually. This parameter is - * optional and defaults to true. - * @param boolean $auto enable or disable to fulfill a dependency - * This parameter is optional and defaults to false. - * @return void - * @throws RuntimeException thrown when a dependency fails - */ - public function setEnabled($enabled, $recursive = true, $auto = false); - - /** - * Returns true if this plugin is enabled, false otherwise - * - * @see PicoPluginInterface::setEnabled() - * @return boolean plugin is enabled (true) or disabled (false) - */ - public function isEnabled(); - - /** - * Returns true if the plugin was ever enabled/disabled manually - * - * @see PicoPluginInterface::setEnabled() - * @return boolean plugin is in its default state (true), false otherwise - */ - public function isStatusChanged(); - - /** - * Returns a list of names of plugins required by this plugin - * - * @return string[] required plugins - */ - public function getDependencies(); - - /** - * Returns a list of plugins which depend on this plugin - * - * @return object[] dependant plugins - */ - public function getDependants(); - - /** - * Returns the plugins instance of Pico - * - * @see Pico - * @return Pico the plugins instance of Pico - */ - public function getPico(); -} diff --git a/lib/PicoTwigExtension.php b/lib/PicoTwigExtension.php @@ -1,238 +0,0 @@ -<?php - -/** - * Picos Twig extension to implement additional filters - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -class PicoTwigExtension extends Twig_Extension -{ - /** - * Current instance of Pico - * - * @see PicoTwigExtension::getPico() - * @var Pico - */ - private $pico; - - /** - * Constructs a new instance of this Twig extension - * - * @param Pico $pico current instance of Pico - */ - public function __construct(Pico $pico) - { - $this->pico = $pico; - } - - /** - * Returns the extensions instance of Pico - * - * @see Pico - * @return Pico the extensions instance of Pico - */ - public function getPico() - { - return $this->pico; - } - - /** - * Returns the name of the extension - * - * @see Twig_ExtensionInterface::getName() - * @return string the extension name - */ - public function getName() - { - return 'PicoTwigExtension'; - } - - /** - * Returns the Twig filters markdown, map and sort_by - * - * @see Twig_ExtensionInterface::getFilters() - * @return Twig_SimpleFilter[] array of Picos Twig filters - */ - public function getFilters() - { - return array( - 'markdown' => new Twig_SimpleFilter('markdown', array($this, 'markdownFilter')), - 'map' => new Twig_SimpleFilter('map', array($this, 'mapFilter')), - 'sort_by' => new Twig_SimpleFilter('sort_by', array($this, 'sortByFilter')), - ); - } - - /** - * Parses a markdown string to HTML - * - * This method is registered as the Twig `markdown` filter. You can use it - * to e.g. parse a meta variable (`{{ meta.description|markdown }}`). - * Don't use it to parse the contents of a page, use the `content` filter - * instead, what ensures the proper preparation of the contents. - * - * @param string $markdown markdown to parse - * @return string parsed HTML - */ - public function markdownFilter($markdown) - { - if ($this->getPico()->getParsedown() === null) { - throw new LogicException( - 'Unable to apply Twig "markdown" filter: ' - . 'Parsedown instance wasn\'t registered yet' - ); - } - - return $this->getPico()->getParsedown()->text($markdown); - } - - /** - * Returns a array with the values of the given key or key path - * - * This method is registered as the Twig `map` filter. You can use this - * filter to e.g. get all page titles (`{{ pages|map("title") }}`). - * - * @param array|Traversable $var variable to map - * @param mixed $mapKeyPath key to map; either a scalar or a - * array interpreted as key path (i.e. ['foo', 'bar'] will return all - * $item['foo']['bar'] values) - * @return array mapped values - */ - public function mapFilter($var, $mapKeyPath) - { - if (!is_array($var) && (!is_object($var) || !is_a($var, 'Traversable'))) { - throw new Twig_Error_Runtime(sprintf( - 'The map filter only works with arrays or "Traversable", got "%s"', - is_object($var) ? get_class($var) : gettype($var) - )); - } - - $result = array(); - foreach ($var as $key => $value) { - $mapValue = $this->getKeyOfVar($value, $mapKeyPath); - $result[$key] = ($mapValue !== null) ? $mapValue : $value; - } - return $result; - } - - /** - * Sorts an array by one of its keys or a arbitrary deep sub-key - * - * This method is registered as the Twig `sort_by` filter. You can use this - * filter to e.g. sort the pages array by a arbitrary meta value. Calling - * `{{ pages|sort_by("meta:nav"|split(":")) }}` returns all pages sorted by - * the meta value `nav`. Please note the `"meta:nav"|split(":")` part of - * the example. The sorting algorithm will never assume equality of two - * values, it will then fall back to the original order. The result is - * always sorted in ascending order, apply Twigs `reverse` filter to - * achieve a descending order. - * - * @param array|Traversable $var variable to sort - * @param mixed $sortKeyPath key to use for sorting; either - * a scalar or a array interpreted as key path (i.e. ['foo', 'bar'] - * will sort $var by $item['foo']['bar']) - * @param string $fallback specify what to do with items - * which don't contain the specified sort key; use "bottom" (default) - * to move those items to the end of the sorted array, "top" to rank - * them first, or "keep" to keep the original order of those items - * @return array sorted array - */ - public function sortByFilter($var, $sortKeyPath, $fallback = 'bottom') - { - if (is_object($var) && is_a($var, 'Traversable')) { - $var = iterator_to_array($var, true); - } elseif (!is_array($var)) { - throw new Twig_Error_Runtime(sprintf( - 'The sort_by filter only works with arrays or "Traversable", got "%s"', - is_object($var) ? get_class($var) : gettype($var) - )); - } - if (($fallback !== 'top') && ($fallback !== 'bottom') && ($fallback !== 'keep')) { - throw new Twig_Error_Runtime('The sort_by filter only supports the "top", "bottom" and "keep" fallbacks'); - } - - $twigExtension = $this; - $varKeys = array_keys($var); - uksort($var, function ($a, $b) use ($twigExtension, $var, $varKeys, $sortKeyPath, $fallback, &$removeItems) { - $aSortValue = $twigExtension->getKeyOfVar($var[$a], $sortKeyPath); - $aSortValueNull = ($aSortValue === null); - - $bSortValue = $twigExtension->getKeyOfVar($var[$b], $sortKeyPath); - $bSortValueNull = ($bSortValue === null); - - if ($aSortValueNull xor $bSortValueNull) { - if ($fallback === 'top') { - return ($aSortValueNull - $bSortValueNull) * -1; - } elseif ($fallback === 'bottom') { - return ($aSortValueNull - $bSortValueNull); - } - } elseif (!$aSortValueNull && !$bSortValueNull) { - if ($aSortValue != $bSortValue) { - return ($aSortValue > $bSortValue) ? 1 : -1; - } - } - - // never assume equality; fallback to original order - $aIndex = array_search($a, $varKeys); - $bIndex = array_search($b, $varKeys); - return ($aIndex > $bIndex) ? 1 : -1; - }); - - return $var; - } - - /** - * Returns the value of a variable item specified by a scalar key or a - * arbitrary deep sub-key using a key path - * - * @param array|Traversable|ArrayAccess|object $var base variable - * @param mixed $keyPath scalar key or a - * array interpreted as key path (when passing e.g. ['foo', 'bar'], - * the method will return $var['foo']['bar']) specifying the value - * @return mixed the requested - * value or NULL when the given key or key path didn't match - */ - public static function getKeyOfVar($var, $keyPath) - { - if (empty($keyPath)) { - return null; - } elseif (!is_array($keyPath)) { - $keyPath = array($keyPath); - } - - foreach ($keyPath as $key) { - if (is_object($var)) { - if (is_a($var, 'ArrayAccess')) { - // use ArrayAccess, see below - } elseif (is_a($var, 'Traversable')) { - $var = iterator_to_array($var); - } elseif (isset($var->{$key})) { - $var = $var->{$key}; - continue; - } elseif (is_callable(array($var, 'get' . ucfirst($key)))) { - try { - $var = call_user_func(array($var, 'get' . ucfirst($key))); - continue; - } catch (BadMethodCallException $e) { - return null; - } - } else { - return null; - } - } elseif (!is_array($var)) { - return null; - } - - if (isset($var[$key])) { - $var = $var[$key]; - continue; - } - - return null; - } - - return $var; - } -} diff --git a/lib/cache/.gitignore b/lib/cache/.gitignore @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/lib/index.html b/lib/index.html diff --git a/lib/pico.php b/lib/pico.php @@ -1,363 +0,0 @@ -<?php -use \Michelf\MarkdownExtra; - -/** - * Pico - * - * @author Gilbert Pellegrom - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 0.8 - */ -class Pico { - - private $plugins; - - /** - * The constructor carries out all the processing in Pico. - * Does URL routing, Markdown processing and Twig processing. - */ - public function __construct() - { - // Load plugins - $this->load_plugins(); - $this->run_hooks('plugins_loaded'); - - // Load the settings - $settings = $this->get_config(); - $this->run_hooks('config_loaded', array(&$settings)); - - // Get request url and script url - $url = ''; - $request_url = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; - $script_url = (isset($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : ''; - - // Get our url path and trim the / of the left and the right - if($request_url != $script_url) $url = trim(preg_replace('/'. str_replace('/', '\/', str_replace('index.php', '', $script_url)) .'/', '', $request_url, 1), '/'); - $url = preg_replace('/\?.*/', '', $url); // Strip query string - $this->run_hooks('request_url', array(&$url)); - - // Get the file path - if($url) $file = $settings['content_dir'] . $url; - else $file = $settings['content_dir'] .'index'; - - // Load the file - if(is_dir($file)) $file = $settings['content_dir'] . $url .'/index'. CONTENT_EXT; - else $file .= CONTENT_EXT; - - $this->run_hooks('before_load_content', array(&$file)); - if(file_exists($file)){ - $content = file_get_contents($file); - } - $this->run_hooks('after_load_content', array(&$file, &$content)); - - $meta = $this->read_file_meta($content); - $this->run_hooks('file_meta', array(&$meta)); - - $this->run_hooks('before_parse_content', array(&$content)); - $content = $this->parse_content($content); - $this->run_hooks('after_parse_content', array(&$content)); - $this->run_hooks('content_parsed', array(&$content)); // Depreciated @ v0.8 - - // Get all the pages - $pages = $this->get_pages($settings['base_url'], $settings['pages_order_by'], $settings['pages_order'], $settings['excerpt_length']); - $prev_page = array(); - $current_page = array(); - $next_page = array(); - while($current_page = current($pages)){ - if((isset($meta['title'])) && ($meta['title'] == $current_page['title'])){ - break; - } - next($pages); - } - $prev_page = next($pages); - prev($pages); - $next_page = prev($pages); - $this->run_hooks('get_pages', array(&$pages, &$current_page, &$prev_page, &$next_page)); - - // Load the theme - $this->run_hooks('before_twig_register'); - Twig_Autoloader::register(); - $loader = new Twig_Loader_Filesystem(THEMES_DIR . $settings['theme']); - $twig = new Twig_Environment($loader, $settings['twig_config']); - $twig->addExtension(new Twig_Extension_Debug()); - $twig_vars = array( - 'config' => $settings, - 'base_dir' => rtrim(ROOT_DIR, '/'), - 'base_url' => $settings['base_url'], - 'theme_dir' => THEMES_DIR . $settings['theme'], - 'theme_url' => $settings['base_url'] .'/'. basename(THEMES_DIR) .'/'. $settings['theme'], - 'site_title' => $settings['site_title'], - 'meta' => $meta, - 'content' => $content, - 'pages' => $pages, - 'prev_page' => $prev_page, - 'current_page' => $current_page, - 'next_page' => $next_page, - 'is_front_page' => $url ? false : true, - ); - - $template = (isset($meta['template']) && $meta['template']) ? $meta['template'] : 'index'; - $this->run_hooks('before_render', array(&$twig_vars, &$twig, &$template)); - $output = $twig->render($template .'.html', $twig_vars); - $this->run_hooks('after_render', array(&$output)); - echo $output; - } - - /** - * Load any plugins - */ - protected function load_plugins() - { - $this->plugins = array(); - $plugins = $this->get_files(PLUGINS_DIR, '.php'); - if(!empty($plugins)){ - foreach($plugins as $plugin){ - include_once($plugin); - $plugin_name = preg_replace("/\\.[^.\\s]{3}$/", '', basename($plugin)); - if(class_exists($plugin_name)){ - $obj = new $plugin_name; - $this->plugins[] = $obj; - } - } - } - } - - /** - * Parses the content using Markdown - * - * @param string $content the raw txt content - * @return string $content the Markdown formatted content - */ - protected function parse_content($content) - { - $content = preg_replace('#/\*.+?\*/#s', '', $content); // Remove comments and meta - $content = str_replace('%base_url%', $this->base_url(), $content); - $content = MarkdownExtra::defaultTransform($content); - - return $content; - } - - /** - * Parses the file meta from the txt file header - * - * @param string $content the raw txt content - * @return array $headers an array of meta values - */ - protected function read_file_meta($content) - { - global $config; - - $headers = array( - 'title' => 'Title', - 'description' => 'Description', - 'author' => 'Author', - 'date' => 'Date', - 'robots' => 'Robots', - 'images' => 'images', - 'template' => 'Template' - ); - - // Add support for custom headers by hooking into the headers array - $this->run_hooks('before_read_file_meta', array(&$headers)); - - foreach ($headers as $field => $regex){ - if (preg_match('/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $content, $match) && $match[1]){ - $headers[ $field ] = trim(preg_replace("/\s*(?:\*\/|\?>).*/", '', $match[1])); - } else { - $headers[ $field ] = ''; - } - } - - if(isset($headers['date'])) $headers['date_formatted'] = utf8_encode(strftime($config['date_format'], strtotime($headers['date']))); - - return $headers; - } - - /** - * Loads the config - * - * @return array $config an array of config values - */ - protected function get_config() - { - global $config; - @include_once(ROOT_DIR .'config.php'); - - $defaults = array( - 'site_title' => 'Pico', - 'base_url' => $this->base_url(), - 'theme' => 'default', - 'date_format' => 'jS M Y', - 'twig_config' => array('cache' => false, 'autoescape' => false, 'debug' => false), - 'pages_order_by' => 'alpha', - 'pages_order' => 'asc', - 'excerpt_length' => 50, - 'content_dir' => 'content-sample/', - ); - - if(is_array($config)) $config = array_merge($defaults, $config); - else $config = $defaults; - - return $config; - } - - /** - * Get a list of pages - * - * @param string $base_url the base URL of the site - * @param string $order_by order by "alpha" or "date" - * @param string $order order "asc" or "desc" - * @return array $sorted_pages an array of pages - */ - protected function get_pages($base_url, $order_by = 'alpha', $order = 'asc', $excerpt_length = 50) - { - global $config; - - $pages = $this->get_files($config['content_dir'], CONTENT_EXT); - $sorted_pages = array(); - $date_id = 0; - foreach($pages as $key=>$page){ - // Skip 404 - if(basename($page) == '404'. CONTENT_EXT){ - unset($pages[$key]); - continue; - } - - // Ignore Emacs (and Nano) temp files - if (in_array(substr($page, -1), array('~','#'))) { - unset($pages[$key]); - continue; - } - // Get title and format $page - $page_content = file_get_contents($page); - $page_meta = $this->read_file_meta($page_content); - $page_content = $this->parse_content($page_content); - $url = str_replace($config['content_dir'], $base_url .'/', $page); - $url = str_replace('index'. CONTENT_EXT, '', $url); - $url = str_replace(CONTENT_EXT, '', $url); - $data = array( - 'title' => isset($page_meta['title']) ? $page_meta['title'] : '', - 'url' => $url, - 'author' => isset($page_meta['author']) ? $page_meta['author'] : '', - 'date' => isset($page_meta['date']) ? $page_meta['date'] : '', - 'date_formatted' => isset($page_meta['date']) ? utf8_encode(strftime($config['date_format'], strtotime($page_meta['date']))) : '', - 'content' => $page_content, - 'excerpt' => $this->limit_words(strip_tags($page_content), $excerpt_length), - //this addition allows the 'description' meta to be picked up in content areas... specifically to replace 'excerpt' - 'description' => isset($page_meta['description']) ? $page_meta['description'] : '', - 'images' => isset($page_meta['images']) ? $page_meta['images'] : '', - - ); - - // Extend the data provided with each page by hooking into the data array - $this->run_hooks('get_page_data', array(&$data, $page_meta)); - - if($order_by == 'date' && isset($page_meta['date'])){ - $sorted_pages[$page_meta['date'].$date_id] = $data; - $date_id++; - } - else $sorted_pages[] = $data; - } - - if($order == 'desc') krsort($sorted_pages); - else ksort($sorted_pages); - - return $sorted_pages; - } - - /** - * Processes any hooks and runs them - * - * @param string $hook_id the ID of the hook - * @param array $args optional arguments - */ - protected function run_hooks($hook_id, $args = array()) - { - if(!empty($this->plugins)){ - foreach($this->plugins as $plugin){ - if(is_callable(array($plugin, $hook_id))){ - call_user_func_array(array($plugin, $hook_id), $args); - } - } - } - } - - /** - * Helper function to work out the base URL - * - * @return string the base url - */ - protected function base_url() - { - global $config; - if(isset($config['base_url']) && $config['base_url']) return $config['base_url']; - - $url = ''; - $request_url = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; - $script_url = (isset($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : ''; - if($request_url != $script_url) $url = trim(preg_replace('/'. str_replace('/', '\/', str_replace('index.php', '', $script_url)) .'/', '', $request_url, 1), '/'); - - $protocol = $this->get_protocol(); - return rtrim(str_replace($url, '', $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']), '/'); - } - - /** - * Tries to guess the server protocol. Used in base_url() - * - * @return string the current protocol - */ - protected function get_protocol() - { - $protocol = 'http'; - if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' && $_SERVER['HTTPS'] != ''){ - $protocol = 'https'; - } - return $protocol; - } - - /** - * Helper function to recusively get all files in a directory - * - * @param string $directory start directory - * @param string $ext optional limit to file extensions - * @return array the matched files - */ - protected function get_files($directory, $ext = '') - { - $array_items = array(); - if($handle = opendir($directory)){ - while(false !== ($file = readdir($handle))){ - if(in_array(substr($file, -1), array('~', '#'))){ - continue; - } - if(preg_match("/^(^\.)/", $file) === 0){ - if(is_dir($directory. "/" . $file)){ - $array_items = array_merge($array_items, $this->get_files($directory. "/" . $file, $ext)); - } else { - $file = $directory . "/" . $file; - if(!$ext || strstr($file, $ext)) $array_items[] = preg_replace("/\/\//si", "/", $file); - } - } - } - closedir($handle); - } - return $array_items; - } - - /** - * Helper function to limit the words in a string - * - * @param string $string the given string - * @param int $word_limit the number of words to limit to - * @return string the limited string - */ - protected function limit_words($string, $word_limit) - { - $words = explode(' ',$string); - $excerpt = trim(implode(' ', array_splice($words, 0, $word_limit))); - if(count($words) > $word_limit) $excerpt .= '&hellip;'; - return $excerpt; - } - -} diff --git a/plugins/00-PicoDeprecated.php b/plugins/00-PicoDeprecated.php @@ -1,437 +0,0 @@ -<?php - -/** - * Serve features of Pico deprecated since v1.0 - * - * This plugin exists for backward compatibility and is disabled by default. - * It gets automatically enabled when a plugin which doesn't implement - * {@link PicoPluginInterface} is loaded. This plugin triggers deprecated - * events and automatically enables {@link PicoParsePagesContent} and - * {@link PicoExcerpt}. These plugins heavily impact Pico's performance! You - * can disable this plugin by calling {@link PicoDeprecated::setEnabled()}. - * - * The following deprecated events are triggered by this plugin: - * - * | Event | ... triggers the deprecated event | - * | ------------------- | --------------------------------------------------------- | - * | onPluginsLoaded | plugins_loaded() | - * | onConfigLoaded | config_loaded($config) | - * | onRequestUrl | request_url($url) | - * | onContentLoading | before_load_content($file) | - * | onContentLoaded | after_load_content($file, $rawContent) | - * | on404ContentLoading | before_404_load_content($file) | - * | on404ContentLoaded | after_404_load_content($file, $rawContent) | - * | onMetaHeaders | before_read_file_meta($headers) | - * | onMetaParsed | file_meta($meta) | - * | onContentParsing | before_parse_content($rawContent) | - * | onContentParsed | after_parse_content($content) | - * | onContentParsed | content_parsed($content) | - * | onSinglePageLoaded | get_page_data($pages, $meta) | - * | onPagesLoaded | get_pages($pages, $currentPage, $previousPage, $nextPage) | - * | onTwigRegistration | before_twig_register() | - * | onPageRendering | before_render($twigVariables, $twig, $templateName) | - * | onPageRendered | after_render($output) | - * - * Since Pico 1.0 the config is stored in {@path "config/config.php"}. This - * plugin tries to read {@path "config.php"} in Pico's root dir and overwrites - * all settings previously specified in {@path "config/config.php"}. - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -class PicoDeprecated extends AbstractPicoPlugin -{ - /** - * This plugin is disabled by default - * - * @see AbstractPicoPlugin::$enabled - */ - protected $enabled = false; - - /** - * The requested file - * - * @see PicoDeprecated::getRequestFile() - * @var string|null - */ - protected $requestFile; - - /** - * Enables this plugin on demand and triggers the deprecated event - * plugins_loaded() - * - * @see DummyPlugin::onPluginsLoaded() - */ - public function onPluginsLoaded(array &$plugins) - { - if (!empty($plugins)) { - foreach ($plugins as $plugin) { - if (!is_a($plugin, 'PicoPluginInterface')) { - // the plugin doesn't implement PicoPluginInterface; it uses deprecated events - // enable PicoDeprecated if it hasn't be explicitly enabled/disabled yet - if (!$this->isStatusChanged()) { - $this->setEnabled(true, true, true); - } - break; - } - } - } else { - // no plugins were found, so it actually isn't necessary to call deprecated events - // anyway, this plugin also ensures compatibility apart from events used by old plugins, - // so enable PicoDeprecated if it hasn't be explicitly enabled/disabled yet - if (!$this->isStatusChanged()) { - $this->setEnabled(true, true, true); - } - } - - if ($this->isEnabled()) { - $this->triggerEvent('plugins_loaded'); - } - } - - /** - * Triggers the deprecated event config_loaded($config) - * - * This method also defines deprecated constants, reads the `config.php` - * in Pico's root dir, enables the plugins {@link PicoParsePagesContent} - * and {@link PicoExcerpt} and makes `$config` globally accessible (the - * latter was removed with Pico 0.9 and was added again as deprecated - * feature with Pico 1.0) - * - * @see PicoDeprecated::defineConstants() - * @see PicoDeprecated::loadRootDirConfig() - * @see PicoDeprecated::enablePlugins() - * @see DummyPlugin::onConfigLoaded() - * @param array &$config array of config variables - * @return void - */ - public function onConfigLoaded(array &$config) - { - $this->defineConstants(); - $this->loadRootDirConfig($config); - $this->enablePlugins(); - $GLOBALS['config'] = &$config; - - $this->triggerEvent('config_loaded', array(&$config)); - } - - /** - * Defines deprecated constants - * - * `ROOT_DIR`, `LIB_DIR`, `PLUGINS_DIR`, `THEMES_DIR` and `CONTENT_EXT` - * are deprecated since v1.0, `CONTENT_DIR` existed just in v0.9, - * `CONFIG_DIR` just for a short time between v0.9 and v1.0 and - * `CACHE_DIR` was dropped with v1.0 without a replacement. - * - * @see PicoDeprecated::onConfigLoaded() - * @return void - */ - protected function defineConstants() - { - if (!defined('ROOT_DIR')) { - define('ROOT_DIR', $this->getRootDir()); - } - if (!defined('CONFIG_DIR')) { - define('CONFIG_DIR', $this->getConfigDir()); - } - if (!defined('LIB_DIR')) { - $picoReflector = new ReflectionClass('Pico'); - define('LIB_DIR', dirname($picoReflector->getFileName()) . '/'); - } - if (!defined('PLUGINS_DIR')) { - define('PLUGINS_DIR', $this->getPluginsDir()); - } - if (!defined('THEMES_DIR')) { - define('THEMES_DIR', $this->getThemesDir()); - } - if (!defined('CONTENT_DIR')) { - define('CONTENT_DIR', $this->getConfig('content_dir')); - } - if (!defined('CONTENT_EXT')) { - define('CONTENT_EXT', $this->getConfig('content_ext')); - } - } - - /** - * Read config.php in Pico's root dir - * - * @see PicoDeprecated::onConfigLoaded() - * @see Pico::loadConfig() - * @param array &$realConfig array of config variables - * @return void - */ - protected function loadRootDirConfig(array &$realConfig) - { - if (file_exists($this->getRootDir() . 'config.php')) { - // config.php in Pico::$rootDir is deprecated - // use config.php in Pico::$configDir instead - $config = null; - require($this->getRootDir() . 'config.php'); - - if (is_array($config)) { - if (isset($config['base_url'])) { - $config['base_url'] = rtrim($config['base_url'], '/') . '/'; - } - if (isset($config['content_dir'])) { - $config['content_dir'] = rtrim($config['content_dir'], '/\\') . '/'; - } - - $realConfig = $config + $realConfig; - } - } - } - - /** - * Enables the plugins PicoParsePagesContent and PicoExcerpt - * - * @see PicoParsePagesContent - * @see PicoExcerpt - * @return void - */ - protected function enablePlugins() - { - // enable PicoParsePagesContent and PicoExcerpt - // we can't enable them during onPluginsLoaded because we can't know - // if the user disabled us (PicoDeprecated) manually in the config - $plugins = $this->getPlugins(); - if (isset($plugins['PicoParsePagesContent'])) { - // parse all pages content if this plugin hasn't - // be explicitly enabled/disabled yet - if (!$plugins['PicoParsePagesContent']->isStatusChanged()) { - $plugins['PicoParsePagesContent']->setEnabled(true, true, true); - } - } - if (isset($plugins['PicoExcerpt'])) { - // enable excerpt plugin if it hasn't be explicitly enabled/disabled yet - if (!$plugins['PicoExcerpt']->isStatusChanged()) { - $plugins['PicoExcerpt']->setEnabled(true, true, true); - } - } - } - - /** - * Triggers the deprecated event request_url($url) - * - * @see DummyPlugin::onRequestUrl() - */ - public function onRequestUrl(&$url) - { - $this->triggerEvent('request_url', array(&$url)); - } - - /** - * Sets PicoDeprecated::$requestFile to trigger the deprecated - * events after_load_content() and after_404_load_content() - * - * @see PicoDeprecated::onContentLoaded() - * @see PicoDeprecated::on404ContentLoaded() - * @see DummyPlugin::onRequestFile() - */ - public function onRequestFile(&$file) - { - $this->requestFile = &$file; - } - - /** - * Triggers the deprecated before_load_content($file) - * - * @see DummyPlugin::onContentLoading() - */ - public function onContentLoading(&$file) - { - $this->triggerEvent('before_load_content', array(&$file)); - } - - /** - * Triggers the deprecated event after_load_content($file, $rawContent) - * - * @see DummyPlugin::onContentLoaded() - */ - public function onContentLoaded(&$rawContent) - { - $this->triggerEvent('after_load_content', array(&$this->requestFile, &$rawContent)); - } - - /** - * Triggers the deprecated before_404_load_content($file) - * - * @see DummyPlugin::on404ContentLoading() - */ - public function on404ContentLoading(&$file) - { - $this->triggerEvent('before_404_load_content', array(&$file)); - } - - /** - * Triggers the deprecated event after_404_load_content($file, $rawContent) - * - * @see DummyPlugin::on404ContentLoaded() - */ - public function on404ContentLoaded(&$rawContent) - { - $this->triggerEvent('after_404_load_content', array(&$this->requestFile, &$rawContent)); - } - - /** - * Triggers the deprecated event before_read_file_meta($headers) - * - * @see DummyPlugin::onMetaHeaders() - */ - public function onMetaHeaders(array &$headers) - { - $this->triggerEvent('before_read_file_meta', array(&$headers)); - } - - /** - * Triggers the deprecated event file_meta($meta) - * - * @see DummyPlugin::onMetaParsed() - */ - public function onMetaParsed(array &$meta) - { - $this->triggerEvent('file_meta', array(&$meta)); - } - - /** - * Triggers the deprecated event before_parse_content($rawContent) - * - * @see DummyPlugin::onContentParsing() - */ - public function onContentParsing(&$rawContent) - { - $this->triggerEvent('before_parse_content', array(&$rawContent)); - } - - /** - * Triggers the deprecated events after_parse_content($content) and - * content_parsed($content) - * - * @see DummyPlugin::onContentParsed() - */ - public function onContentParsed(&$content) - { - $this->triggerEvent('after_parse_content', array(&$content)); - - // deprecated since v0.8 - $this->triggerEvent('content_parsed', array(&$content)); - } - - /** - * Triggers the deprecated event get_page_data($pages, $meta) - * - * @see DummyPlugin::onSinglePageLoaded() - */ - public function onSinglePageLoaded(array &$pageData) - { - $this->triggerEvent('get_page_data', array(&$pageData, $pageData['meta'])); - } - - /** - * Triggers the deprecated event - * get_pages($pages, $currentPage, $previousPage, $nextPage) - * - * Please note that the `get_pages()` event gets `$pages` passed without a - * array index. The index is rebuild later using either the `id` array key - * or is derived from the `url` array key. Duplicates are prevented by - * adding `~dup` when necessary. - * - * @see DummyPlugin::onPagesLoaded() - */ - public function onPagesLoaded( - array &$pages, - array &$currentPage = null, - array &$previousPage = null, - array &$nextPage = null - ) { - // remove keys of pages array - $plainPages = array(); - foreach ($pages as &$pageData) { - $plainPages[] = &$pageData; - } - unset($pageData); - - $this->triggerEvent('get_pages', array(&$plainPages, &$currentPage, &$previousPage, &$nextPage)); - - // re-index pages array - $pages = array(); - foreach ($plainPages as &$pageData) { - if (!isset($pageData['id'])) { - $urlPrefixLength = strlen($this->getBaseUrl()) + intval(!$this->isUrlRewritingEnabled()); - $pageData['id'] = substr($pageData['url'], $urlPrefixLength); - } - - // prevent duplicates - $id = $pageData['id']; - for ($i = 1; isset($pages[$id]); $i++) { - $id = $pageData['id'] . '~dup' . $i; - } - - $pages[$id] = &$pageData; - } - } - - /** - * Triggers the deprecated event before_twig_register() - * - * @see DummyPlugin::onTwigRegistration() - */ - public function onTwigRegistration() - { - $this->triggerEvent('before_twig_register'); - } - - /** - * Triggers the deprecated event before_render($twigVariables, $twig, $templateName) - * - * Please note that the `before_render()` event gets `$templateName` passed - * without its file extension. The file extension is later added again. - * - * @see DummyPlugin::onPageRendering() - */ - public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName) - { - // template name contains file extension since Pico 1.0 - $fileExtension = ''; - if (($fileExtensionPos = strrpos($templateName, '.')) !== false) { - $fileExtension = substr($templateName, $fileExtensionPos); - $templateName = substr($templateName, 0, $fileExtensionPos); - } - - $this->triggerEvent('before_render', array(&$twigVariables, &$twig, &$templateName)); - - // add original file extension - $templateName = $templateName . $fileExtension; - } - - /** - * Triggers the deprecated event after_render($output) - * - * @see DummyPlugin::onPageRendered() - */ - public function onPageRendered(&$output) - { - $this->triggerEvent('after_render', array(&$output)); - } - - /** - * Triggers a deprecated event on all plugins - * - * Deprecated events are also triggered on plugins which implement - * {@link PicoPluginInterface}. Please note that the methods are called - * directly and not through {@link PicoPluginInterface::handleEvent()}. - * - * @param string $eventName event to trigger - * @param array $params parameters to pass - * @return void - */ - protected function triggerEvent($eventName, array $params = array()) - { - foreach ($this->getPlugins() as $plugin) { - if (method_exists($plugin, $eventName)) { - call_user_func_array(array($plugin, $eventName), $params); - } - } - } -} diff --git a/plugins/01-PicoParsePagesContent.php b/plugins/01-PicoParsePagesContent.php @@ -1,40 +0,0 @@ -<?php - -/** - * Parses the contents of all pages - * - * This plugin exists for backward compatibility and is disabled by default. - * It gets automatically enabled when {@link PicoDeprecated} is enabled. You - * can avoid this by calling {@link PicoParsePagesContent::setEnabled()}. - * - * This plugin heavily impacts Pico's performance, you should avoid to enable - * it whenever possible! If you must parse the contents of a page, do this - * selectively and only for pages you really need to. - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -class PicoParsePagesContent extends AbstractPicoPlugin -{ - /** - * This plugin is disabled by default - * - * @see AbstractPicoPlugin::$enabled - */ - protected $enabled = false; - - /** - * Parses the contents of all pages - * - * @see DummyPlugin::onSinglePageLoaded() - */ - public function onSinglePageLoaded(array &$pageData) - { - if (!isset($pageData['content'])) { - $pageData['content'] = $this->prepareFileContent($pageData['raw_content'], $pageData['meta']); - $pageData['content'] = $this->parseFileContent($pageData['content']); - } - } -} diff --git a/plugins/02-PicoExcerpt.php b/plugins/02-PicoExcerpt.php @@ -1,81 +0,0 @@ -<?php - -/** - * Creates a excerpt for the contents of each page (as of Pico v0.9 and older) - * - * This plugin exists for backward compatibility and is disabled by default. - * It gets automatically enabled when {@link PicoDeprecated} is enabled. You - * can avoid this by calling {@link PicoExcerpt::setEnabled()}. - * - * This plugin doesn't do its job very well and depends on - * {@link PicoParsePagesContent}, what heavily impacts Pico's performance. You - * should either use the Description meta header field or write something own. - * Best solution seems to be a filter for twig, see e.g. - * {@link https://gist.github.com/james2doyle/6629712}. - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -class PicoExcerpt extends AbstractPicoPlugin -{ - /** - * This plugin is disabled by default - * - * @see AbstractPicoPlugin::$enabled - */ - protected $enabled = false; - - /** - * This plugin depends on PicoParsePagesContent - * - * @see PicoParsePagesContent - * @see AbstractPicoPlugin::$dependsOn - */ - protected $dependsOn = array('PicoParsePagesContent'); - - /** - * Adds the default excerpt length of 50 words to the config - * - * @see DummyPlugin::onConfigLoaded() - */ - public function onConfigLoaded(array &$config) - { - if (!isset($config['excerpt_length'])) { - $config['excerpt_length'] = 50; - } - } - - /** - * Creates a excerpt for the contents of each page - * - * @see PicoExcerpt::createExcerpt() - * @see DummyPlugin::onSinglePageLoaded() - */ - public function onSinglePageLoaded(array &$pageData) - { - if (!isset($pageData['excerpt'])) { - $pageData['excerpt'] = $this->createExcerpt( - strip_tags($pageData['content']), - $this->getConfig('excerpt_length') - ); - } - } - - /** - * Helper function to create a excerpt of a string - * - * @param string $string the string to create a excerpt from - * @param int $wordLimit the maximum number of words the excerpt should be long - * @return string excerpt of $string - */ - protected function createExcerpt($string, $wordLimit) - { - $words = explode(' ', $string); - if (count($words) > $wordLimit) { - return trim(implode(' ', array_slice($words, 0, $wordLimit))) . '&hellip;'; - } - return $string; - } -} diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php @@ -1,314 +0,0 @@ -<?php - -/** - * Pico dummy plugin - a template for plugins - * - * You're a plugin developer? This template may be helpful :-) - * Simply remove the events you don't need and add your own logic. - * - * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - * @version 1.0 - */ -final class DummyPlugin extends AbstractPicoPlugin -{ - /** - * This plugin is enabled by default? - * - * @see AbstractPicoPlugin::$enabled - * @var boolean - */ - protected $enabled = false; - - /** - * This plugin depends on ... - * - * @see AbstractPicoPlugin::$dependsOn - * @var string[] - */ - protected $dependsOn = array(); - - /** - * Triggered after Pico has loaded all available plugins - * - * This event is triggered nevertheless the plugin is enabled or not. - * It is NOT guaranteed that plugin dependencies are fulfilled! - * - * @see Pico::getPlugin() - * @see Pico::getPlugins() - * @param object[] &$plugins loaded plugin instances - * @return void - */ - public function onPluginsLoaded(array &$plugins) - { - // your code - } - - /** - * Triggered after Pico has read its configuration - * - * @see Pico::getConfig() - * @param array &$config array of config variables - * @return void - */ - public function onConfigLoaded(array &$config) - { - // your code - } - - /** - * Triggered after Pico has evaluated the request URL - * - * @see Pico::getRequestUrl() - * @param string &$url part of the URL describing the requested contents - * @return void - */ - public function onRequestUrl(&$url) - { - // your code - } - - /** - * Triggered after Pico has discovered the content file to serve - * - * @see Pico::getBaseUrl() - * @see Pico::getRequestFile() - * @param string &$file absolute path to the content file to serve - * @return void - */ - public function onRequestFile(&$file) - { - // your code - } - - /** - * Triggered before Pico reads the contents of the file to serve - * - * @see Pico::loadFileContent() - * @see DummyPlugin::onContentLoaded() - * @param string &$file path to the file which contents will be read - * @return void - */ - public function onContentLoading(&$file) - { - // your code - } - - /** - * Triggered after Pico has read the contents of the file to serve - * - * @see Pico::getRawContent() - * @param string &$rawContent raw file contents - * @return void - */ - public function onContentLoaded(&$rawContent) - { - // your code - } - - /** - * Triggered before Pico reads the contents of a 404 file - * - * @see Pico::load404Content() - * @see DummyPlugin::on404ContentLoaded() - * @param string &$file path to the file which contents were requested - * @return void - */ - public function on404ContentLoading(&$file) - { - // your code - } - - /** - * Triggered after Pico has read the contents of the 404 file - * - * @see Pico::getRawContent() - * @param string &$rawContent raw file contents - * @return void - */ - public function on404ContentLoaded(&$rawContent) - { - // your code - } - - /** - * Triggered when Pico reads its known meta header fields - * - * @see Pico::getMetaHeaders() - * @param string[] &$headers list of known meta header - * fields; the array value specifies the YAML key to search for, the - * array key is later used to access the found value - * @return void - */ - public function onMetaHeaders(array &$headers) - { - // your code - } - - /** - * Triggered before Pico parses the meta header - * - * @see Pico::parseFileMeta() - * @see DummyPlugin::onMetaParsed() - * @param string &$rawContent raw file contents - * @param string[] &$headers known meta header fields - * @return void - */ - public function onMetaParsing(&$rawContent, array &$headers) - { - // your code - } - - /** - * Triggered after Pico has parsed the meta header - * - * @see Pico::getFileMeta() - * @param string[] &$meta parsed meta data - * @return void - */ - public function onMetaParsed(array &$meta) - { - // your code - } - - /** - * Triggered before Pico parses the pages content - * - * @see Pico::prepareFileContent() - * @see DummyPlugin::prepareFileContent() - * @see DummyPlugin::onContentParsed() - * @param string &$rawContent raw file contents - * @return void - */ - public function onContentParsing(&$rawContent) - { - // your code - } - - /** - * Triggered after Pico has prepared the raw file contents for parsing - * - * @see Pico::parseFileContent() - * @see DummyPlugin::onContentParsed() - * @param string &$content prepared file contents for parsing - * @return void - */ - public function onContentPrepared(&$content) - { - // your code - } - - /** - * Triggered after Pico has parsed the contents of the file to serve - * - * @see Pico::getFileContent() - * @param string &$content parsed contents - * @return void - */ - public function onContentParsed(&$content) - { - // your code - } - - /** - * Triggered before Pico reads all known pages - * - * @see Pico::readPages() - * @see DummyPlugin::onSinglePageLoaded() - * @see DummyPlugin::onPagesLoaded() - * @return void - */ - public function onPagesLoading() - { - // your code - } - - /** - * Triggered when Pico reads a single page from the list of all known pages - * - * The `$pageData` parameter consists of the following values: - * - * | Array key | Type | Description | - * | -------------- | ------ | ---------------------------------------- | - * | id | string | relative path to the content file | - * | url | string | URL to the page | - * | title | string | title of the page (YAML header) | - * | description | string | description of the page (YAML header) | - * | author | string | author of the page (YAML header) | - * | time | string | timestamp derived from the Date header | - * | date | string | date of the page (YAML header) | - * | date_formatted | string | formatted date of the page | - * | raw_content | string | raw, not yet parsed contents of the page | - * | meta | string | parsed meta data of the page | - * - * @see DummyPlugin::onPagesLoaded() - * @param array &$pageData data of the loaded page - * @return void - */ - public function onSinglePageLoaded(array &$pageData) - { - // your code - } - - /** - * Triggered after Pico has read all known pages - * - * See {@link DummyPlugin::onSinglePageLoaded()} for details about the - * structure of the page data. - * - * @see Pico::getPages() - * @see Pico::getCurrentPage() - * @see Pico::getPreviousPage() - * @see Pico::getNextPage() - * @param array[] &$pages data of all known pages - * @param array|null &$currentPage data of the page being served - * @param array|null &$previousPage data of the previous page - * @param array|null &$nextPage data of the next page - * @return void - */ - public function onPagesLoaded( - array &$pages, - array &$currentPage = null, - array &$previousPage = null, - array &$nextPage = null - ) { - // your code - } - - /** - * Triggered before Pico registers the twig template engine - * - * @return void - */ - public function onTwigRegistration() - { - // your code - } - - /** - * Triggered before Pico renders the page - * - * @see Pico::getTwig() - * @see DummyPlugin::onPageRendered() - * @param Twig_Environment &$twig twig template engine - * @param array &$twigVariables template variables - * @param string &$templateName file name of the template - * @return void - */ - public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName) - { - // your code - } - - /** - * Triggered after Pico has rendered the page - * - * @param string &$output contents which will be sent to the user - * @return void - */ - public function onPageRendered(&$output) - { - // your code - } -} diff --git a/plugins/index.html b/plugins/index.html diff --git a/plugins/pico_pages_images.php b/plugins/pico_pages_images.php @@ -1,87 +0,0 @@ -<?php -class Pico_Pages_Images -{ - private $path; - private $root; - - // Pico hooks --------------- - - /** - * Register the images path. - */ - public function request_url(&$url) - { - $this->path = $this->format($url); - } - public function config_loaded(&$settings) - { - if( !empty($settings['images_path']) ) - $this->root = $this->format($settings['images_path']); - else $this->root = 'content/'; - } - - /** - * Register the images data in {{ images }} Twig variable. - */ - public function before_render(&$twig_vars, &$twig) - { - $twig_vars['images'] = $this->images_list($twig_vars['base_url']); - } - - - // CORE --------------- - - /** - * Format given path. Remove trailing 'index' and add trailing slash if missing. - */ - private function format($path) - { - if( !$path ) return; - - $is_index = strripos($path, 'index') === strlen($path)-5; - if( $is_index ) return substr($path, 0, -5); - elseif( substr($path, -1) != '/' ) $path .= '/'; - - return $path; - } - /** - * Return the list and infos of images in the current directory. - */ - private function images_list($base_url) - { - $images_path = $this->root . $this->path; - - $data = array(); - $pattern = '*.{[jJ][pP][gG],[jJ][pP][eE][gG],[pP][nN][gG],[gG][iI][fF]}'; - $images = glob(ROOT_DIR .'/'. $images_path . $pattern, GLOB_BRACE); - - natsort($images); - foreach( $images as $path ) - { - list($dirname, $basename, $ext, $filename) = array_values(pathinfo($path)); - list($width, $height, $type, $size, $mime) = getimagesize($path); - - $ftype = $dirname . '/' . $filename; - - if (file_exists($ftype . '.pdf')) - $filetype = 'pdf'; - elseif (file_exists($ftype . '.txt')) - $filetype = 'txt'; - else - $filetype = $ext; - - $data[] = array ( - 'url' => $base_url .'/'. $images_path . $basename, - 'path' => $images_path, - 'name' => $filename, - 'ext' => $ext, - 'width' => $width, - 'height' => $height, - 'type' => $filetype, - 'size' => $size - ); - } - return $data; - } -} -?> diff --git a/plugins/pico_plugin.php b/plugins/pico_plugin.php @@ -1,94 +0,0 @@ -<?php - -/** - * Example hooks for a Pico plugin - * - * @author Gilbert Pellegrom - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT - */ -class Pico_Plugin { - - public function plugins_loaded() - { - - } - - public function config_loaded(&$settings) - { - - } - - public function request_url(&$url) - { - - } - - public function before_load_content(&$file) - { - - } - - public function after_load_content(&$file, &$content) - { - - } - - public function before_404_load_content(&$file) - { - - } - - public function after_404_load_content(&$file, &$content) - { - - } - - public function before_read_file_meta(&$headers) - { - - } - - public function file_meta(&$meta) - { - - } - - public function before_parse_content(&$content) - { - - } - - public function after_parse_content(&$content) - { - - } - - public function get_page_data(&$data, $page_meta) - { - - } - - public function get_pages(&$pages, &$current_page, &$prev_page, &$next_page) - { - - } - - public function before_twig_register() - { - - } - - public function before_render(&$twig_vars, &$twig, &$template) - { - - } - - public function after_render(&$output) - { - - } - -} - -?> diff --git a/plugins/pico_sitemap.php b/plugins/pico_sitemap.php @@ -1,90 +0,0 @@ -<?php - -/** - * Generate an xml sitemap for Pico - * - * @author Dave Kinsella - * @link https://github.com/Techn0tic/Pico_Sitemap - * @license http://opensource.org/licenses/MIT - * - * @author Tom Witkowski - * @link https://github.com/Gummibeer - */ -class Pico_Sitemap { - private $is_sitemap; - private $is_html_sitemap; - private $exclude; - private $pages; - - public function __construct() { - $this->is_sitemap = false; - $this->is_html_sitemap = false; - $this->exclude = array('home', 'about'); - $this->pages = array(); - } - - public function request_url(&$url) { - if($url == 'sitemap') $this->is_html_sitemap = true; - if($url == 'sitemap.xml') $this->is_sitemap = true; - } - - public function get_pages($pages) { - if($this->is_sitemap || $this->is_html_sitemap) { - global $config; - foreach($pages as $page) { - preg_match('/\/('.implode('|', $this->exclude).')\//', $page['url'].'/', $matches); - if(empty($matches)) { - $index = ''; - $date = false; - $path = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'content'.DIRECTORY_SEPARATOR.str_ireplace($config['base_url'], '', $page['url']); - if(is_dir($path)) { $index = 'index'; } - $file = realpath($path.$index.'.md'); - if(file_exists($file)) { $date = date ("Y-m-d", filemtime($file)); } - array_push($this->pages, array( 'url' => $page['url'], 'title' => $page['title'], 'lastmod' => $date, 'index' => $index == '' ? false : true )); - } - } - sort($this->pages); - } - } - - public function before_render(&$twig_vars, &$twig, &$template) { - if($this->is_sitemap) { - header($_SERVER['SERVER_PROTOCOL'].' 200 OK'); - header('Content-Type: application/xml; charset=UTF-8'); - $xml = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'; - foreach($this->pages as $page) { - $xml .= '<url><loc>'.$page['url'].'</loc><lastmod>'.$page['lastmod'].'</lastmod></url>'; - } - $xml .= '</urlset>'; - header('Content-Type: text/xml'); - die($xml); - } elseif($this->is_html_sitemap) { - $panel_raw = '<div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title"><a href="%url%">%title%</a></h3></div><div class="list-group">%childs%</div></div></div>'; - $child_raw = '<a href="%url%" class="list-group-item"><i class="icon-link"></i> %title%</a>'; - - $panel = ''; - $childs = array(); - $out = ''; - $out .= '<div class="row">'; - foreach($this->pages as $page) { - if($page['index']) { - $panel = str_replace('%childs%', implode('', $childs), $panel); - $out .= $panel; - $childs = array(); - $panel = $panel_raw; - $panel = str_replace('%url%', $page['url'], $panel); - $panel = str_replace('%title%', $page['title'], $panel); - } else { - $child = $child_raw; - $child = str_replace('%url%', $page['url'], $child); - $child = str_replace('%title%', $page['title'], $child); - array_push($childs, $child); - } - } - $panel = str_replace('%childs%', implode('', $childs), $panel); - $out .= $panel; - $out .= '</div>'; - $twig_vars['sitemap'] = $out; - } - } -} diff --git a/server b/server @@ -1,3 +0,0 @@ -#!/bin/bash - -php -S localhost:8000 diff --git a/themes/beardyjay/css/style.css b/themes/beardyjay/css/style.css @@ -1,199 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Fjalla+One); /* main font */ -code { - background-color: #eee; - padding-left: 6px; - padding-right: 6px; -} - -#post h1 { - font-size: 28px; - color: #333; - border-bottom: 1px dotted #999; -} - -#wrapper { - margin: auto; - width: 50%; -} - -#header { - height: 100px; -} - -nav { - border: 0; - letter-spacing: -1px; - border: 0; - margin: 25px 25px 25px 0px; - float: right; - display: block; -} - -#post a { - text-decoration: none; -} - -a h1 { - letter-spacing: -1px; - border: 0; - text-transform: uppercase; - font-size: 24px; - font-family: 'Fjalla One', sans-serif; - color: #333; - text-decoration: none; -} - -a h1:hover, a h1:hover { - letter-spacing: -1px; - border: 0; - color: #73b1d2; -} - -a { - color: #73b1d2; -} - -nav a { - letter-spacing: -1px; - border: 0; - text-transform: uppercase; - font-size: 18px; - font-family: 'Fjalla One', sans-serif; - color: #8e8e8e; - text-decoration: none; - background-color: #eee; - padding: 2px 10px 2px 10px; -} - -nav a:hover { - background-color: #444; - color: white; -} - -img.center { - display: block; - margin-left: auto; - margin-right: auto; -} - -#content { - padding-top: 30px; - padding-left: 15px; -} - -#posts { - margin-left: 10px; -} - -#post { -} - -h1, h2, h3, h4, h5 { - letter-spacing: -1px; - border: 0; - text-transform: uppercase; - font-size: 18px; - font-family: 'Fjalla One', sans-serif; - color: #555; - text-decoration: none; -} - -.summary { - font-size: 14px; - color: #666; - font-family: helvetica, sans-serif; - margin-left: 14px; - margin-bottom: 75px; -} - -#footer { - margin-top: 4px; - margin-left: 35px; - font-size: 14px; - margin-bottom: 30px; -} - -.icon:hover { - color: #555; -} - -.icon { - color: #eee; - font-size: 20px !important; - margin-right: 6px; -} - -code { - padding-left: 0; - padding-right: 0; -} - -.summary pre { - background-color: #eee; - padding: 5px; -} - -table { - -webkit-font-smoothing: antialiased; - width: auto; - overflow: auto; - display: block; -} - -table th { - background-color: #333; - font-weight: normal; - color: white; - padding: 5px 10px; - text-align: center; -} - -table td { - padding: 5px 10px; -} - -blockquote { - display: block; - background: #fff; - padding: 15px 20px 15px 45px; - margin: 0 0 20px; - position: relative; -/*Font*/ - font-family: Georgia, serif; - font-size: 16px; - line-height: 1.2; - color: #666; - text-align: justify; - border-left: 15px solid #333; - border-right: 2px solid #333; - -moz-box-shadow: 2px 2px 15px #ccc; - -webkit-box-shadow: 2px 2px 15px #ccc; - box-shadow: 2px 2px 15px #ccc; -} - -blockquote::before { - content: "\201C"; - font-family: Georgia, serif; - font-size: 60px; - font-weight: bold; - color: #999; - position: absolute; - left: 10px; - top: 5px; -} - -blockquote::after { - content: ""; -} - -blockquote a { - text-decoration: none; - background: #eee; - cursor: pointer; - padding: 0 3px; - color: #c76c0c; -} - -blockquote a:hover { - color: #666; -} diff --git a/themes/beardyjay/img/logo-orig.png b/themes/beardyjay/img/logo-orig.png Binary files differ. diff --git a/themes/beardyjay/img/logo.png b/themes/beardyjay/img/logo.png Binary files differ. diff --git a/themes/beardyjay/index.html b/themes/beardyjay/index.html @@ -1,72 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <meta name="author" content="beardyjay"> - <link rel="stylesheet" type="text/css" href="{{ theme_url }}/css/style.css"> - <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css"> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/styles/default.min.css"> - <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/highlight.min.js"></script> - <link rel="icon" type="image/png" href="/favicon.ico"> - <title>{% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }}</title> - {% if meta.description %} - <meta name="description" content="{{ meta.description }}"> - {% endif %}{% if meta.robots %} - <meta name="robots" content="{{ meta.robots }}"> - {% endif %} - <script>hljs.initHighlightingOnLoad();</script> - </head> - <body> - - <div id="wrapper"> - <div id="header"> - <nav> - <a href="https://beardyjay.co.uk">home</a> - <a href="/security/">security</a> - <a href="/zines/">zines</a> - <a href="{{ site_url }}/about">about</a> - <a href="https://github.com/beardyjay/">git</a> - </nav> - </div> - - <img class="center" alt="Site logo" src='{{ theme_url }}/img//logo.png' > - {% if is_front_page %} - <div id="content"> - {% for page in pages %} - <div id="post-{{ page.date }}"> - {% if page.date %} - <a href="{{ page.url }}"> - <h1>{{ page.title }}</h1> - </a> - <div class="summary" id="summary-{{ page.date }}"> - {{ page.excerpt }} - </div> - {% endif %} - </div> - {% endfor %} - </div> - {% else %} - <div id="content"> - <div id="post-{{ page.date }}"> - <a href="{{ page.url }}"> - <h1>{{ meta.title }}</h1> - </a> - <div class="summary" id="summary-{{ page.date }}"> - - {% if meta.images %} - {% for image in images %} - <a href='https://beardyjay.co.uk/{{ image.path }}{{ image.name }}.{{ image.type }}'> - <img style="float: left; margin: 0px 15px 15px 0px;" src="{{ image.url }}" width="270" height="367"> - <a/> - {% endfor %} - {% else %} - {{ content }} - {% endif %} - </div> - </div> - </div> - {% endif %} - </div> - </body> -</html> diff --git a/themes/beardyjay/scripts/modernizr-2.6.1.min.js b/themes/beardyjay/scripts/modernizr-2.6.1.min.js @@ -1,4 +0,0 @@ -/* Modernizr 2.6.1 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-flexbox_legacy-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load - */ -;window.Modernizr=function(a,b,c){function C(a){j.cssText=a}function D(a,b){return C(n.join(a+";")+(b||""))}function E(a,b){return typeof a===b}function F(a,b){return!!~(""+a).indexOf(b)}function G(a,b){for(var d in a){var e=a[d];if(!F(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function H(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:E(f,"function")?f.bind(d||b):f}return!1}function I(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return E(b,"string")||E(b,"undefined")?G(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),H(e,b,c))}function J(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.6.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),k.id=h,(l?k:m).innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},z=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=E(e[d],"function"),E(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),A={}.hasOwnProperty,B;!E(A,"undefined")&&!E(A.call,"undefined")?B=function(a,b){return A.call(a,b)}:B=function(a,b){return b in a&&E(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return I("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!E(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!I("indexedDB",a)},s.hashchange=function(){return z("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return C("background-color:rgba(150,255,150,.5)"),F(j.backgroundColor,"rgba")},s.hsla=function(){return C("background-color:hsla(120,40%,100%,.5)"),F(j.backgroundColor,"rgba")||F(j.backgroundColor,"hsla")},s.multiplebgs=function(){return C("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return I("backgroundSize")},s.borderimage=function(){return I("borderImage")},s.borderradius=function(){return I("borderRadius")},s.boxshadow=function(){return I("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return D("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return I("animationName")},s.csscolumns=function(){return I("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return C((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),F(j.backgroundImage,"gradient")},s.cssreflections=function(){return I("boxReflect")},s.csstransforms=function(){return!!I("transform")},s.csstransforms3d=function(){var a=!!I("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return I("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(['#modernizr:after{content:"',l,'";visibility:hidden}'].join(""),function(b){a=b.offsetHeight>=1}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var K in s)B(s,K)&&(x=K.toLowerCase(),e[x]=s[K](),v.push((e[x]?"":"no-")+x));return e.input||J(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)B(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},C(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.hasEvent=z,e.testProp=function(a){return G([a])},e.testAllProps=I,e.testStyles=y,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};- \ No newline at end of file diff --git a/themes/index.html b/themes/index.html