update to version 2.3 and remove submodules

This commit is contained in:
Shannon Sterz 2016-05-26 21:02:48 +02:00
parent b11bd29981
commit 80c59a9876
402 changed files with 49907 additions and 12 deletions

6
.gitmodules vendored
View file

@ -1,9 +1,3 @@
[submodule "panel"]
path = panel
url = https://github.com/getkirby/panel.git
[submodule "kirby"]
path = kirby
url = https://github.com/getkirby/kirby.git
[submodule "site/fields/markdown"]
path = site/fields/markdown
url = https://github.com/JonasDoebertin/kirby-visual-markdown.git

View file

@ -1,6 +1,5 @@
# Sterzy.com
This is the code for the http://sterzy.com website. It is based on the kirby cms (http://getkirby.com), which has it's own license agreement ([Kirby End User License Agreement](https://github.com/getkirby/starterkit/blob/master/license.md)).
The rest of the code is under this license: [License](https://svs.ankaa.uberspace.de/sterzy/sterzycom/blob/master/LICENSE "license"), [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/).
# Installing

1
kirby

@ -1 +0,0 @@
Subproject commit b45f01ba6a6cd56781a7d091a423beb04b0d0eea

69
kirby/bootstrap.php Normal file
View file

@ -0,0 +1,69 @@
<?php
if(!defined('KIRBY')) define('KIRBY', true);
if(!defined('DS')) define('DS', DIRECTORY_SEPARATOR);
// load the kirby toolkit
include_once(__DIR__ . DS . 'toolkit' . DS . 'bootstrap.php');
// load all core classes
load(array(
// kirby class and subclasses
'kirby' => __DIR__ . DS . 'kirby.php',
'kirby\\roots' => __DIR__ . DS . 'kirby' . DS . 'roots.php',
'kirby\\urls' => __DIR__ . DS . 'kirby' . DS . 'urls.php',
'kirby\\component' => __DIR__ . DS . 'kirby' . DS . 'component.php',
'kirby\\registry' => __DIR__ . DS . 'kirby' . DS . 'registry.php',
'kirby\\request' => __DIR__ . DS . 'kirby' . DS . 'request.php',
'kirby\\request\\params' => __DIR__ . DS . 'kirby' . DS . 'request' . DS . 'params.php',
'kirby\\request\\query' => __DIR__ . DS . 'kirby' . DS . 'request' . DS . 'query.php',
'kirby\\request\\path' => __DIR__ . DS . 'kirby' . DS . 'request' . DS . 'path.php',
// core components
'kirby\\component\\template' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'template.php',
'kirby\\component\\thumb' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'thumb.php',
'kirby\\component\\markdown' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'markdown.php',
'kirby\\component\\smartypants' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'smartypants.php',
'kirby\\component\\snippet' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'snippet.php',
'kirby\\component\\css' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'css.php',
'kirby\\component\\js' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'js.php',
'kirby\\component\\tinyurl' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'tinyurl.php',
'kirby\\component\\response' => __DIR__ . DS . 'kirby' . DS . 'component' . DS . 'response.php',
// traits
'kirby\\traits\\image' => __DIR__ . DS . 'kirby' . DS . 'traits' . DS . 'image.php',
// all core abstracts
'assetabstract' => __DIR__ . DS . 'core' . DS . 'asset.php',
'avatarabstract' => __DIR__ . DS . 'core' . DS . 'avatar.php',
'pagesabstract' => __DIR__ . DS . 'core' . DS . 'pages.php',
'childrenabstract' => __DIR__ . DS . 'core' . DS . 'children.php',
'contentabstract' => __DIR__ . DS . 'core' . DS . 'content.php',
'fieldabstract' => __DIR__ . DS . 'core' . DS . 'field.php',
'fileabstract' => __DIR__ . DS . 'core' . DS . 'file.php',
'filesabstract' => __DIR__ . DS . 'core' . DS . 'files.php',
'kirbytextabstract' => __DIR__ . DS . 'core' . DS . 'kirbytext.php',
'kirbytagabstract' => __DIR__ . DS . 'core' . DS . 'kirbytag.php',
'pageabstract' => __DIR__ . DS . 'core' . DS . 'page.php',
'roleabstract' => __DIR__ . DS . 'core' . DS . 'role.php',
'rolesabstract' => __DIR__ . DS . 'core' . DS . 'roles.php',
'siteabstract' => __DIR__ . DS . 'core' . DS . 'site.php',
'usersabstract' => __DIR__ . DS . 'core' . DS . 'users.php',
'userabstract' => __DIR__ . DS . 'core' . DS . 'user.php',
// lib
'pageextension' => __DIR__ . DS . 'lib' . DS . 'pageextension.php',
'structure' => __DIR__ . DS . 'lib' . DS . 'structure.php',
// parsedown
'parsedown' => __DIR__ . DS . 'vendors' . DS . 'parsedown.php',
'parsedownextra' => __DIR__ . DS . 'vendors' . DS . 'parsedownextra.php',
// smartypants
'smartypantstypographer_parser' => __DIR__ . DS . 'vendors' . DS . 'smartypants.php',
));
// load all helper functions
include(__DIR__ . DS . 'helpers.php');

View file

@ -0,0 +1,18 @@
<?php
class Asset extends AssetAbstract {}
class Avatar extends AvatarAbstract {}
class Page extends PageAbstract {}
class Pages extends PagesAbstract {}
class Children extends ChildrenAbstract {}
class Content extends ContentAbstract {}
class Field extends FieldAbstract {}
class File extends FileAbstract {}
class Files extends FilesAbstract {}
class Kirbytext extends KirbytextAbstract {}
class Kirbytag extends KirbytagAbstract {}
class Role extends RoleAbstract {}
class Roles extends RolesAbstract {}
class Site extends SiteAbstract {}
class Users extends UsersAbstract {}
class User extends UserAbstract {}

View file

@ -0,0 +1,29 @@
<?php
/**
* Unmodified classes
*/
class Asset extends AssetAbstract {}
class Avatar extends AvatarAbstract {}
class Pages extends PagesAbstract {}
class Children extends ChildrenAbstract {}
class Files extends FilesAbstract {}
class Kirbytext extends KirbytextAbstract {}
class Kirbytag extends KirbytagAbstract {}
class Role extends RoleAbstract {}
class Roles extends RolesAbstract {}
class Users extends UsersAbstract {}
class User extends UserAbstract {}
/**
* Modified classes
*/
load(array(
'content' => __DIR__ . DS . 'multilang' . DS . 'content.php',
'field' => __DIR__ . DS . 'multilang' . DS . 'field.php',
'file' => __DIR__ . DS . 'multilang' . DS . 'file.php',
'language' => __DIR__ . DS . 'multilang' . DS . 'language.php',
'languages' => __DIR__ . DS . 'multilang' . DS . 'languages.php',
'page' => __DIR__ . DS . 'multilang' . DS . 'page.php',
'site' => __DIR__ . DS . 'multilang' . DS . 'site.php',
));

View file

@ -0,0 +1,41 @@
<?php
/**
* Content
*/
class Content extends ContentAbstract {
public $language = null;
/**
* Constructor
*/
public function __construct($page, $root, $language) {
parent::__construct($page, $root);
$this->name = f::name($this->name);
$this->language = $language;
}
public function realroot() {
return dirname($this->root()) . DS . $this->name() . '.' . $this->language . '.' . f::extension($this->root());
}
public function exists() {
return file_exists($this->realroot());
}
public function language() {
if(!is_null($this->language)) return $this->language;
$codes = $this->page->site()->languages()->codes();
$code = f::extension(f::name($this->root));
return $this->language = in_array($code, $codes) ? $this->page->site()->languages()->find($code) : false;
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* Field
*/
class Field extends FieldAbstract {
/**
* Returns if a field is translated in the current/provided language
* @param string $lang Language code
* @return boolean
*/
public function isTranslated($lang = null) {
$site = $this->page->site();
// use current language if $lang not set
if(is_null($lang)) $lang = $site->language()->code();
// if language is default/fallback language
if($site->language($lang)->default()) return true;
$current = $this->page->content($lang);
$default = $this->page->content($site->defaultLanguage->code);
$field = $current->get($this->key);
$untranslated = $default->get($this->key)->value();
return $field->isNotEmpty() and $field->value() !== $untranslated;
}
}

View file

@ -0,0 +1,170 @@
<?php
/**
* File
*/
class File extends FileAbstract {
/**
* Returns the full root for the content file
*
* @return string
*/
public function textfile($lang = null) {
return $this->page->textfile($this->filename(), $lang);
}
/**
* Get the meta information
*
* @param string $lang optional language code
* @return Content
*/
public function meta($lang = null) {
// get the content for the current language
if(is_null($lang)) {
// the current language's content can be cached
if(isset($this->cache['meta'])) return $this->cache['meta'];
// get the current content
$meta = $this->_meta($this->site->language->code);
// get the fallback content
if($this->site->language->code != $this->site->defaultLanguage->code) {
// fetch the default language content
$defaultMeta = $this->_meta($this->site->defaultLanguage->code);
// replace all missing fields with values from the default content
foreach($defaultMeta->data as $key => $field) {
if(empty($meta->data[$key]->value)) {
$meta->data[$key] = $field;
}
}
}
// cache the meta for this language
return $this->cache['meta'] = $meta;
// get the meta for another language
} else {
return $this->_meta($lang);
}
}
/**
* Private method to simplify meta fetching
*
* @return Content
*/
protected function _meta($lang) {
// get the inventory
$inventory = $this->page->inventory();
// try to fetch the content for this language
$meta = isset($inventory['meta'][$this->filename][$lang]) ? $inventory['meta'][$this->filename][$lang] : null;
// try to replace empty content with the default language content
if(empty($meta) and isset($inventory['meta'][$this->filename][$this->site->defaultLanguage->code])) {
$meta = $inventory['meta'][$this->filename][$this->site->defaultLanguage->code];
}
// find and cache the content for this language
return new Content($this->page, $this->page->root() . DS . $meta, $lang);
}
/**
* Renames the file and also its meta info txt
*
* @param string $filename
* @param boolean $safeName
*/
public function rename($name, $safeName = true) {
$filename = $this->createNewFilename($name, $safeName);
$root = $this->dir() . DS . $filename;
if(empty($name)) {
throw new Exception('The filename is missing');
}
if($root == $this->root()) return $filename;
if(file_exists($root)) {
throw new Exception('A file with that name already exists');
}
if(!f::move($this->root(), $root)) {
throw new Exception('The file could not be renamed');
}
foreach($this->site->languages() as $lang) {
// rename all meta files
$meta = $this->textfile($lang->code());
if(file_exists($meta)) {
f::move($meta, $this->page->textfile($filename, $lang->code()));
}
}
// reset the page cache
$this->page->reset();
// reset the basics
$this->root = $root;
$this->filename = $filename;
$this->name = $name;
$this->cache = array();
cache::flush();
return $filename;
}
public function update($data = array(), $lang = null) {
$data = array_merge((array)$this->meta()->toArray(), $data);
foreach($data as $k => $v) {
if(is_null($v)) unset($data[$k]);
}
if(!data::write($this->textfile($lang), $data, 'kd')) {
throw new Exception('The file data could not be saved');
}
// reset the page cache
$this->page->reset();
// reset the file cache
$this->cache = array();
cache::flush();
return true;
}
public function delete() {
foreach($this->site->languages() as $lang) {
// delete the meta file for each language
f::remove($this->textfile($lang->code()));
}
parent::delete();
return true;
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* Language
*
* A single language object
*/
class Language extends Obj {
public function __construct($site, $lang) {
$this->site = $site;
$this->code = $lang['code'];
$this->name = $lang['name'];
$this->locale = $lang['locale'];
$this->default = (isset($lang['default']) and $lang['default']);
$this->direction = (isset($lang['direction']) and $lang['direction'] == 'rtl') ? 'rtl' : 'ltr';
$this->url = isset($lang['url']) ? $lang['url'] : $lang['code'];
}
public function url() {
return url::makeAbsolute($this->url, $this->site->url());
}
public function isDefault() {
return $this->default;
}
public function __toString() {
return $this->code;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* Languages
*
* Holds all available Language objects for the site
*/
class Languages extends Collection {
protected $site = null;
public function __construct($site) {
return $this->site = $site;
}
public function find($code) {
return isset($this->data[$code]) ? $this->data[$code] : null;
}
public function codes() {
return $this->keys();
}
public function findDefault() {
return $this->site->defaultLanguage();
}
}

View file

@ -0,0 +1,281 @@
<?php
/**
* Page
*/
class Page extends PageAbstract {
/**
* Returns the root for the content file
*
* @return string
*/
public function textfile($template = null, $lang = null) {
if(is_null($template)) $template = $this->intendedTemplate();
if(is_null($lang)) $lang = $this->site->language->code;
return textfile($this->diruri(), $template, $lang);
}
/**
* Returns the translated URI
*/
public function uri($lang = null) {
// build the page's uri with the parent uri and the page's slug
return ltrim($this->parent->uri($lang) . '/' . $this->slug($lang), '/');
}
/**
* Returns the URL key from the content file
* if available and otherwise returns the page UID
*
* @param string $lang
* @return string
*/
public function urlKey($lang = null) {
if($content = $this->content($lang)) {
// search for a translated url_key in that language
if($key = (string)a::get((array)$content->data(), 'url_key')) {
// if available, use the translated url key as slug
return $key;
}
}
return $this->uid();
}
/**
* Returns the slug for the page
* The slug is the last part of the URL path
* For multilang sites this can be translated with a URL-Key field
* in the text file for this page.
*
* @param string $lang Optional language code to get the translated slug
* @return string i.e. 01-projects returns projects
*/
public function slug($lang = null) {
$default = $this->site->defaultLanguage->code;
$current = $this->site->language->code;
// get the slug for the current language
if(is_null($lang)) {
// the current language's slug can be cached
if(isset($this->cache['slug'])) return $this->cache['slug'];
// if the current language is the default language
// simply return the uid
if($current == $default) {
return $this->cache['slug'] = $this->uid();
}
// get the translated url key
return $this->urlKey();
} else {
// if the passed language code is the current language code
// we can simply return the slug method without a language code specified
if($lang == $current) {
return $this->slug();
}
// the slug for the default language is just the name of the folder
if($lang == $default) {
return $this->uid();
}
// get the translated url key
return $this->urlKey($lang);
}
}
/**
* Returns the full url for the page
*
* @param string $lang Optional language code to get the URL for that specific language on multilang sites
* @return string
*/
public function url() {
$args = func_get_args();
$lang = array_shift($args);
// for multi language sites every url needs
// to be treated specially to make sure each uid is translated properly
// and language codes are prepended if needed
if(is_null($lang)) {
// get the current language
$lang = $this->site->language->code;
}
// Kirby is trying to remove the home folder name from the url
if($this->isHomePage()) {
return $this->site->url($lang);
} else if($this->parent->isHomePage()) {
return $this->site->url($lang) . '/' . $this->parent->slug($lang) . '/' . $this->slug($lang);
} else {
return $this->parent->url($lang) . '/' . $this->slug($lang);
}
}
/**
* Modified inventory fetcher
*
* @return array
*/
public function inventory() {
$inventory = parent::inventory();
$defaultLang = $this->site->defaultLanguage->code;
$expression = '!(.*?)(\.(' . implode('|', $this->site->languages->codes()) . ')|)\.' . $this->kirby->options['content.file.extension'] . '$!i';
foreach($inventory['meta'] as $key => $meta) {
$inventory['meta'][$key] = array($defaultLang => $meta);
}
foreach($inventory['content'] as $key => $content) {
preg_match($expression, $content, $match);
$file = $match[1];
$lang = isset($match[3]) ? $match[3] : null;
if(in_array($file, $inventory['files'])) {
$inventory['meta'][$file][$lang] = $content;
} else {
if(is_null($lang)) {
$lang = f::extension($file);
if(empty($lang)) $lang = $defaultLang;
}
$inventory['content'][$lang] = $content;
}
unset($inventory['content'][$key]);
}
// try to fill the default language with something else
if(!isset($inventory['content'][$defaultLang])) {
$inventory['content'][$defaultLang] = a::first($inventory['content']);
}
return $inventory;
}
/**
* Returns the content object for this page
*
* @param string $lang optional language code
* @return Content
*/
public function content($lang = null) {
// get the content for the current language
if(is_null($lang)) {
// the current language's content can be cached
if(isset($this->cache['content'])) return $this->cache['content'];
// get the current content
$content = $this->_content($this->site->language->code);
// get the fallback content
if($this->site->language->code != $this->site->defaultLanguage->code) {
// fetch the default language content
$defaultContent = $this->_content($this->site->defaultLanguage->code);
// replace all missing fields with values from the default content
foreach($defaultContent->data as $key => $field) {
if(empty($content->data[$key]->value)) {
$content->data[$key] = $field;
}
}
}
// find and cache the content for this language
return $this->cache['content'] = $content;
// get the content for another language
} else {
return $this->_content($lang);
}
}
/**
* Private method to simplify content fetching
*
* @return Content
*/
protected function _content($lang) {
// get the inventory
$inventory = $this->inventory();
// try to fetch the content for this language
$content = isset($inventory['content'][$lang]) ? $inventory['content'][$lang] : null;
// try to replace empty content with the default language content
if(empty($content) and isset($inventory['content'][$this->site->defaultLanguage->code])) {
$content = $inventory['content'][$this->site->defaultLanguage->code];
}
// find and cache the content for this language
return new Content($this, $this->root() . DS . $content, $lang);
}
/**
* Creates a new page object
*
* @param string $uri
* @param string $template
* @param array $data
*/
static public function create($uri, $template, $data = array()) {
return parent::create($uri, $template . '.' . site()->defaultLanguage->code, $data);
}
/**
* Update the page with a new set of data
*
* @param array $data
*/
public function update($input = array(), $lang = null) {
$data = a::update($this->content($lang)->toArray(), $input);
if(!data::write($this->textfile(null, $lang), $data, 'kd')) {
throw new Exception('The page could not be updated');
}
$this->kirby->cache()->flush();
$this->reset();
$this->touch();
return true;
}
/**
* Returns the name of the content text file / intended template
* So even if there's no such template it will return the intended name.
*
* @return string
*/
public function intendedTemplate() {
if(isset($this->cache['intendedTemplate'])) return $this->cache['intendedTemplate'];
return $this->cache['intendedTemplate'] = $this->content($this->site->defaultLanguage()->code())->exists() ? $this->content()->name() : 'default';
}
}

View file

@ -0,0 +1,193 @@
<?php
/**
* Site
*
* Modified Site object
*/
class Site extends SiteAbstract {
public $languages;
public $language;
public $defaultLanguage;
/**
* Constructor
*/
public function __construct(Kirby $kirby) {
parent::__construct($kirby);
$this->languages = new Languages($this);
foreach($kirby->options['languages'] as $lang) {
$language = new Language($this, $lang);
// store the default language
if($language->default) $this->defaultLanguage = $this->language = $language;
// add the language to the collection
$this->languages->data[$language->code] = $language;
}
}
/**
* Returns the translated URI
*/
public function uri($lang = null) {
return null;
}
public function slug($lang = null) {
return null;
}
/**
* Returns the url of the site
*
* @return string
*/
public function url($lang = false) {
if($lang) {
// return the specific language url
return $this->languages->find($lang)->url();
} else {
return parent::url();
}
}
/**
* Marks the site as a multilanguage site
*
* @return boolean
*/
public function multilang() {
return true;
}
/**
* Returns the Languages Collection
*
* @return Languages
*/
public function languages() {
return $this->languages;
}
/**
* Returns the current language
* or any other language by language code
*
* @param string $code
* @return Language
*/
public function language($code = null) {
if(is_null($code)) return $this->language;
return $this->languages()->find($code);
}
/**
* Returns the default language
*
* @return Language
*/
public function defaultLanguage() {
return $this->defaultLanguage;
}
/**
* Tries to find the language for the current visitor
*
* @return Language
*/
public function visitorLanguage() {
return $this->languages()->find(visitor::acceptedLanguageCode());
}
/**
* Returns the detected language
*
* @return Language
*/
public function detectedLanguage() {
if($language = $this->visitorLanguage()) {
return $language;
} else {
return $this->defaultLanguage();
}
}
/**
* Returns the language which will be
* remembered for the next visit
*
* @return Language
*/
public function sessionLanguage() {
if($code = s::get('language') and $language = $this->languages()->find($code)) {
return $language;
} else {
return null;
}
}
public function switchLanguage(Language $language) {
s::set('language', $language->code());
if($this->language()->code() != $language->code()) {
go($this->page()->url($language->code()));
}
}
/**
* Sets the currently active page
* and returns its page object
*
* @param string $uri
* @return Page
*/
public function visit($uri = '', $lang = null) {
// if the language code is missing or the code is invalid (TODO)
if(!in_array($lang, $this->languages()->keys())) {
$lang = $this->defaultLanguage->code;
}
// set the current language
$this->language = $this->languages()->data[$lang];
// clean the uri
$uri = trim($uri, '/');
if(empty($uri)) {
return $this->page = $this->homePage();
} else {
if($lang == $this->defaultLanguage->code and $page = $this->children()->find($uri)) {
return $this->page = $page;
} else if($page = $this->children()->findByURI($uri)) {
return $this->page = $page;
} else {
return $this->page = $this->errorPage();
}
}
}
/**
* Returns the locale for the site
*
* @return string
*/
public function locale() {
return $this->language->locale;
}
}

21
kirby/core/asset.php Normal file
View file

@ -0,0 +1,21 @@
<?php
abstract class AssetAbstract extends Media {
public $kirby = null;
use Kirby\Traits\Image;
public function __construct($path) {
$this->kirby = kirby::instance();
if(is_a($path, 'Media')) {
parent::__construct($path->root(), $path->url());
} else {
parent::__construct(
url::isAbsolute($path) ? null : $this->kirby->roots()->index() . DS . ltrim($path, DS),
url::makeAbsolute($path)
);
}
}
}

30
kirby/core/avatar.php Normal file
View file

@ -0,0 +1,30 @@
<?php
abstract class AvatarAbstract extends Media {
public $user = null;
public $kirby = null;
use Kirby\Traits\Image;
public function __construct(User $user) {
// store the parent user object
$this->user = $user;
// this should rather be coming from the user object
$this->kirby = kirby::instance();
// try to find the avatar
if($file = f::resolve($this->kirby->roots()->avatars() . DS . $user->username(), ['jpg', 'jpeg', 'gif', 'png'])) {
$filename = f::filename($file);
} else {
$filename = $user->username() . '.jpg';
$file = $this->kirby->roots()->avatars() . DS . $filename;
}
parent::__construct($file, $this->kirby->urls()->avatars() . '/' . $filename);
}
}

207
kirby/core/children.php Normal file
View file

@ -0,0 +1,207 @@
<?php
/**
* Children
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class ChildrenAbstract extends Pages {
protected $page = null;
protected $cache = array();
/**
* Constructor
*/
public function __construct($page) {
$this->page = $page;
}
/**
* Creates a new Page object and adds it to the collection
*/
public function add($dirname) {
$page = new Page($this->page, $dirname);
$this->data[$page->id()] = $page;
return $page;
}
/**
* Creates a new subpage
*
* @param string $uid
* @param string $template
* @param array $data
*/
public function create($uid, $template, $data = array()) {
$page = page::create($this->page->id() . '/' . $uid, $template, $data);
$this->data[$page->id()] = $page;
return $page;
}
/**
* Returns the parent page
*
* @return Page
*/
public function page() {
return $this->page;
}
/**
* Returns the Children of Children
*
* @return Children
*/
public function children() {
$grandChildren = new Children($this->page);
foreach($this->data as $page) {
foreach($page->children() as $subpage) {
$grandChildren->data[$subpage->id()] = $subpage;
}
}
return $grandChildren;
}
/**
* Find a specific page by its uri
*
* @return Page or false
*/
public function find() {
$args = func_get_args();
if(!count($args)) {
return false;
} else if (count($args) === 1 and is_array($args[0])) {
$args = $args[0];
}
if(count($args) > 1) {
$collection = new Children($this->page);
foreach($args as $id) {
if($page = $this->find($id)) {
$collection->data[$page->id()] = $page;
}
}
return $collection;
} else {
// get the first argument and remove slashes
$id = trim($args[0], '/');
// build the direct uri
$directId = trim($this->page()->id() . '/' . $id, '/');
// fast access to direct uris
if(isset($this->data[$directId])) return $this->data[$directId];
$path = explode('/', $id);
$obj = $this;
$page = false;
foreach($path as $uid) {
$id = ltrim($obj->page()->id() . '/' . $uid, '/');
if(!isset($obj->data[$id])) return false;
$page = $obj->data[$id];
$obj = $page->children();
}
return $page;
}
}
/**
* Finds pages by it's unique URI
*
* @param mixed $uri Either a single URI string or an array of URIs
* @param string $use The field, which should be used (uid or slug)
* @return mixed Either a Page object, a Pages object for multiple pages or null if nothing could be found
*/
public function findByURI() {
$args = func_get_args();
if(count($args) == 0) {
return false;
} else if(count($args) > 1) {
$collection = new Children($this->page);
foreach($args as $uri) {
$page = $this->findByURI($uri);
if($page) $collection->data[$page->id()] = $page;
}
return $collection;
} else {
// get the first argument and remove slashes
$uri = trim($args[0], '/');
$array = str::split($uri, '/');
$obj = $this;
$page = false;
foreach($array as $p) {
$next = $obj->findBy('slug', $p);
if(!$next) break;
$page = $next;
$obj = $next->children();
}
return ($page and $page->slug() != a::last($array)) ? false : $page;
}
}
/**
* Creates a clean one-level collection with all
* pages, subpages, subsubpages, etc.
*
* @param object Pages object for recursive indexing
* @return Children
*/
public function index(Children $obj = null) {
if(is_null($obj)) {
if(isset($this->cache['index'])) return $this->cache['index'];
$this->cache['index'] = new Children($this->page);
$obj = $this;
}
foreach($obj->data as $key => $page) {
$this->cache['index']->data[$page->uri()] = $page;
$this->index($page->children());
}
return $this->cache['index'];
}
/**
* Extended group method
* detaches children and converts them to
* a simple pages collection
*
* @param function $callback
* @return Pages
*/
public function group($callback) {
$collection = new Pages($this);
return $collection->group($callback);
}
}

151
kirby/core/content.php Normal file
View file

@ -0,0 +1,151 @@
<?php
/**
* Content
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class ContentAbstract {
public $page = null;
public $root = null;
public $raw = null;
public $data = array();
public $fields = array();
public $name = null;
/**
* Constructor
*/
public function __construct($page, $root) {
$this->page = $page;
$this->root = $root;
$this->name = pathinfo($root, PATHINFO_FILENAME);
// stop at invalid files
if(empty($this->root) or !is_file($this->root) or !is_readable($this->root)) return;
// read the content file and remove the BOM
$this->raw = str_replace(BOM, '', file_get_contents($this->root));
// explode all fields by the line separator
$fields = preg_split('!\n----\s*\n*!', $this->raw);
// loop through all fields and add them to the content
foreach($fields as $field) {
$pos = strpos($field, ':');
$key = str_replace(array('-', ' '), '_', strtolower(trim(substr($field, 0, $pos))));
// Don't add fields with empty keys
if(empty($key)) continue;
// add the key to the fields list
$this->fields[] = $key;
// add the key object
$this->data[$key] = new Field($this->page, $key, trim(substr($field, $pos+1)));
}
}
/**
* Returns the root for the content file
*/
public function root() {
return $this->root;
}
/**
* Returns the name of the content file
* without the extension. This is
* being used to determine the template for the page
*
* @return string
*/
public function name() {
return $this->name;
}
/**
* Returns an array with all
* field names
*
* @return array3
*/
public function fields() {
return $this->fields;
}
/**
* Returns the raw content from the file
*
* @return string
*/
public function raw() {
return $this->raw;
}
/**
* Returns the entire data array
* with all field objects
*
* @return array
*/
public function data() {
return $this->data;
}
/**
* Checks if the content file exists
*
* @return boolean
*/
public function exists() {
return file_exists($this->root);
}
/**
* Gets a field from the content
*
* @return Field
*/
public function get($key, $arguments = null) {
// case-insensitive data fetching
$key = strtolower($key);
if(isset($this->data[$key])) {
return $this->data[$key];
} else {
// return an empty field as default
return new Field($this->page, $key);
}
}
/**
* Checks if a field exists
*
* @param string $key
* @return boolean
*/
public function has($key) {
return isset($this->data[strtolower($key)]);
}
public function __call($method, $arguments = null) {
return $this->get($method, $arguments);
}
public function toArray() {
return array_map(function($item) {
return $item->value;
}, $this->data);
}
}

55
kirby/core/field.php Normal file
View file

@ -0,0 +1,55 @@
<?php
/**
* Field
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class FieldAbstract {
static public $methods = array();
public $page;
public $key;
public $value;
public function __construct($page, $key, $value = '') {
$this->page = $page;
$this->key = $key;
$this->value = $value;
}
public function page() {
return $this->page;
}
public function exists() {
return $this->page->content()->has($this->key);
}
public function key() {
return $this->key;
}
public function value() {
return $this->value;
}
public function isTranslated($lang = null) {
return true;
}
public function __toString() {
return (string)$this->value;
}
public function toString() {
return $this->value;
}
public function __call($method, $arguments = array()) {
if(isset(static::$methods[$method])) {
array_unshift($arguments, clone $this);
return call(static::$methods[$method], $arguments);
} else {
return $this;
}
}
}

333
kirby/core/file.php Normal file
View file

@ -0,0 +1,333 @@
<?php
/**
* File
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class FileAbstract extends Media {
use Kirby\Traits\Image;
static public $methods = array();
public $kirby;
public $site;
public $page;
public $files;
/**
* Constructor
*
* @param Files The parent files collection
* @param string The filename
*/
public function __construct(Files $files, $filename) {
$this->kirby = $files->kirby;
$this->site = $files->site;
$this->page = $files->page;
$this->files = $files;
$this->root = $this->files->page()->root() . DS . $filename;
parent::__construct($this->root);
}
/**
* Returns the kirby object
*
* @return Kirby
*/
public function kirby() {
return $this->kirby;
}
/**
* Returns the parent site object
*
* @return Site
*/
public function site() {
return $this->site;
}
/**
* Returns the parent page object
*
* @return Page
*/
public function page() {
return $this->page;
}
/**
* Returns the parent files collection
*
* @return Files
*/
public function files() {
return $this->files;
}
/**
* Returns the full root for the content file
*
* @return string
*/
public function textfile() {
return $this->page->textfile($this->filename());
}
public function siblings() {
return $this->files->not($this->filename);
}
public function next() {
$siblings = $this->files;
$index = $siblings->indexOf($this);
if($index === false) return false;
return $this->files->nth($index+1);
}
public function hasNext() {
return $this->next();
}
public function prev() {
$siblings = $this->files;
$index = $siblings->indexOf($this);
if($index === false) return false;
return $this->files->nth($index-1);
}
public function hasPrev() {
return $this->prev();
}
/**
* Returns the absolute URL for the file
*
* @return string
*/
public function url($raw = false) {
if($raw || empty($this->modifications)) {
return $this->page->contentUrl() . '/' . rawurlencode($this->filename);
} else {
return $this->kirby->component('thumb')->url($this);
}
}
/**
* Returns the relative URI for the image
*
* @return string
*/
public function uri() {
return $this->page->uri() . '/' . rawurlencode($this->filename);
}
/**
* Returns the full directory path starting from the content folder
*
* @return string
*/
public function diruri() {
return $this->page->diruri() . '/' . rawurlencode($this->filename);
}
/**
* Get the meta information
*
* @return Content
*/
public function meta() {
if(isset($this->cache['meta'])) {
return $this->cache['meta'];
} else {
$inventory = $this->page->inventory();
$file = isset($inventory['meta'][$this->filename]) ? $this->page->root() . DS . $inventory['meta'][$this->filename] : null;
return $this->cache['meta'] = new Content($this->page, $file);
}
}
/**
* Custom modified method for files
*
* @param string $format
* @return string
*/
public function modified($format = null, $handler = null) {
return parent::modified($format, $handler ? $handler : $this->kirby->options['date.handler']);
}
/**
* Magic getter for all meta fields
*
* @return Field
*/
public function __call($key, $arguments = null) {
if(isset(static::$methods[$key])) {
if(!$arguments) $arguments = array();
array_unshift($arguments, clone $this);
return call(static::$methods[$key], $arguments);
} else {
return $this->meta()->get($key, $arguments);
}
}
/**
* Generates a new filename for a given name
* and makes sure to handle badly given extensions correctly
*
* @param string $name
* @return string
*/
public function createNewFilename($name, $safeName = true) {
$name = basename($safeName ? f::safeName($name) : $name);
$ext = f::extension($name);
// remove possible extensions
if(in_array($ext, f::extensions())) {
$name = f::name($name);
}
return trim($name . '.' . $this->extension(), '.');
}
/**
* Renames the file and also its meta info txt
*
* @param string $filename
* @param boolean $safeName
*/
public function rename($name, $safeName = true) {
$filename = $this->createNewFilename($name, $safeName);
$root = $this->dir() . DS . $filename;
if(empty($name)) {
throw new Exception('The filename is missing');
}
if($root == $this->root()) return $filename;
if(file_exists($root)) {
throw new Exception('A file with that name already exists');
}
if(!f::move($this->root(), $root)) {
throw new Exception('The file could not be renamed');
}
$meta = $this->textfile();
if(file_exists($meta)) {
f::move($meta, $this->page->textfile($filename));
}
// reset the page cache
$this->page->reset();
// reset the basics
$this->root = $root;
$this->filename = $filename;
$this->name = $name;
$this->cache = array();
cache::flush();
return $filename;
}
public function update($data = array()) {
$data = array_merge((array)$this->meta()->toArray(), $data);
foreach($data as $k => $v) {
if(is_null($v)) unset($data[$k]);
}
if(!data::write($this->textfile(), $data, 'kd')) {
throw new Exception('The file data could not be saved');
}
// reset the page cache
$this->page->reset();
// reset the file cache
$this->cache = array();
cache::flush();
return true;
}
public function delete() {
// delete the meta file
f::remove($this->textfile());
if(!f::remove($this->root())) {
throw new Exception('The file could not be deleted');
}
cache::flush();
return true;
}
/**
* Get formatted date fields
*
* @param string $format
* @param string $field
* @return mixed
*/
public function date($format = null, $field = 'date') {
if($timestamp = strtotime($this->meta()->$field())) {
if(is_null($format)) {
return $timestamp;
} else {
return $this->kirby->options['date.handler']($format, $timestamp);
}
} else {
return false;
}
}
/**
* Converts the entire file object into
* a plain PHP array
*
* @param closure $callback Filter callback
* @return array
*/
public function toArray($callback = null) {
$data = parent::toArray();
// add the meta content
$data['meta'] = $this->meta()->toArray();
if(is_null($callback)) {
return $data;
} else {
return array_map($callback, $data);
}
}
}

137
kirby/core/files.php Normal file
View file

@ -0,0 +1,137 @@
<?php
/**
* Files
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class FilesAbstract extends Collection {
static public $methods = array();
public $kirby = null;
public $site = null;
public $page = null;
public function __construct($page) {
$this->kirby = $page->kirby;
$this->site = $page->site;
$this->page = $page;
$inventory = $page->inventory();
foreach($inventory['files'] as $filename) {
$file = new File($this, $filename);
$this->data[strtolower($file->filename())] = $file;
}
}
public function __call($method, $arguments) {
if(isset(static::$methods[$method])) {
array_unshift($arguments, clone $this);
return call(static::$methods[$method], $arguments);
} else {
return $this->get($method);
}
}
public function kirby() {
return $this->kirby;
}
public function site() {
return $this->site;
}
public function page() {
return $this->page;
}
public function find() {
$args = func_get_args();
if(!count($args)) {
return false;
}
if(count($args) === 1 and is_array($args[0])) {
$args = $args[0];
}
if(count($args) > 1) {
$files = clone $this;
$files->data = array();
foreach($args as $filename) {
$file = $this->find($filename);
if(!empty($file)) {
$files->data[$filename] = $file;
}
}
return $files;
} else {
$filename = strtolower($args[0]);
return isset($this->data[$filename]) ? $this->data[$filename] : null;
}
}
/**
* Returns a new collection of files without the given files
*
* @param args any number of filenames or file objects, passed as individual arguments
* @return object a new collection without the files
*/
public function not() {
$collection = clone $this;
foreach(func_get_args() as $filename) {
if(is_array($filename) or $filename instanceof Traversable) {
foreach($filename as $f) {
$collection = $collection->not($f);
}
} else if(is_a($filename, 'Media')) {
// unset by Media object
unset($collection->data[strtolower($filename->filename())]);
} else {
unset($collection->data[strtolower($filename)]);
}
}
return $collection;
}
/**
* Converts the files collection
* into a plain array
*
* @param closure $callback Filter callback for each item
* @return array
*/
public function toArray($callback = null) {
$data = array();
foreach($this as $file) {
$data[] = $file->toArray($callback);
}
return $data;
}
/**
* Converts the files collection
* into a json string
*
* @param closure $callback Filter callback for each item
* @return string
*/
public function toJson($callback = null) {
return json_encode($this->toArray($callback));
}
}

173
kirby/core/kirbytag.php Normal file
View file

@ -0,0 +1,173 @@
<?php
/**
* Kirbytag
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class KirbytagAbstract {
protected $page;
protected $kirbytext;
protected $name;
protected $html;
protected $attr = array();
public function __construct($kirbytext, $name, $tag) {
if(is_null($kirbytext)) $kirbytext = new Kirbytext('');
$this->page = $kirbytext->field->page;
$this->kirbytext = $kirbytext;
$this->name = $name;
$this->html = kirbytext::$tags[$name]['html'];
// get a list with all attributes
$attributes = isset(kirbytext::$tags[$name]['attr']) ? (array)kirbytext::$tags[$name]['attr'] : array();
// add the name as first attribute
array_unshift($attributes, $name);
if(is_array($tag)) {
foreach($attributes as $key) {
if(isset($tag[$key])) $this->attr[$key] = $tag[$key];
}
} else {
// extract all attributes
$search = preg_split('!(' . implode('|', $attributes) . '):!i', $tag, false, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
$num = 0;
foreach($search AS $key) {
if(!isset($search[$num+1])) break;
$key = trim($search[$num]);
$value = trim($search[$num+1]);
$this->attr[$key] = $value;
$num = $num+2;
}
}
}
/**
* Returns the parent active page
*
* @return object Page
*/
public function page() {
return $this->page;
}
/**
* Returns the parent kirbytext object
*
* @return object Kirbytext
*/
public function kirbytext() {
return $this->kirbytext;
}
/**
* Returns the field object
*
* @return object Field
*/
public function field() {
return $this->kirbytext->field();
}
/**
* Tries to find all related files for the current page
*
* @return object Files
*/
public function files() {
return $this->page->files();
}
/**
* Tries to find a file for the given url/uri
*
* @param string $url a full path to a file or just a filename for files form the current active page
* @return object File
*/
public function file($url) {
// if this is an absolute url cancel
if(preg_match('!(http|https)\:\/\/!i', $url)) return false;
// skip urls without extensions
if(!preg_match('!\.[a-z0-9]+$!i',$url)) return false;
// relative url
if(str::contains($url, '/')) {
$path = dirname($url);
$filename = basename($url);
if($page = page($path) and $file = $page->file($filename)) {
return $file;
} else {
return false;
}
}
// try to get all files for the current page
$files = $this->files();
// cancel if no files are available
if(!$files) return false;
// try to find the file
return $files->find($url);
}
/**
* Returns a specific attribute by key or all attributes
* by passing no key at all.
*
* @param mixed $key
* @param mixed $default
* @return array
*/
public function attr($key = null, $default = null) {
if(is_null($key)) return $this->attr;
return isset($this->attr[$key]) ? $this->attr[$key] : $default;
}
/**
* Smart getter for the applicable target attribute.
* This will watch for popup or target attributes and return
* a proper target value if available.
*
* @return string
*/
public function target() {
if(empty($this->attr['popup']) and empty($this->attr['target'])) return false;
return empty($this->attr['popup']) ? $this->attr['target'] : '_blank';
}
public function html() {
if(!is_callable($this->html)) {
return (string)$this->html;
} else {
return call_user_func_array($this->html, array($this));
}
}
public function __toString() {
return (string)$this->html();
}
}

107
kirby/core/kirbytext.php Normal file
View file

@ -0,0 +1,107 @@
<?php
/**
* Kirbytext
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class KirbytextAbstract {
static public $tags = array();
static public $pre = array();
static public $post = array();
public $field;
public function __construct($field) {
if(is_a($field, 'Field')) {
$this->field = $field;
} else if(is_array($field)) {
throw new Exception('Kirbytext cannot handle arrays');
} else if(empty($field) or is_string($field)) {
$this->field = new Field(page(), null, $field);
}
}
public function field() {
return $this->field;
}
public function parse() {
if(!$this->field) return '';
$text = $this->field->value;
// pre filters
foreach(static::$pre as $filter) {
$text = call_user_func_array($filter, array($this, $text));
}
// tagsify
$text = preg_replace_callback('!(?=[^\]])\([a-z0-9_-]+:.*?\)!is', array($this, 'tag'), $text);
// markdownify
$text = kirby::instance()->component('markdown')->parse($text);
// smartypantsify
$text = kirby::instance()->component('smartypants')->parse($text);
// post filters
foreach(static::$post as $filter) {
$text = call_user_func_array($filter, array($this, $text));
}
return $text;
}
public function tag($input) {
// remove the brackets
$tag = trim(rtrim(ltrim($input[0], '('), ')'));
$name = trim(substr($tag, 0, strpos($tag, ':')));
// if the tag is not installed return the entire string
if(!isset(static::$tags[$name])) return $input[0];
try {
$tag = new Kirbytag($this, $name, $tag);
return $tag->html();
} catch(Exception $e) {
// broken tags will be ignored
return $input[0];
}
}
static public function install($root) {
if(!is_dir($root)) return false;
foreach(scandir($root) as $file) {
if(pathinfo($file, PATHINFO_EXTENSION) == 'php') {
$name = pathinfo($file, PATHINFO_FILENAME);
$tag = include($root . DS . $file);
if(is_array($tag)) Kirbytext::$tags[$name] = $tag;
}
}
}
public function __toString() {
try {
return $this->parse();
} catch(Exception $e) {
// on massive render bugs the entire text will be returned
return $this->field->value;
}
}
}

1443
kirby/core/page.php Normal file

File diff suppressed because it is too large Load diff

330
kirby/core/pages.php Normal file
View file

@ -0,0 +1,330 @@
<?php
/**
* Pages
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class PagesAbstract extends Collection {
static public $methods = array();
/**
* Constructor
*/
public function __construct($data = array()) {
foreach($data as $object) {
$this->add($object);
}
}
public function __call($method, $arguments) {
if(isset(static::$methods[$method])) {
array_unshift($arguments, clone $this);
return call(static::$methods[$method], $arguments);
} else {
return $this->get($method);
}
}
/**
* Adds a single page object to the
* collection by id or the entire object
*
* @param mixed $page
*/
public function add($page) {
if(is_a($page, 'Collection')) {
foreach($page as $object) $this->add($object);
} else if(is_string($page) and $object = page($page)) {
$this->data[$object->id()] = $object;
} else if(is_a($page, 'Page')) {
$this->data[$page->id()] = $page;
}
return $this;
}
/**
* Returns a new collection of pages without the given pages
*
* @param args any number of uris or page elements, passed as individual arguments
* @return object a new collection without the pages
*/
public function not() {
$collection = clone $this;
foreach(func_get_args() as $uri) {
if(is_array($uri) or $uri instanceof Traversable) {
foreach($uri as $u) {
$collection = $collection->not($u);
}
} else if(is_a($uri, 'Page')) {
// unset by Page object
unset($collection->data[$uri->id()]);
} else if(isset($collection->data[$uri])) {
// unset by URI
unset($collection->data[$uri]);
} else if($page = $collection->findBy('uid', $uri)) {
// unset by UID
unset($collection->data[$page->id()]);
}
}
return $collection;
}
public function find() {
$args = func_get_args();
if(!count($args)) {
return false;
}
if(count($args) === 1 and is_array($args[0])) {
$args = $args[0];
}
if(count($args) > 1) {
$pages = new static();
foreach($args as $id) {
if($page = $this->find($id)) {
$pages->data[$page->id()] = $page;
}
}
return $pages;
} else {
// get the first argument and remove slashes
$id = trim($args[0], '/');
// fast access to direct uris
return isset($this->data[$id]) ? $this->data[$id] : null;
}
}
/**
* Find a single page by a given value
*
* @param string $field
* @param string $value
* @return Page
*/
public function findBy($field, $value) {
foreach($this->data as $page) {
if($page->$field() == $value) return $page;
}
return false;
}
/**
* Find the open page in a set
*
* @return Page
*/
public function findOpen() {
return $this->findBy('isOpen', true);
}
/**
* Filters the collection by visible pages
*
* @return Children
*/
public function visible() {
$collection = clone $this;
return $collection->filterBy('isVisible', true);
}
/**
* Filters the collection by invisible pages
*
* @return Children
*/
public function invisible() {
$collection = clone $this;
return $collection->filterBy('isInvisible', true);
}
/**
* Checks if a page is in a set of children
*
* @param Page | string $page
* @return boolean
*/
public function has($page) {
$uri = is_string($page) ? $page : $page->id();
return parent::has($uri);
}
/**
* Native search method to search for anything within the collection
*/
public function search($query, $params = array()) {
if(is_string($params)) {
$params = array('fields' => str::split($params, '|'));
}
$defaults = array(
'minlength' => 2,
'fields' => array(),
'words' => false,
'score' => array()
);
$options = array_merge($defaults, $params);
$collection = clone $this;
$searchwords = preg_replace('/(\s)/u',',', $query);
$searchwords = str::split($searchwords, ',', $options['minlength']);
if(!empty($options['stopwords'])) {
$searchwords = array_diff($searchwords, $options['stopwords']);
}
if(empty($searchwords)) return $collection->limit(0);
$searchwords = array_map(function($value) use($options) {
return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
}, $searchwords);
$preg = '!(' . implode('|', $searchwords) . ')!i';
$results = $collection->filter(function($page) use($query, $searchwords, $preg, $options) {
$data = $page->content()->toArray();
$keys = array_keys($data);
if(!empty($options['fields'])) {
$keys = array_intersect($keys, $options['fields']);
}
$page->searchHits = 0;
$page->searchScore = 0;
foreach($keys as $key) {
$score = a::get($options['score'], $key, 1);
// check for a match
if($matches = preg_match_all($preg, $data[$key], $r)) {
$page->searchHits += $matches;
$page->searchScore += $matches * $score;
// check for full matches
if($matches = preg_match_all('!' . preg_quote($query) . '!i', $data[$key], $r)) {
$page->searchScore += $matches * $score;
}
}
}
return $page->searchHits > 0 ? true : false;
});
$results = $results->sortBy('searchScore', SORT_DESC);
return $results;
}
/**
* Returns files from all pages
*
* @return object A collection of all files of the pages (not of their subpages)
*/
public function files() {
$files = new Collection();
foreach($this->data as $page) {
foreach($page->files() as $file) {
$files->append($page->id() . '/' . strtolower($file->filename()), $file);
}
}
return $files;
}
// File type filters
public function images() { return $this->files()->filterBy('type', 'image'); }
public function videos() { return $this->files()->filterBy('type', 'video'); }
public function documents() { return $this->files()->filterBy('type', 'document'); }
public function audio() { return $this->files()->filterBy('type', 'audio'); }
public function code() { return $this->files()->filterBy('type', 'code'); }
public function archives() { return $this->files()->filterBy('type', 'archive'); }
/**
* Groups the pages by a given field
*
* @param string $field
* @param bool $i (ignore upper/lowercase for group names)
* @return object A collection with an item for each group and a Pages object for each group
*/
public function groupBy($field, $i = true) {
$groups = array();
foreach($this->data as $key => $item) {
$value = $item->content()->get($field)->value();
// make sure that there's always a proper value to group by
if(!$value) throw new Exception('Invalid grouping value for key: ' . $key);
// ignore upper/lowercase for group names
if($i) $value = str::lower($value);
if(!isset($groups[$value])) {
// create a new entry for the group if it does not exist yet
$groups[$value] = new Pages(array($key => $item));
} else {
// add the item to an existing group
$groups[$value]->set($key, $item);
}
}
return new Collection($groups);
}
/**
* Converts the pages collection
* into a plain array
*
* @param closure $callback Filter callback for each item
* @return array
*/
public function toArray($callback = null) {
$data = array();
foreach($this as $page) {
$data[] = is_string($page) ? $page : $page->toArray($callback);
}
return $data;
}
/**
* Converts the pages collection
* into a json string
*
* @param closure $callback Filter callback for each item
* @return string
*/
public function toJson($callback = null) {
return json_encode($this->toArray($callback));
}
}

102
kirby/core/role.php Normal file
View file

@ -0,0 +1,102 @@
<?php
/**
* Role
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class RoleAbstract {
protected $id = null;
protected $name = null;
protected $panel = false;
protected $permissions = array(
'panel.access' => true,
'panel.site.update' => true,
'panel.page.create' => true,
'panel.page.update' => true,
'panel.page.move' => true,
'panel.page.sort' => true,
'panel.page.hide' => true,
'panel.page.delete' => true,
'panel.file.upload' => true,
'panel.file.replace' => true,
'panel.file.update' => true,
'panel.file.delete' => true,
'panel.user.add' => true,
'panel.user.edit' => true,
'panel.user.role' => true,
'panel.user.delete' => true,
);
public $default = false;
public function __construct($data = array()) {
if(!isset($data['id'])) throw new Exception('The role id is missing');
if(!isset($data['name'])) throw new Exception('The role name is missing');
// required data
$this->id = $data['id'];
$this->name = $data['name'];
if(isset($data['permissions']) and is_array($data['permissions'])) {
$this->permissions = a::merge($this->permissions, $data['permissions']);
} else if(isset($data['permissions']) and $data['permissions'] === false) {
$this->permissions = array_fill_keys(array_keys($this->permissions), false);
} else {
$this->permissions = $this->permissions;
}
// fallback permissions support for old 'panel' role variable
if(isset($data['panel']) and is_bool($data['panel'])) {
$this->permissions['panel.access'] = $data['panel'];
}
// is this role the default role?
if(isset($data['default'])) {
$this->default = $data['default'] === true;
}
}
public function id() {
return $this->id;
}
public function name() {
return $this->name;
}
// support for old 'panel' role permission
public function hasPanelAccess() {
return $this->hasPermission('panel.access');
}
public function hasPermission($target) {
if($this->id == 'admin') {
return true;
} else if(isset($this->permissions[$target]) and $this->permissions[$target] === true) {
return true;
} else {
return false;
}
}
public function isDefault() {
return $this->default;
}
public function users() {
return kirby::instance()->site()->users()->filterBy('role', $this->id);
}
public function __toString() {
return (string)$this->id;
}
}

84
kirby/core/roles.php Normal file
View file

@ -0,0 +1,84 @@
<?php
/**
* Roles
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class RolesAbstract extends Collection {
// cache for the default role
protected $default = null;
/**
* Constructor
*/
public function __construct() {
$roles = kirby::instance()->option('roles');
// set the default set of roles, if roles are not configured
if(empty($roles)) {
$roles = array(
array(
'id' => 'admin',
'name' => 'Admin',
'default' => true
),
array(
'id' => 'editor',
'name' => 'Editor',
'permissions' => array(
'panel.access' => true,
'panel.site.update' => false,
'panel.page.create' => true,
'panel.page.update' => true,
'panel.page.move' => true,
'panel.page.sort' => true,
'panel.page.hide' => true,
'panel.page.delete' => true,
'panel.file.upload' => true,
'panel.file.replace' => true,
'panel.file.update' => true,
'panel.file.delete' => true,
'panel.user.add' => false,
'panel.user.edit' => false,
'panel.user.role' => false,
'panel.user.delete' => false
)
)
);
}
foreach($roles as $role) {
$role = new Role($role);
$this->data[$role->id()] = $role;
}
// check for a valid admin role
if(!isset($this->data['admin'])) {
throw new Exception('There must be an admin role');
}
// check for a valid default role
if(!$this->findDefault()) {
$this->data['admin']->default = true;
}
}
/**
* Returns the default role for new users
*
* @return Role
*/
public function findDefault() {
if(!is_null($this->default)) return $this->default;
return $this->default = $this->findBy('isDefault', true);
}
}

337
kirby/core/site.php Normal file
View file

@ -0,0 +1,337 @@
<?php
/**
* Site
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class SiteAbstract extends Page {
// the current page
public $page = null;
/**
* Constructor
*
*/
public function __construct(Kirby $kirby) {
$this->kirby = $kirby;
$this->url = $kirby->urls()->index();
$this->depth = 0;
$this->uri = '';
$this->site = $this;
$this->page = null;
// build ugly urls if rewriting is disabled
if($this->kirby->options['rewrite'] === false) {
$this->url .= '/index.php';
}
$this->root = $kirby->roots()->content();
$this->dirname = basename($this->root);
}
/**
* Cleans the temporary internal cache
*/
public function reset() {
$this->cache = array();
}
/**
* The id is an empty string in case of the site object
*
* @return string
*/
public function id() {
return '';
}
/**
* The base diruri is bascially just an empty string
*
* @return string
*/
public function diruri() {
return '';
}
/**
* Returns the base url for the site
*
* @return string
*/
public function url() {
return $this->url;
}
/**
* Returns the full URL for the content folder
*
* @return string
*/
public function contentUrl() {
return $this->kirby()->urls()->content();
}
/**
* Checks if this object is the main site
*
* @return boolean
*/
public function isSite() {
return true;
}
/**
* Returns the usable template
*
* @return string
*/
public function template() {
return 'site';
}
/**
* The site has no template
*
* @return boolean
*/
public function templateFile() {
return false;
}
/**
* Returns the intended template
*
* @return string
*/
public function intendedTemplate() {
return 'site';
}
/**
* Again, the site has no template!
*
* @return boolean
*/
public function intendedTemplateFile() {
return false;
}
/**
* There can't be a template for the site
* Didn't you still get it yet?
*
* @return boolean
*/
public function hasTemplate() {
return false;
}
/**
* Sets the currently active page
* and returns its page object
*
* @param string $uri
* @return Page
*/
public function visit($uri = '') {
$uri = trim($uri, '/');
if(empty($uri)) {
return $this->page = $this->homePage();
} else {
if($page = $this->children()->find($uri)) {
return $this->page = $page;
} else {
return $this->page = $this->errorPage();
}
}
}
/**
* Returns the currently active page or any other page by uri
*
* @param string $uri Optional uri to get any page on the site
* @return Page
*/
public function page($uri = null) {
if(is_null($uri)) {
return is_null($this->page) ? $this->page = $this->homePage() : $this->page;
} else {
return $this->children()->find($uri);
}
}
/**
* Alternative for $this->children()
*
* @return Children
*/
public function pages() {
return $this->children();
}
/**
* Builds a breadcrumb collection
*
* @return Children
*/
public function breadcrumb() {
if(isset($this->cache['breadcrumb'])) return $this->cache['breadcrumb'];
// get all parents and flip the order
$crumb = $this->page()->parents()->flip();
// add the home page
$crumb->prepend($this->homePage()->uri(), $this->homePage());
// add the active page
$crumb->append($this->page()->uri(), $this->page());
return $this->cache['breadcrumb'] = $crumb;
}
/**
* Alternative for $this->page()
*
* @return Page
*/
public function activePage() {
return $this->page();
}
/**
* Returns the error page object
*
* @return Page
*/
public function errorPage() {
if(isset($this->cache['errorPage'])) return $this->cache['errorPage'];
return $this->cache['errorPage'] = $this->children()->find($this->kirby->options['error']);
}
/**
* Returns the home page object
*
* @return Page
*/
public function homePage() {
if(isset($this->cache['homePage'])) return $this->cache['homePage'];
return $this->cache['homePage'] = $this->children()->find($this->kirby->options['home']);
}
/**
* Returns the locale for the site
*
* @return string
*/
public function locale() {
return isset($this->kirby->options['locale']) ? $this->kirby->options['locale'] : 'en_US';
}
/**
* Checks if the site is a multi language site
*
* @return boolean
*/
public function multilang() {
return false;
}
/**
* Placeholder for multilanguage sites
*/
public function languages() {
return null;
}
/**
* Placeholder for multilanguage sites
*/
public function language() {
return null;
}
/**
* Placeholder for multilanguage sites
*/
public function defaultLanguage() {
return null;
}
/**
* Return the detected language
*/
public function detectedLanguage() {
return null;
}
/**
* Returns a collection of all users
*
* @return Users
*/
public function users() {
return new Users();
}
/**
* Returns the current user
*
* @param string $username Optional way to search for a single user
* @return User
*/
public function user($username = null) {
if(is_null($username)) return User::current();
try {
return new User($username);
} catch(Exception $e) {
return null;
}
}
/**
* Returns a collection of all roles
*
* @return Roles
*/
public function roles() {
return new Roles();
}
/**
* Gets the last modification date of all pages
* in the content folder.
*
* @param mixed $format
* @param mixed $handler
* @return mixed
*/
public function modified($format = null, $handler = null) {
return dir::modified($this->root, $format, $handler ? $handler : $this->kirby->options['date.handler']);
}
/**
* Checks if any content of the site has been
* modified after the given unix timestamp
* This is mainly used to auto-update the cache
*
* @return boolean
*/
public function wasModifiedAfter($time) {
return dir::wasModifiedAfter($this->root(), $time);
}
}

349
kirby/core/user.php Normal file
View file

@ -0,0 +1,349 @@
<?php
/**
* Users
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class UserAbstract {
protected $username = null;
protected $cache = array();
protected $data = null;
public function __construct($username) {
$this->username = str::slug(basename($username));
// check if the account file exists
if(!file_exists($this->file())) {
throw new Exception('The user account could not be found');
}
}
/**
* Returns the username
*
* @return string
*/
public function username() {
return $this->username;
}
/**
* get all data for the user
*/
public function data() {
if(!is_null($this->data)) return $this->data;
// get all data from the account file
$this->data = data::read($this->file(), 'yaml');
// make sure all keys are lowercase
$this->data = array_change_key_case($this->data, CASE_LOWER);
// remove garbage
unset($this->data[0]);
// add the username
$this->data['username'] = $this->username;
// return the data array
return $this->data;
}
public function __get($key) {
return a::get($this->data(), strtolower($key));
}
public function __call($key, $arguments = null) {
return $this->__get($key);
}
public function role() {
$roles = kirby::instance()->site()->roles();
$data = $this->data();
if(empty($data['role'])) {
// apply the default role, if no role is stored for the user
$data['role'] = $roles->findDefault()->id();
}
// return the role by id
if($role = $roles->get($data['role'])) {
return $role;
} else {
return $roles->findDefault();
}
}
public function hasRole() {
$roles = func_get_args();
return in_array($this->role()->id(), $roles);
}
// support for old 'panel' role permission
public function hasPanelAccess() {
return $this->role()->hasPermission('panel.access');
}
public function hasPermission($target) {
return $this->role()->hasPermission($target);
}
public function isAdmin() {
return $this->role()->id() == 'admin';
}
public function avatar() {
if(isset($this->cache['avatar'])) return $this->cache['avatar'];
$avatar = new Avatar($this);
return $this->cache['avatar'] = $avatar->exists() ? $avatar : false;
}
public function avatarRoot($extension = 'jpg') {
return kirby::instance()->roots()->avatars() . DS . $this->username() . '.' . $extension;
}
public function gravatar($size = 256) {
return gravatar($this->email(), $size);
}
protected function file() {
return kirby::instance()->roots()->accounts() . DS . $this->username() . '.php';
}
public function textfile() {
return $this->file();
}
public function exists() {
return file_exists($this->file());
}
public function generateKey() {
return str::random(64);
}
public function generateSecret($key) {
return sha1($this->username() . $key);
}
public function login($password) {
static::logout();
if(!password::match($password, $this->password)) return false;
// create a new session id
s::regenerateId();
$key = $this->generateKey();
$secret = $this->generateSecret($key);
s::set('kirby_auth_secret', $secret);
s::set('kirby_auth_username', $this->username());
cookie::set(
s::$name . '_auth',
$key,
s::$cookie['lifetime'],
s::$cookie['path'],
s::$cookie['domain'],
s::$cookie['secure'],
s::$cookie['httponly']
);
return true;
}
static public function logout() {
s::destroy();
cookie::remove(s::$name . '_auth');
}
public function is($user) {
if(!is_a($user, 'User')) return false;
return $this->username() === $user->username();
}
public function isCurrent() {
return $this->is(static::current());
}
static public function validate($data = array(), $mode = 'insert') {
if($mode == 'insert') {
if(empty($data['username'])) {
throw new Exception('Invalid username');
}
if(empty($data['password'])) {
throw new Exception('Invalid password');
}
}
if(!empty($data['email']) and !v::email($data['email'])) {
throw new Exception('Invalid email');
}
}
public function update($data = array()) {
// sanitize the given data
$data = $this->sanitize($data, 'update');
// validate the updated dataset
$this->validate($data, 'update');
// don't update the username
unset($data['username']);
// create a new hash for the password
if(!empty($data['password'])) {
$data['password'] = password::hash($data['password']);
}
// merge with existing fields
$this->data = array_merge($this->data(), $data);
foreach($this->data as $key => $value) {
if(is_null($value)) unset($this->data[$key]);
}
// save the new user data
static::save($this->file(), $this->data);
// return the updated user project
return $this;
}
public function delete() {
if($avatar = $this->avatar()) {
$avatar->delete();
}
if(!f::remove($this->file())) {
throw new Exception('The account could not be deleted');
} else {
return true;
}
}
static public function sanitize($data, $mode = 'insert') {
// all usernames must be lowercase
$data['username'] = str::slug(a::get($data, 'username'));
// convert all keys to lowercase
$data = array_change_key_case($data, CASE_LOWER);
// return the cleaned up data
return $data;
}
/**
* Creates a new user
*
* @param array $user
* @return User
*/
static public function create($data = array()) {
// sanitize the given data for the new user
$data = static::sanitize($data, 'insert');
// validate the dataset
static::validate($data, 'insert');
// create the file root
$file = kirby::instance()->roots()->accounts() . DS . $data['username'] . '.php';
// check for an existing username
if(file_exists($file)) {
throw new Exception('The username is taken');
}
// create a new hash for the password
if(!empty($data['password'])) {
$data['password'] = password::hash($data['password']);
}
static::save($file, $data);
// return the created user project
return new static($data['username']);
}
static protected function save($file, $data) {
$yaml = '<?php if(!defined(\'KIRBY\')) exit ?>' . PHP_EOL . PHP_EOL;
$yaml .= data::encode($data, 'yaml');
if(!f::write($file, $yaml)) {
throw new Exception('The user account could not be saved');
} else {
return true;
}
}
static public function unauthorize() {
s::remove('kirby_auth_secret');
s::remove('kirby_auth_username');
cookie::remove('kirby_auth');
}
static public function current() {
$cookey = cookie::get(s::$name . '_auth');
$username = s::get('kirby_auth_username');
if(empty($cookey)) {
static::unauthorize();
return false;
}
if(s::get('kirby_auth_secret') !== sha1($username . $cookey)) {
static::unauthorize();
return false;
}
// find the logged in user by token
try {
$user = new static($username);
return $user;
} catch(Exception $e) {
static::unauthorize();
return false;
}
}
public function __toString() {
return (string)$this->username;
}
}

38
kirby/core/users.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* Users
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
abstract class UsersAbstract extends Collection {
public function __construct() {
$root = kirby::instance()->roots()->accounts();
foreach(dir::read($root) as $file) {
// skip invalid account files
if(f::extension($file) != 'php') continue;
$user = new User(f::name($file));
$this->append($user->username(), $user);
}
}
public function create($data) {
return user::create($data);
}
public function find($username) {
return $this->findBy('username', $username);
}
}

View file

