first version
This commit is contained in:
15
kirby/.editorconfig
Executable file
15
kirby/.editorconfig
Executable 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
25
kirby/README.md
Executable file
@@ -0,0 +1,25 @@
|
||||
# Kirby
|
||||
|
||||
[](https://travis-ci.com/k-next/kirby)
|
||||
[](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
34
kirby/bootstrap.php
Executable 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
52
kirby/composer.json
Executable 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
577
kirby/composer.lock
generated
Executable 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
65
kirby/config/aliases.php
Executable 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);
|
||||
}
|
||||
});
|
25
kirby/config/api/authentication.php
Executable file
25
kirby/config/api/authentication.php
Executable 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);
|
||||
};
|
80
kirby/config/api/collections.php
Executable file
80
kirby/config/api/collections.php
Executable 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
24
kirby/config/api/models.php
Executable 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
160
kirby/config/api/models/File.php
Executable 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'
|
||||
]
|
||||
],
|
||||
];
|
26
kirby/config/api/models/FileBlueprint.php
Executable file
26
kirby/config/api/models/FileBlueprint.php
Executable 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' => [
|
||||
],
|
||||
];
|
83
kirby/config/api/models/FileVersion.php
Executable file
83
kirby/config/api/models/FileVersion.php
Executable 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'
|
||||
]
|
||||
],
|
||||
];
|
37
kirby/config/api/models/Language.php
Executable file
37
kirby/config/api/models/Language.php
Executable 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
155
kirby/config/api/models/Page.php
Executable 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',
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
35
kirby/config/api/models/PageBlueprint.php
Executable file
35
kirby/config/api/models/PageBlueprint.php
Executable 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' => [
|
||||
],
|
||||
];
|
31
kirby/config/api/models/Role.php
Executable file
31
kirby/config/api/models/Role.php
Executable 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'
|
||||
]
|
||||
]
|
||||
];
|
65
kirby/config/api/models/Site.php
Executable file
65
kirby/config/api/models/Site.php
Executable 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'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
26
kirby/config/api/models/SiteBlueprint.php
Executable file
26
kirby/config/api/models/SiteBlueprint.php
Executable 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' => [
|
||||
],
|
||||
];
|
95
kirby/config/api/models/System.php
Executable file
95
kirby/config/api/models/System.php
Executable 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'
|
||||
]
|
||||
],
|
||||
];
|
34
kirby/config/api/models/Translation.php
Executable file
34
kirby/config/api/models/Translation.php
Executable 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
102
kirby/config/api/models/User.php
Executable 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',
|
||||
],
|
||||
]
|
||||
];
|
26
kirby/config/api/models/UserBlueprint.php
Executable file
26
kirby/config/api/models/UserBlueprint.php
Executable 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
25
kirby/config/api/routes.php
Executable 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;
|
||||
};
|
58
kirby/config/api/routes/auth.php
Executable file
58
kirby/config/api/routes/auth.php
Executable 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
100
kirby/config/api/routes/files.php
Executable 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);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
];
|
46
kirby/config/api/routes/languages.php
Executable file
46
kirby/config/api/routes/languages.php
Executable 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
105
kirby/config/api/routes/pages.php
Executable 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);
|
||||
}
|
||||
}
|
||||
],
|
||||
];
|
21
kirby/config/api/routes/roles.php
Executable file
21
kirby/config/api/routes/roles.php
Executable 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);
|
||||
}
|
||||
]
|
||||
];
|
95
kirby/config/api/routes/site.php
Executable file
95
kirby/config/api/routes/site.php
Executable 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);
|
||||
}
|
||||
]
|
||||
|
||||
];
|
72
kirby/config/api/routes/system.php
Executable file
72
kirby/config/api/routes/system.php
Executable 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()
|
||||
];
|
||||
}
|
||||
]
|
||||
|
||||
];
|
24
kirby/config/api/routes/translations.php
Executable file
24
kirby/config/api/routes/translations.php
Executable 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
137
kirby/config/api/routes/users.php
Executable 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
7
kirby/config/blueprints.php
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'files/default' => __DIR__ . '/blueprints/file.yml',
|
||||
'pages/default' => __DIR__ . '/blueprints/page.yml',
|
||||
'site' => __DIR__ . '/blueprints/site.yml'
|
||||
];
|
2
kirby/config/blueprints/file.yml
Executable file
2
kirby/config/blueprints/file.yml
Executable file
@@ -0,0 +1,2 @@
|
||||
name: File
|
||||
title: file
|
3
kirby/config/blueprints/page.yml
Executable file
3
kirby/config/blueprints/page.yml
Executable file
@@ -0,0 +1,3 @@
|
||||
name: Page
|
||||
title: Page
|
||||
|
7
kirby/config/blueprints/site.yml
Executable file
7
kirby/config/blueprints/site.yml
Executable file
@@ -0,0 +1,7 @@
|
||||
name: Site
|
||||
title: Site
|
||||
sections:
|
||||
pages:
|
||||
headline: Pages
|
||||
type: pages
|
||||
|
95
kirby/config/components.php
Executable file
95
kirby/config/components.php
Executable 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
27
kirby/config/fields.php
Executable 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'
|
||||
];
|
64
kirby/config/fields/checkboxes.php
Executable file
64
kirby/config/fields/checkboxes.php
Executable 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
76
kirby/config/fields/date.php
Executable 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
38
kirby/config/fields/email.php
Executable 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
188
kirby/config/fields/files.php
Executable 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'
|
||||
]
|
||||
];
|
27
kirby/config/fields/headline.php
Executable file
27
kirby/config/fields/headline.php
Executable 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
3
kirby/config/fields/hidden.php
Executable file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
return [];
|
23
kirby/config/fields/info.php
Executable file
23
kirby/config/fields/info.php
Executable 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
5
kirby/config/fields/line.php
Executable file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'save' => false
|
||||
];
|
43
kirby/config/fields/mixins/options.php
Executable file
43
kirby/config/fields/mixins/options.php
Executable 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);
|
||||
},
|
||||
]
|
||||
];
|
25
kirby/config/fields/multiselect.php
Executable file
25
kirby/config/fields/multiselect.php
Executable 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
49
kirby/config/fields/number.php
Executable 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
188
kirby/config/fields/pages.php
Executable 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
32
kirby/config/fields/radio.php
Executable 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
24
kirby/config/fields/range.php
Executable 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
18
kirby/config/fields/select.php
Executable 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
159
kirby/config/fields/structure.php
Executable 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
91
kirby/config/fields/tags.php
Executable 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
27
kirby/config/fields/tel.php
Executable 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
103
kirby/config/fields/text.php
Executable 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'
|
||||
]
|
||||
];
|
61
kirby/config/fields/textarea.php
Executable file
61
kirby/config/fields/textarea.php
Executable 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
68
kirby/config/fields/time.php
Executable 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
64
kirby/config/fields/toggle.php
Executable 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
39
kirby/config/fields/url.php
Executable 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
95
kirby/config/fields/users.php
Executable 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
764
kirby/config/helpers.php
Executable 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
389
kirby/config/methods.php
Executable 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
|
||||
*/
|
||||
'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
14
kirby/config/presets/files.php
Executable 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
72
kirby/config/presets/page.php
Executable 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
57
kirby/config/presets/pages.php
Executable 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
85
kirby/config/roots.php
Executable 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
200
kirby/config/routes.php
Executable 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
|
||||
];
|
||||
};
|
66
kirby/config/sections/fields.php
Executable file
66
kirby/config/sections/fields.php
Executable 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
229
kirby/config/sections/files.php
Executable 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
36
kirby/config/sections/info.php
Executable 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
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
28
kirby/config/sections/mixins/empty.php
Executable file
28
kirby/config/sections/mixins/empty.php
Executable 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;
|
||||
}
|
||||
]
|
||||
];
|
19
kirby/config/sections/mixins/headline.php
Executable file
19
kirby/config/sections/mixins/headline.php
Executable 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);
|
||||
}
|
||||
]
|
||||
];
|
12
kirby/config/sections/mixins/layout.php
Executable file
12
kirby/config/sections/mixins/layout.php
Executable 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';
|
||||
}
|
||||
]
|
||||
];
|
28
kirby/config/sections/mixins/max.php
Executable file
28
kirby/config/sections/mixins/max.php
Executable 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;
|
||||
}
|
||||
]
|
||||
];
|
21
kirby/config/sections/mixins/min.php
Executable file
21
kirby/config/sections/mixins/min.php
Executable 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;
|
||||
}
|
||||
]
|
||||
];
|
36
kirby/config/sections/mixins/pagination.php
Executable file
36
kirby/config/sections/mixins/pagination.php
Executable 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(),
|
||||
];
|
||||
},
|
||||
]
|
||||
];
|
29
kirby/config/sections/mixins/parent.php
Executable file
29
kirby/config/sections/mixins/parent.php
Executable 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
290
kirby/config/sections/pages.php
Executable 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
225
kirby/config/tags.php
Executable 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
15
kirby/config/tests.php
Executable 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
33
kirby/config/urls.php
Executable 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');
|
||||
}
|
||||
];
|
624
kirby/dependencies/parsedown-extra/ParsedownExtra.php
Executable file
624
kirby/dependencies/parsedown-extra/ParsedownExtra.php
Executable 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' => '↩',
|
||||
'allowRawHtmlInSafeMode' => true,
|
||||
'autobreak' => false,
|
||||
);
|
||||
}
|
||||
|
||||
unset($backLinkElements[0]);
|
||||
|
||||
$n = count($textElements) -1;
|
||||
|
||||
if ($textElements[$n]['name'] === 'p') {
|
||||
$backLinkElements = array_merge(
|
||||
array(
|
||||
array(
|
||||
'rawHtml' => ' ',
|
||||
'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]+[ ]*)';
|
||||
}
|
1808
kirby/dependencies/parsedown/Parsedown.php
Executable file
1808
kirby/dependencies/parsedown/Parsedown.php
Executable file
File diff suppressed because it is too large
Load Diff
9
kirby/kirby.pub
Executable file
9
kirby/kirby.pub
Executable 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
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
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
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
401
kirby/panel/dist/img/icons.svg
vendored
Executable 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
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
49
kirby/panel/dist/js/plugins.js
vendored
Executable 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
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
12
kirby/router.php
Executable 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
428
kirby/src/Api/Api.php
Executable 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
124
kirby/src/Api/Collection.php
Executable 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
188
kirby/src/Api/Model.php
Executable 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
77
kirby/src/Cache/ApcuCache.php
Executable 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
237
kirby/src/Cache/Cache.php
Executable 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
126
kirby/src/Cache/FileCache.php
Executable 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
Reference in New Issue
Block a user