Initial commit

This commit is contained in:
isUnknown 2026-02-12 15:22:46 +01:00
commit 65e0da7e11
1397 changed files with 596542 additions and 0 deletions

24
kirby/.editorconfig Normal file
View file

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

99
kirby/CONTRIBUTING.md Normal file
View file

@ -0,0 +1,99 @@
# Contributing
:+1::tada: First off, yes, you can contribute and thanks already for taking the time if you do! :tada::+1:
## How we organize code
To keep track of different states of our code (current release, bugfixes, features) we use branches:
| Branch | Used for | PRs allowed? |
| ----------- | ------------------------------------------------------------------------ | --------------------------- |
| `main` | Latest released version | - |
| `develop` | Working branch for next release, e.g. `3.7.x` | ✅ |
| `fix/*` | Temporary branches for single patch | - |
| `feature/*` | Temporary branches for single feature | - |
| `release/*` | Pre-releases in testing before they are merged into `main` when released | only during release testing |
We will review all pull requests (PRs) to `develop` and merge them if accepted, once an appropriate version is upcoming. Please understand that this might not be the immediate next release and might take some time.
## How you can contribute
### Report a bug
When you find a bug, the first step to fixing it is to help us understand and reproduce the bug as best as possible. When you create a bug report, please include as many details as possible. Fill out [the template](ISSUE_TEMPLATE/bug_report.md) because the requested information helps us resolve issues so much faster.
### Bug fixes
For bug fixes, please create a new branch following the name scheme: `fix/issue_number-bug-x`, e.g. `fix/234-this-nasty-bug`. Limit bug fix PRs to a single bug. **Do not mix multiple bug fixes in a single PR.** This will make it easier for us to review the fix and merge it.
- Always send bug fix PRs against the `develop` branchnot `main`.
- Add a helpful description of what the PR does if it is not 100% self-explanatory.
- Every bug fix should include a [unit test](#tests) to avoid future regressions. Let us know if you need help with that.
- Make sure your code [style](#style) matches ours and includes [comments/in-code documentation](#documentation).
- Make sure your branch is up to date with the latest state on the `develop` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
### Features
For features create a new branch following the name scheme: `feature/issue_number-feature-x`, e.g. `feature/123-awesome-function`. Our [feedback platform](https://feedback.getkirby.com) can be a good source of highly requested features. Maybe your feature idea already exists and you can get valuable feedback from other Kirby users. Focus on a single feature per PR. Don't mix features!
- Always send feature PRs against the `develop` branchnot `main`.
- Add a helpful description of what the PR does.
- New features should include [unit tests](#tests). Let us know if you need help with that.
- Make your code [style](#style) matches ours and includes [comments/in-code documentation](#documentation).
- Make sure your branch is up to date with the latest state on the `develop` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
We try to bundle features in our major releases, e.g. `3.x`. That is why we might only review and, if accepted, merge your PR once an appropriate release is upcoming. Please understand that we cannot merge all feature ideas or that it might take a while. Check out the [roadmap](https://roadmap.getkirby.com) to see upcoming releases.
### Translations
We are really happy about any help with translations. Please do not directly translate JSON files, though. We use a service called Transifex to handle [all translations](https://translation.getkirby.com/). Create an account there and send us a request to join our translator group. Additionally, also send an email to <support@getkirby.com>. Unfortunately, we don't get notified properly about new translator requests.
## How we write code
### Style
#### Backend (PHP)
We use [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to ensure a consistent style for our PHP code. It is mainly based on [PSR-12](https://www.php-fig.org/psr/psr-12/). [Install PHP CS Fixer globally](https://github.com/FriendsOfPHP/PHP-CS-Fixer#globally-composer) via Composer and then run `composer fix` in the `kirby` folder to check for inconsistencies and fix them. Our automated PR checks will fail if there are code style issues with your code.
#### Frontend/Panel (JavaScript, Vue)
We use [Prettier](https://prettier.io) to ensure a consistent style for our JavaScript and Vue code. After running `npm install` in the `kirby/panel` folder, you can run `npm run format` to check for inconsistencies and fix them. We also use [ESLint](https://eslint.org) which you can use by running `npm run lint` and/or `npm run lint:fix`.
### Documentation
In-code documentation and comments help us understand each other's code - or our own code after some months. Especially when matters get more complicated, we try to add a lot of comments to explain what the code does or why we implemented it like this. Even better than good comments is good code that is easy to understand.
#### Backend (PHP)
We use PHP [DocBlocks](https://docs.phpdoc.org/guide/references/phpdoc/basic-syntax.html#what-is-a-docblock) for classes and methods.
#### Frontend/Panel (JavaScript, Vue)
We use [JSDoc](https://jsdoc.app) for documenting JavaScript code, especially for [Vue components](https://vue-styleguidist.github.io/docs/Documenting.html).
#### Public documentation
We also document Kirby on the Kirby website at <https://getkirby.com>. However we recommend to wait with writing public documentation until the feature PR is merged. If you don't know where the documentation for a feature best belongs, don't worry. We can take care of writing the docs.
### Tests
Unit and integration tests help us prevent regressions when we make changes to the code. Every bug fix should also add a unit test for the fixed bug to make sure we won't re-introduce the same problem later down the road. Every new feature should be accompanied by unit tests to protect it from breaking through future changes.
#### Backend (PHP)
We use [PHPUnit](https://phpunit.de) for unit test for our PHP code. You can find all existing tests in the [`kirby/tests` subfolders](https://github.com/getkirby/kirby/tree/main/tests). Take a look to see how we usually structure our tests.
#### Frontend/Panel (JavaScript, Vue)
The Panel doesn't have extensive test coverage yet. That's an area we are still trying to improve.
We use [vitest](https://vitest.dev) for unit tests for JavaScript and Vue components - `.test.js` files next to the actual JavaScript/Vue file.
For integration tests, we use [cypress](https://www.cypress.io) - `.e2e.js` files.
## And last…
Let us know [in the forum](https://forum.getkirby.com) if you have questions.
**And once more: thank you!** :+1::tada:

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.4.0', '>=') === false ||
version_compare(PHP_VERSION, '8.2.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
*/
}

3347
kirby/cacert.pem Normal file

File diff suppressed because it is too large Load diff

110
kirby/composer.json Normal file
View file

@ -0,0 +1,110 @@
{
"name": "getkirby/cms",
"description": "The Kirby 3 core",
"license": "proprietary",
"type": "kirby-cms",
"version": "3.7.1",
"keywords": [
"kirby",
"cms",
"core"
],
"authors": [
{
"name": "Kirby Team",
"email": "support@getkirby.com",
"homepage": "https://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.4.0 <8.2.0",
"ext-SimpleXML": "*",
"ext-ctype": "*",
"ext-curl": "*",
"ext-dom": "*",
"ext-filter": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"claviska/simpleimage": "3.6.5",
"filp/whoops": "2.14.5",
"getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.10.0",
"michelf/php-smartypants": "1.8.1",
"phpmailer/phpmailer": "6.6.3",
"symfony/polyfill-intl-idn": "1.26.0",
"symfony/polyfill-mbstring": "1.26.0"
},
"replace": {
"symfony/polyfill-php72": "*"
},
"suggest": {
"ext-PDO": "Support for using databases",
"ext-apcu": "Support for the Apcu cache driver",
"ext-exif": "Support for exif information from images",
"ext-fileinfo": "Improved mime type detection for files",
"ext-intl": "Improved i18n number formatting",
"ext-memcached": "Support for the Memcached cache driver",
"ext-zip": "Support for ZIP archive file functions",
"ext-zlib": "Sanitization and validation for svgz files"
},
"autoload": {
"psr-4": {
"Kirby\\": "src/"
},
"classmap": [
"dependencies/"
],
"files": [
"config/setup.php",
"config/helpers.php"
]
},
"config": {
"allow-plugins": {
"getkirby/composer-installer": true
},
"optimize-autoloader": true,
"platform": {
"php": "7.4.0"
},
"platform-check": false
},
"extra": {
"unused": [
"symfony/polyfill-intl-idn"
]
},
"scripts": {
"post-update-cmd": "curl -o cacert.pem https://curl.se/ca/cacert.pem",
"analyze": [
"@analyze:composer",
"@analyze:psalm",
"@analyze:phpcpd",
"@analyze:phpmd"
],
"analyze:composer": "composer validate --strict --no-check-version --no-check-all",
"analyze:phpcpd": "phpcpd --fuzzy --exclude tests --exclude vendor .",
"analyze:phpmd": "phpmd . ansi phpmd.xml.dist --exclude 'dependencies/*,tests/*,vendor/*'",
"analyze:psalm": "psalm",
"build": "./scripts/build",
"ci": [
"@fix",
"@analyze",
"@test"
],
"fix": "php-cs-fixer fix",
"test": "phpunit --stderr --coverage-html=tests/coverage",
"zip": "composer archive --format=zip --file=dist"
}
}

758
kirby/composer.lock generated Normal file
View file

@ -0,0 +1,758 @@
{
"_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": "1d71d8053b544a0d3380bc67234ff81d",
"packages": [
{
"name": "claviska/simpleimage",
"version": "3.6.5",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "00f90662686696b9b7157dbb176183aabe89700f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/00f90662686696b9b7157dbb176183aabe89700f",
"reference": "00f90662686696b9b7157dbb176183aabe89700f",
"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.",
"support": {
"issues": "https://github.com/claviska/SimpleImage/issues",
"source": "https://github.com/claviska/SimpleImage/tree/3.6.5"
},
"funding": [
{
"url": "https://github.com/claviska",
"type": "github"
}
],
"time": "2021-12-01T12:42:55+00:00"
},
{
"name": "filp/whoops",
"version": "2.14.5",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
"reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"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.7-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"
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.14.5"
},
"funding": [
{
"url": "https://github.com/denis-sokolov",
"type": "github"
}
],
"time": "2022-01-07T12:00:00+00:00"
},
{
"name": "getkirby/composer-installer",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/getkirby/composer-installer.git",
"reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d",
"reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "^1.8 || ^2.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",
"support": {
"issues": "https://github.com/getkirby/composer-installer/issues",
"source": "https://github.com/getkirby/composer-installer/tree/1.2.1"
},
"funding": [
{
"url": "https://getkirby.com/buy",
"type": "custom"
}
],
"time": "2020-12-28T12:54:39+00:00"
},
{
"name": "laminas/laminas-escaper",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "58af67282db37d24e584a837a94ee55b9c7552be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be",
"reference": "58af67282db37d24e584a837a94ee55b9c7552be",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "^7.4 || ~8.0.0 || ~8.1.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.26.6",
"laminas/laminas-coding-standard": "~2.3.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.5.18",
"psalm/plugin-phpunit": "^0.16.1",
"vimeo/psalm": "^4.22.0"
},
"type": "library",
"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"
],
"support": {
"chat": "https://laminas.dev/chat",
"docs": "https://docs.laminas.dev/laminas-escaper/",
"forum": "https://discourse.laminas.dev",
"issues": "https://github.com/laminas/laminas-escaper/issues",
"rss": "https://github.com/laminas/laminas-escaper/releases.atom",
"source": "https://github.com/laminas/laminas-escaper"
},
"funding": [
{
"url": "https://funding.communitybridge.org/projects/laminas-project",
"type": "community_bridge"
}
],
"time": "2022-03-08T20:15:36+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"
],
"support": {
"issues": "https://github.com/thephpleague/color-extractor/issues",
"source": "https://github.com/thephpleague/color-extractor/tree/master"
},
"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"
],
"support": {
"issues": "https://github.com/michelf/php-smartypants/issues",
"source": "https://github.com/michelf/php-smartypants/tree/1.8.1"
},
"time": "2016-12-13T01:01:17+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.6.3",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "9400f305a898f194caff5521f64e5dfa926626f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/9400f305a898f194caff5521f64e5dfa926626f3",
"reference": "9400f305a898f194caff5521f64e5dfa926626f3",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.2",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.6.2",
"yoast/phpunit-polyfills": "^1.0.0"
},
"suggest": {
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"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",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.3"
},
"funding": [
{
"url": "https://github.com/Synchro",
"type": "github"
}
],
"time": "2022-06-20T09:21:02+00:00"
},
{
"name": "psr/log",
"version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
"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": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.4"
},
"time": "2021-05-03T11:20:27+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "219aa369ceff116e673852dce47c3a41794c14bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
"reference": "219aa369ceff116e673852dce47c3a41794c14bd",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"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 intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"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"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.4.0 <8.2.0",
"ext-simplexml": "*",
"ext-ctype": "*",
"ext-curl": "*",
"ext-dom": "*",
"ext-filter": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-openssl": "*"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.4.0"
},
"plugin-api-version": "2.3.0"
}

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

@ -0,0 +1,81 @@
<?php
return [
// cms classes
'collection' => 'Kirby\Cms\Collection',
'field' => 'Kirby\Cms\Field',
'file' => 'Kirby\Cms\File',
'files' => 'Kirby\Cms\Files',
'find' => 'Kirby\Cms\Find',
'helpers' => 'Kirby\Cms\Helpers',
'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',
'sane' => 'Kirby\Sane\Sane',
'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',
// file classes
'asset' => 'Kirby\Filesystem\Asset',
'dir' => 'Kirby\Filesystem\Dir',
'f' => 'Kirby\Filesystem\F',
'mime' => 'Kirby\Filesystem\Mime',
// 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',
// panel classes
'panel' => 'Kirby\Panel\Panel',
// toolkit classes
'a' => 'Kirby\Toolkit\A',
'c' => 'Kirby\Toolkit\Config',
'config' => 'Kirby\Toolkit\Config',
'escape' => 'Kirby\Toolkit\Escape',
'i18n' => 'Kirby\Toolkit\I18n',
'obj' => 'Kirby\Toolkit\Obj',
'str' => 'Kirby\Toolkit\Str',
'tpl' => 'Kirby\Toolkit\Tpl',
'v' => 'Kirby\Toolkit\V',
'xml' => 'Kirby\Toolkit\Xml',
// TODO: remove in 4.0.0
'kirby\cms\asset' => 'Kirby\Filesystem\Asset',
'kirby\cms\dir' => 'Kirby\Filesystem\Dir',
'kirby\cms\filename' => 'Kirby\Filesystem\Filename',
'kirby\cms\filefoundation' => 'Kirby\Filesystem\IsFile',
'kirby\cms\form' => 'Kirby\Form\Form',
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
'kirby\toolkit\dir' => 'Kirby\Filesystem\Dir',
'kirby\toolkit\f' => 'Kirby\Filesystem\F',
'kirby\toolkit\file' => 'Kirby\Filesystem\File',
'kirby\toolkit\mime' => 'Kirby\Filesystem\Mime',
];

View file

@ -0,0 +1,27 @@
<?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,70 @@
<?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' => fn () => $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,115 @@
<?php
use Kirby\Cms\File;
use Kirby\Form\Form;
/**
* File
*/
return [
'fields' => [
'blueprint' => fn (File $file) => $file->blueprint(),
'content' => fn (File $file) => Form::for($file)->values(),
'dimensions' => fn (File $file) => $file->dimensions()->toArray(),
'dragText' => fn (File $file) => $file->panel()->dragText(),
'exists' => fn (File $file) => $file->exists(),
'extension' => fn (File $file) => $file->extension(),
'filename' => fn (File $file) => $file->filename(),
'id' => fn (File $file) => $file->id(),
'link' => fn (File $file) => $file->panel()->url(true),
'mime' => fn (File $file) => $file->mime(),
'modified' => fn (File $file) => $file->modified('c'),
'name' => fn (File $file) => $file->name(),
'next' => fn (File $file) => $file->next(),
'nextWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sorted();
$index = $files->indexOf($file);
return $files->nth($index + 1);
},
'niceSize' => fn (File $file) => $file->niceSize(),
'options' => fn (File $file) => $file->panel()->options(),
'panelImage' => fn (File $file) => $file->panel()->image(),
'panelUrl' => fn (File $file) => $file->panel()->url(true),
'prev' => fn (File $file) => $file->prev(),
'prevWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sorted();
$index = $files->indexOf($file);
return $files->nth($index - 1);
},
'parent' => fn (File $file) => $file->parent(),
'parents' => fn (File $file) => $file->parents()->flip(),
'size' => fn (File $file) => $file->size(),
'template' => fn (File $file) => $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' => fn (File $file) => $file->type(),
'url' => fn (File $file) => $file->url(),
],
'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,18 @@
<?php
use Kirby\Cms\FileBlueprint;
/**
* FileBlueprint
*/
return [
'fields' => [
'name' => fn (FileBlueprint $blueprint) => $blueprint->name(),
'options' => fn (FileBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (FileBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (FileBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\FileBlueprint',
'views' => [
],
];

View file

@ -0,0 +1,59 @@
<?php
use Kirby\Cms\FileVersion;
/**
* FileVersion
*/
return [
'fields' => [
'dimensions' => fn (FileVersion $file) => $file->dimensions()->toArray(),
'exists' => fn (FileVersion $file) => $file->exists(),
'extension' => fn (FileVersion $file) => $file->extension(),
'filename' => fn (FileVersion $file) => $file->filename(),
'id' => fn (FileVersion $file) => $file->id(),
'mime' => fn (FileVersion $file) => $file->mime(),
'modified' => fn (FileVersion $file) => $file->modified('c'),
'name' => fn (FileVersion $file) => $file->name(),
'niceSize' => fn (FileVersion $file) => $file->niceSize(),
'size' => fn (FileVersion $file) => $file->size(),
'type' => fn (FileVersion $file) => $file->type(),
'url' => fn (FileVersion $file) => $file->url(),
],
'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,30 @@
<?php
use Kirby\Cms\Language;
/**
* Language
*/
return [
'fields' => [
'code' => fn (Language $language) => $language->code(),
'default' => fn (Language $language) => $language->isDefault(),
'direction' => fn (Language $language) => $language->direction(),
'locale' => fn (Language $language) => $language->locale(),
'name' => fn (Language $language) => $language->name(),
'rules' => fn (Language $language) => $language->rules(),
'url' => fn (Language $language) => $language->url(),
],
'type' => 'Kirby\Cms\Language',
'views' => [
'default' => [
'code',
'default',
'direction',
'locale',
'name',
'rules',
'url'
]
]
];

View file

@ -0,0 +1,123 @@
<?php
use Kirby\Cms\Helpers;
use Kirby\Cms\Page;
use Kirby\Form\Form;
/**
* Page
*/
return [
'fields' => [
'blueprint' => fn (Page $page) => $page->blueprint(),
'blueprints' => fn (Page $page) => $page->blueprints(),
'children' => fn (Page $page) => $page->children(),
'content' => fn (Page $page) => Form::for($page)->values(),
'drafts' => fn (Page $page) => $page->drafts(),
'errors' => fn (Page $page) => $page->errors(),
'files' => fn (Page $page) => $page->files()->sorted(),
'hasChildren' => fn (Page $page) => $page->hasChildren(),
'hasDrafts' => fn (Page $page) => $page->hasDrafts(),
'hasFiles' => fn (Page $page) => $page->hasFiles(),
'id' => fn (Page $page) => $page->id(),
'isSortable' => fn (Page $page) => $page->isSortable(),
/**
* @deprecated 3.6.0
* @todo Remove in 3.8.0
* @codeCoverageIgnore
*/
'next' => function (Page $page) {
Helpers::deprecated('The API field page.next has been deprecated and will be removed in 3.8.0.');
return $page
->nextAll()
->filter('intendedTemplate', $page->intendedTemplate())
->filter('status', $page->status())
->filter('isReadable', true)
->first();
},
'num' => fn (Page $page) => $page->num(),
'options' => fn (Page $page) => $page->panel()->options(['preview']),
'panelImage' => fn (Page $page) => $page->panel()->image(),
'parent' => fn (Page $page) => $page->parent(),
'parents' => fn (Page $page) => $page->parents()->flip(),
/**
* @deprecated 3.6.0
* @todo Remove in 3.8.0
* @codeCoverageIgnore
*/
'prev' => function (Page $page) {
Helpers::deprecated('The API field page.prev has been deprecated and will be removed in 3.8.0.');
return $page
->prevAll()
->filter('intendedTemplate', $page->intendedTemplate())
->filter('status', $page->status())
->filter('isReadable', true)
->last();
},
'previewUrl' => fn (Page $page) => $page->previewUrl(),
'siblings' => function (Page $page) {
if ($page->isDraft() === true) {
return $page->parentModel()->children()->not($page);
} else {
return $page->siblings();
}
},
'slug' => fn (Page $page) => $page->slug(),
'status' => fn (Page $page) => $page->status(),
'template' => fn (Page $page) => $page->intendedTemplate()->name(),
'title' => fn (Page $page) => $page->title()->value(),
'url' => fn (Page $page) => $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,21 @@
<?php
use Kirby\Cms\PageBlueprint;
/**
* PageBlueprint
*/
return [
'fields' => [
'name' => fn (PageBlueprint $blueprint) => $blueprint->name(),
'num' => fn (PageBlueprint $blueprint) => $blueprint->num(),
'options' => fn (PageBlueprint $blueprint) => $blueprint->options(),
'preview' => fn (PageBlueprint $blueprint) => $blueprint->preview(),
'status' => fn (PageBlueprint $blueprint) => $blueprint->status(),
'tabs' => fn (PageBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (PageBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\PageBlueprint',
'views' => [
],
];

View file

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

View file

@ -0,0 +1,52 @@
<?php
use Kirby\Cms\Site;
use Kirby\Form\Form;
/**
* Site
*/
return [
'default' => fn () => $this->site(),
'fields' => [
'blueprint' => fn (Site $site) => $site->blueprint(),
'children' => fn (Site $site) => $site->children(),
'content' => fn (Site $site) => Form::for($site)->values(),
'drafts' => fn (Site $site) => $site->drafts(),
'files' => fn (Site $site) => $site->files()->sorted(),
'options' => fn (Site $site) => $site->permissions()->toArray(),
'previewUrl' => fn (Site $site) => $site->previewUrl(),
'title' => fn (Site $site) => $site->title()->value(),
'url' => fn (Site $site) => $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,17 @@
<?php
use Kirby\Cms\SiteBlueprint;
/**
* SiteBlueprint
*/
return [
'fields' => [
'name' => fn (SiteBlueprint $blueprint) => $blueprint->name(),
'options' => fn (SiteBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (SiteBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (SiteBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\SiteBlueprint',
'views' => [],
];

View file

@ -0,0 +1,98 @@
<?php
use Kirby\Cms\System;
use Kirby\Toolkit\Str;
/**
* System
*/
return [
'fields' => [
'ascii' => fn () => Str::$ascii,
'authStatus' => fn () => $this->kirby()->auth()->status()->toArray(),
'defaultLanguage' => fn () => $this->kirby()->panelLanguage(),
'isOk' => fn (System $system) => $system->isOk(),
'isInstallable' => fn (System $system) => $system->isInstallable(),
'isInstalled' => fn (System $system) => $system->isInstalled(),
'isLocal' => fn (System $system) => $system->isLocal(),
'multilang' => fn () => $this->kirby()->option('languages', false) !== false,
'languages' => fn () => $this->kirby()->languages(),
'license' => fn (System $system) => $system->license(),
'locales' => function () {
$locales = [];
$translations = $this->kirby()->translations();
foreach ($translations as $translation) {
$locales[$translation->code()] = $translation->locale();
}
return $locales;
},
'loginMethods' => fn (System $system) => array_keys($system->loginMethods()),
'requirements' => fn (System $system) => $system->toArray(),
'site' => fn (System $system) => $system->title(),
'slugs' => fn () => Str::$language,
'title' => fn () => $this->site()->title()->value(),
'translation' => function () {
if ($user = $this->user()) {
$translationCode = $user->language();
} else {
$translationCode = $this->kirby()->panelLanguage();
}
if ($translation = $this->kirby()->translation($translationCode)) {
return $translation;
} else {
return $this->kirby()->translation('en');
}
},
'kirbytext' => fn () => $this->kirby()->option('panel.kirbytext') ?? true,
'user' => fn () => $this->user(),
'version' => function () {
$user = $this->user();
if ($user && $user->role()->permissions()->for('access', 'system') === true) {
return $this->kirby()->version();
} else {
return null;
}
}
],
'type' => 'Kirby\Cms\System',
'views' => [
'login' => [
'authStatus',
'isOk',
'isInstallable',
'isInstalled',
'loginMethods',
'title',
'translation'
],
'troubleshooting' => [
'isOk',
'isInstallable',
'isInstalled',
'title',
'translation',
'requirements'
],
'panel' => [
'ascii',
'defaultLanguage',
'isOk',
'isInstalled',
'isLocal',
'kirbytext',
'languages',
'license',
'locales',
'multilang',
'requirements',
'site',
'slugs',
'title',
'translation',
'user' => 'auth',
'version'
]
],
];

View file

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

View file

@ -0,0 +1,77 @@
<?php
use Kirby\Cms\User;
use Kirby\Form\Form;
/**
* User
*/
return [
'default' => fn () => $this->user(),
'fields' => [
'avatar' => fn (User $user) => $user->avatar() ? $user->avatar()->crop(512) : null,
'blueprint' => fn (User $user) => $user->blueprint(),
'content' => fn (User $user) => Form::for($user)->values(),
'email' => fn (User $user) => $user->email(),
'files' => fn (User $user) => $user->files()->sorted(),
'id' => fn (User $user) => $user->id(),
'language' => fn (User $user) => $user->language(),
'name' => fn (User $user) => $user->name()->value(),
'next' => fn (User $user) => $user->next(),
'options' => fn (User $user) => $user->panel()->options(),
'panelImage' => fn (User $user) => $user->panel()->image(),
'permissions' => fn (User $user) => $user->role()->permissions()->toArray(),
'prev' => fn (User $user) => $user->prev(),
'role' => fn (User $user) => $user->role(),
'roles' => fn (User $user) => $user->roles(),
'username' => fn (User $user) => $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,18 @@
<?php
use Kirby\Cms\UserBlueprint;
/**
* UserBlueprint
*/
return [
'fields' => [
'name' => fn (UserBlueprint $blueprint) => $blueprint->name(),
'options' => fn (UserBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (UserBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (UserBlueprint $blueprint) => $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,108 @@
<?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/code',
'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');
}
$user = $auth->verifyChallenge($this->requestBody('code'));
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
],
[
'pattern' => 'auth/login',
'method' => 'POST',
'auth' => false,
'action' => function () {
$auth = $this->kirby()->auth();
$methods = $this->kirby()->system()->loginMethods();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException('Invalid CSRF token');
}
$email = $this->requestBody('email');
$long = $this->requestBody('long');
$password = $this->requestBody('password');
if ($password) {
if (isset($methods['password']) !== true) {
throw new InvalidArgumentException('Login with password is not enabled');
}
if (
isset($methods['password']['2fa']) === true &&
$methods['password']['2fa'] === true
) {
$status = $auth->login2fa($email, $password, $long);
} else {
$user = $auth->login($email, $password, $long);
}
} else {
if (isset($methods['code']) === true) {
$mode = 'login';
} elseif (isset($methods['password-reset']) === true) {
$mode = 'password-reset';
} else {
throw new InvalidArgumentException('Login without password is not enabled');
}
$status = $auth->createChallenge($email, $long, $mode);
}
if (isset($user)) {
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
} else {
return [
'code' => 200,
'status' => 'ok',
'challenge' => $status->challenge()
];
}
}
],
[
'pattern' => 'auth/logout',
'method' => 'POST',
'auth' => false,
'action' => function () {
$this->kirby()->auth()->logout();
return true;
}
],
];

View file

@ -0,0 +1,132 @@
<?php
// routing pattern to match all models with files
$pattern = '(account|pages/[^/]+|site|users/[^/]+)';
/**
* Files Routes
*/
return [
[
'pattern' => $pattern . '/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' => $pattern . '/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' => $pattern . '/files',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->files()->sorted();
}
],
[
'pattern' => $pattern . '/files',
'method' => 'POST',
'action' => function (string $path) {
// move_uploaded_file() not working with unit test
// @codeCoverageIgnoreStart
return $this->upload(function ($source, $filename) use ($path) {
return $this->parent($path)->createFile([
'content' => [
'sort' => $this->requestBody('sort')
],
'source' => $source,
'template' => $this->requestBody('template'),
'filename' => $filename
]);
});
// @codeCoverageIgnoreEnd
}
],
[
'pattern' => $pattern . '/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' => $pattern . '/files/sort',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->files()->changeSort(
$this->requestBody('files'),
$this->requestBody('index')
);
}
],
[
'pattern' => $pattern . '/files/(:any)',
'method' => 'GET',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename);
}
],
[
'pattern' => $pattern . '/files/(:any)',
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => $pattern . '/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' => $pattern . '/files/(:any)',
'method' => 'DELETE',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->delete();
}
],
[
'pattern' => $pattern . '/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)
->filter('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,44 @@
<?php
/**
* Content Lock Routes
*/
return [
[
'pattern' => '(:all)/lock',
'method' => 'PATCH',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->create();
}
}
],
[
'pattern' => '(:all)/lock',
'method' => 'DELETE',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->remove();
}
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'PATCH',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->unlock();
}
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'DELETE',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->resolve();
}
}
],
];

View file

@ -0,0 +1,121 @@
<?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)/blueprint',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->blueprint();
}
],
[
'pattern' => 'pages/(:any)/blueprints',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'GET',
'action' => function (string $id) {
return $this->pages($id, $this->requestQuery('status'));
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->createChild($this->requestBody());
}
],
[
'pattern' => 'pages/(:any)/children/search',
'method' => 'GET|POST',
'action' => function (string $id) {
return $this->searchPages($id);
}
],
[
'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,30 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'roles',
'method' => 'GET',
'action' => function () {
$kirby = $this->kirby();
switch ($kirby->request()->get('canBe')) {
case 'changed':
return $kirby->roles()->canBeChanged();
case 'created':
return $kirby->roles()->canBeCreated();
default:
return $kirby->roles();
}
}
],
[
'pattern' => 'roles/(:any)',
'method' => 'GET',
'action' => function (string $name) {
return $this->kirby()->roles()->find($name);
}
]
];

View file

@ -0,0 +1,104 @@
<?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->pages(null, $this->requestQuery('status'));
}
],
[
'pattern' => 'site/children',
'method' => 'POST',
'action' => function () {
return $this->site()->createChild($this->requestBody());
}
],
[
'pattern' => 'site/children/search',
'method' => 'GET|POST',
'action' => function () {
return $this->searchPages();
}
],
[
'pattern' => 'site/blueprint',
'method' => 'GET',
'action' => function () {
return $this->site()->blueprint();
}
],
[
'pattern' => 'site/blueprints',
'method' => 'GET',
'action' => function () {
return $this->site()->blueprints($this->requestQuery('section'));
}
],
[
'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)
->filter('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,207 @@
<?php
use Kirby\Filesystem\F;
/**
* User Routes
*/
return [
[
'pattern' => 'users',
'method' => 'GET',
'action' => function () {
return $this->users()->sort('username', 'asc', 'email', 'asc');
}
],
[
'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' => [
'(account)',
'users/(:any)',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id);
}
],
[
'pattern' => [
'(account)',
'users/(:any)',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => [
'(account)',
'users/(:any)',
],
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->delete();
}
],
[
'pattern' => [
'(account)/avatar',
'users/(:any)/avatar',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->avatar();
}
],
// @codeCoverageIgnoreStart
[
'pattern' => [
'(account)/avatar',
'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);
}
],
// @codeCoverageIgnoreEnd
[
'pattern' => [
'(account)/avatar',
'users/(:any)/avatar',
],
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->avatar()->delete();
}
],
[
'pattern' => [
'(account)/blueprint',
'users/(:any)/blueprint',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->blueprint();
}
],
[
'pattern' => [
'(account)/blueprints',
'users/(:any)/blueprints',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => [
'(account)/email',
'users/(:any)/email',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeEmail($this->requestBody('email'));
}
],
[
'pattern' => [
'(account)/language',
'users/(:any)/language',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeLanguage($this->requestBody('language'));
}
],
[
'pattern' => [
'(account)/name',
'users/(:any)/name',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeName($this->requestBody('name'));
}
],
[
'pattern' => [
'(account)/password',
'users/(:any)/password',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changePassword($this->requestBody('password'));
}
],
[
'pattern' => [
'(account)/role',
'users/(:any)/role',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeRole($this->requestBody('role'));
}
],
[
'pattern' => [
'(account)/roles',
'users/(:any)/roles',
],
'action' => function (string $id) {
return $this->user($id)->roles();
}
],
[
'pattern' => [
'(account)/sections/(:any)',
'users/(:any)/sections/(:any)',
],
'method' => 'GET',
'action' => function (string $id, string $sectionName) {
if ($section = $this->user($id)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => [
'(account)/fields/(:any)/(:all?)',
'users/(:any)/fields/(:any)/(:all?)',
],
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
return $this->fieldApi($this->user($id), $fieldName, $path);
}
],
];

View file

@ -0,0 +1,14 @@
<?php
use Kirby\Toolkit\I18n;
return function () {
return [
'icon' => 'account',
'label' => I18n::translate('view.account'),
'search' => 'users',
'dialogs' => require __DIR__ . '/account/dialogs.php',
'dropdowns' => require __DIR__ . '/account/dropdowns.php',
'views' => require __DIR__ . '/account/views.php'
];
};

View file

@ -0,0 +1,70 @@
<?php
$dialogs = require __DIR__ . '/../users/dialogs.php';
return [
// change email
'account.changeEmail' => [
'pattern' => '(account)/changeEmail',
'load' => $dialogs['user.changeEmail']['load'],
'submit' => $dialogs['user.changeEmail']['submit'],
],
// change language
'account.changeLanguage' => [
'pattern' => '(account)/changeLanguage',
'load' => $dialogs['user.changeLanguage']['load'],
'submit' => $dialogs['user.changeLanguage']['submit'],
],
// change name
'account.changeName' => [
'pattern' => '(account)/changeName',
'load' => $dialogs['user.changeName']['load'],
'submit' => $dialogs['user.changeName']['submit'],
],
// change password
'account.changePassword' => [
'pattern' => '(account)/changePassword',
'load' => $dialogs['user.changePassword']['load'],
'submit' => $dialogs['user.changePassword']['submit'],
],
// change role
'account.changeRole' => [
'pattern' => '(account)/changeRole',
'load' => $dialogs['user.changeRole']['load'],
'submit' => $dialogs['user.changeRole']['submit'],
],
// delete
'account.delete' => [
'pattern' => '(account)/delete',
'load' => $dialogs['user.delete']['load'],
'submit' => $dialogs['user.delete']['submit'],
],
// change file name
'account.file.changeName' => [
'pattern' => '(account)/files/(:any)/changeName',
'load' => $dialogs['user.file.changeName']['load'],
'submit' => $dialogs['user.file.changeName']['submit'],
],
// change file sort
'account.file.changeSort' => [
'pattern' => '(account)/files/(:any)/changeSort',
'load' => $dialogs['user.file.changeSort']['load'],
'submit' => $dialogs['user.file.changeSort']['submit'],
],
// delete
'account.file.delete' => [
'pattern' => '(account)/files/(:any)/delete',
'load' => $dialogs['user.file.delete']['load'],
'submit' => $dialogs['user.file.delete']['submit'],
],
];

View file

@ -0,0 +1,14 @@
<?php
$dropdowns = require __DIR__ . '/../users/dropdowns.php';
return [
'account' => [
'pattern' => '(account)',
'options' => $dropdowns['user']['options']
],
'account.file' => [
'pattern' => '(account)/files/(:any)',
'options' => $dropdowns['user.file']['options']
],
];

View file

@ -0,0 +1,35 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Panel\Panel;
return [
'account' => [
'pattern' => 'account',
'action' => fn () => [
'component' => 'k-account-view',
'props' => App::instance()->user()->panel()->props(),
],
],
'account.file' => [
'pattern' => 'account/files/(:any)',
'action' => function (string $filename) {
return Find::file('account', $filename)->panel()->view();
}
],
'account.logout' => [
'pattern' => 'logout',
'auth' => false,
'action' => function () {
if ($user = App::instance()->user()) {
$user->logout();
}
Panel::go('login');
},
],
'account.password' => [
'pattern' => 'reset-password',
'action' => fn () => ['component' => 'k-reset-password-view']
]
];

View file

@ -0,0 +1,132 @@
<?php
use Kirby\Cms\Find;
use Kirby\Panel\Field;
use Kirby\Panel\Panel;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
/**
* Shared file dialogs
* They are included in the site and
* users area to create dialogs there.
* The array keys are replaced by
* the appropriate routes in the areas.
*/
return [
'changeName' => [
'load' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'name' => [
'label' => I18n::translate('name'),
'type' => 'slug',
'required' => true,
'icon' => 'title',
'allow' => '@._-',
'after' => '.' . $file->extension(),
'preselect' => true
]
],
'submitButton' => I18n::translate('rename'),
'value' => [
'name' => $file->name(),
]
]
];
},
'submit' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$renamed = $file->changeName($file->kirby()->request()->get('name'));
$oldUrl = $file->panel()->url(true);
$newUrl = $renamed->panel()->url(true);
$response = [
'event' => 'file.changeName',
'dispatch' => [
'content/move' => [
$oldUrl,
$newUrl
]
],
];
// check for a necessary redirect after the filename has changed
if (Panel::referrer() === $oldUrl && $oldUrl !== $newUrl) {
$response['redirect'] = $newUrl;
}
return $response;
}
],
'changeSort' => [
'load' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'position' => Field::filePosition($file)
],
'submitButton' => I18n::translate('change'),
'value' => [
'position' => $file->sort()->isEmpty() ? $file->siblings(false)->count() + 1 : $file->sort()->toInt(),
]
]
];
},
'submit' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$files = $file->siblings()->sorted();
$ids = $files->keys();
$newIndex = (int)($file->kirby()->request()->get('position')) - 1;
$oldIndex = $files->indexOf($file);
array_splice($ids, $oldIndex, 1);
array_splice($ids, $newIndex, 0, $file->id());
$files->changeSort($ids);
return [
'event' => 'file.sort',
];
}
],
'delete' => [
'load' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => I18n::template('file.delete.confirm', [
'filename' => Escape::html($file->filename())
]),
]
];
},
'submit' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$redirect = false;
$referrer = Panel::referrer();
$url = $file->panel()->url(true);
$file->delete();
// redirect to the parent model URL
// if the dialog has been opened in the file view
if ($referrer === $url) {
$redirect = $file->parent()->panel()->url(true);
}
return [
'event' => 'file.delete',
'dispatch' => ['content/remove' => [$url]],
'redirect' => $redirect
];
}
],
];

View file

@ -0,0 +1,9 @@
<?php
use Kirby\Cms\Find;
return [
'file' => function (string $parent, string $filename) {
return Find::file($parent, $filename)->panel()->dropdown();
}
];

View file

@ -0,0 +1,40 @@
<?php
use Kirby\Panel\Panel;
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'settings',
'label' => I18n::translate('view.installation'),
'views' => [
'installation' => [
'pattern' => 'installation',
'auth' => false,
'action' => function () use ($kirby) {
$system = $kirby->system();
return [
'component' => 'k-installation-view',
'props' => [
'isInstallable' => $system->isInstallable(),
'isInstalled' => $system->isInstalled(),
'isOk' => $system->isOk(),
'requirements' => $system->status(),
'translations' => $kirby->translations()->values(function ($translation) {
return [
'text' => $translation->name(),
'value' => $translation->code(),
];
}),
]
];
}
],
'installation.fallback' => [
'pattern' => '(:all)',
'auth' => false,
'action' => fn () => Panel::go('installation')
]
]
];
};

