first version

This commit is contained in:
Bastian Allgeier
2019-01-13 23:17:34 +01:00
commit 01277f79f2
595 changed files with 82913 additions and 0 deletions

15
kirby/.editorconfig Executable file
View File

@@ -0,0 +1,15 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# PHP PSR-2 Coding Standards
# http://www.php-fig.org/psr/psr-2/
root = true
[*.php]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

25
kirby/README.md Executable file
View File

@@ -0,0 +1,25 @@
# Kirby
[![Build Status](https://travis-ci.com/k-next/kirby.svg?branch=master)](https://travis-ci.com/k-next/kirby)
[![Coverage Status](https://coveralls.io/repos/github/k-next/kirby/badge.svg?branch=master)](https://coveralls.io/github/k-next/kirby?branch=master)
This is Kirby's core application folder. Get started with one of the following repositories instead:
- [Starterkit](https://github.com/getkirby/starterkit)
- [Plainkit](https://github.com/getkirby/plainkit)
## Bug reports
Please post all bug reports in our issue tracker:
https://github.com/getkirby/kirby/issues
## Feature suggestions
If you want to suggest features or enhancements for Kirby, please use our Ideas repository:
https://github.com/getkirby/ideas/issues
## License
Kirby is not free software. In order to run Kirby on a public server you must purchase a valid license.
- https://getkirby.com/buy
- https://getkirby.com/license

34
kirby/bootstrap.php Executable file
View File

@@ -0,0 +1,34 @@
<?php
/**
* Validate the PHP version to already
* stop at older versions
*/
if (version_compare(phpversion(), '7.1.0', '>') === false) {
die(include __DIR__ . '/views/php.php');
}
if (is_file($autoloader = dirname(__DIR__) . '/vendor/autoload.php')) {
/**
* Always prefer a site-wide Composer autoloader
* if it exists, it means that the user has probably
* installed additional packages
*/
include $autoloader;
} elseif (is_file($autoloader = __DIR__ . '/vendor/autoload.php')) {
/**
* Fall back to the local autoloader if that exists
*/
include $autoloader;
} else {
/**
* If neither one exists, don't bother searching
* it's a custom directory setup and the users need to
* load the autoloader themselves
*/
}
define('DS', '/');

52
kirby/composer.json Executable file
View File

@@ -0,0 +1,52 @@
{
"name": "getkirby/cms",
"description": "The Kirby 3 core",
"version": "3.0.0-RC-5.0",
"license": "proprietary",
"keywords": ["kirby", "cms", "core"],
"homepage": "https://getkirby.com",
"type": "kirby-cms",
"authors": [
{
"name": "Kirby Team",
"email": "support@getkirby.com",
"homepage": "https://getkirby.com"
}
],
"support": {
"email": "support@getkirby.com",
"issues": "https://github.com/getkirby/kirby/issues",
"forum": "https://forum.getkirby.com",
"source": "https://github.com/getkirby/kirby"
},
"require": {
"php": ">=7.1.0",
"ext-mbstring": "*",
"ext-ctype": "*",
"getkirby/composer-installer": "*",
"mustangostang/spyc": "0.6.*",
"michelf/php-smartypants": "1.8.*",
"claviska/simpleimage": "3.3.*",
"phpmailer/phpmailer": "6.0.*",
"filp/whoops": "2.3.*",
"true/punycode": "2.1.*",
"zendframework/zend-escaper": "2.6.*"
},
"autoload": {
"files": ["config/helpers.php", "config/aliases.php", "config/tests.php"],
"classmap": ["dependencies/"],
"psr-4": {
"Kirby\\": "src/"
}
},
"scripts": {
"analyze": "phpstan analyse src",
"test": "phpunit --stderr --coverage-html=tests/coverage",
"zip": "composer archive --format=zip --file=dist",
"build": "./scripts/build",
"fix": "php-cs-fixer fix ."
},
"config": {
"optimize-autoloader": true
}
}

577
kirby/composer.lock generated Executable file
View File

@@ -0,0 +1,577 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0f4c41d639d0273e46924acd006c47cc",
"packages": [
{
"name": "claviska/simpleimage",
"version": "3.3.3",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "31ba5b8358e1663a2813e2ada7242fa8d97a96dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/31ba5b8358e1663a2813e2ada7242fa8d97a96dc",
"reference": "31ba5b8358e1663a2813e2ada7242fa8d97a96dc",
"shasum": ""
},
"require": {
"ext-gd": "*",
"league/color-extractor": "0.3.*",
"php": ">=5.6.0"
},
"type": "library",
"autoload": {
"psr-0": {
"claviska": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Cory LaViska",
"homepage": "http://www.abeautifulsite.net/",
"role": "Developer"
}
],
"description": "A PHP class that makes working with images as simple as possible.",
"time": "2017-09-12T09:03:56+00:00"
},
{
"name": "filp/whoops",
"version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7",
"reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0",
"psr/log": "^1.0.1"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.35 || ^5.7",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
},
"autoload": {
"psr-4": {
"Whoops\\": "src/Whoops/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"description": "php error handling for cool kids",
"homepage": "https://filp.github.io/whoops/",
"keywords": [
"error",
"exception",
"handling",
"library",
"throwable",
"whoops"
],
"time": "2018-10-23T09:00:00+00:00"
},
{
"name": "getkirby/composer-installer",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/k-next/composer-installer.git",
"reference": "dc0e38c4f0fc04875c1a523d82364a44f436cbf4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/k-next/composer-installer/zipball/dc0e38c4f0fc04875c1a523d82364a44f436cbf4",
"reference": "dc0e38c4f0fc04875c1a523d82364a44f436cbf4",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0"
},
"require-dev": {
"composer/composer": "^1.3",
"phpunit/phpunit": "^6.0"
},
"type": "composer-plugin",
"extra": {
"class": "Kirby\\ComposerInstaller\\Plugin"
},
"autoload": {
"psr-4": {
"Kirby\\ComposerInstaller\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Kirby's custom Composer installer for the Kirby CMS",
"homepage": "https://getkirby.com",
"time": "2018-12-19T10:01:45+00:00"
},
{
"name": "league/color-extractor",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/color-extractor.git",
"reference": "837086ec60f50c84c611c613963e4ad2e2aec806"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/837086ec60f50c84c611c613963e4ad2e2aec806",
"reference": "837086ec60f50c84c611c613963e4ad2e2aec806",
"shasum": ""
},
"require": {
"ext-gd": "*",
"php": ">=5.4.0"
},
"replace": {
"matthecat/colorextractor": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2",
"phpunit/phpunit": "~5"
},
"type": "library",
"autoload": {
"psr-4": {
"": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mathieu Lechat",
"email": "math.lechat@gmail.com",
"homepage": "http://matthecat.com",
"role": "Developer"
}
],
"description": "Extract colors from an image as a human would do.",
"homepage": "https://github.com/thephpleague/color-extractor",
"keywords": [
"color",
"extract",
"human",
"image",
"palette"
],
"time": "2016-12-15T09:30:02+00:00"
},
{
"name": "michelf/php-smartypants",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/michelf/php-smartypants.git",
"reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad",
"reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Michelf": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Michel Fortin",
"email": "michel.fortin@michelf.ca",
"homepage": "https://michelf.ca/",
"role": "Developer"
},
{
"name": "John Gruber",
"homepage": "https://daringfireball.net/"
}
],
"description": "PHP SmartyPants",
"homepage": "https://michelf.ca/projects/php-smartypants/",
"keywords": [
"dashes",
"quotes",
"spaces",
"typographer",
"typography"
],
"time": "2016-12-13T01:01:17+00:00"
},
{
"name": "mustangostang/spyc",
"version": "0.6.2",
"source": {
"type": "git",
"url": "https://github.com/mustangostang/spyc.git",
"reference": "23c35ae854d835f2d7bcc3e3ad743d7e57a8c14d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mustangostang/spyc/zipball/23c35ae854d835f2d7bcc3e3ad743d7e57a8c14d",
"reference": "23c35ae854d835f2d7bcc3e3ad743d7e57a8c14d",
"shasum": ""
},
"require": {
"php": ">=5.3.1"
},
"require-dev": {
"phpunit/phpunit": "4.3.*@dev"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.5.x-dev"
}
},
"autoload": {
"files": [
"Spyc.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "mustangostang",
"email": "vlad.andersen@gmail.com"
}
],
"description": "A simple YAML loader/dumper class for PHP",
"homepage": "https://github.com/mustangostang/spyc/",
"keywords": [
"spyc",
"yaml",
"yml"
],
"time": "2017-02-24T16:06:33+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.0.6",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "8190d73eb5def11a43cfb020b7f36db65330698c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/8190d73eb5def11a43cfb020b7f36db65330698c",
"reference": "8190d73eb5def11a43cfb020b7f36db65330698c",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"php": ">=5.5.0"
},
"require-dev": {
"doctrine/annotations": "1.2.*",
"friendsofphp/php-cs-fixer": "^2.2",
"phpdocumentor/phpdocumentor": "2.*",
"phpunit/phpunit": "^4.8 || ^5.7",
"zendframework/zend-eventmanager": "3.0.*",
"zendframework/zend-i18n": "2.7.3",
"zendframework/zend-serializer": "2.7.*"
},
"suggest": {
"ext-mbstring": "Needed to send email in multibyte encoding charset",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2018-11-16T00:41:32+00:00"
},
{
"name": "psr/log",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2018-11-20T15:27:04+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2018-09-21T13:07:52+00:00"
},
{
"name": "true/punycode",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/true/php-punycode.git",
"reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
"reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpunit/phpunit": "~4.7",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"TrueBV\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Renan Gonçalves",
"email": "renan.saddam@gmail.com"
}
],
"description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)",
"homepage": "https://github.com/true/php-punycode",
"keywords": [
"idna",
"punycode"
],
"time": "2016-11-16T10:37:54+00:00"
},
{
"name": "zendframework/zend-escaper",
"version": "2.6.0",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-escaper.git",
"reference": "31d8aafae982f9568287cb4dce987e6aff8fd074"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074",
"reference": "31d8aafae982f9568287cb4dce987e6aff8fd074",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2",
"zendframework/zend-coding-standard": "~1.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6.x-dev",
"dev-develop": "2.7.x-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Escaper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs",
"keywords": [
"ZendFramework",
"escaper",
"zf"
],
"time": "2018-04-25T15:48:53+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.0",
"ext-mbstring": "*",
"ext-ctype": "*"
},
"platform-dev": []
}

65
kirby/config/aliases.php Executable file
View File

@@ -0,0 +1,65 @@
<?php
$aliases = [
// cms classes
'collection' => 'Kirby\Cms\Collection',
'dir' => 'Kirby\Cms\Dir',
'field' => 'Kirby\Cms\Field',
'file' => 'Kirby\Cms\File',
'files' => 'Kirby\Cms\Files',
'html' => 'Kirby\Cms\Html',
'kirby' => 'Kirby\Cms\App',
'page' => 'Kirby\Cms\Page',
'pages' => 'Kirby\Cms\Pages',
'pagination' => 'Kirby\Cms\Pagination',
'r' => 'Kirby\Cms\R',
'response' => 'Kirby\Cms\Response',
's' => 'Kirby\Cms\S',
'site' => 'Kirby\Cms\Site',
'structure' => 'Kirby\Cms\Structure',
'url' => 'Kirby\Cms\Url',
'user' => 'Kirby\Cms\User',
'users' => 'Kirby\Cms\Users',
'visitor' => 'Kirby\Cms\Visitor',
// data handler
'data' => 'Kirby\Data\Data',
'json' => 'Kirby\Data\Json',
'yaml' => 'Kirby\Data\Yaml',
// data classes
'database' => 'Kirby\Database\Database',
'db' => 'Kirby\Database\Db',
// http classes
'cookie' => 'Kirby\Http\Cookie',
'header' => 'Kirby\Http\Header',
'remote' => 'Kirby\Http\Remote',
'server' => 'Kirby\Http\Server',
// image classes
'dimensions' => 'Kirby\Image\Dimensions',
// toolkit classes
'a' => 'Kirby\Toolkit\A',
'c' => 'Kirby\Toolkit\Config',
'config' => 'Kirby\Toolkit\Config',
'escape' => 'Kirby\Toolkit\Escape',
'f' => 'Kirby\Toolkit\F',
'i18n' => 'Kirby\Toolkit\I18n',
'mime' => 'Kirby\Toolkit\Mime',
'obj' => 'Kirby\Toolkit\Obj',
'str' => 'Kirby\Toolkit\Str',
'tpl' => 'Kirby\Toolkit\Tpl',
'v' => 'Kirby\Toolkit\V',
'xml' => 'Kirby\Toolkit\Xml'
];
spl_autoload_register(function ($class) use ($aliases) {
$class = strtolower($class);
if (isset($aliases[$class]) === true) {
class_alias($aliases[$class], $class);
}
});

View File

@@ -0,0 +1,25 @@
<?php
use Kirby\Cms\Auth;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\Str;
return function () {
$auth = $this->kirby()->auth();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new PermissionException('Unauthenticated', 403);
}
// get user from session or basic auth
if ($user = $auth->user()) {
if ($user->role()->permissions()->for('access', 'panel') === false) {
throw new PermissionException(['key' => 'access.panel']);
}
return $user;
}
throw new PermissionException('Unauthenticated', 403);
};

View File

@@ -0,0 +1,80 @@
<?php
use Kirby\Cms\Files;
use Kirby\Cms\Languages;
use Kirby\Cms\Pages;
use Kirby\Cms\Roles;
use Kirby\Cms\Translations;
use Kirby\Cms\Users;
/**
* Api Collection Definitions
*/
return [
/**
* Children
*/
'children' => [
'model' => 'page',
'type' => Pages::class,
'view' => 'compact'
],
/**
* Files
*/
'files' => [
'model' => 'file',
'type' => Files::class
],
/**
* Languages
*/
'languages' => [
'model' => 'language',
'type' => Languages::class,
'view' => 'compact'
],
/**
* Pages
*/
'pages' => [
'model' => 'page',
'type' => Pages::class,
'view' => 'compact'
],
/**
* Roles
*/
'roles' => [
'model' => 'role',
'type' => Roles::class,
'view' => 'compact'
],
/**
* Translations
*/
'translations' => [
'model' => 'translation',
'type' => Translations::class,
'view' => 'compact'
],
/**
* Users
*/
'users' => [
'default' => function () {
return $this->users();
},
'model' => 'user',
'type' => Users::class,
'view' => 'compact'
]
];

24
kirby/config/api/models.php Executable file
View File

@@ -0,0 +1,24 @@
<?php
use Kirby\Cms\File;
use Kirby\Cms\Page;
use Kirby\Cms\Site;
/**
* Api Model Definitions
*/
return [
'File' => include __DIR__ . '/models/File.php',
'FileBlueprint' => include __DIR__ . '/models/FileBlueprint.php',
'FileVersion' => include __DIR__ . '/models/FileVersion.php',
'Language' => include __DIR__ . '/models/Language.php',
'Page' => include __DIR__ . '/models/Page.php',
'PageBlueprint' => include __DIR__ . '/models/PageBlueprint.php',
'Role' => include __DIR__ . '/models/Role.php',
'Site' => include __DIR__ . '/models/Site.php',
'SiteBlueprint' => include __DIR__ . '/models/SiteBlueprint.php',
'System' => include __DIR__ . '/models/System.php',
'Translation' => include __DIR__ . '/models/Translation.php',
'User' => include __DIR__ . '/models/User.php',
'UserBlueprint' => include __DIR__ . '/models/UserBlueprint.php',
];

160
kirby/config/api/models/File.php Executable file
View File

@@ -0,0 +1,160 @@
<?php
use Kirby\Cms\File;
use Kirby\Cms\Form;
/**
* File
*/
return [
'fields' => [
'blueprint' => function (File $file) {
return $file->blueprint();
},
'content' => function (File $file) {
return Form::for($file)->values();
},
'dimensions' => function (File $file) {
return $file->dimensions()->toArray();
},
'exists' => function (File $file) {
return $file->exists();
},
'extension' => function (File $file) {
return $file->extension();
},
'filename' => function (File $file) {
return $file->filename();
},
'id' => function (File $file) {
return $file->id();
},
'link' => function (File $file) {
return $file->panelUrl(true);
},
'mime' => function (File $file) {
return $file->mime();
},
'modified' => function (File $file) {
return $file->modified('c');
},
'name' => function (File $file) {
return $file->name();
},
'next' => function (File $file) {
return $file->next();
},
'nextWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sortBy('sort', 'asc');
$index = $files->indexOf($file);
return $files->nth($index + 1);
},
'options' => function (File $file) {
return $file->permissions()->toArray();
},
'prev' => function (File $file) {
return $file->prev();
},
'prevWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sortBy('sort', 'asc');
$index = $files->indexOf($file);
return $files->nth($index - 1);
},
'niceSize' => function (File $file) {
return $file->niceSize();
},
'panelIcon' => function (File $file) {
return $file->panelIcon();
},
'panelImage' => function (File $file) {
return $file->panelImage();
},
'parent' => function (File $file) {
return $file->parent();
},
'parents' => function (File $file) {
return $file->parents()->flip();
},
'template' => function (File $file) {
return $file->template();
},
'size' => function (File $file) {
return $file->size();
},
'thumbs' => function ($file) {
if ($file->isResizable() === false) {
return null;
}
return [
'tiny' => $file->resize(128)->url(),
'small' => $file->resize(256)->url(),
'medium' => $file->resize(512)->url(),
'large' => $file->resize(768)->url(),
'huge' => $file->resize(1024)->url(),
];
},
'type' => function (File $file) {
return $file->type();
},
'url' => function (File $file) {
return $file->url(true);
},
],
'type' => File::class,
'views' => [
'default' => [
'content',
'dimensions',
'exists',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'next' => 'compact',
'niceSize',
'parent' => 'compact',
'options',
'prev' => 'compact',
'size',
'template',
'type',
'url'
],
'compact' => [
'filename',
'id',
'link',
'type',
'url',
],
'panel' => [
'blueprint',
'content',
'dimensions',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'nextWithTemplate' => 'compact',
'niceSize',
'options',
'panelIcon',
'panelImage',
'parent' => 'compact',
'parents' => ['id', 'slug', 'title'],
'prevWithTemplate' => 'compact',
'template',
'type',
'url'
]
],
];

View File

@@ -0,0 +1,26 @@
<?php
use Kirby\Cms\FileBlueprint;
/**
* FileBlueprint
*/
return [
'fields' => [
'name' => function (FileBlueprint $blueprint) {
return $blueprint->name();
},
'options' => function (FileBlueprint $blueprint) {
return $blueprint->options();
},
'tabs' => function (FileBlueprint $blueprint) {
return $blueprint->tabs();
},
'title' => function (FileBlueprint $blueprint) {
return $blueprint->title();
},
],
'type' => FileBlueprint::class,
'views' => [
],
];

View File

@@ -0,0 +1,83 @@
<?php
use Kirby\Cms\FileVersion;
/**
* FileVersion
*/
return [
'fields' => [
'dimensions' => function (FileVersion $file) {
return $file->dimensions()->toArray();
},
'exists' => function (FileVersion $file) {
return $file->exists();
},
'extension' => function (FileVersion $file) {
return $file->extension();
},
'filename' => function (FileVersion $file) {
return $file->filename();
},
'id' => function (FileVersion $file) {
return $file->id();
},
'mime' => function (FileVersion $file) {
return $file->mime();
},
'modified' => function (FileVersion $file) {
return $file->modified('c');
},
'name' => function (FileVersion $file) {
return $file->name();
},
'niceSize' => function (FileVersion $file) {
return $file->niceSize();
},
'size' => function (FileVersion $file) {
return $file->size();
},
'type' => function (FileVersion $file) {
return $file->type();
},
'url' => function (FileVersion $file) {
return $file->url(true);
},
],
'type' => FileVersion::class,
'views' => [
'default' => [
'dimensions',
'exists',
'extension',
'filename',
'id',
'mime',
'modified',
'name',
'niceSize',
'size',
'type',
'url'
],
'compact' => [
'filename',
'id',
'type',
'url',
],
'panel' => [
'dimensions',
'extension',
'filename',
'id',
'mime',
'modified',
'name',
'niceSize',
'template',
'type',
'url'
]
],
];

View File

@@ -0,0 +1,37 @@
<?php
use Kirby\Cms\Language;
/**
* Language
*/
return [
'fields' => [
'code' => function (Language $language) {
return $language->code();
},
'default' => function (Language $language) {
return $language->isDefault();
},
'direction' => function (Language $language) {
return $language->direction();
},
'locale' => function (Language $language) {
return $language->locale();
},
'name' => function (Language $language) {
return $language->name();
},
'url' => function (Language $language) {
return $language->url();
},
],
'type' => Language::class,
'views' => [
'compact' => [
'code',
'default',
'name',
]
]
];

155
kirby/config/api/models/Page.php Executable file
View File

@@ -0,0 +1,155 @@
<?php
use Kirby\Cms\Form;
use Kirby\Cms\Page;
/**
* Page
*/
return [
'fields' => [
'blueprint' => function (Page $page) {
return $page->blueprint();
},
'blueprints' => function (Page $page) {
return $page->blueprints();
},
'children' => function (Page $page) {
return $page->children();
},
'content' => function (Page $page) {
return Form::for($page)->values();
},
'drafts' => function (Page $page) {
return $page->drafts();
},
'errors' => function (Page $page) {
return $page->errors();
},
'files' => function (Page $page) {
return $page->files();
},
'hasChildren' => function (Page $page) {
return $page->hasChildren();
},
'hasDrafts' => function (Page $page) {
return $page->hasDrafts();
},
'id' => function (Page $page) {
return $page->id();
},
'isSortable' => function (Page $page) {
return $page->isSortable();
},
'next' => function (Page $page) {
return $page
->nextAll()
->filterBy('intendedTemplate', $page->intendedTemplate())
->filterBy('status', $page->status())
->filterBy('isReadable', true)
->first();
},
'num' => function (Page $page) {
return $page->num();
},
'options' => function (Page $page) {
return $page->permissions()->toArray();
},
'panelIcon' => function (Page $page) {
return $page->panelIcon();
},
'panelImage' => function (Page $page) {
return $page->panelImage();
},
'parent' => function (Page $page) {
return $page->parent();
},
'parents' => function (Page $page) {
return $page->parents()->flip();
},
'prev' => function (Page $page) {
return $page
->prevAll()
->filterBy('intendedTemplate', $page->intendedTemplate())
->filterBy('status', $page->status())
->filterBy('isReadable', true)
->last();
},
'previewUrl' => function (Page $page) {
return $page->previewUrl();
},
'siblings' => function (Page $page) {
if ($page->isDraft() === true) {
return $page->parentModel()->children()->not($page);
} else {
return $page->siblings();
}
},
'slug' => function (Page $page) {
return $page->slug();
},
'status' => function (Page $page) {
return $page->status();
},
'template' => function (Page $page) {
return $page->intendedTemplate()->name();
},
'title' => function (Page $page) {
return $page->title()->value();
},
'url' => function (Page $page) {
return $page->url();
},
],
'type' => Page::class,
'views' => [
'compact' => [
'id',
'title',
'url',
'num'
],
'default' => [
'content',
'id',
'status',
'num',
'options',
'parent' => 'compact',
'slug',
'template',
'title',
'url'
],
'panel' => [
'id',
'blueprint',
'content',
'errors',
'status',
'options',
'next' => ['id', 'slug', 'title'],
'parents' => ['id', 'slug', 'title'],
'prev' => ['id', 'slug', 'title'],
'previewUrl',
'slug',
'title',
'url'
],
'selector' => [
'id',
'title',
'parent' => [
'id',
'title'
],
'children' => [
'hasChildren',
'id',
'panelIcon',
'panelImage',
'title',
],
]
],
];

View File

@@ -0,0 +1,35 @@
<?php
use Kirby\Cms\PageBlueprint;
/**
* PageBlueprint
*/
return [
'fields' => [
'name' => function (PageBlueprint $blueprint) {
return $blueprint->name();
},
'num' => function (PageBlueprint $blueprint) {
return $blueprint->num();
},
'options' => function (PageBlueprint $blueprint) {
return $blueprint->options();
},
'preview' => function (PageBlueprint $blueprint) {
return $blueprint->preview();
},
'status' => function (PageBlueprint $blueprint) {
return $blueprint->status();
},
'tabs' => function (PageBlueprint $blueprint) {
return $blueprint->tabs();
},
'title' => function (PageBlueprint $blueprint) {
return $blueprint->title();
},
],
'type' => PageBlueprint::class,
'views' => [
],
];

View File

@@ -0,0 +1,31 @@
<?php
use Kirby\Cms\Role;
/**
* Role
*/
return [
'fields' => [
'description' => function (Role $role) {
return $role->description();
},
'name' => function (Role $role) {
return $role->name();
},
'permissions' => function (Role $role) {
return $role->permissions()->toArray();
},
'title' => function (Role $role) {
return $role->title();
},
],
'type' => Role::class,
'views' => [
'compact' => [
'description',
'name',
'title'
]
]
];

View File

@@ -0,0 +1,65 @@
<?php
use Kirby\Cms\Form;
use Kirby\Cms\Site;
/**
* Site
*/
return [
'default' => function () {
return $this->site();
},
'fields' => [
'blueprint' => function (Site $site) {
return $site->blueprint();
},
'children' => function (Site $site) {
return $site->children();
},
'content' => function (Site $site) {
return Form::for($site)->values();
},
'files' => function (Site $site) {
return $site->files();
},
'options' => function (Site $site) {
return $site->permissions()->toArray();
},
'title' => function (Site $site) {
return $site->title()->value();
},
'url' => function (Site $site) {
return $site->url();
},
],
'type' => Site::class,
'views' => [
'compact' => [
'title',
'url'
],
'default' => [
'content',
'options',
'title',
'url'
],
'panel' => [
'title',
'blueprint',
'content',
'options',
'url'
],
'selector' => [
'title',
'children' => [
'id',
'title',
'panelIcon',
'hasChildren'
],
]
]
];

View File

@@ -0,0 +1,26 @@
<?php
use Kirby\Cms\SiteBlueprint;
/**
* SiteBlueprint
*/
return [
'fields' => [
'name' => function (SiteBlueprint $blueprint) {
return $blueprint->name();
},
'options' => function (SiteBlueprint $blueprint) {
return $blueprint->options();
},
'tabs' => function (SiteBlueprint $blueprint) {
return $blueprint->tabs();
},
'title' => function (SiteBlueprint $blueprint) {
return $blueprint->title();
},
],
'type' => SiteBlueprint::class,
'views' => [
],
];

View File

@@ -0,0 +1,95 @@
<?php
use Kirby\Cms\System;
/**
* System
*/
return [
'fields' => [
'isOk' => function (System $system) {
return $system->isOk();
},
'isInstallable' => function (System $system) {
return $system->isInstallable();
},
'isInstalled' => function (System $system) {
return $system->isInstalled();
},
'isLocal' => function (System $system) {
return $system->isLocal();
},
'multilang' => function () {
return $this->kirby()->option('languages', false) !== false;
},
'languages' => function () {
return $this->kirby()->languages();
},
'license' => function (System $system) {
return $system->license();
},
'requirements' => function (System $system) {
return $system->toArray();
},
'breadcrumbTitle' => function () {
return $this->site()->blueprint()->title();
},
'title' => function () {
return $this->site()->title()->value();
},
'translation' => function () {
if ($user = $this->user()) {
$translationCode = $user->language();
} else {
$translationCode = $this->kirby()->option('panel.language', 'en');
}
if ($translation = $this->kirby()->translation($translationCode)) {
return $translation;
} else {
return $this->kirby()->translation('en');
}
},
'kirbytext' => function () {
return $this->kirby()->option('panel')['kirbytext'] ?? true;
},
'user' => function () {
return $this->user();
},
'version' => function () {
return $this->kirby()->version();
}
],
'type' => System::class,
'views' => [
'login' => [
'isOk',
'isInstalled',
'title',
'translation'
],
'troubleshooting' => [
'isOk',
'isInstallable',
'isInstalled',
'title',
'translation',
'requirements'
],
'panel' => [
'breadcrumbTitle',
'isOk',
'isInstalled',
'isLocal',
'kirbytext',
'languages' => 'compact',
'license',
'multilang',
'requirements',
'title',
'translation',
'user' => 'auth',
'version'
]
],
];

View File

@@ -0,0 +1,34 @@
<?php
use Kirby\Cms\Translation;
/**
* Translation
*/
return [
'fields' => [
'author' => function (Translation $translation) {
return $translation->author();
},
'data' => function (Translation $translation) {
return $translation->dataWithFallback();
},
'direction' => function (Translation $translation) {
return $translation->direction();
},
'id' => function (Translation $translation) {
return $translation->id();
},
'name' => function (Translation $translation) {
return $translation->name();
},
],
'type' => Translation::class,
'views' => [
'compact' => [
'direction',
'id',
'name'
]
]
];

102
kirby/config/api/models/User.php Executable file
View File

@@ -0,0 +1,102 @@
<?php
use Kirby\Cms\Form;
use Kirby\Cms\User;
/**
* User
*/
return [
'default' => function () {
return $this->user();
},
'fields' => [
'avatar' => function (User $user) {
return $user->avatar() ? $user->avatar()->crop(512) : null;
},
'blueprint' => function (User $user) {
return $user->blueprint();
},
'content' => function (User $user) {
return Form::for($user)->values();
},
'email' => function (User $user) {
return $user->email();
},
'id' => function (User $user) {
return $user->id();
},
'language' => function (User $user) {
return $user->language();
},
'name' => function (User $user) {
return $user->name()->value();
},
'next' => function (User $user) {
return $user->next();
},
'options' => function (User $user) {
return $user->permissions()->toArray();
},
'permissions' => function (User $user) {
return $user->role()->permissions()->toArray();
},
'prev' => function (User $user) {
return $user->prev();
},
'role' => function (User $user) {
return $user->role();
},
'username' => function (User $user) {
return $user->username();
}
],
'type' => User::class,
'views' => [
'default' => [
'avatar',
'content',
'email',
'id',
'language',
'name',
'next' => 'compact',
'options',
'prev' => 'compact',
'role',
'username'
],
'compact' => [
'avatar' => 'compact',
'id',
'email',
'language',
'name',
'role' => 'compact',
'username'
],
'auth' => [
'avatar' => 'compact',
'permissions',
'email',
'id',
'name',
'role',
'language'
],
'panel' => [
'avatar' => 'compact',
'blueprint',
'content',
'email',
'id',
'language',
'name',
'next' => ['id', 'name'],
'options',
'prev' => ['id', 'name'],
'role',
'username',
],
]
];

View File

@@ -0,0 +1,26 @@
<?php
use Kirby\Cms\UserBlueprint;
/**
* UserBlueprint
*/
return [
'fields' => [
'name' => function (UserBlueprint $blueprint) {
return $blueprint->name();
},
'options' => function (UserBlueprint $blueprint) {
return $blueprint->options();
},
'tabs' => function (UserBlueprint $blueprint) {
return $blueprint->tabs();
},
'title' => function (UserBlueprint $blueprint) {
return $blueprint->title();
},
],
'type' => UserBlueprint::class,
'views' => [
],
];

25
kirby/config/api/routes.php Executable file
View File

@@ -0,0 +1,25 @@
<?php
/**
* Api Routes Definitions
*/
return function ($kirby) {
$routes = array_merge(
include __DIR__ . '/routes/auth.php',
include __DIR__ . '/routes/pages.php',
include __DIR__ . '/routes/roles.php',
include __DIR__ . '/routes/site.php',
include __DIR__ . '/routes/users.php',
include __DIR__ . '/routes/files.php',
include __DIR__ . '/routes/system.php',
include __DIR__ . '/routes/translations.php'
);
// only add the language routes if the
// multi language setup is activated
if ($kirby->option('languages', false) !== false) {
$routes = array_merge($routes, include __DIR__ . '/routes/languages.php');
}
return $routes;
};

View File

@@ -0,0 +1,58 @@
<?php
use Kirby\Exception\NotFoundException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
/**
* Authentication
*/
return [
[
'pattern' => 'auth',
'method' => 'GET',
'action' => function () {
if ($user = $this->kirby()->auth()->user()) {
return $this->resolve($user)->view('auth');
}
throw new NotFoundException('The user cannot be found');
}
],
[
'pattern' => 'auth/login',
'method' => 'POST',
'auth' => false,
'action' => function () {
$auth = $this->kirby()->auth();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException('Invalid CSRF token');
}
$email = $this->requestBody('email');
$long = $this->requestBody('long');
$password = $this->requestBody('password');
if ($user = $this->kirby()->auth()->login($email, $password, $long)) {
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
throw new InvalidArgumentException('Invalid email or password');
}
],
[
'pattern' => 'auth/logout',
'method' => 'POST',
'auth' => false,
'action' => function () {
$this->kirby()->auth()->logout();
return true;
}
],
];

100
kirby/config/api/routes/files.php Executable file
View File

@@ -0,0 +1,100 @@
<?php
use Kirby\Exception\InvalidArgumentException;
/**
* Files Routes
*/
return [
[
'pattern' => '(:all)/files',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->files();
}
],
[
'pattern' => '(:all)/files',
'method' => 'POST',
'action' => function (string $path) {
return $this->upload(function ($source, $filename) use ($path) {
return $this->parent($path)->createFile([
'source' => $source,
'template' => $this->requestBody('template'),
'filename' => $filename
]);
});
}
],
[
'pattern' => '(:all)/files/search',
'method' => 'POST',
'action' => function (string $path) {
return $this->parent($path)->files()->query($this->requestBody());
}
],
[
'pattern' => '(:all)/files/sort',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->files()->changeSort($this->requestBody('files'));
}
],
[
'pattern' => '(:all)/files/(:any)',
'method' => 'GET',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename);
}
],
[
'pattern' => '(:all)/files/(:any)',
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => '(:all)/files/(:any)',
'method' => 'POST',
'action' => function (string $path, string $filename) {
return $this->upload(function ($source) use ($path, $filename) {
return $this->file($path, $filename)->replace($source);
});
}
],
[
'pattern' => '(:all)/files/(:any)',
'method' => 'DELETE',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->delete();
}
],
[
'pattern' => '(:all)/files/(:any)/name',
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->changeName($this->requestBody('name'));
}
],
[
'pattern' => '(:all)/files/(:any)/sections/(:any)',
'method' => 'GET',
'action' => function (string $path, string $filename, string $sectionName) {
if ($section = $this->file($path, $filename)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => '(:all)/files/(:any)/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $parent, string $filename, string $fieldName, string $path = null) {
if ($file = $this->file($parent, $filename)) {
return $this->fieldApi($file, $fieldName, $path);
}
}
]
];

View File

@@ -0,0 +1,46 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'languages',
'method' => 'GET',
'action' => function () {
return $this->kirby()->languages();
}
],
[
'pattern' => 'languages',
'method' => 'POST',
'action' => function () {
return $this->kirby()->languages()->create($this->requestBody());
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'GET',
'action' => function (string $code) {
return $this->kirby()->languages()->find($code);
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'PATCH',
'action' => function (string $code) {
if ($language = $this->kirby()->languages()->find($code)) {
return $language->update($this->requestBody());
}
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'DELETE',
'action' => function (string $code) {
if ($language = $this->kirby()->languages()->find($code)) {
return $language->delete();
}
}
]
];

105
kirby/config/api/routes/pages.php Executable file
View File

@@ -0,0 +1,105 @@
<?php
use Kirby\Exception\InvalidArgumentException;
/**
* Page Routes
*/
return [
[
'pattern' => 'pages/(:any)',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id);
}
],
[
'pattern' => 'pages/(:any)',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => 'pages/(:any)',
'method' => 'DELETE',
'action' => function (string $id) {
return $this->page($id)->delete($this->requestBody('force', false));
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->children();
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->createChild($this->requestBody());
}
],
[
'pattern' => 'pages/(:any)/children/blueprints',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => 'pages/(:any)/children/search',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->children()->query($this->requestBody());
}
],
[
'pattern' => 'pages/(:any)/slug',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeSlug($this->requestBody('slug'));
}
],
[
'pattern' => 'pages/(:any)/status',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeStatus($this->requestBody('status'), $this->requestBody('position'));
}
],
[
'pattern' => 'pages/(:any)/template',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeTemplate($this->requestBody('template'));
}
],
[
'pattern' => 'pages/(:any)/title',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeTitle($this->requestBody('title'));
}
],
[
'pattern' => 'pages/(:any)/sections/(:any)',
'method' => 'GET',
'action' => function (string $id, string $sectionName) {
if ($section = $this->page($id)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
if ($page = $this->page($id)) {
return $this->fieldApi($page, $fieldName, $path);
}
}
],
];

View File

@@ -0,0 +1,21 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'roles',
'method' => 'GET',
'action' => function () {
return $this->kirby()->roles();
}
],
[
'pattern' => 'roles/(:any)',
'method' => 'GET',
'action' => function (string $name) {
return $this->kirby()->roles()->find($name);
}
]
];

View File

@@ -0,0 +1,95 @@
<?php
/**
* Site Routes
*/
return [
[
'pattern' => 'site',
'action' => function () {
return $this->site();
}
],
[
'pattern' => 'site',
'method' => 'PATCH',
'action' => function () {
return $this->site()->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => 'site/children',
'method' => 'GET',
'action' => function () {
return $this->site()->children();
}
],
[
'pattern' => 'site/children',
'method' => 'POST',
'action' => function () {
return $this->site()->createChild($this->requestBody());
}
],
[
'pattern' => 'site/children/blueprints',
'method' => 'GET',
'action' => function () {
return $this->site()->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => 'site/children/search',
'method' => 'POST',
'action' => function () {
return $this->site()->children()->query($this->requestBody());
}
],
[
'pattern' => 'site/find',
'method' => 'POST',
'action' => function () {
return $this->site()->find(false, ...$this->requestBody());
}
],
[
'pattern' => 'site/title',
'method' => 'PATCH',
'action' => function () {
return $this->site()->changeTitle($this->requestBody('title'));
}
],
[
'pattern' => 'site/search',
'method' => 'GET',
'action' => function () {
return $this->site()
->index(true)
->filterBy('isReadable', true)
->search($this->requestQuery('q'), [
'score' => [
'id' => 64,
'title' => 64,
]
]);
}
],
[
'pattern' => 'site/sections/(:any)',
'method' => 'GET',
'action' => function (string $sectionName) {
if ($section = $this->site()->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => 'site/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $fieldName, string $path = null) {
return $this->fieldApi($this->site(), $fieldName, $path);
}
]
];

View File

@@ -0,0 +1,72 @@
<?php
/**
* System Routes
*/
return [
[
'pattern' => 'system',
'method' => 'GET',
'auth' => false,
'action' => function () {
$system = $this->kirby()->system();
if ($this->kirby()->user()) {
return $system;
} else {
if ($system->isOk() === true) {
$info = $this->resolve($system)->view('login')->toArray();
} else {
$info = $this->resolve($system)->view('troubleshooting')->toArray();
}
return [
'status' => 'ok',
'data' => $info,
'type' => 'model'
];
}
}
],
[
'pattern' => 'system/register',
'method' => 'POST',
'action' => function () {
return $this->kirby()->system()->register($this->requestBody('license'), $this->requestBody('email'));
}
],
[
'pattern' => 'system/install',
'method' => 'POST',
'auth' => false,
'action' => function () {
$system = $this->kirby()->system();
$auth = $this->kirby()->auth();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException('Invalid CSRF token');
}
if ($system->isOk() === false) {
throw new Exception('The server is not setup correctly');
}
if ($system->isInstalled() === true) {
throw new Exception('The panel is already installed');
}
// create the first user
$user = $this->users()->create($this->requestBody());
$token = $user->login($this->requestBody('password'));
return [
'status' => 'ok',
'token' => $token,
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
]
];

View File

@@ -0,0 +1,24 @@
<?php
/**
* Translations Routes
*/
return [
[
'pattern' => 'translations',
'method' => 'GET',
'auth' => false,
'action' => function () {
return $this->kirby()->translations();
}
],
[
'pattern' => 'translations/(:any)',
'method' => 'GET',
'auth' => false,
'action' => function (string $code) {
return $this->kirby()->translations()->find($code);
}
]
];

137
kirby/config/api/routes/users.php Executable file
View File

@@ -0,0 +1,137 @@
<?php
use Kirby\Toolkit\F;
/**
* User Routes
*/
return [
[
'pattern' => 'users',
'method' => 'GET',
'action' => function () {
return $this->users();
}
],
[
'pattern' => 'users',
'method' => 'POST',
'action' => function () {
return $this->users()->create($this->requestBody());
}
],
[
'pattern' => 'users/search',
'method' => 'POST',
'action' => function () {
return $this->users()->query($this->requestBody());
}
],
[
'pattern' => 'users/(:any)',
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id);
}
],
[
'pattern' => 'users/(:any)',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => 'users/(:any)',
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->delete();
}
],
[
'pattern' => 'users/(:any)/avatar',
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->avatar();
}
],
[
'pattern' => 'users/(:any)/avatar',
'method' => 'POST',
'action' => function (string $id) {
if ($avatar = $this->user($id)->avatar()) {
$avatar->delete();
}
return $this->upload(function ($source, $filename) use ($id) {
return $this->user($id)->createFile([
'filename' => 'profile.' . F::extension($filename),
'template' => 'avatar',
'source' => $source
]);
}, $single = true);
}
],
[
'pattern' => 'users/(:any)/avatar',
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->avatar()->delete();
}
],
[
'pattern' => 'users/(:any)/email',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeEmail($this->requestBody('email'));
}
],
[
'pattern' => 'users/(:any)/language',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeLanguage($this->requestBody('language'));
}
],
[
'pattern' => 'users/(:any)/name',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeName($this->requestBody('name'));
}
],
[
'pattern' => 'users/(:any)/password',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changePassword($this->requestBody('password'));
}
],
[
'pattern' => 'users/(:any)/role',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeRole($this->requestBody('role'));
}
],
[
'pattern' => 'users/(:any)/sections/(:any)',
'method' => 'GET',
'action' => function (string $id, string $sectionName) {
if ($section = $this->user($id)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => 'users/(:any)/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
if ($user = $this->user($id)) {
return $this->fieldApi($user, $fieldName, $path);
}
}
]
];

7
kirby/config/blueprints.php Executable file
View File

@@ -0,0 +1,7 @@
<?php
return [
'files/default' => __DIR__ . '/blueprints/file.yml',
'pages/default' => __DIR__ . '/blueprints/page.yml',
'site' => __DIR__ . '/blueprints/site.yml'
];

View File

@@ -0,0 +1,2 @@
name: File
title: file

View File

@@ -0,0 +1,3 @@
name: Page
title: Page

View File

@@ -0,0 +1,7 @@
name: Site
title: Site
sections:
pages:
headline: Pages
type: pages

95
kirby/config/components.php Executable file
View File

@@ -0,0 +1,95 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Filename;
use Kirby\Cms\FileVersion;
use Kirby\Cms\Model;
use Kirby\Cms\Response;
use Kirby\Cms\Template;
use Kirby\Data\Data;
use Kirby\Exception\NotFoundException;
use Kirby\Image\Darkroom;
use Kirby\Text\SmartyPants;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Tpl as Snippet;
return [
'file::version' => function (App $kirby, Model $file, array $options = []) {
if ($file->isResizable() === false) {
return $file;
}
// pre-calculate all thumb attributes
$darkroom = Darkroom::factory(option('thumbs.driver', 'gd'), option('thumbs', []));
$attributes = $darkroom->preprocess($file->root(), $options);
// create url and root
$parent = $file->parent();
$mediaRoot = $parent->mediaRoot() . '/' . $file->mediaHash();
$dst = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}';
$thumbRoot = (new Filename($file->root(), $dst, $attributes))->toString();
$thumbName = basename($thumbRoot);
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
if (file_exists($thumbRoot) === false) {
try {
Data::write($job, array_merge($attributes, [
'filename' => $file->filename()
]));
} catch (Throwable $e) {
return $file;
}
}
return new FileVersion([
'modifications' => $options,
'original' => $file,
'root' => $thumbRoot,
'url' => $parent->mediaUrl() . '/' . $file->mediaHash() . '/' . $thumbName,
]);
},
'file::url' => function (App $kirby, Model $file) {
return $file->mediaUrl();
},
'markdown' => function (App $kirby, string $text = null, array $options = []): string {
static $markdown;
if (isset($markdown) === false) {
$parser = ($options['extra'] ?? false) === true ? 'ParsedownExtra' : 'Parsedown';
$markdown = new $parser;
$markdown->setBreaksEnabled($options['breaks'] ?? true);
}
// we need the @ here, because parsedown has some notice issues :(
return @$markdown->text($text);
},
'smartypants' => function (App $kirby, string $text = null, array $options = []): string {
static $smartypants;
$smartypants = $smartypants ?? new Smartypants($options);
return $smartypants->parse($text);
},
'snippet' => function (App $kirby, string $name, array $data = []) {
$file = $kirby->root('snippets') . '/' . $name . '.php';
if (file_exists($file) === false) {
$file = $kirby->extensions('snippets')[$name] ?? null;
}
return Snippet::load($file, $data);
},
'template' => function (App $kirby, string $name, string $type = 'html', string $defaultType = 'html') {
return new Template($name, $type, $defaultType);
},
'thumb' => function (App $kirby, string $src, string $dst, array $options) {
$darkroom = Darkroom::factory(option('thumbs.driver', 'gd'), option('thumbs', []));
$options = $darkroom->preprocess($src, $options);
$root = (new Filename($src, $dst, $options))->toString();
F::copy($src, $root);
$darkroom->process($root, $options);
return $root;
},
];

27
kirby/config/fields.php Executable file
View File

@@ -0,0 +1,27 @@
<?php
return [
'checkboxes' => __DIR__ . '/fields/checkboxes.php',
'date' => __DIR__ . '/fields/date.php',
'email' => __DIR__ . '/fields/email.php',
'files' => __DIR__ . '/fields/files.php',
'headline' => __DIR__ . '/fields/headline.php',
'hidden' => __DIR__ . '/fields/hidden.php',
'info' => __DIR__ . '/fields/info.php',
'line' => __DIR__ . '/fields/line.php',
'multiselect' => __DIR__ . '/fields/multiselect.php',
'number' => __DIR__ . '/fields/number.php',
'pages' => __DIR__ . '/fields/pages.php',
'radio' => __DIR__ . '/fields/radio.php',
'range' => __DIR__ . '/fields/range.php',
'select' => __DIR__ . '/fields/select.php',
'structure' => __DIR__ . '/fields/structure.php',
'tags' => __DIR__ . '/fields/tags.php',
'tel' => __DIR__ . '/fields/tel.php',
'text' => __DIR__ . '/fields/text.php',
'textarea' => __DIR__ . '/fields/textarea.php',
'time' => __DIR__ . '/fields/time.php',
'toggle' => __DIR__ . '/fields/toggle.php',
'url' => __DIR__ . '/fields/url.php',
'users' => __DIR__ . '/fields/users.php'
];

View File

@@ -0,0 +1,64 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
return [
'mixins' => ['options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Arranges the checkboxes in the given number of columns
*/
'columns' => function (int $columns = 1) {
return $columns;
},
/**
* Default value for the field, which will be used when a Page/File/User is created
*/
'default' => function ($default = null) {
return Str::split($default, ',');
},
/**
* Maximum number of checked boxes
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Minimum number of checked boxes
*/
'min' => function (int $min = null) {
return $min;
},
'value' => function ($value = null) {
return Str::split($value, ',');
},
],
'computed' => [
'options' => function (): array {
return $this->getOptions();
},
'default' => function () {
return $this->sanitizeOptions($this->default);
},
'value' => function () {
return $this->sanitizeOptions($this->value);
},
],
'save' => function ($value): string {
return A::join($value, ', ');
},
'validations' => [
'options',
'max',
'min'
]
];

76
kirby/config/fields/date.php Executable file
View File

