1443 lines
35 KiB
PHP
1443 lines
35 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Page
|
|
*
|
|
* This class represents a single page of
|
|
* a Kirby CMS powered website.
|
|
* A page is derived from a subfolder of the content folder.
|
|
* A page can have unlimited subpages (children)
|
|
* and attached media files. Its custom data is fetched from
|
|
* a text file with separated fields.
|
|
*
|
|
* @package Kirby CMS
|
|
* @author Bastian Allgeier <bastian@getkirby.com>
|
|
* @link http://getkirby.com
|
|
* @copyright Bastian Allgeier
|
|
* @license http://getkirby.com/license
|
|
*/
|
|
abstract class PageAbstract {
|
|
|
|
static public $models = array();
|
|
static public $methods = array();
|
|
|
|
public $kirby;
|
|
public $site;
|
|
public $parent;
|
|
|
|
protected $id;
|
|
protected $dirname;
|
|
protected $root;
|
|
protected $depth;
|
|
protected $uid;
|
|
protected $num;
|
|
protected $uri;
|
|
protected $cache = array();
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param Page $parent
|
|
* @param string $dirname
|
|
*/
|
|
public function __construct($parent, $dirname) {
|
|
|
|
$this->kirby = $parent->kirby;
|
|
$this->site = $parent->site;
|
|
$this->parent = $parent;
|
|
$this->dirname = $dirname;
|
|
$this->root = $parent->root() . DS . $dirname;
|
|
$this->depth = $parent->depth() + 1;
|
|
|
|
// extract the uid and num of the directory
|
|
if(preg_match('/^([0-9]+)[\-](.*)$/', $this->dirname, $match)) {
|
|
$this->uid = $match[2];
|
|
$this->num = $match[1];
|
|
} else {
|
|
$this->num = null;
|
|
$this->uid = $this->dirname;
|
|
}
|
|
|
|
// assign the uid
|
|
$this->id = $this->uri = ltrim($parent->id() . '/' . $this->uid, '/');
|
|
|
|
}
|
|
|
|
/**
|
|
* Cleans the temporary page cache and
|
|
* the cache of all parent pages
|
|
*/
|
|
public function reset() {
|
|
$this->cache = array();
|
|
$this->parent()->reset();
|
|
}
|
|
|
|
/**
|
|
* Mark the page as modified
|
|
*/
|
|
public function touch() {
|
|
return touch($this->root());
|
|
}
|
|
|
|
/**
|
|
* Returns the kirby object
|
|
*
|
|
* @return Kirby
|
|
*/
|
|
public function kirby() {
|
|
return $this->kirby;
|
|
}
|
|
|
|
/**
|
|
* Returns the site object
|
|
*
|
|
* @return Site
|
|
*/
|
|
public function site() {
|
|
return $this->site;
|
|
}
|
|
|
|
/**
|
|
* Returns the parent page element
|
|
*
|
|
* @return Page
|
|
*/
|
|
public function parent() {
|
|
return $this->parent;
|
|
}
|
|
|
|
/**
|
|
* Returns all parents
|
|
*
|
|
* @return Children
|
|
*/
|
|
public function parents() {
|
|
|
|
if(isset($this->cache['parents'])) return $this->cache['parents'];
|
|
|
|
$children = new Children($this->site);
|
|
$parents = array();
|
|
$next = $this->parent();
|
|
|
|
while($next and $next->depth() > 0) {
|
|
$children->data[$next->id()] = $next;
|
|
$next = $next->parent();
|
|
}
|
|
|
|
return $this->cache['parents'] = $children;
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the full root of the page folder
|
|
*
|
|
* @return string
|
|
*/
|
|
public function root() {
|
|
return $this->root;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the directory
|
|
*
|
|
* @return string
|
|
*/
|
|
public function dirname() {
|
|
return $this->dirname;
|
|
}
|
|
|
|
/**
|
|
* Returns the relative URL for the directory.
|
|
* Relative to the base content directory
|
|
*
|
|
* @return string
|
|
*/
|
|
public function diruri() {
|
|
if(isset($this->cache['diruri'])) return $this->cache['diruri'];
|
|
return $this->cache['diruri'] = ltrim($this->parent()->diruri() . '/' . $this->dirname(), '/');
|
|
}
|
|
|
|
/**
|
|
* Returns the full url for the page
|
|
*
|
|
* @return string
|
|
*/
|
|
public function url() {
|
|
|
|
if(isset($this->cache['url'])) return $this->cache['url'];
|
|
|
|
// Kirby is trying to remove the home folder name from the url
|
|
if($this->isHomePage()) {
|
|
// return the base url
|
|
return $this->cache['url'] = $this->site->url();
|
|
} else if($this->parent->isHomePage()) {
|
|
return $this->cache['url'] = $this->site->url() . '/' . $this->parent->uid . '/' . $this->uid;
|
|
} else {
|
|
$purl = $this->parent->url();
|
|
return $this->cache['url'] = $purl == '/' ? '/' . $this->uid : $this->parent->url() . '/' . $this->uid;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the full URL for the content folder
|
|
*
|
|
* @return string
|
|
*/
|
|
public function contentUrl() {
|
|
return $this->kirby()->urls()->content() . '/' . $this->diruri();
|
|
}
|
|
|
|
/**
|
|
* Builds and returns the short url for the current page
|
|
*
|
|
* @return string
|
|
*/
|
|
public function tinyurl() {
|
|
if(!$this->kirby->options['tinyurl.enabled']) {
|
|
return $this->url();
|
|
} else {
|
|
return url($this->kirby->options['tinyurl.folder'] . '/' . $this->hash());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a number indicating how deep the page
|
|
* is nested within the content folder
|
|
*
|
|
* @return int
|
|
*/
|
|
public function depth() {
|
|
return $this->depth;
|
|
}
|
|
|
|
/**
|
|
* Returns the uri for the page
|
|
* which is being used for the url later
|
|
*
|
|
* @return string
|
|
*/
|
|
public function uri() {
|
|
return $this->uri;
|
|
}
|
|
|
|
/**
|
|
* Returns the id, which is going to be used for
|
|
* Collection keys and things like that
|
|
*
|
|
* @return string
|
|
*/
|
|
public function id() {
|
|
return $this->id;
|
|
}
|
|
|
|
/**
|
|
* Checks if the page can be cached
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isCachable() {
|
|
|
|
// The error page should not be cached
|
|
if($this->isErrorPage()) {
|
|
return false;
|
|
}
|
|
|
|
foreach($this->kirby->option('cache.ignore') as $pattern) {
|
|
if(fnmatch($pattern, $this->uri()) === true) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the page uid, which is the
|
|
* folder name without the sorting number
|
|
*
|
|
* @return string
|
|
*/
|
|
public function uid() {
|
|
return $this->uid;
|
|
}
|
|
|
|
/**
|
|
* Alternative for $this->uid()
|
|
*
|
|
* @return string
|
|
*/
|
|
public function slug() {
|
|
return $this->uid;
|
|
}
|
|
|
|
/**
|
|
* Returns the sorting number if it exists
|
|
*
|
|
* @return string
|
|
*/
|
|
public function num() {
|
|
return $this->num;
|
|
}
|
|
|
|
/**
|
|
* Reads the directory and returns an inventory array
|
|
*
|
|
* @return array
|
|
*/
|
|
public function inventory() {
|
|
|
|
if(isset($this->cache['inventory'])) return $this->cache['inventory'];
|
|
|
|
// get all items within the directory
|
|
$ignore = array('.', '..', '.DS_Store', '.git', '.svn', 'Thumb.db');
|
|
$items = array_diff(scandir($this->root), array_merge($ignore, (array)$this->kirby->option('content.file.ignore')));
|
|
|
|
// create the inventory
|
|
$this->cache['inventory'] = array(
|
|
'children' => array(),
|
|
'content' => array(),
|
|
'meta' => array(),
|
|
'thumbs' => array(),
|
|
'files' => array(),
|
|
);
|
|
|
|
// normalize the filename if possible
|
|
if($this->kirby->option('content.file.normalize') && class_exists('Normalizer')) {
|
|
$items = array_map('Normalizer::normalize', $items);
|
|
}
|
|
|
|
foreach($items as $item) {
|
|
|
|
// skip any invisible files and folders
|
|
if(substr($item, 0, 1) === '.') continue;
|
|
|
|
$root = $this->root . DS . $item;
|
|
|
|
if(is_dir($root)) {
|
|
$this->cache['inventory']['children'][] = $item;
|
|
} else if(pathinfo($item, PATHINFO_EXTENSION) == $this->kirby->options['content.file.extension']) {
|
|
$this->cache['inventory']['content'][] = $item;
|
|
} else if(strpos($item, '.thumb.') !== false and preg_match('!\.thumb\.(jpg|jpeg|png|gif)$!i', $item)) {
|
|
// get the filename of the original image and use it as the array key
|
|
$image = str_replace('.thumb', '', $item);
|
|
// this makes it easier to find the corresponding image later
|
|
$this->cache['inventory']['thumbs'][$image] = $item;
|
|
} else {
|
|
$this->cache['inventory']['files'][] = $item;
|
|
}
|
|
|
|
}
|
|
|
|
foreach($this->cache['inventory']['thumbs'] as $key => $thumb) {
|
|
// remove invalid thumbs by looking for a matching image file and
|
|
if(!in_array($key, $this->cache['inventory']['files'])) {
|
|
$this->cache['inventory']['files'][] = $thumb;
|
|
unset($this->cache['inventory']['thumbs'][$key]);
|
|
}
|
|
}
|
|
|
|
foreach($this->cache['inventory']['content'] as $key => $content) {
|
|
$file = pathinfo($content, PATHINFO_FILENAME);
|
|
if(in_array($file, $this->cache['inventory']['files'])) {
|
|
$this->cache['inventory']['meta'][$file] = $content;
|
|
unset($this->cache['inventory']['content'][$key]);
|
|
}
|
|
}
|
|
|
|
// sort the children
|
|
natsort($this->cache['inventory']['children']);
|
|
|
|
return $this->cache['inventory'];
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns all children for this page
|
|
*
|
|
* @return Children
|
|
*/
|
|
public function children() {
|
|
|
|
if(isset($this->cache['children'])) return $this->cache['children'];
|
|
|
|
$this->cache['children'] = new Children($this);
|
|
|
|
$inventory = $this->inventory();
|
|
|
|
// with page models
|
|
if(!empty(static::$models)) {
|
|
foreach($inventory['children'] as $dirname) {
|
|
$child = new Page($this, $dirname);
|
|
// let's create a model if one is defined
|
|
if(isset(static::$models[$child->intendedTemplate()])) {
|
|
$model = static::$models[$child->intendedTemplate()];
|
|
$child = new $model($this, $dirname);
|
|
}
|
|
$this->cache['children']->data[$child->id()] = $child;
|
|
}
|
|
// without page models
|
|
} else {
|
|
foreach($inventory['children'] as $dirname) {
|
|
$child = new Page($this, $dirname);
|
|
$this->cache['children']->data[$child->id()] = $child;
|
|
}
|
|
}
|
|
|
|
return $this->cache['children'];
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks if the page has children
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasChildren() {
|
|
return $this->children()->count();
|
|
}
|
|
|
|
/**
|
|
* Checks if the page has visible children
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasVisibleChildren() {
|
|
return $this->children()->visible()->count();
|
|
}
|
|
|
|
/**
|
|
* Checks if the page has invisible children
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasInvisibleChildren() {
|
|
return $this->children()->invisible()->count();
|
|
}
|
|
|
|
/**
|
|
* Returns the grand children of this page
|
|
*
|
|
* @return Children
|
|
*/
|
|
public function grandChildren() {
|
|
return $this->children()->children();
|
|
}
|
|
|
|
/**
|
|
* Returns the siblings for this page, not including this page
|
|
*
|
|
* @param boolean $self
|
|
* @return Children
|
|
*/
|
|
public function siblings($self = true) {
|
|
return $self ? $this->parent->children() : $this->parent->children()->not($this);
|
|
}
|
|
|
|
/**
|
|
* Internal method to return the next page
|
|
*
|
|
* @param object $siblings Children A collection of siblings to search in
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
protected function _next(Children $siblings, $sort = array(), $visibility = false) {
|
|
|
|
if($sort) $siblings = call(array($siblings, 'sortBy'), $sort);
|
|
$index = $siblings->indexOf($this);
|
|
if($index === false) return null;
|
|
if($visibility) {
|
|
$siblings = $siblings->offset($index+1);
|
|
$siblings = $siblings->{$visibility}();
|
|
return $siblings->first();
|
|
} else {
|
|
return $siblings->nth($index + 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal method to return the previous page
|
|
*
|
|
* @param object $siblings Children A collection of siblings to search in
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
protected function _prev(Children $siblings, $sort = array(), $visibility = false) {
|
|
if($sort) $siblings = call(array($siblings, 'sortBy'), $sort);
|
|
$index = $siblings->indexOf($this);
|
|
if($index === false or $index === 0) return null;
|
|
if($visibility) {
|
|
$siblings = $siblings->limit($index);
|
|
$siblings = $siblings->{$visibility}();
|
|
return $siblings->last();
|
|
} else {
|
|
return $siblings->nth($index - 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the next page element
|
|
*
|
|
* @return Page
|
|
*/
|
|
public function next() {
|
|
return $this->_next($this->parent->children(), func_get_args());
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a next page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasNext() {
|
|
return call(array($this, 'next'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Returns the next visible page in the current collection if available
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
public function nextVisible() {
|
|
if(!$this->parent) {
|
|
return null;
|
|
} else {
|
|
return $this->_next($this->parent->children(), func_get_args(), 'visible');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a next visible page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasNextVisible() {
|
|
return call(array($this, 'nextVisible'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Returns the next invisible page in the current collection if available
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
public function nextInvisible() {
|
|
if(!$this->parent) {
|
|
return null;
|
|
} else {
|
|
return $this->_next($this->parent->children(), func_get_args(), 'invisible');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a next invisible page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasNextInvisible() {
|
|
return call(array($this, 'nextInvisible'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Returns the previous page element
|
|
*
|
|
* @return Page
|
|
*/
|
|
public function prev() {
|
|
return $this->_prev($this->parent->children(), func_get_args());
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a previous page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasPrev() {
|
|
return call(array($this, 'prev'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Returns the previous visible page in the current collection if available
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
public function prevVisible() {
|
|
if(!$this->parent) {
|
|
return null;
|
|
} else {
|
|
return $this->_prev($this->parent->children(), func_get_args(), 'visible');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a previous visible page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasPrevVisible() {
|
|
return call(array($this, 'prevVisible'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Returns the previous invisible page in the current collection if available
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return mixed Page or null
|
|
*/
|
|
public function prevInvisible() {
|
|
if(!$this->parent) {
|
|
return null;
|
|
} else {
|
|
return $this->_prev($this->parent->children(), func_get_args(), 'invisible');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a previous invisible page
|
|
*
|
|
* @param string $sort An optional sort field for the siblings
|
|
* @param string $direction An optional sort direction
|
|
* @return boolean
|
|
*/
|
|
public function hasPrevInvisible() {
|
|
return call(array($this, 'prevInvisible'), func_get_args()) != null;
|
|
}
|
|
|
|
/**
|
|
* Find any child or a set of children of this page
|
|
*
|
|
* @return Page | Children
|
|
*/
|
|
public function find() {
|
|
return call_user_func_array(array($this->children(), 'find'), func_get_args());
|
|
}
|
|
|
|
/**
|
|
* Find any file or a set of files for this page
|
|
*
|
|
* @return File | Files
|
|
*/
|
|
public function file() {
|
|
$args = func_get_args();
|
|
if(empty($args)) return $this->files()->first();
|
|
return call_user_func_array(array($this->files(), 'find'), $args);
|
|
}
|
|
|
|
// file stuff
|
|
|
|
/**
|
|
* Returns all files for this page
|
|
*
|
|
* @return Files
|
|
*/
|
|
public function files() {
|
|
if(isset($this->cache['files'])) return $this->cache['files'];
|
|
return $this->cache['files'] = new Files($this);
|
|
}
|
|
|
|
/**
|
|
* Checks if this page has attached files
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasFiles() {
|
|
return $this->files()->count();
|
|
}
|
|
|
|
// File 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'); }
|
|
|
|
// File checkers
|
|
public function hasImages() { return $this->images()->count(); }
|
|
public function hasVideos() { return $this->videos()->count(); }
|
|
public function hasDocuments() { return $this->documents()->count(); }
|
|
public function hasAudio() { return $this->audio()->count(); }
|
|
public function hasCode() { return $this->code()->count(); }
|
|
public function hasArchives() { return $this->archives()->count(); }
|
|
|
|
/**
|
|
* Returns a single image
|
|
*
|
|
* @return File
|
|
*/
|
|
public function image($filename = null) {
|
|
if(is_null($filename)) return $this->images()->first();
|
|
return $this->images()->find($filename);
|
|
}
|
|
|
|
/**
|
|
* Returns a single video
|
|
*
|
|
* @return File
|
|
*/
|
|
public function video($filename = null) {
|
|
if(is_null($filename)) return $this->videos()->first();
|
|
return $this->videos()->find($filename);
|
|
}
|
|
|
|
/**
|
|
* Returns a single document
|
|
*
|
|
* @return File
|
|
*/
|
|
public function document($filename = null) {
|
|
if(is_null($filename)) return $this->documents()->first();
|
|
return $this->documents()->find($filename);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the content object for this page
|
|
*
|
|
* @return Content
|
|
*/
|
|
public function content() {
|
|
|
|
if(isset($this->cache['content'])) {
|
|
return $this->cache['content'];
|
|
} else {
|
|
$inventory = $this->inventory();
|
|
return $this->cache['content'] = new Content($this, $this->root() . DS . array_shift($inventory['content']));
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the title for this page and
|
|
* falls back to the uid if no title exists
|
|
*
|
|
* @return Field
|
|
*/
|
|
public function title() {
|
|
$title = $this->content()->get('title');
|
|
if($title != '') {
|
|
return $title;
|
|
} else {
|
|
$title->value = $this->uid();
|
|
return $title;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get formatted date fields
|
|
*
|
|
* @param string $format
|
|
* @param string $field
|
|
* @return mixed
|
|
*/
|
|
public function date($format = null, $field = 'date') {
|
|
|
|
if($timestamp = strtotime($this->content()->$field())) {
|
|
if(is_null($format)) {
|
|
return $timestamp;
|
|
} else {
|
|
return $this->kirby->options['date.handler']($format, $timestamp);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns a unique hashed version of the uri,
|
|
* which is used for the tinyurl for example
|
|
*
|
|
* @return string
|
|
*/
|
|
public function hash() {
|
|
if(isset($this->cache['hash'])) return $this->cache['hash'];
|
|
|
|
// add a unique hash
|
|
$checksum = sprintf('%u', crc32($this->uri()));
|
|
return $this->cache['hash'] = base_convert($checksum, 10, 36);
|
|
}
|
|
|
|
/**
|
|
* Magic getter for all content fields
|
|
*
|
|
* @return Field
|
|
*/
|
|
public function __call($key, $arguments = null) {
|
|
if(isset($this->$key)) {
|
|
return $this->$key;
|
|
} else if(isset(static::$methods[$key])) {
|
|
if(!$arguments) $arguments = array();
|
|
array_unshift($arguments, clone $this);
|
|
return call(static::$methods[$key], $arguments);
|
|
} else {
|
|
return $this->content()->get($key, $arguments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alternative for $this->equals()
|
|
*/
|
|
public function is(Page $page) {
|
|
return $this->id() == $page->id();
|
|
}
|
|
|
|
/**
|
|
* Alternative for $this->is()
|
|
*/
|
|
public function equals(Page $page) {
|
|
return $this->is($page);
|
|
}
|
|
|
|
/**
|
|
* Checks if this page object is the main site
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isSite() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if this is the active page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isActive() {
|
|
return $this->site->page()->is($this);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is open
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isOpen() {
|
|
if($this->isActive()) return true;
|
|
return $this->site->page()->parents()->has($this);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is visible
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isVisible() {
|
|
return !is_null($this->num);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is invisible
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isInvisible() {
|
|
return !$this->isVisible();
|
|
}
|
|
|
|
/**
|
|
* Checks if this page is the home page
|
|
* You can define the uri of the homepage in your config
|
|
* file with the home option. By default it's assumed
|
|
* that the homepage folder has the name "home"
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isHomePage() {
|
|
return $this->uri === $this->kirby->options['home'];
|
|
}
|
|
|
|
/**
|
|
* Checks if this page is the error page
|
|
* You can define the uri of the error page in your config
|
|
* file with the error option. By default it's assumed
|
|
* that the error page folder has the name "error"
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isErrorPage() {
|
|
return $this->uri === $this->kirby->options['error'];
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is a child of the given page
|
|
*
|
|
* @param object Page the page object to check
|
|
* @return boolean
|
|
*/
|
|
public function isChildOf(Page $page) {
|
|
return $this->is($page) ? false : $this->parent->is($page);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is the parent of the given page
|
|
*
|
|
* @param object Page the page object to check
|
|
* @return boolean
|
|
*/
|
|
public function isParentOf(Page $page) {
|
|
return $this->is($page) ? false : $page->parent->is($this);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is a descendant of the given page
|
|
*
|
|
* @param object Page the page object to check
|
|
* @return boolean
|
|
*/
|
|
public function isDescendantOf(Page $page) {
|
|
return $this->is($page) ? false : $this->parents()->has($page);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is a descendant of the currently active page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isDescendantOfActive() {
|
|
return $this->isDescendantOf($this->site->page());
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is an ancestor of the given page
|
|
*
|
|
* @param object Page the page object to check
|
|
* @return boolean
|
|
*/
|
|
public function isAncestorOf($page) {
|
|
return $page->isDescendantOf($this);
|
|
}
|
|
|
|
/**
|
|
* Checks if the page or any of its files are writable
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isWritable() {
|
|
|
|
$folder = new Folder($this->root());
|
|
|
|
if(!$folder->isWritable()) return false;
|
|
|
|
foreach($folder->files() as $f) {
|
|
if(!$f->isWritable()) return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamp when the page
|
|
* has been modified
|
|
*
|
|
* @return int
|
|
*/
|
|
public function modified($format = null, $handler = null) {
|
|
return f::modified($this->root, $format, $handler ? $handler : $this->kirby->options['date.handler']);
|
|
}
|
|
|
|
/**
|
|
* Returns the index starting from this page
|
|
*
|
|
* @return Children
|
|
*/
|
|
public function index() {
|
|
return $this->children()->index();
|
|
}
|
|
|
|
/**
|
|
* Search in subpages and all descendants of this page
|
|
*
|
|
* @param string $query
|
|
* @param array $params
|
|
* @return Children
|
|
*/
|
|
public function search($query, $params = array()) {
|
|
return $this->children()->index()->search($query, $params);
|
|
}
|
|
|
|
// template stuff
|
|
|
|
/**
|
|
* Returns the name of the used template
|
|
* The name of the template is defined by the name
|
|
* of the content text file.
|
|
*
|
|
* i.e. text file: project.txt / template name: project
|
|
*
|
|
* This method returns the name of the default template
|
|
* if there's no template with such a name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function template() {
|
|
|
|
// check for a cached template name
|
|
if(isset($this->cache['template'])) return $this->cache['template'];
|
|
|
|
// get the template name
|
|
$templateName = $this->intendedTemplate();
|
|
|
|
if($this->kirby->registry->get('template', $templateName)) {
|
|
return $this->cache['template'] = $templateName;
|
|
} else {
|
|
return $this->cache['template'] = 'default';
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the full path to the used template file
|
|
*
|
|
* @return string
|
|
*/
|
|
public function templateFile() {
|
|
if($template = $this->kirby->registry->get('template', $this->intendedTemplate())) {
|
|
return $template;
|
|
} else {
|
|
return $this->kirby->registry->get('template', 'default');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Additional data, which will be passed to the template
|
|
*
|
|
* @return array
|
|
*/
|
|
public function templateData() {
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* 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()->exists() ? $this->content()->name() : 'default';
|
|
}
|
|
|
|
/**
|
|
* Returns the full path to the intended template file
|
|
* This template file may not exist.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function intendedTemplateFile() {
|
|
return $this->kirby->component('template')->file($this->intendedTemplate());
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a dedicated template for this page
|
|
* Will return false when the default template is used
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasTemplate() {
|
|
return $this->intendedTemplate() == $this->template();
|
|
}
|
|
|
|
/**
|
|
* Sends all appropriate headers for this page
|
|
* Can be configured with the headers config array,
|
|
* which should contain all header definitions for each template
|
|
*/
|
|
public function headers() {
|
|
|
|
$template = $this->template();
|
|
if(isset($this->kirby->options['headers'][$template])) {
|
|
$headers = $this->kirby->options['headers'][$template];
|
|
|
|
if(is_numeric($headers)) {
|
|
header::status($headers);
|
|
} else if(is_callable($headers)) {
|
|
call($headers, $this);
|
|
}
|
|
|
|
} else if($this->isErrorPage()) {
|
|
header::notfound();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the root for the content file
|
|
*
|
|
* @return string
|
|
*/
|
|
public function textfile($template = null) {
|
|
if(is_null($template)) $template = $this->intendedTemplate();
|
|
return textfile($this->diruri(), $template);
|
|
}
|
|
|
|
/**
|
|
* Private method to create a page directory
|
|
*/
|
|
static protected function createDirectory($uri) {
|
|
|
|
$uid = str::slug(basename($uri));
|
|
$parentURI = dirname($uri);
|
|
$parent = ($parentURI == '.' or empty($parentURI) or $parentURI == DS) ? site() : page($parentURI);
|
|
|
|
if(!$parent) {
|
|
throw new Exception('The parent does not exist');
|
|
}
|
|
|
|
// check for an entered sorting number
|
|
if(preg_match('!^(\d+)\-(.*)!', $uid, $matches)) {
|
|
$num = $matches[1];
|
|
$uid = $matches[2];
|
|
$dir = $num . '-' . $uid;
|
|
} else {
|
|
$num = false;
|
|
$dir = $uid;
|
|
}
|
|
|
|
// make sure to check a fresh page
|
|
$parent->reset();
|
|
|
|
if($parent->children()->findBy('uid', $uid)) {
|
|
throw new Exception('The page UID exists');
|
|
}
|
|
|
|
if(!dir::make($parent->root() . DS . $dir)) {
|
|
throw new Exception('The directory could not be created');
|
|
}
|
|
|
|
// make sure the new directory is available everywhere
|
|
$parent->reset();
|
|
|
|
return $parent->id() . '/' . $uid;
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates a new page object
|
|
*
|
|
* @param string $uri
|
|
* @param string $template
|
|
* @param array $data
|
|
*/
|
|
static public function create($uri, $template, $data = array()) {
|
|
|
|
if(!is_string($template) or empty($template)) {
|
|
throw new Exception('Please pass a valid template name as second argument');
|
|
}
|
|
|
|
// try to create the new directory
|
|
$uri = static::createDirectory($uri);
|
|
|
|
// create the path for the textfile
|
|
$file = textfile($uri, $template);
|
|
|
|
// try to store the data in the text file
|
|
if(!data::write($file, $data, 'kd')) {
|
|
throw new Exception('The page file could not be created');
|
|
}
|
|
|
|
// get the new page object
|
|
$page = page($uri);
|
|
|
|
if(!is_a($page, 'Page')) {
|
|
throw new Exception('The new page object could not be found');
|
|
}
|
|
|
|
// let's create a model if one is defined
|
|
if(isset(static::$models[$template])) {
|
|
$model = static::$models[$template];
|
|
$page = new $model($page->parent(), $page->dirname());
|
|
}
|
|
|
|
kirby::instance()->cache()->flush();
|
|
|
|
return $page;
|
|
|
|
}
|
|
|
|
/**
|
|
* Update the page with a new set of data
|
|
*
|
|
* @param array
|
|
*/
|
|
public function update($input = array()) {
|
|
|
|
$data = a::update($this->content()->toArray(), $input);
|
|
|
|
if(!data::write($this->textfile(), $data, 'kd')) {
|
|
throw new Exception('The page could not be updated');
|
|
}
|
|
|
|
$this->kirby->cache()->flush();
|
|
$this->reset();
|
|
$this->touch();
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Increment a field value by one or a given value
|
|
*
|
|
* @param string $field
|
|
* @param int $by
|
|
* @param int $max
|
|
* @return Page
|
|
*/
|
|
public function increment($field, $by = 1, $max = null) {
|
|
$this->update(array(
|
|
$field => function($value) use($by, $max) {
|
|
$new = (int)$value + $by;
|
|
return ($max and $new >= $max) ? $max : $new;
|
|
}
|
|
));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Decrement a field value by one or a given value
|
|
*
|
|
* @param string $field
|
|
* @param int $by
|
|
* @param int $min
|
|
* @return Page
|
|
*/
|
|
public function decrement($field, $by = 1, $min = 0) {
|
|
$this->update(array(
|
|
$field => function($value) use($by, $min) {
|
|
$new = (int)$value - $by;
|
|
return $new <= $min ? $min : $new;
|
|
}
|
|
));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Changes the uid for the page
|
|
*
|
|
* @param string $uid
|
|
*/
|
|
public function move($uid) {
|
|
|
|
$uid = str::slug($uid);
|
|
|
|
if(empty($uid)) {
|
|
throw new Exception('The uid is missing');
|
|
}
|
|
|
|
// don't do anything if the uid exists
|
|
if($this->uid() === $uid) return true;
|
|
|
|
// check for an existing page with the same UID
|
|
if($this->siblings()->not($this)->find($uid)) {
|
|
throw new Exception('A page with this uid already exists');
|
|
}
|
|
|
|
$dir = $this->isVisible() ? $this->num() . '-' . $uid : $uid;
|
|
$root = dirname($this->root()) . DS . $dir;
|
|
|
|
if(!dir::move($this->root(), $root)) {
|
|
throw new Exception('The directory could not be moved');
|
|
}
|
|
|
|
$this->dirname = $dir;
|
|
$this->root = $root;
|
|
$this->uid = $uid;
|
|
|
|
// assign a new id and uri
|
|
$this->id = $this->uri = ltrim($this->parent->id() . '/' . $this->uid, '/');
|
|
|
|
// clean the cache
|
|
$this->kirby->cache()->flush();
|
|
$this->reset();
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Return the prepended number for the page
|
|
* or changes it to the number passed as parameter
|
|
*/
|
|
public function sort($num = null) {
|
|
|
|
if(!$num and $num !== 0) return $this->num();
|
|
if($num === $this->num()) return true;
|
|
|
|
$dir = $num . '-' . $this->uid();
|
|
$root = dirname($this->root()) . DS . $dir;
|
|
|
|
if(!dir::move($this->root(), $root)) {
|
|
throw new Exception('The directory could not be moved');
|
|
}
|
|
|
|
$this->dirname = $dir;
|
|
$this->num = $num;
|
|
$this->root = $root;
|
|
$this->kirby->cache()->flush();
|
|
$this->reset();
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Make the page invisible by removing the prepended number
|
|
*/
|
|
public function hide() {
|
|
|
|
if($this->isInvisible()) return true;
|
|
|
|
$root = dirname($this->root()) . DS . $this->uid();
|
|
|
|
if(!dir::move($this->root(), $root)) {
|
|
throw new Exception('The directory could not be moved');
|
|
}
|
|
|
|
$this->dirname = $this->uid();
|
|
$this->num = null;
|
|
$this->root = $root;
|
|
$this->kirby->cache()->flush();
|
|
$this->reset();
|
|
return true;
|
|
|
|
}
|
|
|
|
public function isDeletable() {
|
|
|
|
if($this->isSite()) return false;
|
|
if($this->isHomePage()) return false;
|
|
if($this->isErrorPage()) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Deletes the page
|
|
*
|
|
* @param boolean $force Forces the page to be deleted even if there are subpages
|
|
*/
|
|
public function delete($force = false) {
|
|
|
|
if(!$this->isDeletable()) {
|
|
throw new Exception('The page cannot be deleted');
|
|
}
|
|
|
|
if($force === false and $this->children()->count()) {
|
|
throw new Exception('This page has subpages');
|
|
}
|
|
|
|
$parent = $this->parent();
|
|
|
|
if(!dir::remove($this->root())) {
|
|
throw new Exception('The page could not be deleted');
|
|
}
|
|
|
|
$this->kirby->cache()->flush();
|
|
$parent->reset();
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Converts the entire page object into
|
|
* a plain PHP array
|
|
*
|
|
* @param closure $callback Filter callback
|
|
* @return array
|
|
*/
|
|
public function toArray($callback = null) {
|
|
|
|
$data = array(
|
|
'id' => $this->id(),
|
|
'title' => $this->title()->toString(),
|
|
'parent' => $this->parent()->uri(),
|
|
'dirname' => $this->dirname(),
|
|
'diruri' => $this->diruri(),
|
|
'url' => $this->url(),
|
|
'contentUrl' => $this->contentUrl(),
|
|
'tinyUrl' => $this->tinyUrl(),
|
|
'depth' => $this->depth(),
|
|
'uri' => $this->uri(),
|
|
'root' => $this->root(),
|
|
'uid' => $this->uid(),
|
|
'slug' => $this->slug(),
|
|
'num' => $this->num(),
|
|
'hash' => $this->hash(),
|
|
'modified' => $this->modified(),
|
|
'template' => $this->template(),
|
|
'intendedTemplate' => $this->intendedTemplate(),
|
|
'content' => $this->content()->toArray(),
|
|
);
|
|
|
|
if(is_null($callback)) {
|
|
return $data;
|
|
} else if(is_callable($callback)) {
|
|
return $callback($this);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Tries to find a controller for
|
|
* the current page and loads the data
|
|
*
|
|
* @return array
|
|
*/
|
|
public function controller($arguments = array()) {
|
|
|
|
$controller = $this->kirby->registry->get('controller', $this->template());
|
|
|
|
if(is_a($controller, 'Closure')) {
|
|
return (array)call_user_func_array($controller, array(
|
|
$this->site,
|
|
$this->site->children(),
|
|
$this,
|
|
$arguments
|
|
));
|
|
}
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
/**
|
|
* Converts the entire page array into
|
|
* a json string
|
|
*
|
|
* @param closure $callback Filter callback
|
|
* @return string
|
|
*/
|
|
public function toJson($callback = null) {
|
|
return json_encode($this->toArray($callback));
|
|
}
|
|
|
|
/**
|
|
* Makes it possible to echo the entire object
|
|
*
|
|
* @return string
|
|
*/
|
|
public function __toString() {
|
|
return (string)$this->id();
|
|
}
|
|
|
|
}
|