View file

@ -0,0 +1,13 @@
<?php
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'globe',
'label' => I18n::translate('view.languages'),
'menu' => true,
'dialogs' => require __DIR__ . '/languages/dialogs.php',
'views' => require __DIR__ . '/languages/views.php'
];
};

View file

@ -0,0 +1,155 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Panel\Field;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
$languageDialogFields = [
'name' => [
'label' => I18n::translate('language.name'),
'type' => 'text',
'required' => true,
'icon' => 'title'
],
'code' => [
'label' => I18n::translate('language.code'),
'type' => 'text',
'required' => true,
'counter' => false,
'icon' => 'globe',
'width' => '1/2'
],
'direction' => [
'label' => I18n::translate('language.direction'),
'type' => 'select',
'required' => true,
'empty' => false,
'options' => [
['value' => 'ltr', 'text' => I18n::translate('language.direction.ltr')],
['value' => 'rtl', 'text' => I18n::translate('language.direction.rtl')]
],
'width' => '1/2'
],
'locale' => [
'label' => I18n::translate('language.locale'),
'type' => 'text',
],
];
return [
// create language
'language.create' => [
'pattern' => 'languages/create',
'load' => function () use ($languageDialogFields) {
return [
'component' => 'k-language-dialog',
'props' => [
'fields' => $languageDialogFields,
'submitButton' => I18n::translate('language.create'),
'value' => [
'code' => '',
'direction' => 'ltr',
'locale' => '',
'name' => '',
]
]
];
},
'submit' => function () {
$kirby = App::instance();
$data = $kirby->request()->get([
'code',
'direction',
'locale',
'name'
]);
$kirby->languages()->create($data);
return [
'event' => 'language.create'
];
}
],
// delete language
'language.delete' => [
'pattern' => 'languages/(:any)/delete',
'load' => function (string $id) {
$language = Find::language($id);
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => I18n::template('language.delete.confirm', [
'name' => Escape::html($language->name())
])
]
];
},
'submit' => function (string $id) {
Find::language($id)->delete();
return [
'event' => 'language.delete',
];
}
],
// update language
'language.update' => [
'pattern' => 'languages/(:any)/update',
'load' => function (string $id) use ($languageDialogFields) {
$language = Find::language($id);
$fields = $languageDialogFields;
$locale = $language->locale();
// use the first locale key if there's only one
if (count($locale) === 1) {
$locale = A::first($locale);
}
// the code of an existing language cannot be changed
$fields['code']['disabled'] = true;
// if the locale settings is more complex than just a
// single string, the text field won't do it anymore.
// Changes can only be made in the language file and
// we display a warning box instead.
if (is_array($locale) === true) {
$fields['locale'] = [
'label' => $fields['locale']['label'],
'type' => 'info',
'text' => I18n::translate('language.locale.warning')
];
}
return [
'component' => 'k-language-dialog',
'props' => [
'fields' => $fields,
'submitButton' => I18n::translate('save'),
'value' => [
'code' => $language->code(),
'direction' => $language->direction(),
'locale' => $locale,
'name' => $language->name(),
'rules' => $language->rules(),
]
]
];
},
'submit' => function (string $id) {
$kirby = App::instance();
$data = $kirby->request()->get(['direction', 'locale', 'name']);
$language = Find::language($id)->update($data);
return [
'event' => 'language.update'
];
}
],
];

View file

@ -0,0 +1,25 @@
<?php
use Kirby\Cms\App;
use Kirby\Toolkit\Escape;
return [
'languages' => [
'pattern' => 'languages',
'action' => function () {
$kirby = App::instance();
return [
'component' => 'k-languages-view',
'props' => [
'languages' => $kirby->languages()->values(fn ($language) => [
'default' => $language->isDefault(),
'id' => $language->code(),
'info' => Escape::html($language->code()),
'text' => Escape::html($language->name()),
])
]
];
}
],
];

View file

@ -0,0 +1,44 @@
<?php
use Kirby\Panel\Panel;
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'user',
'label' => I18n::translate('login'),
'views' => [
'login' => [
'pattern' => 'login',
'auth' => false,
'action' => function () use ($kirby) {
$system = $kirby->system();
$status = $kirby->auth()->status();
return [
'component' => 'k-login-view',
'props' => [
'methods' => array_keys($system->loginMethods()),
'pending' => [
'email' => $status->email(),
'challenge' => $status->challenge()
]
],
];
}
],
'login.fallback' => [
'pattern' => '(:all)',
'auth' => false,
'action' => function ($path) use ($kirby) {
/**
* Store the current path in the session
* Once the user is logged in, the path will
* be used to redirect to that view again
*/
$kirby->session()->set('panel.path', $path);
Panel::go('login');
}
]
]
];
};

