initial commit

This commit is contained in:
isUnknown 2026-02-09 08:36:12 +01:00
commit 9439de0603
600 changed files with 124248 additions and 0 deletions

15
kirby/.editorconfig Normal file
View file

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

58
kirby/.php_cs Normal file
View file

@ -0,0 +1,58 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('dependencies')
->in(__DIR__);
return PhpCsFixer\Config::create()
->setRules([
'@PSR1' => true,
'@PSR2' => true,
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'cast_spaces' => ['space' => 'none'],
// 'class_keyword_remove' => true, // replaces static::class with 'static' (won't work)
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'combine_nested_dirname' => true,
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'single'],
'dir_constant' => true,
'function_typehint_space' => true,
'include' => true,
'logical_operators' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'method_chaining_indentation' => true,
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'native_function_casing' => true,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_unneeded_control_parentheses' => true,
'no_unused_imports' => true,
'no_useless_return' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
// 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], // adds params in the wrong order
'phpdoc_align' => ['align' => 'left'],
'phpdoc_indent' => true,
'phpdoc_scalar' => true,
'phpdoc_trim' => true,
'short_scalar_cast' => true,
'single_line_comment_style' => true,
'single_quote' => true,
'ternary_to_null_coalescing' => true,
'whitespace_after_comma_in_array' => true
])
->setRiskyAllowed(true)
->setFinder($finder);

3
kirby/SECURITY.md Normal file
View file

@ -0,0 +1,3 @@
# Security Policy
Please see the [Security Policy on the Kirby website](https://getkirby.com/security) for a list of the currently supported Kirby versions and of past security incidents as well as for information on how to report security vulnerabilities in the Kirby core or in the Panel.

83
kirby/assets/whoops.css Normal file
View file

@ -0,0 +1,83 @@
body {
background: #efefef;
font: normal normal 400 12px/1.5 -apple-system, BlinkMacSystemFont, Segoe UI,
Roboto, Helvetica, Arial, sans-serif;
}
.left-panel {
background: transparent;
}
header {
background-color: #313740;
}
.exc-title-primary {
color: hsl(0, 71%, 55%);
}
.frame.active {
color: hsl(0, 71%, 55%);
box-shadow: inset -5px 0 0 0 #d16464;
}
.frame:not(.active):hover {
background: rgba(203, 215, 229, 0.5);
}
.rightButton {
color: #999;
box-shadow: inset 0 0 0 1px #777;
border-radius: 0;
}
.rightButton:hover {
box-shadow: inset 0 0 0 1px #555;
color: #777;
}
.details-heading {
color: #7e9abf;
font-weight: 500;
}
.frame-code {
background: #000;
}
pre.code-block,
code.code-block,
.frame-args.code-block,
.frame-args.code-block samp {
background: #16171a;
}
.linenums li.current {
background: transparent;
}
.linenums li.current.active {
background: rgba(209, 100, 100, 0.3);
}
pre .atv,
code .atv,
pre .str,
code .str {
color: #a7bd68;
}
pre .tag,
code .tag {
color: #d16464;
}
pre .kwd,
code .kwd {
color: #8abeb7;
}
pre .atn,
code .atn {
color: #de935f;
}

35
kirby/bootstrap.php Normal file
View file

@ -0,0 +1,35 @@
<?php
/**
* Validate the PHP version to already
* stop at older or too recent versions
*/
if (
version_compare(PHP_VERSION, '7.2.0', '>=') === false ||
version_compare(PHP_VERSION, '7.5.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
*/
}

3466
kirby/cacert.pem Normal file

File diff suppressed because it is too large Load diff

53
kirby/composer.json Normal file
View file

@ -0,0 +1,53 @@
{
"name": "getkirby/cms",
"description": "The Kirby 3 core",
"version": "3.4.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.2.0 <7.5.0",
"ext-mbstring": "*",
"ext-ctype": "*",
"getkirby/composer-installer": "^1.0",
"mustangostang/spyc": "0.6.3",
"michelf/php-smartypants": "1.8.1",
"claviska/simpleimage": "3.3.4",
"phpmailer/phpmailer": "6.1.6",
"filp/whoops": "2.7.2",
"true/punycode": "2.1.1",
"laminas/laminas-escaper": "2.6.1"
},
"autoload": {
"files": ["config/setup.php"],
"classmap": ["dependencies/"],
"psr-4": {
"Kirby\\": "src/"
}
},
"scripts": {
"analyze": "phpstan analyse",
"build": "./scripts/build",
"fix": "php-cs-fixer fix --config .php_cs",
"post-update-cmd": "curl -o cacert.pem https://curl.haxx.se/ca/cacert.pem",
"test": "phpunit --stderr --coverage-html=tests/coverage",
"zip": "composer archive --format=zip --file=dist"
},
"config": {
"optimize-autoloader": true
}
}

633
kirby/composer.lock generated Normal file
View file

@ -0,0 +1,633 @@
{
"_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": "8ec61411e7f3b3a717572d0847654a96",
"packages": [
{
"name": "claviska/simpleimage",
"version": "3.3.4",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "3786d80af8e6d05e5e42f0350e5e5da5b92041a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/3786d80af8e6d05e5e42f0350e5e5da5b92041a0",
"reference": "3786d80af8e6d05e5e42f0350e5e5da5b92041a0",
"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": "2019-09-26T01:22:02+00:00"
},
{
"name": "filp/whoops",
"version": "2.7.2",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/17d0d3f266c8f925ebd035cd36f83cf802b47d4a",
"reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a",
"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 || ^6.0",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.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.6-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": "2020-05-05T12:28:07+00:00"
},
{
"name": "getkirby/composer-installer",
"version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/getkirby/composer-installer.git",
"reference": "2d6b8f5601a31caeeea45623e1643fbb437eb94e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getkirby/composer-installer/zipball/2d6b8f5601a31caeeea45623e1643fbb437eb94e",
"reference": "2d6b8f5601a31caeeea45623e1643fbb437eb94e",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0"
},
"require-dev": {
"composer/composer": "^1.8",
"phpunit/phpunit": "^7.0"
},
"type": "composer-plugin",
"extra": {
"class": "Kirby\\ComposerInstaller\\Plugin"
},
"autoload": {
"psr-4": {
"Kirby\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins",
"homepage": "https://getkirby.com",
"time": "2019-02-11T20:27:36+00:00"
},
{
"name": "laminas/laminas-escaper",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "25f2a053eadfa92ddacb609dcbbc39362610da70"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/25f2a053eadfa92ddacb609dcbbc39362610da70",
"reference": "25f2a053eadfa92ddacb609dcbbc39362610da70",
"shasum": ""
},
"require": {
"laminas/laminas-zendframework-bridge": "^1.0",
"php": "^5.6 || ^7.0"
},
"replace": {
"zendframework/zend-escaper": "self.version"
},
"require-dev": {
"laminas/laminas-coding-standard": "~1.0.0",
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6.x-dev",
"dev-develop": "2.7.x-dev"
}
},
"autoload": {
"psr-4": {
"Laminas\\Escaper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs",
"homepage": "https://laminas.dev",
"keywords": [
"escaper",
"laminas"
],
"time": "2019-12-31T16:43:30+00:00"
},
{
"name": "laminas/laminas-zendframework-bridge",
"version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-zendframework-bridge.git",
"reference": "fcd87520e4943d968557803919523772475e8ea3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/fcd87520e4943d968557803919523772475e8ea3",
"reference": "fcd87520e4943d968557803919523772475e8ea3",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev",
"dev-develop": "1.1.x-dev"
},
"laminas": {
"module": "Laminas\\ZendFrameworkBridge"
}
},
"autoload": {
"files": [
"src/autoload.php"
],
"psr-4": {
"Laminas\\ZendFrameworkBridge\\": "src//"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Alias legacy ZF class names to Laminas Project equivalents.",
"keywords": [
"ZendFramework",
"autoloading",
"laminas",
"zf"
],
"time": "2020-05-20T16:45:56+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.3",
"source": {
"type": "git",
"url": "git@github.com:mustangostang/spyc.git",
"reference": "4627c838b16550b666d15aeae1e5289dd5b77da0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0",
"reference": "4627c838b16550b666d15aeae1e5289dd5b77da0",
"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": "2019-09-10T13:16:29+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.6",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"php": ">=5.5.0"
},
"require-dev": {
"doctrine/annotations": "^1.2",
"friendsofphp/php-cs-fixer": "^2.2",
"phpunit/phpunit": "^4.8 || ^5.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-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"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": "2020-05-27T12:24:03+00:00"
},
{
"name": "psr/log",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.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": "2020-03-23T09:12:05+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "7110338d81ce1cbc3e273136e4574663627037a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7",
"reference": "7110338d81ce1cbc3e273136e4574663627037a7",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"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": "2020-06-06T08:46:27+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"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.2.0 <7.5.0",
"ext-mbstring": "*",
"ext-ctype": "*"
},
"platform-dev": []
}

60
kirby/config/aliases.php Normal file
View file

@ -0,0 +1,60 @@
<?php
return [
// cms classes
'asset' => 'Kirby\Cms\Asset',
'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',
// exceptions
'errorpageexception' => 'Kirby\Exception\ErrorPageException',
// 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'
];

View file

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

View file

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

View file

@ -0,0 +1,20 @@
<?php
/**
* 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',
];

View file

@ -0,0 +1,166 @@
<?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();
},
'dragText' => function (File $file) {
return $file->dragText();
},
'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', 'filename', 'asc');
$index = $files->indexOf($file);
return $files->nth($index + 1);
},
'niceSize' => function (File $file) {
return $file->niceSize();
},
'options' => function (File $file) {
return $file->panelOptions();
},
'panelIcon' => function (File $file) {
return $file->panelIcon();
},
'panelImage' => function (File $file) {
return $file->panelImage();
},
'panelUrl' => function (File $file) {
return $file->panelUrl(true);
},
'prev' => function (File $file) {
return $file->prev();
},
'prevWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sortBy('sort', 'asc', 'filename', 'asc');
$index = $files->indexOf($file);
return $files->nth($index - 1);
},
'parent' => function (File $file) {
return $file->parent();
},
'parents' => function (File $file) {
return $file->parents()->flip();
},
'size' => function (File $file) {
return $file->size();
},
'template' => function (File $file) {
return $file->template();
},
'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' => 'Kirby\Cms\File',
'views' => [
'default' => [
'content',
'dimensions',
'exists',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'next' => 'compact',
'niceSize',
'parent' => 'compact',
'options',
'prev' => 'compact',
'size',
'template',
'type',
'url'
],
'compact' => [
'filename',
'id',
'link',
'type',
'url',
],
'panel' => [
'blueprint',
'content',
'dimensions',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'nextWithTemplate' => 'compact',
'niceSize',
'options',
'panelIcon',
'panelImage',
'parent' => 'compact',
'parents' => ['id', 'slug', 'title'],
'prevWithTemplate' => 'compact',
'template',
'type',
'url'
]
],
];

View file

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

View file

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

View file

@ -0,0 +1,44 @@
<?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();
},
'rules' => function (Language $language) {
return $language->rules();
},
'url' => function (Language $language) {
return $language->url();
},
],
'type' => 'Kirby\Cms\Language',
'views' => [
'default' => [
'code',
'default',
'direction',
'locale',
'name',
'rules',
'url'
]
]
];

View file

@ -0,0 +1,157 @@
<?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()->sortBy('sort', 'asc', 'filename', 'asc');
},
'hasChildren' => function (Page $page) {
return $page->hasChildren();
},
'hasDrafts' => function (Page $page) {
return $page->hasDrafts();
},
'hasFiles' => function (Page $page) {
return $page->hasFiles();
},
'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->panelOptions(['preview']);
},
'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' => 'Kirby\Cms\Page',
'views' => [
'compact' => [
'id',
'title',
'url',
'num'
],
'default' => [
'content',
'id',
'status',
'num',
'options',
'parent' => 'compact',
'slug',
'template',
'title',
'url'
],
'panel' => [
'id',
'blueprint',
'content',
'status',
'options',
'next' => ['id', 'slug', 'title'],
'parents' => ['id', 'slug', 'title'],
'prev' => ['id', 'slug', 'title'],
'previewUrl',
'slug',
'title',
'url'
],
'selector' => [
'id',
'title',
'parent' => [
'id',
'title'
],
'children' => [
'hasChildren',
'id',
'panelIcon',
'panelImage',
'title',
],
]
],
];

View file

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

View file

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

View file

@ -0,0 +1,72 @@
<?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();
},
'drafts' => function (Site $site) {
return $site->drafts();
},
'files' => function (Site $site) {
return $site->files()->sortBy('sort', 'asc', 'filename', 'asc');
},
'options' => function (Site $site) {
return $site->permissions()->toArray();
},
'previewUrl' => function (Site $site) {
return $site->previewUrl();
},
'title' => function (Site $site) {
return $site->title()->value();
},
'url' => function (Site $site) {
return $site->url();
},
],
'type' => 'Kirby\Cms\Site',
'views' => [
'compact' => [
'title',
'url'
],
'default' => [
'content',
'options',
'title',
'url'
],
'panel' => [
'title',
'blueprint',
'content',
'options',
'previewUrl',
'url'
],
'selector' => [
'title',
'children' => [
'id',
'title',
'panelIcon',
'hasChildren'
],
]
]
];

View file

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

View file

@ -0,0 +1,119 @@
<?php
use Kirby\Cms\System;
use Kirby\Toolkit\Str;
/**
* System
*/
return [
'fields' => [
'ascii' => function () {
return Str::$ascii;
},
'defaultLanguage' => function () {
return $this->kirby()->option('panel.language', 'en');
},
'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();
},
'site' => function () {
try {
return $this->site()->blueprint()->title();
} catch (Throwable $e) {
return $this->site()->title()->value();
}
},
'slugs' => function () {
return Str::$language;
},
'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 () {
$user = $this->user();
if ($user && $user->role()->permissions()->for('access', 'settings') === true) {
return $this->kirby()->version();
} else {
return null;
}
}
],
'type' => 'Kirby\Cms\System',
'views' => [
'login' => [
'isOk',
'isInstallable',
'isInstalled',
'title',
'translation'
],
'troubleshooting' => [
'isOk',
'isInstallable',
'isInstalled',
'title',
'translation',
'requirements'
],
'panel' => [
'ascii',
'defaultLanguage',
'isOk',
'isInstalled',
'isLocal',
'kirbytext',
'languages',
'license',
'multilang',
'requirements',
'site',
'slugs',
'title',
'translation',
'user' => 'auth',
'version'
]
],
];

View file

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

View file

@ -0,0 +1,108 @@
<?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();
},
'files' => function (User $user) {
return $user->files()->sortBy('sort', 'asc', 'filename', 'asc');
},
'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->panelOptions();
},
'permissions' => function (User $user) {
return $user->role()->permissions()->toArray();
},
'prev' => function (User $user) {
return $user->prev();
},
'role' => function (User $user) {
return $user->role();
},
'roles' => function (User $user) {
return $user->roles();
},
'username' => function (User $user) {
return $user->username();
}
],
'type' => 'Kirby\Cms\User',
'views' => [
'default' => [
'avatar',
'content',
'email',
'id',
'language',
'name',
'next' => 'compact',
'options',
'prev' => 'compact',
'role',
'username'
],
'compact' => [
'avatar' => 'compact',
'id',
'email',
'language',
'name',
'role' => 'compact',
'username'
],
'auth' => [
'avatar' => 'compact',
'permissions',
'email',
'id',
'name',
'role',
'language'
],
'panel' => [
'avatar' => 'compact',
'blueprint',
'content',
'email',
'id',
'language',
'name',
'next' => ['id', 'name'],
'options',
'prev' => ['id', 'name'],
'role',
'username',
],
]
];