@@ -0,0 +1,76 @@
<?php
return [
'props' => [
/**
* Default date when a new Page/File/User gets created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Changes the calendar icon to something custom
*/
'icon' => function (string $icon = "calendar") {
return $icon;
},
/**
* Youngest date, which can be selected/saved
*/
'max' => function (string $max = null) {
return $this->toDate($max);
},
/**
* Oldest date, which can be selected/saved
*/
'min' => function (string $min = null) {
return $this->toDate($min);
},
/**
* The placeholder is not available
*/
'placeholder' => null,
/**
* Pass true or an array of time field options to show the time selector.
*/
'time' => function ($time = false) {
return $time;
},
/**
* Must be a parseable date string
*/
'value' => function ($value = null) {
return $value;
},
],
'computed' => [
'default' => function () {
return $this->toDate($this->default);
},
'format' => function () {
return $this->props['format'] ?? ($this->time() === false ? 'Y-m-d' : 'Y-m-d H:i');
},
'value' => function () {
return $this->toDate($this->value);
},
],
'methods' => [
'toDate' => function ($value) {
if ($timestamp = timestamp($value, $this->time['step'] ?? 5)) {
return date(DATE_W3C, $timestamp);
}
return null;
}
],
'save' => function ($value) {
if ($value !== null && $date = strtotime($value)) {
return date($this->format(), $date);
}
return '';
},
'validations' => [
'date'
]
];

38
kirby/config/fields/email.php Executable file
View File

@@ -0,0 +1,38 @@
<?php
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
/**
* Sets the HTML5 autocomplete mode for the input
*/
'autocomplete' => function (string $autocomplete = 'email') {
return $autocomplete;
},
/**
* Changes the email icon to something custom
*/
'icon' => function (string $icon = 'email') {
return $icon;
},
/**
* Custom placeholder text, when the field is empty.
*/
'placeholder' => function ($value = null) {
return I18n::translate($value, $value) ?? I18n::translate('email.placeholder');
}
],
'validations' => [
'minlength',
'maxlength',
'email'
]
];

188
kirby/config/fields/files.php Executable file
View File

@@ -0,0 +1,188 @@
<?php
use Kirby\Toolkit\A;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'autofocus' => null,
'icon' => null,
'placeholder' => null,
/**
* Sets the file(s), which are selected by default when a new page is created
*/
'default' => function ($default = null) {
return $default;
},
/**
* The placeholder text if no pages have been selected yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Image settings for each item
*/
'image' => function (array $image = null) {
return $image ?? [];
},
/**
* Info text
*/
'info' => function (string $info = null) {
return $info;
},
/**
* Changes the layout of the selected files. Available layouts: list, cards
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* Minimum number of required files
*/
'min' => function (int $min = null) {
return $min;
},
/**
* Maximum number of allowed files
*/
'max' => function (int $max = null) {
return $max;
},
/**
* If false, only a single file can be selected
*/
'multiple' => function (bool $multiple = true) {
return $multiple;
},
/**
* Query for the files to be included
*/
'query' => function (string $query = 'page.files') {
return $query;
},
/**
* Layout size for cards
*/
'size' => function (string $size = null) {
return $size;
},
/**
* Main text
*/
'text' => function (string $text = '{{ file.filename }}') {
return $text;
},
'value' => function ($value = null) {
return $value;
}
],
'computed' => [
'parentModel' => function () {
if (is_string($this->parent) === true && $model = $this->model()->query($this->parent, 'Kirby\Cms\Model')) {
return $model;
}
return $this->model();
},
'parent' => function () {
return $this->parentModel->apiUrl(true);
},
'default' => function () {
return $this->toFiles($this->default);
},
'value' => function () {
return $this->toFiles($this->value);
},
],
'methods' => [
'fileResponse' => function ($file) {
if ($this->layout === 'list') {
$thumb = [
'width' => 100,
'height' => 100
];
} else {
$thumb = [
'width' => 400,
'height' => 400
];
}
$image = $file->panelImage($this->image, $thumb);
$model = $this->model();
$uuid = $file->parent() === $model ? $file->filename() : $file->id();
return [
'filename' => $file->filename(),
'text' => $file->toString($this->text),
'link' => $file->panelUrl(true),
'id' => $file->id(),
'uuid' => $uuid,
'url' => $file->url(),
'info' => $file->toString($this->info ?? false),
'image' => $image,
'icon' => $file->panelIcon($image),
'type' => $file->type(),
];
},
'toFiles' => function ($value = null) {
$files = [];
$kirby = kirby();
foreach (Yaml::decode($value) as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}
if ($id !== null && ($file = $this->kirby()->file($id, $this->model()))) {
$files[] = $this->fileResponse($file);
}
}
return $files;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
$files = $field->model()->query($field->query(), 'Kirby\Cms\Files');
$data = [];
foreach ($files as $index => $file) {
$data[] = $field->fileResponse($file);
}
return $data;
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'uuid');
},
'validations' => [
'max',
'min'
]
];

View File

@@ -0,0 +1,27 @@
<?php
return [
'save' => false,
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => null,
'help' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* If false, the prepended number will be hidden
*/
'numbered' => function (bool $numbered = true) {
return $numbered;
}
]
];

3
kirby/config/fields/hidden.php Executable file
View File

@@ -0,0 +1,3 @@
<?php
return [];

23
kirby/config/fields/info.php Executable file
View File

@@ -0,0 +1,23 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
'text' => function ($value = null) {
return I18n::translate($value, $value);
},
],
'computed' => [
'text' => function () {
$text = $this->text;
if ($model = $this->model()) {
$text = $this->model()->toString($text);
}
return kirbytext($text);
}
],
'save' => false,
];

5
kirby/config/fields/line.php Executable file
View File

@@ -0,0 +1,5 @@
<?php
return [
'save' => false
];

View File

@@ -0,0 +1,43 @@
<?php
use Kirby\Form\Options;
return [
'props' => [
/**
* API settings for options requests. This will only take affect when <code>options</code> is set to <code>api</code>.
*/
'api' => function ($api = null) {
return $api;
},
/**
* An array with options
*/
'options' => function ($options = []) {
return $options;
},
/**
* Query settings for options queries. This will only take affect when <code>options</code> is set to <code>query</code>.
*/
'query' => function ($query = null) {
return $query;
},
],
'methods' => [
'getOptions' => function () {
return Options::factory(
$this->options(),
$this->props,
$this->model()
);
},
'sanitizeOption' => function ($option) {
$allowed = array_column($this->options(), 'value');
return in_array($option, $allowed, true) === true ? $option : null;
},
'sanitizeOptions' => function ($options) {
$allowed = array_column($this->options(), 'value');
return array_intersect($options, $allowed);
},
]
];

View File

@@ -0,0 +1,25 @@
<?php
return [
'extends' => 'tags',
'props' => [
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
return $icon;
},
/**
* Enable/disable the search in the dropdown
*/
'search' => function (bool $search = true) {
return $search;
},
/**
* If true, entries will be sorted alphabetically on selection
*/
'sort' => function (bool $sort = false) {
return $sort;
},
]
];

49
kirby/config/fields/number.php Executable file
View File

@@ -0,0 +1,49 @@
<?php
return [
'props' => [
/**
* Default number that will be saved when a new Page/User/File is created
*/
'default' => function ($default = null) {
return $this->toNumber($default);
},
/**
* The lowest allowed number
*/
'min' => function (float $min = null) {
return $min;
},
/**
* The highest allowed number
*/
'max' => function (float $max = null) {
return $max;
},
/**
* Allowed incremental steps between numbers (i.e 0.5)
*/
'step' => function ($step = 1) {
return $this->toNumber($step);
},
'value' => function ($value = null) {
return $this->toNumber($value);
}
],
'methods' => [
'toNumber' => function ($value) {
if ($this->isEmpty($value) === true) {
return null;
}
$value = str_replace(',', '.', $value);
$value = floatval($value);
return $value;
}
],
'validations' => [
'min',
'max'
]
];

188
kirby/config/fields/pages.php Executable file
View File

@@ -0,0 +1,188 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Default selected page(s) when a new Page/File/User is created
*/
'default' => function ($default = null) {
return $this->toPages($default);
},
/**
* The placeholder text if no pages have been selected yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Image settings for each item
*/
'image' => function (array $image = null) {
return $image ?? [];
},
/**
* Info text
*/
'info' => function (string $info = null) {
return $info;
},
/**
* Changes the layout of the selected files. Available layouts: list, cards
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* The minimum number of required selected pages
*/
'min' => function (int $min = null) {
return $min;
},
/**
* The maximum number of allowed selected pages
*/
'max' => function (int $max = null) {
return $max;
},
/**
* If false, only a single page can be selected
*/
'multiple' => function (bool $multiple = true) {
return $multiple;
},
/**
* Optional query to select a specific set of pages
*/
'query' => function (string $query = null) {
return $query;
},
/**
* Layout size for cards
*/
'size' => function (string $size = null) {
return $size;
},
/**
* Main text
*/
'text' => function (string $text = null) {
return $text;
},
'value' => function ($value = null) {
return $this->toPages($value);
},
],
'methods' => [
'pageResponse' => function ($page) {
if ($this->layout === 'list') {
$thumb = [
'width' => 100,
'height' => 100
];
} else {
$thumb = [
'width' => 400,
'height' => 400
];
}
$image = $page->panelImage($this->image, $thumb);
$model = $this->model();
return [
'text' => $page->toString($this->text ?? '{{ page.title }}'),
'link' => $page->panelUrl(true),
'id' => $page->id(),
'info' => $page->toString($this->info ?? false),
'image' => $image,
'icon' => $page->panelIcon($image),
'hasChildren' => $page->hasChildren(),
];
},
'toPages' => function ($value = null) {
$pages = [];
$kirby = kirby();
foreach (Yaml::decode($value) as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}
if ($id !== null && ($page = $kirby->page($id))) {
$pages[] = $this->pageResponse($page);
}
}
return $pages;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
$query = $field->query();
if ($query) {
$pages = $field->model()->query($query, 'Kirby\Cms\Pages');
$model = null;
} else {
if (!$parent = $this->site()->find($this->requestQuery('parent'))) {
$parent = $this->site();
}
$pages = $parent->children();
$model = [
'id' => $parent->id() == '' ? null : $parent->id(),
'title' => $parent->title()->value()
];
}
$children = [];
foreach ($pages as $index => $page) {
if ($page->isReadable() === true) {
$children[] = $field->pageResponse($page);
}
}
return [
'model' => $model,
'pages' => $children
];
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'id');
},
'validations' => [
'max',
'min'
]
];

32
kirby/config/fields/radio.php Executable file
View File

@@ -0,0 +1,32 @@
<?php
return [
'mixins' => ['options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Arranges the radio buttons in the given number of columns
*/
'columns' => function (int $columns = 1) {
return $columns;
},
],
'computed' => [
'options' => function (): array {
return $this->getOptions();
},
'default' => function () {
return $this->sanitizeOption($this->default);
},
'value' => function () {
return $this->sanitizeOption($this->value) ?? '';
}
]
];

24
kirby/config/fields/range.php Executable file
View File

@@ -0,0 +1,24 @@
<?php
return [
'extends' => 'number',
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* The maximum value on the slider
*/
'max' => function (float $max = 100) {
return $max;
},
/**
* Enables/disables the tooltip and set the before and after values
*/
'tooltip' => function ($tooltip = true) {
return $tooltip;
},
]
];

18
kirby/config/fields/select.php Executable file
View File

@@ -0,0 +1,18 @@
<?php
return [
'extends' => 'radio',
'props' => [
/**
* Unset inherited props
*/
'columns' => null,
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
return $icon;
},
]
];

159
kirby/config/fields/structure.php Executable file
View File

@@ -0,0 +1,159 @@
<?php
use Kirby\Form\Form;
use Kirby\Cms\Blueprint;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'autofocus' => null,
'icon' => null,
'placeholder' => null,
/**
* Optional columns definition to only show selected fields in the structure table.
*/
'columns' => function (array $columns = []) {
// lower case all keys, because field names will
// be lowercase as well.
return array_change_key_case($columns);
},
/**
* Fields setup for the structure form. Works just like fields in regular forms.
*/
'fields' => function (array $fields) {
return $fields;
},
/**
* The number of entries that will be displayed on a single page. Afterwards pagination kicks in.
*/
'limit' => function (int $limit = null) {
return $limit;
},
/**
* Maximum allowed entries in the structure. Afterwards the "Add" button will be switched off.
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Minimum required entries in the structure
*/
'min' => function (int $min = null) {
return $min;
},
/**
* Toggles drag & drop sorting
*/
'sortable' => function (bool $sortable = null) {
return $sortable;
},
/**
* Sorts the entries by the given field and order (i.e. title desc)
* Drag & drop is disabled in this case
*/
'sortBy' => function (string $sort = null) {
return $sort;
}
],
'computed' => [
'default' => function () {
return $this->rows($this->default);
},
'value' => function () {
return $this->rows($this->value);
},
'fields' => function () {
return $this->form()->fields()->toArray();
},
'columns' => function () {
$columns = [];
if (empty($this->columns)) {
foreach ($this->fields as $field) {
// Skip hidden fields.
// They should never be included as column
if ($field['type'] === 'hidden') {
continue;
}
$columns[$field['name']] = [
'type' => $field['type'],
'label' => $field['label'] ?? $field['name']
];
}
} else {
foreach ($this->columns as $columnName => $columnProps) {
if (is_array($columnProps) === false) {
$columnProps = [];
}
$field = $this->fields[$columnName] ?? null;
if (empty($field) === true) {
continue;
}
$columns[$columnName] = array_merge($columnProps, [
'type' => $field['type'],
'label' => $field['label'] ?? $field['name']
]);
}
}
return $columns;
},
],
'methods' => [
'rows' => function ($value) {
$rows = Yaml::decode($value);
$value = [];
foreach ($rows as $index => $row) {
if (is_array($row) === false) {
continue;
}
$value[] = $this->form($row)->values();
}
return $value;
},
'form' => function (array $values = []) {
return new Form([
'fields' => $this->attrs['fields'],
'values' => $values,
'model' => $this->model
]);
},
],
'api' => function () {
return [
[
'pattern' => 'validate',
'method' => 'ALL',
'action' => function () {
return array_values($this->field()->form($this->requestBody())->errors());
}
]
];
},
'save' => function () {
$data = [];
foreach ($this->value() as $row) {
$data[] = $this->form($row)->data();
}
return $data;
},
'validations' => [
'min',
'max'
]
];

91
kirby/config/fields/tags.php Executable file
View File

@@ -0,0 +1,91 @@
<?php
return [
'mixins' => ['options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'placeholder' => null,
/**
* If set to <code>all</code>, any type of input is accepted. If set to <code>options</code> only the predefined options are accepted as input.
*/
'accept' => function ($value = 'all') {
return V::in($value, ['all', 'options']) ? $value : 'all';
},
/**
* Changes the tag icon
*/
'icon' => function ($icon = 'tag') {
return $icon;
},
/**
* Minimum number of required entries/tags
*/
'min' => function (int $min = null) {
return $min;
},
/**
* Maximum number of allowed entries/tags
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Custom tags separator, which will be used to store tags in the content file
*/
'separator' => function (string $separator = ',') {
return $separator;
},
],
'computed' => [
'options' => function () {
return $this->getOptions();
},
'default' => function (): array {
return $this->toTags($this->default);
},
'value' => function (): array {
return $this->toTags($this->value);
}
],
'methods' => [
'toTags' => function ($value) {
$options = $this->options();
// transform into value-text objects
return array_map(function ($option) use ($options) {
// already a valid object
if (is_array($option) === true && isset($option['value'], $option['text']) === true) {
return $option;
}
$index = array_search($option, array_column($options, 'value'));
if ($index !== false) {
return $options[$index];
}
return [
'value' => $option,
'text' => $option,
];
}, Str::split($value));
}
],
'save' => function (array $value = null): string {
return A::join(
A::pluck($value, 'value'),
$this->separator() . ' '
);
},
'validations' => [
'min',
'max'
]
];

27
kirby/config/fields/tel.php Executable file
View File

@@ -0,0 +1,27 @@
<?php
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
'spellcheck' => null,
/**
* Sets the HTML5 autocomplete attribute
*/
'autocomplete' => function (string $autocomplete = 'tel') {
return $autocomplete;
},
/**
* Changes the phone icon
*/
'icon' => function (string $icon = 'phone') {
return $icon;
}
]
];

103
kirby/config/fields/text.php Executable file
View File

@@ -0,0 +1,103 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
return [
'props' => [
/**
* The field value will be converted with the selected converter before the value gets saved. Available converters: lower, upper, ucfirst, slug
*/
'converter' => function ($value = null) {
if ($value !== null && in_array($value, array_keys($this->converters())) === false) {
throw new InvalidArgumentException([
'key' => 'field.converter.invalid',
'data' => ['converter' => $value]
]);
}
return $value;
},
/**
* Shows or hides the character counter in the top right corner
*/
'counter' => function (bool $counter = true) {
return $counter;
},
/**
* Maximum number of allowed characters
*/
'maxlength' => function (int $maxlength = null) {
return $maxlength;
},
/**
* Minimum number of required characters
*/
'minlength' => function (int $minlength = null) {
return $minlength;
},
/**
* A regular expression, which will be used to validate the input
*/
'pattern' => function (string $pattern = null) {
return $pattern;
},
/**
* If false, spellcheck will be switched off
*/
'spellcheck' => function (bool $spellcheck = false) {
return $spellcheck;
},
],
'computed' => [
'default' => function () {
return $this->convert($this->default);
},
'value' => function () {
return (string)$this->convert($this->value);
}
],
'methods' => [
'convert' => function ($value) {
if ($this->converter() === null) {
return $value;
}
$value = trim($value);
$converter = $this->converters()[$this->converter()];
if (is_array($value) === true) {
return array_map($converter, $value);
}
return call_user_func($converter, $value);
},
'converters' => function (): array {
return [
'lower' => function ($value) {
return Str::lower($value);
},
'slug' => function ($value) {
return Str::slug($value);
},
'ucfirst' => function ($value) {
return Str::ucfirst($value);
},
'upper' => function ($value) {
return Str::upper($value);
},
];
},
],
'validations' => [
'minlength',
'maxlength'
]
];

View File

@@ -0,0 +1,61 @@
<?php
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
/**
* Enables/disables the format buttons. Can either be true/false or a list of allowed buttons. Available buttons: headlines, italic, bold, link, email, list, code, ul, ol
*/
'buttons' => function ($buttons = true) {
return $buttons;
},
/**
* Enables/disables the character counter in the top right corner
*/
'counter' => function (bool $counter = true) {
return $counter;
},
/**
* Sets the default text when a new Page/File/User is created
*/
'default' => function (string $default = null) {
return trim($default);
},
/**
* Maximum number of allowed characters
*/
'maxlength' => function (int $maxlength = null) {
return $maxlength;
},
/**
* Minimum number of required characters
*/
'minlength' => function (int $minlength = null) {
return $minlength;
},
/**
* Changes the size of the textarea. Available sizes: small, medium, large, huge
*/
'size' => function (string $size = null) {
return $size;
},
'value' => function (string $value = null) {
return trim($value);
}
],
'validations' => [
'minlength',
'maxlength'
]
];

68
kirby/config/fields/time.php Executable file
View File

@@ -0,0 +1,68 @@
<?php
return [
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Sets the default time when a new Page/File/User is created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Changes the clock icon
*/
'icon' => function (string $icon = 'clock') {
return $icon;
},
/**
* 12 or 24 hour notation. If 12, an AM/PM selector will be shown.
*/
'notation' => function (int $value = 24) {
return $value === 24 ? 24 : 12;
},
/**
* The interval between minutes in the minutes select dropdown.
*/
'step' => function (int $step = 5) {
return $step;
},
'value' => function ($value = null) {
return $value;
}
],
'computed' => [
'default' => function () {
return $this->toTime($this->default);
},
'format' => function () {
return $this->notation === 24 ? 'H:i' : 'h:i a';
},
'value' => function () {
return $this->toTime($this->value);
}
],
'methods' => [
'toTime' => function ($value) {
if ($timestamp = timestamp($value, $this->step)) {
return date('H:i', $timestamp);
}
return null;
}
],
'save' => function ($value): string {
if ($timestamp = strtotime($value)) {
return date($this->format, $timestamp);
}
return '';
},
'validations' => [
'time',
]
];

64
kirby/config/fields/toggle.php Executable file
View File

@@ -0,0 +1,64 @@
<?php
use Kirby\Exception\InvalidArgumentException;
return [
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Default value which will be saved when a new Page/User/File is created
*/
'default' => function ($value = null) {
return $this->toBool($value);
},
/**
* Sets the text next to the toggle. The text can be a string or an array of two options. The first one is the negative text and the second one the positive. The text will automatically switch when the toggle is triggered.
*/
'text' => function ($value = null) {
if (is_array($value) === true) {
if (A::isAssociative($value) === true) {
return I18n::translate($value, $value);
}
foreach ($value as $key => $val) {
$value[$key] = I18n::translate($val, $val);
}
return $value;
}
return I18n::translate($value, $value);
},
],
'computed' => [
'value' => function () {
if ($this->props['value'] === null) {
return $this->default();
} else {
return $this->toBool($this->props['value']);
}
}
],
'methods' => [
'toBool' => function ($value) {
return in_array($value, [true, 'true', 1, '1', 'on'], true) === true;
}
],
'save' => function (): string {
return $this->value() === true ? 'true' : 'false';
},
'validations' => [
'boolean',
'required' => function ($value) {
if ($this->isRequired() && ($value === false || $this->isEmpty($value))) {
throw new InvalidArgumentException([
'key' => 'form.field.required'
]);
}
},
]
];

39
kirby/config/fields/url.php Executable file
View File

@@ -0,0 +1,39 @@
<?php
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
'spellcheck' => null,
/**
* Sets the HTML5 autocomplete attribute
*/
'autocomplete' => function (string $autocomplete = 'url') {
return $autocomplete;
},
/**
* Changes the link icon
*/
'icon' => function (string $icon = 'url') {
return $icon;
},
/**
* Sets custom placeholder text, when the field is empty
*/
'placeholder' => function ($value = null) {
return I18n::translate($value, $value) ?? 'https://example.com';
}
],
'validations' => [
'minlength',
'maxlength',
'url'
],
];

95
kirby/config/fields/users.php Executable file
View File

@@ -0,0 +1,95 @@
<?php
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Default selected user(s) when a new Page/File/User is created
*/
'default' => function ($default = null) {
if ($default === false) {
return [];
}
if ($default === null && $user = $this->kirby()->user()) {
return [
$this->userResponse($user)
];
}
return $this->toUsers($default);
},
/**
* The minimum number of required selected users
*/
'min' => function (int $min = null) {
return $min;
},
/**
* The maximum number of allowed selected users
*/
'max' => function (int $max = null) {
return $max;
},
/**
* If false, only a single user can be selected
*/
'multiple' => function (bool $multiple = true) {
return $multiple;
},
'value' => function ($value = null) {
return $this->toUsers($value);
},
],
'methods' => [
'userResponse' => function ($user) {
$avatar = function ($user) {
if ($avatar = $user->avatar()) {
return [
'url' => $avatar->crop(512)->url()
];
}
return null;
};
return [
'username' => $user->username(),
'id' => $user->id(),
'email' => $user->email(),
'avatar' => $avatar($user)
];
},
'toUsers' => function ($value = null) {
$users = [];
$kirby = kirby();
foreach (Yaml::decode($value) as $email) {
if (is_array($email) === true) {
$email = $email['email'] ?? null;
}
if ($email !== null && ($user = $kirby->user($email))) {
$users[] = $this->userResponse($user);
}
}
return $users;
}
],
'save' => function ($value = null) {
return A::pluck($value, 'email');
},
'validations' => [
'max',
'min'
]
];

764
kirby/config/helpers.php Executable file
View File