View file

@ -0,0 +1,18 @@
<?php
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'breadcrumbLabel' => function () use ($kirby) {
return $kirby->site()->title()->or(I18n::translate('view.site'))->toString();
},
'icon' => 'home',
'label' => $kirby->site()->blueprint()->title() ?? I18n::translate('view.site'),
'menu' => true,
'dialogs' => require __DIR__ . '/site/dialogs.php',
'dropdowns' => require __DIR__ . '/site/dropdowns.php',
'searches' => require __DIR__ . '/site/searches.php',
'views' => require __DIR__ . '/site/views.php',
];
};

View file

@ -0,0 +1,584 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
use Kirby\Panel\Field;
use Kirby\Panel\Panel;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
$files = require __DIR__ . '/../files/dialogs.php';
return [
// change page position
'page.changeSort' => [
'pattern' => 'pages/(:any)/changeSort',
'load' => function (string $id) {
$page = Find::page($id);
if ($page->blueprint()->num() !== 'default') {
throw new PermissionException([
'key' => 'page.sort.permission',
'data' => [
'slug' => $page->slug()
]
]);
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'position' => Field::pagePosition($page),
],
'submitButton' => I18n::translate('change'),
'value' => [
'position' => $page->panel()->position()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::page($id)->changeStatus(
'listed',
$request->get('position')
);
return [
'event' => 'page.sort',
];
}
],
// change page status
'page.changeStatus' => [
'pattern' => 'pages/(:any)/changeStatus',
'load' => function (string $id) {
$page = Find::page($id);
$blueprint = $page->blueprint();
$status = $page->status();
$states = [];
$position = null;
foreach ($blueprint->status() as $key => $state) {
$states[] = [
'value' => $key,
'text' => $state['label'],
'info' => $state['text'],
];
}
if ($status === 'draft') {
$errors = $page->errors();
// switch to the error dialog if there are
// errors and the draft cannot be published
if (count($errors) > 0) {
return [
'component' => 'k-error-dialog',
'props' => [
'message' => I18n::translate('error.page.changeStatus.incomplete'),
'details' => $errors,
]
];
}
}
$fields = [
'status' => [
'label' => I18n::translate('page.changeStatus.select'),
'type' => 'radio',
'required' => true,
'options' => $states
]
];
if ($blueprint->num() === 'default') {
$fields['position'] = Field::pagePosition($page, [
'when' => [
'status' => 'listed'
]
]);
$position = $page->panel()->position();
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => $fields,
'submitButton' => I18n::translate('change'),
'value' => [
'status' => $status,
'position' => $position
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::page($id)->changeStatus(
$request->get('status'),
$request->get('position')
);
return [
'event' => 'page.changeStatus',
];
}
],
// change template
'page.changeTemplate' => [
'pattern' => 'pages/(:any)/changeTemplate',
'load' => function (string $id) {
$page = Find::page($id);
$blueprints = $page->blueprints();
if (count($blueprints) <= 1) {
throw new Exception([
'key' => 'page.changeTemplate.invalid',
'data' => [
'slug' => $id
]
]);
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'template' => Field::template($blueprints, [
'required' => true
])
],
'submitButton' => I18n::translate('change'),
'value' => [
'template' => $page->intendedTemplate()->name()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::page($id)->changeTemplate($request->get('template'));
return [
'event' => 'page.changeTemplate',
];
}
],
// change title
'page.changeTitle' => [
'pattern' => 'pages/(:any)/changeTitle',
'load' => function (string $id) {
$request = App::instance()->request();
$page = Find::page($id);
$permissions = $page->permissions();
$select = $request->get('select', 'title');
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'title' => Field::title([
'required' => true,
'preselect' => $select === 'title',
'disabled' => $permissions->can('changeTitle') === false
]),
'slug' => Field::slug([
'required' => true,
'preselect' => $select === 'slug',
'path' => $page->parent() ? '/' . $page->parent()->id() . '/' : '/',
'disabled' => $permissions->can('changeSlug') === false,
'wizard' => [
'text' => I18n::translate('page.changeSlug.fromTitle'),
'field' => 'title'
]
])
],
'autofocus' => false,
'submitButton' => I18n::translate('change'),
'value' => [
'title' => $page->title()->value(),
'slug' => $page->slug(),
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$page = Find::page($id);
$title = trim($request->get('title', ''));
$slug = trim($request->get('slug', ''));
// basic input validation before we move on
if (Str::length($title) === 0) {
throw new InvalidArgumentException([
'key' => 'page.changeTitle.empty'
]);
}
if (Str::length($slug) === 0) {
throw new InvalidArgumentException([
'key' => 'page.slug.invalid'
]);
}
// nothing changed
if ($page->title()->value() === $title && $page->slug() === $slug) {
return true;
}
// prepare the response
$response = [
'event' => []
];
// the page title changed
if ($page->title()->value() !== $title) {
$page->changeTitle($title);
$response['event'][] = 'page.changeTitle';
}
// the slug changed
if ($page->slug() !== $slug) {
$newPage = $page->changeSlug($slug);
$response['event'][] = 'page.changeSlug';
$response['dispatch'] = [
'content/move' => [
$oldUrl = $page->panel()->url(true),
$newUrl = $newPage->panel()->url(true)
]
];
// check for a necessary redirect after the slug has changed
if (Panel::referrer() === $oldUrl && $oldUrl !== $newUrl) {
$response['redirect'] = $newUrl;
}
}
return $response;
}
],
// create a new page
'page.create' => [
'pattern' => 'pages/create',
'load' => function () {
$kirby = App::instance();
$request = $kirby->request();
// the parent model for the new page
$parent = $request->get('parent', 'site');
// the view on which the add button is located
// this is important to find the right section
// and provide the correct templates for the new page
$view = $request->get('view', $parent);
// templates will be fetched depending on the
// section settings in the blueprint
$section = $request->get('section');
// this is the parent model
$model = Find::parent($parent);
// this is the view model
// i.e. site if the add button is on
// the dashboard
$view = Find::parent($view);
// available blueprints/templates for the new page
// are always loaded depending on the matching section
// in the view model blueprint
$blueprints = $view->blueprints($section);
// the pre-selected template
$template = $blueprints[0]['name'] ?? $blueprints[0]['value'] ?? null;
$fields = [
'parent' => Field::hidden(),
'title' => Field::title([
'required' => true,
'preselect' => true
]),
'slug' => Field::slug([
'required' => true,
'sync' => 'title',
'path' => empty($model->id()) === false ? '/' . $model->id() . '/' : '/'
]),
'template' => Field::hidden()
];
// only show template field if > 1 templates available
// or when in debug mode
if (count($blueprints) > 1 || $kirby->option('debug') === true) {
$fields['template'] = Field::template($blueprints, [
'required' => true
]);
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => $fields,
'submitButton' => I18n::translate('page.draft.create'),
'value' => [
'parent' => $parent,
'slug' => '',
'template' => $template,
'title' => '',
]
]
];
},
'submit' => function () {
$request = App::instance()->request();
$title = trim($request->get('title', ''));
if (Str::length($title) === 0) {
throw new InvalidArgumentException([
'key' => 'page.changeTitle.empty'
]);
}
$page = Find::parent($request->get('parent', 'site'))->createChild([
'content' => ['title' => $title],
'slug' => $request->get('slug'),
'template' => $request->get('template'),
]);
return [
'event' => 'page.create',
'redirect' => $page->panel()->url(true)
];
}
],
// delete page
'page.delete' => [
'pattern' => 'pages/(:any)/delete',
'load' => function (string $id) {
$page = Find::page($id);
$text = I18n::template('page.delete.confirm', [
'title' => Escape::html($page->title()->value())
]);
if ($page->childrenAndDrafts()->count() > 0) {
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'info' => [
'type' => 'info',
'theme' => 'negative',
'text' => I18n::translate('page.delete.confirm.subpages')
],
'check' => [
'label' => I18n::translate('page.delete.confirm.title'),
'type' => 'text',
'counter' => false
]
],
'size' => 'medium',
'submitButton' => I18n::translate('delete'),
'text' => $text,
'theme' => 'negative',
]
];
}
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => $text
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$page = Find::page($id);
$redirect = false;
$referrer = Panel::referrer();
$url = $page->panel()->url(true);
if (
$page->childrenAndDrafts()->count() > 0 &&
$request->get('check') !== $page->title()->value()
) {
throw new InvalidArgumentException(['key' => 'page.delete.confirm']);
}
$page->delete(true);
// redirect to the parent model URL
// if the dialog has been opened in the page view
if ($referrer === $url) {
$redirect = $page->parentModel()->panel()->url(true);
}
return [
'event' => 'page.delete',
'dispatch' => ['content/remove' => [$url]],
'redirect' => $redirect
];
}
],
// duplicate page
'page.duplicate' => [
'pattern' => 'pages/(:any)/duplicate',
'load' => function (string $id) {
$page = Find::page($id);
$hasChildren = $page->hasChildren();
$hasFiles = $page->hasFiles();
$toggleWidth = '1/' . count(array_filter([$hasChildren, $hasFiles]));
$fields = [
'title' => Field::title([
'required' => true
]),
'slug' => Field::slug([
'required' => true,
'path' => $page->parent() ? '/' . $page->parent()->id() . '/' : '/',
'wizard' => [
'text' => I18n::translate('page.changeSlug.fromTitle'),
'field' => 'title'
]
])
];
if ($hasFiles === true) {
$fields['files'] = [
'label' => I18n::translate('page.duplicate.files'),
'type' => 'toggle',
'required' => true,
'width' => $toggleWidth
];
}
if ($hasChildren === true) {
$fields['children'] = [
'label' => I18n::translate('page.duplicate.pages'),
'type' => 'toggle',
'required' => true,
'width' => $toggleWidth
];
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => $fields,
'submitButton' => I18n::translate('duplicate'),
'value' => [
'children' => false,
'files' => false,
'slug' => $page->slug() . '-' . Str::slug(I18n::translate('page.duplicate.appendix')),
'title' => $page->title() . ' ' . I18n::translate('page.duplicate.appendix')
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$newPage = Find::page($id)->duplicate($request->get('slug'), [
'children' => (bool)$request->get('children'),
'files' => (bool)$request->get('files'),
'title' => (string)$request->get('title'),
]);
return [
'event' => 'page.duplicate',
'redirect' => $newPage->panel()->url(true)
];
}
],
// change filename
'page.file.changeName' => [
'pattern' => '(pages/.*?)/files/(:any)/changeName',
'load' => $files['changeName']['load'],
'submit' => $files['changeName']['submit'],
],
// change sort
'page.file.changeSort' => [
'pattern' => '(pages/.*?)/files/(:any)/changeSort',
'load' => $files['changeSort']['load'],
'submit' => $files['changeSort']['submit'],
],
// delete
'page.file.delete' => [
'pattern' => '(pages/.*?)/files/(:any)/delete',
'load' => $files['delete']['load'],
'submit' => $files['delete']['submit'],
],
// change site title
'site.changeTitle' => [
'pattern' => 'site/changeTitle',
'load' => function () {
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'title' => Field::title([
'required' => true,
'preselect' => true
])
],
'submitButton' => I18n::translate('rename'),
'value' => [
'title' => App::instance()->site()->title()->value()
]
]
];
},
'submit' => function () {
$kirby = App::instance();
$kirby->site()->changeTitle($kirby->request()->get('title'));
return [
'event' => 'site.changeTitle',
];
}
],
// change filename
'site.file.changeName' => [
'pattern' => '(site)/files/(:any)/changeName',
'load' => $files['changeName']['load'],
'submit' => $files['changeName']['submit'],
],
// change sort
'site.file.changeSort' => [
'pattern' => '(site)/files/(:any)/changeSort',
'load' => $files['changeSort']['load'],
'submit' => $files['changeSort']['submit'],
],
// delete
'site.file.delete' => [
'pattern' => '(site)/files/(:any)/delete',
'load' => $files['delete']['load'],
'submit' => $files['delete']['submit'],
],
];