View file

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

View file

@ -0,0 +1,26 @@
<?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/lock.php',
include __DIR__ . '/routes/system.php',
include __DIR__ . '/routes/translations.php'
);
// only add the language routes if the
// multi language setup is activated
if ($kirby->option('languages', false) !== false) {
$routes = array_merge($routes, include __DIR__ . '/routes/languages.php');
}
return $routes;
};

View file

@ -0,0 +1,55 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
/**
* 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');
$user = $this->kirby()->auth()->login($email, $password, $long);
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
],
[
'pattern' => 'auth/logout',
'method' => 'POST',
'auth' => false,
'action' => function () {
$this->kirby()->auth()->logout();
return true;
}
],
];

View file

@ -0,0 +1,123 @@
<?php
/**
* Files Routes
*/
return [
[
'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);
}
}
],
[
'pattern' => '(:all)/files',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->files()->sortBy('sort', 'asc', 'filename', 'asc');
}
],
[
'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' => 'GET|POST',
'action' => function (string $path) {
$files = $this->parent($path)->files();
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
} else {
return $files->query($this->requestBody());
}
}
],
[
'pattern' => '(:all)/files/sort',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->files()->changeSort(
$this->requestBody('files'),
$this->requestBody('index')
);
}
],
[
'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' => 'files/search',
'method' => 'GET|POST',
'action' => function () {
$files = $this
->site()
->index(true)
->filterBy('isReadable', true)
->files();
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
} else {
return $files->query($this->requestBody());
}
}
],
];

View file

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

View file

@ -0,0 +1,99 @@
<?php
use Kirby\Exception\Exception;
/**
* Content Lock Routes
*/
return [
[
'pattern' => '(:all)/lock',
'method' => 'GET',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return [
'supported' => true,
'locked' => $lock->get()
];
}
return [
'supported' => false,
'locked' => null
];
}
],
[
'pattern' => '(:all)/lock',
'method' => 'PATCH',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->create();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/lock',
'method' => 'DELETE',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->remove();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'GET',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return [
'supported' => true,
'unlocked' => $lock->isUnlocked()
];
}
return [
'supported' => false,
'unlocked' => null
];
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'PATCH',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->unlock();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'DELETE',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->resolve();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
];

View file

@ -0,0 +1,119 @@
<?php
/**
* 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' => 'GET|POST',
'action' => function (string $id) {
$pages = $this->page($id)->children();
if ($this->requestMethod() === 'GET') {
return $pages->search($this->requestQuery('q'));
} else {
return $pages->query($this->requestBody());
}
}
],
[
'pattern' => 'pages/(:any)/duplicate',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->duplicate($this->requestBody('slug'), [
'children' => $this->requestBody('children'),
'files' => $this->requestBody('files'),
]);
}
],
[
'pattern' => 'pages/(:any)/slug',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeSlug($this->requestBody('slug'));
}
],
[
'pattern' => 'pages/(:any)/status',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeStatus($this->requestBody('status'), $this->requestBody('position'));
}
],
[
'pattern' => 'pages/(:any)/template',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeTemplate($this->requestBody('template'));
}
],
[
'pattern' => 'pages/(:any)/title',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeTitle($this->requestBody('title'));
}
],
[
'pattern' => 'pages/(:any)/sections/(:any)',
'method' => 'GET',
'action' => function (string $id, string $sectionName) {
if ($section = $this->page($id)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
if ($page = $this->page($id)) {
return $this->fieldApi($page, $fieldName, $path);
}
}
],
];

View file

@ -0,0 +1,28 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'roles',
'method' => 'GET',
'action' => function () {
switch (get('canBe')) {
case 'changed':
return $this->kirby()->roles()->canBeChanged();
case 'created':
return $this->kirby()->roles()->canBeCreated();
default:
return $this->kirby()->roles();
}
}
],
[
'pattern' => 'roles/(:any)',
'method' => 'GET',
'action' => function (string $name) {
return $this->kirby()->roles()->find($name);
}
]
];

View file

@ -0,0 +1,96 @@
<?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|POST',
'action' => function () {
$pages = $this
->site()
->index(true)
->filterBy('isReadable', true);
if ($this->requestMethod() === 'GET') {
return $pages->search($this->requestQuery('q'));
} else {
return $pages->query($this->requestBody());
}
}
],
[
'pattern' => 'site/sections/(:any)',
'method' => 'GET',
'action' => function (string $sectionName) {
if ($section = $this->site()->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => 'site/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $fieldName, string $path = null) {
return $this->fieldApi($this->site(), $fieldName, $path);
}
]
];

View file

@ -0,0 +1,79 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
/**
* 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->isInstallable() === false) {
throw new Exception('The Panel cannot be installed');
}
if ($system->isInstalled() === true) {
throw new Exception('The Panel is already installed');
}
// create the first user
$user = $this->users()->create($this->requestBody());
$token = $user->login($this->requestBody('password'));
return [
'status' => 'ok',
'token' => $token,
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
]
];

View file

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

View file

@ -0,0 +1,147 @@
<?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' => 'GET|POST',
'action' => function () {
if ($this->requestMethod() === 'GET') {
return $this->users()->search($this->requestQuery('q'));
} else {
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)/roles',
'action' => function (string $id) {
return $this->user($id)->roles();
}
],
[
'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);
}
}
]
];

View file

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

View file

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

View file

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

View file

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

375
kirby/config/components.php Normal file
View file

@ -0,0 +1,375 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
use Kirby\Cms\Filename;
use Kirby\Cms\FileVersion;
use Kirby\Cms\Template;
use Kirby\Data\Data;
use Kirby\Http\Server;
use Kirby\Http\Uri;
use Kirby\Http\Url;
use Kirby\Image\Darkroom;
use Kirby\Text\Markdown;
use Kirby\Text\SmartyPants;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\Tpl as Snippet;
return [
/**
* Used by the `css()` helper
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string
*/
'css' => function (App $kirby, string $url, $options = null): string {
return $url;
},
/**
* Object and variable dumper
* to help with debugging.
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param mixed $variable
* @param bool $echo
* @return string
*/
'dump' => function (App $kirby, $variable, bool $echo = true) {
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;
},
/**
* Modify URLs for file objects
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\File $file The original file object
* @return string
*/
'file::url' => function (App $kirby, File $file): string {
return $file->mediaUrl();
},
/**
* Adapt file characteristics
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\File|\Kirby\Cms\FileModifications $file The file object
* @param array $options All thumb options (width, height, crop, blur, grayscale)
* @return \Kirby\Cms\File|\Kirby\Cms\FileVersion
*/
'file::version' => function (App $kirby, $file, array $options = []) {
if ($file->isResizable() === false) {
return $file;
}
// create url and root
$mediaRoot = dirname($file->mediaRoot());
$dst = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}';
$thumbRoot = (new Filename($file->root(), $dst, $options))->toString();
$thumbName = basename($thumbRoot);
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
if (file_exists($thumbRoot) === false) {
try {
Data::write($job, array_merge($options, [
'filename' => $file->filename()
]));
} catch (Throwable $e) {
return $file;
}
}
return new FileVersion([
'modifications' => $options,
'original' => $file,
'root' => $thumbRoot,
'url' => dirname($file->mediaUrl()) . '/' . $thumbName,
]);
},
/**
* Used by the `js()` helper
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string
*/
'js' => function (App $kirby, string $url, $options = null): string {
return $url;
},
/**
* Add your own Markdown parser
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $text Text to parse
* @param array $options Markdown options
* @param bool $inline Whether to wrap the text in `<p>` tags
* @return string
*/
'markdown' => function (App $kirby, string $text = null, array $options = [], bool $inline = false): string {
static $markdown;
static $config;
// if the config options have changed or the component is called for the first time,
// (re-)initialize the parser object
if ($config !== $options) {
$markdown = new Markdown($options);
$config = $options;
}
return $markdown->parse($text, $inline);
},
/**
* Add your own search engine
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\Collection $collection Collection of searchable models
* @param string $query
* @param mixed $params
* @return \Kirby\Cms\Collection|bool
*/
'search' => function (App $kirby, Collection $collection, string $query = null, $params = []) {
if (empty(trim($query)) === true) {
return $collection->limit(0);
}
if (is_string($params) === true) {
$params = ['fields' => Str::split($params, '|')];
}
$defaults = [
'fields' => [],
'minlength' => 2,
'score' => [],
'words' => false,
];
$options = array_merge($defaults, $params);
$collection = clone $collection;
$searchwords = preg_replace('/(\s)/u', ',', $query);
$searchwords = Str::split($searchwords, ',', $options['minlength']);
$lowerQuery = mb_strtolower($query);
if (empty($options['stopwords']) === false) {
$searchwords = array_diff($searchwords, $options['stopwords']);
}
$searchwords = array_map(function ($value) use ($options) {
return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
}, $searchwords);
$preg = '!(' . implode('|', $searchwords) . ')!i';
$results = $collection->filter(function ($item) use ($query, $preg, $options, $lowerQuery) {
$data = $item->content()->toArray();
$keys = array_keys($data);
$keys[] = 'id';
if (is_a($item, 'Kirby\Cms\User') === true) {
$keys[] = 'name';
$keys[] = 'email';
$keys[] = 'role';
} elseif (is_a($item, 'Kirby\Cms\Page') === true) {
// apply the default score for pages
$options['score'] = array_merge([
'id' => 64,
'title' => 64,
], $options['score']);
}
if (empty($options['fields']) === false) {
$fields = array_map('strtolower', $options['fields']);
$keys = array_intersect($keys, $fields);
}
$item->searchHits = 0;
$item->searchScore = 0;
foreach ($keys as $key) {
$score = $options['score'][$key] ?? 1;
$value = $data[$key] ?? (string)$item->$key();
$lowerValue = mb_strtolower($value);
// check for exact matches
if ($lowerQuery == $lowerValue) {
$item->searchScore += 16 * $score;
$item->searchHits += 1;
// check for exact beginning matches
} elseif (Str::startsWith($lowerValue, $lowerQuery) === true) {
$item->searchScore += 8 * $score;
$item->searchHits += 1;
// check for exact query matches
} elseif ($matches = preg_match_all('!' . preg_quote($query) . '!i', $value, $r)) {
$item->searchScore += 2 * $score;
$item->searchHits += $matches;
}
// check for any match
if ($matches = preg_match_all($preg, $value, $r)) {
$item->searchHits += $matches;
$item->searchScore += $matches * $score;
}
}
return $item->searchHits > 0 ? true : false;
});
return $results->sortBy('searchScore', 'desc');
},
/**
* Add your own SmartyPants parser
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $text Text to parse
* @param array $options SmartyPants options
* @return string
*/
'smartypants' => function (App $kirby, string $text = null, array $options = []): string {
static $smartypants;
static $config;
// if the config options have changed or the component is called for the first time,
// (re-)initialize the parser object
if ($config !== $options) {
$smartypants = new Smartypants($options);
$config = $options;
}
return $smartypants->parse($text);
},
/**
* Add your own snippet loader
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string|array $name Snippet name
* @param array $data Data array for the snippet
* @return string|null
*/
'snippet' => function (App $kirby, $name, array $data = []): ?string {
$snippets = A::wrap($name);
foreach ($snippets as $name) {
$name = (string)$name;
$file = $kirby->root('snippets') . '/' . $name . '.php';
if (file_exists($file) === false) {
$file = $kirby->extensions('snippets')[$name] ?? null;
}
if ($file) {
break;
}
}
return Snippet::load($file, $data);
},
/**
* Add your own template engine
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $name Template name
* @param string $type Extension type
* @param string $defaultType Default extension type
* @return \Kirby\Cms\Template
*/
'template' => function (App $kirby, string $name, string $type = 'html', string $defaultType = 'html') {
return new Template($name, $type, $defaultType);
},
/**
* Add your own thumb generator
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $src The root of the original file
* @param string $dst The root to the desired destination
* @param array $options All thumb options that should be applied: `width`, `height`, `crop`, `blur`, `grayscale`
* @return string
*/
'thumb' => function (App $kirby, string $src, string $dst, array $options): string {
$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, true);
$darkroom->process($root, $options);
return $root;
},
/**
* Modify all URLs
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $path URL path
* @param array|string|null $options Array of options for the Uri class
* @param Closure $originalHandler Deprecated: Callback function to the original URL handler with `$path` and `$options` as parameters
* Use `$kirby->nativeComponent('url')` inside your URL component instead.
* @return string
*/
'url' => function (App $kirby, string $path = null, $options = null, Closure $originalHandler = null): string {
$language = null;
// get language from simple string option
if (is_string($options) === true) {
$language = $options;
$options = null;
}
// get language from array
if (is_array($options) === true && isset($options['language']) === true) {
$language = $options['language'];
unset($options['language']);
}
// get a language url for the linked page, if the page can be found
if ($kirby->multilang() === true) {
$parts = Str::split($path, '#');
if ($page = page($parts[0] ?? null)) {
$path = $page->url($language);
if (isset($parts[1]) === true) {
$path .= '#' . $parts[1];
}
}
}
// keep relative urls
if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') {
return $path;
}
$url = Url::makeAbsolute($path, $kirby->url());
if ($options === null) {
return $url;
}
return (new Uri($url, $options))->toString();
},
];

27
kirby/config/fields.php Normal file
View file

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

View file

@ -0,0 +1,61 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
return [
'mixins' => ['min', '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' => [
'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'
]
];

View file

@ -0,0 +1,129 @@
<?php
use Kirby\Exception\Exception;
return [
'props' => [
/**
* Default date when a new page/file/user gets created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Defines a custom format that is used when the field is saved
*/
'format' => function (string $format = null) {
return $format;
},
/**
* 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('Y-m-d H:i:s', $timestamp);
}
return null;
}
],
'save' => function ($value) {
if ($value !== null && $date = strtotime($value)) {
return date($this->format(), $date);
}
return '';
},
'validations' => [
'date',
'minMax' => function ($value) {
$min = $this->min ? strtotime($this->min) : null;
$max = $this->max ? strtotime($this->max) : null;
$value = strtotime($this->value());
$format = 'd.m.Y';
$errors = [];
if ($value && $min && $value < $min) {
$errors['min'] = $min;
}
if ($value && $max && $value > $max) {
$errors['max'] = $max;
}
if (empty($errors) === false) {
if ($min && $max) {
throw new Exception([
'key' => 'validation.date.between',
'data' => [
'min' => date($format, $min),
'max' => date($format, $max)
]
]);
} elseif ($min) {
throw new Exception([
'key' => 'validation.date.after',
'data' => [
'date' => date($format, $min),
]
]);
} else {
throw new Exception([
'key' => 'validation.date.before',
'data' => [
'date' => date($format, $max),
]
]);
}
}
return true;
},
]
];

View file

@ -0,0 +1,40 @@
<?php
use Kirby\Toolkit\I18n;
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'
]
];

View file

@ -0,0 +1,138 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => [
'picker',
'filepicker',
'min',
'upload'
],
'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;
},
/**
* Changes the layout of the selected files. Available layouts: `list`, `cards`
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
*/
'size' => function (string $size = 'auto') {
return $size;
},
'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);
},
'query' => function () {
return $this->query ?? $this->parentModel::CLASS_ALIAS . '.files';
},
'default' => function () {
return $this->toFiles($this->default);
},
'value' => function () {
return $this->toFiles($this->value);
},
],
'methods' => [
'fileResponse' => function ($file) {
return $file->panelPickerData([
'image' => $this->image,
'info' => $this->info ?? false,
'model' => $this->model(),
'text' => $this->text,
]);
},
'toFiles' => function ($value = null) {
$files = [];
foreach (Data::decode($value, 'yaml') 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();
return $field->filepicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'text' => $field->text()
]);
}
],
[
'pattern' => 'upload',
'method' => 'POST',
'action' => function () {
$field = $this->field();
$uploads = $field->uploads();
return $field->upload($this, $uploads, function ($file, $parent) use ($field) {
return $file->panelPickerData([
'image' => $field->image(),
'info' => $field->info(),
'model' => $field->model(),
'text' => $field->text(),
]);
});
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'uuid');
},
'validations' => [
'max',
'min'
]
];

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,14 @@
<?php
use Kirby\Cms\FilePicker;
return [
'methods' => [
'filepicker' => function (array $params = []) {
// fetch the parent model
$params['model'] = $this->model();
return (new FilePicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,22 @@
<?php
return [
'computed' => [
'min' => function () {
// set min to at least 1, if required
if ($this->required === true) {
return $this->min ?? 1;
}
return $this->min;
},
'required' => function () {
// set required to true if min is set
if ($this->min) {
return true;
}
return $this->required;
}
]
];

View file

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

View file

@ -0,0 +1,14 @@
<?php
use Kirby\Cms\PagePicker;
return [
'methods' => [
'pagepicker' => function (array $params = []) {
// inject the current model
$params['model'] = $this->model();
return (new PagePicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,78 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* The placeholder text if none have been selected yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Image settings for each item
*/
'image' => function ($image = null) {
return $image;
},
/**
* Info text for each item
*/
'info' => function (string $info = null) {
return $info;
},
/**
* Whether each item should be clickable
*/
'link' => function (bool $link = true) {
return $link;
},
/**
* The minimum number of required selected
*/
'min' => function (int $min = null) {
return $min;
},
/**
* The maximum number of allowed selected
*/
'max' => function (int $max = null) {
return $max;
},
/**
* If `false`, only a single one can be selected
*/
'multiple' => function (bool $multiple = true) {
return $multiple;
},
/**
* Query for the items to be included in the picker
*/
'query' => function (string $query = null) {
return $query;
},
/**
* Enable/disable the search field in the picker
*/
'search' => function (bool $search = true) {
return $search;
},
/**
* Main text for each item
*/
'text' => function (string $text = null) {
return $text;
},
],
];

View file

@ -0,0 +1,72 @@
<?php
use Kirby\Cms\Api;
use Kirby\Cms\File;
use Kirby\Exception\Exception;
return [
'props' => [
/**
* Sets the upload options for linked files (since 3.2.0)
*/
'uploads' => function ($uploads = []) {
if ($uploads === false) {
return false;
}
if (is_string($uploads) === true) {
$uploads = ['template' => $uploads];
}
if (is_array($uploads) === false) {
$uploads = [];
}
$template = $uploads['template'] ?? null;
if ($template) {
$file = new File([
'filename' => 'tmp',
'template' => $template
]);
$uploads['accept'] = $file->blueprint()->accept()['mime'] ?? '*';
} else {
$uploads['accept'] = '*';
}
return $uploads;
},
],
'methods' => [
'upload' => function (Api $api, $params, Closure $map) {
if ($params === false) {
throw new Exception('Uploads are disabled for this field');
}
if ($parentQuery = ($params['parent'] ?? null)) {
$parent = $this->model()->query($parentQuery);
} else {
$parent = $this->model();
}
if (is_a($parent, 'Kirby\Cms\File') === true) {
$parent = $parent->parent();
}
return $api->upload(function ($source, $filename) use ($parent, $params, $map) {
$file = $parent->createFile([
'source' => $source,
'template' => $params['template'] ?? null,
'filename' => $filename,
]);
if (is_a($file, 'Kirby\Cms\File') === false) {
throw new Exception('The file could not be uploaded');
}
return $map($file, $parent);
});
}
]
];

View file

@ -0,0 +1,13 @@
<?php
use Kirby\Cms\UserPicker;
return [
'methods' => [
'userpicker' => function (array $params = []) {
$params['model'] = $this->model();
return (new UserPicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,32 @@
<?php
return [
'extends' => 'tags',
'props' => [
/**
* Unset inherited props
*/
'accept' => null,
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
return $icon;
},
/**
* Enable/disable the search in the dropdown
* Also limit displayed items (display: 20)
* and set minimum number of characters to search (min: 3)
*/
'search' => function ($search = true) {
return $search;
},
/**
* If `true`, selected entries will be sorted
* according to their position in the dropdown
*/
'sort' => function (bool $sort = false) {
return $sort;
},
]
];

View file

@ -0,0 +1,48 @@
<?php
use Kirby\Toolkit\Str;
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 = null) {
return $this->toNumber($step);
},
'value' => function ($value = null) {
return $this->toNumber($value);
}
],
'methods' => [
'toNumber' => function ($value) {
if ($this->isEmpty($value) === true) {
return null;
}
return is_float($value) === true ? $value : (float)Str::float($value);
}
],
'validations' => [
'min',
'max'
]
];

View file

@ -0,0 +1,117 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => ['min', 'pagepicker', 'picker'],
'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);
},
/**
* Changes the layout of the selected files. Available layouts: `list`, `cards`
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* Optional query to select a specific set of pages
*/
'query' => function (string $query = null) {
return $query;
},
/**
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
*/
'size' => function (string $size = 'auto') {
return $size;
},
/**
* Optionally include subpages of pages
*/
'subpages' => function (bool $subpages = true) {
return $subpages;
},
'value' => function ($value = null) {
return $this->toPages($value);
},
],
'computed' => [
/**
* Unset inherited computed
*/
'default' => null
],
'methods' => [
'pageResponse' => function ($page) {
return $page->panelPickerData([
'image' => $this->image,
'info' => $this->info,
'text' => $this->text,
]);
},
'toPages' => function ($value = null) {
$pages = [];
$kirby = kirby();
foreach (Data::decode($value, 'yaml') 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();
return $field->pagepicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'parent' => $this->requestQuery('parent'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'subpages' => $field->subpages(),
'text' => $field->text()
]);
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'id');
},
'validations' => [
'max',
'min'
]
];

View file

@ -0,0 +1,29 @@
<?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' => [
'default' => function () {
return $this->sanitizeOption($this->default);
},
'value' => function () {
return $this->sanitizeOption($this->value) ?? '';
}
]
];

View file

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

View file

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

View file

@ -0,0 +1,193 @@
<?php
use Kirby\Cms\Form;
use Kirby\Data\Data;
use Kirby\Toolkit\I18n;
return [
'mixins' => ['min'],
'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);
},
/**
* Toggles duplicating rows for the structure
*/
'duplicate' => function (bool $duplicate = true) {
return $duplicate;
},
/**
* The placeholder text if no items have been added yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Set the default rows for the structure
*/
'default' => function (array $default = null) {
return $default;
},
/**
* 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 adding to the top or bottom of the list
*/
'prepend' => function (bool $prepend = null) {
return $prepend;
},
/**
* 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 () {
if (empty($this->fields) === true) {
throw new Exception('Please provide some fields for the structure');
}
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 = Data::decode($value, 'yaml');
$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 ($value) {
$data = [];
foreach ($value as $row) {
$data[] = $this->form($row)->data();
}
return $data;
},
'validations' => [
'min',
'max'
]
];

View file

@ -0,0 +1,96 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
return [
'mixins' => ['min', 'options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'placeholder' => null,
/**
* If set to `all`, any type of input is accepted. If set to `options` 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' => [
'default' => function (): array {
return $this->toTags($this->default);
},
'value' => function (): array {
return $this->toTags($this->value);
}
],
'methods' => [
'toTags' => function ($value) {
if (is_null($value) === true) {
return [];
}
$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, $this->separator()));
}
],
'save' => function (array $value = null): string {
return A::join(
A::pluck($value, 'value'),
$this->separator() . ' '
);
},
'validations' => [
'min',
'max'
]
];

View file

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

View file

@ -0,0 +1,103 @@
<?php
use Kirby\Exception\InvalidArgumentException;
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',
'pattern'
]
];

View file

@ -0,0 +1,122 @@
<?php
return [
'mixins' => ['filepicker', 'upload'],
'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`, `file`, `code`, `ul`, `ol` (as well as `|` for a divider)
*/
'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);
},
/**
* Sets the options for the files picker
*/
'files' => function ($files = []) {
if (is_string($files) === true) {
return ['query' => $files];
}
if (is_array($files) === false) {
$files = [];
}
return $files;
},
/**
* Sets the font family (sans or monospace)
*/
'font' => function (string $font = null) {
return $font === 'monospace' ? 'monospace' : 'sans-serif';
},
/**
* 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;
},
/**
* If `false`, spellcheck will be switched off
*/
'spellcheck' => function (bool $spellcheck = true) {
return $spellcheck;
},
'value' => function (string $value = null) {
return trim($value);
}
],
'api' => function () {
return [
[
'pattern' => 'files',
'action' => function () {
$params = array_merge($this->field()->files(), [
'page' => $this->requestQuery('page'),
'search' => $this->requestQuery('search')
]);
return $this->field()->filepicker($params);
}
],
[
'pattern' => 'upload',
'action' => function () {
$field = $this->field();
$uploads = $field->uploads();
return $this->field()->upload($this, $uploads, function ($file, $parent) use ($field) {
$absolute = $field->model()->is($parent) === false;
return [
'filename' => $file->filename(),
'dragText' => $file->dragText('auto', $absolute),
];
});
}
]
];
},
'validations' => [
'minlength',
'maxlength'
]
];

View file

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

View file

@ -0,0 +1,68 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Default value which will be saved when a new page/user/file is created
*/
'default' => function ($default = null) {
return $this->default = $default;
},
/**
* 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' => [
'default' => function () {
return $this->toBool($this->default);
},
'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'
]);
}
},
]
];

View file

@ -0,0 +1,41 @@
<?php
use Kirby\Toolkit\I18n;
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'
],
];

View file

@ -0,0 +1,97 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => ['min', 'picker', 'userpicker'],
'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);
},
'value' => function ($value = null) {
return $this->toUsers($value);
},
],
'computed' => [
/**
* Unset inherited computed
*/
'default' => null
],
'methods' => [
'userResponse' => function ($user) {
return $user->panelPickerData([
'info' => $this->info,
'image' => $this->image,
'text' => $this->text,
]);
},
'toUsers' => function ($value = null) {
$users = [];
$kirby = kirby();
foreach (Data::decode($value, 'yaml') as $email) {
if (is_array($email) === true) {
$email = $email['email'] ?? null;
}
if ($email !== null && ($user = $kirby->user($email))) {
$users[] = $this->userResponse($user);
}
}
return $users;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
return $field->userpicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'text' => $field->text()
]);
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'id');
},
'validations' => [
'max',
'min'
]
];

880
kirby/config/helpers.php Normal file
View file

@ -0,0 +1,880 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Asset;
use Kirby\Cms\Html;
use Kirby\Cms\Response;
use Kirby\Cms\Url;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\F;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
/**
* Helper to create an asset object
*
* @param string $path
* @return \Kirby\Cms\Asset
*/
function asset(string $path)
{
return new Asset($path);
}
/**
* 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 \Kirby\Cms\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|bool 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): ?string
{
if (is_array($url) === true) {
$links = array_map(function ($url) use ($options) {
return css($url, $options);
}, $url);
return implode(PHP_EOL, $links);
}
if (is_string($options) === true) {
$options = ['media' => $options];
}
$kirby = App::instance();
if ($url === '@auto') {
if (!$url = Url::toTemplateAsset('css/templates', 'css')) {
return null;
}
}
$url = $kirby->component('css')($kirby, $url, $options);
$url = Url::to($url);
$attr = array_merge((array)$options, [
'href' => $url,
'rel' => 'stylesheet'
]);
return '<link ' . attr($attr) . '>';
}
/**
* Triggers a deprecation warning if debug mode is active
* @since 3.3.0
*
* @param string $message
* @return bool Whether the warning was triggered
*/
function deprecated(string $message): bool
{
if (App::instance()->option('debug') === true) {
return trigger_error($message, E_USER_DEPRECATED) === true;
}
return false;
}
/**
* Simple object and variable dumper
* to help with debugging.
*
* @param mixed $variable
* @param bool $echo
* @return string
*/
function dump($variable, bool $echo = true): string
{
$kirby = App::instance();
return $kirby->component('dump')($kirby, $variable, $echo);
}
/**
* 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 bool $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 int $code
* @return void
*/
function go(string $url = null, int $code = 302)
{
die(Response::redirect($url, $code));
}
/**
* Shortcut for html()
*
* @param string|null $string unencoded text
* @param bool $keepTags
* @return string
*/
function h(?string $string, bool $keepTags = false)
{
return Html::encode($string, $keepTags);
}
/**
* Creates safe html by encoding special characters
*
* @param string|null $string unencoded text
* @param bool $keepTags
* @return string
*/
function html(?string $string, 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 \Kirby\Cms\File|null
*/
function image(string $path = null)
{
if ($path === null) {
return page()->image();
}
$uri = dirname($path);
$filename = basename($path);
if ($uri === '.') {
$uri = null;
}
switch ($uri) {
case '/':
$parent = site();
break;
case null:
$parent = page();
break;
default:
$parent = page($uri);
break;
}
if ($parent) {
return $parent->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 $url
* @param string|array $options
* @return string|null
*/
function js($url, $options = null): ?string
{
if (is_array($url) === true) {
$scripts = array_map(function ($url) use ($options) {
return js($url, $options);
}, $url);
return implode(PHP_EOL, $scripts);
}
if (is_bool($options) === true) {
$options = ['async' => $options];
}
$kirby = App::instance();
if ($url === '@auto') {
if (!$url = Url::toTemplateAsset('js/templates', 'js')) {
return null;
}
}
$url = $kirby->component('js')($kirby, $url, $options);
$url = Url::to($url);
$attr = array_merge((array)$options, ['src' => $url]);
return '<script ' . attr($attr) . '></script>';
}
/**
* Returns the Kirby object in any situation
*
* @return \Kirby\Cms\App
*/
function kirby()
{
return App::instance();
}
/**
* Makes it possible to use any defined Kirbytag as standalone function
*
* @param string|array $type
* @param string $value
* @param array $attr
* @param array $data
* @return string
*/
function kirbytag($type, string $value = null, array $attr = [], array $data = []): string
{
if (is_array($type) === true) {
$kirbytag = $type;
$type = key($kirbytag);
$value = current($kirbytag);
$attr = $kirbytag;
// check data attribute and separate from attr data if exists
if (isset($attr['data']) === true) {
$data = $attr['data'];
unset($attr['data']);
}
}
return App::instance()->kirbytag($type, $value, $attr, $data);
}
/**
* 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);
}
/**
* Parses KirbyTags and inline Markdown in the
* given string.
* @since 3.1.0
*
* @param string $text
* @param array $data
* @return string
*/
function kirbytextinline(string $text = null, array $data = []): string
{
return App::instance()->kirbytext($text, $data, true);
}
/**
* Shortcut for `kirbytext()` helper
*
* @param string $text
* @param array $data
* @return string
*/
function kt(string $text = null, array $data = []): string
{
return kirbytext($text, $data);
}
/**
* Shortcut for `kirbytextinline()` helper
* @since 3.1.0
*
* @param string $text
* @param array $data
* @return string
*/
function kti(string $text = null, array $data = []): string
{
return kirbytextinline($text, $data);
}
/**
* A super simple class autoloader
*
* @param array $classmap
* @param string $base
* @return void
*/
function load(array $classmap, string $base = null)
{
// convert all classnames to lowercase
$classmap = array_change_key_case($classmap);
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 \Kirby\Cms\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 \Kirby\Cms\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 int $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 \Kirby\Cms\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|array $name
* @param array|object $data
* @param bool $return
* @return string
*/
function snippet($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|\Kirby\Cms\File $file
* @return string|false
*/
function svg($file)
{
// support for Kirby's file objects
if (is_a($file, 'Kirby\Cms\File') === true && $file->extension() === 'svg') {
return $file->read();
}
if (is_string($file) === false) {
return false;
}
$extension = F::extension($file);
// check for valid svg files
if ($extension !== 'svg') {
return false;
}
// try to convert relative paths to absolute
if (file_exists($file) === false) {
$root = App::instance()->root();
$file = realpath($root . '/' . $file);
if (file_exists($file) === false) {
return false;
}
}
return F::read($file);
}
/**
* 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);
}
/**
* Translate by key and then replace
* placeholders in the text
*
* @param string $key
* @param string $fallback
* @param array $replace
* @param string $locale
* @return string
*/
function tt(string $key, $fallback = null, array $replace = null, string $locale = null)
{
return I18n::template($key, $fallback, $replace, $locale);
}
/**
* 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|string|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|string|null $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::vimeo($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::youtube($url, $options, $attr);
}

568
kirby/config/methods.php Normal file
View file

@ -0,0 +1,568 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Field;
use Kirby\Cms\Files;
use Kirby\Cms\Html;
use Kirby\Cms\Structure;
use Kirby\Cms\Url;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
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 \Kirby\Cms\Field $field
* @return bool
*/
'isFalse' => function (Field $field): bool {
return $field->toBool() === false;
},
/**
* Converts the field value into a proper boolean
*
* @param \Kirby\Cms\Field $field
* @return bool
*/
'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 bool
*/
'isValid' => function (Field $field, string $validator, ...$arguments): bool {
return V::$validator($field->value, ...$arguments);
},
// converters
/**
* Parses the field value with the given method
*
* @param \Kirby\Cms\Field $field
* @param string $method [',', 'yaml', 'json']
* @return array
*/
'toData' => function (Field $field, string $method = ',') {
switch ($method) {
case 'yaml':
case 'json':
return Data::decode($field->value, $method);
default:
return $field->split($method);
}
},
/**
* Converts the field value into a proper boolean
*
* @param \Kirby\Cms\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 \Kirby\Cms\Field $field
* @param string|null $format PHP date formatting string
* @param string|null $fallback Fallback string for `strtotime` (since 3.2)
* @return string|int
*/
'toDate' => function (Field $field, string $format = null, string $fallback = null) use ($app) {
if (empty($field->value) === true && $fallback === null) {
return null;
}
$time = empty($field->value) === true ? strtotime($fallback) : $field->toTimestamp();
if ($format === null) {
return $time;
}
return $app->option('date.handler', 'date')($format, $time);
},
/**
* Returns a file object from a filename in the field
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\File|null
*/
'toFile' => function (Field $field) {
return $field->toFiles()->first();
},
/**
* Returns a file collection from a yaml list of filenames in the field
*
* @param \Kirby\Cms\Field $field
* @param string $separator
* @return \Kirby\Cms\Files
*/
'toFiles' => function (Field $field, string $separator = 'yaml') {
$parent = $field->parent();
$files = new Files([]);
foreach ($field->toData($separator) as $id) {
if ($file = $parent->kirby()->file($id, $parent)) {
$files->add($file);
}
}
return $files;
},
/**
* Converts the field value into a proper float
*
* @param \Kirby\Cms\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 (float)$value;
},
/**
* Converts the field value into a proper integer
*
* @param \Kirby\Cms\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 (int)$value;
},
/**
* Wraps a link tag around the field value. The field value is used as the link text
*
* @param \Kirby\Cms\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 \Kirby\Cms\Field $field
* @return \Kirby\Cms\Page|null
*/
'toPage' => function (Field $field) {
return $field->toPages()->first();
},
/**
* Returns a pages collection from a yaml list of page ids in the field
*
* @param \Kirby\Cms\Field $field
* @param string $separator Can be any other separator to split the field value by
* @return \Kirby\Cms\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
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Structure
*/
'toStructure' => function (Field $field) {
try {
return new Structure(Data::decode($field->value, 'yaml'), $field->parent());
} catch (Exception $e) {
if ($field->parent() === null) {
$message = 'Invalid structure data for "' . $field->key() . '" field';
} else {
$message = 'Invalid structure data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"';
}
throw new InvalidArgumentException($message);
}
},
/**
* Converts the field value to a Unix timestamp
*
* @param \Kirby\Cms\Field $field
* @return int
*/
'toTimestamp' => function (Field $field): int {
return strtotime($field->value);
},
/**
* Turns the field value into an absolute Url
*
* @param \Kirby\Cms\Field $field
* @return string
*/
'toUrl' => function (Field $field): string {
return Url::to($field->value);
},
/**
* Converts a user email address to a user object
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\User|null
*/
'toUser' => function (Field $field) {
return $field->toUsers()->first();
},
/**
* Returns a users collection from a yaml list of user email addresses in the field
*
* @param \Kirby\Cms\Field $field
* @param string $separator
* @return \Kirby\Cms\Users
*/
'toUsers' => function (Field $field, string $separator = 'yaml') use ($app) {
return $app->users()->find(false, false, ...$field->toData($separator));
},
// 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
/**
* Applies the callback function to the field
* @since 3.4.0
*
* @param \Kirby\Cms\Field $field
* @param Closure $callback
*/
'callback' => function (Field $field, Closure $callback) {
return $callback($field);
},
/**
* Escapes the field value to be safely used in HTML
* templates without the risk of XSS attacks
*
* @param \Kirby\Cms\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.
*
* @param \Kirby\Cms\Field $field
* @param int $cahrs
* @param bool $strip
* @param string $rep
* @return \Kirby\Cms\Field
*/
'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
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'html' => function (Field $field) {
$field->value = htmlentities($field->value, ENT_COMPAT, 'utf-8');
return $field;
},
/**
* Strips all block-level HTML elements from the field value,
* it can be safely placed inside of other inline elements
* without the risk of breaking the HTML structure.
* @since 3.3.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'inline' => function (Field $field) {
// List of valid inline elements taken from: https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente
// Obsolete elements, script tags, image maps and form elements have
// been excluded for safety reasons and as they are most likely not
// needed in most cases.
$field->value = strip_tags($field->value, '<b><i><small><abbr><cite><code><dfn><em><kbd><strong><samp><var><a><bdo><br><img><q><span><sub><sup>');
return $field;
},
/**
* Converts the field content from Markdown/Kirbytext to valid HTML
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'kirbytext' => function (Field $field) use ($app) {
$field->value = $app->kirbytext($field->value, [
'parent' => $field->parent(),
'field' => $field
]);
return $field;
},
/**
* Converts the field content from inline Markdown/Kirbytext
* to valid HTML
* @since 3.1.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'kirbytextinline' => function (Field $field) use ($app) {
$field->value = $app->kirbytext($field->value, [
'parent' => $field->parent(),
'field' => $field
], true);
return $field;
},
/**
* Parses all KirbyTags without also parsing Markdown
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'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
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'lower' => function (Field $field) {
$field->value = Str::lower($field->value);
return $field;
},
/**
* Converts markdown to valid HTML
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'markdown' => function (Field $field) use ($app) {
$field->value = $app->markdown($field->value);
return $field;
},
/**
* Converts all line breaks in the field content to `<br>` tags.
* @since 3.3.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'nl2br' => function (Field $field) {
$field->value = nl2br($field->value, false);
return $field;
},
/**
* Uses the field value as Kirby query
*
* @param \Kirby\Cms\Field $field
* @param string|null $expect
* @return mixed
*/
'query' => function (Field $field, string $expect = null) use ($app) {
if ($parent = $field->parent()) {
return $parent->query($field->value, $expect);
}
return Str::query($field->value, [
'kirby' => $app,
'site' => $app->site(),
'page' => $app->page()
]);
},
/**
* It parses any queries found in the field value.
*
* @param \Kirby\Cms\Field $field
* @param array $data
* @param string $fallback Fallback for tokens in the template that cannot be replaced
* @return \Kirby\Cms\Field
*/
'replace' => function (Field $field, array $data = [], string $fallback = '') use ($app) {
if ($parent = $field->parent()) {
$field->value = $field->parent()->toString($field->value, $data, $fallback);
} else {
$field->value = Str::template($field->value, array_replace([
'kirby' => $app,
'site' => $app->site(),
'page' => $app->page()
], $data), $fallback);
}
return $field;
},
/**
* Cuts the string after the given length and
* adds "" if it is longer
*
* @param \Kirby\Cms\Field $field
* @param int $length The number of characters in the string
* @param string $appendix An optional replacement for the missing rest
* @return \Kirby\Cms\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
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\cms\Field
*/
'slug' => function (Field $field) {
$field->value = Str::slug($field->value);
return $field;
},
/**
* Applies SmartyPants to the field
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\cms\Field
*/
'smartypants' => function (Field $field) use ($app) {
$field->value = $app->smartypants($field->value);
return $field;
},
/**
* Splits the field content into an array
*
* @param \Kirby\Cms\Field $field
* @return array
*/
'split' => function (Field $field, $separator = ',') {
return Str::split((string)$field->value, $separator);
},
/**
* Converts the field content to uppercase
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\cms\Field
*/
'upper' => function (Field $field) {
$field->value = Str::upper($field->value);
return $field;
},
/**
* Avoids typographical widows in strings by replacing
* the last space with `&nbsp;`
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\cms\Field
*/
'widont' => function (Field $field) {
$field->value = Str::widont($field->value);
return $field;
},
/**
* Converts the field content to valid XML
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'xml' => function (Field $field) {
$field->value = Xml::encode($field->value);
return $field;
},
// aliases
/**
* Parses yaml in the field content and returns an array
*
* @param \Kirby\Cms\Field $field
* @return array
*/
'yaml' => function (Field $field): array {
return $field->toData('yaml');
},
];
};

View file

@ -0,0 +1,24 @@
<?php
return function (array $props) {
$props['sections'] = [
'files' => [
'headline' => $props['headline'] ?? t('files'),
'type' => 'files',
'layout' => $props['layout'] ?? 'cards',
'template' => $props['template'] ?? null,
'image' => $props['image'] ?? null,
'info' => '{{ file.dimensions }}'
]
];
// remove global options
unset(
$props['headline'],
$props['layout'],
$props['template'],
$props['image']
);
return $props;
};

View file

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

View file

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

90
kirby/config/roots.php Normal file
View file

@ -0,0 +1,90 @@
<?php
return [
// kirby
'kirby' => function (array $roots) {
return realpath(__DIR__ . '/../');
},
// i18n
'i18n' => function (array $roots) {
return $roots['kirby'] . '/i18n';
},
'i18n:translations' => function (array $roots) {
return $roots['i18n'] . '/translations';
},
'i18n:rules' => function (array $roots) {
return $roots['i18n'] . '/rules';
},
// 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';
},
'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';
},
];

151
kirby/config/routes.php Normal file
View file

@ -0,0 +1,151 @@
<?php
use Kirby\Cms\LanguageRoutes;
use Kirby\Cms\Media;
use Kirby\Cms\Panel;
use Kirby\Cms\PanelPlugins;
use Kirby\Cms\PluginAssets;
use Kirby\Toolkit\Str;
return function ($kirby) {
$api = $kirby->option('api.slug', 'api');
$panel = $kirby->option('panel.slug', 'panel');
$index = $kirby->url('index');
$media = $kirby->url('media');
if (Str::startsWith($media, $index) === true) {
$media = Str::after($media, $index);
} else {
// media URL is outside of the site, we can't make routing work;
// fall back to the standard media route
$media = 'media';
}
/**
* 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 $type) use ($kirby) {
$plugins = new PanelPlugins();
return $kirby
->response()
->type($type)
->body($plugins->read($type));
}
],
[
'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) {
return PluginAssets::resolve($provider . '/' . $pluginName, $filename . '.' . $extension);
}
],
[
'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);
}
],
[
'pattern' => $media . '/assets/(:all)/(:any)/(:any)',
'env' => 'media',
'action' => function ($path, $hash, $filename) {
return Media::thumb($path, $hash, $filename);
}
]
];
// Multi-language setup
if ($kirby->multilang() === true) {
$after = LanguageRoutes::create($kirby);
} else {
// Single-language home
$after[] = [
'pattern' => '',
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
return $kirby->resolve();
}
];
// redirect the home page folder to the real homepage
$after[] = [
'pattern' => $kirby->option('home', 'home'),
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
return $kirby
->response()
->redirect($kirby->site()->url());
}
];
// Single-language subpages
$after[] = [
'pattern' => '(:all)',
'method' => 'ALL',
'env' => 'site',
'action' => function (string $path) use ($kirby) {
return $kirby->resolve($path);
}
];
}
return [
'before' => $before,
'after' => $after
];
};

View file

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

View file

@ -0,0 +1,241 @@
<?php
use Kirby\Cms\File;
use Kirby\Toolkit\I18n;
return [
'mixins' => [
'empty',
'headline',
'help',
'layout',
'min',
'max',
'pagination',
'parent',
],
'props' => [
/**
* Enables/disables reverse sorting
*/
'flip' => function (bool $flip = false) {
return $flip;
},
/**
* 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`, `huge`
*/
'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;
},
'parent' => function () {
return $this->parentModel();
},
'files' => function () {
$files = $this->parent->files()->template($this->template);
// filter out all protected files
$files = $files->filterBy('isReadable', true);
if ($this->sortBy) {
$files = $files->sortBy(...$files::sortArgs($this->sortBy));
} elseif ($this->sortable === true) {
$files = $files->sortBy('sort', 'asc', 'filename', 'asc');
}
// flip
if ($this->flip === true) {
$files = $files->flip();
}
// apply the default pagination
$files = $files->paginate([
'page' => $this->page,
'limit' => $this->limit,
'method' => 'none' // the page is manually provided
]);
return $files;
},
'data' => function () {
$data = [];
// the drag text needs to be absolute when the files come from
// a different parent model
$dragTextAbsolute = $this->model->is($this->parent) === false;
foreach ($this->files as $file) {
$image = $file->panelImage($this->image);
$data[] = [
'dragText' => $file->dragText('auto', $dragTextAbsolute),
'extension' => $file->extension(),
'filename' => $file->filename(),
'id' => $file->id(),
'icon' => $file->panelIcon($image),
'image' => $image,
'info' => $file->toString($this->info ?? false),
'link' => $file->panelUrl(true),
'mime' => $file->mime(),
'parent' => $file->parent()->panelPath(),
'text' => $file->toString($this->text),
'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;
}
if ($this->flip === true) {
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,
'apiUrl' => $this->parent->apiUrl(true),
'empty' => $this->empty,
'headline' => $this->headline,
'help' => $this->help,
'layout' => $this->layout,
'link' => $this->link,
'max' => $this->max,
'min' => $this->min,
'size' => $this->size,
'sortable' => $this->sortable,
'upload' => $this->upload
],
'pagination' => $this->pagination
];
}
];

View file

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

View file

@ -0,0 +1,21 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Sets the text for the empty state box
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
}
],
'computed' => [
'empty' => function () {
if ($this->empty) {
return $this->model()->toString($this->empty);
}
}
]
];

View file

@ -0,0 +1,23 @@
<?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 () {
if ($this->headline) {
return $this->model()->toString($this->headline);
}
return ucfirst($this->name);
}
]
];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,43 @@
<?php
use Kirby\Exception\Exception;
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) {
$query = $parent;
$parent = $this->model->query($query);
if (!$parent) {
throw new Exception('The parent for the query "' . $query . '" cannot be found in the section "' . $this->name() . '"');
}
if (
is_a($parent, 'Kirby\Cms\Page') === false &&
is_a($parent, 'Kirby\Cms\Site') === false &&
is_a($parent, 'Kirby\Cms\File') === false &&
is_a($parent, 'Kirby\Cms\User') === false
) {
throw new Exception('The parent for the section "' . $this->name() . '" has to be a page, site or user object');
}
}
if ($parent === null) {
return $this->model;
}
return $parent;
}
]
];

View file

@ -0,0 +1,298 @@
<?php
use Kirby\Cms\Blueprint;
use Kirby\Toolkit\A;
use Kirby\Toolkit\I18n;
return [
'mixins' => [
'empty',
'headline',
'help',
'layout',
'min',
'max',
'pagination',
'parent'
],
'props' => [
/**
* Optional array of templates that should only be allowed to add
* or `false` to completely disable page creation
*/
'create' => function ($create = null) {
return $create;
},
/**
* Enables/disables reverse sorting
*/
'flip' => function (bool $flip = false) {
return $flip;
},
/**
* 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`, `huge`
*/
'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;
},
/**
* Filters the list by templates and sets template options when adding new pages to the section.
*/
'templates' => function ($templates = null) {
return A::wrap($templates ?? $this->template);
},
/**
* 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' => [
'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(...$pages::sortArgs($this->sortBy));
}
// flip
if ($this->flip === true) {
$pages = $pages->flip();
}
// pagination
$pages = $pages->paginate([
'page' => $this->page,
'limit' => $this->limit,
'method' => 'none' // the page is manually provided
]);
return $pages;
},
'total' => function () {
return $this->pages->pagination()->total();
},
'data' => function () {
$data = [];
foreach ($this->pages as $item) {
$permissions = $item->permissions();
$image = $item->panelImage($this->image);
$data[] = [
'id' => $item->id(),
'dragText' => $item->dragText(),
'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->min), [
'min' => $this->min,
'section' => $this->headline
]);
}
if (empty($errors) === true) {
return [];
}
return [
$this->name => [
'label' => $this->headline,
'message' => $errors,
]
];
},
'add' => function () {
if ($this->create === false) {
return false;
}
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 (in_array($this->status, ['listed', 'published', 'all']) === false) {
return false;
}
if ($this->sortable === false) {
return false;
}
if ($this->sortBy !== null) {
return false;
}
if ($this->flip === true) {
return false;
}
return true;
}
],
'methods' => [
'blueprints' => function () {
$blueprints = [];
$templates = empty($this->create) === false ? A::wrap($this->create) : $this->templates;
if (empty($templates) === true) {
$templates = $this->kirby()->blueprints();
}
// 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,
'help' => $this->help,
'layout' => $this->layout,
'link' => $this->link,
'max' => $this->max,
'min' => $this->min,
'size' => $this->size,
'sortable' => $this->sortable
],
'pagination' => $this->pagination,
];
}
];

41
kirby/config/setup.php Normal file
View file

@ -0,0 +1,41 @@
<?php
/**
* Constants
*/
define('DS', '/');
/**
* Load files that can't be autoloaded
*/
require_once __DIR__ . '/helpers.php';
/**
* Class aliases
*/
$aliases = require_once __DIR__ . '/aliases.php';
spl_autoload_register(function ($class) use ($aliases) {
$class = strtolower($class);
if (isset($aliases[$class]) === true) {
class_alias($aliases[$class], $class);
}
});
/**
* Tests
*/
$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;
}
});
}