@@ -0,0 +1,764 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Html;
use Kirby\Cms\Response;
use Kirby\Cms\Url;
use Kirby\Exception\Exception;
use Kirby\Http\Server;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\F;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\View;
/**
* Generates a list of HTML attributes
*
* @param array $attr A list of attributes as key/value array
* @param string $before An optional string that will be prepended if the result is not empty
* @param string $after An optional string that will be appended if the result is not empty
* @return string
*/
function attr(array $attr = null, $before = null, $after = null)
{
if ($attrs = Html::attr($attr)) {
return $before . $attrs . $after;
}
return null;
}
/**
* Returns the result of a collection by name
*
* @param string $name
* @return Collection|null
*/
function collection(string $name)
{
return App::instance()->collection($name);
}
/**
* Checks / returns a CSRF token
*
* @param string $check Pass a token here to compare it to the one in the session
* @return string|boolean Either the token or a boolean check result
*/
function csrf(string $check = null)
{
$session = App::instance()->session();
// check explicitly if there have been no arguments at all;
// checking for null introduces a security issue because null could come
// from user input or bugs in the calling code!
if (func_num_args() === 0) {
// no arguments, generate/return a token
$token = $session->get('csrf');
if (is_string($token) !== true) {
$token = bin2hex(random_bytes(32));
$session->set('csrf', $token);
}
return $token;
} elseif (is_string($check) === true && is_string($session->get('csrf')) === true) {
// argument has been passed, check the token
return hash_equals($session->get('csrf'), $check) === true;
}
return false;
}
/**
* Creates one or multiple CSS link tags
*
* @param string|array $url Relative or absolute URLs, an array of URLs or `@auto` for automatic template css loading
* @param string|array $options Pass an array of attributes for the link tag or a media attribute string
* @return string|null
*/
function css($url, $options = null)
{
if (is_array($url) === true) {
$links = array_map(function ($url) use ($options) {
return css($url, $options);
}, $url);
return implode(PHP_EOL, $links);
}
$href = $url === '@auto' ? Url::toTemplateAsset('css/templates', 'css') : Url::to($url);
$attr = [
'href' => $href,
'rel' => 'stylesheet'
];
if (is_string($options) === true) {
$attr['media'] = $options;
}
if (is_array($options) === true) {
$attr = array_merge($options, $attr);
}
return '<link ' . attr($attr) . '>';
}
/**
* Simple object and variable dumper
* to help with debugging.
*
* @param mixed $variable
* @param boolean $echo
* @return string
*/
function dump($variable, bool $echo = true): string
{
if (Server::cli() === true) {
$output = print_r($variable, true) . PHP_EOL;
} else {
$output = '<pre>' . print_r($variable, true) . '</pre>';
}
if ($echo === true) {
echo $output;
}
return $output;
}
/**
* 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);
}
/**
* 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('Kirby\Toolkit\Escape', $context) === true) {
return Escape::$context($string, $strict);
}
return $string;
}
/**
* Shortcut for $kirby->request()->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 App::instance()->request()->get($key, $default);
}
/**
* Embeds a Github Gist
*
* @param string $url
* @param string $file
* @return string
*/
function gist(string $url, string $file = null): string
{
return kirbytag([
'gist' => $url,
'file' => $file,
]);
}
/**
* Redirects to the given Urls
* Urls can be relative or absolute.
*
* @param string $url
* @param integer $code
* @return void
*/
function go(string $url = null, int $code = 302)
{
die(Response::redirect($url, $code));
}
/**
* Shortcut for html()
*
* @param string $text unencoded text
* @param bool $keepTags
* @return string
*/
function h(string $string = null, bool $keepTags = false)
{
return Html::encode($string, $keepTags);
}
/**
* Creates safe html by encoding special characters
*
* @param string $text unencoded text
* @param bool $keepTags
* @return string
*/
function html(string $string = null, bool $keepTags = false)
{
return Html::encode($string, $keepTags);
}
/**
* Return an image from any page
* specified by the path
*
* Example:
* <?= image('some/page/myimage.jpg') ?>
*
* @param string $path
* @return File|null
*/
function image(string $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;
}
}
/**
* Runs a number of validators on a set of data and checks if the data is invalid
*
* @param array $data
* @param array $rules
* @param array $messages
* @return false|array
*/
function invalid(array $data = [], array $rules = [], array $messages = [])
{
$errors = [];
foreach ($rules as $field => $validations) {
$validationIndex = -1;
// See: http://php.net/manual/en/types.comparisons.php
// only false for: null, undefined variable, '', []
$filled = isset($data[$field]) && $data[$field] !== '' && $data[$field] !== [];
$message = $messages[$field] ?? $field;
// True if there is an error message for each validation method.
$messageArray = is_array($message);
foreach ($validations as $method => $options) {
if (is_numeric($method) === true) {
$method = $options;
}
$validationIndex++;
if ($method === 'required') {
if ($filled) {
// Field is required and filled.
continue;
}
} elseif ($filled) {
if (is_array($options) === false) {
$options = [$options];
}
array_unshift($options, $data[$field] ?? null);
if (V::$method(...$options) === true) {
// Field is filled and passes validation method.
continue;
}
} else {
// If a field is not required and not filled, no validation should be done.
continue;
}
// If no continue was called we have a failed validation.
if ($messageArray) {
$errors[$field][] = $message[$validationIndex] ?? $field;
} else {
$errors[$field] = $message;
}
}
}
return $errors;
}
/**
* Creates a script tag to load a javascript file
*
* @param string|array $src
* @param string|array $options
* @return void
*/
function js($url, $options = null)
{
if (is_array($url) === true) {
$scripts = array_map(function ($url) use ($options) {
return js($url, $options);
}, $url);
return implode(PHP_EOL, $scripts);
}
$src = $url === '@auto' ? Url::toTemplateAsset('js/templates', 'js') : Url::to($url);
$attr = [
'src' => $src,
];
if (is_bool($options) === true) {
$attr['async'] = $options;
}
if (is_array($options) === true) {
$attr = array_merge($options, $attr);
}
return '<script ' . attr($attr) . '></script>';
}
/**
* Returns the Kirby object in any situation
*
* @return App
*/
function kirby(): App
{
return App::instance();
}
/**
* Makes it possible to use any defined Kirbytag as standalone function
*
* @param string|array $type
* @param string $value
* @param array $attr
* @return string
*/
function kirbytag($type, string $value = null, array $attr = []): string
{
if (is_array($type) === true) {
return App::instance()->kirbytag(key($type), current($type), $type);
}
return App::instance()->kirbytag($type, $value, $attr);
}
/**
* Parses KirbyTags in the given string. Shortcut
* for `$kirby->kirbytags($text, $data)`
*
* @param string $text
* @param array $data
* @return string
*/
function kirbytags(string $text = null, array $data = []): string
{
return App::instance()->kirbytags($text, $data);
}
/**
* Parses KirbyTags and Markdown in the
* given string. Shortcut for `$kirby->kirbytext()`
*
* @param string $text
* @param array $data
* @return string
*/
function kirbytext(string $text = null, array $data = []): string
{
return App::instance()->kirbytext($text, $data);
}
/**
* A super simple class autoloader
*
* @param array $classmap
* @param string $base
* @return void
*/
function load(array $classmap, string $base = null)
{
spl_autoload_register(function ($class) use ($classmap, $base) {
$class = strtolower($class);
if (!isset($classmap[$class])) {
return false;
}
if ($base) {
include $base . '/' . $classmap[$class];
} else {
include $classmap[$class];
}
});
}
/**
* Parses markdown in the given string. Shortcut for
* `$kirby->markdown($text)`
*
* @param string $text
* @return string
*/
function markdown(string $text = null): string
{
return App::instance()->markdown($text);
}
/**
* Shortcut for `$kirby->option($key, $default)`
*
* @param string $key
* @param mixed $default
* @return mixed
*/
function option(string $key, $default = null)
{
return App::instance()->option($key, $default);
}
/**
* Fetches a single page or multiple pages by
* id or the current page when no id is specified
*
* @param string|array ...$id
* @return Page|null
*/
function page(...$id)
{
if (empty($id) === true) {
return App::instance()->site()->page();
}
return App::instance()->site()->find(...$id);
}
/**
* Helper to build page collections
*
* @param string|array ...$id
* @return Pages
*/
function pages(...$id)
{
return App::instance()->site()->find(...$id);
}
/**
* Returns a single param from the URL
*
* @param string $key
* @param string $fallback
* @return string|null
*/
function param(string $key, string $fallback = null): ?string
{
return App::instance()->request()->url()->params()->$key ?? $fallback;
}
/**
* Returns all params from the current Url
*
* @return array
*/
function params(): array
{
return App::instance()->request()->url()->params()->toArray();
}
/**
* 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 mixed
*/
function r($condition, $value, $alternative = null)
{
return $condition ? $value : $alternative;
}
/**
* Rounds the minutes of the given date
* by the defined step
*
* @param string $date
* @param integer $step
* @return string|null
*/
function timestamp(string $date = null, int $step = null): ?string
{
if (V::date($date) === false) {
return null;
}
$date = strtotime($date);
if ($step === null) {
return $date;
}
$hours = date('H', $date);
$minutes = date('i', $date);
$minutes = floor($minutes / $step) * $step;
$minutes = str_pad($minutes, 2, 0, STR_PAD_LEFT);
$date = date('Y-m-d', $date) . ' ' . $hours . ':' . $minutes;
return strtotime($date);
}
/**
* Returns the currrent site object
*
* @return Site
*/
function site()
{
return App::instance()->site();
}
/**
* Determines the size/length of numbers, strings, arrays and countable objects
*
* @param mixed $value
* @return int
*/
function size($value): int
{
if (is_numeric($value)) {
return $value;
}
if (is_string($value)) {
return Str::length(trim($value));
}
if (is_array($value)) {
return count($value);
}
if (is_object($value)) {
if (is_a($value, 'Countable') === true) {
return count($value);
}
if (is_a($value, 'Kirby\Toolkit\Collection') === true) {
return $value->count();
}
}
}
/**
* Enhances the given string with
* smartypants. Shortcut for `$kirby->smartypants($text)`
*
* @param string $text
* @return string
*/
function smartypants(string $text = null): string
{
return App::instance()->smartypants($text);
}
/**
* Embeds a snippet from the snippet folder
*
* @param string $name
* @param array|object $data
* @param boolean $return
* @return string
*/
function snippet(string $name, $data = [], bool $return = false)
{
if (is_object($data) === true) {
$data = ['item' => $data];
}
$snippet = App::instance()->snippet($name, $data);
if ($return === true) {
return $snippet;
}
echo $snippet;
}
/**
* Includes an SVG file by absolute or
* relative file path.
*
* @param string $file
* @return string
*/
function svg(string $file)
{
$root = App::instance()->root();
$file = $root . '/' . $file;
if (file_exists($file) === false) {
return false;
}
ob_start();
include F::realpath($file, $root);
$svg = ob_get_contents();
ob_end_clean();
return $svg;
}
/**
* Returns translate string for key from translation file
*
* @param string|array $key
* @param string|null $fallback
* @return mixed
*/
function t($key, string $fallback = null)
{
return I18n::translate($key, $fallback);
}
/**
* Translates a count
*
* @param string|array $key
* @param int $count
* @return mixed
*/
function tc($key, int $count)
{
return I18n::translateCount($key, $count);
}
/**
* Builds a Twitter link
*
* @param string $username
* @param string $text
* @param string $title
* @param string $class
* @return string
*/
function twitter(string $username, string $text = null, string $title = null, string $class = null): string
{
return kirbytag([
'twitter' => $username,
'text' => $text,
'title' => $title,
'class' => $class
]);
}
/**
* Shortcut for url()
*
* @param string $path
* @param array|null $options
* @return string
*/
function u(string $path = null, $options = null): string
{
return Url::to($path, $options);
}
/**
* Builds an absolute URL for a given path
*
* @param string $path
* @param array $options
* @return string
*/
function url(string $path = null, $options = null): string
{
return Url::to($path, $options);
}
/**
* Creates a video embed via iframe for Youtube or Vimeo
* videos. The embed Urls are automatically detected from
* the given Url.
*
* @param string $url
* @param array $options
* @param array $attr
* @return string
*/
function video(string $url, array $options = [], array $attr = []): string
{
return Html::video($url, $options, $attr);
}
/**
* Embeds a Vimeo video by URL in an iframe
*
* @param string $url
* @param array $options
* @param array $attr
* @return string
*/
function vimeo(string $url, array $options = [], array $attr = []): string
{
return Html::video($url, $options, $attr);
}
/**
* 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|null $string
* @return string
*/
function widont(string $string = null): string
{
return Str::widont($string);
}
/**
* Embeds a Youtube video by URL in an iframe
*
* @param string $url
* @param array $options
* @param array $attr
* @return string
*/
function youtube(string $url, array $options = [], array $attr = []): string
{
return Html::video($url, $options, $attr);
}

389
kirby/config/methods.php Executable file
View File

@@ -0,0 +1,389 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Field;
use Kirby\Cms\File;
use Kirby\Cms\Files;
use Kirby\Cms\Html;
use Kirby\Cms\Structure;
use Kirby\Cms\Page;
use Kirby\Cms\Url;
use Kirby\Data\Json;
use Kirby\Data\Yaml;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
use Kirby\Toolkit\Xml;
/**
* Field method setup
*/
return function (App $app) {
return [
// states
/**
* Converts the field value into a proper boolean and inverts it
*
* @param Field $field
* @return boolean
*/
'isFalse' => function (Field $field): bool {
return $field->toBool() === false;
},
/**
* Converts the field value into a proper boolean
*
* @param Field $field
* @return boolean
*/
'isTrue' => function (Field $field): bool {
return $field->toBool() === true;
},
/**
* Validates the field content with the given validator and parameters
*
* @param string $validator
* @param mixed[] ...$arguments A list of optional validator arguments
* @return boolean
*/
'isValid' => function (Field $field, string $validator, ...$arguments): bool {
return V::$validator($field->value, ...$arguments);
},
// converters
/**
* Parses the field value with the given method
*
* @param Field $field
* @param string $method [',', 'yaml', 'json']
* @return array
*/
'toData' => function (Field $field, string $method = ',') {
switch ($method) {
case 'yaml':
return Yaml::decode($field->value);
case 'json':
return Json::decode($field->value);
default:
return $field->split($method);
}
},
/**
* Converts the field value into a proper boolean
*
* @param Field $field
* @param bool $default Default value if the field is empty
* @return bool
*/
'toBool' => function (Field $field, $default = false): bool {
$value = $field->isEmpty() ? $default : $field->value;
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
},
/**
* Converts the field value to a timestamp or a formatted date
*
* @param Field $field
* @param string $format PHP date formatting string
* @return string|int
*/
'toDate' => function (Field $field, string $format = null) use ($app) {
if (empty($field->value) === true) {
return null;
}
if ($format === null) {
return $field->toTimestamp();
}
return $app->option('date.handler', 'date')($format, $field->toTimestamp());
},
/**
* Returns a file object from a filename in the field
*
* @param Field $field
* @return File|null
*/
'toFile' => function (Field $field) {
return $field->toFiles()->first();
},
/**
* Returns a file collection from a yaml list of filenames in the field
*
* @return Files
*/
'toFiles' => function (Field $field) {
return $field->parent()->files()->find(false, false, ...$field->toData('yaml'));
},
/**
* Converts the field value into a proper float
*
* @param Field $field
* @param float $default Default value if the field is empty
* @return float
*/
'toFloat' => function (Field $field, float $default = 0) {
$value = $field->isEmpty() ? $default : $field->value;
return floatval($value);
},
/**
* Converts the field value into a proper integer
*
* @param Field $field
* @param int $default Default value if the field is empty
* @return int
*/
'toInt' => function (Field $field, int $default = 0) {
$value = $field->isEmpty() ? $default : $field->value;
return intval($value);
},
/**
* Wraps a link tag around the field value. The field value is used as the link text
*
* @param Field $field
* @param mixed $attr1 Can be an optional Url. If no Url is set, the Url of the Page, File or Site will be used. Can also be an array of link attributes
* @param mixed $attr2 If `$attr1` is used to set the Url, you can use `$attr2` to pass an array of additional attributes.
* @return string
*/
'toLink' => function (Field $field, $attr1 = null, $attr2 = null) {
if (is_string($attr1) === true) {
$href = $attr1;
$attr = $attr2;
} else {
$href = $field->parent()->url();
$attr = $attr1;
}
if ($field->parent()->isActive()) {
$attr['aria-current'] = 'page';
}
return Html::a($href, $field->value, $attr ?? []);
},
/**
* Returns a page object from a page id in the field
*
* @param Field $field
* @return Page|null
*/
'toPage' => function (Field $field) use ($app) {
return $field->toPages()->first();
},
/**
* Returns a pages collection from a yaml list of page ids in the field
*
* @param string $separator Can be any other separator to split the field value by
* @return Pages
*/
'toPages' => function (Field $field, string $separator = 'yaml') use ($app) {
return $app->site()->find(false, false, ...$field->toData($separator));
},
/**
* Converts a yaml field to a Structure object
*/
'toStructure' => function (Field $field) {
return new Structure(Yaml::decode($field->value), $field->parent());
},
/**
* Converts the field value to a Unix timestamp
*/
'toTimestamp' => function (Field $field) {
return strtotime($field->value);
},
/**
* Turns the field value into an absolute Url
*/
'toUrl' => function (Field $field) {
return Url::to($field->value);
},
/**
* Converts a user email address to a user object
*
* @return User|null
*/
'toUser' => function (Field $field) use ($app) {
return $field->toUsers()->first();
},
/**
* Returns a users collection from a yaml list of user email addresses in the field
*
* @return Users
*/
'toUsers' => function (Field $field) use ($app) {
return $app->users()->find(false, false, ...$field->toData('yaml'));
},
// inspectors
/**
* Returns the length of the field content
*/
'length' => function (Field $field) {
return Str::length($field->value);
},
/**
* Returns the number of words in the text
*/
'words' => function (Field $field) {
return str_word_count(strip_tags($field->value));
},
// manipulators
/**
* Escapes the field value to be safely used in HTML
* templates without the risk of XSS attacks
*
* @param Field $field
* @param string $context html, attr, js or css
*/
'escape' => function (Field $field, string $context = 'html') {
$field->value = esc($field->value, $context);
return $field;
},
/**
* Creates an excerpt of the field value without html
* or any other formatting.
*/
'excerpt' => function (Field $field, int $chars = 0, bool $strip = true, string $rep = '…') {
$field->value = Str::excerpt($field->kirbytext()->value(), $chars, $strip, $rep);
return $field;
},
/**
* Converts the field content to valid HTML
*/
'html' => function (Field $field) {
$field->value = htmlentities($field->value, ENT_COMPAT, 'utf-8');
return $field;
},
/**
* Converts the field content from Markdown/Kirbytext to valid HTML
*/
'kirbytext' => function (Field $field) use ($app) {
$field->value = $app->kirbytext($field->value, [
'parent' => $field->parent(),
'field' => $field
]);
return $field;
},
/**
* Parses all KirbyTags without also parsing Markdown
*/
'kirbytags' => function (Field $field) use ($app) {
$field->value = $app->kirbytags($field->value, [
'parent' => $field->parent(),
'field' => $field
]);
return $field;
},
/**
* Converts the field content to lowercase
*/
'lower' => function (Field $field) {
$field->value = Str::lower($field->value);
return $field;
},
/**
* Converts markdown to valid HTML
*/
'markdown' => function (Field $field) use ($app) {
$field->value = $app->markdown($field->value);
return $field;
},
/**
* Converts the field content to valid XML
*/
'xml' => function (Field $field) {
$field->value = Xml::encode($field->value);
return $field;
},
/**
* Cuts the string after the given length and adds "…" if it is longer
*
* @param int $length The number of characters in the string
* @param string $appendix An optional replacement for the missing rest
* @return Field
*/
'short' => function (Field $field, int $length, string $appendix = '…') {
$field->value = Str::short($field->value, $length, $appendix);
return $field;
},
/**
* Converts the field content to a slug
*/
'slug' => function (Field $field) {
$field->value = Str::slug($field->value);
return $field;
},
/**
* Applies SmartyPants to the field
*/
'smartypants' => function (Field $field) use ($app) {
$field->value = $app->smartypants($field->value);
return $field;
},
/**
* Splits the field content into an array
*/
'split' => function (Field $field, $separator = ',') {
return Str::split((string)$field->value, $separator);
},
/**
* Converts the field content to uppercase
*/
'upper' => function (Field $field) {
$field->value = Str::upper($field->value);
return $field;
},
/**
* Avoids typographical widows in strings by replacing the last space with &nbsp;
*/
'widont' => function (Field $field) {
$field->value = Str::widont($field->value);
return $field;
},
// aliases
/**
* Parses yaml in the field content and returns an array
*/
'yaml' => function (Field $field): array {
return $field->toData('yaml');
},
];
};

14
kirby/config/presets/files.php Executable file
View File

@@ -0,0 +1,14 @@
<?php
return function (array $props) {
$props['sections'] = [
'files' => [
'headline' => $props['headline'] ?? t('files'),
'type' => 'files',
'layout' => $props['layout'] ?? 'cards',
'info' => '{{ file.dimensions }}'
]
];
return $props;
};

72
kirby/config/presets/page.php Executable file
View File

@@ -0,0 +1,72 @@
<?php
return function ($props) {
$section = function ($defaults, $props) {
if ($props === true) {
$props = [];
}
if (is_string($props) === true) {
$props = [
'headline' => $props
];
}
return array_replace_recursive($defaults, $props);
};
if (empty($props['sidebar']) === false) {
$sidebar = $props['sidebar'];
} else {
$sidebar = [];
$pages = $props['pages'] ?? [];
$files = $props['files'] ?? [];
if ($pages !== false) {
$sidebar['pages'] = $section([
'headline' => t('pages'),
'type' => 'pages',
'status' => 'all',
'layout' => 'list',
], $pages);
}
if ($files !== false) {
$sidebar['files'] = $section([
'headline' => t('files'),
'type' => 'files',
'layout' => 'list'
], $files);
}
}
if (empty($sidebar) === true) {
$props['fields'] = $props['fields'] ?? [];
unset(
$props['files'],
$props['pages']
);
} else {
$props['columns'] = [
[
'width' => '2/3',
'fields' => $props['fields'] ?? []
],
[
'width' => '1/3',
'sections' => $sidebar
],
];
unset(
$props['fields'],
$props['files'],
$props['pages'],
$props['sidebar']
);
}
return $props;
};

57
kirby/config/presets/pages.php Executable file
View File

@@ -0,0 +1,57 @@
<?php
return function (array $props) {
// load the general templates setting for all sections
$templates = $props['templates'] ?? null;
$section = function ($headline, $status, $props) use ($templates) {
$defaults = [
'headline' => $headline,
'type' => 'pages',
'layout' => 'list',
'status' => $status
];
if ($props === true) {
$props = [];
}
if (is_string($props) === true) {
$props = [
'headline' => $props
];
}
// inject the global templates definition
if (empty($templates) === false) {
$props['templates'] = $props['templates'] ?? $templates;
}
return array_replace_recursive($defaults, $props);
};
$sections = [];
$drafts = $props['drafts'] ?? [];
$unlisted = $props['unlisted'] ?? false;
$listed = $props['listed'] ?? [];
if ($drafts !== false) {
$sections['drafts'] = $section(t('pages.status.draft'), 'drafts', $drafts);
}
if ($unlisted !== false) {
$sections['unlisted'] = $section(t('pages.status.unlisted'), 'unlisted', $unlisted);
}
if ($listed !== false) {
$sections['listed'] = $section(t('pages.status.listed'), 'listed', $listed);
}
// cleaning up
unset($props['drafts'], $props['unlisted'], $props['listed'], $props['templates']);
return array_merge($props, ['sections' => $sections]);
};

85
kirby/config/roots.php Executable file
View File