View file

@ -0,0 +1,26 @@
<?php
use Kirby\Panel\Dropdown;
$files = require __DIR__ . '/../files/dropdowns.php';
return [
'changes' => [
'pattern' => 'changes',
'options' => fn () => Dropdown::changes()
],
'page' => [
'pattern' => 'pages/(:any)',
'options' => function (string $path) {
return Find::page($path)->panel()->dropdown();
}
],
'page.file' => [
'pattern' => '(pages/.*?)/files/(:any)',
'options' => $files['file']
],
'site.file' => [
'pattern' => '(site)/files/(:any)',
'options' => $files['file']
]
];

View file

@ -0,0 +1,57 @@
<?php
use Kirby\Cms\App;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
return [
'pages' => [
'label' => I18n::translate('pages'),
'icon' => 'page',
'query' => function (string $query = null) {
$pages = App::instance()->site()
->index(true)
->search($query)
->filter('isReadable', true)
->limit(10);
$results = [];
foreach ($pages as $page) {
$results[] = [
'image' => $page->panel()->image(),
'text' => Escape::html($page->title()->value()),
'link' => $page->panel()->url(true),
'info' => Escape::html($page->id())
];
}
return $results;
}
],
'files' => [
'label' => I18n::translate('files'),
'icon' => 'image',
'query' => function (string $query = null) {
$files = App::instance()->site()
->index(true)
->filter('isReadable', true)
->files()
->search($query)
->limit(10);
$results = [];
foreach ($files as $file) {
$results[] = [
'image' => $file->panel()->image(),
'text' => Escape::html($file->filename()),
'link' => $file->panel()->url(true),
'info' => Escape::html($file->id())
];
}
return $results;
}
]
];