260
kirby/config/tags.php Normal file
View file

@ -0,0 +1,260 @@
<?php
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',
'download',
'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' => $tag->download !== 'false',
'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',
'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;
}
if ($link = $tag->file($tag->link)) {
$link = $link->url();
} else {
$link = $tag->link === 'self' ? $tag->src : $tag->link;
}
return Html::a($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);
}
// render KirbyText in caption
if ($tag->caption) {
$tag->caption = [$tag->kirby()->kirbytext($tag->caption, [], true)];
}
return Html::figure([ $link($image) ], $tag->caption, [
'class' => $tag->class
]);
}
],
/**
* Link
*/
'link' => [
'attr' => [
'class',
'lang',
'rel',
'role',
'target',
'title',
'text',
],
'html' => function ($tag) {
if (empty($tag->lang) === false) {
$tag->value = Url::to($tag->value, $tag->lang);
}
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', []),
[
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]
);
return Html::figure([$video], $tag->caption, [
'class' => $tag->class ?? $tag->kirby()->option('kirbytext.video.class', 'video'),
]);
}
],
];

33
kirby/config/urls.php Normal file
View file

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

View file

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

File diff suppressed because it is too large Load diff

9
kirby/i18n/rules/LICENSE Normal file
View file

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2012-217 Florian Eckerstorfer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

30
kirby/i18n/rules/ar.json Normal file
View file

@ -0,0 +1,30 @@
{
"أ" : "a",
"ب" : "b",
"ت" : "t",
"ث" : "th",
"ج" : "g",
"ح" : "h",
"خ" : "kh",
"د" : "d",
"ذ" : "th",
"ر" : "r",
"ز" : "z",
"س" : "s",
"ش" : "sh",
"ص" : "s",
"ض" : "d",
"ط" : "t",
"ظ" : "th",
"ع" : "aa",
"غ" : "gh",
"ف" : "f",
"ق" : "k",
"ك" : "k",
"ل" : "l",
"م" : "m",
"ن" : "n",
"ه" : "h",
"و" : "o",
"ي" : "y"
}

16
kirby/i18n/rules/az.json Normal file
View file

@ -0,0 +1,16 @@
{
"Ə": "E",
"Ç": "C",
"Ğ": "G",
"İ": "I",
"Ş": "S",
"Ö": "O",
"Ü": "U",
"ə": "e",
"ç": "c",
"ğ": "g",
"ı": "i",
"ş": "s",
"ö": "o",
"ü": "u"
}

65
kirby/i18n/rules/bg.json Normal file
View file

@ -0,0 +1,65 @@
{
"А": "A",
"Б": "B",
"В": "V",
"Г": "G",
"Д": "D",
"Е": "E",
"Ж": "J",
"З": "Z",
"И": "I",
"Й": "Y",
"К": "K",
"Л": "L",
"М": "M",
"Н": "N",
"О": "O",
"П": "P",
"Р": "R",
"С": "S",
"Т": "T",
"У": "U",
"Ф": "F",
"Х": "H",
"Ц": "Ts",
"Ч": "Ch",
"Ш": "Sh",
"Щ": "Sht",
"Ъ": "A",
"Ь": "I",
"Ю": "Iu",
"Я": "Ia",
"а": "a",
"б": "b",
"в": "v",
"г": "g",
"д": "d",
"е": "e",
"ж": "j",
"з": "z",
"и": "i",
"й": "y",
"к": "k",
"л": "l",
"м": "m",
"н": "n",
"о": "o",
"п": "p",
"р": "r",
"с": "s",
"т": "t",
"у": "u",
"ф": "f",
"х": "h",
"ц": "ts",
"ч": "ch",
"ш": "sh",
"щ": "sht",
"ъ": "a",
"ь": "i",
"ю": "iu",
"я": "ia",
"ия": "ia",
"йо": "iо",
"ьо": "io"
}

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