@@ -0,0 +1,85 @@
<?php
return [
// kirby
'kirby' => function (array $roots) {
return realpath(__DIR__ . '/../');
},
'translations' => function (array $roots) {
return $roots['kirby'] . '/translations';
},
// index
'index' => function (array $roots) {
return realpath(__DIR__ . '/../../');
},
// assets
'assets' => function (array $roots) {
return $roots['index'] . '/assets';
},
// content
'content' => function (array $roots) {
return $roots['index'] . '/content';
},
// media
'media' => function (array $roots) {
return $roots['index'] . '/media';
},
// panel
'panel' => function (array $roots) {
return $roots['kirby'] . '/panel';
},
// site
'site' => function (array $roots) {
return $roots['index'] . '/site';
},
'accounts' => function (array $roots) {
return $roots['site'] . '/accounts';
},
'blueprints' => function (array $roots) {
return $roots['site'] . '/blueprints';
},
'cache' => function (array $roots) {
return $roots['site'] . '/cache';
},
'collections' => function (array $roots) {
return $roots['site'] . '/collections';
},
'config' => function (array $roots) {
return $roots['site'] . '/config';
},
'controllers' => function (array $roots) {
return $roots['site'] . '/controllers';
},
'emails' => function (array $roots) {
return $roots['site'] . '/emails';
},
'languages' => function (array $roots) {
return $roots['site'] . '/languages';
},
'models' => function (array $roots) {
return $roots['site'] . '/models';
},
'plugins' => function (array $roots) {
return $roots['site'] . '/plugins';
},
'sessions' => function (array $roots) {
return $roots['site'] . '/sessions';
},
'snippets' => function (array $roots) {
return $roots['site'] . '/snippets';
},
'templates' => function (array $roots) {
return $roots['site'] . '/templates';
},
// blueprints
'roles' => function (array $roots) {
return $roots['blueprints'] . '/users';
},
];

200
kirby/config/routes.php Executable file
View File

@@ -0,0 +1,200 @@
<?php
use Kirby\Api\Api;
use Kirby\Cms\App;
use Kirby\Cms\Media;
use Kirby\Cms\Panel;
use Kirby\Cms\PluginAssets;
use Kirby\Cms\Response;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Response\Redirect;
use Kirby\Http\Router\Route;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\View;
return function ($kirby) {
$api = $kirby->option('api.slug', 'api');
$panel = $kirby->option('panel.slug', 'panel');
/**
* Before routes are running before the
* plugin routes and cannot be overwritten by
* plugins.
*/
$before = [
[
'pattern' => $api . '/(:all)',
'method' => 'ALL',
'env' => 'api',
'action' => function ($path = null) use ($kirby) {
if ($kirby->option('api') === false) {
return null;
}
$request = $kirby->request();
return $kirby->api()->render($path, $this->method(), [
'body' => $request->body()->toArray(),
'files' => $request->files()->toArray(),
'headers' => $request->headers(),
'query' => $request->query()->toArray(),
]);
}
],
[
'pattern' => 'media/plugins/index.(css|js)',
'env' => 'media',
'action' => function (string $extension) use ($kirby) {
return $kirby
->response()
->type($extension)
->body(PluginAssets::index($extension));
}
],
[
'pattern' => 'media/plugins/(:any)/(:any)/(:all).(css|gif|js|jpg|png|svg|webp|woff2|woff)',
'env' => 'media',
'action' => function (string $provider, string $pluginName, string $filename, string $extension) use ($kirby) {
if ($url = PluginAssets::resolve($provider . '/' . $pluginName, $filename . '.' . $extension)) {
return $kirby
->response()
->redirect($url, 307);
}
}
],
[
'pattern' => $panel . '/(:all?)',
'env' => 'panel',
'action' => function () use ($kirby) {
if ($kirby->option('panel') === false) {
return null;
}
return Panel::render($kirby);
}
],
[
'pattern' => 'media/pages/(:all)/(:any)/(:any)',
'env' => 'media',
'action' => function ($path, $hash, $filename) use ($kirby) {
return Media::link($kirby->page($path), $hash, $filename);
}
],
[
'pattern' => 'media/site/(:any)/(:any)',
'env' => 'media',
'action' => function ($hash, $filename) use ($kirby) {
return Media::link($kirby->site(), $hash, $filename);
}
],
[
'pattern' => 'media/users/(:any)/(:any)/(:any)',
'env' => 'media',
'action' => function ($id, $hash, $filename) use ($kirby) {
return Media::link($kirby->user($id), $hash, $filename);
}
]
];
// Multi-language setup
if ($kirby->multilang() === true) {
// Multi-language home
$after[] = [
'pattern' => '',
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
$home = $kirby->site()->homePage();
if ($kirby->url() !== $home->url()) {
if ($kirby->option('languages.detect') === true) {
return $kirby
->response()
->redirect($kirby->detectedLanguage()->url());
} else {
return $kirby
->response()
->redirect($kirby->site()->url());
}
} else {
return $home;
}
}
];
foreach ($kirby->languages() as $language) {
$after[] = [
'pattern' => trim($language->pattern() . '/(:all?)', '/'),
'method' => 'ALL',
'env' => 'site',
'action' => function ($path = null) use ($kirby, $language) {
return $kirby->resolve($path, $language->code());
}
];
}
// fallback route for unprefixed default language URLs.
$after[] = [
'pattern' => '(:all)',
'method' => 'ALL',
'env' => 'site',
'action' => function (string $path) use ($kirby) {
if ($page = $kirby->page($path)) {
$url = $kirby->request()->url([
'query' => null,
'params' => null,
'fragment' => null
]);
if ($url->toString() !== $page->url()) {
return $kirby
->response()
->redirect($page->url());
}
return $kirby->resolve($path, $kirby->defaultLanguage()->code());
}
}
];
} else {
// Single-language home
$after[] = [
'pattern' => '',
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
return $kirby->site()->homePage();
}
];
// redirect the home page folder to the real homepage
$after[] = [
'pattern' => $kirby->option('home', 'home'),
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
return $kirby
->response()
->redirect($kirby->site()->url());
}
];
// Single-language subpages
$after[] = [
'pattern' => '(:all)',
'method' => 'ALL',
'env' => 'site',
'action' => function (string $path) use ($kirby) {
return $kirby->resolve($path);
}
];
}
return [
'before' => $before,
'after' => $after
];
};

View File

@@ -0,0 +1,66 @@
<?php
use Kirby\Cms\Form;
return [
'props' => [
'fields' => function (array $fields = []) {
return $fields;
}
],
'computed' => [
'form' => function () {
$fields = $this->fields;
$disabled = $this->model->permissions()->update() === false;
$content = $this->model->content()->toArray();
if ($disabled === true) {
foreach ($fields as $key => $props) {
$fields[$key]['disabled'] = true;
}
}
return new Form([
'fields' => $fields,
'values' => $content,
'model' => $this->model,
'strict' => true
]);
},
'fields' => function () {
$fields = $this->form->fields()->toArray();
if (is_a($this->model, 'Kirby\Cms\Page') === true || is_a($this->model, 'Kirby\Cms\Site') === true) {
// the title should never be updated directly via
// fields section to avoid conflicts with the rename dialog
unset($fields['title']);
}
foreach ($fields as $index => $props) {
unset($fields[$index]['value']);
}
return $fields;
},
'errors' => function () {
return $this->form->errors();
},
'data' => function () {
$values = $this->form->values();
if (is_a($this->model, 'Kirby\Cms\Page') === true || is_a($this->model, 'Kirby\Cms\Site') === true) {
// the title should never be updated directly via
// fields section to avoid conflicts with the rename dialog
unset($values['title']);
}
return $values;
}
],
'toArray' => function () {
return [
'errors' => $this->errors,
'fields' => $this->fields,
];
}
];

229
kirby/config/sections/files.php Executable file
View File

@@ -0,0 +1,229 @@
<?php
use Kirby\Cms\File;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
return [
'mixins' => [
'empty',
'headline',
'layout',
'min',
'max',
'pagination',
'parent',
],
'props' => [
/**
* Image options to control the source and look of file previews
*/
'image' => function ($image = null) {
return $image ?? [];
},
/**
* Optional info text setup. Info text is shown on the right (lists) or below (cards) the filename.
*/
'info' => function (string $info = null) {
return $info;
},
/**
* The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: tiny, small, medium, large
*/
'size' => function (string $size = 'auto') {
return $size;
},
/**
* Enables/disables manual sorting
*/
'sortable' => function (bool $sortable = true) {
return $sortable;
},
/**
* Overwrites manual sorting and sorts by the given field and sorting direction (i.e. filename desc)
*/
'sortBy' => function (string $sortBy = null) {
return $sortBy;
},
/**
* Filters all files by template and also sets the template, which will be used for all uploads
*/
'template' => function (string $template = null) {
return $template;
},
/**
* Setup for the main text in the list or cards. By default this will display the filename.
*/
'text' => function (string $text = '{{ file.filename }}') {
return $text;
}
],
'computed' => [
'accept' => function () {
if ($this->template) {
$file = new File([
'filename' => 'tmp',
'template' => $this->template
]);
return $file->blueprint()->accept()['mime'] ?? '*';
}
return null;
},
'dragTextType' => function () {
return (option('panel')['kirbytext'] ?? true) ? 'kirbytext' : 'markdown';
},
'parent' => function () {
return $this->parentModel();
},
'files' => function () {
$files = $this->parent->files()->template($this->template);
if ($this->sortBy) {
$files = $files->sortBy(...Str::split($this->sortBy, ' '));
} elseif ($this->sortable === true) {
$files = $files->sortBy('sort', 'asc');
}
// apply the default pagination
$files = $files->paginate([
'page' => $this->page,
'limit' => $this->limit
]);
return $files;
},
'data' => function () {
$data = [];
if ($this->layout === 'list') {
$thumb = [
'width' => 100,
'height' => 100
];
} else {
$thumb = [
'width' => 400,
'height' => 400
];
}
foreach ($this->files as $file) {
$image = $file->panelImage($this->image, $thumb);
$data[] = [
'dragText' => $file->dragText($this->dragTextType),
'filename' => $file->filename(),
'id' => $file->id(),
'text' => $file->toString($this->text),
'info' => $file->toString($this->info ?? false),
'icon' => $file->panelIcon($image),
'image' => $image,
'link' => $file->panelUrl(true),
'parent' => $file->parent()->panelPath(),
'url' => $file->url(),
];
}
return $data;
},
'total' => function () {
return $this->files->pagination()->total();
},
'errors' => function () {
$errors = [];
if ($this->validateMax() === false) {
$errors['max'] = I18n::template('error.section.files.max.' . I18n::form($this->max), [
'max' => $this->max,
'section' => $this->headline
]);
}
if ($this->validateMin() === false) {
$errors['min'] = I18n::template('error.section.files.min.' . I18n::form($this->min), [
'min' => $this->min,
'section' => $this->headline
]);
}
if (empty($errors) === true) {
return [];
}
return [
$this->name => [
'label' => $this->headline,
'message' => $errors,
]
];
},
'link' => function () {
$modelLink = $this->model->panelUrl(true);
$parentLink = $this->parent->panelUrl(true);
if ($modelLink !== $parentLink) {
return $parentLink;
}
},
'pagination' => function () {
return $this->pagination();
},
'sortable' => function () {
if ($this->sortable === false) {
return false;
}
if ($this->sortBy !== null) {
return false;
}
return true;
},
'upload' => function () {
if ($this->isFull() === true) {
return false;
}
// count all uploaded files
$total = count($this->data);
$max = $this->max ? $this->max - $total : null;
if ($this->max && $total === $this->max - 1) {
$multiple = false;
} else {
$multiple = true;
}
return [
'accept' => $this->accept,
'multiple' => $multiple,
'max' => $max,
'api' => $this->parent->apiUrl(true) . '/files',
'attributes' => array_filter([
'template' => $this->template
])
];
}
],
'toArray' => function () {
return [
'data' => $this->data,
'errors' => $this->errors,
'options' => [
'accept' => $this->accept,
'empty' => $this->empty,
'headline' => $this->headline,
'layout' => $this->layout,
'link' => $this->link,
'max' => $this->max,
'min' => $this->min,
'size' => $this->size,
'sortable' => $this->sortable,
'upload' => $this->upload
],
'pagination' => $this->pagination
];
}
];

36
kirby/config/sections/info.php Executable file
View File

@@ -0,0 +1,36 @@
<?php
use Kirby\Toolkit\I18n;
return [
'mixins' => [
'headline'
],
'props' => [
'text' => function ($text = null) {
return I18n::translate($text, $text);
},
'theme' => function (string $theme = null) {
return $theme;
}
],
'computed' => [
'text' => function () {
if ($this->text) {
$text = $this->model()->toString($this->text);
$text = $this->kirby()->kirbytext($text);
return $text;
}
},
],
'toArray' => function () {
return [
'options' => [
'headline' => $this->headline,
'text' => $this->text,
'theme' => $this->theme
]
];
}
];

View File

@@ -0,0 +1,28 @@
<?php
return [
'props' => [
/**
* Sets the text for the empty state box
*/
'empty' => function (string $empty = null) {
return I18n::translate($empty);
}
],
'methods' => [
'isFull' => function () {
if ($this->max) {
return $this->total >= $this->max;
}
return false;
},
'validateMax' => function () {
if ($this->max && $this->max < $this->total) {
return false;
}
return true;
}
]
];

View File

@@ -0,0 +1,19 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* The headline for the section. This can be a simple string or a template with additional info from the parent page.
*/
'headline' => function ($headline = null) {
return I18n::translate($headline, $headline);
}
],
'computed' => [
'headline' => function () {
return $this->headline ?? ucfirst($this->name);
}
]
];

View File

@@ -0,0 +1,12 @@
<?php
return [
'props' => [
/**
* Section layout. Available layout methods: list, cards.
*/
'layout' => function (string $layout = 'list') {
return $layout === 'cards' ? 'cards' : 'list';
}
]
];

View File

@@ -0,0 +1,28 @@
<?php
return [
'props' => [
/**
* Sets the maximum number of allowed entries in the section
*/
'max' => function (int $max = null) {
return $max;
}
],
'methods' => [
'isFull' => function () {
if ($this->max) {
return $this->total >= $this->max;
}
return false;
},
'validateMax' => function () {
if ($this->max && $this->max < $this->total) {
return false;
}
return true;
}
]
];

View File

@@ -0,0 +1,21 @@
<?php
return [
'props' => [
/**
* Sets the minimum number of required entries in the section
*/
'min' => function (int $min = null) {
return $min;
}
],
'methods' => [
'validateMin' => function () {
if ($this->min && $this->min > $this->total) {
return false;
}
return true;
}
]
];

View File

@@ -0,0 +1,36 @@
<?php
use Kirby\Toolkit\Pagination;
return [
'props' => [
/**
* Sets the number of items per page. If there are more items the pagination navigation will be shown at the bottom of the section.
*/
'limit' => function (int $limit = 20) {
return $limit;
},
/**
* Sets the default page for the pagination. This will overwrite default pagination.
*/
'page' => function (int $page = null) {
return $page ?? get('page', 1);
},
],
'methods' => [
'pagination' => function () {
$pagination = new Pagination([
'limit' => $this->limit,
'page' => $this->page,
'total' => $this->total
]);
return [
'limit' => $pagination->limit(),
'offset' => $pagination->offset(),
'page' => $pagination->page(),
'total' => $pagination->total(),
];
},
]
];

View File

@@ -0,0 +1,29 @@
<?php
use Kirby\Toolkit\Str;
return [
'props' => [
/**
* Sets the query to a parent to find items for the list
*/
'parent' => function (string $parent = null) {
return $parent;
}
],
'methods' => [
'parentModel' => function () {
$parent = $this->parent;
if (is_string($parent) === true) {
$parent = $this->model->query($parent);
}
if ($parent === null) {
return $this->model;
}
return $parent;
}
]
];

290
kirby/config/sections/pages.php Executable file
View File

@@ -0,0 +1,290 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Blueprint;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
return [
'mixins' => [
'empty',
'headline',
'layout',
'min',
'max',
'pagination',
'parent'
],
'props' => [
/**
* Optional array of templates that should only be allowed to add.
*/
'create' => function ($add = null) {
return A::wrap($add);
},
/**
* Image options to control the source and look of page previews
*/
'image' => function ($image = null) {
return $image ?? [];
},
/**
* Optional info text setup. Info text is shown on the right (lists) or below (cards) the page title.
*/
'info' => function (string $info = null) {
return $info;
},
/**
* The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: tiny, small, medium, large
*/
'size' => function (string $size = 'auto') {
return $size;
},
/**
* Enables/disables manual sorting
*/
'sortable' => function (bool $sortable = true) {
return $sortable;
},
/**
* Overwrites manual sorting and sorts by the given field and sorting direction (i.e. date desc)
*/
'sortBy' => function (string $sortBy = null) {
return $sortBy;
},
/**
* Filters pages by their status. Available status settings: draft, unlisted, listed, published, all.
*/
'status' => function (string $status = '') {
if ($status === 'drafts') {
$status = 'draft';
}
if (in_array($status, ['all', 'draft', 'published', 'listed', 'unlisted']) === false) {
$status = 'all';
}
return $status;
},
/**
* Setup for the main text in the list or cards. By default this will display the page title.
*/
'text' => function (string $text = '{{ page.title }}') {
return $text;
}
],
'computed' => [
'dragTextType' => function () {
return option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
},
'templates' => function () {
return A::wrap($this->templates ?? $this->template);
},
'parent' => function () {
return $this->parentModel();
},
'pages' => function () {
switch ($this->status) {
case 'draft':
$pages = $this->parent->drafts();
break;
case 'listed':
$pages = $this->parent->children()->listed();
break;
case 'published':
$pages = $this->parent->children();
break;
case 'unlisted':
$pages = $this->parent->children()->unlisted();
break;
default:
$pages = $this->parent->childrenAndDrafts();
}
// loop for the best performance
foreach ($pages->data as $id => $page) {
// remove all protected pages
if ($page->isReadable() === false) {
unset($pages->data[$id]);
continue;
}
// filter by all set templates
if ($this->templates && in_array($page->intendedTemplate()->name(), $this->templates) === false) {
unset($pages->data[$id]);
continue;
}
}
// sort
if ($this->sortBy) {
$pages = $pages->sortBy(...Str::split($this->sortBy, ' '));
}
// pagination
$pages = $pages->paginate([
'page' => $this->page,
'limit' => $this->limit
]);
return $pages;
},
'total' => function () {
return $this->pages->pagination()->total();
},
'data' => function () {
$data = [];
if ($this->layout === 'list') {
$thumb = [
'width' => 100,
'height' => 100
];
} else {
$thumb = [
'width' => 400,
'height' => 400
];
}
foreach ($this->pages as $item) {
$permissions = $item->permissions();
$blueprint = $item->blueprint();
$image = $item->panelImage($this->image, $thumb);
$data[] = [
'id' => $item->id(),
'dragText' => $item->dragText($this->dragTextType),
'text' => $item->toString($this->text),
'info' => $item->toString($this->info ?? false),
'parent' => $item->parentId(),
'icon' => $item->panelIcon($image),
'image' => $image,
'link' => $item->panelUrl(true),
'status' => $item->status(),
'permissions' => [
'sort' => $permissions->can('sort'),
'changeStatus' => $permissions->can('changeStatus')
]
];
}
return $data;
},
'errors' => function () {
$errors = [];
if ($this->validateMax() === false) {
$errors['max'] = I18n::template('error.section.pages.max.' . I18n::form($this->max), [
'max' => $this->max,
'section' => $this->headline
]);
}
if ($this->validateMin() === false) {
$errors['min'] = I18n::template('error.section.pages.min.' . I18n::form($this->max), [
'min' => $this->min,
'section' => $this->headline
]);
}
if (empty($errors) === true) {
return [];
}
return [
$this->name => [
'label' => $this->headline,
'message' => $errors,
]
];
},
'add' => function () {
if (in_array($this->status, ['draft', 'all']) === false) {
return false;
}
if ($this->isFull() === true) {
return false;
}
return true;
},
'link' => function () {
$modelLink = $this->model->panelUrl(true);
$parentLink = $this->parent->panelUrl(true);
if ($modelLink !== $parentLink) {
return $parentLink;
}
},
'pagination' => function () {
return $this->pagination();
},
'sortable' => function () {
if ($this->status !== 'listed' && $this->status !== 'all') {
return false;
}
if ($this->sortable === false) {
return false;
}
if ($this->sortBy !== null) {
return false;
}
return true;
}
],
'methods' => [
'blueprints' => function () {
$blueprints = [];
$templates = empty($this->create) === false ? $this->create : $this->templates;
if (empty($templates) === true) {
foreach (glob(App::instance()->root('blueprints') . '/pages/*.yml') as $blueprint) {
$templates[] = F::name($blueprint);
}
}
// convert every template to a usable option array
// for the template select box
foreach ($templates as $template) {
try {
$props = Blueprint::load('pages/' . $template);
$blueprints[] = [
'name' => basename($props['name']),
'title' => $props['title'],
];
} catch (Throwable $e) {
$blueprints[] = [
'name' => basename($template),
'title' => ucfirst($template),
];
}
}
return $blueprints;
}
],
'toArray' => function () {
return [
'data' => $this->data,
'errors' => $this->errors,
'options' => [
'add' => $this->add,
'empty' => $this->empty,
'headline' => $this->headline,
'layout' => $this->layout,
'link' => $this->link,
'size' => $this->size,
'sortable' => $this->sortable
],
'pagination' => $this->pagination,
];
}
];

225
kirby/config/tags.php Executable file
View File

@@ -0,0 +1,225 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Html;
use Kirby\Cms\Url;
/**
* Default KirbyTags definition
*/
return [
/* Date */
'date' => [
'attr' => [],
'html' => function ($tag) {
return strtolower($tag->date) === 'year' ? date('Y') : date($tag->date);
}
],
/* Email */
'email' => [
'attr' => [
'class',
'rel',
'target',
'text',
'title'
],
'html' => function ($tag) {
return Html::email($tag->value, $tag->text, [
'class' => $tag->class,
'rel' => $tag->rel,
'target' => $tag->target,
'title' => $tag->title,
]);
}
],
/* File */
'file' => [
'attr' => [
'class',
'rel',
'target',
'text',
'title'
],
'html' => function ($tag) {
if (!$file = $tag->file($tag->value)) {
return $tag->text;
}
// use filename if the text is empty and make sure to
// ignore markdown italic underscores in filenames
if (empty($tag->text) === true) {
$tag->text = str_replace('_', '\_', $file->filename());
}
return Html::a($file->url(), $tag->text, [
'class' => $tag->class,
'download' => true,
'rel' => $tag->rel,
'target' => $tag->target,
'title' => $tag->title,
]);
}
],
/* Gist */
'gist' => [
'attr' => [
'file'
],
'html' => function ($tag) {
return Html::gist($tag->value, $tag->file);
}
],
/* Image */
'image' => [
'attr' => [
'alt',
'caption',
'class',
'height',
'imgclass',
'link',
'linkclass',
'rel',
'target',
'text',
'title',
'width'
],
'html' => function ($tag) {
if ($tag->file = $tag->file($tag->value)) {
$tag->src = $tag->file->url();
$tag->alt = $tag->alt ?? $tag->file->alt()->or(' ')->value();
$tag->title = $tag->title ?? $tag->file->title()->value();
$tag->caption = $tag->caption ?? $tag->file->caption()->value();
} else {
$tag->src = Url::to($tag->value);
}
$link = function ($img) use ($tag) {
if (empty($tag->link) === true) {
return $img;
}
return Html::a($tag->link === 'self' ? $tag->src : $tag->link, [$img], [
'rel' => $tag->rel,
'class' => $tag->linkclass,
'target' => $tag->target
]);
};
$image = Html::img($tag->src, [
'width' => $tag->width,
'height' => $tag->height,
'class' => $tag->imgclass,
'title' => $tag->title,
'alt' => $tag->alt ?? ' '
]);
if ($tag->kirby()->option('kirbytext.image.figure', true) === false) {
return $link($image);
}
return Html::figure([ $link($image) ], $tag->caption, [
'class' => $tag->class
]);
}
],
/* Link */
'link' => [
'attr' => [
'class',
'rel',
'role',
'target',
'title',
'text',
],
'html' => function ($tag) {
return Html::a($tag->value, $tag->text, [
'rel' => $tag->rel,
'class' => $tag->class,
'role' => $tag->role,
'title' => $tag->title,
'target' => $tag->target,
]);
}
],
/* Tel */
'tel' => [
'attr' => [
'class',
'rel',
'text',
'title'
],
'html' => function ($tag) {
return Html::tel($tag->value, $tag->text, [
'class' => $tag->class,
'rel' => $tag->rel,
'title' => $tag->title
]);
}
],
/* Twitter */
'twitter' => [
'attr' => [
'class',
'rel',
'target',
'text',
'title'
],
'html' => function ($tag) {
// get and sanitize the username
$username = str_replace('@', '', $tag->value);
// build the profile url
$url = 'https://twitter.com/' . $username;
// sanitize the link text
$text = $tag->text ?? '@' . $username;
// build the final link
return Html::a($url, $text, [
'class' => $tag->class,
'rel' => $tag->rel,
'target' => $tag->target,
'title' => $tag->title,
]);
}
],
/* Video */
'video' => [
'attr' => [
'class',
'caption',
'height',
'width'
],
'html' => function ($tag) {
$video = Html::video(
$tag->value,
$tag->kirby()->option('kirbytext.video.options', [])
);
return Html::figure([$video], $tag->caption, [
'class' => $tag->class ?? $tag->kirby()->option('kirbytext.video.class', 'video'),
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]);
}
],
];