@ -0,0 +1,279 @@
<?php
/**
* Converts the field value to valid html
* @param Field $field The calling Kirby Field instance
* @param boolean $keepTags Don't touch valid html tags
* @return Field
*/
field::$methods['html'] = field::$methods['h'] = function($field, $keepTags = true) {
$field->value = html($field->value, $keepTags);
return $field;
};
/**
* Escapes unwanted characters in the field value
* to protect from possible xss attacks or other
* unwanted side effects in your html code
* @param Field $field The calling Kirby Field instance
* @param string $context html|attr|css|js|url
* @return Field
*/
field::$methods['escape'] = field::$methods['esc'] = function($field, $context = 'html') {
$field->value = esc($field->value, $context);
return $field;
};
/**
* Converts html entities and specialchars in the field
* value to valid xml entities
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['xml'] = field::$methods['x'] = function($field) {
$field->value = xml($field->value);
return $field;
};
/**
* Parses the field value as kirbytext
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['kirbytext'] = field::$methods['kt'] = function($field) {
$field->value = kirbytext($field);
return $field;
};
/**
* Parses the field value as markdown
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['markdown'] = field::$methods['md'] = function($field) {
$field->value = markdown($field->value);
return $field;
};
/**
* Converts the field value to lower case
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['lower'] = function($field) {
$field->value = str::lower($field->value);
return $field;
};
/**
* Converts the field value to upper case
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['upper'] = function($field) {
$field->value = str::upper($field->value);
return $field;
};
/**
* Applies the widont rule to avoid single
* words on the last line
* @param Field $field The calling Kirby Field instance
* @return Field
*/
field::$methods['widont'] = function($field) {
$field->value = widont($field->value);
return $field;
};
/**
* Creates a simple text excerpt without formats
* @param Field $field The calling Kirby Field instance
* @param integer $chars The desired excerpt length
* @return string
*/
field::$methods['excerpt'] = function($field, $chars = 140, $mode = 'chars') {
return excerpt($field, $chars, $mode);
};
/**
* Shortens the field value by the given length
* @param Field $field The calling Kirby Field instance
* @param integer $length The desired string length
* @param string $rep The attached ellipsis character if the string is longer
* @return string
*/
field::$methods['short'] = function($field, $length, $rep = '…') {
return str::short($field->value, $length, $rep);
};
/**
* Returns the string length of the field value
* @param Field $field The calling Kirby Field instance
* @return integer
*/
field::$methods['length'] = function($field) {
return str::length($field->value);
};
/**
* Returns the word count for the field value
* @param Field $field The calling Kirby Field instance
* @return integer
*/
field::$methods['words'] = function($field) {
return str_word_count(strip_tags($field->value));
};
/**
* Splits the field value by the given separator
* @param Field $field The calling Kirby Field instance
* @param string $separator The string to split the field value by
* @return array
*/
field::$methods['split'] = function($field, $separator = ',') {
return str::split($field->value, $separator);
};
/**
* Parses the field value as yaml and returns an array
* @param Field $field The calling Kirby Field instance
* @return array
*/
field::$methods['yaml'] = function($field) {
return yaml($field->value);
};
/**
* Checks if the field value is empty
* @param Field $field The calling Kirby Field instance
* @return boolean
*/
field::$methods['empty'] = field::$methods['isEmpty'] = function($field) {
return empty($field->value);
};
/**
* Checks if the field value is not empty
* @param Field $field The calling Kirby Field instance
* @return boolean
*/
field::$methods['isNotEmpty'] = function($field) {
return !$field->isEmpty();
};
/**
* Returns a page object from a uri in a field
* @param Field $field The calling Kirby Field instance
* @return Collection
*/
field::$methods['toPage'] = function($field) {
return page($field->value);
};
/**
* Returns all page objects from a yaml list or a $sep separated string in a field
* @param Field $field The calling Kirby Field instance
* @return Collection
*/
field::$methods['pages'] = field::$methods['toPages'] = function($field, $sep = null) {
if($sep !== null) {
$array = $field->split($sep);
} else {
$array = $field->yaml();
}
return $field->site()->pages()->find($array);
};
/**
* Returns a file object from a filename in a field
* @param Field $field The calling Kirby Field instance
* @return Collection
*/
field::$methods['toFile'] = function($field) {
return $field->page()->file($field->value);
};
/**
* Adds 'or' method to Field objects, which allows getting a field
* value or getting back a default value if the field is empty.
* @author fvsch <florent@fvsch.com>
* @param Field $field The calling Kirby Field instance
* @param mixed $fallback Fallback value returned if field is empty
* @return mixed
*/
field::$methods['or'] = function($field, $fallback = null) {
return $field->empty() ? $fallback : $field;
};
/**
* Filter the Field value, or a fallback value if the Field is empty,
* to get a boolean value. '1', 'on', 'true' or 'yes' will be true,
* and everything else will be false.
* @author fvsch <florent@fvsch.com>
* @param Field $field The calling Kirby Field instance
* @param boolean $default Default value returned if field is empty
* @return boolean
*/
field::$methods['bool'] = field::$methods['isTrue'] = function($field, $default = false) {
$val = $field->empty() ? $default : $field->value;
return filter_var($val, FILTER_VALIDATE_BOOLEAN);
};
/**
* Checks if the field content is false
* @param Field $field The calling Kirby Field instance
* @return boolean
*/
field::$methods['isFalse'] = function($field) {
return !$field->bool();
};
/**
* Get an integer value for the Field.
* @author fvsch <florent@fvsch.com>
* @param Object(Field) [$field] The calling Kirby Field instance
* @param integer [$default] Default value returned if field is empty
* @return integer
*/
field::$methods['int'] = function($field, $default = 0) {
$val = $field->empty() ? $default : $field->value;
return intval($val);
};
/**
* Get a float value for the Field
* @param Field $field The calling Kirby Field instance
* @param int $default Default value returned if field is empty
* @return float
*/
field::$methods['float'] = function($field, $default = 0) {
$val = $field->empty() ? $default : $field->value;
return floatval($val);
};
field::$methods['toStructure'] = field::$methods['structure'] = function($field) {
return structure($field->yaml(), $field->page());
};
field::$methods['link'] = function($field, $attr1 = array(), $attr2 = array()) {
$a = new Brick('a', $field->value());
if(is_string($attr1)) {
$a->attr('href', url($attr1));
$a->attr($attr2);
} else {
$a->attr('href', $field->page()->url());
$a->attr($attr1);
}
return $a;
};
field::$methods['toUrl'] = field::$methods['url'] = function($field) {
return url($field->value());
};

309
kirby/extensions/tags.php Normal file
View file

@ -0,0 +1,309 @@
<?php
// date tag
kirbytext::$tags['date'] = array(
'attr' => array(),
'html' => function($tag) {
return strtolower($tag->attr('date')) == 'year' ? date('Y') : date($tag->attr('date'));
}
);
// email tag
kirbytext::$tags['email'] = array(
'attr' => array(
'class',
'title',
'text',
'rel'
),
'html' => function($tag) {
return html::email($tag->attr('email'), html($tag->attr('text')), array(
'class' => $tag->attr('class'),
'title' => $tag->attr('title'),
'rel' => $tag->attr('rel'),
));
}
);
// file tag
kirbytext::$tags['file'] = array(
'attr' => array(
'text',
'class',
'title',
'rel',
'target',
'popup'
),
'html' => function($tag) {
// build a proper link to the file
$file = $tag->file($tag->attr('file'));
$text = $tag->attr('text');
if(!$file) return $text;
// use filename if the text is empty and make sure to
// ignore markdown italic underscores in filenames
if(empty($text)) $text = str_replace('_', '\_', $file->name());
return html::a($file->url(), html($text), array(
'class' => $tag->attr('class'),
'title' => html($tag->attr('title')),
'rel' => $tag->attr('rel'),
'target' => $tag->target(),
));
}
);
// image tag
kirbytext::$tags['image'] = array(
'attr' => array(
'width',
'height',
'alt',
'text',
'title',
'class',
'imgclass',
'linkclass',
'caption',
'link',
'target',
'popup',
'rel'
),
'html' => function($tag) {
$url = $tag->attr('image');
$alt = $tag->attr('alt');
$title = $tag->attr('title');
$link = $tag->attr('link');
$caption = $tag->attr('caption');
$file = $tag->file($url);
// use the file url if available and otherwise the given url
$url = $file ? $file->url() : url($url);
// alt is just an alternative for text
if($text = $tag->attr('text')) $alt = $text;
// try to get the title from the image object and use it as alt text
if($file) {
if(empty($alt) and $file->alt() != '') {
$alt = $file->alt();
}
if(empty($title) and $file->title() != '') {
$title = $file->title();
}
}
// at least some accessibility for the image
if(empty($alt)) $alt = ' ';
// link builder
$_link = function($image) use($tag, $url, $link, $file) {
if(empty($link)) return $image;
// build the href for the link
if($link == 'self') {
$href = $url;
} else if($file and $link == $file->filename()) {
$href = $file->url();
} else if($tag->file($link)) {
$href = $tag->file($link)->url();
} else {
$href = $link;
}
return html::a(url($href), $image, array(
'rel' => $tag->attr('rel'),
'class' => $tag->attr('linkclass'),
'title' => $tag->attr('title'),
'target' => $tag->target()
));
};
// image builder
$_image = function($class) use($tag, $url, $alt, $title) {
return html::img($url, array(
'width' => $tag->attr('width'),
'height' => $tag->attr('height'),
'class' => $class,
'title' => $title,
'alt' => $alt
));
};
if(kirby()->option('kirbytext.image.figure') or !empty($caption)) {
$image = $_link($_image($tag->attr('imgclass')));
$figure = new Brick('figure');
$figure->addClass($tag->attr('class'));
$figure->append($image);
if(!empty($caption)) {
$figure->append('<figcaption>' . html($caption) . '</figcaption>');
}
return $figure;
} else {
$class = trim($tag->attr('class') . ' ' . $tag->attr('imgclass'));
return $_link($_image($class));
}
}
);
// link tag
kirbytext::$tags['link'] = array(
'attr' => array(
'text',
'class',
'title',
'rel',
'lang',
'target',
'popup'
),
'html' => function($tag) {
$link = url($tag->attr('link'), $tag->attr('lang'));
$text = $tag->attr('text');
if(empty($text)) {
$text = $link;
}
if(str::isURL($text)) {
$text = url::short($text);
}
return html::a($link, $text, array(
'rel' => $tag->attr('rel'),
'class' => $tag->attr('class'),
'title' => $tag->attr('title'),
'target' => $tag->target(),
));
}
);
// tel tag
kirbytext::$tags['tel'] = array(
'attr' => array(
'text',
'class',
'title'
),
'html' => function($tag) {
$text = $tag->attr('text');
$tel = str_replace(array('/', ' ', '-'), '', $tag->attr('tel'));
if(empty($text)) $text = $tag->attr('tel');
return html::a('tel:' . $tel, html($text), array(
'rel' => $tag->attr('rel'),
'class' => $tag->attr('class'),
'title' => html($tag->attr('title'))
));
}
);
// twitter tag
kirbytext::$tags['twitter'] = array(
'attr' => array(
'class',
'title',
'text',
'rel',
'target',
'popup',
),
'html' => function($tag) {
// get and sanitize the username
$username = str_replace('@', '', $tag->attr('twitter'));
// build the profile url
$url = 'https://twitter.com/' . $username;
// sanitize the link text
$text = $tag->attr('text', '@' . $username);
// build the final link
return html::a($url, $text, array(
'class' => $tag->attr('class'),
'title' => $tag->attr('title'),
'rel' => $tag->attr('rel'),
'target' => $tag->target(),
));
}
);
kirbytext::$tags['youtube'] = array(
'attr' => array(
'width',
'height',
'class',
'caption'
),
'html' => function($tag) {
$caption = $tag->attr('caption');
if(!empty($caption)) {
$figcaption = '<figcaption>' . escape::html($caption) . '</figcaption>';
} else {
$figcaption = null;
}
return '<figure class="' . $tag->attr('class', kirby()->option('kirbytext.video.class', 'video')) . '">' . embed::youtube($tag->attr('youtube'), array(
'width' => $tag->attr('width', kirby()->option('kirbytext.video.width')),
'height' => $tag->attr('height', kirby()->option('kirbytext.video.height')),
'options' => kirby()->option('kirbytext.video.youtube.options')
)) . $figcaption . '</figure>';
}
);
kirbytext::$tags['vimeo'] = array(
'attr' => array(
'width',
'height',
'class',
'caption'
),
'html' => function($tag) {
$caption = $tag->attr('caption');
if(!empty($caption)) {
$figcaption = '<figcaption>' . escape::html($caption) . '</figcaption>';
} else {
$figcaption = null;
}
return '<figure class="' . $tag->attr('class', kirby()->option('kirbytext.video.class', 'video')) . '">' . embed::vimeo($tag->attr('vimeo'), array(
'width' => $tag->attr('width', kirby()->option('kirbytext.video.width')),
'height' => $tag->attr('height', kirby()->option('kirbytext.video.height')),
'options' => kirby()->option('kirbytext.video.vimeo.options')
)) . $figcaption . '</figure>';
}
);
kirbytext::$tags['gist'] = array(
'attr' => array(
'file'
),
'html' => function($tag) {
return embed::gist($tag->attr('gist'), $tag->attr('file'));
}
);

327
kirby/helpers.php Normal file
View file

@ -0,0 +1,327 @@
<?php
/**
* Embeds a snippet from the snippet folder
*
* @param string $file
* @param mixed $data array or object
* @param boolean $return
* @return string
*/
function snippet($file, $data = array(), $return = false) {
return kirby::instance()->component('snippet')->render($file, $data, $return);
}
/**
* Builds a css link tag for relative or absolute urls
*
* @param string $url
* @param string $media
* @return string
*/
function css() {
return call([kirby::instance()->component('css'), 'tag'], func_get_args());
}
/**
* Builds a script tag for relative or absolute links
*
* @param string $src
* @param boolean $async
* @return string
*/
function js($src, $async = false) {
return call([kirby::instance()->component('js'), 'tag'], func_get_args());
}
/**
* Global markdown parser shortcut
*
* @param string $text
* @return string
*/
function markdown($text) {
return kirby::instance()->component('markdown')->parse($text);
}
/**
* Global smartypants parser shortcut
*
* @param string $text
* @return string
*/
function smartypants($text) {
return kirby::instance()->component('smartypants')->parse($text);
}
/**
* Converts a string to Kirbytext
*
* @param Field $field
* @return string
*/
function kirbytext($field) {
return (string)new Kirbytext($field);
}
/**
* Returns the Kirby class singleton
*
* @return Kirby
*/
function kirby($class = null) {
return kirby::instance($class);
}
/**
* Returns the site object
*
* @return Site
*/
function site() {
return kirby::instance()->site();
}
/**
* Returns either the current page or any page for a given uri
*
* @return Page
*/
function page() {
return call_user_func_array(array(kirby::instance()->site(), 'page'), func_get_args());
}
/**
* Helper to build page collections
*
* @param array $data
*/
function pages($data = array()) {
return new Pages($data);
}
/**
* Creates an excerpt without html and kirbytext
*
* @param mixed $text Variable object or string
* @param int $length The number of characters which should be included in the excerpt
* @param array $params an array of options for kirbytext: array('markdown' => true, 'smartypants' => true)
* @return string The shortened text
*/
function excerpt($text, $length = 140, $mode = 'chars') {
if(strtolower($mode) == 'words') {
$text = str::excerpt(kirbytext($text), 0);
if(str_word_count($text, 0) > $length) {
$words = str_word_count($text, 2);
$pos = array_keys($words);
$text = str::substr($text, 0, $pos[$length]) . '...';
}
return $text;
} else {
return str::excerpt(kirbytext($text), $length);
}
}
/**
* Helper to create correct text file names for content files
*
* @param string $uri
* @param string $template
* @param string $lang
* @return string
*/
function textfile($uri, $template, $lang = null) {
$curi = '';
$parts = str::split($uri, '/');
$parent = site();
foreach($parts as $p) {
if($parent and $child = $parent->children()->find($p)) {
$curi .= '/' . $child->dirname();
$parent = $child;
} else {
$curi .= '/' . $p;
$parent = null;
}
}
$uri = ltrim($curi, '/');
$root = kirby::instance()->roots()->content();
$ext = kirby::instance()->option('content.file.extension', 'txt');
return $root . DS . r(!empty($uri), str_replace('/', DS, $uri) . DS) . $template . r($lang, '.' . $lang) . '.' . $ext;
}
/**
* Renders a kirbytag
*
* @param array $attr
* @return Kirbytag
*/
function kirbytag($attr) {
return new Kirbytag(null, key($attr), $attr);
}
/**
* Builds a Youtube video iframe
*
* @param string $url
* @param mixed $width
* @param mixed $height
* @param string $class
* @return string
*/
function youtube($url, $width = null, $height = null, $class = null) {
return kirbytag(array(
'youtube' => $url,
'width' => $width,
'height' => $height,
'class' => $class
));
}
/**
* Builds a Vimeo video iframe
*
* @param string $url
* @param mixed $width
* @param mixed $height
* @param string $class
* @return string
*/
function vimeo($url, $width = null, $height = null, $class = null) {
return kirbytag(array(
'vimeo' => $url,
'width' => $width,
'height' => $height,
'class' => $class
));
}
/**
* Builds a Twitter link
*
* @param string $username
* @param string $text
* @param string $title
* @param string $class
* @return string
*/
function twitter($username, $text = null, $title = null, $class = null) {
return kirbytag(array(
'twitter' => $username,
'text' => $text,
'title' => $title,
'class' => $class
));
}
/**
* Embeds a Github Gist
*
* @param string $url
* @param string $file
* @return string
*/
function gist($url, $file = null) {
return kirbytag(array(
'gist' => $url,
'file' => $file,
));
}
/**
* Returns the current url
*
* @return string
*/
function thisUrl() {
return url::current();
}
/**
* Give this any kind of array
* to get some kirby style structure
*
* @param mixed $data
* @param mixed $page
* @param mixed $key
* @return mixed
*/
function structure($data, $page = null, $key = null) {
if(is_null($page)) {
$page = page();
}
if(is_array($data)) {
$result = new Structure();
$result->page = $page;
foreach($data as $key => $value) {
$result->append($key, structure($value, $page, $key));
}
return $result;
} else if(is_a($data, 'Field')) {
return $data;
} else {
return new Field($page, $key, $data);
}
};
/**
* Return an image from any page
* specified by the path
*
* Example:
* <?= image('some/page/myimage.jpg') ?>
*
* @param string $path
* @return File|null
*/
function image($path = null) {
if($path === null) {
return page()->image();
}
$uri = dirname($path);
$filename = basename($path);
if($uri == '.') {
$uri = null;
}
$page = $uri == '/' ? site() : page($uri);
if($page) {
return $page->image($filename);
} else {
return null;
}
}
/**
* Shortcut to create a new thumb object
*
* @param mixed Either a file path or a Media object
* @param array An array of additional params for the thumb
* @return object Thumb
*/
function thumb($image, $params = array(), $obj = true) {
if(is_a($image, 'File') || is_a($image, 'Asset')) {
return $obj ? $image->thumb($params) : $image->thumb($params)->url();
} else {
$class = new Thumb($image, $params);
return $obj ? $class : $class->url();
}
}

785
kirby/kirby.php Normal file
View file

@ -0,0 +1,785 @@
<?php
use Kirby\Component;
use Kirby\Pipe;
use Kirby\Registry;
use Kirby\Request;
use Kirby\Roots;
use Kirby\Urls;
class Kirby {
static public $version = '2.3.0';
static public $instance;
static public $hooks = array();
static public $triggered = array();
public $roots;
public $urls;
public $cache;
public $path;
public $options = array();
public $routes;
public $router;
public $route;
public $site;
public $page;
public $plugins;
public $response;
public $request;
public $components = [];
public $registry;
static public function instance($class = null) {
if(!is_null(static::$instance)) return static::$instance;
return static::$instance = $class ? new $class : new static;
}
static public function version() {
return static::$version;
}
public function __construct($options = array()) {
$this->roots = new Roots(dirname(__DIR__));
$this->urls = new Urls();
$this->registry = new Registry($this);
$this->options = array_merge($this->defaults(), $options);
$this->path = implode('/', (array)url::fragments(detect::path()));
// make sure the instance is stored / overwritten
static::$instance = $this;
}
public function defaults() {
$defaults = array(
'url' => false,
'timezone' => 'UTC',
'license' => null,
'rewrite' => true,
'error' => 'error',
'home' => 'home',
'locale' => 'en_US.UTF8',
'routes' => array(),
'headers' => array(),
'languages' => array(),
'roles' => array(),
'cache' => false,
'debug' => 'env',
'ssl' => false,
'cache.driver' => 'file',
'cache.options' => array(),
'cache.ignore' => array(),
'cache.autoupdate' => true,
'date.handler' => 'date',
'kirbytext.video.class' => 'video',
'kirbytext.video.width' => false,
'kirbytext.video.height' => false,
'kirbytext.video.youtube.options' => array(),
'kirbytext.video.vimeo.options' => array(),
'kirbytext.image.figure' => true,
'content.file.extension' => 'txt',
'content.file.ignore' => array(),
'content.file.normalize' => false,
'email.service' => 'mail',
'email.to' => null,
'email.replyTo' => null,
'email.subject' => null,
'email.body' => null,
'email.options' => array(),
);
return $defaults;
}
public function roots() {
return $this->roots;
}
public function urls() {
return $this->urls;
}
public function registry() {
return $this->registry;
}
public function url() {
return $this->urls->index();
}
public function options() {
return $this->options;
}
public function option($key, $default = null) {
return a::get($this->options, $key, $default);
}
public function path() {
return $this->path;
}
public function page() {
return $this->page;
}
public function response() {
return $this->response;
}
/**
* Install a new entry in the registry
*/
public function set() {
return call_user_func_array([$this->registry, 'set'], func_get_args());
}
/**
* Retrieve an entry from the registry
*/
public function get() {
return call_user_func_array([$this->registry, 'get'], func_get_args());
}
public function configure() {
// load all available config files
$root = $this->roots()->config();
$configs = array(
'main' => 'config.php',
'host' => 'config.' . server::get('SERVER_NAME') . '.php',
'addr' => 'config.' . server::get('SERVER_ADDR') . '.php',
);
$allowed = array_filter(dir::read($root), function($file) {
return substr($file, 0, 7) === 'config.' and substr($file, -4) === '.php';
});
foreach($configs as $config) {
$file = $root . DS . $config;
if(in_array($config, $allowed, true) and file_exists($file)) include_once($file);
}
// apply the options
$this->options = array_merge($this->options, c::$data);
// overwrite the autodetected url
if($this->options['url']) {
$this->urls->index = $this->options['url'];
}
// connect the url class with its handlers
url::$home = $this->urls()->index();
url::$to = $this->option('url.to', function($url = '', $lang = null) {
if(url::isAbsolute($url)) return $url;
$start = substr($url, 0, 1);
switch($start) {
case '#':
return $url;
break;
case '.':
return page()->url() . '/' . $url;
break;
default:
if($page = page($url)) {
// use the "official" page url
return $page->url($lang);
} else {
// don't convert absolute urls
return url::makeAbsolute($url);
}
break;
}
});
// setup the pagination redirect to the error page
pagination::$defaults['redirect'] = $this->option('error');
// setting up the email class
email::$defaults['service'] = $this->option('email.service');
email::$defaults['from'] = $this->option('email.from');
email::$defaults['to'] = $this->option('email.to');
email::$defaults['replyTo'] = $this->option('email.replyTo');
email::$defaults['subject'] = $this->option('email.subject');
email::$defaults['body'] = $this->option('email.body');
email::$defaults['options'] = $this->option('email.options');
// simple error handling
if($this->options['debug'] === true) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else if($this->options['debug'] === false) {
error_reporting(0);
ini_set('display_errors', 0);
}
}
/**
* Registers all routes
*
* @param array $routes New routes
* @return array
*/
public function routes($routes = array()) {
// extend the existing routes
if(!empty($routes) and is_array($routes)) {
return $this->options['routes'] = array_merge($this->options['routes'], $routes);
}
$routes = $this->options['routes'];
$kirby = $this;
$site = $this->site();
if($site->multilang()) {
foreach($site->languages() as $lang) {
$routes[] = array(
'pattern' => ltrim($lang->url . '/(:all?)', '/'),
'method' => 'ALL',
'lang' => $lang,
'action' => function($path = null) use($kirby, $site) {
return $site->visit($path, $kirby->route->lang->code());
}
);
}
// fallback for the homepage
$routes[] = array(
'pattern' => '/',
'method' => 'ALL',
'action' => function() use($kirby, $site) {
// check if the language detector is activated
if($kirby->option('language.detect')) {
if(s::get('language') and $language = $kirby->site()->sessionLanguage()) {
// $language is already set but the user wants to
// select the default language
$referer = r::referer();
if(!empty($referer) && str::startsWith($referer, $this->urls()->index())) {
$language = $kirby->site()->defaultLanguage();
}
} else {
// detect the user language
$language = $kirby->site()->detectedLanguage();
}
} else {
// always use the default language if the detector is disabled
$language = $kirby->site()->defaultLanguage();
}
// redirect to the language homepage if necessary
if($language->url != '/' and $language->url != '') {
go($language->url());
}
// plain home pages
return $site->visit('/', $language->code());
}
);
}
// tinyurl handling
$routes['tinyurl'] = $this->component('tinyurl')->route();
// home redirect
$routes['homeRedirect'] = array(
'pattern' => $this->options['home'],
'action' => function() {
redirect::send(page('home')->url(), 307);
}
);
// plugin assets
$routes['pluginAssets'] = array(
'pattern' => 'assets/plugins/(:any)/(:all)',
'method' => 'GET',
'action' => function($plugin, $path) use($kirby) {
$root = $kirby->roots()->plugins() . DS . $plugin . DS . 'assets' . DS . $path;
$file = new Media($root);
if($file->exists()) {
return new Response(f::read($root), f::extension($root));
} else {
return new Response('The file could not be found', f::extension($path), 404);
}
}
);
// all other urls
$routes['others'] = array(
'pattern' => '(:all)',
'method' => 'ALL',
'action' => function($path = null) use($site, $kirby) {
// visit the currently active page
$page = $site->visit($path);
// react on errors for invalid URLs
if($page->isErrorPage() and $page->uri() != $path) {
// get the filename
$filename = rawurldecode(basename($path));
$pagepath = dirname($path);
// check if there's a page for the parent path
if($page = $site->find($pagepath)) {
// check if there's a file for the last element of the path
if($file = $page->file($filename)) {
go($file->url());
}
}
// return the error page if there's no such page
return $site->errorPage();
}
return $page;
}
);
return $routes;
}
/**
* Loads all available plugins for the site
*
* @return array
*/
public function plugins() {
// check for a cached plugins array
if(!is_null($this->plugins)) return $this->plugins;
// get the plugins root
$root = $this->roots->plugins();
// start the plugin registry
$this->plugins = array();
// check for an existing plugins dir
if(!is_dir($root)) return $this->plugins;
foreach(array_diff(scandir($root), array('.', '..')) as $file) {
if(is_dir($root . DS . $file)) {
$this->plugin($file, 'dir');
} else if(f::extension($file) == 'php') {
$this->plugin(f::name($file), 'file');
}
}
return $this->plugins;
}
/**
* Loads a single plugin
*
* @param string $name
* @param string $mode
* @return mixed
*/
public function plugin($name, $mode = 'dir') {
if(isset($this->plugins[$name])) return $this->plugins[$name];
if($mode == 'dir') {
$file = $this->roots->plugins() . DS . $name . DS . $name . '.php';
} else {
$file = $this->roots->plugins() . DS . $name . '.php';
}
// make the kirby variable available in plugin files
$kirby = $this;
if(file_exists($file)) return $this->plugins[$name] = include_once($file);
return false;
}
/**
* Load all default extensions
*/
public function extensions() {
// load all kirby tags and field methods
include_once(__DIR__ . DS . 'extensions' . DS . 'tags.php');
include_once(__DIR__ . DS . 'extensions' . DS . 'methods.php');
// install additional kirby tags
kirbytext::install($this->roots->tags());
}
/**
* Autoloads all page models
*/
public function models() {
if(!is_dir($this->roots()->models())) return false;
$root = $this->roots()->models();
$files = dir::read($root);
$load = array();
foreach($files as $file) {
if(f::extension($file) != 'php') continue;
$name = f::name($file);
$classname = str_replace(array('.', '-', '_'), '', $name . 'page');
$load[$classname] = $root . DS . $file;
// register the model
page::$models[$name] = $classname;
}
// start the autoloader
if(!empty($load)) {
load($load);
}
}
public function localize() {
$site = $this->site();
if($site->multilang() and !$site->language()) {
$site->language = $site->languages()->findDefault();
}
// set the local for the specific language
if(is_array($site->locale())) {
foreach($site->locale() as $key => $value) {
setlocale($key, $value);
}
} else {
setlocale(LC_ALL, $site->locale());
}
// additional language variables for multilang sites
if($site->multilang()) {
// path for the language file
$file = $this->roots()->languages() . DS . $site->language()->code() . '.php';
// load the file if it exists
if(file_exists($file)) include_once($file);
}
}
/**
* Returns the branch file
*
* @return string
*/
public function branch() {
// which branch?
$branch = count($this->options['languages']) > 0 ? 'multilang' : 'default';
// build the path for the branch file
return __DIR__ . DS . 'branches' . DS . $branch . '.php';
}
/**
* Initializes and returns the site object
* depending on the appropriate branch
*
* @return Site
*/
public function site() {
// check for a cached version of the site object
if(!is_null($this->site)) return $this->site;
// load all options
$this->configure();
// setup the cache
$this->cache();
// load the main branch file
include_once($this->branch());
// create the site object
return $this->site = new Site($this);
}
/**
* Cache setup
*
* @return Cache
*/
public function cache() {
if(!is_null($this->cache)) return $this->cache;
// cache setup
if($this->options['cache']) {
if($this->options['cache.driver'] == 'file' and empty($this->options['cache.options'])) {
$this->options['cache.options'] = array(
'root' => $this->roots()->cache()
);
}
return $this->cache = cache::setup($this->options['cache.driver'], $this->options['cache.options']);
} else {
return $this->cache = cache::setup('mock');
}
}
/**
* Renders the HTML for the page or fetches it from the cache
*
* @param Page $page
* @param boolean $headers
* @return string
*/
public function render(Page $page, $data = array(), $headers = true) {
// register the currently rendered page
$this->page = $page;
// send all headers for the page
if($headers) $page->headers();
// configure pagination urls
$query = (string)$this->request()->query();
$params = (string)$this->request()->params() . r($query, '?') . $query;
pagination::$defaults['url'] = $page->url() . r($params, '/') . $params;
// cache the result if possible
if($this->options['cache'] and $page->isCachable()) {
// try to read the cache by cid (cache id)
$cacheId = md5(url::current());
// check for modified content within the content folder
// and auto-expire the page cache in such a case
if($this->options['cache.autoupdate'] and $this->cache()->exists($cacheId)) {
// get the creation date of the cache file
$created = $this->cache()->created($cacheId);
// make sure to kill the cache if the site has been modified
if($this->site->wasModifiedAfter($created)) {
$this->cache()->remove($cacheId);
}
}
// try to fetch the template from cache
$template = $this->cache()->get($cacheId);
// fetch fresh content if the cache is empty
if(empty($template)) {
$template = $this->template($page, $data);
// store the result for the next round
$this->cache()->set($cacheId, $template);
}
return $template;
}
// return a fresh template
return $this->template($page, $data);
}
/**
* Template configuration
*
* @param Page $page
* @param array $data
* @return string
*/
public function template(Page $page, $data = array()) {
return $this->component('template')->render($page, $data);
}
public function request() {
if(!is_null($this->request)) return $this->request;
return $this->request = new Request($this);
}
public function router() {
return $this->router;
}
public function route() {
return $this->route;
}
/**
* Starts the router, renders the page and returns the response
*
* @return mixed
*/
public function launch() {
// this will trigger the configuration
$site = $this->site();
// force secure connections if enabled
if($this->option('ssl') and !r::secure()) {
// rebuild the current url with https
go(url::build(array('scheme' => 'https')));
}
// set the timezone for all date functions
date_default_timezone_set($this->options['timezone']);
// load all extensions
$this->extensions();
// load all plugins
$this->plugins();
// load all models
$this->models();
// start the router
$this->router = new Router($this->routes());
$this->route = $this->router->run($this->path());
// check for a valid route
if(is_null($this->route)) {
header::status('500');
header::type('json');
die(json_encode(array(
'status' => 'error',
'message' => 'Invalid route or request method'
)));
}
// call the router action with all arguments from the pattern
$response = call($this->route->action(), $this->route->arguments());
// load all language variables
// this can only be loaded once the router action has been called
// otherwise the current language is not yet available
$this->localize();
// build the response
$this->response = $this->component('response')->make($response);
// store the current language in the session
if($this->site()->multilang() && $language = $this->site()->language()) {
s::set('language', $language->code());
}
return $this->response;
}
/**
* Register a new hook
*
* @param string $hook The name of the hook
* @param closure $callback
*/
public function hook($hook, $callback) {
if(isset(static::$hooks[$hook]) and is_array(static::$hooks[$hook])) {
static::$hooks[$hook][] = $callback;
} else {
static::$hooks[$hook] = array($callback);
}
}
/**
* Trigger a hook
*
* @param string $hook The name of the hook
* @param mixed $args Additional arguments for the hook
* @return mixed
*/
public function trigger($hook, $args = null) {
if(isset(static::$hooks[$hook]) and is_array(static::$hooks[$hook])) {
foreach(static::$hooks[$hook] as $key => $callback) {
if(array_key_exists($hook, static::$triggered) && in_array($key, static::$triggered[$hook])) continue;
static::$triggered[$hook] = $key;
try {
call($callback, $args);
} catch(Exception $e) {
// caught callback error
}
}
}
}
static public function start() {
return kirby()->launch();
}
/**
* Register and fetch core components
*/
public function component($name, $component = null) {
if(is_null($component)) {
if(!isset($this->components[$name])) {
// load the default component if it exists
if(file_exists(__DIR__ . DS . 'kirby' . DS . 'component' . DS . strtolower($name) . '.php')) {
$this->component($name, 'Kirby\\Component\\' . $name);
} else {
throw new Exception('The component "' . $name . '" does not exist');
}
}
return $this->components[$name];
} else {
if(!is_string($component)) {
throw new Exception('Please provide a valid component name');
}
// init the component
$object = new $component($this);
if(!is_a($object, 'Kirby\\Component')) {
throw new Exception('The component "' . $name . '" must be an instance of the Kirby\\Component class');
}
if(!is_a($object, 'Kirby\\Component\\' . $name)) {
throw new Exception('The component "' . $name . '" must be an instance of the Kirby\\Component\\' . ucfirst($name) . ' class');
}
// add the component defaults
$this->options = array_merge($object->defaults(), $this->options);
// configure the component
$object->configure();
// register the component
$this->components[$name] = $object;
}
}
}

27
kirby/kirby/component.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace Kirby;
use Kirby;
class Component {
protected $kirby;
public function __construct(Kirby $kirby) {
$this->kirby = $kirby;
}
public function defaults() {
return [];
}
public function configure() {
}
public function kirby() {
return $this->kirby;
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Kirby\Component;
use HTML;
/**
* Kirby CSS Tag Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class CSS extends \Kirby\Component {
/**
* Builds the html link tag for the given css file
*
* @param string $url
* @param null|string $media
* @return string
*/
public function tag($url, $media = null) {
if(is_array($url)) {
$css = array();
foreach($url as $u) $css[] = $this->tag($u, $media);
return implode(PHP_EOL, $css) . PHP_EOL;
}
// auto template css files
if($url == '@auto') {
$file = $this->kirby->site()->page()->template() . '.css';
$root = $this->kirby->roots()->autocss() . DS . $file;
$url = $this->kirby->urls()->autocss() . '/' . $file;
if(!file_exists($root)) return false;
}
return html::tag('link', null, array(
'rel' => 'stylesheet',
'href' => url($url),
'media' => $media
));
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Kirby\Component;
use HTML;
/**
* Kirby Script Tag Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class JS extends \Kirby\Component {
/**
* Builds the html script tag for the given javascript file
*
* @param string $src
* @param boolean async
* @return string
*/
public function tag($src, $async = false) {
if(is_array($src)) {
$js = array();
foreach($src as $s) $js[] = $this->tag($s, $async);
return implode(PHP_EOL, $js) . PHP_EOL;
}
// auto template css files
if($src == '@auto') {
$file = $this->kirby->site()->page()->template() . '.js';
$root = $this->kirby->roots()->autojs() . DS . $file;
$src = $this->kirby->urls()->autojs() . '/' . $file;
if(!file_exists($root)) return false;
}
return html::tag('script', '', array(
'src' => url($src),
'async' => $async
));
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Kirby\Component;
use Parsedown;
use ParsedownExtra;
/**
* Kirby Markdown Parser Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Markdown extends \Kirby\Component {
/**
* Returns the default options for the component
*
* @return array
*/
public function defaults() {
return [
'markdown' => true,
'markdown.extra' => false,
'markdown.breaks' => true,
];
}
/**
* Initializes the Parsedown parser and
* transforms the given markdown to HTML
*
* @param string $markdown
* @return string
*/
public function parse($markdown) {
if(!$this->kirby->options['markdown']) {
return $markdown;
} else {
// initialize the right markdown class
$parsedown = $this->kirby->options['markdown.extra'] ? new ParsedownExtra() : new Parsedown();
// set markdown auto-breaks
$parsedown->setBreaksEnabled($this->kirby->options['markdown.breaks']);
// parse it!
return $parsedown->text($markdown);
}
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Kirby\Component;
/**
* Kirby Response Builder Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Response extends \Kirby\Component {
/**
* Builds and return the response by various input
*
* @param mixed $response
* @return mixed
*/
public function make($response) {
if(is_string($response)) {
return $this->kirby->render(page($response));
} else if(is_array($response)) {
return $this->kirby->render(page($response[0]), $response[1]);
} else if(is_a($response, 'Page')) {
return $this->kirby->render($response);
} else if(is_a($response, 'Response')) {
return $response;
} else {
return null;
}
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Kirby\Component;
use SmartyPantsTypographer_Parser;
/**
* Kirby Smartypants Parser Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Smartypants extends \Kirby\Component {
/**
* Returns the default options for
* the smartypants parser
*
* @return array
*/
public function defaults() {
return [
'smartypants' => false,
'smartypants.attr' => 1,
'smartypants.doublequote.open' => '&#8220;',
'smartypants.doublequote.close' => '&#8221;',
'smartypants.space.emdash' => ' ',
'smartypants.space.endash' => ' ',
'smartypants.space.colon' => '&#160;',
'smartypants.space.semicolon' => '&#160;',
'smartypants.space.marks' => '&#160;',
'smartypants.space.frenchquote' => '&#160;',
'smartypants.space.thousand' => '&#160;',
'smartypants.space.unit' => '&#160;',
'smartypants.skip' => 'pre|code|kbd|script|style|math',
];
}
/**
* Initializes the parser and transforms
* the given text.
*
* @param string $text
* @return string
*/
public function parse($text) {
if(!$this->kirby->options['smartypants']) {
return $text;
} else {
// prepare the text
$text = str_replace('&quot;', '"', $text);
// run the parser
$parser = new SmartyPantsTypographer_Parser($this->kirby->options['smartypants.attr']);
return $parser->transform($text);
}
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Kirby\Component;
use Tpl;
/**
* Kirby Snippet Builder Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Snippet extends \Kirby\Component {
/**
* Returns a snippet file path by name
*
* @param string $name
* @return string
*/
public function file($name) {
return $this->kirby->roots()->snippets() . DS . str_replace('/', DS, $name) . '.php';
}
/**
* Renders the snippet with the given data
*
* @param string $name
* @param array $data
* @param boolean $return
* @return string
*/
public function render($name, $data = [], $return = false) {
if(is_object($data)) $data = ['item' => $data];
return tpl::load($this->kirby->registry->get('snippet', $name), $data, $return);
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Kirby\Component;
use Exception;
use Page;
use Tpl;
/**
* Kirby Template Builder Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Template extends \Kirby\Component {
/**
* Collects all template data by page
*
* @param mixed $page
* @param array $data
* @return array
*/
public function data($page, $data = []) {
if($page instanceof Page) {
$data = array_merge(
$page->templateData(),
$data,
$page->controller($data)
);
}
// apply the basic template vars
return array_merge(array(
'kirby' => $this->kirby,
'site' => $this->kirby->site(),
'pages' => $this->kirby->site()->children(),
'page' => $page
), $data);
}
/**
* Returns a template file path by name
*
* @param string $name
* @return string
*/
public function file($name) {
return $this->kirby->roots()->templates() . DS . str_replace('/', DS, $name) . '.php';
}
/**
* Renders the template by page with the additional data
*
* @param Page|string $template
* @param array $data
* @param boolean $return
* @return string
*/
public function render($template, $data = [], $return = true) {
if($template instanceof Page) {
$page = $template;
$file = $page->templateFile();
$data = $this->data($page, $data);
} else {
$file = $template;
$data = $this->data(null, $data);
}
// check for an existing template
if(!file_exists($file)) {
throw new Exception('The template could not be found');
}
// merge and register the template data globally
tpl::$data = array_merge(tpl::$data, $data);
// load the template
return tpl::load($file, null, $return);
}
}

View file

@ -0,0 +1,206 @@
<?php
namespace Kirby\Component;
use A;
use Asset;
use F;
use File;
use Media;
use Obj;
use R;
use Redirect;
use Str;
use Thumb as Generator;
use Kirby\Component;
/**
* Kirby Thumb Render and API Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Thumb extends Component {
/**
* Returns the default options for the thumb component
*
* @return array
*/
public function defaults() {
$self = $this;
return [
'thumbs.driver' => 'gd',
'thumbs.bin' => 'convert',
'thumbs.interlace' => false,
'thumbs.quality' => 90,
'thumbs.memory' => '128M',
'thumbs.filename' => false,
'thumbs.destination' => function($thumb) use($self) {
$path = $self->path($thumb);
return new Obj([
'root' => $self->kirby->roots()->thumbs() . DS . str_replace('/', DS, $path),
'url' => $self->kirby->urls()->thumbs() . '/' . $path,
]);
}
];
}
/**
* Configures the thumb driver
*/
public function configure() {
$self = $this;
// setup the thumbnail location
generator::$defaults['root'] = $this->kirby->roots->thumbs();
generator::$defaults['url'] = $this->kirby->urls->thumbs();
// setup the default thumbnail options
generator::$defaults['driver'] = $this->kirby->option('thumbs.driver');
generator::$defaults['bin'] = $this->kirby->option('thumbs.bin');
generator::$defaults['quality'] = $this->kirby->option('thumbs.quality');
generator::$defaults['interlace'] = $this->kirby->option('thumbs.interlace');
generator::$defaults['memory'] = $this->kirby->option('thumbs.memory');
generator::$defaults['destination'] = $this->kirby->option('thumbs.destination');
generator::$defaults['filename'] = $this->kirby->option('thumbs.filename');
}
public function create($file, $params) {
if(!$file->isWebsafe()) {
return $file;
}
$thumb = new Generator($file, $params);
$asset = new Asset($thumb->result);
// store a reference to the original file
$asset->original($file);
return $thumb->exists() ? $asset : $file;
}
/**
* Returns the clean path for a thumbnail
*
* @param Generator $thumb
* @return string
*/
protected function path(Generator $thumb) {
return ltrim($this->dir($thumb) . '/' . $this->filename($thumb), '/');
}
/**
* @param Generator $thumb
* @return string
*/
protected function dir(Generator $thumb) {
if(is_a($thumb->source, 'File')) {
return $thumb->source->page()->id();
} else {
return str_replace($this->kirby->urls()->index(), '', dirname($thumb->source->url()));
}
}
/**
* Returns the filename for a thumb including the
* identifying option hash
*
* @param Generator $thumb
* @return string
*/
protected function filename(Generator $thumb) {
$dimensions = $this->dimensions($thumb);
$wh = $dimensions->width() . 'x' . $dimensions->height();
$safeName = f::safeName($thumb->source->name());
$options = $this->options($thumb);
$extension = $thumb->source->extension();
if($thumb->options['filename'] === false) {
return $safeName . '-' . $wh . r($options, '-' . $options) . '.' . $extension;
} else {
return str::template($thumb->options['filename'], [
'extension' => $extension,
'name' => $thumb->source->name(),
'filename' => $thumb->source->filename(),
'safeName' => $safeName,
'safeFilename' => $safeName . '.' . $extension,
'width' => $dimensions->width(),
'height' => $dimensions->height(),
'dimensions' => $wh,
'options' => $options,
'hash' => md5($thumb->source->root() . $thumb->settingsIdentifier()),
]);
}
}
/**
* Returns an identifying option hash for thumb filenames
*
* @param Generator $thumb
* @return string
*/
protected function options(Generator $thumb) {
$keys = [
'blur' => 'blur',
'grayscale' => 'bw',
'quality' => 'q',
];
$string = [];
foreach($keys as $long => $key) {
$value = a::get($thumb->options, $long);
if($value === true) {
$string[] = $key;
} else if($value === false) {
continue;
} else if($key === 'q' && $value == generator::$defaults['quality']) {
// ignore the default quality setting
continue;
} else {
$string[] = $key . $value;
}
}
return implode('-', array_filter($string));
}
/**
* @param Generator $thumb
* @return string
*/
protected function dimensions(Generator $thumb) {
$dimensions = clone $thumb->source->dimensions();
if(isset($thumb->options['crop']) && $thumb->options['crop']) {
$dimensions->crop(a::get($thumb->options, 'width'), a::get($thumb->options, 'height'));
} else {
$dimensions->resize(a::get($thumb->options, 'width'), a::get($thumb->options, 'height'), a::get($thumb->options, 'upscale'));
}
return $dimensions;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Kirby\Component;
/**
* Kirby TinyUrl Component
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class TinyUrl extends \Kirby\Component {
/**
* Returns the default options for the tinyurl component
*
* @return array
*/
public function defaults() {
return [
'tinyurl.enabled' => true,
'tinyurl.folder' => 'x',
];
}
/**
* Returns the tinyurl fetching route
*
* @return array
*/
public function route() {
if(!$this->kirby->options['tinyurl.enabled']) {
return false;
} else {
return [
'pattern' => $this->kirby->options['tinyurl.folder'] . '/(:any)/(:any?)',
'action' => function($hash, $lang = null) {
// get the site object
$site = site();
// make sure the language is set
$site->visit('/', $lang);
// find the page by it's tiny hash
if($page = $site->index()->findBy('hash', $hash)) {
go($page->url($lang));
} else {
return $site->errorPage();
}
}
];
}
}
}

118
kirby/kirby/registry.php Normal file
View file

@ -0,0 +1,118 @@
<?php
namespace Kirby;
use Exception;
use Kirby;
use Str;
/**
* Registry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Registry {
/**
* Kirby Instance
*
* @var Kirby
*/
protected $kirby;
/**
* @param Kirby $kirby
*/
public function __construct(Kirby $kirby) {
$this->kirby = $kirby;
// start the registry entry autoloader
load([
'kirby\\registry\\entry' => __DIR__ . DS . 'registry' . DS . 'entry.php',
'kirby\\registry\\blueprint' => __DIR__ . DS . 'registry' . DS . 'blueprint.php',
'kirby\\registry\\component' => __DIR__ . DS . 'registry' . DS . 'component.php',
'kirby\\registry\\controller' => __DIR__ . DS . 'registry' . DS . 'controller.php',
'kirby\\registry\\hook' => __DIR__ . DS . 'registry' . DS . 'hook.php',
'kirby\\registry\\field' => __DIR__ . DS . 'registry' . DS . 'field.php',
'kirby\\registry\\method' => __DIR__ . DS . 'registry' . DS . 'method.php',
'kirby\\registry\\model' => __DIR__ . DS . 'registry' . DS . 'model.php',
'kirby\\registry\\option' => __DIR__ . DS . 'registry' . DS . 'option.php',
'kirby\\registry\\route' => __DIR__ . DS . 'registry' . DS . 'route.php',
'kirby\\registry\\snippet' => __DIR__ . DS . 'registry' . DS . 'snippet.php',
'kirby\\registry\\template' => __DIR__ . DS . 'registry' . DS . 'template.php',
'kirby\\registry\\tag' => __DIR__ . DS . 'registry' . DS . 'tag.php',
'kirby\\registry\\widget' => __DIR__ . DS . 'registry' . DS . 'widget.php',
]);
}
/**
* Returns the Kirby instance
*
* @return Kirby
*/
public function kirby() {
return $this->kirby;
}
/**
* Returns a registry entry object by type
*
* @param string $type
* @param string $subtype
* @return Kirby\Registry\Entry
*/
public function entry($type, $subtype = null) {
$class = 'kirby\\registry\\' . $type;
if(!class_exists('kirby\\registry\\' . $type)) {
if(str::contains($type, '::')) {
$parts = str::split($type, '::');
$subtype = $parts[0];
$type = $parts[1];
return $this->entry($type, $subtype);
}
throw new Exception('Unsupported registry entry type: ' . $type);
}
return new $class($this, $subtype);
}
/**
* Adds a new entry to the registry
* This will initialize a registry object
* and call the set method of it
* with the passed arguments
*/
public function set() {
$args = func_get_args();
$type = strtolower(array_shift($args));
return $this->entry($type)->call('set', $args);
}
/**
* Retrieves an entry from the registry
*
* This will initialize a registry object
* and call the get method of it
* with the passed arguments
*
* @return Entry
*/
public function get() {
$args = func_get_args();
$type = array_shift($args);
return $this->entry($type)->call('get', $args);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
use F;
/**
* Blueprint Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Blueprint extends Entry {
/**
* Blueprint store
*
* @var array $blueprints
*/
protected static $blueprints = [];
/**
* Adds a new blueprint entry
*
* Pass a path to an existing blueprint file
* to add it to the registry
*
* @param string $name
* @param string $path
* @return $path
*/
public function set($name, $path) {
if(!$this->kirby->option('debug') || file_exists($path)) {
return static::$blueprints[$name] = $path;
}
throw new Exception('The blueprint does not exist at the specified path: ' . $path);
}
/**
* Retreives a registered blueprint file path
*
* @param string $name
* @return string
*/
public function get($name = null) {
if(is_null($name)) {
return static::$blueprints;
}
$file = f::resolve($this->kirby->roots()->blueprints() . DS . str_replace('/', DS, $name), ['php', 'yml', 'yaml']);
if(file_exists($file)) {
return $file;
} else {
return a::get(static::$blueprints, $name);
}
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Kirby\Registry;
/**
* Component Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Component extends Entry {
/**
* Adds a new core component to Kirby
*
* This will directly call the component method of the
* Kirby instance to register the component
*
* @param string $name The name of the component
* @param string $class A valid component classname. Must be extend the according Kirby component type class
*/
public function set($name, $class) {
return $this->kirby->component($name, $class);
}
/**
* Retreives a component from the Kirby component registry
*
* @param string $name
* @return Kirby\Component
*/
public function get($name) {
return $this->kirby->component($name);
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
/**
* Controller Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Controller extends Entry {
/**
* Store of registered controllers
*
* @var array $controllers
*/
protected static $controllers = [];
/**
* Adds a new controller to the registry
*
* @param string $name
* @param Closure $callback Must be a valid controller callback
* @return Closure
*/
public function set($name, $callback) {
$name = strtolower($name);
if($name === 'site') {
throw new Exception('You are not allowed to set the site controller');
}
if(!$this->kirby->option('debug') || is_a($callback, 'Closure') || file_exists($callback)) {
return static::$controllers[$name] = $callback;
} else {
throw new Exception('Invalid controller. You must pass a closure or an existing file');
}
}
/**
* Retreives a controller from the registry
*
* @param string $name
* @return Closure
*/
public function get($name) {
$name = strtolower($name);
$file = $this->kirby->roots()->controllers() . DS . $name . '.php';
if(file_exists($file)) {
return include_once $file;
}
if(isset(static::$controllers[$name])) {
if(is_a(static::$controllers[$name], 'Closure')) {
return static::$controllers[$name];
} else if(file_exists(static::$controllers[$name])) {
return include_once static::$controllers[$name];
}
}
if(file_exists($this->kirby->roots()->controllers() . DS . 'site.php')) {
return include_once $this->kirby->roots()->controllers() . DS . 'site.php';
}
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Kirby\Registry;
use Kirby;
use Kirby\Registry;
/**
* Registy Entry
*
* Base Entry Class. All other registry entries
* must extend this class to inherit basic
* functionalities of registry entries and to
* be accepted by the registry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Entry {
/**
* Kirby instance
*
* @var Kirby
*/
protected $kirby;
/**
* Kirby Registry instance
*
* @var Kirby\Registry
*/
protected $registry;
/**
* Optional subtype for something
* like $kirby->set('field::method', '…')
* where `field` is the subtype of type `method`.
*
* @param string $subtype
*/
protected $subtype;
/**
* @param Kirby $kirby
* @param Kirby\Registry $registry
* @param string $subtype
*/
public function __construct(Registry $registry, $subtype = null) {
$this->registry = $registry;
$this->kirby = $registry->kirby();
$this->subtype = $subtype;
}
/**
* Interface to call any registry entry method
*
* Mostly used for set() and get()
*
* @param string $method
* @param array $args
* @return mixed
*/
public function call($method, $args) {
return call([$this, $method], $args);
}
/**
* Returns the Kirby instance
*
* @return Kirby
*/
public function kirby() {
return $this->kirby;
}
/**
* Returns the Registry instance
*
* @return Kirby\Registry
*/
public function registry() {
return $this->registry;
}
/**
* Returns the optional subtype
*
* @return string
*/
public function subtype() {
return $this->subtype;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
use Obj;
/**
* Field Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Field extends Entry {
/**
* Store for registered fields
*
* @var array $fields
*/
protected static $fields = [];
/**
* Adds a new field to the registry
*
* @param string $name
* @param string $root valid field directory path
* @return Obj generic Kirby object with info about the field
*/
public function set($name, $root) {
$name = strtolower($name);
$file = $root . DS . $name . '.php';
if(!$this->kirby->option('debug') || (is_dir($root) && is_file($file))) {
return static::$fields[$name] = new Obj([
'root' => $root,
'file' => $file,
'name' => $name,
'class' => $name . 'field',
]);
}
throw new Exception('The field does not exist at the specified path: ' . $root);
}
/**
* Retreives a field info object from the registry
*
* @param string|null $name If null, all registered fields will be returned as array
* @param Obj|null|array
*/
public function get($name = null) {
if(is_null($name)) {
return static::$fields;
}
return a::get(static::$fields, $name);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Kirby\Registry;
/**
* Hook Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Hook extends Entry {
/**
* Registers a new hook
*
* This will directly call the $kirby->hook() method
* A hook has to be a valid closure
*
* @param string $name
* @param Closure $callback
* @return Closure
*/
public function set($name, $callback) {
return $this->kirby->hook($name, $callback);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
use Kirby;
use Kirby\Registry;
/**
* Method Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Method extends Entry {
/**
* List of allowed subtypes
*
* @var array $subtypes
*/
protected $subtypes = ['site', 'page', 'pages', 'file', 'files', 'field'];
/**
* @param Kirby\Registry $registry
* @param string $subtype
*/
public function __construct(Registry $registry, $subtype) {
parent::__construct($registry, $subtype);
if(!in_array($this->subtype, $this->subtypes)) {
throw new Exception('Invalid method type: ' . $this->subtype . '::method');
}
}
/**
* Adds a new method to the registry
*
* A method can be registered for any of the allowed
* subtypes, by using the static method syntax:
* $kirby->set('page::method')
* $kirby->set('field::method')
* etc.
*
* The first part of the name is the subtype.
* The second part of the name is the main type (`method` in this case)
*
* @param string $name
* @param Closure $callback
* @return Closure
*/
public function set($name, $callback) {
$class = $this->subtype;
return $class::$methods[$name] = $callback;
}
/**
* Retrieves a registered method
*
* @param string $name
* @return Closure
*/
public function get($name) {
$class = $this->subtype;
return a::get($class::$methods, $name);
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
use Kirby;
use Kirby\Registry;
/**
* Model Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Model extends Entry {
/**
* List of allowed subtypes
*
* @var array $subtypes
*/
protected $subtypes = ['page'];
/**
* @param Kirby\Registry $registry
* @param string $subtype
*/
public function __construct(Registry $registry, $subtype) {
parent::__construct($registry, $subtype);
if(!in_array($this->subtype, $this->subtypes)) {
throw new Exception('Invalid model type: ' . $this->subtype . '::model');
}
}
/**
* Adds a new model to the registry
*
* A model can be registered for any of the allowed
* subtypes, by using the static method syntax:
*
* $kirby->set('page::model')
*
* The first part of the name is the subtype.
* The second part of the name is the main type (`model` in this case)
*
* @param string $name
* @param string $classname Must be a valid classname of a loaded/auto-loaded class
* @return string
*/
public function set($name, $classname) {
$class = $this->subtype;
if(!class_exists($classname)) {
throw new Exception('The model class does not exist: ' . $classname);
}
return $class::$models[$name] = $classname;
}
/**
* Retrieves a registered model
*
* @param string $name
* @return string
*/
public function get($name) {
$class = $this->subtype;
return a::get($class::$models, $name);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Kirby\Registry;
/**
* Option Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Option extends Entry {
/**
* Sets a Kirby option
*
* This directly adds passed options to the
* $kirby->options array and is just a convenient
* way to do this through the registry
*
* @param string $key
* @param mixed $value
* @return mixed
*/
public function set($key, $value) {
return $this->kirby->options[$key] = $value;
}
/**
* Retreives an option from the $kirby->$options array
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null) {
return $this->kirby->option($key, $default);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Kirby\Registry;
/**
* Route Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Route extends Entry {
/**
* Registers a new route
*
* This will directly add a route to
* Kirby's route system, by calling $kirby->routes()
*
* @param string $attr
*/
public function set($attr) {
$this->kirby->routes([$attr]);
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
/**
* Snippet Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Snippet extends Entry {
/**
* List of registered snippet files
*
* @var array $snippets
*/
protected static $snippets = [];
/**
* Registers a new snippet file
*
* You must pass an existing file in order
* to register it as a valid snippet
*
* @param string $name The name of the snippet. Can contain slashes (i.e. form/field)
* @param string $path
* @return string
*/
public function set($name, $path) {
if(!$this->kirby->option('debug') || file_exists($path)) {
return static::$snippets[$name] = $path;
}
throw new Exception('The snippet does not exist at the specified path: ' . $path);
}
/**
* Retrieve the file path for a registered snippet
*
* @param string $name
* @return string
*/
public function get($name) {
$file = $this->kirby->component('snippet')->file($name);
if(file_exists($file)) {
return $file;
} else {
return a::get(static::$snippets, $name);
}
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Kirby\Registry;
use A;
use Kirbytext;
/**
* Tag Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Tag extends Entry {
/**
* Registers a new kirby tag array
*
* This will directly add the tag to the
* kirbytext::$tags array.
*
* @param string $name
* @param array $tag
*/
public function set($name, $tag) {
kirbytext::$tags[$name] = $tag;
}
/**
* Retreives a registered kirby tag
*
* @param string $name
* @return array
*/
public function get($name) {
return a::get(kirbytext::$tags, $name);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
/**
* Template Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Template extends Entry {
/**
* List of registered template files
*
* @var array $templates
*/
protected static $templates = [];
/**
* Registers a new template file
*
* Must be an existing file
*
* @param string $name
* @param string $path
*/
public function set($name, $path) {
if(!$this->kirby->option('debug') || file_exists($path)) {
return static::$templates[$name] = $path;
}
throw new Exception('The template does not exist at the specified path: ' . $path);
}
/**
* Retrieves a registered template file
*
* @param string $name
* @return string
*/
public function get($name) {
$file = $this->kirby->component('template')->file($name);
if(file_exists($file)) {
return $file;
} else {
return a::get(static::$templates, $name);
}
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Kirby\Registry;
use A;
use Exception;
/**
* Widget Registy Entry
*
* @package Kirby CMS
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://getkirby.com/license
*/
class Widget extends Entry {
/**
* List of registered widget directories
*
* @var array $widgets
*/
protected static $widgets = [];
/**
* Registers a new widget
*
* You must pass an existing widget directory
*
* @param string $name
* @param string $path
* @return string
*/
public function set($name, $path) {
if(!$this->kirby->option('debug') || is_dir($path)) {
return static::$widgets[$name] = $path;
}
throw new Exception('The widget does not exist at the specified path: ' . $path);
}
/**
* Retreives a registered widget directory
*
* @param string|null $name If null, all registered widgets will be returned as array
* @return string|array
*/
public function get($name = null) {
if(is_null($name)) {
return static::$widgets;
}
$file = $this->kirby->roots()->widgets() . DS . str_replace('/', DS, $name) . '.php';
if(file_exists($file)) {
return $file;
} else {
return a::get(static::$widgets, $name);
}
}
}

41
kirby/kirby/request.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace Kirby;
use Exception;
use R;
use URL;
class Request {
protected $kirby;
public function __construct($kirby) {
$this->kirby = $kirby;
}
public function url() {
return url::current();
}
public function params() {
return new Request\Params(url::params());
}
public function query() {
return new Request\Query(url::query());
}
public function path() {
return new Request\Path($this->kirby->path());
}
public function __call($method, $arguments) {
if(method_exists('r', $method)) {
return call('r::' . $method, $arguments);
} else {
throw new Exception('Invalid method: ' . $method);
}
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Kirby\Request;
use Obj;
use Url;
class Params extends Obj {
public function __toString() {
$params = array();
foreach((array)$this as $key => $value) {
$params[] = $key . url::paramSeparator() . $value;
}
return implode('/', $params);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Kirby\Request;
use Collection;
use Str;
class Path extends Collection {
public function __construct($path) {
parent::__construct(str::split($path, '/'));
}
public function __toString() {
return implode('/', $this->data);
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Kirby\Request;
use Obj;
class Query extends Obj {
public function __toString() {
return http_build_query((array)$this);
}
}

99
kirby/kirby/roots.php Normal file
View file

@ -0,0 +1,99 @@
<?php
namespace Kirby;
use Obj;
class Roots extends Obj {
public $index;
public function __construct($index) {
$this->index = $index;
}
public function content() {
return isset($this->content) ? $this->content : $this->index . DS . 'content';
}
public function site() {
return isset($this->site) ? $this->site : $this->index . DS . 'site';
}
public function kirby() {
return isset($this->kirby) ? $this->kirby : $this->index . DS . 'kirby';
}
public function thumbs() {
return isset($this->thumbs) ? $this->thumbs : $this->index . DS . 'thumbs';
}
public function assets() {
return isset($this->assets) ? $this->assets : $this->index . DS . 'assets';
}
public function autocss() {
return isset($this->autocss) ? $this->autocss : $this->assets() . DS . 'css' . DS . 'templates';
}
public function autojs() {
return isset($this->autojs) ? $this->autojs : $this->assets() . DS . 'js' . DS . 'templates';
}
public function avatars() {
return isset($this->avatars) ? $this->avatars : $this->assets() . DS . 'avatars';
}
public function config() {
return $this->site() . DS . 'config';
}
public function accounts() {
return isset($this->accounts) ? $this->accounts : $this->site() . DS . 'accounts';
}
public function blueprints() {
return $this->site() . DS . 'blueprints';
}
public function plugins() {
return $this->site() . DS . 'plugins';
}
public function cache() {
return isset($this->cache) ? $this->cache : $this->site() . DS . 'cache';
}
public function tags() {
return $this->site() . DS . 'tags';
}
public function fields() {
return $this->site() . DS . 'fields';
}
public function widgets() {
return $this->site() . DS . 'widgets';
}
public function controllers() {
return $this->site() . DS . 'controllers';
}
public function models() {
return $this->site() . DS . 'models';
}
public function templates() {
return $this->site() . DS . 'templates';
}
public function snippets() {
return $this->site() . DS . 'snippets';
}
public function languages() {
return $this->site() . DS . 'languages';
}
}

View file

@ -0,0 +1,207 @@
<?php
namespace Kirby\Traits;
use A;
use Exception;
use Media;
use Str;
/**
*
*/
trait Image {
/**
* store for the original image file
*
* @var Media|Asset|File
*/
protected $original;
/**
* @param Media $original
* @return Media|this
*/
public function original(Media $original = null) {
if($original === null) {
return $this->original;
} else {
$this->original = $original;
return $this;
}
}
/**
* Creates a thumbnail for the image
*
* @param array $params
* @return Asset
*/
public function thumb($params = []) {
// don't scale thumbs further down
if($this->original()) {
throw new Exception('Thumbnails cannot be modified further');
} else {
return $this->kirby->component('thumb')->create($this, $params);
}
}
/**
* Scales the image if possible
*
* @param int $width
* @param mixed $height
* @param mixed $quality
* @return Asset
*/
public function resize($width, $height = null, $quality = null) {
$params = ['width' => $width];
if($height) $params['height'] = $height;
if($quality) $params['quality'] = $quality;
return $this->thumb($params);
}
/**
* Scales and crops the image if possible
*
* @param int $width
* @param mixed $height
* @param mixed $quality
* @return Asset
*/
public function crop($width, $height = null, $quality = null) {
$params = ['width' => $width, 'crop' => true];
if($height) $params['height'] = $height;
if($quality) $params['quality'] = $quality;
return $this->thumb($params);
}
/**
* Scales the width of the image
*
* @param int $width
* @param mixed $quality
* @return Asset
*/
public function width($width = null, $quality = null) {
if($width === null) {
return parent::width();
}
$params = ['width' => $width];
if($quality) $params['quality'] = $quality;
return $this->thumb($params);
}
/**
* Scales the height of the image
*
* @param int $height
* @param mixed $quality
* @return Asset
*/
public function height($height = null, $quality = null) {
if($height === null) {
return parent::height();
}
$params = ['height' => $height];
if($quality) $params['quality'] = $quality;
return $this->thumb($params);
}
/**
*
*/
public function ratio($ratio = null) {
if($ratio === null) {
return parent::ratio();
}
if($this->isLandscape() || $this->isSquare()) {
$width = $this->width();
$height = round($width / $ratio);
} else {
$height = $this->height();
$width = round($height * $ratio);
}
return $this->crop($width, $height);
}
/**
*
*/
public function scale($value) {
return $this->thumb(['width' => $this->width() * $value, 'upscale' => true]);
}
/**
* Converts the image to grayscale
*
* @return Asset
*/
public function bw() {
return $this->thumb(['grayscale' => true]);
}
/**
* Blurs the image
*
* @return Asset
*/
public function blur() {
return $this->thumb(['blur' => true]);
}
/**
* Checks if the asset is a thumbnail
*
* @return boolean
*/
public function isThumb() {
return str::startsWith($this->url(), $this->kirby->urls()->thumbs());
}
/**
* Check if the file/image has a websafe format
*
* @return boolean
*/
public function isWebsafe() {
return in_array(strtolower($this->extension()), ['jpg', 'jpeg', 'gif', 'png']);
}
/**
* Makes it possible to echo the entire object
*
* @return string
*/
public function __toString() {
if($this->isWebsafe()) {
return (string)$this->html();
} else {
return (string)$this->root;
}
}
}

47
kirby/kirby/urls.php Normal file
View file

@ -0,0 +1,47 @@
<?php
namespace Kirby;
use R;
use Server;
use URL;
class Urls {
public function index() {
if(isset($this->index)) return $this->index;
if(r::cli()) {
return $this->index = '/';
} else {
return $this->index = url::base() . preg_replace('!\/index\.php$!i', '', server::get('SCRIPT_NAME'));
}
}
public function content() {
return isset($this->content) ? $this->content : url::makeAbsolute('content', $this->index);
}
public function thumbs() {
return isset($this->thumbs) ? $this->thumbs : url::makeAbsolute('thumbs', $this->index);
}
public function assets() {
return isset($this->assets) ? $this->assets : url::makeAbsolute('assets', $this->index);
}
public function autocss() {
return isset($this->autocss) ? $this->autocss : $this->assets() . '/css/templates';
}
public function autojs() {
return isset($this->autojs) ? $this->autojs : $this->assets() . '/js/templates';
}
public function avatars() {
return isset($this->avatars) ? $this->avatars : $this->assets() . '/avatars';
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* Helper to create extended page objects
*
* @param mixed $page
*/
class PageExtension extends Page {
public function __construct($page) {
$page = is_string($page) ? page($page) : $page;
if($page) {
parent::__construct($page->parent(), $page->dirname());
} else {
throw new Exception('The page could not be found');
}
}
}

42
kirby/lib/structure.php Normal file
View file

@ -0,0 +1,42 @@
<?php
class Structure extends Collection {
public $page = null;
public function get($key, $default = null) {
if(isset($this->data[$key])) {
return $this->data[$key];
} else {
$lowerkeys = array_change_key_case($this->data, CASE_LOWER);
$lowerkey = strtolower($key);
if(isset($lowerkeys[$lowerkey])) {
return $lowerkeys[$lowerkey];
}
}
return new Field($this->page, $key, null);
}
/**
* Get formatted date fields
*
* @param string $format
* @param string $field
* @return string
*/
public function date($format = null, $field = 'date') {
if($timestamp = strtotime($this->get($field))) {
if(is_null($format)) {
return $timestamp;
} else {
return kirby()->options['date.handler']($format, $timestamp);
}
}
}
}

18
kirby/readme.md Normal file
View file

@ -0,0 +1,18 @@
# Kirby Core
This is the Kirby Core submodule.
Please refer to the [Kirby Starterkit](http://github.com/getkirby/starterkit)
for a complete installation of Kirby
![Build Status](https://travis-ci.org/getkirby/kirby.svg?branch=master)
## Author
Bastian Allgeier
<bastian@getkirby.com>
## Website
<http://getkirby.com>
## License
<http://getkirby.com>

21
kirby/system.php Normal file
View file

@ -0,0 +1,21 @@
<?php
/**
* This is a legacy file, which makes it possible
* to keep using the old v1 index.php with v2.
* It's recommended to use the new index.php though.
*/
define('DS', DIRECTORY_SEPARATOR);
// load the bootstrapper
include(__DIR__ . DS . 'bootstrap.php');
// take the old variables to setup roots
$kirby = kirby();
$kirby->roots->index = $root;
$kirby->roots->site = $rootSite;
$kirby->roots->content = $rootContent;
// render
echo $kirby->launch();

View file

@ -0,0 +1,97 @@
<?php
if(!defined('DS')) define('DS', DIRECTORY_SEPARATOR);
if(!defined('MB')) define('MB', (int)function_exists('mb_get_info'));
if(!defined('BOM')) define('BOM', "\xEF\xBB\xBF");
// polyfill for new sort flag
if(!defined('SORT_NATURAL')) define('SORT_NATURAL', 'SORT_NATURAL');
// a super simple autoloader
function load($classmap, $base = null) {
spl_autoload_register(function($class) use ($classmap, $base) {
$class = strtolower($class);
if(!isset($classmap[$class])) return false;
if($base) {
include($base . DS . $classmap[$class]);
} else {
include($classmap[$class]);
}
});
}
// auto-load all toolkit classes
load(array(
// classes
'a' => __DIR__ . DS . 'lib' . DS . 'a.php',
'bitmask' => __DIR__ . DS . 'lib' . DS . 'bitmask.php',
'brick' => __DIR__ . DS . 'lib' . DS . 'brick.php',
'c' => __DIR__ . DS . 'lib' . DS . 'c.php',
'cookie' => __DIR__ . DS . 'lib' . DS . 'cookie.php',
'cache' => __DIR__ . DS . 'lib' . DS . 'cache.php',
'cache\\driver' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver.php',
'cache\\driver\\apc' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver' . DS . 'apc.php',
'cache\\driver\\file' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver' . DS . 'file.php',
'cache\\driver\\memcached' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver' . DS . 'memcached.php',
'cache\\driver\\mock' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver' . DS . 'mock.php',
'cache\\driver\\session' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'driver' . DS . 'session.php',
'cache\\value' => __DIR__ . DS . 'lib' . DS . 'cache' . DS . 'value.php',
'collection' => __DIR__ . DS . 'lib' . DS . 'collection.php',
'crypt' => __DIR__ . DS . 'lib' . DS . 'crypt.php',
'data' => __DIR__ . DS . 'lib' . DS . 'data.php',
'database' => __DIR__ . DS . 'lib' . DS . 'database.php',
'database\\query' => __DIR__ . DS . 'lib' . DS . 'database' . DS . 'query.php',
'db' => __DIR__ . DS . 'lib' . DS . 'db.php',
'detect' => __DIR__ . DS . 'lib' . DS . 'detect.php',
'dimensions' => __DIR__ . DS . 'lib' . DS . 'dimensions.php',
'dir' => __DIR__ . DS . 'lib' . DS . 'dir.php',
'email' => __DIR__ . DS . 'lib' . DS . 'email.php',
'embed' => __DIR__ . DS . 'lib' . DS . 'embed.php',
'error' => __DIR__ . DS . 'lib' . DS . 'error.php',
'errorreporting' => __DIR__ . DS . 'lib' . DS . 'errorreporting.php',
'escape' => __DIR__ . DS . 'lib' . DS . 'escape.php',
'exif' => __DIR__ . DS . 'lib' . DS . 'exif.php',
'exif\\camera' => __DIR__ . DS . 'lib' . DS . 'exif' . DS . 'camera.php',
'exif\\location' => __DIR__ . DS . 'lib' . DS . 'exif' . DS . 'location.php',
'f' => __DIR__ . DS . 'lib' . DS . 'f.php',
'folder' => __DIR__ . DS . 'lib' . DS . 'folder.php',
'header' => __DIR__ . DS . 'lib' . DS . 'header.php',
'html' => __DIR__ . DS . 'lib' . DS . 'html.php',
'i' => __DIR__ . DS . 'lib' . DS . 'i.php',
'l' => __DIR__ . DS . 'lib' . DS . 'l.php',
'media' => __DIR__ . DS . 'lib' . DS . 'media.php',
'obj' => __DIR__ . DS . 'lib' . DS . 'obj.php',
'pagination' => __DIR__ . DS . 'lib' . DS . 'pagination.php',
'password' => __DIR__ . DS . 'lib' . DS . 'password.php',
'r' => __DIR__ . DS . 'lib' . DS . 'r.php',
'redirect' => __DIR__ . DS . 'lib' . DS . 'redirect.php',
'remote' => __DIR__ . DS . 'lib' . DS . 'remote.php',
'response' => __DIR__ . DS . 'lib' . DS . 'response.php',
'router' => __DIR__ . DS . 'lib' . DS . 'router.php',
's' => __DIR__ . DS . 'lib' . DS . 's.php',
'server' => __DIR__ . DS . 'lib' . DS . 'server.php',
'silo' => __DIR__ . DS . 'lib' . DS . 'silo.php',
'sql' => __DIR__ . DS . 'lib' . DS . 'sql.php',
'str' => __DIR__ . DS . 'lib' . DS . 'str.php',
'system' => __DIR__ . DS . 'lib' . DS . 'system.php',
'thumb' => __DIR__ . DS . 'lib' . DS . 'thumb.php',
'timer' => __DIR__ . DS . 'lib' . DS . 'timer.php',
'toolkit' => __DIR__ . DS . 'lib' . DS . 'toolkit.php',
'tpl' => __DIR__ . DS . 'lib' . DS . 'tpl.php',
'upload' => __DIR__ . DS . 'lib' . DS . 'upload.php',
'url' => __DIR__ . DS . 'lib' . DS . 'url.php',
'v' => __DIR__ . DS . 'lib' . DS . 'v.php',
'visitor' => __DIR__ . DS . 'lib' . DS . 'visitor.php',
'xml' => __DIR__ . DS . 'lib' . DS . 'xml.php',
'yaml' => __DIR__ . DS . 'lib' . DS . 'yaml.php',
// vendors
'spyc' => __DIR__ . DS . 'vendors' . DS . 'yaml' . DS . 'yaml.php',
'abeautifulsite\\simpleimage' => __DIR__ . DS . 'vendors' . DS . 'abeautifulsite' . DS . 'SimpleImage.php',
'mimereader' => __DIR__ . DS . 'vendors' . DS . 'mimereader' . DS . 'mimereader.php',
));
// load all helpers
include(__DIR__ . DS . 'helpers.php');

353
kirby/toolkit/helpers.php Normal file
View file

@ -0,0 +1,353 @@
<?php
/**
* Shortcut for url::to()
*
* @return string
*/
function url() {
return call_user_func_array('url::to', func_get_args());
}
/**
* Even shorter shortcut for url::to()
*
* @return string
*/
function u() {
return call_user_func_array('url::to', func_get_args());
}
/**
* Redirects the user to a new URL
* This uses the URL::to() method and can be super
* smart with the custom url::to() handler. Check out
* the URL class for more information
*/
function go() {
call_user_func_array('redirect::to', func_get_args());
}
/**
* Shortcut for r::get()
*
* @param mixed $key The key to look for. Pass false or null to return the entire request array.
* @param mixed $default Optional default value, which should be returned if no element has been found
* @return mixed
*/
function get($key = null, $default = null) {
return r::data($key, $default);
}
/**
* Returns all params from the current url
*
* @return array
*/
function params() {
return url::params();
}
/**
* Get a parameter from the current URI object
*
* @param mixed $key The key to look for. Pass false or null to return the entire params array.
* @param mixed $default Optional default value, which should be returned if no element has been found
* @return mixed
*/
function param($key = null, $default = null) {
static $params;
if(!$params) $params = url::params();
return a::get($params, $key, $default);
}
/**
* Smart version of return with an if condition as first argument
*
* @param mixed $condition
* @param mixed $value The string to be returned if the condition is true
* @param mixed $alternative An alternative string which should be returned when the condition is false
* @return null
*/
function r($condition, $value, $alternative = null) {
return $condition ? $value : $alternative;
}
/**
* Smart version of echo with an if condition as first argument
*
* @param mixed $condition
* @param mixed $value The string to be echoed if the condition is true
* @param mixed $alternative An alternative string which should be echoed when the condition is false
*/
function e($condition, $value, $alternative = null) {
echo r($condition, $value, $alternative);
}
/**
* Alternative for e()
*
* @see e()
* @param $condition
* @param $value
* @param null $alternative
*/
function ecco($condition, $value, $alternative = null) {
e($condition, $value, $alternative);
}
/**
* Dumps any array or object in a human readable way
*
* @param mixed $variable Whatever you like to inspect
* @param boolean $echo
* @return string
*/
function dump($variable, $echo = true) {
if(r::cli()) {
$output = print_r($variable, true) . PHP_EOL;
} else {
$output = '<pre>' . print_r($variable, true) . '</pre>';
}
if($echo === true) echo $output;
return $output;
}
/**
* Generates a single attribute or a list of attributes
*
* @see html::attr();
* @param string $name mixed string: a single attribute with that name will be generated. array: a list of attributes will be generated. Don't pass a second argument in that case.
* @param string $value if used for a single attribute, pass the content for the attribute here
* @return string the generated html
*/
function attr($name, $value = null) {
return html::attr($name, $value);
}
/**
* Creates safe html by encoding special characters
*
* @param string $text unencoded text
* @param bool $keepTags
* @return string
*/
function html($text, $keepTags = true) {
return html::encode($text, $keepTags);
}
/**
* Shortcut for html()
*
* @see html()
* @param $text
* @param bool $keepTags
* @return string
*/
function h($text, $keepTags = true) {
return html::encode($text, $keepTags);
}
/**
* Shortcut for xml::encode()
*
* @param $text
* @return string
*/
function xml($text) {
return xml::encode($text);
}
/**
* Escape context specific output
*
* @param string $string Untrusted data
* @param string $context Location of output
* @param boolean $strict Whether to escape an extended set of characters (HTML attributes only)
* @return string Escaped data
*/
function esc($string, $context = 'html', $strict = false) {
if (method_exists('escape', $context)) {
return escape::$context($string, $strict);
}
}
/**
* The widont function makes sure that there are no
* typographical widows at the end of a paragraph
* that's a single word in the last line
*
* @param string $string
* @return string
*/
function widont($string = '') {
return str::widont($string);
}
/**
* Convert a text to multiline text
*
* @param string $text
* @return string
*/
function multiline($text) {
return nl2br(html($text));
}
/**
* Returns the memory usage in a readable format
*
* @return string
*/
function memory() {
return f::niceSize(memory_get_usage());
}
/**
* Determines the size/length of numbers, strings, arrays and files
*
* @param mixed $value
* @return int
*/
function size($value) {
if(is_numeric($value)) return $value;
if(is_string($value)) return str::length(trim($value));
if(is_array($value)) return count($value);
if(f::exists($value)) return f::size($value) / 1024;
}
/**
* Generates a gravatar image link
*
* @param string $email
* @param int $size
* @param string $default
* @return string
*/
function gravatar($email, $size = 256, $default = 'mm') {
return 'https://gravatar.com/avatar/' . md5(strtolower(trim($email))) . '?d=' . urlencode($default) . '&s=' . $size;
}
/**
* Checks / returns a csrf token
*
* @param string $check Pass a token here to compare it to the one in the session
* @return mixed Either the token or a boolean check result
*/
function csrf($check = null) {
// make sure a session is started
s::start();
if(is_null($check)) {
$token = str::random(64);
s::set('csrf', $token);
return $token;
}
return ($check === s::get('csrf')) ? true : false;
}
/**
* Facepalm typo alias
* @see csrf()
*/
function csfr($check = null) {
return csrf($check);
}
/**
* Shortcut for call_user_func_array with a better handling of arguments
*
* @param mixed $function
* @param mixed $arguments
* @return mixed
*/
function call($function, $arguments = array()) {
if(!is_callable($function)) return false;
if(!is_array($arguments)) $arguments = array($arguments);
return call_user_func_array($function, $arguments);
}
/**
* Parses yaml structured text
*
* @param $string
* @return array
*/
function yaml($string) {
return yaml::decode($string);
}
/**
* Simple email sender helper
*
* @param array $params
* @return Email
*/
function email($params = array()) {
return new Email($params);
}
/**
* Shortcut for the upload class
*
* @param $to
* @param array $params
* @return Upload
*/
function upload($to, $params = array()) {
return new Upload($to, $params);
}
/**
* Checks for invalid data
*
* @param array $data
* @param array $rules
* @param array $messages
* @return mixed
*/
function invalid($data, $rules, $messages = array()) {
$errors = array();
foreach($rules as $field => $validations) {
foreach($validations as $method => $options) {
if(is_numeric($method)) $method = $options;
if($method == 'required') {
if(!isset($data[$field]) || (empty($data[$field]) && $data[$field] !== 0)) {
$errors[$field] = a::get($messages, $field, $field);
}
} else if(!empty($data[$field]) || $data[$field] === 0) {
if(!is_array($options)) $options = array($options);
array_unshift($options, a::get($data, $field));
if(!call(array('v', $method), $options)) {
$errors[$field] = a::get($messages, $field, $field);
}
}
}
}
return array_unique($errors);
}
/**
* Shortcut for the language variable getter
*
* @param string $key
* @param mixed $default
* @return string
*/
function l($key, $default = null) {
return l::get($key, $default);
}
/**
* @param $tag
* @param bool $html
* @param array $attr
* @return Brick
*/
function brick($tag, $html = false, $attr = array()) {
return new Brick($tag, $html, $attr);
}

495
kirby/toolkit/lib/a.php Normal file
View file

@ -0,0 +1,495 @@
<?php
/**
* Array
*
* This class is supposed to simplify array handling
* and make it more consistent.
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class A {
/**
* Gets an element of an array by key
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* echo a::get($array, 'cat');
* // output: 'miao'
*
* echo a::get($array, 'elephant', 'shut up');
* // output: 'shut up'
*
* $catAndDog = a::get(array('cat', 'dog'));
* // result: array(
* // 'cat' => 'miao',
* // 'dog' => 'wuff'
* // );
*
* </code>
*
* @param array $array The source array
* @param mixed $key The key to look for
* @param mixed $default Optional default value, which should be returned if no element has been found
* @return mixed
*/
public static function get($array, $key, $default = null) {
// get an array of keys
if(is_array($key)) {
$result = array();
foreach($key as $k) $result[$k] = static::get($array, $k);
return $result;
// get a single
} else if(isset($array[$key])) {
return $array[$key];
// return the entire array if the key is null
} else if(is_null($key)) {
return $array;
// get the default value if nothing else worked out
} else {
return $default;
}
}
/**
* Shows an entire array or object in a human readable way
* This is perfect for debugging
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* a::show($array);
*
* // output:
* // Array
* // (
* // [cat] => miao
* // [dog] => wuff
* // [bird] => tweet
* // )
*
* </code>
*
* @param array $array The source array
* @param boolean $echo By default the result will be echoed instantly. You can switch that off here.
* @return mixed If echo is false, this will return the generated array output.
*/
public static function show($array, $echo = true) {
return dump($array, $echo);
}
/**
* Converts an array to a JSON string
* It's basically a shortcut for json_encode()
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* echo a::json($array);
* // output: {"cat":"miao","dog":"wuff","bird":"tweet"}
*
* </code>
*
* @param array $array The source array
* @return string The JSON string
*/
public static function json($array) {
return json_encode((array)$array);
}
/**
* Converts an array to a XML string
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* echo a::xml($array, 'animals');
* // output:
* // <animals>
* // <cat>miao</cat>
* // <dog>wuff</dog>
* // <bird>tweet</bird>
* // </animals>
*
* </code>
*
* @param array $array The source array
* @param string $tag The name of the root element
* @param boolean $head Include the xml declaration head or not
* @param string $charset The charset, which should be used for the header
* @param int $level The indendation level
* @return string The XML string
*/
public static function xml($array, $tag = 'root', $head = true, $charset = 'utf-8', $tab = ' ', $level = 0) {
return xml::create($array, $tag, $head, $charset, $tab, $level);
}
/**
* Extracts a single column from an array
*
* <code>
*
* $array[0] = array(
* 'id' => 1,
* 'username' => 'bastian',
* );
*
* $array[1] = array(
* 'id' => 2,
* 'username' => 'peter',
* );
*
* $array[3] = array(
* 'id' => 3,
* 'username' => 'john',
* );
*
* $extract = a::extract($array, 'username');
*
* // result: array(
* // 'bastian',
* // 'peter',
* // 'john'
* // );
*
* </code>
*
* @param array $array The source array
* @param string $key The key name of the column to extract
* @return array The result array with all values from that column.
*/
public static function extract($array, $key) {
$output = array();
foreach($array AS $a) if(isset($a[$key])) $output[] = $a[ $key ];
return $output;
}
/**
* Shuffles an array and keeps the keys
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* $shuffled = a::shuffle($array);
* // output: array(
* // 'dog' => 'wuff',
* // 'cat' => 'miao',
* // 'bird' => 'tweet'
* // );
*
* </code>
*
* @param array $array The source array
* @return array The shuffled result array
*/
public static function shuffle($array) {
$keys = array_keys($array);
$new = array();
shuffle($keys);
// resort the array
foreach($keys as $key) $new[$key] = $array[$key];
return $new;
}
/**
* Returns the first element of an array
*
* I always have to lookup the names of that function
* so I decided to make this shortcut which is
* easier to remember.
*
* <code>
*
* $array = array(
* 'cat',
* 'dog',
* 'bird',
* );
*
* $first = a::first($array);
* // first: 'cat'
*
* </code>
*
* @param array $array The source array
* @return mixed The first element
*/
public static function first($array) {
return array_shift($array);
}
/**
* Returns the last element of an array
*
* I always have to lookup the names of that function
* so I decided to make this shortcut which is
* easier to remember.
*
* <code>
*
* $array = array(
* 'cat',
* 'dog',
* 'bird',
* );
*
* $last = a::last($array);
* // first: 'bird'
*
* </code>
*
* @param array $array The source array
* @return mixed The last element
*/
public static function last($array) {
return array_pop($array);
}
/**
* Fills an array up with additional elements to certain amount.
*
* <code>
*
* $array = array(
* 'cat',
* 'dog',
* 'bird',
* );
*
* $result = a::fill($array, 5, 'elephant');
*
* // result: array(
* // 'cat',
* // 'dog',
* // 'bird',
* // 'elephant',
* // 'elephant',
* // );
*
* </code>
*
* @param array $array The source array
* @param int $limit The number of elements the array should contain after filling it up.
* @param mixed $fill The element, which should be used to fill the array
* @return array The filled-up result array
*/
public static function fill($array, $limit, $fill='placeholder') {
if(count($array) < $limit) {
$diff = $limit-count($array);
for($x=0; $x<$diff; $x++) $array[] = $fill;
}
return $array;
}
/**
* Checks for missing elements in an array
*
* This is very handy to check for missing
* user values in a request for example.
*
* <code>
*
* $array = array(
* 'cat' => 'miao',
* 'dog' => 'wuff',
* 'bird' => 'tweet'
* );
*
* $required = array('cat', 'elephant');
*
* $missng = a::missing($array, $required);
* // missing: array(
* // 'elephant'
* // );
*
* </code>
*
* @param array $array The source array
* @param array $required An array of required keys
* @return array An array of missing fields. If this is empty, nothing is missing.
*/
public static function missing($array, $required=array()) {
$missing = array();
foreach($required AS $r) {
if(empty($array[$r])) $missing[] = $r;
}
return $missing;
}
/**
* Sorts a multi-dimensional array by a certain column
*
* <code>
*
* $array[0] = array(
* 'id' => 1,
* 'username' => 'bastian',
* );
*
* $array[1] = array(
* 'id' => 2,
* 'username' => 'peter',
* );
*
* $array[3] = array(
* 'id' => 3,
* 'username' => 'john',
* );
*
* $sorted = a::sort($array, 'username ASC');
* // Array
* // (
* // [0] => Array
* // (
* // [id] => 1
* // [username] => bastian
* // )
* // [1] => Array
* // (
* // [id] => 3
* // [username] => john
* // )
* // [2] => Array
* // (
* // [id] => 2
* // [username] => peter
* // )
* // )
*
* </code>
*
* @param array $array The source array
* @param string $field The name of the column
* @param string $direction desc (descending) or asc (ascending)
* @param const $method A PHP sort method flag or 'natural' for natural sorting, which is not supported in PHP by sort flags
* @return array The sorted array
*/
public static function sort($array, $field, $direction = 'desc', $method = SORT_REGULAR) {
$direction = strtolower($direction) == 'desc' ? SORT_DESC : SORT_ASC;
$helper = array();
$result = array();
// build the helper array
foreach($array as $key => $row) $helper[$key] = $row[$field];
// natural sorting
if($method === SORT_NATURAL) {
natsort($helper);
if($direction === SORT_DESC) $helper = array_reverse($helper);
} else if($direction === SORT_DESC) {
arsort($helper, $method);
} else {
asort($helper, $method);
}
// rebuild the original array
foreach($helper as $key => $val) $result[$key] = $array[$key];
return $result;
}
/**
* Checks wether an array is associative or not (experimental)
*
* @param array $array The array to analyze
* @return boolean true: The array is associative false: It's not
*/
public static function isAssociative($array) {
return !ctype_digit(implode(NULL, array_keys($array)));
}
/**
* Returns the average value of an array
*
* @param array $array The source array
* @param int $decimals The number of decimals to return
* @return int The average value
*/
public static function average($array, $decimals = 0) {
return round(array_sum($array), $decimals) / sizeof($array);
}
/**
* Merges arrays recursively
*
* @param array $array1
* @param array $array2
* @return array
*/
public static function merge($array1, $array2) {
$merged = $array1;
foreach($array2 as $key => $value) {
if(is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = static::merge($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
/**
* Update an array with a second array
* The second array can contain callbacks as values,
* which will get the original values as argument
*
* @param array $array
* @param array $update
*/
public static function update($array, $update) {
foreach($update as $key => $value) {
if(is_a($value, 'Closure')) {
$array[$key] = call($value, static::get($array, $key));
} else {
$array[$key] = $value;
}
}
return $array;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* Bitmask
*
* Analyzes and sets bitmasks
*
* @package Kirby Toolkit
* @author Lukas Bestle <lukas@getkirby.com>
* @link http://getkirby.com
* @copyright Lukas Bestle
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Bitmask {
/**
* Checks if a value can be used as bitmask value (checks for a power of two)
*
* @param mixed $value The value to check for
* @return boolean
*/
public static function validValue($value) {
return is_int($value) && ($value & ($value - 1)) == 0;
}
/**
* Checks if a bitmask includes a value
*
* @param int $value The value to check for
* @param int $bitmask The bitmask to check in
* @return boolean
*/
public static function includes($value, $bitmask) {
if(!static::validValue($value)) return false;
return ($bitmask & $value) !== 0;
}
/**
* Adds a value to a bitmask
*
* @param int $value The value to add
* @param int $bitmask The bitmask to add the value to
* @return int
*/
public static function add($value, $bitmask) {
if(!static::validValue($value)) {
throw new Exception('The value "' . $value . '" is not appropriate for usage in bitmasks.');
}
// check if the bitmask already includes the value
if(static::includes($value, $bitmask)) return $bitmask;
return $bitmask | $value;
}
/**
* Removes a value from a bitmask
*
* @param int $value The value to remove
* @param int $bitmask The bitmask to remove the value from
* @return int
*/
public static function remove($value, $bitmask) {
if(!static::validValue($value)) {
throw new Exception('The value "' . $value . '" is not appropriate for usage in bitmasks.');
}
// check if the bitmask even includes the value
if(!static::includes($value, $bitmask)) return $bitmask;
return $bitmask ^ $value;
}
}

204
kirby/toolkit/lib/brick.php Normal file
View file

@ -0,0 +1,204 @@
<?php
class Brick {
public static $bricks = array();
public $tag = null;
public $attr = array();
public $html = null;
public $events = array();
public function __construct($tag, $html = false, $attr = array()) {
if(is_array($html)) {
$attr = $html;
$html = false;
}
$this->tag($tag);
$this->html($html);
$this->attr($attr);
}
public function __set($attr, $value) {
$this->attr($attr, $value);
}
public function on($event, $callback) {
if(!isset($this->events[$event])) $this->events[$event] = array();
$this->events[$event][] = $callback;
return $this;
}
public function trigger($event, $args = array()) {
if(isset($this->events[$event])) {
array_unshift($args, $this);
foreach($this->events[$event] as $e) {
call_user_func_array($e, $args);
}
}
}
public function tag($tag = null) {
if(is_null($tag)) return $this->tag;
$this->tag = $tag;
return $this;
}
public function attr($key = null, $value = null) {
if(is_null($key)) {
return $this->attr;
} else if(is_array($key)) {
foreach($key as $k => $v) {
$this->attr($k, $v);
}
return $this;
} else if(is_null($value)) {
return a::get($this->attr, $key);
} else if($key == 'class') {
$this->addClass($value);
return $this;
} else {
$this->attr[$key] = $value;
return $this;
}
}
public function data($key = null, $value = null) {
if(is_null($key)) {
$data = array();
foreach($this->attr as $key => $val) {
if(str::startsWith($key, 'data-')) {
$data[$key] = $val;
}
}
return $data;
} else if(is_array($key)) {
foreach($key as $k => $v) {
$this->data($k, $v);
}
return $this;
} else if(is_null($value)) {
return a::get($this->attr, 'data-' . $key);
} else {
$this->attr['data-' . $key] = $value;
return $this;
}
}
public function removeAttr($key) {
unset($this->attr[$key]);
}
public function classNames() {
if(!isset($this->attr['class'])) {
$this->attr['class'] = array();
} else if(is_string($this->attr['class'])) {
$raw = $this->attr['class'];
$this->attr['class'] = array();
$this->addClass($raw);
}
return $this->attr['class'];
}
public function val($value = null) {
return $this->attr('value', $value);
}
public function addClass($class) {
$classNames = $this->classNames();
$classIndex = array_map('strtolower', $classNames);
foreach(str::split($class, ' ') as $c) {
if(!in_array(strtolower($c), $classIndex)) {
$classNames[] = $c;
}
}
$this->attr['class'] = $classNames;
return $this;
}
public function removeClass($class) {
$classNames = $this->classNames();
foreach(str::split($class, ' ') as $c) {
$classNames = array_filter($classNames, function($e) use($c) {
return (strtolower($e) !== strtolower($c));
});
}
$this->attr['class'] = $classNames;
return $this;
}
public function replaceClass($classA, $classB) {
return $this->removeClass($classA)->addClass($classB);
}
public function text($text = null) {
if(is_null($text)) return trim(strip_tags($this->html));
$this->html = html($text, false);
return $this;
}
public function html($html = null) {
if(is_null($html)) {
return $this->html = $this->isVoid() ? null : $this->html;
}
$this->html = $html;
return $this;
}
public function prepend($html) {
if(is_callable($html)) $html = $html();
$this->html = $html . $this->html;
return $this;
}
public function append($html) {
if(is_callable($html)) $html = $html();
$this->html = $this->html . $html;
return $this;
}
public function isVoid() {
return html::isVoid($this->tag());
}
public function toString() {
$this->attr['class'] = implode(' ', $this->classNames());
return html::tag($this->tag(), $this->html(), $this->attr());
}
public function __toString() {
try {
return $this->toString();
} catch(Exception $e) {
return 'Error: ' . $e->getMessage();
}
}
public static function make($id, $callback) {
static::$bricks[$id] = $callback;
}
public static function get($id) {
if(!isset(static::$bricks[$id])) return false;
$args = array_slice(func_get_args(), 1);
return call_user_func_array(static::$bricks[$id], $args);
}
}

17
kirby/toolkit/lib/c.php Normal file
View file

@ -0,0 +1,17 @@
<?php
/**
* Config
*
* This is the core class to handle
* configuration values/constants.
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class C extends Silo {
public static $data = array();
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Cache
*
* The ultimate cache wrapper for
* all available drivers
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Cache {
const ERROR_INVALID_DRIVER = 0;
const ERROR_INVALID_DRIVER_INSTANCE = 1;
const ERROR_UNKNOWN_METHOD = 2;
public static $driver = null;
/**
* Setup simplifier for the current driver
*
* @param string $driver
* @param mixed $args
* @return Cache\Driver
*/
public static function setup($driver, $args = null) {
$ref = new ReflectionClass('Cache\\Driver\\' . $driver);
return static::$driver = $ref->newInstanceArgs(array($args));
}
/**
* Accessor for all static driver methods
*
* @param string $method
* @param mixed $args
* @return mixed
*/
public static function __callStatic($method, $args) {
if(is_null(static::$driver)) {
throw new Error('Please define a cache driver', static::ERROR_INVALID_DRIVER);
}
if(!is_a(static::$driver, 'Cache\\Driver')) {
throw new Error('The cache driver must be an instance of the Cache\\Driver class', static::ERROR_INVALID_DRIVER_INSTANCE);
}
if(method_exists(static::$driver, $method)) {
return call(array(static::$driver, $method), $args);
} else {
throw new Error('Invalid cache method: ' . $method, static::ERROR_UNKNOWN_METHOD);
}
}
}

190
kirby/toolkit/lib/cache/driver.php vendored Executable file
View file

@ -0,0 +1,190 @@
<?php
namespace Cache;
/**
* Cache Driver Abstract
*
* Template for all cache drivers
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
abstract class Driver {
// stores all options for the driver
protected $options = array();
/**
* Set all parameters which are needed to connect to the cache storage
*
* @param array $params
*/
public function __construct($params = array()) {}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public abstract function set($key, $value, $minutes = null);
/**
* Private method to retrieve the cache value
* This needs to be defined by the driver
*
* @param string $key
* @return object Value
*/
public abstract function retrieve($key);
/**
* Get an item from the cache.
*
* <code>
* // Get an item from the cache driver
* $value = Cache::get('value');
*
* // Return a default value if the requested item isn't cached
* $value = Cache::get('value', 'default value');
* </code>
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null) {
// get the Value
$value = $this->retrieve($key);
// check for a valid cache value
if(!is_a($value, 'Cache\\Value')) return $default;
// remove the item if it is expired
if(time() > $value->expires()) {
$this->remove($key);
return $default;
}
// get the pure value
$cache = $value->value();
// return the cache value or the default
return (!is_null($cache)) ? $cache : $default;
}
/**
* Calculates the expiration timestamp
*
* @param int $minutes
* @return int
*/
protected function expiration($minutes = null) {
// keep forever if minutes are not defined
if(is_null($minutes)) $minutes = 2628000;
// calculate the time
return time() + ($minutes * 60);
}
/**
* Checks when an item in the cache expires
*
* @param string $key
* @return mixed
*/
public function expires($key) {
// get the Value object
$value = $this->retrieve($key);
// check for a valid Value object
if(!is_a($value, 'Cache\\Value')) return false;
// return the expires timestamp
return $value->expires();
}
/**
* Checks if an item in the cache is expired
*
* @param string $key
* @return int
*/
public function expired($key) {
return $this->expires($key) <= time();
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return mixed
*/
public function created($key) {
// get the Value object
$value = $this->retrieve($key);
// check for a valid Value object
if(!is_a($value, 'Cache\\Value')) return false;
// return the expires timestamp
return $value->created();
}
/**
* Alternate version for cache::created($key)
*/
public function modified($key) {
return static::created($key);
}
/**
* An array with value, created timestamp and expires timestamp
*
* @param mixed $value The value, which should be cached
* @param int $minutes The number of minutes before expiration
* @return array
*/
protected function value($value, $minutes) {
return new Value($value, $minutes);
}
/**
* Determine if an item exists in the cache.
*
* @param string $key
* @return boolean
*/
public function exists($key) {
return !$this->expired($key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public abstract function remove($key);
/**
* Flush the entire cache
*
* @return boolean
*/
public abstract function flush();
}

74
kirby/toolkit/lib/cache/driver/apc.php vendored Normal file
View file

@ -0,0 +1,74 @@
<?php
namespace Cache\Driver;
use Cache\Driver;
/**
* APC Cache
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Apc extends Driver {
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function set($key, $value, $minutes = null) {
return apc_store($key, $this->value($value, $minutes), $this->expiration($minutes));
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve($key) {
return apc_fetch($key);
}
/**
* Checks if the current key exists in cache
*
* @param string $key
* @return boolean
*/
public function exists($key) {
return apc_exists($key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove($key) {
return apc_delete($key);
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush() {
return apc_clear_cache('user');
}
}

121
kirby/toolkit/lib/cache/driver/file.php vendored Normal file
View file

@ -0,0 +1,121 @@
<?php
namespace Cache\Driver;
use Cache\Driver;
use Error;
use F;
use Dir;
/**
* File Cache
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class File extends Driver {
const ERROR_MISSING_CACHE_DIRECTORY = 0;
/**
* Set all parameters which are needed for the file cache
* see defaults for available parameters
*
* @param array $params
*/
public function __construct($params = array()) {
if(is_string($params)) {
$params = array('root' => $params);
}
$defaults = array(
'root' => null,
'extension' => null
);
$this->options = array_merge($defaults, $params);
// check for a valid cache directory
if(!is_dir($this->options['root'])) {
throw new Error('The cache directory does not exist', static::ERROR_MISSING_CACHE_DIRECTORY);
}
}
/**
* Returns the full path to a file for a given key
*
* @param string $key
* @return string
*/
protected function file($key) {
return $this->options['root'] . DS . $key . r($this->options['extension'], '.' . $this->options['extension']);
}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function set($key, $value, $minutes = null) {
return f::write($this->file($key), serialize($this->value($value, $minutes)));
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return object CacheValue
*/
public function retrieve($key) {
// unserialized value array (see $this->value())
return unserialize(f::read($this->file($key)));
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return int
*/
public function created($key) {
// use the modification timestamp
// as indicator when the cache has been created/overwritten
clearstatcache();
// get the file for this cache key
$file = $this->file($key);
return file_exists($file) ? filemtime($this->file($key)) : 0;
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove($key) {
return f::remove($this->file($key));
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush() {
return dir::clean($this->options['root']);
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace Cache\Driver;
use Cache\Driver;
/**
* Memcache
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Memcached extends Driver {
// store for the memache connection
protected $connection = null;
/**
* Set all parameters which are needed for the memcache client
* see defaults for available parameters
*
* @param array $params
*/
public function __construct($params = array()) {
$defaults = array(
'host' => 'localhost',
'port' => 11211,
'prefix' => null,
);
$this->options = array_merge($defaults, (array)$params);
$this->connection = new \Memcached();
$this->connection->addServer($this->options['host'], $this->options['port']);
}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function set($key, $value, $minutes = null) {
return $this->connection->set($this->key($key), $this->value($value, $minutes), $this->expiration($minutes));
}
/**
* Returns the full keyname
* including the prefix (if set)
*
* @param string $key
* @return string
*/
public function key($key) {
return $this->options['prefix'] . $key;
}
/**
* Retrieve the CacheValue object from the cache.
*
* @param string $key
* @return object CacheValue
*/
public function retrieve($key) {
return $this->connection->get($this->key($key));
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove($key) {
return $this->connection->delete($this->key($key));
}
/**
* Checks when an item in the cache expires
*
* @param string $key
* @return int
*/
public function expires($key) {
return parent::expires($this->key($key));
}
/**
* Checks if an item in the cache is expired
*
* @param string $key
* @return int
*/
public function expired($key) {
return parent::expired($this->key($key));
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return int
*/
public function created($key) {
return parent::created($this->key($key));
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush() {
return $this->connection->flush();
}
}

74
kirby/toolkit/lib/cache/driver/mock.php vendored Normal file
View file

@ -0,0 +1,74 @@
<?php
namespace Cache\Driver;
use Cache\Driver;
/**
* Mock Cache Driver
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Mock extends Driver {
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function set($key, $value, $minutes = null) {
return true;
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve($key) {
return null;
}
/**
* Checks if the current key exists in cache
*
* @param string $key
* @return boolean
*/
public function exists($key) {
return null;
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove($key) {
return true;
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush() {
return true;
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Cache\Driver;
use Cache\Driver;
use A;
use Exception;
use S;
/**
* Session Cache
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Session extends Driver {
/**
* Make sure the session is started within the constructor
*/
public function __construct() {
s::start();
if(!isset($_SESSION['_cache'])) {
$_SESSION['_cache'] = array();
}
}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function set($key, $value, $minutes = null) {
return $_SESSION['_cache'][$key] = $this->value($value, $minutes);
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return object CacheValue
*/
public function retrieve($key) {
return a::get($_SESSION['_cache'], $key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove($key) {
unset($_SESSION['_cache'][$key]);
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush() {
$_SESSION['_cache'] = array();
return true;
}
}

75
kirby/toolkit/lib/cache/value.php vendored Normal file
View file

@ -0,0 +1,75 @@
<?php
namespace Cache;
/**
* Cache Value
*
* Stores the value, creation timestamp and expiration timestamp
* and makes it possible to store all three with a single cache key.
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Value {
// the cached value
protected $value;
// the expiration timestamp
protected $expires;
// the creation timestamp
protected $created;
/**
* Constructor
*
* @param mixed $value
* @param int $minutes the number of minutes until the value expires
*/
public function __construct($value, $minutes = null) {
// keep forever if minutes are not defined
if(is_null($minutes)) $minutes = 2628000;
// take the current time
$time = time();
$this->value = $value;
$this->expires = $time + ($minutes * 60);
$this->created = $time;
}
/**
* Returns the value
*
* @return mixed
*/
public function value() {
return $this->value;
}
/**
* Returns the expiration date as UNIX timestamp
*
* @return int
*/
public function expires() {
return $this->expires;
}
/**
* Returns the creation date as UNIX timestamp
*
* @return int
*/
public function created() {
return $this->created;
}
}

View file

@ -0,0 +1,652 @@
<?php
/**
* Collection
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Collection extends I {
public static $filters = array();
protected $pagination;
/**
* Returns a slice of the collection
*
* @param int $offset The optional index to start the slice from
* @param int $limit The optional number of elements to return
* @return Collection
*/
public function slice($offset = null, $limit = null) {
if($offset === null && $limit === null) return $this;
$collection = clone $this;
$collection->data = array_slice($collection->data, $offset, $limit);
return $collection;
}
/**
* Returns a new combined collection
*
* @return Collection
*/
public function merge($collection2) {
$collection = clone $this;
$collection->data = a::merge($collection->data, $collection2->data);
return $collection;
}
/**
* Returns a new collection with a limited number of elements
*
* @param int $limit The number of elements to return
* @return Collection
*/
public function limit($limit) {
return $this->slice(0, $limit);
}
/**
* Returns a new collection starting from the given offset
*
* @param int $offset The index to start from
* @return Collection
*/
public function offset($offset) {
return $this->slice($offset);
}
/**
* Returns the array in reverse order
*
* @return Collection
*/
public function flip() {
$collection = clone $this;
$collection->data = array_reverse($collection->data, true);
return $collection;
}
/**
* Counts all elements in the array
*
* @return int
*/
public function count() {
return count($this->data);
}
/**
* Returns the first element from the array
*
* @return mixed
*/
public function first() {
$array = $this->data;
return array_shift($array);
}
/**
* Checks if an element is in the collection by key.
*
* @param string $key
* @return boolean
*/
public function has($key) {
return isset($this->data[$key]);
}
/**
* Returns the last element from the array
*
* @return mixed
*/
public function last() {
$array = $this->data;
return array_pop($array);
}
/**
* Returns the nth element from the array
*
* @return mixed
*/
public function nth($n) {
$array = array_values($this->data);
return (isset($array[$n])) ? $array[$n] : false;
}
/**
* Converts the current object into an array
*
* @return array
*/
public function toArray($callback = null) {
if(is_null($callback)) return $this->data;
return array_map($callback, $this->data);
}
/**
* Converts the current object into a json string
*
* @return string
*/
public function toJson() {
return json_encode($this->data);
}
/**
* Appends an element to the data array
*
* @param string $key
* @param mixed $object
* @return Collection
*/
public function append($key, $object) {
$this->data = $this->data + array($key => $object);
return $this;
}
/**
* Prepends an element to the data array
*
* @param string $key
* @param mixed $object
* @return Collection
*/
public function prepend($key, $object) {
$this->data = array($key => $object) + $this->data;
return $this;
}
/**
* Returns a new collection without the given element(s)
*
* @param args any number of keys, passed as individual arguments
* @return Collection
*/
public function not() {
$collection = clone $this;
foreach(func_get_args() as $kill) {
unset($collection->data[$kill]);
}
return $collection;
}
/**
* Returns a new collection without the given element(s)
*
* @param args any number of keys, passed as individual arguments
* @return Collection
*/
public function without() {
return call_user_func_array(array($this, 'not'), func_get_args());
}
/**
* Shuffle all elements in the array
*
* @return object a new shuffled collection
*/
public function shuffle() {
$collection = clone $this;
$keys = array_keys($collection->data);
shuffle($keys);
$collection->data = array_merge(array_flip($keys), $collection->data);
return $collection;
}
/**
* Returns an array of all keys in the collection
*
* @return array
*/
public function keys() {
return array_keys($this->data);
}
/**
* Tries to find the key for the given element
*
* @param mixed $needle the element to search for
* @return mixed the name of the key or false
*/
public function keyOf($needle) {
return array_search($needle, $this->data);
}
/**
* Tries to find the index number for the given element
*
* @param mixed $needle the element to search for
* @return mixed the name of the key or false
*/
public function indexOf($needle) {
return array_search($needle, array_values($this->data));
}
/**
* Filter the elements in the array by a callback function
*
* @param func $callback the callback function
* @return Collection
*/
public function filter($callback) {
$collection = clone $this;
$collection->data = array_filter($collection->data, $callback);
return $collection;
}
/**
* Find a single item by a key and value pair
*
* @param string $key
* @param mixed $value
* @return mixed
*/
public function findBy($key, $value) {
foreach($this->data as $item) {
if($this->extractValue($item, $key) == $value) return $item;
}
}
/**
* Filters the current collection by a field, operator and search value
*
* @return Collection
*/
public function filterBy() {
$args = func_get_args();
$operator = '==';
$field = @$args[0];
$value = @$args[1];
$split = @$args[2];
$collection = clone $this;
if(is_string($value) && array_key_exists($value, static::$filters)) {
$operator = $value;
$value = @$args[2];
$split = @$args[3];
}
if(is_object($value)) {
$value = (string)$value;
}
if(array_key_exists($operator, static::$filters)) {
$collection = call_user_func_array(static::$filters[$operator], array(
$collection,
$field,
$value,
$split
));
}
return $collection;
}
/**
* Makes sure to provide a valid value for each filter method
* no matter if an object or an array is given
*
* @param mixed $item
* @param string $field
* @return mixed
*/
static public function extractValue($item, $field) {
if(is_array($item) && isset($item[$field])) {
return $item[$field];
} else if(is_object($item)) {
return $item->$field();
} else {
return false;
}
}
/**
* Sorts the collection by any number of fields
*
* @return Collection
*/
public function sortBy() {
$args = func_get_args();
$collection = clone $this;
$array = $collection->data;
$params = array();
if(empty($array)) return $collection;
foreach($args as $i => $param) {
if(is_string($param)) {
if(strtolower($param) === 'desc') {
${"param_$i"} = SORT_DESC;
} else if(strtolower($param) === 'asc') {
${"param_$i"} = SORT_ASC;
} else {
${"param_$i"} = array();
foreach($array as $index => $row) {
${"param_$i"}[$index] = is_array($row) ? str::lower($row[$param]) : str::lower($row->$param());
}
}
} else {
${"param_$i"} = $args[$i];
}
$params[] = &${"param_$i"};
}
$params[] = &$array;
call_user_func_array('array_multisort', $params);
$collection->data = $array;
return $collection;
}
/**
* Add pagination
*
* @param int $limit the number of items per page
* @param array $options and optional array with options for the pagination class
* @return object a sliced set of data
*/
public function paginate($limit, $options = array()) {
if(is_a($limit, 'Pagination')) {
$this->pagination = $limit;
return $this;
}
$pagination = new Pagination($this->count(), $limit, $options);
$pages = $this->slice($pagination->offset(), $pagination->limit());
$pages->pagination = $pagination;
return $pages;
}
/**
* Get the previously added pagination object
*
* @return object
*/
public function pagination() {
return $this->pagination;
}
/**
* Map a function to each item in the collection
*
* @param function $callback
* @return Collection
*/
public function map($callback) {
$this->data = array_map($callback, $this->data);
return $this;
}
/**
* Extracts all values for a single field into
* a new array
*
* @param string $field
* @return array
*/
public function pluck($field, $split = null, $unique = false) {
$result = array();
foreach($this->data as $item) {
$row = $this->extractValue($item, $field);
if($split) {
$result = array_merge($result, str::split($row, $split));
} else {
$result[] = $row;
}
}
if($unique) {
$result = array_unique($result);
}
return array_values($result);
}
/**
* Groups the collection by a given callback
*
* @param callable $callback
* @return object A new collection with an item for each group and a subcollection in each group
*/
public function group($callback) {
if (!is_callable($callback)) throw new Exception($callback . ' is not callable. Did you mean to use groupBy()?');
$groups = array();
foreach($this->data as $key => $item) {
// get the value to group by
$value = call_user_func($callback, $item);
// make sure that there's always a proper value to group by
if(!$value) throw new Exception('Invalid grouping value for key: ' . $key);
// make sure we have a proper key for each group
if(is_array($value)) {
throw new Exception('You cannot group by arrays or objects');
} else if(is_object($value)) {
if(!method_exists($value, '__toString')) {
throw new Exception('You cannot group by arrays or objects');
} else {
$value = (string)$value;
}
}
if(!isset($groups[$value])) {
// create a new entry for the group if it does not exist yet
$groups[$value] = new static(array($key => $item));
} else {
// add the item to an existing group
$groups[$value]->set($key, $item);
}
}
return new Collection($groups);
}
/**
* Groups the collection by a given field
*
* @param string $field
* @return object A new collection with an item for each group and a subcollection in each group
*/
public function groupBy($field, $i = true) {
if (!is_string($field)) throw new Exception('Cannot group by non-string values. Did you mean to call group()?');
return $this->group(function($item) use ($field, $i) {
$value = $this->extractValue($item, $field);
// ignore upper/lowercase for group names
return ($i == true) ? str::lower($value) : $value;
});
}
public function set($key, $value) {
if(is_array($key)) {
$this->data = array_merge($this->data, $key);
return $this;
}
$this->data[$key] = $value;
return $this;
}
public function __set($key, $value) {
$this->set($key, $value);
}
public function get($key, $default = null) {
if(isset($this->data[$key])) {
return $this->data[$key];
} else {
$lowerkeys = array_change_key_case($this->data, CASE_LOWER);
if(isset($lowerkeys[strtolower($key)])) {
return $lowerkeys[$key];
} else {
return $default;
}
}
}
public function __get($key) {
return $this->get($key);
}
public function __call($key, $arguments) {
return $this->get($key);
}
/**
* Makes it possible to echo the entire object
*
* @return string
*/
public function __toString() {
return implode('<br />', array_map(function($item) {
return (string)$item;
}, $this->data));
}
}
/**
* Add all available collection filters
* Those can be extended by creating your own:
* collection::$filters['your operator'] = function($collection, $field, $value, $split = false) {
* // your filter code
* };
*/
// take all matching elements
collection::$filters['=='] = function($collection, $field, $value, $split = false) {
foreach($collection->data as $key => $item) {
if($split) {
$values = str::split((string)collection::extractValue($item, $field), $split);
if(!in_array($value, $values)) unset($collection->$key);
} else if(collection::extractValue($item, $field) != $value) {
unset($collection->$key);
}
}
return $collection;
};
// take all elements that won't match
collection::$filters['!='] = function($collection, $field, $value, $split = false) {
foreach($collection->data as $key => $item) {
if($split) {
$values = str::split((string)collection::extractValue($item, $field), $split);
if(in_array($value, $values)) unset($collection->$key);
} else if(collection::extractValue($item, $field) == $value) {
unset($collection->$key);
}
}
return $collection;
};
// take all elements that partly match
collection::$filters['*='] = function($collection, $field, $value, $split = false) {
foreach($collection->data as $key => $item) {
if($split) {
$values = str::split((string)collection::extractValue($item, $field), $split);
foreach($values as $val) {
if(strpos($val, $value) === false) {
unset($collection->$key);
break;
}
}
} else if(strpos(collection::extractValue($item, $field), $value) === false) {
unset($collection->$key);
}
}
return $collection;
};
// greater than
collection::$filters['>'] = function($collection, $field, $value) {
foreach($collection->data as $key => $item) {
if(collection::extractValue($item, $field) > $value) continue;
unset($collection->$key);
}
return $collection;
};
// greater and equals
collection::$filters['>='] = function($collection, $field, $value) {
foreach($collection->data as $key => $item) {
if(collection::extractValue($item, $field) >= $value) continue;
unset($collection->$key);
}
return $collection;
};
// less than
collection::$filters['<'] = function($collection, $field, $value) {
foreach($collection->data as $key => $item) {
if(collection::extractValue($item, $field) < $value) continue;
unset($collection->$key);
}
return $collection;
};
// less and equals
collection::$filters['<='] = function($collection, $field, $value) {
foreach($collection->data as $key => $item) {
if(collection::extractValue($item, $field) <= $value) continue;
unset($collection->$key);
}
return $collection;
};

View file

@ -0,0 +1,168 @@
<?php
/**
* Cookie
*
* This class makes cookie handling easy
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Cookie {
// configuration
public static $salt = 'KirbyToolkitCookieSalt';
/**
* Set a new cookie
*
* <code>
*
* cookie::set('mycookie', 'hello', 60);
* // expires in 1 hour
*
* </code>
*
* @param string $key The name of the cookie
* @param string $value The cookie content
* @param int $lifetime The number of minutes until the cookie expires
* @param string $path The path on the server to set the cookie for
* @param string $domain the domain
* @param boolean $secure only sets the cookie over https
* @param boolean $httpOnly avoids the cookie to be accessed via javascript
* @return boolean true: the cookie has been created, false: cookie creation failed
*/
public static function set($key, $value, $lifetime = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) {
// convert array values to json
if(is_array($value)) $value = a::json($value);
// hash the value
$value = static::hash($value) . '+' . $value;
// store that thing in the cookie global
$_COOKIE[$key] = $value;
// store the cookie
return setcookie($key, $value, static::lifetime($lifetime), $path, $domain, $secure, $httpOnly);
}
/**
* Calculates the lifetime for a cookie
*
* @return int
*/
public static function lifetime($minutes) {
return $minutes > 0 ? (time() + ($minutes * 60)) : 0;
}
/**
* Stores a cookie forever
*
* <code>
*
* cookie::forever('mycookie', 'hello');
* // never expires
*
* </code>
*
* @param string $key The name of the cookie
* @param string $value The cookie content
* @param string $path The path on the server to set the cookie for
* @param string $domain the domain
* @param boolean $secure only sets the cookie over https
* @return boolean true: the cookie has been created, false: cookie creation failed
*/
public static function forever($key, $value, $path = '/', $domain = null, $secure = false) {
return static::set($key, $value, 2628000, $path, $domain, $secure);
}
/**
* Get a cookie value
*
* <code>
*
* cookie::get('mycookie', 'peter');
* // sample output: 'hello' or if the cookie is not set 'peter'
*
* </code>
*
* @param string $key The name of the cookie
* @param string $default The default value, which should be returned if the cookie has not been found
* @return mixed The found value
*/
public static function get($key = null, $default = null) {
if(is_null($key)) return $_COOKIE;
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : null;
return empty($value) ? $default : static::parse($value);
}
/**
* Checks if a cookie exists
*
* @return boolean
*/
public static function exists($key) {
return !is_null(static::get($key));
}
/**
* Creates a hash for the cookie value
* salted with the secret cookie salt string from the defaults
*
* @param string $value
* @return string
*/
protected static function hash($value) {
return sha1($value . static::$salt);
}
/**
* Parses the hashed value from a cookie
* and tries to extract the value
*
* @param string $string
* @return mixed
*/
protected static function parse($string) {
// extract hash and value
$parts = str::split($string, '+');
$hash = a::first($parts);
$value = a::last($parts);
// if the hash or the value is missing at all return null
if(empty($hash) || empty($value)) return null;
// compare the extracted hash with the hashed value
if($hash !== static::hash($value)) return null;
return $value;
}
/**
* Remove a cookie
*
* <code>
*
* cookie::remove('mycookie');
* // mycookie is now gone
*
* </code>
*
* @param string $key The name of the cookie
* @return mixed true: the cookie has been removed, false: the cookie could not be removed
*/
public static function remove($key) {
if(isset($_COOKIE[$key])) {
unset($_COOKIE[$key]);
return setcookie($key, '', time() - 3600, '/');
}
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Crypt
*
* Encodes and decodes strings with different encryption methods
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>, Arno Richter <oelna@oelna.de>
* @link http://getkirby.com
* @copyright Bastian Allgeier, Arno Richter
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Crypt {
// encryption salt - should be changed
public static $salt = '-';
// all available encryption modes
public static $encryption = array(
'rijndael-128',
'rijndael-256',
'blowfish',
'twofish',
'des'
);
/**
* Encodes a string
*
* @param string $text
* @param string $key An optional encryption key
* @param string $mode Check out the $encryption array for available modes
* @return string
*/
public static function encode($text, $key = null, $mode = 'blowfish') {
// check for mcrypt support
if(!function_exists('mcrypt_get_iv_size')) {
throw new Exception('The mcrypt extension is missing');
}
// all modes are lowercase so we try to avoid errors here
$mode = strtolower($mode);
// check for a valid encryption mode
if(!in_array($mode, static::$encryption)) throw new Exception('Invalid encryption mode: ' . $mode);
$size = mcrypt_get_iv_size($mode, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$result = mcrypt_encrypt($mode, static::$salt . $key, $text, MCRYPT_MODE_ECB, $iv);
return trim($result);
}
/**
* Decodes a string
*
* @param string $text
* @param string $key An optional encryption key
* @param string $mode Check out the $encryption array for available modes
* @return string
*/
public static function decode($text, $key = null, $mode = 'blowfish') {
// check for mcrypt support
if(!function_exists('mcrypt_get_iv_size')) {
throw new Exception('The mcrypt extension is missing');
}
// all modes are lowercase so we try to avoid errors here
$mode = strtolower($mode);
// check for a valid encryption mode
if(!in_array($mode, static::$encryption)) throw new Exception('Invalid encryption mode: ' . $mode);
$size = mcrypt_get_iv_size($mode, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$result = mcrypt_decrypt($mode, static::$salt . $key, $text, MCRYPT_MODE_ECB, $iv);
return trim($result);
}
}

175
kirby/toolkit/lib/data.php Normal file
View file

@ -0,0 +1,175 @@
<?php
/**
* Data
*
* Universal data writer/reader/decoder/encoder for
* json, yaml and structured kirby content
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Data {
const ERROR_INVALID_ADAPTER = 0;
public static $adapters = array();
public static function adapter($type) {
if(isset(static::$adapters[$type])) return static::$adapters[$type];
foreach(static::$adapters as $adapter) {
if(is_array($adapter['extension']) && in_array($type, $adapter['extension'])) {
return $adapter;
} else if($adapter['extension'] == $type) {
return $adapter;
}
}
throw new Error('Invalid adapter type', static::ERROR_INVALID_ADAPTER);
}
public static function encode($data, $type) {
$adapter = static::adapter($type);
return call_user_func($adapter['encode'], $data);
}
public static function decode($data, $type) {
$adapter = static::adapter($type);
return call_user_func($adapter['decode'], $data);
}
public static function read($file, $type = null) {
// type autodetection
if(is_null($type)) $type = f::extension($file);
// get the adapter
$adapter = static::adapter($type);
if(isset($adapter['read'])) {
return call($adapter['read'], $file);
} else {
return data::decode(f::read($file), $type);
}
}
public static function write($file, $data, $type = null) {
// type autodetection
if(is_null($type)) $type = f::extension($file);
return f::write($file, data::encode($data, $type));
}
}
/**
* Json adapter
*/
data::$adapters['json'] = array(
'extension' => 'json',
'encode' => function($data) {
return json_encode($data);
},
'decode' => function($string) {
return json_decode($string, true);
}
);
/**
* Kirby data adapter
*/
data::$adapters['kd'] = array(
'extension' => array('md', 'txt'),
'encode' => function($data) {
$result = array();
foreach($data AS $key => $value) {
$key = str::ucfirst(str::slug($key));
if(empty($key) || is_null($value)) continue;
// avoid problems with arrays
if(is_array($value)) {
$value = '';
}
// escape accidental dividers within a field
$value = preg_replace('!(\n|^)----(.*?\R*)!', "$1\\----$2", $value);
// multi-line content
if(preg_match('!\R!', $value, $matches)) {
$result[$key] = $key . ": \n\n" . trim($value);
// single-line content
} else {
$result[$key] = $key . ': ' . trim($value);
}
}
return implode("\n\n----\n\n", $result);
},
'decode' => function($string) {
// remove BOM
$string = str_replace(BOM, '', $string);
// explode all fields by the line separator
$fields = preg_split('!\n----\s*\n*!', $string);
// start the data array
$data = array();
// loop through all fields and add them to the content
foreach($fields as $field) {
$pos = strpos($field, ':');
$key = str_replace(array('-', ' '), '_', strtolower(trim(substr($field, 0, $pos))));
// Don't add fields with empty keys
if(empty($key)) continue;
$data[$key] = trim(substr($field, $pos+1));
}
return $data;
}
);
/**
* PHP serializer adapter
*/
data::$adapters['php'] = array(
'extension' => array('php'),
'encode' => function($array) {
return '<?php ' . PHP_EOL . PHP_EOL . 'return ' . var_export($array, true) . PHP_EOL . PHP_EOL . '?>';
},
'decode' => function() {
throw new Error('Decoding PHP strings is not supported');
},
'read' => function($file) {
$array = require $file;
return $array;
}
);
/**
* YAML adapter
*/
data::$adapters['yaml'] = array(
'extension' => array('yaml', 'yml'),
'encode' => function($data) {
return yaml::encode($data);
},
'decode' => function($string) {
return yaml::decode($string);
}
);

View file

@ -0,0 +1,496 @@
<?php
/**
*
* Database
*
* The ingenius Kirby Database class
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Database {
public static $connectors = array();
// a global array of started connections
public static $connections = array();
// the established connection
protected $connection;
// dsn
protected $dsn;
// the database type (mysql, sqlite)
protected $type;
// the connection id
protected $id;
// the optional prefix for table names
protected $prefix;
// the PDO query statement
protected $statement;
// whitelists for tables and their columns
protected $tableWhitelist;
protected $columnWhitelist = array();
// the number of affected rows for the last query
protected $affected;
// the last insert id
protected $lastId;
// the last query
protected $lastQuery;
// the last result set
protected $lastResult;
// the last error
protected $lastError;
// set to true to throw exceptions on failed queries
protected $fail = false;
// an array with all queries which are being made
protected $trace = array();
/**
* Constructor
*/
public function __construct($params = null) {
$this->connect($params);
}
/**
* Returns one of the started instance
*
* @param string $id
* @return object
*/
public static function instance($id = null) {
return (is_null($id)) ? a::last(static::$connections) : a::get(static::$connections, $id);
}
/**
* Returns all started instances
*
* @return array
*/
public static function instances() {
return static::$connections;
}
/**
* Connects to a database
*
* @param mixed $params This can either be a config key or an array of parameters for the connection
* @return object
*/
public function connect($params = null) {
$defaults = array(
'database' => null,
'type' => 'mysql',
'prefix' => null,
'user' => null,
'password' => null,
'id' => uniqid()
);
$options = array_merge($defaults, $params);
// store the database information
$this->database = $options['database'];
$this->type = $options['type'];
$this->prefix = $options['prefix'];
$this->id = $options['id'];
if(!isset(static::$connectors[$this->type])) {
throw new Exception('Invalid database connector: ' . $this->type);
}
// fetch the dsn and store it
$this->dsn = call_user_func(static::$connectors[$this->type], $options);
// try to connect
$this->connection = new PDO($this->dsn, $options['user'], $options['password']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// store the connection
static::$connections[$this->id] = $this;
// return the connection
return $this->connection;
}
/**
* Returns the currently active connection
*
* @return object
*/
public function connection() {
return $this->connection;
}
/**
* Sets the exception mode for the next query
*
* @param boolean $fail
*/
public function fail($fail = true) {
$this->fail = $fail;
return $this;
}
/**
* Returns the used database type
*
* @return string
*/
public function type() {
return $this->type;
}
/**
* Returns the used table name prefix
*
* @return string
*/
public function prefix() {
return $this->prefix;
}
/**
* Escapes a value to be used for a safe query
* NOTE: Prepared statements using bound parameters are more secure and solid
*
* @param string $value
* @return string
*/
public function escape($value) {
return substr($this->connection()->quote($value), 1, -1);
}
/**
* Adds a value to the db trace and also returns the entire trace if nothing is specified
*
* @param array $data
* @return array
*/
public function trace($data = null) {
if(is_null($data)) return $this->trace;
$this->trace[] = $data;
}
/**
* Returns the number of affected rows for the last query
*
* @return int
*/
public function affected() {
return $this->affected;
}
/**
* Returns the last id if available
*
* @return int
*/
public function lastId() {
return $this->lastId;
}
/**
* Returns the last query
*
* @return string
*/
public function lastQuery() {
return $this->lastQuery;
}
/**
* Returns the last set of results
*
* @return mixed
*/
public function lastResult() {
return $this->lastResult;
}
/**
* Returns the last db error (exception object)
*
* @return object
*/
public function lastError() {
return $this->lastError;
}
/**
* Private method to execute database queries.
* This is used by the query() and execute() methods
*
* @param string $query
* @param array $bindings
* @return mixed
*/
protected function hit($query, $bindings = array()) {
// try to prepare and execute the sql
try {
$this->statement = $this->connection->prepare($query);
$this->statement->execute($bindings);
$this->affected = $this->statement->rowCount();
$this->lastId = $this->connection->lastInsertId();
$this->lastError = null;
// store the final sql to add it to the trace later
$this->lastQuery = $this->statement->queryString;
} catch(\Exception $e) {
// store the error
$this->affected = 0;
$this->lastError = $e;
$this->lastId = null;
$this->lastQuery = $query;
// only throw the extension if failing is allowed
if($this->fail) throw $e;
}
// add a new entry to the singleton trace array
$this->trace(array(
'query' => $this->lastQuery,
'bindings' => $bindings,
'error' => $this->lastError
));
// reset some stuff
$this->fail = false;
// return true or false on success or failure
return is_null($this->lastError);
}
/**
* Exectues a sql query, which is expected to return a set of results
*
* @param string $query
* @param array $bindings
* @param array $params
* @return mixed
*/
public function query($query, $bindings = array(), $params = array()) {
$defaults = array(
'flag' => null,
'method' => 'fetchAll',
'fetch' => 'Obj',
'iterator' => 'Collection',
);
$options = array_merge($defaults, $params);
if(!$this->hit($query, $bindings)) return false;
// define the default flag for the fetch method
$flags = $options['fetch'] == 'array' ? PDO::FETCH_ASSOC : PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE;
// add optional flags
if(!empty($options['flag'])) $flags |= $options['flag'];
// set the fetch mode
if($options['fetch'] == 'array') {
$this->statement->setFetchMode($flags);
} else {
$this->statement->setFetchMode($flags, $options['fetch']);
}
// fetch that stuff
$results = $this->statement->{$options['method']}();
if($options['iterator'] == 'array') return $this->lastResult = $results;
return $this->lastResult = new $options['iterator']($results);
}
/**
* Executes a sql query, which is expected to not return a set of results
*
* @param string $query
* @param array $bindings
* @return boolean
*/
public function execute($query, $bindings = array()) {
return $this->lastResult = $this->hit($query, $bindings);
}
/**
* Sets the current table, which should be queried
*
* @param string $table
* @return object Returns a Query object, which can be used to build a full query for that table
*/
public function table($table) {
return new Database\Query($this, $this->prefix() . $table);
}
/**
* Checks if a table exists in the current database
*
* @param string $table
* @return boolean
*/
public function validateTable($table) {
if(!$this->tableWhitelist) {
// Get the table whitelist from the database
$sql = new SQL($this);
$query = $sql->tableList($this->database);
$results = $this->query($query, $sql->bindings($query));
if($results) {
$this->tableWhitelist = $results->pluck('name');
} else {
return false;
}
}
return in_array($table, $this->tableWhitelist);
}
/**
* Checks if a column exists in a specified table
*
* @param string $table
* @param string $column
* @return boolean
*/
public function validateColumn($table, $column) {
if(!isset($this->columnWhitelist[$table])) {
if(!$this->validateTable($table)) {
$this->columnWhitelist[$table] = array();
return false;
}
// Get the column whitelist from the database
$sql = new SQL($this);
$query = $sql->columnList($this->database, $table);
$results = $this->query($query, $sql->bindings($query));
if($results) {
$this->columnWhitelist[$table] = $results->pluck('name');
} else {
return false;
}
}
return in_array($column, $this->columnWhitelist[$table]);
}
/**
* Creates a new table
*
* @param string $table
* @param array $columns
* @return boolean
*/
public function createTable($table, $columns = array()) {
$sql = new SQL($this);
$query = $sql->createTable($table, $columns);
$queries = str::split($query, ';');
foreach($queries as $query) {
$query = trim($query);
if(!$this->execute($query, $sql->bindings($query))) return false;
}
return true;
}
/**
* Drops a table
*
* @param string $table
* @return boolean
*/
public function dropTable($table) {
$sql = new SQL($this);
$query = $sql->dropTable($table);
return $this->execute($query, $sql->bindings($query));
}
/**
* Magic way to start queries for tables by
* using a method named like the table.
* I.e. $db->users()->all()
*/
public function __call($method, $arguments = null) {
return $this->table($method);
}
}
/**
* MySQL database connector
*/
database::$connectors['mysql'] = function($params) {
if(!isset($params['host']) && !isset($params['socket'])) {
throw new Error('The mysql connection requires either a "host" or a "socket" parameter');
}
if(!isset($params['database'])) {
throw new Error('The mysql connection requires a "database" parameter');
}
$parts = array();
if(!empty($params['host'])) {
$parts[] = 'host=' . $params['host'];
}
if(!empty($params['port'])) {
$parts[] = 'port=' . $params['port'];
}
if(!empty($params['socket'])) {
$parts[] = 'unix_socket=' . $params['socket'];
}
if(!empty($params['database'])) {
$parts[] = 'dbname=' . $params['database'];
}
$parts[] = 'charset=' . a::get($params, 'charset', 'utf8');
return 'mysql:' . implode(';', $parts);
};
/**
* SQLite database connector
*/
database::$connectors['sqlite'] = function($params) {
if(!isset($params['database'])) throw new Error('The sqlite connection requires a "database" parameter');
return 'sqlite:' . $params['database'];
};

View file

@ -0,0 +1,923 @@
<?php
namespace Database;
use A;
use Error;
use Pagination;
use Str;
use Sql;
/**
*
* Database Query
*
* The query builder is used by the Database class
* to build SQL queries in a fluent, jquery-style way
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Query {
const ERROR_INVALID_QUERY_METHOD = 0;
protected $database = null;
// The object which should be fetched for each row
protected $fetch = 'Obj';
// The iterator class, which should be used for result sets
protected $iterator = 'Collection';
// An array of bindings for the final query
protected $bindings = array();
// The table name
protected $table;
// The name of the primary key column
protected $primaryKeyName = 'id';
// An array with additional join parameters
protected $join;
// A list of columns, which should be selected
protected $select;
// Boolean for distinct select clauses
protected $distinct;
// Boolean for if exceptions should be thrown on failing queries
protected $fail = false;
// A list of values for update and insert clauses
protected $values;
// WHERE clause
protected $where;
// GROUP BY clause
protected $group;
// HAVING clause
protected $having;
// ORDER BY clause
protected $order;
// The offset, which should be applied to the select query
protected $offset = 0;
// The limit, which should be applied to the select query
protected $limit;
// Boolean to enable query debugging
protected $debug = false;
/**
* Constructor
*
* @param Database $database Database object
* @param string $table Optional name of the table, which should be queried
*/
public function __construct($database, $table) {
$this->database = $database;
$this->table($table);
if(!$this->table) throw new Error('Invalid table ' . $table);
}
/**
* Reset the query class after each db hit
*/
protected function reset() {
$this->join = null;
$this->select = null;
$this->distinct = null;
$this->fail = false;
$this->values = null;
$this->where = null;
$this->group = null;
$this->having = null;
$this->order = null;
$this->offset = null;
$this->limit = null;
$this->debug = null;
}
/**
* Enables query debugging.
* If enabled, the query will return an array with all important info about
* the query instead of actually executing the query and returning results
*
* @param boolean $debug
* @return object
*/
public function debug($debug = true) {
$this->debug = $debug;
return $this;
}
/**
* Enables distinct select clauses.
*
* @param boolean $distinct
* @return object
*/
public function distinct($distinct = true) {
$this->distinct = $distinct;
return $this;
}
/**
* Enables failing queries.
* If enabled queries will no longer fail silently but throw an exception
*
* @param boolean $fail
* @return object
*/
public function fail($fail = true) {
$this->fail = $fail;
return $this;
}
/**
* Sets the object class, which should be fetched
* Set this to array to get a simple array instead of an object
*
* @param string $fetch
* @return object
*/
public function fetch($fetch) {
if(!is_null($fetch)) $this->fetch = $fetch;
return $this;
}
/**
* Sets the iterator class, which should be used for multiple results
* Set this to array to get a simple array instead of an iterator object
*
* @param string $iterator
* @return object
*/
public function iterator($iterator) {
if(!is_null($iterator)) $this->iterator = $iterator;
return $this;
}
/**
* Sets the name of the table, which should be queried
*
* @param string $table
* @return object
*/
public function table($table) {
if(!is_null($table) && $this->database->validateTable($table)) $this->table = $table;
return $this;
}
/**
* Sets the name of the primary key column
*
* @param string $primaryKeyName
* @return object
*/
public function primaryKeyName($primaryKeyName) {
$this->primaryKeyName = $primaryKeyName;
return $this;
}
/**
* Sets the columns, which should be selected from the table
* By default all columns will be selected
*
* @param mixed $select Pass either a string of columns or an array
* @return object
*/
public function select($select) {
$this->select = $select;
return $this;
}
/**
* Adds a new join clause to the query
*
* @param string $table Name of the table, which should be joined
* @param string $on The on clause for this join
* @param string $type The join type. Uses an inner join by default
* @return object
*/
public function join($table, $on, $type = '') {
$join = array(
'table' => $table,
'on' => $on,
'type' => $type
);
$this->join[] = $join;
return $this;
}
/**
* Shortcut for creating a left join clause
*
* @param string $table Name of the table, which should be joined
* @param string $on The on clause for this join
* @return object
*/
public function leftJoin($table, $on) {
return $this->join($table, $on, 'left');
}
/**
* Shortcut for creating a right join clause
*
* @param string $table Name of the table, which should be joined
* @param string $on The on clause for this join
* @return object
*/
public function rightJoin($table, $on) {
return $this->join($table, $on, 'right');
}
/**
* Shortcut for creating an inner join clause
*
* @param string $table Name of the table, which should be joined
* @param string $on The on clause for this join
* @return object
*/
public function innerJoin($table, $on) {
return $this->join($table, $on, 'inner');
}
/**
* Sets the values which should be used for the update or insert clause
*
* @param mixed $values Can either be a string or an array of values
* @return object
*/
public function values($values = array()) {
if(!is_null($values)) $this->values = $values;
return $this;
}
/**
* Attaches additional bindings to the query.
* Also can be used as getter for all attached bindings by not passing an argument.
*
* @param mixed $bindings Array of bindings or null to use this method as getter
* @return mixed
*/
public function bindings($bindings = null) {
if(is_array($bindings)) {
$this->bindings = array_merge($this->bindings, $bindings);
return $this;
}
return $this->bindings;
}
/**
* Attaches an additional where clause
*
* All available ways to add where clauses
*
* ->where('username like "myuser"'); (args: 1)
* ->where(array('username' => 'myuser')); (args: 1)
* ->where(function($where) { $where->where('id', '=', 1) }) (args: 1)
* ->where('username like ?', 'myuser') (args: 2)
* ->where('username', 'like', 'myuser'); (args: 3)
*
* @param list
* @return object
*/
public function where() {
$this->where = $this->filterQuery(func_get_args(), $this->where);
return $this;
}
/**
* Shortcut to attach a where clause with an OR operator.
* Check out the where() method docs for additional info.
*
* @param list
* @return object
*/
public function orWhere() {
$args = func_get_args();
$mode = a::last($args);
// if there's a where clause mode attribute attached…
if(in_array($mode, array('AND', 'OR'))) {
// remove that from the list of arguments
array_pop($args);
}
// make sure to always attach the OR mode indicator
$args[] = 'OR';
call_user_func_array(array($this, 'where'), $args);
return $this;
}
/**
* Shortcut to attach a where clause with an AND operator.
* Check out the where() method docs for additional info.
*
* @param list
* @return object
*/
public function andWhere() {
$args = func_get_args();
$mode = a::last($args);
// if there's a where clause mode attribute attached…
if(in_array($mode, array('AND', 'OR'))) {
// remove that from the list of arguments
array_pop($args);
}
// make sure to always attach the AND mode indicator
$args[] = 'AND';
call_user_func_array(array($this, 'where'), func_get_args());
return $this;
}
/**
* Attaches a group by clause
*
* @param string $group
* @return object
*/
public function group($group) {
$this->group = $group;
return $this;
}
/**
* Attaches an additional having clause
*
* All available ways to add having clauses
*
* ->having('username like "myuser"'); (args: 1)
* ->having(array('username' => 'myuser')); (args: 1)
* ->having(function($having) { $having->having('id', '=', 1) }) (args: 1)
* ->having('username like ?', 'myuser') (args: 2)
* ->having('username', 'like', 'myuser'); (args: 3)
*
* @param list
* @return object
*/
public function having() {
$this->having = $this->filterQuery(func_get_args(), $this->having);
return $this;
}
/**
* Attaches an order clause
*
* @param string $order
* @return object
*/
public function order($order) {
$this->order = $order;
return $this;
}
/**
* Sets the offset for select clauses
*
* @param int $offset
* @return object
*/
public function offset($offset) {
$this->offset = $offset;
return $this;
}
/**
* Sets the limit for select clauses
*
* @param int $limit
* @return object
*/
public function limit($limit) {
$this->limit = $limit;
return $this;
}
/**
* Builds the different types of SQL queries
* This uses the SQL class to build stuff.
*
* @param string $type (select, update, insert)
* @return string The final query
*/
public function build($type) {
$sql = new SQL($this->database, $this);
switch($type) {
case 'select':
return $sql->select(array(
'table' => $this->table,
'columns' => $this->select,
'join' => $this->join,
'distinct' => $this->distinct,
'where' => $this->where,
'group' => $this->group,
'having' => $this->having,
'order' => $this->order,
'offset' => $this->offset,
'limit' => $this->limit
));
case 'update':
return $sql->update(array(
'table' => $this->table,
'where' => $this->where,
'values' => $this->values,
));
case 'insert':
return $sql->insert(array(
'table' => $this->table,
'values' => $this->values,
));
case 'delete':
return $sql->delete(array(
'table' => $this->table,
'where' => $this->where,
));
}
}
/**
* Builds a count query
*
* @return object
*/
public function count() {
return $this->aggregate('COUNT');
}
/**
* Builds a max query
*
* @param string $column
* @return object
*/
public function max($column) {
return $this->aggregate('MAX', $column);
}
/**
* Builds a min query
*
* @param string $column
* @return object
*/
public function min($column) {
return $this->aggregate('MIN', $column);
}
/**
* Builds a sum query
*
* @param string $column
* @return object
*/
public function sum($column) {
return $this->aggregate('SUM', $column);
}
/**
* Builds an average query
*
* @param string $column
* @return object
*/
public function avg($column) {
return $this->aggregate('AVG', $column);
}
/**
* Builds an aggregation query.
* This is used by all the aggregation methods above
*
* @param string $method
* @param string $column
* @param string $default An optional default value, which should be returned if the query fails
* @return object
*/
public function aggregate($method, $column = '*', $default = 0) {
// reset the sorting to avoid counting issues
$this->order = null;
// validate column
if($column !== '*') {
$sql = new SQL($this->database, $this);
list($table, $columnPart) = $sql->splitIdentifier($this->table, $column);
if(!$this->database->validateColumn($table, $columnPart)) {
throw new Error('Invalid column ' . $column);
}
$column = $sql->combineIdentifier($table, $columnPart);
}
$fetch = $this->fetch;
$row = $this->select($method . '(' . $column . ') as aggregation')->fetch('Obj')->first();
$result = $row ? $row->get('aggregation') : $default;
$this->fetch($fetch);
return $result;
}
/**
* Used as an internal shortcut for firing a db query
*
* @param string $query
* @param array $params
* @return mixed
*/
protected function query($query, $params = array()) {
if($this->debug) return array(
'query' => $query,
'bindings' => $this->bindings(),
'options' => $params
);
if($this->fail) $this->database->fail();
$result = $this->database->query($query, $this->bindings(), $params);
$this->reset();
return $result;
}
/**
* Used as an internal shortcut for executing a db query
*
* @param string $query
* @param array $params
* @return mixed
*/
protected function execute($query, $params = array()) {
if($this->debug) return array(
'query' => $query,
'bindings' => $this->bindings(),
'options' => $params
);
if($this->fail) $this->database->fail();
$result = $this->database->execute($query, $this->bindings(), $params);
$this->reset();
return $result;
}
/**
* Selects only one row from a table
*
* @return object
*/
public function first() {
return $this->query($this->offset(0)->limit(1)->build('select'), array(
'fetch' => $this->fetch,
'iterator' => 'array',
'method' => 'fetch',
));
}
/**
* Selects only one row from a table
*
* @return object
*/
public function row() {
return $this->first();
}
/**
* Selects only one row from a table
*
* @return object
*/
public function one() {
return $this->first();
}
/**
* Automatically adds pagination to a query
*
* @param int $page
* @param int $limit The number of rows, which should be returned for each page
* @param array $params Optional params for the pagination object
* @return object Collection iterator with attached pagination object
*/
public function page($page, $limit, $params = array()) {
$defaults = array(
'page' => $page
);
$options = array_merge($defaults, $params);
// clone this to create a counter query
$counter = clone $this;
// count the total number of rows for this query
$count = $counter->count();
// pagination
$pagination = new Pagination($count, $limit, $options);
// apply it to the dataset and retrieve all rows. make sure to use Collection as the iterator to be able to attach the pagination object
$collection = $this->offset($pagination->offset())->limit($pagination->limit())->all();
// store all pagination vars in a separate object
if($collection) $collection->paginate($pagination);
// return the limited collection
return $collection;
}
/**
* Returns all matching rows from a table
*
* @return mixed
*/
public function all() {
return $this->query($this->build('select'), array(
'fetch' => $this->fetch,
'iterator' => $this->iterator,
));
}
/**
* Returns only values from a single column
*
* @param string $column
* @return mixed
*/
public function column($column) {
$sql = new SQL($this->database, $this);
$primaryKey = $sql->combineIdentifier($this->table, $this->primaryKeyName);
$results = $this->query($this->select(array($column))->order($primaryKey . ' ASC')->build('select'), array(
'iterator' => 'array',
'fetch' => 'array',
));
$results = a::extract($results, $column);
if($this->iterator == 'array') return $results;
$iterator = $this->iterator;
return new $iterator($results);
}
/**
* Find a single row by column and value
*
* @param string $column
* @param mixed $value
* @return mixed
*/
public function findBy($column, $value) {
return $this->where(array($column => $value))->first();
}
/**
* Find a single row by its primary key
*
* @param mixed $id
* @return mixed
*/
public function find($id) {
return $this->findBy($this->primaryKeyName, $id);
}
/**
* Fires an insert query
*
* @param array $values You can pass values here or set them with ->values() before
* @return mixed Returns the last inserted id on success or false.
*/
public function insert($values = null) {
$query = $this->execute($this->values($values)->build('insert'));
return ($query) ? $this->database->lastId() : false;
}
/**
* Fires an update query
*
* @param array $values You can pass values here or set them with ->values() before
* @param mixed $where You can pass a where clause here or set it with ->where() before
* @return boolean
*/
public function update($values = null, $where = null) {
return $this->execute($this->values($values)->where($where)->build('update'));
}
/**
* Fires a delete query
*
* @param mixed $where You can pass a where clause here or set it with ->where() before
* @return boolean
*/
public function delete($where = null) {
return $this->execute($this->where($where)->build('delete'));
}
/**
* Enables magic queries like findByUsername or findByEmail
*
* @param string $method
* @param array $arguments
* @return mixed
*/
public function __call($method, $arguments) {
if(preg_match('!^findBy([a-z]+)!i', $method, $match)) {
$column = str::lower($match[1]);
return $this->findBy($column, $arguments[0]);
} else {
throw new Error('Invalid query method: ' . $method, static::ERROR_INVALID_QUERY_METHOD);
}
}
/**
* Builder for where and having clauses
*
* @param array $args Arguments, see where() description
* @param string $current Current value (like $this->where)
* @return string
*/
protected function filterQuery($args, $current) {
$mode = a::last($args);
$result = '';
// if there's a where clause mode attribute attached…
if(in_array($mode, array('AND', 'OR'))) {
// remove that from the list of arguments
array_pop($args);
} else {
$mode = 'AND';
}
switch(count($args)) {
case 1:
if(is_null($args[0])) {
return $current;
// ->where('username like "myuser"');
} else if(is_string($args[0])) {
// simply add the entire string to the where clause
// escaping or using bindings has to be done before calling this method
$result = $args[0];
// ->where(array('username' => 'myuser'));
} else if(is_array($args[0])) {
$sql = new SQL($this->database, $this);
// simple array mode (AND operator)
$result = $sql->values($this->table, $args[0], ' AND ', true, true);
} else if(is_callable($args[0])) {
$query = clone $this;
call_user_func($args[0], $query);
$result = '(' . $query->where . ')';
}
break;
case 2:
// ->where('username like :username', array('username' => 'myuser'))
if(is_string($args[0]) && is_array($args[1])) {
// prepared where clause
$result = $args[0];
// store the bindings
$this->bindings($args[1]);
// ->where('username like ?', 'myuser')
} else if(is_string($args[0]) && is_string($args[1])) {
// prepared where clause
$result = $args[0];
// store the bindings
$this->bindings(array($args[1]));
}
break;
case 3:
// ->where('username', 'like', 'myuser');
if(is_string($args[0]) && is_string($args[1])) {
// validate column
$sql = new SQL($this->database, $this);
list($table, $column) = $sql->splitIdentifier($this->table, $args[0]);
if(!$this->database->validateColumn($table, $column)) {
throw new Error('Invalid column ' . $args[0]);
}
$key = $sql->combineIdentifier($table, $column);
// ->where('username', 'in', array('myuser', 'myotheruser'));
if(is_array($args[2])) {
$predicate = trim(strtoupper($args[1]));
if(!in_array($predicate, array(
'IN', 'NOT IN'
))) throw new Error('Invalid predicate ' . $predicate);
// build a list of bound values
$values = array();
$bindings = array();
foreach($args[2] as $value) {
$valueBinding = sql::generateBindingName('value');
$bindings[$valueBinding] = $value;
$values[] = $valueBinding;
}
// add that to the where clause in parenthesis
$result = $key . ' ' . $predicate . ' (' . implode(', ', $values) . ')';
$this->bindings($bindings);
// ->where('username', 'like', 'myuser');
} else {
$predicate = trim(strtoupper($args[1]));
if(!in_array($predicate, array(
'=', '>=', '>', '<=', '<', '<>', '!=', '<=>',
'IS', 'IS NOT',
'BETWEEN', 'NOT BETWEEN',
'LIKE', 'NOT LIKE',
'SOUNDS LIKE',
'REGEXP', 'NOT REGEXP'
))) throw new Error('Invalid predicate/operator ' . $predicate);
$valueBinding = sql::generateBindingName('value');
$bindings[$valueBinding] = $args[2];
$result = $key . ' ' . $predicate . ' ' . $valueBinding;
$this->bindings($bindings);
}
}
break;
}
// attach the where clause
if(!empty($current)) {
return $current . ' ' . $mode . ' ' . $result;
} else {
return $result;
}
}
}

251
kirby/toolkit/lib/db.php Normal file
View file

@ -0,0 +1,251 @@
<?php
/**
* DB
*
* Database shortcuts
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class DB {
const ERROR_UNKNOWN_METHOD = 0;
// query shortcuts
public static $queries = array();
// The singleton Database object
public static $connection = null;
/**
* (Re)connect the database
*
* @param mixed $params Pass array() to use the default params from the config
* @return object
*/
public static function connect($params = null) {
if(is_null($params) && !is_null(static::$connection)) return static::$connection;
if(is_null($params)) {
// try to connect with the default connection settings
$params = array(
'type' => c::get('db.type', 'mysql'),
'host' => c::get('db.host', 'localhost'),
'user' => c::get('db.user', 'root'),
'password' => c::get('db.password', ''),
'database' => c::get('db.name', ''),
'prefix' => c::get('db.prefix', ''),
);
}
return static::$connection = new Database($params);
}
/**
* Returns the current database connection
*
* @return object
*/
public static function connection() {
return static::$connection;
}
/**
* Sets the current table, which should be queried
*
* @param string $table
* @return object Returns a DBQuery object, which can be used to build a full query for that table
*/
public static function table($table) {
$connection = db::connect();
return $connection->table($table);
}
/**
* Executes a raw sql query which expects a set of results
*
* @param string $query
* @param array $bindings
* @param array $params
* @return mixed
*/
public static function query($query, $bindings = array(), $params = array()) {
$connection = db::connect();
return $connection->query($query, $bindings, $params);
}
/**
* Executes a raw sql query which expects no set of results (i.e. update, insert, delete)
*
* @param string $query
* @param array $bindings
* @return mixed
*/
public static function execute($query, $bindings = array()) {
$connection = db::connect();
return $connection->execute($query, $bindings);
}
/**
* Magic calls for other static db methods,
* which are redircted to the database class if available
*
* @param string $method
* @param mixed $arguments
* @return mixed
*/
public static function __callStatic($method, $arguments) {
if(isset(static::$queries[$method])) {
return call(static::$queries[$method], $arguments);
} else if(!is_callable(array(static::$connection, $method))) {
throw new Error('invalid static db method: ' . $method, static::ERROR_UNKNOWN_METHOD);
} else {
return call(array(static::$connection, $method), $arguments);
}
}
}
/**
* Shortcut for select clauses
*
* @param string $table The name of the table, which should be queried
* @param mixed $columns Either a string with columns or an array of column names
* @param mixed $where The where clause. Can be a string or an array
* @param mixed $order
* @param int $offset
* @param int $limit
* @return mixed
*/
db::$queries['select'] = function($table, $columns = '*', $where = null, $order = null, $offset = 0, $limit = null) {
return db::table($table)->select($columns)->where($where)->order($order)->offset($offset)->limit($limit)->all();
};
/**
* Shortcut for selecting a single row in a table
*
* @param string $table The name of the table, which should be queried
* @param mixed $columns Either a string with columns or an array of column names
* @param mixed $where The where clause. Can be a string or an array
* @param mixed $order
* @param int $offset
* @param int $limit
* @return mixed
*/
db::$queries['first'] = db::$queries['row'] = db::$queries['one'] = function($table, $columns = '*', $where = null, $order = null) {
return db::table($table)->select($columns)->where($where)->order($order)->first();
};
/**
* Returns only values from a single column
*
* @param string $table The name of the table, which should be queried
* @param mixed $column The name of the column to select from
* @param mixed $where The where clause. Can be a string or an array
* @param mixed $order
* @param int $offset
* @param int $limit
* @return mixed
*/
db::$queries['column'] = function($table, $column, $where = null, $order = null, $offset = 0, $limit = null) {
return db::table($table)->where($where)->order($order)->offset($offset)->limit($limit)->column($column);
};
/**
* Shortcut for inserting a new row into a table
*
* @param string $table The name of the table, which should be queried
* @param string $values An array of values, which should be inserted
* @return boolean
*/
db::$queries['insert'] = function($table, $values) {
return db::table($table)->insert($values);
};
/**
* Shortcut for updating a row in a table
*
* @param string $table The name of the table, which should be queried
* @param string $values An array of values, which should be inserted
* @param mixed $where An optional where clause
* @return boolean
*/
db::$queries['update'] = function($table, $values, $where = null) {
return db::table($table)->where($where)->update($values);
};
/**
* Shortcut for deleting rows in a table
*
* @param string $table The name of the table, which should be queried
* @param mixed $where An optional where clause
* @return boolean
*/
db::$queries['delete'] = function($table, $where = null) {
return db::table($table)->where($where)->delete();
};
/**
* Shortcut for counting rows in a table
*
* @param string $table The name of the table, which should be queried
* @param string $where An optional where clause
* @return int
*/
db::$queries['count'] = function($table, $where = null) {
return db::table($table)->where($where)->count();
};
/**
* Shortcut for calculating the minimum value in a column
*
* @param string $table The name of the table, which should be queried
* @param string $column The name of the column of which the minimum should be calculated
* @param string $where An optional where clause
* @return mixed
*/
db::$queries['min'] = function($table, $column, $where = null) {
return db::table($table)->where($where)->min($column);
};
/**
* Shortcut for calculating the maximum value in a column
*
* @param string $table The name of the table, which should be queried
* @param string $column The name of the column of which the maximum should be calculated
* @param string $where An optional where clause
* @return mixed
*/
db::$queries['max'] = function($table, $column, $where = null) {
return db::table($table)->where($where)->max($column);
};
/**
* Shortcut for calculating the average value in a column
*
* @param string $table The name of the table, which should be queried
* @param string $column The name of the column of which the average should be calculated
* @param string $where An optional where clause
* @return mixed
*/
db::$queries['avg'] = function($table, $column, $where = null) {
return db::table($table)->where($where)->avg($column);
};
/**
* Shortcut for calculating the sum of all values in a column
*
* @param string $table The name of the table, which should be queried
* @param string $column The name of the column of which the sum should be calculated
* @param string $where An optional where clause
* @return mixed
*/
db::$queries['sum'] = function($table, $column, $where = null) {
return db::table($table)->where($where)->sum($column);
};

View file

@ -0,0 +1,261 @@
<?php
/**
* Detect
*
* This class is a system feature detection helper
* to check for installed packages and software
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Detect {
/**
* Checks if the mb string extension is installed
*
* @return boolean
*/
public static function mbstring() {
return function_exists('mb_split');
}
/**
* Checks if the required php version is installed
*
* @param mixed $min
* @return boolean
*/
public static function php($min = '5.3') {
return version_compare(PHP_VERSION, $min, '>=');
}
/**
* Checks if PHP is running on Apache
*
* @return boolean
*/
public static function apache() {
return apache_get_version() ? true : false;
}
/**
* Checks if the site is running on Windows
*
* @return boolean
*/
public static function windows() {
return DS == '/' ? false : true;
}
/**
* Checks if the site is running on IIS
*
* @return boolean
*/
public static function iis() {
return isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'],'IIS') !== false ? true : false;
}
/**
* Checks if mysql installed with the minimum required version
*
* @param mixed $min
* @return boolean
*/
public static function mysql($min = '5') {
$extensions = get_loaded_extensions();
if(!in_array('mysql', $extensions)) return false;
$version = preg_replace('#(^\D*)([0-9.]+).*$#', '\2', mysql_get_client_info());
return version_compare($version, $min, '>=');
}
/**
* Checks if SQLite 3 is installed
*
* @return boolean
*/
public static function sqlite() {
return in_array('sqlite3', get_loaded_extensions());
}
/**
* Checks if safe mode is enabled
*
* @return boolean
*/
public static function safemode() {
return ini_get('safe_mode');
}
/**
* Checks if gdlib is installed
*
* @return boolean
*/
public static function gdlib() {
return function_exists('gd_info');
}
/**
* Checks if imageick is installed
*
* @return boolean
*/
public static function imagick() {
return class_exists('Imagick');
}
/**
* Checks if CURL is installed
*
* @return boolean
*/
public static function curl() {
return in_array('curl', get_loaded_extensions());
}
/**
* Check if APC cache is installed
*
* @return boolean
*/
public static function apc() {
return function_exists('apc_add');
}
/**
* Check if the Memcache extension is installed
*
* @return boolean
*/
public static function memcache() {
return class_exists('Memcache');
}
/**
* Check if the Memcached extension is installed
*
* @return boolean
*/
public static function memcached() {
return class_exists('Memcached');
}
/**
* Check if the imap extension is installed
*
* @return boolean
*/
public static function imap() {
return function_exists('imap_body');
}
/**
* Check if the mcrypt extension is installed
*
* @return boolean
*/
public static function mcrypt() {
return function_exists('mcrypt_encrypt');
}
/**
* Check if the exif extension is installed
*
* @return boolean
*/
public static function exif() {
return function_exists('read_exif_data');
}
/**
* Detect if the script is installed in a subfolder
*
* @return string
*/
public static function subfolder() {
return trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
}
/**
* Detects the current path
*
* @return string
*/
public static function path() {
$uri = explode('/', url::path());
$script = explode('/', trim($_SERVER['SCRIPT_NAME'], '/\\'));
$parts = array_diff_assoc($uri, $script);
if(empty($parts)) return false;
return implode('/', $parts);
}
/**
* Detect the document root
*
* @return string
*/
public static function documentRoot() {
$local = $_SERVER['SCRIPT_NAME'];
$absolute = $_SERVER['SCRIPT_FILENAME'];
return substr($absolute, 0, strpos($absolute, $local));
}
/**
* Converts any ini size value to an integer
*
* @param string $key
* @return int
*/
public static function iniSize($key) {
$size = ini_get($key);
$size = trim($size);
$last = strtolower($size[strlen($size)-1]);
switch($last) {
case 'g':
$size *= 1024;
case 'm':
$size *= 1024;
case 'k':
$size *= 1024;
}
return $size;
}
/**
* Returns the max accepted upload size
* defined in the php.ini
*
* @return int
*/
public static function maxUploadSize() {
return static::iniSize('upload_max_filesize');
}
/**
* Returns the max accepted post size
* defined in the php.ini
*
* @return int
*/
public static function maxPostSize() {
return static::iniSize('post_max_size');
}
/**
* Dirty browser sniffing for an ios device
*
* @return boolean
*/
public static function ios() {
$ua = visitor::ua();
return (str::contains($ua, 'iPod') || str::contains($ua, 'iPhone') || str::contains($ua, 'iPad'));
}
}

View file

@ -0,0 +1,316 @@
<?php
/**
* Dimensions
*
* The dimension object is used to provide additional
* methods for KirbyImage objects and possibly other
* objects with width and height to recalculate the size,
* get the ratio or just the width and height.
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Dimensions {
// the width of the parent object
public $width = 0;
// the height of the parent object
public $height = 0;
/**
* Constructor
*
* @param int $width
* @param int $height
*/
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
/**
* Returns the width
*
* @return int
*/
public function width() {
return $this->width;
}
/**
* Returns the height
*
* @return int
*/
public function height() {
return $this->height;
}
/**
* Calculates and returns the ratio
*
* <code>
*
* $dimensions = new Dimensions(1200, 768);
* echo $dimensions->ratio();
* // output: 1.5625
*
* </code>
*
* @return float
*/
public function ratio() {
if($this->width && $this->height) {
return ($this->width / $this->height);
} else {
return 0;
}
}
/**
* Recalculates the width and height
* to fit into the given box.
*
* <code>
*
* $dimensions = new Dimensions(1200, 768);
* $dimensions->fit(500);
*
* echo $dimensions->width();
* // output: 500
*
* echo $dimensions->height();
* // output: 320
*
* </code>
*
* @param int $box the max width and/or height
* @param boolean $force If true, the dimensions will be upscaled to fit the box if smaller
* @return object returns this object with recalculated dimensions
*/
public function fit($box, $force = false) {
if($this->width == 0 || $this->height == 0) {
$this->width = $box;
$this->height = $box;
return $this;
}
$ratio = $this->ratio();
if($this->width > $this->height) {
if($this->width > $box || $force === true) $this->width = $box;
$this->height = round($this->width / $ratio);
} elseif($this->height > $this->width) {
if($this->height > $box || $force === true) $this->height = $box;
$this->width = round($this->height * $ratio);
} elseif($this->width > $box) {
$this->width = $box;
$this->height = $box;
}
return $this;
}
/**
* Recalculates the width and height
* to fit the given width
*
* <code>
*
* $dimensions = new Dimensions(1200, 768);
* $dimensions->fitWidth(500);
*
* echo $dimensions->width();
* // output: 500
*
* echo $dimensions->height();
* // output: 320
*
* </code>
*
* @param int $fit the max width
* @param boolean $force If true, the dimensions will be upscaled to fit the width if smaller
* @return object returns this object with recalculated dimensions
*/
public function fitWidth($fit, $force = false) {
if(!$fit) return $this;
if($this->width <= $fit && !$force) return $this;
$ratio = $this->ratio();
$this->width = $fit;
$this->height = round($fit / $ratio);
return $this;
}
/**
* Recalculates the width and height
* to fit the given height
*
* <code>
*
* $dimensions = new Dimensions(1200, 768);
* $dimensions->fitHeight(500);
*
* echo $dimensions->width();
* // output: 781
*
* echo $dimensions->height();
* // output: 500
*
* </code>
*
* @param int $fit the max height
* @param boolean $force If true, the dimensions will be upscaled to fit the height if smaller
* @return object returns this object with recalculated dimensions
*/
public function fitHeight($fit, $force = false) {
if(!$fit) return $this;
if($this->height <= $fit && !$force) return $this;
$ratio = $this->ratio();
$this->width = round($fit * $ratio);
$this->height = $fit;
return $this;
}
/**
* Recalculates the dimensions by the width and height
*
* @param int $width the max height
* @param int $height the max width
* @return object
*/
public function fitWidthAndHeight($width, $height, $force = false) {
if($this->width > $this->height) {
$this->fitWidth($width, $force);
// do another check for the max height
if($this->height > $height) $this->fitHeight($height);
} else {
$this->fitHeight($height, $force);
// do another check for the max width
if($this->width > $width) $this->fitWidth($width);
}
return $this;
}
/**
* @param int $width
* @param int $height
* @param boolean $force
* @return Dimensions
*/
public function resize($width, $height, $force = false) {
$this->fitWidthAndHeight($width, $height, $force);
return $this;
}
/**
* Crops the dimensions by width and height
*
* @param int $width
* @param int $height
* @return object
*/
public function crop($width, $height = null) {
$this->width = $width;
$this->height = $width;
if($height) {
$this->height = $height;
}
return $this;
}
/**
* Returns a string representation of the orientation
*
* @return string
*/
public function orientation() {
if(!$this->ratio()) return false;
if($this->portrait()) return 'portrait';
if($this->landscape()) return 'landscape';
if($this->square()) return 'square';
}
/**
* Checks if the dimensions are portrait
*
* @return boolean
*/
public function portrait() {
return $this->height > $this->width;
}
/**
* Checks if the dimensions are landscape
*
* @return boolean
*/
public function landscape() {
return $this->width > $this->height;
}
/**
* Checks if the dimensions are square
*
* @return boolean
*/
public function square() {
return $this->width == $this->height;
}
/**
* Converts the dimensions object
* to a plain PHP array
*
* @return array
*/
public function toArray() {
return array(
'width' => $this->width(),
'height' => $this->height(),
'ratio' => $this->ratio(),
'orientation' => $this->orientation(),
);
}
/**
* Echos the dimensions as width × height
*
* @return string
*/
public function __toString() {
return $this->width . ' × ' . $this->height;
}
}

209
kirby/toolkit/lib/dir.php Normal file
View file

@ -0,0 +1,209 @@
<?php
/**
* Dir
*
* Low level directory handling utilities
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Dir {
public static $defaults = array(
'permissions' => 0755,
'ignore' => array('.', '..', '.DS_Store', '.gitignore', '.git', '.svn', '.htaccess', 'Thumb.db', '@eaDir')
);
/**
* Creates a new directory
*
* <code>
*
* $create = dir::make('/app/test/new-directory');
*
* if($create) echo 'the directory has been created';
*
* </code>
*
* @param string $dir The path for the new directory
* @return boolean True: the dir has been created, false: creating failed
*/
public static function make($dir, $recursive = true) {
return is_dir($dir) ? true : @mkdir($dir, static::$defaults['permissions'], $recursive);
}
/**
* Reads all files from a directory and returns them as an array.
* It skips unwanted invisible stuff.
*
* <code>
*
* $files = dir::read('mydirectory');
* // returns array('file-1.txt', 'file-2.txt', 'file-3.txt', etc...);
*
* </code>
*
* @param string $dir The path of directory
* @param array $ignore Optional array with filenames, which should be ignored
* @return mixed An array of filenames or false
*/
public static function read($dir, $ignore = array()) {
if(!is_dir($dir)) return array();
$skip = array_merge(static::$defaults['ignore'], $ignore);
return (array)array_diff(scandir($dir),$skip);
}
/**
* Moves a directory to a new location
*
* <code>
*
* $move = dir::move('mydirectory', 'mynewdirectory');
*
* if($move) echo 'the directory has been moved to mynewdirectory';
*
* </code>
*
* @param string $old The current path of the directory
* @param string $new The desired path where the dir should be moved to
* @return boolean True: the directory has been moved, false: moving failed
*/
public static function move($old, $new) {
if(!is_dir($old)) return false;
return @rename($old, $new);
}
/**
* Deletes a directory
*
* <code>
*
* $remove = dir::remove('mydirectory');
*
* if($remove) echo 'the directory has been removed';
*
* </code>
*
* @param string $dir The path of the directory
* @param boolean $keep If set to true, the directory will flushed but not removed.
* @return boolean True: the directory has been removed, false: removing failed
*/
public static function remove($dir, $keep = false) {
if(!is_dir($dir)) return false;
// It's easier to handle this with the Folder class
$object = new Folder($dir);
return $object->remove($keep);
}
/**
* Flushes a directory
*
* @param string $dir The path of the directory
* @return boolean True: the directory has been flushed, false: flushing failed
*/
public static function clean($dir) {
return static::remove($dir, true);
}
/**
* Gets the size of the directory and all subfolders and files
*
* @param string $dir The path of the directory
* @return mixed
*/
public static function size($dir) {
if(!file_exists($dir)) return false;
// It's easier to handle this with the Folder class
$object = new Folder($dir);
return $object->size();
}
/**
* Returns a nicely formatted size of all the contents of the folder
*
* @param string $dir The path of the directory
* @return mixed
*/
public static function niceSize($dir) {
return f::niceSize(static::size($dir));
}
/**
* Recursively check when the dir and all
* subfolders have been modified for the last time.
*
* @param string $dir The path of the directory
* @param string $format
* @return int
*/
public static function modified($dir, $format = null, $handler = 'date') {
// It's easier to handle this with the Folder class
$object = new Folder($dir);
return $object->modified($format, $handler);
}
/**
* Checks if the directory or any subdirectory has been
* modified after the given timestamp
*
* @param string $dir
* @param int $time
* @return boolean
*/
public static function wasModifiedAfter($dir, $time) {
if(filemtime($dir) > $time) return true;
$content = dir::read($dir);
foreach($content as $item) {
$subdir = $dir . DS . $item;
if(filemtime($subdir) > $time) return true;
if(is_dir($subdir) && dir::wasModifiedAfter($subdir, $time)) return true;
}
return false;
}
/**
* Checks if the dir is writable
*
* @param string $dir
* @return boolean
*/
public static function writable($dir) {
return is_writable($dir);
}
/**
* Checks if the dir is readable
*
* @param string $dir
* @return boolean
*/
public static function readable($dir) {
return is_readable($dir);
}
/**
* Copy a file, or recursively copy a folder and its contents
*
* @param string $dir Source path
* @param string $to Destination path
*/
public static function copy($dir, $to) {
// It's easier to handle this with the Folder class
$object = new Folder($dir);
return $object->copy($to);
}
}

291
kirby/toolkit/lib/email.php Normal file
View file

@ -0,0 +1,291 @@
<?php
/**
* Email
*
* A simple email handling class which supports
* multiple email services. Check out the email subfolder
* for all available services
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Email extends Obj {
const ERROR_INVALID_RECIPIENT = 0;
const ERROR_INVALID_SENDER = 1;
const ERROR_INVALID_REPLY_TO = 2;
const ERROR_INVALID_SUBJECT = 3;
const ERROR_INVALID_BODY = 4;
const ERROR_INVALID_SERVICE = 5;
const ERROR_DISABLED = 6;
public static $defaults = array(
'service' => 'mail',
'options' => array(),
'to' => null,
'from' => null,
'replyTo' => null,
'subject' => null,
'body' => null
);
public static $services = array();
public static $disabled = false;
public $error;
public $service;
public $options;
public $to;
public $from;
public $replyTo;
public $subject;
public $body;
public function __construct($params = array()) {
$options = a::merge(static::$defaults, $params);
parent::__construct($options);
}
public function __set($key, $value) {
$this->$key = $value;
}
/**
* Validates the constructed email
* to make sure it can be sent at all
*/
public function validate() {
if(!v::email($this->extractAddress($this->to))) throw new Error('Invalid recipient', static::ERROR_INVALID_RECIPIENT);
if(!v::email($this->extractAddress($this->from))) throw new Error('Invalid sender', static::ERROR_INVALID_SENDER);
if(!v::email($this->extractAddress($this->replyTo))) throw new Error('Invalid reply address', static::ERROR_INVALID_REPLY_TO);
if(empty($this->subject)) throw new Error('Missing subject', static::ERROR_INVALID_SUBJECT);
if(empty($this->body)) throw new Error('Missing body', static::ERROR_INVALID_BODY);
}
/**
* Public getter for the error exception
*
* @return Exception
*/
public function error() {
return $this->error;
}
/**
* Extracts the email address from an address string
*
* @return string
*/
protected function extractAddress($string) {
if(v::email($string)) return $string;
preg_match('/<(.*?)>/i', $string, $array);
return (empty($array[1])) ? $string : $array[1];
}
/**
* Sends the constructed email
*
* @param array $params Optional way to set values for the email
* @return boolean
*/
public function send($params = null) {
try {
// fail silently if sending emails is disabled
if(static::$disabled) throw new Error('Sending emails is disabled', static::ERROR_DISABLED);
// overwrite already set values
if(is_array($params) && !empty($params)) {
foreach(a::merge($this->toArray(), $params) as $key => $val) {
$this->set($key, $val);
}
}
// reset all errors
$this->error = null;
// default service
if(empty($this->service)) $this->service = 'mail';
// if there's no dedicated reply to address, use the from address
if(empty($this->replyTo)) $this->replyTo = $this->from;
// validate the email
$this->validate();
// check if the email service is available
if(!isset(static::$services[$this->service])) {
throw new Error('The email service is not available: ' . $this->service, static::ERROR_INVALID_SERVICE);
}
// run the service
call(static::$services[$this->service], $this);
// reset the error
$this->error = null;
return true;
} catch(Exception $e) {
$this->error = $e;
return false;
}
}
}
/**
* Default mail driver
*/
email::$services['mail'] = function($email) {
$headers = array(
'From: ' . $email->from,
'Reply-To: ' . $email->replyTo,
'Return-Path: ' . $email->replyTo,
'Message-ID: <' . time() . '-' . $email->from . '>',
'X-Mailer: PHP v' . phpversion(),
'Content-Type: text/plain; charset=utf-8',
'Content-Transfer-Encoding: 8bit',
);
ini_set('sendmail_from', $email->from);
$send = mail($email->to, str::utf8($email->subject), str::utf8($email->body), implode(PHP_EOL, $headers));
ini_restore('sendmail_from');
if(!$send) {
throw new Error('The email could not be sent');
}
};
/**
* Amazon mail driver
*/
email::$services['amazon'] = function($email) {
if(empty($email->options['key'])) throw new Error('Missing Amazon API key');
if(empty($email->options['secret'])) throw new Error('Missing Amazon API secret');
$setup = array(
'Action' => 'SendEmail',
'Destination.ToAddresses.member.1' => $email->to,
'ReplyToAddresses.member.1' => $email->replyTo,
'ReturnPath' => $email->replyTo,
'Source' => $email->from,
'Message.Subject.Data' => $email->subject,
'Message.Body.Text.Data' => $email->body
);
$params = array();
foreach($setup as $key => $value) {
$params[] = $key . '=' . str_replace('%7E', '~', rawurlencode($value));
}
sort($params, SORT_STRING);
$host = a::get($email->options, 'host', 'email.us-east-1.amazonaws.com');
$url = 'https://' . $host . '/';
$date = gmdate('D, d M Y H:i:s e');
$signature = base64_encode(hash_hmac('sha256', $date, $email->options['secret'], true));
$query = implode('&', $params);
$headers = array();
$auth = 'AWS3-HTTPS AWSAccessKeyId=' . $email->options['key'];
$auth .= ',Algorithm=HmacSHA256,Signature=' . $signature;
$headers[] = 'Date: ' . $date;
$headers[] = 'Host: ' . $host;
$headers[] = 'X-Amzn-Authorization: '. $auth;
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$email->response = remote::post($url, array(
'data' => $query,
'headers' => $headers
));
if(!in_array($email->response->code(), array(200, 201, 202, 204))) {
throw new Error('The mail could not be sent!', $email->response->code());
}
};
/**
* Mailgun mail driver
*/
email::$services['mailgun'] = function($email) {
if(empty($email->options['key'])) throw new Error('Missing Mailgun API key');
if(empty($email->options['domain'])) throw new Error('Missing Mailgun API domain');
$url = 'https://api.mailgun.net/v2/' . $email->options['domain'] . '/messages';
$auth = base64_encode('api:' . $email->options['key']);
$headers = array(
'Accept: application/json',
'Authorization: Basic ' . $auth
);
$data = array(
'from' => $email->from,
'to' => $email->to,
'subject' => $email->subject,
'text' => $email->body,
'h:Reply-To' => $email->replyTo,
);
$email->response = remote::post($url, array(
'data' => $data,
'headers' => $headers
));
if($email->response->code() != 200) {
throw new Error('The mail could not be sent!');
}
};
/**
* Postmark mail driver
*/
email::$services['postmark'] = function($email) {
if(empty($email->options['key'])) throw new Error('Invalid Postmark API Key');
// reset the api key if we are in test mode
if(a::get($email->options, 'test')) $email->options['key'] = 'POSTMARK_API_TEST';
// the url for postmarks api
$url = 'https://api.postmarkapp.com/email';
$headers = array(
'Accept: application/json',
'Content-Type: application/json',
'X-Postmark-Server-Token: ' . $email->options['key']
);
$data = array(
'From' => $email->from,
'To' => $email->to,
'ReplyTo' => $email->replyTo,
'Subject' => $email->subject,
'TextBody' => $email->body
);
// fetch the response
$email->response = remote::post($url, array(
'data' => json_encode($data),
'headers' => $headers
));
if($email->response->code() != 200) {
throw new Error('The mail could not be sent');
}
};

124
kirby/toolkit/lib/embed.php Normal file
View file

@ -0,0 +1,124 @@
<?php
/**
* Embed
*
* Simple embedding of stuff like
* flash, youtube videos, vimeo videos or gists
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Embed {
/**
* Embeds a youtube video by passing the Youtube url
*
* @param string $url Youtube url i.e. http://www.youtube.com/watch?v=d9NF2edxy-M
* @param array $attr Additional attributes for the iframe
* @return string
*/
public static function youtube($url, $attr = array()) {
// http://www.youtube.com/embed/d9NF2edxy-M
if(preg_match('!youtube.com\/embed\/([a-z0-9_-]+)!i', $url, $array)) {
$id = $array[1];
// http://www.youtube.com/watch?feature=player_embedded&v=d9NF2edxy-M#!
} elseif(preg_match('!v=([a-z0-9_-]+)!i', $url, $array)) {
$id = $array[1];
// http://youtu.be/d9NF2edxy-M
} elseif(preg_match('!youtu.be\/([a-z0-9_-]+)!i', $url, $array)) {
$id = $array[1];
}
// no id no result!
if(empty($id)) return false;
// default options
if(!empty($attr['options'])) {
$options = '?' . http_build_query($attr['options']);
// options should not propagate to the attr list
unset($attr['options']);
} else {
$options = '';
}
// default attributes
$attr = array_merge(array(
'src' => '//youtube.com/embed/' . $id . $options,
'frameborder' => '0',
'webkitAllowFullScreen' => 'true',
'mozAllowFullScreen' => 'true',
'allowFullScreen' => 'true',
'width' => '100%',
'height' => '100%',
), $attr);
return html::tag('iframe', '', $attr);
}
/**
* Embeds a vimeo video by passing the vimeo url
*
* @param string $url vimeo url i.e. http://vimeo.com/52345557
* @param array $attr Additional attributes for the iframe
* @return string
*/
public static function vimeo($url, $attr = array()) {
// get the uid from the url
if(preg_match('!vimeo.com\/([0-9]+)!i', $url, $array)) {
$id = $array[1];
} else {
$id = null;
}
// no id no result!
if(empty($id)) return false;
// default options
if(!empty($attr['options'])) {
$options = '?' . http_build_query($attr['options']);
// options should not propagate to the attr list
unset($attr['options']);
} else {
$options = '';
}
// default attributes
$attr = array_merge(array(
'src' => '//player.vimeo.com/video/' . $id . $options,
'frameborder' => '0',
'webkitAllowFullScreen' => 'true',
'mozAllowFullScreen' => 'true',
'allowFullScreen' => 'true',
'width' => '100%',
'height' => '100%',
), $attr);
return html::tag('iframe', '', $attr);
}
/**
* Embeds a github gist
*
* @param string $url Gist url: i.e. https://gist.github.com/2924148
* @param string $file The name of a particular file from the gist, which should displayed only.
* @return string
*/
public static function gist($url, $file = null) {
// url for the script file
$url = $url . '.js' . r(!is_null($file), '?file=' . $file);
// load the gist
return html::tag('script', '', array('src' => $url));
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* Error
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Error extends Exception {
public function message() {
return $this->message;
}
public function code() {
return $this->code;
}
public function __toString() {
return $this->message;
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* Error Reporting
*
* Changes values of the PHP error reporting
*
* @package Kirby Toolkit
* @author Lukas Bestle <lukas@getkirby.com>
* @link http://getkirby.com
* @copyright Lukas Bestle
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class ErrorReporting {
/**
* Returns the current raw value
*
* @return int The current value
*/
public static function get() {
return error_reporting();
}
/**
* Sets a new raw error reporting value
*
* @param int $level The new level to set
* @return int The new value
*/
public static function set($level) {
if(static::get() !== error_reporting($level)) {
throw new Exception('Internal error: error_reporting() did not return the old value.');
}
return static::get();
}
/**
* Check if the current error reporting includes an error level
*
* @param mixed $level The level to check for
* @param int $current A custom current level
* @return boolean
*/
public static function includes($level, $current = null) {
// also allow strings
if(is_string($level)) {
if(defined($level)) {
$level = constant($level);
} else if(defined('E_' . strtoupper($level))) {
$level = constant('E_' . strtoupper($level));
} else {
throw new Exception('The level "' . $level . '" does not exist.');
}
}
$value = ($current)? $current : static::get();
return bitmask::includes($level, $value);
}
/**
* Adds a level to the current error reporting
*
* @param int $level The level to add
* @return boolean
*/
public static function add($level) {
// check if it is already added
if(static::includes($level)) return false;
$old = static::get();
$newExpected = bitmask::add($level, $old);
$newActual = static::set($newExpected);
return $newActual === $newExpected;
}
/**
* Removes a level from the current error reporting
*
* @param int $level The level to remove
* @return boolean
*/
public static function remove($level) {
// check if it is already removed
if(!static::includes($level)) return false;
$old = static::get();
$newExpected = bitmask::remove($level, $old);
$newActual = static::set($newExpected);
return $newActual === $newExpected;
}
}

View file

@ -0,0 +1,266 @@
<?php
/**
* Escape
*
* Class to handle context specific output escaping per OWASP recommendations.
*
* Most of this class is based on methods from Zend\Escaper, but modified for Kirby.
* Copyrighted (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
* under the New BSD License (http://framework.zend.com/license/new-bsd).
*
* @link https://github.com/zendframework/zf2/blob/master/library/Zend/Escaper/Escaper.php
* @link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
*
* @package Kirby Toolkit
* @author Ezra Verheijen <ezra.verheijen@gmail.com>
* @link https://github.com/ezraverheijen/escape
* @copyright Ezra Verheijen
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Escape {
/**
* Check if a string needs to be escaped or not
*
* @param string $string
* @return boolean
*/
public static function noNeedToEscape($string) {
return $string === '' || ctype_digit($string);
}
/**
* Convert a character from UTF-8 to UTF-16BE
*
* @param string $char
* @return string
*/
public static function convertEncoding($char) {
return str::convert($char, 'UTF-16BE', 'UTF-8');
}
/**
* Check if a character is undefined in HTML
*
* @param string $char
* @return boolean
*/
public static function charIsUndefined($char) {
$ascii = ord($char);
return ($ascii <= 0x1f && $char != "\t" && $char != "\n" && $char != "\r")
|| ($ascii >= 0x7f && $ascii <= 0x9f);
}
/**
* Escape HTML element content
*
* This can be used to put untrusted data directly into the HTML body somewhere.
* This includes inside normal tags like div, p, b, td, etc.
*
* Escapes &, <, >, ", and ' with HTML entity encoding to prevent switching
* into any execution context, such as script, style, or event handlers.
*
* <body>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</body>
* <div>...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...</div>
*
* @uses ENT_SUBSTITUE if available (PHP >= 5.4)
*
* @param string $string
* @return string
*/
public static function html($string) {
$flags = ENT_QUOTES;
if(defined('ENT_SUBSTITUTE')) {
$flags |= ENT_SUBSTITUTE;
}
return htmlspecialchars($string, $flags, 'UTF-8');
}
/**
* Escape XML element content
*
* Removes offending characters that could be wrongfully interpreted as XML markup.
*
* The following characters are reserved in XML and will be replaced with their
* corresponding XML entities:
*
* ' is replaced with &apos;
* " is replaced with &quot;
* & is replaced with &amp;
* < is replaced with &lt;
* > is replaced with &gt;
*
* @uses ENT_XML1 if available (PHP >= 5.4)
*
* @param string $string
* @return string
*/
public static function xml($string) {
if (defined('ENT_XML1')) {
return htmlspecialchars($string, ENT_QUOTES | ENT_XML1, 'UTF-8');
} else {
return str_replace('&#039;', '&apos;', htmlspecialchars($string, ENT_QUOTES, 'UTF-8'));
}
}
/**
* Escape common HTML attributes data
*
* This can be used to put untrusted data into typical attribute values
* like width, name, value, etc.
*
* This should not be used for complex attributes like href, src, style,
* or any of the event handlers like onmouseover.
* Use esc($string, 'js') for event handler attributes, esc($string, 'url')
* for src attributes and esc($string, 'css') for style attributes.
*
* <div attr=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...>content</div>
* <div attr='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'>content</div>
* <div attr="...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">content</div>
*
* @param string $string
* @param string $strict Whether to escape characters like [space] % * + , - / ; < = > ^ and |
* which is necessary in case of unquoted HTML attributes.
* @return string
*/
public static function attr($string, $strict = false) {
if(static::noNeedToEscape($string)) return $string;
if($strict !== true) {
return preg_replace_callback('/[^a-z0-9,\.\-_]/iSu', 'static::escapeAttrChar', $string);
}
return static::html($string);
}
/**
* Escape JavaScript data values
*
* This can be used to put dynamically generated JavaScript code
* into both script blocks and event-handler attributes.
*
* <script>alert('...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...')</script>
* <script>x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'</script>
* <div onmouseover="x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'"</div>
*
* @param string $string
* @return string
*/
public static function js($string) {
if(static::noNeedToEscape($string)) return $string;
return preg_replace_callback('/[^a-z0-9,\._]/iSu', 'static::escapeJSChar', $string);
}
/**
* Escape HTML style property values
*
* This can be used to put untrusted data into a stylesheet or a style tag.
*
* Stay away from putting untrusted data into complex properties like url,
* behavior, and custom (-moz-binding). You should also not put untrusted data
* into IEs expression property value which allows JavaScript.
*
* <style>selector { property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...; } </style>
* <style>selector { property : "...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE..."; } </style>
* <span style="property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">text</span>
*
* @param string $string
* @return string
*/
public static function css($string) {
if(static::noNeedToEscape($string)) return $string;
return preg_replace_callback('/[^a-z0-9]/iSu', 'static::escapeCSSChar', $string);
}
/**
* Escape URL parameter values
*
* This can be used to put untrusted data into HTTP GET parameter values.
* This should not be used to escape an entire URI.
*
* <a href="http://www.somesite.com?test=...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">link</a>
*
* @param string $string
* @return string
*/
public static function url($string) {
return rawurlencode($string);
}
/**
* Escape character for HTML attribute
*
* Callback function for preg_replace_callback() that applies HTML attribute
* escaping to all matches.
*
* @param array $matches
* @return mixed Unicode replacement if character is undefined in HTML,
* named HTML entity if available (only those that XML supports),
* upper hex entity if a named entity does not exist or
* entity with the &#xHH; format if ASCII value is less than 256.
*/
protected static function escapeAttrChar($matches) {
$char = $matches[0];
if(static::charIsUndefined($char)) {
return '&#xFFFD;';
}
$dec = hexdec(bin2hex($char));
$namedEntities = array(
34 => '&quot;', // "
38 => '&amp;', // &
60 => '&lt;', // <
62 => '&gt;' // >
);
if(isset($namedEntities[$dec])) {
return $namedEntities[$dec];
}
if($dec > 255) {
return sprintf('&#x%04X;', $dec);
}
return sprintf('&#x%02X;', $dec);
}
/**
* Escape character for JavaScript
*
* Callback function for preg_replace_callback() that applies Javascript
* escaping to all matches.
*
* @param array $matches
* @return string
*/
protected static function escapeJSChar($matches) {
$char = $matches[0];
if(str::length($char) == 1) {
return sprintf('\\x%02X', ord($char));
}
$char = static::convertEncoding($char);
return sprintf('\\u%04s', str::upper(bin2hex($char)));
}
/**
* Escape character for CSS
*
* Callback function for preg_replace_callback() that applies CSS
* escaping to all matches.
*
* @param array $matches
* @return string
*/
protected static function escapeCSSChar($matches) {
$char = $matches[0];
if(str::length($char) == 1) {
$ord = ord($char);
} else {
$char = static::convertEncoding($char);
$ord = hexdec(bin2hex($char));
}
return sprintf('\\%X ', $ord);
}
}

221
kirby/toolkit/lib/exif.php Normal file
View file

@ -0,0 +1,221 @@
<?php
/**
* Exif
*
* Reads exif data from a given media object
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Exif {
// the parent media object
protected $media = null;
// the raw exif array
protected $data = null;
// the camera object with model and make
protected $camera = null;
// the location object
protected $location = null;
// the timestamp
protected $timestamp = null;
// the exposure value
protected $exposure = null;
// the aperture value
protected $aperture = null;
// iso value
protected $iso = null;
// focal length
protected $focalLength = null;
// color or black/white
protected $isColor = null;
/**
* Constructor
*
* @param Media $media
*/
public function __construct(Media $media) {
$this->media = $media;
$this->parse();
}
/**
* Returns the raw data array from the parser
*
* @return array
*/
public function data() {
return $this->data;
}
/**
* Returns the Camera object
*
* @return object KirbyExifCamera
*/
public function camera() {
if(!is_null($this->camera)) return $this->camera;
// check for valid exif data
if(!is_array($this->data)) return null;
// initialize and return it
return $this->camera = new Exif\Camera($this->data);
}
/**
* Returns the location object
*
* @return object ExifLocation
*/
public function location() {
if(!is_null($this->location)) return $this->location;
// check for valid exif data
if(!is_array($this->data)) return null;
// initialize and return it
return $this->location = new Exif\Location($this->data);
}
/**
* Returns the timestamp
*
* @return string
*/
public function timestamp() {
return $this->timestamp;
}
/**
* Returns the exposure
*
* @return string
*/
public function exposure() {
return $this->exposure;
}
/**
* Returns the aperture
*
* @return string
*/
public function aperture() {
return $this->aperture;
}
/**
* Returns the iso value
*
* @return int
*/
public function iso() {
return $this->iso;
}
/**
* Checks if this is a color picture
*
* @return boolean
*/
public function isColor() {
return $this->isColor;
}
/**
* Checks if this is a bw picture
*
* @return boolean
*/
public function isBW() {
return !$this->isColor;
}
/**
* Returns the focal length
*
* @return string
*/
public function focalLength() {
return $this->focalLength;
}
/**
* Pareses and stores all relevant exif data
*/
protected function parse() {
// read the exif data of the media object if possible
$this->data = @read_exif_data($this->media->root());
// stop on invalid exif data
if(!is_array($this->data)) return false;
// store the timestamp when the picture has been taken
if(isset($this->data['DateTimeOriginal'])) {
$this->timestamp = strtotime($this->data['DateTimeOriginal']);
} else {
$this->timestamp = a::get($this->data, 'FileDateTime', $this->media->modified());
}
// exposure
$this->exposure = a::get($this->data, 'ExposureTime');
// iso
$this->iso = a::get($this->data, 'ISOSpeedRatings');
// focal length
if(isset($this->data['FocalLength'])) {
$this->focalLength = $this->data['FocalLength'];
} else if(isset($this->data['FocalLengthIn35mmFilm'])) {
$this->focalLength = $this->data['FocalLengthIn35mmFilm'];
}
// aperture
$this->aperture = @$this->data['COMPUTED']['ApertureFNumber'];
// color or bw
$this->isColor = @$this->data['COMPUTED']['IsColor'] == true;
}
/**
* Converts the object into a nicely readable array
*
* @return array
*/
public function toArray() {
return array(
'camera' => $this->camera()->toArray(),
'location' => $this->location()->toArray(),
'timestamp' => $this->timestamp(),
'exposure' => $this->exposure(),
'aperture' => $this->aperture(),
'iso' => $this->iso(),
'focalLength' => $this->focalLength(),
'isColor' => $this->isColor()
);
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Exif;
/**
* Small class which hold info about the camera
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Camera {
protected $make;
protected $model;
/**
* Constructor
*
* @param array $exif
*/
public function __construct($exif) {
$this->make = @$exif['Make'];
$this->model = @$exif['Model'];
}
/**
* Returns the make of the camera
*
* @return string
*/
public function make() {
return $this->make;
}
/**
* Returns the camera model
*
* @return string
*/
public function model() {
return $this->model;
}
/**
* Converts the object into a nicely readable array
*
* @return array
*/
public function toArray() {
return array(
'make' => $this->make,
'model' => $this->model
);
}
/**
* Returns the full make + model name
*
* @return string
*/
public function __toString() {
return trim($this->make . ' ' . $this->model);
}
}

Some files were not shown because too many files have changed in this diff Show more