View file

@ -0,0 +1,27 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
return [
'page' => [
'pattern' => 'pages/(:any)',
'action' => fn (string $path) => Find::page($path)->panel()->view()
],
'page.file' => [
'pattern' => 'pages/(:any)/files/(:any)',
'action' => function (string $id, string $filename) {
return Find::file('pages/' . $id, $filename)->panel()->view();
}
],
'site' => [
'pattern' => 'site',
'action' => fn () => App::instance()->site()->panel()->view()
],
'site.file' => [
'pattern' => 'site/files/(:any)',
'action' => function (string $filename) {
return Find::file('site', $filename)->panel()->view();
}
],
];

View file

@ -0,0 +1,13 @@
<?php
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'settings',
'label' => I18n::translate('view.system'),
'menu' => true,
'dialogs' => require __DIR__ . '/system/dialogs.php',
'views' => require __DIR__ . '/system/views.php'
];
};

View file

@ -0,0 +1,86 @@
<?php
use Kirby\Cms\App;
use Kirby\Panel\Field;
use Kirby\Toolkit\I18n;
return [
// license key
'license' => [
'load' => function () {
$license = App::instance()->system()->license();
// @codeCoverageIgnoreStart
// the system is registered but the license
// key is only visible for admins
if ($license === true) {
$license = 'Kirby 3';
}
// @codeCoverageIgnoreEnd
return [
'component' => 'k-form-dialog',
'props' => [
'size' => 'medium',
'fields' => [
'license' => [
'type' => 'info',
'label' => I18n::translate('license'),
'text' => $license ? $license : I18n::translate('license.unregistered.label'),
'theme' => $license ? 'code' : 'negative',
'help' => $license ?
// @codeCoverageIgnoreStart
'<a href="https://hub.getkirby.com">' . I18n::translate('license.manage') . ' &rarr;</a>' :
// @codeCoverageIgnoreEnd
'<a href="https://getkirby.com/buy">' . I18n::translate('license.buy') . ' &rarr;</a>'
]
],
'submitButton' => false,
'cancelButton' => false,
]
];
}
],
// license registration
'registration' => [
'load' => function () {
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'license' => [
'label' => I18n::translate('license.register.label'),
'type' => 'text',
'required' => true,
'counter' => false,
'placeholder' => 'K3-',
'help' => I18n::translate('license.register.help')
],
'email' => Field::email([
'required' => true
])
],
'submitButton' => I18n::translate('license.register'),
'value' => [
'license' => null,
'email' => null
]
]
];
},
'submit' => function () {
// @codeCoverageIgnoreStart
$kirby = App::instance();
$kirby->system()->register(
$kirby->request()->get('license'),
$kirby->request()->get('email')
);
return [
'event' => 'system.register',
'message' => I18n::translate('license.register.success')
];
// @codeCoverageIgnoreEnd
}
],
];

View file

@ -0,0 +1,55 @@
<?php
use Kirby\Cms\App;
return [
'system' => [
'pattern' => 'system',
'action' => function () {
$kirby = App::instance();
$system = $kirby->system();
$license = $system->license();
// @codeCoverageIgnoreStart
if ($license === true) {
// valid license, but user is not admin
$license = 'Kirby 3';
} elseif ($license === false) {
// no valid license
$license = null;
}
// @codeCoverageIgnoreEnd
$plugins = $system->plugins()->values(function ($plugin) {
return [
'author' => $plugin->authorsNames(),
'license' => $plugin->license(),
'name' => [
'text' => $plugin->name(),
'href' => $plugin->link(),
],
'version' => $plugin->version(),
];
});
return [
'component' => 'k-system-view',
'props' => [
'debug' => $kirby->option('debug', false),
'license' => $license,
'plugins' => $plugins,
'php' => phpversion(),
'server' => $system->serverSoftware(),
'https' => $kirby->environment()->https(),
'version' => $kirby->version(),
'urls' => [
'content' => $system->exposedFileUrl('content'),
'git' => $system->exposedFileUrl('git'),
'kirby' => $system->exposedFileUrl('kirby'),
'site' => $system->exposedFileUrl('site')
]
]
];
}
],
];

View file

@ -0,0 +1,16 @@
<?php
use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'users',
'label' => I18n::translate('view.users'),
'search' => 'users',
'menu' => true,
'dialogs' => require __DIR__ . '/users/dialogs.php',
'dropdowns' => require __DIR__ . '/users/dropdowns.php',
'searches' => require __DIR__ . '/users/searches.php',
'views' => require __DIR__ . '/users/views.php'
];
};

View file

@ -0,0 +1,311 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Cms\UserRules;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Panel\Field;
use Kirby\Panel\Panel;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
$files = require __DIR__ . '/../files/dialogs.php';
return [
// create
'user.create' => [
'pattern' => 'users/create',
'load' => function () {
$kirby = App::instance();
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'name' => Field::username(),
'email' => Field::email([
'link' => false,
'required' => true
]),
'password' => Field::password(),
'translation' => Field::translation([
'required' => true
]),
'role' => Field::role([
'required' => true
])
],
'submitButton' => I18n::translate('create'),
'value' => [
'name' => '',
'email' => '',
'password' => '',
'translation' => $kirby->panelLanguage(),
'role' => $kirby->user()->role()->name()
]
]
];
},
'submit' => function () {
$kirby = App::instance();
$kirby->users()->create([
'name' => $kirby->request()->get('name'),
'email' => $kirby->request()->get('email'),
'password' => $kirby->request()->get('password'),
'language' => $kirby->request()->get('translation'),
'role' => $kirby->request()->get('role')
]);
return [
'event' => 'user.create'
];
}
],
// change email
'user.changeEmail' => [
'pattern' => 'users/(:any)/changeEmail',
'load' => function (string $id) {
$user = Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'email' => [
'label' => I18n::translate('email'),
'required' => true,
'type' => 'email',
'preselect' => true
]
],
'submitButton' => I18n::translate('change'),
'value' => [
'email' => $user->email()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::user($id)->changeEmail($request->get('email'));
return [
'event' => 'user.changeEmail'
];
}
],
// change language
'user.changeLanguage' => [
'pattern' => 'users/(:any)/changeLanguage',
'load' => function (string $id) {
$user = Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'translation' => Field::translation(['required' => true])
],
'submitButton' => I18n::translate('change'),
'value' => [
'translation' => $user->language()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::user($id)->changeLanguage($request->get('translation'));
return [
'event' => 'user.changeLanguage',
'reload' => [
'globals' => '$translation'
]
];
}
],
// change name
'user.changeName' => [
'pattern' => 'users/(:any)/changeName',
'load' => function (string $id) {
$user = Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'name' => Field::username([
'preselect' => true
])
],
'submitButton' => I18n::translate('rename'),
'value' => [
'name' => $user->name()->value()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
Find::user($id)->changeName($request->get('name'));
return [
'event' => 'user.changeName'
];
}
],
// change password
'user.changePassword' => [
'pattern' => 'users/(:any)/changePassword',
'load' => function (string $id) {
$user = Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'password' => Field::password([
'label' => I18n::translate('user.changePassword.new'),
]),
'passwordConfirmation' => Field::password([
'label' => I18n::translate('user.changePassword.new.confirm'),
])
],
'submitButton' => I18n::translate('change'),
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$user = Find::user($id);
$password = $request->get('password');
$passwordConfirmation = $request->get('passwordConfirmation');
// validate the password
UserRules::validPassword($user, $password ?? '');
// compare passwords
if ($password !== $passwordConfirmation) {
throw new InvalidArgumentException([
'key' => 'user.password.notSame'
]);
}
// change password if everything's fine
$user->changePassword($password);
return [
'event' => 'user.changePassword'
];
}
],
// change role
'user.changeRole' => [
'pattern' => 'users/(:any)/changeRole',
'load' => function (string $id) {
$user = Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'role' => Field::role([
'label' => I18n::translate('user.changeRole.select'),
'required' => true,
])
],
'submitButton' => I18n::translate('user.changeRole'),
'value' => [
'role' => $user->role()->name()
]
]
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$user = Find::user($id)->changeRole($request->get('role'));
return [
'event' => 'user.changeRole',
'user' => $user->toArray()
];
}
],
// delete
'user.delete' => [
'pattern' => 'users/(:any)/delete',
'load' => function (string $id) {
$user = Find::user($id);
$i18nPrefix = $user->isLoggedIn() ? 'account' : 'user';
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => I18n::template($i18nPrefix . '.delete.confirm', [
'email' => Escape::html($user->email())
])
]
];
},
'submit' => function (string $id) {
$user = Find::user($id);
$redirect = false;
$referrer = Panel::referrer();
$url = $user->panel()->url(true);
$user->delete();
// redirect to the users view
// if the dialog has been opened in the user view
if ($referrer === $url) {
$redirect = '/users';
}
// logout the user if they deleted themselves
if ($user->isLoggedIn()) {
$redirect = '/logout';
}
return [
'event' => 'user.delete',
'dispatch' => ['content/remove' => [$url]],
'redirect' => $redirect
];
}
],
// change file name
'user.file.changeName' => [
'pattern' => '(users/.*?)/files/(:any)/changeName',
'load' => $files['changeName']['load'],
'submit' => $files['changeName']['submit'],
],
// change file sort
'user.file.changeSort' => [
'pattern' => '(users/.*?)/files/(:any)/changeSort',
'load' => $files['changeSort']['load'],
'submit' => $files['changeSort']['submit'],
],
// delete file
'user.file.delete' => [
'pattern' => '(users/.*?)/files/(:any)/delete',
'load' => $files['delete']['load'],
'submit' => $files['delete']['submit'],
]
];

View file

@ -0,0 +1,18 @@
<?php
use Kirby\Cms\Find;
$files = require __DIR__ . '/../files/dropdowns.php';
return [
'user' => [
'pattern' => 'users/(:any)',
'options' => function (string $id) {
return Find::user($id)->panel()->dropdown();
}
],
'user.file' => [
'pattern' => '(users/.*?)/files/(:any)',
'options' => $files['file']
]
];

View file

@ -0,0 +1,27 @@
<?php
use Kirby\Cms\App;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
return [
'users' => [
'label' => I18n::translate('users'),
'icon' => 'users',
'query' => function (string $query = null) {
$users = App::instance()->users()->search($query)->limit(10);
$results = [];
foreach ($users as $user) {
$results[] = [
'image' => $user->panel()->image(),
'text' => Escape::html($user->username()),
'link' => $user->panel()->url(true),
'info' => Escape::html($user->role()->title())
];
}
return $results;
}
]
];

View file

@ -0,0 +1,66 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Toolkit\Escape;
return [
'users' => [
'pattern' => 'users',
'action' => function () {
$kirby = App::instance();
$role = $kirby->request()->get('role');
$roles = $kirby->roles()->toArray(fn ($role) => [
'id' => $role->id(),
'title' => $role->title(),
]);
return [
'component' => 'k-users-view',
'props' => [
'role' => function () use ($kirby, $roles, $role) {
if ($role) {
return $roles[$role] ?? null;
}
},
'roles' => array_values($roles),
'users' => function () use ($kirby, $role) {
$users = $kirby->users();
if (empty($role) === false) {
$users = $users->role($role);
}
$users = $users->paginate([
'limit' => 20,
'page' => $kirby->request()->get('page')
]);
return [
'data' => $users->values(fn ($user) => [
'id' => $user->id(),
'image' => $user->panel()->image(),
'info' => Escape::html($user->role()->title()),
'link' => $user->panel()->url(true),
'text' => Escape::html($user->username())
]),
'pagination' => $users->pagination()->toArray()
];
},
]
];
}
],
'user' => [
'pattern' => 'users/(:any)',
'action' => function (string $id) {
return Find::user($id)->panel()->view();
}
],
'user.file' => [
'pattern' => 'users/(:any)/files/(:any)',
'action' => function (string $id, string $filename) {
return Find::file('users/' . $id, $filename)->panel()->view();
}
],
];

View file

@ -0,0 +1,2 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<pre><code class="language-<?= $block->language()->or('text') ?>"><?= $block->code()->html(false) ?></code></pre>

View file

@ -0,0 +1,59 @@
name: field.blocks.code.name
icon: code
wysiwyg: true
preview: code
fields:
code:
label: field.blocks.code.name
type: textarea
placeholder: field.blocks.code.placeholder
buttons: false
font: monospace
language:
label: field.blocks.code.language
type: select
default: text
options:
bash: Bash
basic: BASIC
c: C
clojure: Clojure
cpp: C++
csharp: C#
css: CSS
diff: Diff
elixir: Elixir
elm: Elm
erlang: Erlang
go: Go
graphql: GraphQL
haskell: Haskell
html: HTML
java: Java
js: JavaScript
json: JSON
latext: LaTeX
less: Less
lisp: Lisp
lua: Lua
makefile: Makefile
markdown: Markdown
markup: Markup
objectivec: Objective-C
pascal: Pascal
perl: Perl
php: PHP
text: Plain Text
python: Python
r: R
ruby: Ruby
rust: Rust
sass: Sass
scss: SCSS
shell: Shell
sql: SQL
swift: Swift
typescript: TypeScript
vbnet: VB.net
xml: XML
yaml: YAML

View file

@ -0,0 +1,10 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<figure>
<ul>
<?php foreach ($block->images()->toFiles() as $image): ?>
<li>
<?= $image ?>
</li>
<?php endforeach ?>
</ul>
</figure>

View file

@ -0,0 +1,16 @@
name: field.blocks.gallery.name
icon: dashboard
preview: gallery
fields:
images:
label: field.blocks.gallery.images.label
type: files
query: model.images
multiple: true
layout: cards
size: tiny
empty: field.blocks.gallery.images.empty
uploads:
template: blocks/image
image:
ratio: 1/1

View file

@ -0,0 +1,2 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<<?= $level = $block->level()->or('h2') ?>><?= $block->text() ?></<?= $level ?>>

View file

@ -0,0 +1,24 @@
name: field.blocks.heading.name
icon: title
wysiwyg: true
preview: heading
fields:
level:
label: field.blocks.heading.level
type: select
empty: false
default: "h2"
width: 1/6
options:
- h1
- h2
- h3
- h4
- h5
- h6
text:
label: field.blocks.heading.text
type: writer
inline: true
width: 5/6
placeholder: field.blocks.heading.placeholder

View file

@ -0,0 +1,35 @@
<?php
/** @var \Kirby\Cms\Block $block */
$alt = $block->alt();
$caption = $block->caption();
$crop = $block->crop()->isTrue();
$link = $block->link();
$ratio = $block->ratio()->or('auto');
$src = null;
if ($block->location() == 'web') {
$src = $block->src()->esc();
} elseif ($image = $block->image()->toFile()) {
$alt = $alt ?? $image->alt();
$src = $image->url();
}
?>
<?php if ($src): ?>
<figure<?= Html::attr(['data-ratio' => $ratio, 'data-crop' => $crop], null, ' ') ?>>
<?php if ($link->isNotEmpty()): ?>
<a href="<?= Str::esc($link->toUrl()) ?>">
<img src="<?= $src ?>" alt="<?= $alt->esc() ?>">
</a>
<?php else: ?>
<img src="<?= $src ?>" alt="<?= $alt->esc() ?>">
<?php endif ?>
<?php if ($caption->isNotEmpty()): ?>
<figcaption>
<?= $caption ?>
</figcaption>
<?php endif ?>
</figure>
<?php endif ?>

View file

@ -0,0 +1,60 @@
name: field.blocks.image.name
icon: image
preview: image
fields:
location:
label: field.blocks.image.location
type: radio
columns: 2
default: "kirby"
options:
kirby: Kirby
web: Web
image:
label: field.blocks.image.name
type: files
query: model.images
multiple: false
image:
back: black
uploads:
template: blocks/image
when:
location: kirby
src:
label: field.blocks.image.url
type: url
when:
location: web
alt:
label: field.blocks.image.alt
type: text
icon: title
caption:
label: field.blocks.image.caption
type: writer
icon: text
inline: true
link:
label: field.blocks.image.link
type: text
icon: url
ratio:
label: field.blocks.image.ratio
type: select
placeholder: Auto
width: 1/2
options:
1/1: "1:1"
16/9: "16:9"
10/8: "10:8"
21/9: "21:9"
7/5: "7:5"
4/3: "4:3"
5/3: "5:3"
3/2: "3:2"
3/1: "3:1"
crop:
label: field.blocks.image.crop
type: toggle
width: 1/2

View file

@ -0,0 +1 @@
<hr />

View file

@ -0,0 +1,4 @@
name: field.blocks.line.name
icon: divider
preview: line
wysiwyg: true

View file

@ -0,0 +1,2 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<?= $block->text();

View file

@ -0,0 +1,8 @@
name: field.blocks.list.name
icon: list-bullet
wysiwyg: true
preview: list
fields:
text:
label: field.blocks.list.name
type: list

View file

@ -0,0 +1,2 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<?= $block->text()->kt();

View file

@ -0,0 +1,11 @@
name: field.blocks.markdown.name
icon: markdown
preview: markdown
wysiwyg: true
fields:
text:
label: field.blocks.markdown.label
placeholder: field.blocks.markdown.placeholder
type: textarea
buttons: false
font: monospace

View file

@ -0,0 +1,9 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<blockquote>
<?= $block->text() ?>
<?php if ($block->citation()->isNotEmpty()): ?>
<footer>
<?= $block->citation() ?>
</footer>
<?php endif ?>
</blockquote>

View file

@ -0,0 +1,17 @@
name: field.blocks.quote.name
icon: quote
wysiwyg: true
preview: quote
fields:
text:
label: field.blocks.quote.text.label
placeholder: field.blocks.quote.text.placeholder
type: writer
inline: true
icon: quote
citation:
label: field.blocks.quote.citation.label
placeholder: field.blocks.quote.citation.placeholder
type: writer
inline: true
icon: user

View file

@ -0,0 +1,3 @@
name: Table
icon: menu
preview: table

View file

@ -0,0 +1,2 @@
<?php /** @var \Kirby\Cms\Block $block */ ?>
<?= $block->text();

View file

@ -0,0 +1,9 @@
name: field.blocks.text.name
icon: text
wysiwyg: true
preview: text
fields:
text:
type: writer
nodes: false
placeholder: field.blocks.text.placeholder

View file

@ -0,0 +1,13 @@
<?php
use Kirby\Cms\Html;
/** @var \Kirby\Cms\Block $block */
?>
<?php if ($video = Html::video($block->url())): ?>
<figure>
<?= $video ?>
<?php if ($block->caption()->isNotEmpty()): ?>
<figcaption><?= $block->caption() ?></figcaption>
<?php endif ?>
</figure>
<?php endif ?>

View file

@ -0,0 +1,12 @@
name: field.blocks.video.name
icon: video
preview: video
fields:
url:
label: field.blocks.video.url.label
type: url
placeholder: field.blocks.video.url.placeholder
caption:
label: field.blocks.video.caption
type: writer
inline: true

View file

@ -0,0 +1,56 @@
name: Code
icon: code
fields:
code:
label: Code
type: textarea
buttons: false
font: monospace
language:
label: Language
type: select
default: text
options:
bash: Bash
basic: BASIC
c: C
clojure: Clojure
cpp: C++
csharp: C#
css: CSS
diff: Diff
elixir: Elixir
elm: Elm
erlang: Erlang
go: Go
graphql: GraphQL
haskell: Haskell
html: HTML
java: Java
js: JavaScript
json: JSON
latext: LaTeX
less: Less
lisp: Lisp
lua: Lua
makefile: Makefile
markdown: Markdown
markup: Markup
objectivec: Objective-C
pascal: Pascal
perl: Perl
php: PHP
text: Plain Text
python: Python
r: R
ruby: Ruby
rust: Rust
sass: Sass
scss: SCSS
shell: Shell
sql: SQL
swift: Swift
typescript: TypeScript
vbnet: VB.net
xml: XML
yaml: YAML

View file

@ -0,0 +1,20 @@
icon: title
fields:
text:
type: text
level:
type: select
width: 1/2
empty: false
default: "2"
options:
- value: "1"
text: Heading 1
- value: "2"
text: Heading 2
- value: "3"
text: Heading 3
id:
type: text
label: ID
width: 1/2

View file

@ -0,0 +1,16 @@
name: Image
icon: image
fields:
image:
type: files
multiple: false
alt:
type: text
icon: title
caption:
type: writer
inline: true
icon: text
link:
type: text
icon: url

View file

@ -0,0 +1,12 @@
name: Quote
icon: quote
fields:
text:
label: Quote Text
type: writer
inline: true
citation:
label: Citation
type: writer
inline: true
placeholder: by …

View file

@ -0,0 +1,25 @@
name: Table
icon: menu
fields:
rows:
label: Menu
type: structure
columns:
dish: true
description: true
price:
before:
width: 1/4
align: right
fields:
dish:
label: Dish
type: text
description:
label: Description
type: text
price:
label: Price
type: number
before:
step: 0.01

View file

@ -0,0 +1,5 @@
name: Text
icon: text
fields:
text:
type: writer

View file

@ -0,0 +1,8 @@
name: Video
icon: video
label: "{{ url }}"
fields:
url:
type: url
caption:
type: writer

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

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

@ -0,0 +1,415 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
use Kirby\Cms\FileVersion;
use Kirby\Cms\Helpers;
use Kirby\Cms\Template;
use Kirby\Data\Data;
use Kirby\Email\PHPMailer as Emailer;
use Kirby\Filesystem\F;
use Kirby\Filesystem\Filename;
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\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' => fn (App $kirby, string $url, $options = null): string => $url,
/**
* Object and variable dumper
* to help with debugging.
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param mixed $variable
* @param bool $echo
* @return string
*
* @deprecated 3.7.0 Disable `dump()` via `KIRBY_HELPER_DUMP` instead and create your own function
* @todo move to `Helpers::dump()`, remove component in 3.8.0
*/
'dump' => function (App $kirby, $variable, bool $echo = true) {
if ($kirby->environment()->cli() === true) {
$output = print_r($variable, true) . PHP_EOL;
} else {
$output = '<pre>' . print_r($variable, true) . '</pre>';
}
if ($echo === true) {
echo $output;
}
return $output;
},
/**
* Add your own email provider
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param array $props
* @param bool $debug
*/
'email' => function (App $kirby, array $props = [], bool $debug = false) {
return new Emailer($props, $debug);
},
/**
* 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\Filesystem\Asset $file The file object
* @param array $options All thumb options (width, height, crop, blur, grayscale)
* @return \Kirby\Cms\File|\Kirby\Cms\FileVersion|\Kirby\Filesystem\Asset
*/
'file::version' => function (App $kirby, $file, array $options = []) {
// if file is not resizable, return
if ($file->isResizable() === false) {
return $file;
}
// create url and root
$mediaRoot = dirname($file->mediaRoot());
$template = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}';
$thumbRoot = (new Filename($file->root(), $template, $options))->toString();
$thumbName = basename($thumbRoot);
// check if the thumb already exists
if (file_exists($thumbRoot) === false) {
// if not, create job file
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
try {
Data::write($job, array_merge($options, [
'filename' => $file->filename()
]));
} catch (Throwable $e) {
// if thumb doesn't exist yet and job file cannot
// be created, return
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' => fn (App $kirby, string $url, $options = null): string => $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 (deprecated: set via $options['inline'] instead)
* @return string
* @todo remove $inline parameter in in 3.8.0
*/
'markdown' => function (App $kirby, string $text = null, array $options = [], bool $inline = false): string {
static $markdown;
static $config;
// warning for deprecated fourth parameter
if (func_num_args() === 4 && isset($options['inline']) === false) {
// @codeCoverageIgnoreStart
Helpers::deprecated('markdown component: the $inline parameter is deprecated and will be removed in Kirby 3.8.0. Use $options[\'inline\'] instead.');
// @codeCoverageIgnoreEnd
}
// support for the deprecated fourth argument
$options['inline'] ??= $inline;
// 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, $options['inline'] ?? false);
},
/**
* 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 = Str::lower($query);
$exactQuery = $options['words'] ? '(\b' . preg_quote($query) . '\b)' : preg_quote($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, $exactQuery) {
$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 = Str::lower($value);
// check for exact matches
if ($lowerQuery == $lowerValue) {
$item->searchScore += 16 * $score;
$item->searchHits += 1;
// check for exact beginning matches
} elseif ($options['words'] === false && Str::startsWith($lowerValue, $lowerQuery) === true) {
$item->searchScore += 8 * $score;
$item->searchHits += 1;
// check for exact query matches
} elseif ($matches = preg_match_all('!' . $exactQuery . '!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;
});
return $results->sort('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 Root of the original file
* @param string $dst Template string for 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(
$kirby->option('thumbs.driver', 'gd'),
$kirby->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|null $path URL path
* @param array|string|null $options Array of options for the Uri class
* @return string
*/
'url' => function (App $kirby, string $path = null, $options = 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 ($parts[0] ?? null) {
$page = $kirby->site()->find($parts[0]);
} else {
$page = $kirby->site()->page();
}
if ($page) {
$path = $page->url($language);
if (isset($parts[1]) === true) {
$path .= '#' . $parts[1];
}
}
}
// keep relative urls
if (
$path !== null &&
(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();
},
];

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,154 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Form\Field;
use Kirby\Toolkit\Date;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
return [
'mixins' => ['datetime'],
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Activate/deactivate the dropdown calendar
*/
'calendar' => function (bool $calendar = true) {
return $calendar;
},
/**
* Default date when a new page/file/user gets created
*/
'default' => function (string $default = null): string {
return $this->toDatetime($default) ?? '';
},
/**
* Custom format (dayjs tokens: `DD`, `MM`, `YYYY`) that is
* used to display the field in the Panel
*/
'display' => function ($display = 'YYYY-MM-DD') {
return I18n::translate($display, $display);
},
/**
* Changes the calendar icon to something custom
*/
'icon' => function (string $icon = 'calendar') {
return $icon;
},
/**
* Latest date, which can be selected/saved (Y-m-d)
*/
'max' => function (string $max = null): ?string {
return Date::optional($max);
},
/**
* Earliest date, which can be selected/saved (Y-m-d)
*/
'min' => function (string $min = null): ?string {
return Date::optional($min);
},
/**
* Round to the nearest: sub-options for `unit` (day) and `size` (1)
*/
'step' => function ($step = null) {
return $step;
},
/**
* 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' => [
'display' => function () {
if ($this->display) {
return Str::upper($this->display);
}
},
'format' => function () {
return $this->props['format'] ?? ($this->time === false ? 'Y-m-d' : 'Y-m-d H:i:s');
},
'time' => function () {
if ($this->time === false) {
return false;
}
$props = is_array($this->time) ? $this->time : [];
$props['model'] = $this->model();
$field = new Field('time', $props);
return $field->toArray();
},
'step' => function () {
if ($this->time === false || empty($this->time['step']) === true) {
return Date::stepConfig($this->step, [
'size' => 1,
'unit' => 'day'
]);
}
return Date::stepConfig($this->time['step'], [
'size' => 5,
'unit' => 'minute'
]);
},
'value' => function (): string {
return $this->toDatetime($this->value) ?? '';
},
],
'validations' => [
'date',
'minMax' => function ($value) {
if (!$value = Date::optional($value)) {
return true;
}
$min = Date::optional($this->min);
$max = Date::optional($this->max);
$format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i';
if ($min && $max && $value->isBetween($min, $max) === false) {
throw new Exception([
'key' => 'validation.date.between',
'data' => [
'min' => $min->format($format),
'max' => $min->format($format)
]
]);
} elseif ($min && $value->isMin($min) === false) {
throw new Exception([
'key' => 'validation.date.after',
'data' => [
'date' => $min->format($format),
]
]);
} elseif ($max && $value->isMax($max) === false) {
throw new Exception([
'key' => 'validation.date.before',
'data' => [
'date' => $max->format($format),
]
]);
}
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,131 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => [
'filepicker',
'layout',
'min',
'picker',
'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;
},
'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->panel()->pickerData([
'image' => $this->image,
'info' => $this->info ?? false,
'layout' => $this->layout,
'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(),
'layout' => $field->layout(),
'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();
// move_uploaded_file() not working with unit test
// @codeCoverageIgnoreStart
return $field->upload($this, $uploads, function ($file, $parent) use ($field) {
return $file->panel()->pickerData([
'image' => $field->image(),
'info' => $field->info(),
'layout' => $field->layout(),
'model' => $field->model(),
'text' => $field->text(),
]);
});
// @codeCoverageIgnoreEnd
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'uuid');
},
'validations' => [
'max',
'min'
]
];

View file

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

View file

@ -0,0 +1,26 @@
<?php
return [
'save' => false,
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => 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,44 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* Text to be displayed
*/
'text' => function ($value = null) {
return I18n::translate($value, $value);
},
/**
* Change the design of the info box
*/
'theme' => function (string $theme = null) {
return $theme;
}
],
'computed' => [
'text' => function () {
if ($text = $this->text) {
$text = $this->model()->toSafeString($text);
$text = $this->kirby()->kirbytext($text);
return $text;
}
}
],
'save' => false,
];

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