15
kirby/config/tests.php Executable file
View File

@@ -0,0 +1,15 @@
<?php
$testDir = dirname(__DIR__) . '/tests';
if (is_dir($testDir) === true) {
spl_autoload_register(function ($className) use ($testDir) {
$path = str_replace('Kirby\\', '', $className);
$path = str_replace('\\', '/', $path);
$file = $testDir . '/' . $path . '.php';
if (file_exists($file)) {
include $file;
}
});
}

33
kirby/config/urls.php Executable file
View File

@@ -0,0 +1,33 @@
<?php
use Kirby\Cms\Url;
return [
'index' => function () {
return Url::index();
},
'base' => function (array $urls) {
return rtrim($urls['index'], '/');
},
'current' => function (array $urls) {
$path = trim($this->path(), '/');
if (empty($path) === true) {
return $urls['index'];
} else {
return $urls['base'] . '/' . $path;
}
},
'assets' => function (array $urls) {
return $urls['base'] . '/assets';
},
'api' => function (array $urls) {
return $urls['base'] . '/' . ($this->options['api']['slug'] ?? 'api');
},
'media' => function (array $urls) {
return $urls['base'] . '/media';
},
'panel' => function (array $urls) {
return $urls['base'] . '/' . ($this->options['panel']['slug'] ?? 'panel');
}
];

View File

@@ -0,0 +1,624 @@
<?php
#
#
# Parsedown Extra
# https://github.com/erusev/parsedown-extra
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class ParsedownExtra extends Parsedown
{
# ~
const version = '0.8.0-beta-1';
# ~
public function __construct()
{
if (version_compare(parent::version, '1.7.1') < 0) {
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions
array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
}
#
# ~
public function text($text)
{
$Elements = $this->textElements($text);
# convert to markup
$markup = $this->elements($Elements);
# trim line breaks
$markup = trim($markup, "\n");
# merge consecutive dl elements
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
# add footnotes
if (isset($this->DefinitionData['Footnote'])) {
$Element = $this->buildFootnoteElement();
$markup .= "\n" . $this->element($Element);
}
return $markup;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation($Line)
{
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches)) {
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Footnote
protected function blockFootnote($Line)
{
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches)) {
$Block = array(
'label' => $matches[1],
'text' => $matches[2],
'hidden' => true,
);
return $Block;
}
}
protected function blockFootnoteContinue($Line, $Block)
{
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text'])) {
return;
}
if (isset($Block['interrupted'])) {
if ($Line['indent'] >= 4) {
$Block['text'] .= "\n\n" . $Line['text'];
return $Block;
}
} else {
$Block['text'] .= "\n" . $Line['text'];
return $Block;
}
}
protected function blockFootnoteComplete($Block)
{
$this->DefinitionData['Footnote'][$Block['label']] = array(
'text' => $Block['text'],
'count' => null,
'number' => null,
);
return $Block;
}
#
# Definition List
protected function blockDefinitionList($Line, $Block)
{
if (! isset($Block) or $Block['type'] !== 'Paragraph') {
return;
}
$Element = array(
'name' => 'dl',
'elements' => array(),
);
$terms = explode("\n", $Block['element']['handler']['argument']);
foreach ($terms as $term) {
$Element['elements'] []= array(
'name' => 'dt',
'handler' => array(
'function' => 'lineElements',
'argument' => $term,
'destination' => 'elements'
),
);
}
$Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
protected function blockDefinitionListContinue($Line, array $Block)
{
if ($Line['text'][0] === ':') {
$Block = $this->addDdElement($Line, $Block);
return $Block;
} else {
if (isset($Block['interrupted']) and $Line['indent'] === 0) {
return;
}
if (isset($Block['interrupted'])) {
$Block['dd']['handler']['function'] = 'textElements';
$Block['dd']['handler']['argument'] .= "\n\n";
$Block['dd']['handler']['destination'] = 'elements';
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['handler']['argument'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) {
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkup($Line)
{
if ($this->markupEscaped or $this->safeMode) {
return;
}
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) {
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements)) {
return;
}
$Block = array(
'name' => $matches[1],
'depth' => 0,
'element' => array(
'rawHtml' => $Line['text'],
'autobreak' => true,
),
);
$length = strlen($matches[0]);
$remainder = substr($Line['text'], $length);
if (trim($remainder) === '') {
if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) {
$Block['closed'] = true;
$Block['void'] = true;
}
} else {
if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) {
return;
}
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) {
$Block['closed'] = true;
}
}
return $Block;
}
}
protected function blockMarkupContinue($Line, array $Block)
{
if (isset($Block['closed'])) {
return;
}
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) { # open
$Block['depth'] ++;
}
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) { # close
if ($Block['depth'] > 0) {
$Block['depth'] --;
} else {
$Block['closed'] = true;
}
}
if (isset($Block['interrupted'])) {
$Block['element']['rawHtml'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['rawHtml'] .= "\n".$Line['body'];
return $Block;
}
protected function blockMarkupComplete($Block)
{
if (! isset($Block['void'])) {
$Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) {
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker($Excerpt)
{
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) {
$name = $matches[1];
if (! isset($this->DefinitionData['Footnote'][$name])) {
return;
}
$this->DefinitionData['Footnote'][$name]['count'] ++;
if (! isset($this->DefinitionData['Footnote'][$name]['number'])) {
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
}
$Element = array(
'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'element' => array(
'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->DefinitionData['Footnote'][$name]['number'],
),
);
return array(
'extent' => strlen($matches[0]),
'element' => $Element,
);
}
}
private $footnoteCount = 0;
#
# Link
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
$remainder = substr($Excerpt['text'], $Link['extent']);
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) {
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Link['extent'] += strlen($matches[0]);
}
return $Link;
}
#
# ~
#
private $currentAbreviation;
private $currentMeaning;
protected function insertAbreviation(array $Element)
{
if (isset($Element['text'])) {
$Element['elements'] = self::pregReplaceElements(
'/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
array(
array(
'name' => 'abbr',
'attributes' => array(
'title' => $this->currentMeaning,
),
'text' => $this->currentAbreviation,
)
),
$Element['text']
);
unset($Element['text']);
}
return $Element;
}
protected function inlineText($text)
{
$Inline = parent::inlineText($text);
if (isset($this->DefinitionData['Abbreviation'])) {
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning) {
$this->currentAbreviation = $abbreviation;
$this->currentMeaning = $meaning;
$Inline['element'] = $this->elementApplyRecursiveDepthFirst(
array($this, 'insertAbreviation'),
$Inline['element']
);
}
}
return $Inline;
}
#
# Util Methods
#
protected function addDdElement(array $Line, array $Block)
{
$text = substr($Line['text'], 1);
$text = trim($text);
unset($Block['dd']);
$Block['dd'] = array(
'name' => 'dd',
'handler' => array(
'function' => 'lineElements',
'argument' => $text,
'destination' => 'elements'
),
);
if (isset($Block['interrupted'])) {
$Block['dd']['handler']['function'] = 'textElements';
unset($Block['interrupted']);
}
$Block['element']['elements'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'elements' => array(
array('name' => 'hr'),
array(
'name' => 'ol',
'elements' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData) {
if (! isset($DefinitionData['number'])) {
continue;
}
$text = $DefinitionData['text'];
$textElements = parent::textElements($text);
$numbers = range(1, $DefinitionData['count']);
$backLinkElements = array();
foreach ($numbers as $number) {
$backLinkElements[] = array('text' => ' ');
$backLinkElements[] = array(
'name' => 'a',
'attributes' => array(
'href' => "#fnref$number:$definitionId",
'rev' => 'footnote',
'class' => 'footnote-backref',
),
'rawHtml' => '&#8617;',
'allowRawHtmlInSafeMode' => true,
'autobreak' => false,
);
}
unset($backLinkElements[0]);
$n = count($textElements) -1;
if ($textElements[$n]['name'] === 'p') {
$backLinkElements = array_merge(
array(
array(
'rawHtml' => '&#160;',
'allowRawHtmlInSafeMode' => true,
),
),
$backLinkElements
);
unset($textElements[$n]['name']);
$textElements[$n] = array(
'name' => 'p',
'elements' => array_merge(
array($textElements[$n]),
$backLinkElements
),
);
} else {
$textElements[] = array(
'name' => 'p',
'elements' => $backLinkElements
);
}
$Element['elements'][1]['elements'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'elements' => array_merge(
$textElements
),
);
}
return $Element;
}
# ~
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute) {
if ($attribute[0] === '#') {
$Data['id'] = substr($attribute, 1);
} else { # "."
$classes []= substr($attribute, 1);
}
}
if (isset($classes)) {
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
# ~
protected function processTag($elementMarkup) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors(true);
$DOMDocument = new DOMDocument;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
# http://stackoverflow.com/q/4879946/200145
$DOMDocument->loadHTML($elementMarkup);
$DOMDocument->removeChild($DOMDocument->doctype);
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
$elementText = '';
if ($DOMDocument->documentElement->getAttribute('markdown') === '1') {
foreach ($DOMDocument->documentElement->childNodes as $Node) {
$elementText .= $DOMDocument->saveHTML($Node);
}
$DOMDocument->documentElement->removeAttribute('markdown');
$elementText = "\n".$this->text($elementText)."\n";
} else {
foreach ($DOMDocument->documentElement->childNodes as $Node) {
$nodeMarkup = $DOMDocument->saveHTML($Node);
if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements)) {
$elementText .= $this->processTag($nodeMarkup);
} else {
$elementText .= $nodeMarkup;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}

File diff suppressed because it is too large Load Diff

9
kirby/kirby.pub Executable file
View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Ux4q7LmQ5hfTYTtz3/a
mohFJMWo/iCnxVcY84PZjLwWnT+G2DTKGaEWydB77TteJQnmsgtvO5734oj3Ga3r
QCfwr2gxo/0WDEBq7C5HP+YNJiuZ/iD/tYV+gloF+Aaa3Mo8AK5DYH3dnjuyfHc1
veIlYX1D2MXji2IRqdweAzVi1dfI4I3Ys8awhzv653vFLj5LvAtlwlYlmYeRwci7
GkAOWw709CuKQNdPBXGFQQ/pEB5mnp8mI31j8og845u6v/Sk4+85gFORSufIRfnQ
GFYrPOeavxfAWQGjh7JQjr/sbKSXaJ3nDlrYsOPIrC0Rwn/jsQPO7OLdVwkc9ofL
GQIDAQAB
-----END PUBLIC KEY-----

BIN
kirby/panel/dist/apple-touch-icon.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

1
kirby/panel/dist/css/app.css vendored Executable file

File diff suppressed because one or more lines are too long

BIN
kirby/panel/dist/favicon.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

401
kirby/panel/dist/img/icons.svg vendored Executable file
View File

@@ -0,0 +1,401 @@
<svg aria-hidden="true" style="position:absolute;width:0;height:0" xmlns="http://www.w3.org/2000/svg" overflow="hidden">
<defs>
<symbol id="icon-account" viewBox="0 0 16 16">
<path d="M11 7a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 12.4c-.6-.7-1.4-1.4-3-1.4H7c-1.6 0-2.4.7-3 1.4C2.8 11.3 2 9.8 2 8c0-3.3 2.7-6 6-6s6 2.7 6 6c0 1.8-.8 3.3-2 4.4z"
/>
</symbol>
<symbol id="icon-add" viewBox="0 0 16 16">
<path d="M9 4H7v3H4v2h3v3h2V9h3V7H9z" />
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z"
/>
</symbol>
<symbol id="icon-alert" viewBox="0 0 16 16">
<path d="M7 6h2v4H7V6zM9 12a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
<path d="M15 16H1a1.001 1.001 0 0 1-.895-1.448l7-14c.34-.678 1.449-.678 1.789 0l7 14A1 1 0 0 1 15 16zM2.618 14h10.764L8 3.236 2.618 14z"
/>
</symbol>
<symbol id="icon-angle-down" viewBox="0 0 16 16">
<path d="M8 11.4L2.6 6 4 4.6l4 4 4-4L13.4 6z" />
</symbol>
<symbol id="icon-angle-left" viewBox="0 0 16 16">
<path d="M10 13.4L4.6 8 10 2.6 11.4 4l-4 4 4 4z" />
</symbol>
<symbol id="icon-angle-right" viewBox="0 0 16 16">
<path d="M6 13.4L4.6 12l4-4-4-4L6 2.6 11.4 8z" />
</symbol>
<symbol id="icon-angle-up" viewBox="0 0 16 16">
<path d="M12 11.4l-4-4-4 4L2.6 10 8 4.6l5.4 5.4z" />
</symbol>
<symbol id="icon-bars" viewBox="0 0 16 16">
<path d="M15 7H1c-.6 0-1 .4-1 1s.4 1 1 1h14c.6 0 1-.4 1-1s-.4-1-1-1zM15 1H1c-.6 0-1 .4-1 1s.4 1 1 1h14c.6 0 1-.4 1-1s-.4-1-1-1zM15 13H1c-.6 0-1 .4-1 1s.4 1 1 1h14c.6 0 1-.4 1-1s-.4-1-1-1z"
/>
</symbol>
<symbol id="icon-bold" viewBox="0 0 16 16">
<path d="M11.561 7.316a3.485 3.485 0 0 0 1.418-3.208C12.781 2.305 11.144.999 9.33.999H2.001v1l1.447.724a1 1 0 0 1 .553.894v8.764a.998.998 0 0 1-.553.894l-1.447.724v1h7.823c2.104 0 3.98-1.547 4.162-3.643a4 4 0 0 0-2.424-4.04zM7 3h1a2 2 0 1 1 0 4H7V3zm2 10H7V9h2a2 2 0 1 1 0 4z"
/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 16 16">
<path d="M9.2 0H5.4c-.4 0-.8.3-1 .7l-2 7c-.2.7.3 1.3.9 1.3H7l-1.5 7 7.3-9.4c.5-.6 0-1.6-.8-1.6H9l1.1-3.7C10.3.6 9.8 0 9.2 0z"
/>
</symbol>
<symbol id="icon-calendar" viewBox="0 0 16 16">
<path d="M15 2h-2V0h-2v2H9V0H7v2H5V0H3v2H1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm-1 12H2V5h12v9z"
/>
<path d="M4 7h2v2H4V7zM7 7h2v2H7V7zM4 10h2v2H4v-2zM7 10h2v2H7v-2zM10 7h2v2h-2V7zM10 10h2v2h-2v-2z" />
</symbol>
<symbol id="icon-cancel" viewBox="0 0 16 16">
<path d="M10.1 4.5L8 6.6 5.9 4.5 4.5 5.9 6.6 8l-2.1 2.1 1.4 1.4L8 9.4l2.1 2.1 1.4-1.4L9.4 8l2.1-2.1z" />
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z"
/>
</symbol>
<symbol id="icon-check" viewBox="0 0 16 16">
<path d="M8 0C3.589 0 0 3.589 0 8s3.589 8 8 8 8-3.589 8-8-3.589-8-8-8zm0 14c-3.309 0-6-2.691-6-6s2.691-6 6-6 6 2.691 6 6-2.691 6-6 6z"
/>
<path d="M7 11.414L3.586 8 5 6.586l2 2 4-4L12.414 6z" />
</symbol>
<symbol id="icon-clock" viewBox="0 0 16 16">
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z" />
<path d="M9 4H7v5h5V7H9z" />
</symbol>
<symbol id="icon-code" viewBox="0 0 16 16">
<path d="M12.7 11.7l-1.4-1.4L13.6 8l-2.3-2.3 1.4-1.4 3 3c.4.4.4 1 0 1.4l-3 3zM3.3 11.7l-3-3c-.4-.4-.4-1 0-1.4l3-3 1.4 1.4L2.4 8l2.3 2.3-1.4 1.4zM6 15c-.1 0-.2 0-.3-.1-.5-.2-.8-.7-.6-1.3l4-12c.2-.5.7-.8 1.3-.6.5.2.8.7.6 1.3l-4 12c-.2.4-.6.7-1 .7z"
/>
</symbol>
<symbol id="icon-cog" viewBox="0 0 16 16">
<path d="M13.3 5.2l1.1-2.1L13 1.7l-2.1 1.1c-.3-.2-.7-.3-1.1-.4L9 0H7l-.8 2.3c-.3.1-.7.2-1 .4L3.1 1.6 1.6 3.1l1.1 2.1c-.2.3-.3.7-.4 1L0 7v2l2.3.8c.1.4.3.7.4 1.1L1.6 13 3 14.4l2.1-1.1c.3.2.7.3 1.1.4L7 16h2l.8-2.3c.4-.1.7-.3 1.1-.4l2.1 1.1 1.4-1.4-1.1-2.1c.2-.3.3-.7.4-1.1L16 9V7l-2.3-.8c-.1-.3-.2-.7-.4-1zM8 11c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"
/>
</symbol>
<symbol id="icon-collapse" viewBox="0 0 16 16">
<path d="M15.707 1.707A.999.999 0 1 0 14.293.293l-2.5 2.5L9 0v7h7l-2.793-2.793 2.5-2.5zM2.793 11.793l-2.5 2.5a.999.999 0 1 0 1.414 1.414l2.5-2.5L7 16V9H0l2.793 2.793z"
/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 16 16">
<path d="M10 4H2c-.6 0-1 .4-1 1v10c0 .6.4 1 1 1h8c.6 0 1-.4 1-1V5c0-.6-.4-1-1-1z" />
<path d="M14 0H4v2h9v11h2V1c0-.6-.4-1-1-1z" />
</symbol>
<symbol id="icon-dashboard" viewBox="0 0 16 16">
<path d="M6 9H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1zM6 16H1a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1zM15 6h-5a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1zM15 16h-5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1z"
/>
</symbol>
<symbol id="icon-document" viewBox="0 0 16 16">
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h12c.6 0 1-.4 1-1V1c0-.6-.4-1-1-1zm-1 14H3V2h10v12z" />
<path d="M4 3h4v4H4V3zM9 4h3v1H9V4zM9 6h3v1H9V6zM4 8h8v1H4V8zM4 10h8v1H4v-1zM4 12h5v1H4v-1z" />
</symbol>
<symbol id="icon-dots" viewBox="0 0 16 16">
<path d="M10 8a2 2 0 1 1-3.999.001A2 2 0 0 1 10 8zM4 8a2 2 0 1 1-3.999.001A2 2 0 0 1 4 8zM16 8a2 2 0 1 1-3.999.001A2 2 0 0 1 16 8z"
/>
</symbol>
<symbol id="icon-download" viewBox="0 0 16 16">
<path d="M8 12c.3 0 .5-.1.7-.3L14.4 6 13 4.6l-4 4V0H7v8.6l-4-4L1.6 6l5.7 5.7c.2.2.4.3.7.3z" />
<path d="M14 14H2v-3H0v4c0 .6.4 1 1 1h14c.6 0 1-.4 1-1v-4h-2v3z" />
</symbol>
<symbol id="icon-draft" viewBox="0 0 16 16">
<path d="M4 0v9H0v4c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3V0H4zm10 13c0 .6-.4 1-1 1H6V2h8v11z" />
<path d="M8 5h4v2H8V5zM8 9h4v2H8V9z" />
</symbol>
<symbol id="icon-edit" viewBox="0 0 16 16">
<path d="M8.1 3.5L.3 11.3c-.2.2-.3.4-.3.7v3c0 .6.4 1 1 1h3c.3 0 .5-.1.7-.3l7.8-7.8-4.4-4.4zM15.7 3.3l-3-3c-.4-.4-1-.4-1.4 0L9.5 2.1l4.4 4.4 1.8-1.8c.4-.4.4-1 0-1.4z"
/>
</symbol>
<symbol id="icon-email" viewBox="0 0 16 16">
<path d="M15 1H1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm-1 12H2V6.723l5.504 3.145a.998.998 0 0 0 .992 0L14 6.723V13zm0-8.58L8 7.849 2 4.42V3h12v1.42z"
/>
</symbol>
<symbol id="icon-expand" viewBox="0 0 16 16">
<path d="M7.4 10L6 8.6l-2.7 2.7L0 8v8h8l-3.3-3.3zM11.3 3.3L8.6 6 10 7.4l2.7-2.7L16 8V0H8z" />
</symbol>
<symbol id="icon-facebook" viewBox="0 0 16 16">
<path d="M15.3 0H.7C.3 0 0 .3 0 .7v14.7c0 .3.3.6.7.6H8v-5H6V8h2V6c0-2.1 1.2-3 3-3h2v3h-1c-.6 0-1 .4-1 1v1h2.6l-.6 3h-2v5h4.3c.4 0 .7-.3.7-.7V.7c0-.4-.3-.7-.7-.7z"
/>
</symbol>
<symbol id="icon-file-word" viewBox="0 0 32 32">
<g>
<circle cx="14" cy="17" r="1" />
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<path d="M17.6,25h-7.2l-4.6-8.1L14,6.4l8.2,10.5L17.6,25z M11.6,23h4.8l3.4-5.9L14,9.6l-5.8,7.5L11.6,23z" />
<rect x="7" y="23" width="14" height="2" />
<circle cx="14" cy="17" r="2" />
</g>
</symbol>
<symbol id="icon-file-audio" viewBox="0 0 32 32">
<g>
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<polygon points="12,20.5 10,20.5 10,8 21,8 21,18.5 19,18.5 19,10 12,10 " />
<path d="M8.5,24C6.6,24,5,22.4,5,20.5S6.6,17,8.5,17s3.5,1.6,3.5,3.5S10.4,24,8.5,24z M8.5,19C7.7,19,7,19.7,7,20.5
S7.7,22,8.5,22s1.5-0.7,1.5-1.5S9.3,19,8.5,19z" />
<path d="M17.5,22c-1.9,0-3.5-1.6-3.5-3.5s1.6-3.5,3.5-3.5s3.5,1.6,3.5,3.5S19.4,22,17.5,22z M17.5,17
c-0.8,0-1.5,0.7-1.5,1.5s0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5S18.3,17,17.5,17z" />
</g>
</symbol>
<symbol id="icon-file-image" viewBox="0 0 32 32">
<g>
<polygon points="13.1,23.4 8.1,19.3 5,22.4 3.6,21 7.9,16.7 12.9,20.6 19,13.4 24.4,20.2 22.8,21.4 19,16.6 " />
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<path d="M12.5,14C10.6,14,9,12.4,9,10.5S10.6,7,12.5,7S16,8.6,16,10.5S14.4,14,12.5,14z M12.5,9
C11.7,9,11,9.7,11,10.5s0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5S13.3,9,12.5,9z" />
</g>
</symbol>
<symbol id="icon-file-text" viewBox="0 0 32 32">
<g>
<path d="M32,32H0V0h32V32z M2,30h28V2H2V30z" />
<polygon points="23.1,25.4 16.3,8 15.7,8 8.9,25.4 7.1,24.6 14.3,6 17.7,6 24.9,24.6 " />
<rect x="11.1" y="16" width="9.8" height="2" />
<rect x="5" y="24" width="6" height="2" />
<rect x="21" y="24" width="6" height="2" />
</g>
</symbol>
<symbol id="icon-file-video" viewBox="0 0 32 32">
<g>
<path d="M32,32H0V0h32V32z M2,30h28V2H2V30z" />
<path d="M11,18.8V5.2L21.9,12L11,18.8z M13,8.8v6.4l5.1-3.2L13,8.8z" />
<rect x="24" y="23" width="4" height="2" />
<rect x="4" y="23" width="12" height="2" />
<path d="M18,28c-2.2,0-4-1.8-4-4s1.8-4,4-4c2.2,0,4,1.8,4,4S20.2,28,18,28z M18,22c-1.1,0-2,0.9-2,2s0.9,2,2,2
c1.1,0,2-0.9,2-2S19.1,22,18,22z" />
</g>
</symbol>
<symbol id="icon-file-code" viewBox="0 0 32 32">
<g>
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<polygon points="9,20.4 4.6,16 9,11.6 10.4,13 7.4,16 10.4,19 " />
<polygon points="19,20.4 17.6,19 20.6,16 17.6,13 19,11.6 23.4,16 " />
<rect x="13" y="8.7" transform="matrix(0.9488 0.3159 -0.3159 0.9488 5.7719 -3.6035)" width="2"
height="14.6" />
</g>
</symbol>
<symbol id="icon-file-zip" viewBox="0 0 32 32">
<g>
<polygon points="28,32 0,32 0,0 15,0 15,8 13,8 13,2 2,2 2,30 26,30 26,2 18,2 18,0 28,0 " />
<path d="M18,19h-8v-9h8V19z M12,17h4v-5h-4V17z" />
<rect x="12" y="25" width="4" height="2" />
<rect x="12" y="21" width="4" height="2" />
</g>
</symbol>
<symbol id="icon-file-document" viewBox="0 0 32 32">
<g>
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<rect x="16" y="7" width="6" height="2" />
<rect x="16" y="12" width="6" height="2" />
<rect x="6" y="17" width="16" height="2" />
<rect x="6" y="22" width="16" height="2" />
<path d="M14,14H6V7h8V14z M8,12h4V9H8V12z" />
</g>
</symbol>
<symbol id="icon-file-spreadsheet" viewBox="0 0 32 32">
<g>
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<rect x="13" y="10" width="2" height="12" />
<rect x="8" y="17" width="2" height="5" />
<rect x="18" y="14" width="2" height="8" />
</g>
</symbol>
<symbol id="icon-file" viewBox="0 0 32 32">
<g>
<path d="M28,32H0V0h28V32z M2,30h24V2H2V30z" />
<path d="M19.5,22h-11C6.6,22,5,20.4,5,18.5c0-1.8,1.3-3.3,3.1-3.5c0.5-2.8,2.9-5,5.9-5s5.5,2.2,5.9,5
c1.7,0.2,3.1,1.7,3.1,3.5C23,20.4,21.4,22,19.5,22z M8.5,17C7.7,17,7,17.7,7,18.5S7.7,20,8.5,20h11c0.8,0,1.5-0.7,1.5-1.5
S20.3,17,19.5,17c-0.1,0-0.2,0-0.3,0L18,17.3l0-1.3c0-2.2-1.8-3.9-4-3.9c-2.2,0-4,1.8-4,3.9l0,0.1v1.2L8.8,17C8.7,17,8.6,17,8.5,17
z" />
</g>
</symbol>
<symbol id="icon-funnel" viewBox="0 0 16 16">
<path d="M9 15H7a1 1 0 0 1 0-2h2a1 1 0 0 1 0 2zM11 11H5a1 1 0 0 1 0-2h6a1 1 0 0 1 0 2zM13 7H3a1 1 0 0 1 0-2h10a1 1 0 0 1 0 2zM15 3H1a1 1 0 0 1 0-2h14a1 1 0 0 1 0 2z"
/>
</symbol>
<symbol id="icon-globe" viewBox="0 0 16 16">
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5.9 7H12c-.1-1.5-.4-2.9-.8-4.1 1.4.9 2.4 2.4 2.7 4.1zM8 14c-.6 0-1.8-1.9-2-5h4c-.2 3.1-1.4 5-2 5zM6 7c.2-3.1 1.3-5 2-5s1.8 1.9 2 5H6zM4.9 2.9C4.4 4.1 4.1 5.5 4 7H2.1c.3-1.7 1.3-3.2 2.8-4.1zM2.1 9H4c.1 1.5.4 2.9.8 4.1-1.4-.9-2.4-2.4-2.7-4.1zm9 4.1c.5-1.2.7-2.6.8-4.1h1.9c-.2 1.7-1.2 3.2-2.7 4.1z"
/>
</symbol>
<symbol id="icon-headline" viewBox="0 0 16 16">
<path d="M0 0v6h1l2-3h3v10l-3 2v1h10v-1l-3-2V3h3l2 3h1V0z" />
</symbol>
<symbol id="icon-image" viewBox="0 0 16 16">
<path d="M15 16H1c-.6 0-1-.4-1-1V1c0-.6.4-1 1-1h14c.6 0 1 .4 1 1v14c0 .6-.4 1-1 1zM2 14h12V2H2v12z" />
<path d="M6 4c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM3 12l2-4 2 2 3-4 3 6z" />
</symbol>
<symbol id="icon-import" viewBox="0 0 16 16">
<path d="M15 15H1a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h4v2H2v10h12V3h-3V1h4a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1z" />
<path d="M9 7V1H7v6H4l4 4 4-4z" />
</symbol>
<symbol id="icon-instagram" viewBox="0 0 16 16">
<path d="M13.105 3.892a.96.96 0 1 1-1.92 0 .96.96 0 0 1 1.92 0zM8 12c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4zm0-6c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2z"
/>
<path d="M12 16H4c-2.056 0-4-1.944-4-4V4c0-2.056 1.944-4 4-4h8c2.056 0 4 1.944 4 4v8c0 2.056-1.944 4-4 4zM4 2c-.935 0-2 1.065-2 2v8c0 .953 1.047 2 2 2h8c.935 0 2-1.065 2-2V4c0-.935-1.065-2-2-2H4z"
/>
</symbol>
<symbol id="icon-italic" viewBox="0 0 16 16">
<path d="M12 2V0H6v2h1.271a.5.5 0 0 1 .495.571l-1.51 10.571a1 1 0 0 1-.99.859H4v2h6v-2H8.729a.5.5 0 0 1-.495-.571l1.51-10.571a1 1 0 0 1 .99-.859H12z"
/>
</symbol>
<symbol id="icon-key" viewBox="0 0 16 16">
<path d="M12.7 0L6.5 6.3C6 6.1 5.5 6 5 6c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5c0-.5-.1-1.1-.3-1.6L11 8V6h2V4h2l1-1V0h-3.3zM4.5 12c-.8 0-1.5-.7-1.5-1.5S3.7 9 4.5 9 6 9.7 6 10.5 5.3 12 4.5 12z"
/>
</symbol>
<symbol id="icon-list-bullet" viewBox="0 0 16 16">
<path d="M4 2a2 2 0 1 1-3.999.001A2 2 0 0 1 4 2zM4 8a2 2 0 1 1-3.999.001A2 2 0 0 1 4 8zM4 14a2 2 0 1 1-3.999.001A2 2 0 0 1 4 14zM6 1h10v2H6V1zM6 7h10v2H6V7zM6 13h10v2H6v-2z"
/>
</symbol>
<symbol id="icon-list-numbers" viewBox="0 0 16 16">
<path d="M5 1h11v2H5V1zM5 7h11v2H5V7zM5 13h11v2H5v-2zM.368 4v-.549l.597-.049c.097-.007.111-.035.111-.139V.839c0-.083-.021-.125-.09-.146L.382.568l.076-.569h1.667v3.264c0 .111.007.132.111.139l.569.049V4H.367zM.047 10v-.63l.907-.886c.683-.663.975-.934.975-1.32 0-.251-.122-.433-.48-.433-.373 0-.528.122-.528.602l-.92-.081C.008 6.176.827 6 1.537 6c1.049 0 1.421.447 1.421 1.083s-.44 1.056-.934 1.53l-.765.731h.995c.068 0 .095-.014.108-.081l.095-.534h.717v1.272H.047zM2.344 13.682c.566.087.879.426.879 1.026 0 .879-.659 1.292-1.612 1.292-.673 0-1.272-.239-1.612-.613l.533-.586c.253.246.533.439.999.439.373 0 .693-.133.693-.599 0-.406-.253-.573-.666-.573a2.66 2.66 0 0 0-.493.047v-.666l.327-.04c.479-.06.739-.293.739-.719 0-.253-.113-.46-.486-.46-.359 0-.532.12-.532.593l-.912-.08c0-1.052.793-1.232 1.492-1.232 1.026 0 1.419.393 1.419 1.106 0 .56-.346.912-.766 1.039v.027z"
/>
</symbol>
<symbol id="icon-loader" viewBox="0 0 16 16">
<path d="M8 16a7.928 7.928 0 0 1-3.428-.77l.857-1.807A6.006 6.006 0 0 0 14 8c0-3.309-2.691-6-6-6a6.006 6.006 0 0 0-5.422 8.572l-1.806.859A7.929 7.929 0 0 1 0 8c0-4.411 3.589-8 8-8s8 3.589 8 8-3.589 8-8 8z"
/>
</symbol>
<symbol id="icon-lock" viewBox="0 0 16 16">
<path d="M8 0C5.8 0 4 1.8 4 4v1H2c-.6 0-1 .4-1 1v9c0 .6.4 1 1 1h12c.6 0 1-.4 1-1V6c0-.6-.4-1-1-1h-2V4c0-2.2-1.8-4-4-4zm1 11.7V13H7v-1.3c-.6-.3-1-1-1-1.7 0-1.1.9-2 2-2s2 .9 2 2c0 .7-.4 1.4-1 1.7zM10 5H6V4c0-1.1.9-2 2-2s2 .9 2 2v1z"
/>
</symbol>
<symbol id="icon-logout" viewBox="0 0 16 16">
<path d="M3.4 2H8v2h2V1c0-.6-.4-1-1-1H1C.4 0 0 .4 0 1v9c0 .3.1.5.3.7l5 5c.2.2.4.3.7.3.1 0 .3 0 .4-.1.4-.1.6-.5.6-.9V6c0-.3-.1-.5-.3-.7L3.4 2zM5 12.6l-3-3V3.4l3 3v6.2z"
/>
<path d="M15.7 7.3L12 3.6 10.6 5l2 2H8v2h4.6l-2 2 1.4 1.4 3.7-3.7c.4-.4.4-1 0-1.4z" />
</symbol>
<symbol id="icon-markdown" viewBox="0 0 16 16">
<path d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"
/>
</symbol>
<symbol id="icon-menu" viewBox="0 0 16 16">
<path d="M0 0h16v3H0V0zM0 6h16v3H0V6zM0 12h16v3H0v-3z" />
</symbol>
<symbol id="icon-open" viewBox="0 0 16 16">
<path d="M8 7l-4 4h3v5h2v-5h3z" />
<path d="M0 1v12a1 1 0 0 0 1 1h4v-2H2V6h12v6h-3v2h4a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1H1a1 1 0 0 0-1 1zm2 3V2h12v2H2z" />
</symbol>
<symbol id="icon-page" viewBox="0 0 16 16">
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h12c.6 0 1-.4 1-1V1c0-.6-.4-1-1-1zm-1 14H3V2h10v12z" />
<path d="M4 3h4v4H4V3zM9 4h3v1H9V4zM9 6h3v1H9V6zM4 8h8v1H4V8zM4 10h8v1H4v-1zM4 12h5v1H4v-1z" />
</symbol>
<symbol id="icon-phone" viewBox="0 0 16 16">
<path d="M15.285 12.305l-2.578-2.594a1 1 0 0 0-1.416-.002L9 12 4 7l2.294-2.294a1 1 0 0 0 .001-1.414L3.715.708a1 1 0 0 0-1.415 0L.004 3.003 0 3c0 7.18 5.82 13 13 13l2.283-2.283a1 1 0 0 0 .002-1.412z"
/>
</symbol>
<symbol id="icon-pinterest" viewBox="0 0 16 16">
<path d="M8 0C3.6 0 0 3.6 0 8c0 3.4 2.1 6.3 5.1 7.4-.1-.6-.1-1.6 0-2.3.1-.6.9-4 .9-4s-.2-.4-.2-1.1c0-1.1.7-2 1.5-2 .7 0 1 .5 1 1.1 0 .7-.4 1.7-.7 2.7-.2.8.4 1.4 1.2 1.4 1.4 0 2.5-1.5 2.5-3.7 0-1.9-1.4-3.3-3.3-3.3-2.3 0-3.6 1.7-3.6 3.5 0 .7.3 1.4.6 1.8v.4c-.1.3-.2.8-.2.9s-.1.2-.3.1c-1-.5-1.6-1.9-1.6-3.1C2.9 5.3 4.7 3 8.2 3c2.8 0 4.9 2 4.9 4.6 0 2.8-1.7 5-4.2 5-.8 0-1.6-.4-1.8-.9 0 0-.4 1.5-.5 1.9-.2.7-.7 1.6-1 2.1.8.2 1.6.3 2.4.3 4.4 0 8-3.6 8-8s-3.6-8-8-8z"
/>
</symbol>
<symbol id="icon-preview" viewBox="0 0 16 16">
<path d="M8 14c4.707 0 7.744-5.284 7.871-5.508a1 1 0 0 0 .001-.98C15.746 7.287 12.731 2 8 2 3.245 2 .251 7.289.126 7.514a.998.998 0 0 0 .002.975C.254 8.713 3.269 14 8 14zM8 4c2.839 0 5.036 2.835 5.818 4-.784 1.166-2.981 4-5.818 4-2.841 0-5.038-2.838-5.819-4.001C2.958 6.835 5.146 4 8 4z"
/>
<path d="M10 8a2 2 0 1 1-3.999.001A2 2 0 0 1 10 8z" />
</symbol>
<symbol id="icon-quote" viewBox="0 0 16 16">
<path d="M3.024 4.561C1.839 6.042 1.668 7.535 2.016 8.58c1.316-1.045 3.145-.826 4.258.214 1.125 1.051 1.222 2.898.504 4.094a3.293 3.293 0 0 1-2.847 1.613C1.238 14.501 0 12.126 0 9.462c0-1.73.441-3.28 1.323-4.649S3.536 2.34 5.317 1.5l.479.932c-1.075.454-1.999 1.163-2.772 2.129zm8.768 0c-1.185 1.481-1.356 2.974-1.008 4.019.588-.454 1.226-.68 1.915-.68C14.511 7.9 16 9.107 16 11.201c0 1.926-1.478 3.301-3.301 3.301-2.693 0-3.931-2.375-3.931-5.039 0-1.73.441-3.28 1.323-4.649s2.213-2.473 3.994-3.313l.479.932c-1.075.454-1.999 1.163-2.772 2.129z"
/>
</symbol>
<symbol id="icon-refresh" viewBox="0 0 16 16">
<path d="M4.5 4.5c1.9-1.9 5.1-1.9 7 0 .7.7 1.2 1.7 1.4 2.7l2-.3c-.2-1.5-.9-2.8-1.9-3.8C10.3.4 5.9.4 3.1 3.1L.9.9.2 7.3l6.4-.7-2.1-2.1zM15.8 8.7l-6.4.7 2.1 2.1c-1.9 1.9-5.1 1.9-7 0-.7-.7-1.2-1.7-1.4-2.7l-2 .3c.2 1.5.9 2.8 1.9 3.8 1.4 1.4 3.1 2 4.9 2s3.6-.7 4.9-2l2.2 2.2.8-6.4z"
/>
</symbol>
<symbol id="icon-search" viewBox="0 0 16 16">
<path d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM15.707 14.293L13.314 11.9a8.019 8.019 0 0 1-1.414 1.414l2.393 2.393a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414z"
/>
</symbol>
<symbol id="icon-settings" viewBox="0 0 16 16">
<path d="M5 5V1c0-.6-.4-1-1-1S3 .4 3 1v4c0 .6.4 1 1 1s1-.4 1-1zM1 11c0 1.3.9 2.4 2 2.8V15c0 .6.4 1 1 1s1-.4 1-1v-1-.2c1.2-.4 2-1.5 2-2.8 0-1.7-1.3-3-3-3s-3 1.3-3 3zM11 11v4c0 .6.4 1 1 1s1-.4 1-1v-4c0-.6-.4-1-1-1s-1 .4-1 1zM9 5c0 1.7 1.3 3 3 3s3-1.3 3-3c0-1.3-.9-2.4-2-2.8V1c0-.6-.4-1-1-1s-1 .4-1 1v1.2C9.9 2.6 9 3.7 9 5z"
/>
</symbol>
<symbol id="icon-smile" viewBox="0 0 16 16">
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z" />
<path d="M7 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM11 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM8 12c1.7 0 3-1.3 3-3H5c0 1.7 1.3 3 3 3z"
/>
</symbol>
<symbol id="icon-sort" viewBox="0 0 10 16">
<path d="M0 0h3v3H0V0zM6 0h3v3H6V0zM0 6h3v3H0V6zM6 6h3v3H6V6zM0 12h3v3H0v-3zM6 12h3v3H6v-3z" />
</symbol>
<symbol id="icon-tag" viewBox="0 0 16 16">
<path d="M15.7 8.3l-8-8C7.5.1 7.3 0 7 0H1C.4 0 0 .4 0 1v6c0 .3.1.5.3.7l8 8c.2.2.4.3.7.3s.5-.1.7-.3l6-6c.4-.4.4-1 0-1.4zM4 5c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z"
/>
</symbol>
<symbol id="icon-text" viewBox="0 0 16 16">
<path d="M0 1h16v2H0V1zM0 5h10v2H0V5zM0 9h16v2H0V9zM0 13h10v2H0v-2z" />
</symbol>
<symbol id="icon-title" viewBox="0 0 16 16">
<path d="M6 1H0v3h1l1-1h3v11l-2 1v1h6v-1l-2-1V3h3l1 1h1V1z" />
<path d="M8 6v3h1l1-1h1v6l-1 1v1h4v-1l-1-1V8h1l1 1h1V6z" />
</symbol>
<symbol id="icon-toggle-off" viewBox="0 0 16 16">
<path d="M8 8a2 2 0 1 1-3.999.001A2 2 0 0 1 8 8z" />
<path d="M10 2H6C2.7 2 0 4.7 0 8s2.7 6 6 6h4c3.3 0 6-2.7 6-6s-2.7-6-6-6zm0 10H6c-2.2 0-4-1.8-4-4s1.8-4 4-4h4c2.2 0 4 1.8 4 4s-1.8 4-4 4z"
/>
</symbol>
<symbol id="icon-toggle-on" viewBox="0 0 16 16">
<path d="M8,8 C8,6.3 9.3,5 11,5 C12.7,5 14,6.3 14,8 C14,9.7 12.7,11 11,11 C9.3,11 8,9.7 8,8 M0,8 C0,10.8 2.2,13 5,13 L11,13 C13.8,13 16,10.8 16,8 C16,5.2 13.8,3 11,3 L5,3 C2.2,3 0,5.2 0,8"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 16 16">
<path d="M5 7h2v6H5V7zM9 7h2v6H9V7z" />
<path d="M12 1c0-.6-.4-1-1-1H5c-.6 0-1 .4-1 1v2H0v2h1v10c0 .6.4 1 1 1h12c.6 0 1-.4 1-1V5h1V3h-4V1zM6 2h4v1H6V2zm7 3v9H3V5h10z"
/>
</symbol>
<symbol id="icon-twitter" viewBox="0 0 16 16">
<path d="M16 3c-.6.3-1.2.4-1.9.5.7-.4 1.2-1 1.4-1.8-.6.4-1.3.6-2.1.8-.6-.6-1.5-1-2.4-1-1.7 0-3.2 1.5-3.2 3.3 0 .3 0 .5.1.7-2.7-.1-5.2-1.4-6.8-3.4-.3.5-.4 1-.4 1.7 0 1.1.6 2.1 1.5 2.7-.5 0-1-.2-1.5-.4C.7 7.7 1.8 9 3.3 9.3c-.3.1-.6.1-.9.1-.2 0-.4 0-.6-.1.4 1.3 1.6 2.3 3.1 2.3-1.1.9-2.5 1.4-4.1 1.4H0c1.5.9 3.2 1.5 5 1.5 6 0 9.3-5 9.3-9.3v-.4C15 4.3 15.6 3.7 16 3z"
/>
</symbol>
<symbol id="icon-undo" viewBox="0 0 16 16">
<path d="M2.502 12.333a6.996 6.996 0 0 1-1.405-3.168l1.973-.331a4.982 4.982 0 0 0 1.003 2.26l-1.571 1.239zM6.834 14.903a7.015 7.015 0 0 1-2.976-1.259l1.186-1.611c.624.459 1.358.77 2.122.898l-.332 1.972zM9.165 14.903l-.33-1.973a4.99 4.99 0 0 0 2.209-.964l1.219 1.586a6.997 6.997 0 0 1-3.098 1.351zM8 1c-1.873 0-3.65.759-4.948 2.052L.9.9.2 7.3l6.4-.7-2.135-2.135A5.023 5.023 0 0 1 8 3c2.757 0 5 2.243 5 5 0 1.06-.327 2.072-.947 2.928l1.621 1.173A6.96 6.96 0 0 0 15 8c0-3.86-3.141-7-7-7z"
/>
</symbol>
<symbol id="icon-upload" viewBox="0 0 16 16">
<path d="M7 3.4V12h2V3.4l4 4L14.4 6 8.7.3c-.4-.4-1-.4-1.4 0L1.6 6 3 7.4l4-4z" />
<path d="M14 14H2v-3H0v4c0 .6.4 1 1 1h14c.6 0 1-.4 1-1v-4h-2v3z" />
</symbol>
<symbol id="icon-url" viewBox="0 0 16 16">
<path d="M11 0C9.7 0 8.4.5 7.5 1.5L6.3 2.6c-.4.4-.4 1 0 1.4s1 .4 1.4 0l1.2-1.2c1.1-1.1 3.1-1.1 4.2 0 .6.6.9 1.4.9 2.2s-.3 1.6-.9 2.1L12 8.3c-.4.4-.4 1 0 1.4.2.2.5.3.7.3s.5-.1.7-.3l1.2-1.2C15.5 7.6 16 6.3 16 5s-.5-2.6-1.5-3.5C13.6.5 12.3 0 11 0zM8.3 12l-1.2 1.2c-1.1 1.1-3.1 1.1-4.2 0-.6-.6-.9-1.4-.9-2.2s.3-1.6.9-2.1L4 7.7c.4-.4.4-1 0-1.4s-1-.4-1.4 0L1.5 7.5C.5 8.4 0 9.7 0 11s.5 2.6 1.5 3.5c.9 1 2.2 1.5 3.5 1.5s2.6-.5 3.5-1.5l1.2-1.2c.4-.4.4-1 0-1.4s-1-.3-1.4.1z"
/>
<path d="M9.4 5.2L5.2 9.4c-.4.4-.4 1 0 1.4.2.2.5.3.7.3s.5-.1.7-.3l4.2-4.2c.4-.4.4-1 0-1.4s-1-.4-1.4 0z" />
</symbol>
<symbol id="icon-user" viewBox="0 0 16 16">
<path d="M8 0C5.794 0 4 1.794 4 4v1c0 2.206 1.794 4 4 4s4-1.794 4-4V4c0-2.206-1.794-4-4-4zM12.036 9.426C10.969 10.4 9.555 11 8 11s-2.969-.6-4.036-1.574C2.203 10 1 11.311 1 13v3h14v-3c0-1.689-1.203-3-2.964-3.574z"
/>
</symbol>
<symbol id="icon-users" viewBox="0 0 16 16">
<path d="M10.2 11.4l-2.7-2C8.4 8.7 9 7.7 9 6.5v-.8C9 3.8 7.6 2.1 5.7 2 3.7 1.9 2 3.5 2 5.5v1c0 1.2.6 2.2 1.5 2.9L.8 11.5c-.5.4-.8 1-.8 1.6V15c0 .6.4 1 1 1h9c.6 0 1-.4 1-1v-2c0-.6-.3-1.2-.8-1.6zM15.1 6.4l-1.8-1.2c.4-.4.7-1 .7-1.7v-.9c0-1.2-.9-2.4-2.1-2.6S9.7.5 9.2 1.4c1.1 1 1.8 2.4 1.8 4v1c0 .9-.2 1.8-.6 2.5 0 0 1.2.9 1.2 1H15c.6 0 1-.4 1-1v-.8c0-.7-.3-1.3-.9-1.7z"
/>
</symbol>
<symbol id="icon-video" viewBox="0 0 16 16">
<path d="M11 9V7c0-.6-.4-1-1-1H1c-.6 0-1 .4-1 1v8c0 .6.4 1 1 1h9c.6 0 1-.4 1-1v-2l5 2V7l-5 2zM5 2.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zM10 3a2 2 0 1 1-3.999.001A2 2 0 0 1 10 3z"
/>
</symbol>
<symbol id="icon-wand" viewBox="0 0 16 16">
<path d="M8.046 4.985l2.97 2.97-7.99 7.99-2.97-2.97 7.99-7.99zM12.995 1.591l1.414 1.414-2.404 2.404-1.414-1.414 2.404-2.404zM13 7h3v2h-3V7zM12.243 10.834l2.121 2.121-1.414 1.414-2.121-2.121 1.414-1.414zM7 0h2v3H7V0zM3.05 1.631l2.121 2.121-1.414 1.414-2.121-2.121L3.05 1.631z"
/>
</symbol>
<symbol id="icon-template" viewBox="0 0 16 16">
<path d="M15 1h-3v2h2v11H2V3h2V1H1c-.6 0-1 .4-1 1v13c0 .6.4 1 1 1h14c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1z" />
<path d="M5 0h6v4H5zM4 6h8v2H4zM4 10h8v2H4z" />
</symbol>
<symbol id="icon-parent" viewBox="0 0 16 16">
<path d="M14,6H3.4l4-4L6,0.6L0.3,6.3c-0.4,0.4-0.4,1,0,1.4L6,13.4L7.4,12l-4-4H13v7h2V7C15,6.4,14.6,6,14,6z" />
</symbol>
<symbol id="icon-circle" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="8" />
</symbol>
<symbol id="icon-circle-outline" viewBox="0 0 16 16">
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z" />
</symbol>
<symbol id="icon-circle-filled" viewBox="0 0 16 16">
<path d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,14c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6 S11.3,14,8,14z" />
<circle cx="8" cy="8" r="4" />
</symbol>
<symbol id="icon-protected" viewBox="0 0 16 16">
<path d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,2c1.3,0,2.5,0.4,3.5,1.1l-8.4,8.4 C2.4,10.5,2,9.3,2,8C2,4.7,4.7,2,8,2z M8,14c-1.3,0-2.5-0.4-3.5-1.1l8.4-8.4C13.6,5.5,14,6.7,14,8C14,11.3,11.3,14,8,14z" />
</symbol>
<symbol id="icon-share" viewBox="0 0 16 16">
<path d="M15,16H1c-0.6,0-1-0.4-1-1V3c0-0.6,0.4-1,1-1h3v2H2v10h12V9h2v6C16,15.6,15.6,16,15,16z" />
<path d="M10,3c-3.2,0-6,2.5-6,7c1.1-1.7,2.4-3,6-3v3l6-5l-6-5V3z" />
</symbol>
<symbol id="icon-remove" viewBox="0 0 16 16">
<rect data-color="color-2" x="4" y="7" width="8" height="2"></rect> <path d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,14c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6 S11.3,14,8,14z"></path>
</symbol>
<symbol id="icon-cart" viewBox="0 0 16 16">
<path d="M15,3H4.5L4,0.8C3.9,0.3,3.5,0,3,0H0v2h2.2L4,10.2C4.1,10.7,4.5,11,5,11h8c0.4,0,0.8-0.3,0.9-0.7l2-6 C16.1,3.8,15.8,3,15,3z"></path>
<circle data-color="color-2" cx="5" cy="14" r="2"></circle>
<circle data-color="color-2" cx="13" cy="14" r="2"></circle>
</symbol>
<symbol id="icon-key" viewBox="0 0 16 16">
<path d="M12.7,0L6.5,6.3C6,6.1,5.5,6,5,6c-2.8,0-5,2.2-5,5s2.2,5,5,5s5-2.2,5-5c0-0.5-0.1-1.1-0.3-1.6L11,8V6h2V4h2 l1-1V0H12.7z M4.5,12C3.7,12,3,11.3,3,10.5S3.7,9,4.5,9S6,9.7,6,10.5S5.3,12,4.5,12z"></path>
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB

1
kirby/panel/dist/js/app.js vendored Executable file

File diff suppressed because one or more lines are too long

49
kirby/panel/dist/js/plugins.js vendored Executable file
View File

@@ -0,0 +1,49 @@
window.panel = window.panel || {};
window.panel.plugins = {
components: {},
fields: {},
sections: {},
routes: [],
use: [],
views: {},
};
window.panel.plugin = function (plugin, parts) {
// Components
resolve(parts, "components", function (name, options) {
window.panel.plugins["components"][name] = options;
});
// Fields
resolve(parts, "fields", function (name, options) {
window.panel.plugins["fields"][`k-${name}-field`] = options;
});
// Sections
resolve(parts, "sections", function (name, options) {
window.panel.plugins["sections"][`k-${name}-section`] = options;
});
// Vue.use
resolve(parts, "use", function (name, options) {
window.panel.plugins["use"].push(options);
});
// Views
resolve(parts, "views", function (name, options) {
window.panel.plugins["views"][name] = options;
});
};
function resolve(object, type, callback) {
if (object[type]) {
if (Object.entries) {
Object.entries(object[type]).forEach(function ([name, options]) {
callback(name, options);
});
}
}
}

39
kirby/panel/dist/js/vendor.js vendored Executable file

File diff suppressed because one or more lines are too long

12
kirby/router.php Executable file
View File

@@ -0,0 +1,12 @@
<?php
$root = dirname(__DIR__);
// https://yourdomain.com/media/super/nice.jpg
if (file_exists($root . '/' . $_SERVER['SCRIPT_NAME'])) {
return false; // serve the requested resource as-is.
}
$_SERVER['SCRIPT_NAME'] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $index = $root . '/index.php');
include $index;

428
kirby/src/Api/Api.php Executable file
View File

@@ -0,0 +1,428 @@
<?php
namespace Kirby\Api;
use Closure;
use Exception;
use Throwable;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Router;
use Kirby\Http\Response;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Properties;
/**
* The API class is a generic container
* for API routes, models and collections and is used
* to run our REST API. You can find our API setup
* in kirby/config/api.php
*/
class Api
{
use Properties;
protected $authentication;
protected $debug = false;
protected $collections = [];
protected $data = [];
protected $models = [];
protected $routes = [];
protected $requestData = [];
protected $requestMethod;
public function __call($method, $args)
{
return $this->data($method, ...$args);
}
public function __construct(array $props)
{
$this->setProperties($props);
}
public function authenticate()
{
if ($auth = $this->authentication()) {
return $auth->call($this);
}
return true;
}
public function authentication()
{
return $this->authentication;
}
public function call(string $path = null, string $method = 'GET', array $requestData = [])
{
$path = rtrim($path, '/');
$this->setRequestMethod($method);
$this->setRequestData($requestData);
$router = new Router($this->routes());
$result = $router->find($path, $method);
$auth = $result->attributes()['auth'] ?? true;
if ($auth !== false) {
$this->authenticate();
}
$output = $result->action()->call($this, ...$result->arguments());
if (is_object($output) === true) {
return $this->resolve($output)->toResponse();
}
return $output;
}
public function collection(string $name, $collection = null)
{
if (isset($this->collections[$name]) === false) {
throw new NotFoundException(sprintf('The collection "%s" does not exist', $name));
}
return new Collection($this, $collection, $this->collections[$name]);
}
public function collections(): array
{
return $this->collections;
}
public function data($key = null, ...$args)
{
if ($key === null) {
return $this->data;
}
if ($this->hasData($key) === false) {
throw new NotFoundException(sprintf('Api data for "%s" does not exist', $key));
}
// lazy-load data wrapped in Closures
if (is_a($this->data[$key], 'Closure') === true) {
return $this->data[$key]->call($this, ...$args);
}
return $this->data[$key];
}
public function hasData($key): bool
{
return isset($this->data[$key]) === true;
}
public function model(string $name, $object = null)
{
if (isset($this->models[$name]) === false) {
throw new NotFoundException(sprintf('The model "%s" does not exist', $name));
}
return new Model($this, $object, $this->models[$name]);
}
public function models(): array
{
return $this->models;
}
public function requestData($type = null, $key = null, $default = null)
{
if ($type === null) {
return $this->requestData;
}
if ($key === null) {
return $this->requestData[$type] ?? [];
}
$data = array_change_key_case($this->requestData($type));
$key = strtolower($key);
return $data[$key] ?? $default;
}
public function requestBody(string $key = null, $default = null)
{
return $this->requestData('body', $key, $default);
}
public function requestFiles(string $key = null, $default = null)
{
return $this->requestData('files', $key, $default);
}
public function requestHeaders(string $key = null, $default = null)
{
return $this->requestData('headers', $key, $default);
}
public function requestMethod(): string
{
return $this->requestMethod;
}
public function requestQuery(string $key = null, $default = null)
{
return $this->requestData('query', $key, $default);
}
public function resolve($object)
{
if (is_a($object, 'Kirby\Api\Model') === true || is_a($object, 'Kirby\Api\Collection') === true) {
return $object;
}
$className = strtolower(get_class($object));
$lastDash = strrpos($className, '\\');
if ($lastDash !== false) {
$className = substr($className, $lastDash + 1);
}
if (isset($this->models[$className]) === true) {
return $this->model($className, $object);
}
if (isset($this->collections[$className]) === true) {
return $this->collection($className, $object);
}
// now models deeply by checking for the actual type
foreach ($this->models as $modelClass => $model) {
if (is_a($object, $model['type']) === true) {
return $this->model($modelClass, $object);
}
}
throw new NotFoundException(sprintf('The object "%s" cannot be resolved', $className));
}
public function routes(): array
{
return $this->routes;
}
protected function setAuthentication(Closure $authentication = null)
{
$this->authentication = $authentication;
return $this;
}
protected function setCollections(array $collections = null)
{
if ($collections !== null) {
$this->collections = array_change_key_case($collections);
}
return $this;
}
protected function setData(array $data = null)
{
$this->data = $data ?? [];
return $this;
}
protected function setDebug(bool $debug = false)
{
$this->debug = $debug;
return $this;
}
protected function setModels(array $models = null)
{
if ($models !== null) {
$this->models = array_change_key_case($models);
}
return $this;
}
protected function setRequestData(array $requestData = null)
{
$defaults = [
'query' => [],
'body' => [],
'files' => []
];
$this->requestData = array_merge($defaults, (array)$requestData);
return $this;
}
protected function setRequestMethod(string $requestMethod = null)
{
$this->requestMethod = $requestMethod;
return $this;
}
protected function setRoutes(array $routes = null)
{
$this->routes = $routes ?? [];
return $this;
}
public function render(string $path, $method = 'GET', array $requestData = [])
{
try {
$result = $this->call($path, $method, $requestData);
} catch (Throwable $e) {
if (is_a($e, 'Kirby\Exception\Exception') === true) {
$result = ['status' => 'error'] + $e->toArray();
} else {
$result = [
'status' => 'error',
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => ltrim($e->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null),
'line' => $e->getLine(),
'code' => empty($e->getCode()) === false ? $e->getCode() : 500
];
}
}
if ($result === null) {
$result = [
'status' => 'error',
'message' => 'not found',
'code' => 404,
];
}
if ($result === true) {
$result = [
'status' => 'ok',
];
}
if ($result === false) {
$result = [
'status' => 'error',
'message' => 'bad request',
'code' => 400,
];
}
if (is_array($result) === false) {
return $result;
}
// pretty print json data
$pretty = (bool)($requestData['query']['pretty'] ?? false) === true;
// remove critical info from the result set if
// debug mode is switched off
if ($this->debug !== true) {
unset(
$result['file'],
$result['exception'],
$result['line']
);
}
if (($result['status'] ?? 'ok') === 'error') {
$code = $result['code'] ?? 400;
// sanitize the error code
if ($code < 400 || $code > 599) {
$code = 500;
}
return Response::json($result, $code, $pretty);
}
return Response::json($result, 200, $pretty);
}
public function upload(Closure $callback, $single = false): array
{
$trials = 0;
$uploads = [];
$errors = [];
$files = $this->requestFiles();
if (empty($files) === true) {
throw new Exception('No uploaded files');
}
foreach ($files as $upload) {
if (isset($upload['tmp_name']) === false && is_array($upload)) {
continue;
}
$trials++;
try {
if ($upload['error'] !== 0) {
throw new Exception('Upload error');
}
// get the extension of the uploaded file
$extension = F::extension($upload['name']);
// try to detect the correct mime and add the extension
// accordingly. This will avoid .tmp filenames
if (empty($extension) === true || in_array($extension, ['tmp', 'temp'])) {
$mime = F::mime($upload['tmp_name']);
$extension = F::mimeToExtension($mime);
$filename = F::name($upload['name']) . '.' .$extension;
} else {
$filename = basename($upload['name']);
}
$source = dirname($upload['tmp_name']) . '/' . uniqid() . '.' . $filename;
// move the file to a location including the extension,
// for better mime detection
if (move_uploaded_file($upload['tmp_name'], $source) === false) {
throw new Exception('The uploaded file could not be moved');
}
$data = $callback($source, $filename);
if (is_object($data) === true) {
$data = $this->resolve($data)->toArray();
}
$uploads[$upload['name']] = $data;
} catch (Exception $e) {
$errors[$upload['name']] = $e->getMessage();
}
if ($single === true) {
break;
}
}
// return a single upload response
if ($trials === 1) {
if (empty($errors) === false) {
return [
'status' => 'error',
'message' => current($errors)
];
}
return [
'status' => 'ok',
'data' => current($uploads)
];
}
if (empty($errors) === false) {
return [
'status' => 'error',
'errors' => $errors
];
}
return [
'status' => 'ok',
'data' => $uploads
];
}
}

124
kirby/src/Api/Collection.php Executable file
View File

@@ -0,0 +1,124 @@
<?php
namespace Kirby\Api;
use Closure;
use Exception;
use Kirby\Toolkit\Str;
/**
* The Collection class is a wrapper
* around our Kirby Collections and handles
* stuff like pagination and proper JSON output
* for collections in REST calls.
*/
class Collection
{
protected $api;
protected $data;
protected $model;
protected $select;
protected $view;
public function __construct(Api $api, $data = null, array $schema)
{
$this->api = $api;
$this->data = $data;
$this->model = $schema['model'];
$this->view = $schema['view'] ?? null;
if ($data === null) {
if (is_a($schema['default'] ?? null, 'Closure') === false) {
throw new Exception('Missing collection data');
}
$this->data = $schema['default']->call($this->api);
}
if (isset($schema['type']) === true && is_a($this->data, $schema['type']) === false) {
throw new Exception('Invalid collection type');
}
}
public function select($keys = null)
{
if ($keys === false) {
return $this;
}
if (is_string($keys)) {
$keys = Str::split($keys);
}
if ($keys !== null && is_array($keys) === false) {
throw new Exception('Invalid select keys');
}
$this->select = $keys;
return $this;
}
public function toArray(): array
{
$result = [];
foreach ($this->data as $item) {
$model = $this->api->model($this->model, $item);
if ($this->view !== null) {
$model = $model->view($this->view);
}
if ($this->select !== null) {
$model = $model->select($this->select);
}
$result[] = $model->toArray();
}
return $result;
}
public function toResponse(): array
{
if ($query = $this->api->requestQuery('query')) {
$this->data = $this->data->query($query);
}
if (!$this->data->pagination()) {
$this->data = $this->data->paginate([
'page' => $this->api->requestQuery('page', 1),
'limit' => $this->api->requestQuery('limit', 100)
]);
}
$pagination = $this->data->pagination();
if ($select = $this->api->requestQuery('select')) {
$this->select($select);
}
if ($view = $this->api->requestQuery('view')) {
$this->view($view);
}
return [
'code' => 200,
'data' => $this->toArray(),
'pagination' => [
'page' => $pagination->page(),
'total' => $pagination->total(),
'offset' => $pagination->offset(),
'limit' => $pagination->limit(),
],
'status' => 'ok',
'type' => 'collection'
];
}
public function view(string $view)
{
$this->view = $view;
return $this;
}
}

188
kirby/src/Api/Model.php Executable file
View File

@@ -0,0 +1,188 @@
<?php
namespace Kirby\Api;
use Closure;
use Exception;
use Kirby\Toolkit\Str;
/**
* The API Model class can be wrapped around any
* kind of object. Each model defines a set of properties that
* are availabel in REST calls. Those properties are defined as
* simple Closures which are resolved on demand. This is inspired
* by GraphQLs architecture and makes it possible to load
* only the model data that is needed for the current API call.
*
*/
class Model
{
protected $api;
protected $data;
protected $fields;
protected $select;
protected $views;
public function __construct(Api $api, $data = null, array $schema)
{
$this->api = $api;
$this->data = $data;
$this->fields = $schema['fields'] ?? [];
$this->select = $schema['select'] ?? null;
$this->views = $schema['views'] ?? [];
if ($this->select === null && array_key_exists('default', $this->views)) {
$this->view('default');
}
if ($data === null) {
if (is_a($schema['default'] ?? null, 'Closure') === false) {
throw new Exception('Missing model data');
}
$this->data = $schema['default']->call($this->api);
}
if (isset($schema['type']) === true && is_a($this->data, $schema['type']) === false) {
throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', get_class($this->data), $schema['type']));
}
}
public function select($keys = null)
{
if ($keys === false) {
return $this;
}
if (is_string($keys)) {
$keys = Str::split($keys);
}
if ($keys !== null && is_array($keys) === false) {
throw new Exception('Invalid select keys');
}
$this->select = $keys;
return $this;
}
public function selection(): array
{
$select = $this->select;
if ($select === null) {
$select = array_keys($this->fields);
}
$selection = [];
foreach ($select as $key => $value) {
if (is_int($key) === true) {
$selection[$value] = [
'view' => null,
'select' => null
];
continue;
}
if (is_string($value) === true) {
if ($value === 'any') {
throw new Exception('Invalid sub view: "any"');
}
$selection[$key] = [
'view' => $value,
'select' => null
];
continue;
}
if (is_array($value) === true) {
$selection[$key] = [
'view' => null,
'select' => $value
];
}
}
return $selection;
}
public function toArray(): array
{
$select = $this->selection();
$result = [];
foreach ($this->fields as $key => $resolver) {
if (array_key_exists($key, $select) === false || is_a($resolver, 'Closure') === false) {
continue;
}
$value = $resolver->call($this->api, $this->data);
if (is_object($value)) {
$value = $this->api->resolve($value);
}
if (is_a($value, 'Kirby\Api\Collection') === true || is_a($value, 'Kirby\Api\Model') === true) {
$selection = $select[$key];
if ($subview = $selection['view']) {
$value->view($subview);
}
if ($subselect = $selection['select']) {
$value->select($subselect);
}
$value = $value->toArray();
}
$result[$key] = $value;
}
ksort($result);
return $result;
}
public function toResponse(): array
{
$model = $this;
if ($select = $this->api->requestQuery('select')) {
$model = $model->select($select);
}
if ($view = $this->api->requestQuery('view')) {
$model = $model->view($view);
}
return [
'code' => 200,
'data' => $model->toArray(),
'status' => 'ok',
'type' => 'model'
];
}
public function view(string $name)
{
if ($name === 'any') {
return $this->select(null);
}
if (isset($this->views[$name]) === false) {
$name = 'default';
// try to fall back to the default view at least
if (isset($this->views[$name]) === false) {
throw new Exception(sprintf('The view "%s" does not exist', $name));
}
}
return $this->select($this->views[$name]);
}
}

77
kirby/src/Cache/ApcuCache.php Executable file
View File

@@ -0,0 +1,77 @@
<?php
namespace Kirby\Cache;
/**
* APCu Cache Driver
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
*/
class ApcuCache extends Cache
{
/**
* 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(string $key, $value, int $minutes = 0)
{
return apcu_store($key, $this->value($value, $minutes)->toJson(), $this->expiration($minutes));
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve(string $key)
{
return Value::fromJson(apcu_fetch($key));
}
/**
* Checks if the current key exists in cache
*
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
{
return apcu_exists($key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return apcu_delete($key);
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush(): bool
{
return apcu_clear_cache();
}
}

237
kirby/src/Cache/Cache.php Executable file
View File

@@ -0,0 +1,237 @@
<?php
namespace Kirby\Cache;
/**
* Cache foundation
* This class doesn't do anything
* and is perfect as foundation for
* other cache drivers and to be used
* when the cache is disabled
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
*/
class Cache
{
/**
* stores all options for the driver
* @var array
*/
protected $options = [];
/**
* Set all parameters which are needed to connect to the cache storage
*
* @param array $options
*/
public function __construct(array $options = [])
{
$this->options = $options;
}
/**
* 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(string $key, $value, int $minutes = 0)
{
return null;
}
/**
* Private method to retrieve the cache value
* This needs to be defined by the driver
*
* @param string $key
* @return mixed
*/
public function retrieve(string $key)
{
return null;
}
/**
* 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(string $key, $default = null)
{
// get the Value
$value = $this->retrieve($key);
// check for a valid cache value
if (!is_a($value, 'Kirby\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 $cache ?? $default;
}
/**
* Calculates the expiration timestamp
*
* @param int $minutes
* @return int
*/
protected function expiration(int $minutes = 0): int
{
// keep forever if minutes are not defined
if ($minutes === 0) {
$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(string $key)
{
// get the Value object
$value = $this->retrieve($key);
// check for a valid Value object
if (!is_a($value, 'Kirby\Cache\Value')) {
return false;
}
// return the expires timestamp
return $value->expires();
}
/**
* Checks if an item in the cache is expired
*
* @param string $key
* @return boolean
*/
public function expired(string $key): bool
{
return $this->expires($key) <= time();
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return mixed
*/
public function created(string $key)
{
// get the Value object
$value = $this->retrieve($key);
// check for a valid Value object
if (!is_a($value, 'Kirby\Cache\Value')) {
return false;
}
// return the expires timestamp
return $value->created();
}
/**
* Alternate version for Cache::created($key)
*
* @param string $key
* @return mixed
*/
public function modified(string $key)
{
return static::created($key);
}
/**
* Returns Value object
*
* @param mixed $value The value, which should be cached
* @param int $minutes The number of minutes before expiration
* @return Value
*/
protected function value($value, int $minutes): Value
{
return new Value($value, $minutes);
}
/**
* Determine if an item exists in the cache.
*
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
{
return !$this->expired($key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return true;
}
/**
* Flush the entire cache
*
* @return boolean
*/
public function flush(): bool
{
return true;
}
/**
* Returns all passed cache options
*
* @return array
*/
public function options(): array
{
return $this->options;
}
}

126
kirby/src/Cache/FileCache.php Executable file
View File

@@ -0,0 +1,126 @@
<?php
namespace Kirby\Cache;
use Exception;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
/**
* File System Cache Driver
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
*/
class FileCache extends Cache
{
/**
* Set all parameters which are needed for the file cache
* see defaults for available parameters
*
* @param array $params
*/
public function __construct(array $params)
{
$defaults = [
'root' => null,
'extension' => null
];
parent::__construct(array_merge($defaults, $params));
// try to create the directory
Dir::make($this->options['root'], true);
// check for a valid cache directory
if (is_dir($this->options['root']) === false) {
throw new Exception('The cache directory does not exist');
}
}
/**
* Returns the full path to a file for a given key
*
* @param string $key
* @return string
*/
protected function file(string $key): string
{
$extension = isset($this->options['extension']) ? '.' . $this->options['extension'] : '';
return $this->options['root'] . '/' . $key . $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
*/
public function set(string $key, $value, int $minutes = 0)
{
return F::write($this->file($key), $this->value($value, $minutes)->toJson());
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve(string $key)
{
return Value::fromJson(F::read($this->file($key)));
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return int
*/
public function created(string $key): int
{
// 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(string $key): bool
{
return F::remove($this->file($key));
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush(): bool
{
if (Dir::remove($this->options['root']) === true && Dir::make($this->options['root']) === true) {
return true;
}
return false;
}
}

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