Initial commit
This commit is contained in:
commit
08a8a71c55
631 changed files with 139902 additions and 0 deletions
389
public/kirby/src/Panel/PageCreateDialog.php
Normal file
389
public/kirby/src/Panel/PageCreateDialog.php
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\PageBlueprint;
|
||||
use Kirby\Cms\PageRules;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* Manages the Panel dialog to create new pages
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class PageCreateDialog
|
||||
{
|
||||
protected PageBlueprint $blueprint;
|
||||
protected Page $model;
|
||||
protected Page|Site $parent;
|
||||
protected string $parentId;
|
||||
protected string|null $sectionId;
|
||||
protected string|null $slug;
|
||||
protected string|null $template;
|
||||
protected string|null $title;
|
||||
protected Page|Site|User|File $view;
|
||||
protected string|null $viewId;
|
||||
|
||||
public static array $fieldTypes = [
|
||||
'checkboxes',
|
||||
'date',
|
||||
'email',
|
||||
'info',
|
||||
'line',
|
||||
'link',
|
||||
'list',
|
||||
'number',
|
||||
'multiselect',
|
||||
'radio',
|
||||
'range',
|
||||
'select',
|
||||
'slug',
|
||||
'tags',
|
||||
'tel',
|
||||
'text',
|
||||
'toggles',
|
||||
'time',
|
||||
'url'
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
string|null $parentId,
|
||||
string|null $sectionId,
|
||||
string|null $template,
|
||||
string|null $viewId,
|
||||
|
||||
// optional
|
||||
string|null $slug = null,
|
||||
string|null $title = null,
|
||||
) {
|
||||
$this->parentId = $parentId ?? 'site';
|
||||
$this->parent = Find::parent($this->parentId);
|
||||
$this->sectionId = $sectionId;
|
||||
$this->slug = $slug;
|
||||
$this->template = $template;
|
||||
$this->title = $title;
|
||||
$this->viewId = $viewId;
|
||||
$this->view = Find::parent($this->viewId ?? $this->parentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blueprint settings for the new page
|
||||
*/
|
||||
public function blueprint(): PageBlueprint
|
||||
{
|
||||
// create a temporary page object
|
||||
return $this->blueprint ??= $this->model()->blueprint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all blueprints for the parent view
|
||||
*/
|
||||
public function blueprints(): array
|
||||
{
|
||||
return A::map(
|
||||
$this->view->blueprints($this->sectionId),
|
||||
function ($blueprint) {
|
||||
$blueprint['name'] ??= $blueprint['value'] ?? null;
|
||||
return $blueprint;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* All the default fields for the dialog
|
||||
*/
|
||||
public function coreFields(): array
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
$title = $this->blueprint()->create()['title'] ?? null;
|
||||
$slug = $this->blueprint()->create()['slug'] ?? null;
|
||||
|
||||
if ($title === false || $slug === false) {
|
||||
throw new InvalidArgumentException('Page create dialog: title and slug must not be false');
|
||||
}
|
||||
|
||||
// title field
|
||||
if ($title === null || is_array($title) === true) {
|
||||
$label = $title['label'] ?? 'title';
|
||||
$fields['title'] = Field::title([
|
||||
...$title ?? [],
|
||||
'label' => I18n::translate($label, $label),
|
||||
'required' => true,
|
||||
'preselect' => true
|
||||
]);
|
||||
}
|
||||
|
||||
// slug field
|
||||
if ($slug === null) {
|
||||
$fields['slug'] = Field::slug([
|
||||
'required' => true,
|
||||
'sync' => 'title',
|
||||
'path' => $this->parent instanceof Page ? '/' . $this->parent->id() . '/' : '/'
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
...$fields,
|
||||
'parent' => Field::hidden(),
|
||||
'section' => Field::hidden(),
|
||||
'template' => Field::hidden(),
|
||||
'view' => Field::hidden(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads custom fields for the page type
|
||||
*/
|
||||
public function customFields(): array
|
||||
{
|
||||
$custom = [];
|
||||
$ignore = ['title', 'slug', 'parent', 'template'];
|
||||
$blueprint = $this->blueprint();
|
||||
$fields = $blueprint->fields();
|
||||
|
||||
foreach ($blueprint->create()['fields'] ?? [] as $name) {
|
||||
if (!$field = ($fields[$name] ?? null)) {
|
||||
throw new InvalidArgumentException('Unknown field "' . $name . '" in create dialog');
|
||||
}
|
||||
|
||||
if (in_array($field['type'], static::$fieldTypes) === false) {
|
||||
throw new InvalidArgumentException('Field type "' . $field['type'] . '" not supported in create dialog');
|
||||
}
|
||||
|
||||
if (in_array($name, $ignore) === true) {
|
||||
throw new InvalidArgumentException('Field name "' . $name . '" not allowed as custom field in create dialog');
|
||||
}
|
||||
|
||||
// switch all fields to 1/1
|
||||
$field['width'] = '1/1';
|
||||
|
||||
// add the field to the form
|
||||
$custom[$name] = $field;
|
||||
}
|
||||
|
||||
// create form so that field props, options etc.
|
||||
// can be properly resolved
|
||||
$form = new Form([
|
||||
'fields' => $custom,
|
||||
'model' => $this->model(),
|
||||
'strict' => true
|
||||
]);
|
||||
|
||||
return $form->fields()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the fields for the dialog
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
...$this->coreFields(),
|
||||
...$this->customFields()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides all the props for the
|
||||
* dialog, including the fields and
|
||||
* initial values
|
||||
*/
|
||||
public function load(): array
|
||||
{
|
||||
$blueprints = $this->blueprints();
|
||||
|
||||
$this->template ??= $blueprints[0]['name'];
|
||||
|
||||
$status = $this->blueprint()->create()['status'] ?? 'draft';
|
||||
$status = $this->blueprint()->status()[$status]['label'] ?? null;
|
||||
$status ??= I18n::translate('page.status.' . $status);
|
||||
|
||||
$fields = $this->fields();
|
||||
$visible = array_filter(
|
||||
$fields,
|
||||
fn ($field) => ($field['hidden'] ?? null) !== true
|
||||
);
|
||||
|
||||
// immediately submit the dialog if there is no editable field
|
||||
if (count($visible) === 0 && count($blueprints) < 2) {
|
||||
$input = $this->value();
|
||||
$response = $this->submit($input);
|
||||
$response['redirect'] ??= $this->parent->panel()->url(true);
|
||||
Panel::go($response['redirect']);
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-page-create-dialog',
|
||||
'props' => [
|
||||
'blueprints' => $blueprints,
|
||||
'fields' => $fields,
|
||||
'submitButton' => I18n::template('page.create', [
|
||||
'status' => $status
|
||||
]),
|
||||
'template' => $this->template,
|
||||
'value' => $this->value()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary model for the page to
|
||||
* be created, used to properly render
|
||||
* the blueprint for fields
|
||||
*/
|
||||
public function model(): Page
|
||||
{
|
||||
return $this->model ??= Page::factory([
|
||||
'slug' => 'new',
|
||||
'template' => $this->template,
|
||||
'model' => $this->template,
|
||||
'parent' => $this->parent instanceof Page ? $this->parent : null
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates values for title and slug
|
||||
* from template strings from the blueprint
|
||||
*/
|
||||
public function resolveFieldTemplates(array $input): array
|
||||
{
|
||||
$title = $this->blueprint()->create()['title'] ?? null;
|
||||
$slug = $this->blueprint()->create()['slug'] ?? null;
|
||||
|
||||
// create temporary page object
|
||||
// to resolve the template strings
|
||||
$page = new Page([
|
||||
'slug' => 'tmp',
|
||||
'template' => $this->template,
|
||||
'parent' => $this->model(),
|
||||
'content' => $input
|
||||
]);
|
||||
|
||||
if (is_string($title)) {
|
||||
$input['title'] = $page->toSafeString($title);
|
||||
}
|
||||
|
||||
if (is_string($slug)) {
|
||||
$input['slug'] = $page->toSafeString($slug);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and cleans up the input data
|
||||
*/
|
||||
public function sanitize(array $input): array
|
||||
{
|
||||
$input['title'] ??= $this->title ?? '';
|
||||
$input['slug'] ??= $this->slug ?? '';
|
||||
|
||||
$input = $this->resolveFieldTemplates($input);
|
||||
$content = ['title' => trim($input['title'])];
|
||||
|
||||
foreach ($this->customFields() as $name => $field) {
|
||||
$content[$name] = $input[$name] ?? null;
|
||||
}
|
||||
|
||||
// create temporary form to sanitize the input
|
||||
// and add default values
|
||||
$form = Form::for($this->model(), ['values' => $content]);
|
||||
|
||||
return [
|
||||
'content' => $form->strings(true),
|
||||
'slug' => $input['slug'],
|
||||
'template' => $this->template,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the dialog form and creates the new page
|
||||
*/
|
||||
public function submit(array $input): array
|
||||
{
|
||||
$input = $this->sanitize($input);
|
||||
$status = $this->blueprint()->create()['status'] ?? 'draft';
|
||||
|
||||
// validate the input before creating the page
|
||||
$this->validate($input, $status);
|
||||
|
||||
$page = $this->parent->createChild($input);
|
||||
|
||||
if ($status !== 'draft') {
|
||||
// grant all permissions as the status is set in the blueprint and
|
||||
// should not be treated as if the user would try to change it
|
||||
$page->kirby()->impersonate(
|
||||
'kirby',
|
||||
fn () => $page->changeStatus($status)
|
||||
);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'event' => 'page.create'
|
||||
];
|
||||
|
||||
// add redirect, if not explicitly disabled
|
||||
if (($this->blueprint()->create()['redirect'] ?? null) !== false) {
|
||||
$payload['redirect'] = $page->panel()->url(true);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function validate(array $input, string $status = 'draft'): bool
|
||||
{
|
||||
// basic validation
|
||||
PageRules::validateTitleLength($input['content']['title']);
|
||||
PageRules::validateSlugLength($input['slug']);
|
||||
|
||||
// if the page is supposed to be published directly,
|
||||
// ensure that all field validations are met
|
||||
if ($status !== 'draft') {
|
||||
// create temporary form to validate the input
|
||||
$form = Form::for($this->model(), ['values' => $input['content']]);
|
||||
|
||||
if ($form->isInvalid() === true) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeStatus.incomplete'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function value(): array
|
||||
{
|
||||
$value = [
|
||||
'parent' => $this->parentId,
|
||||
'section' => $this->sectionId,
|
||||
'slug' => $this->slug ?? '',
|
||||
'template' => $this->template,
|
||||
'title' => $this->title ?? '',
|
||||
'view' => $this->viewId,
|
||||
];
|
||||
|
||||
// add default values for custom fields
|
||||
foreach ($this->customFields() as $name => $field) {
|
||||
if ($default = $field['default'] ?? null) {
|
||||
$value[$name] = $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue