2022-06-17 17:51:59 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Kirby\Cms;
|
|
|
|
|
|
|
|
use Kirby\Exception\InvalidArgumentException;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The PagePicker class helps to
|
|
|
|
* fetch the right pages and the parent
|
|
|
|
* model for the API calls for the
|
|
|
|
* page picker component in the panel.
|
|
|
|
*
|
|
|
|
* @package Kirby Cms
|
|
|
|
* @author Bastian Allgeier <bastian@getkirby.com>
|
|
|
|
* @link https://getkirby.com
|
|
|
|
* @copyright Bastian Allgeier
|
|
|
|
* @license https://getkirby.com/license
|
|
|
|
*/
|
|
|
|
class PagePicker extends Picker
|
|
|
|
{
|
2025-04-21 18:57:21 +02:00
|
|
|
// TODO: null only due to our Properties setters,
|
|
|
|
// remove once our implementation is better
|
|
|
|
protected Pages|null $items = null;
|
|
|
|
protected Pages|null $itemsForQuery = null;
|
|
|
|
protected Page|Site|null $parent = null;
|
2022-08-31 15:02:43 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extends the basic defaults
|
|
|
|
*/
|
|
|
|
public function defaults(): array
|
|
|
|
{
|
|
|
|
return array_merge(parent::defaults(), [
|
|
|
|
// Page ID of the selected parent. Used to navigate
|
|
|
|
'parent' => null,
|
|
|
|
// enable/disable subpage navigation
|
|
|
|
'subpages' => true,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the parent model object that
|
|
|
|
* is currently selected in the page picker.
|
|
|
|
* It normally starts at the site, but can
|
|
|
|
* also be any subpage. When a query is given
|
|
|
|
* and subpage navigation is deactivated,
|
|
|
|
* there will be no model available at all.
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function model(): Page|Site|null
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
// no subpages navigation = no model
|
|
|
|
if ($this->options['subpages'] === false) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the model for queries is a bit more tricky to find
|
|
|
|
if (empty($this->options['query']) === false) {
|
|
|
|
return $this->modelForQuery();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->parent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a model object for the given
|
|
|
|
* query, depending on the parent and subpages
|
|
|
|
* options.
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function modelForQuery(): Page|Site|null
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
if ($this->options['subpages'] === true && empty($this->options['parent']) === false) {
|
|
|
|
return $this->parent();
|
|
|
|
}
|
|
|
|
|
2022-12-19 14:56:05 +01:00
|
|
|
return $this->items()?->parent();
|
2022-08-31 15:02:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns basic information about the
|
|
|
|
* parent model that is currently selected
|
|
|
|
* in the page picker.
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function modelToArray(Page|Site|null $model = null): array|null
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
if ($model === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the selected model is the site. there's nothing above
|
2022-12-19 14:56:05 +01:00
|
|
|
if ($model instanceof Site) {
|
2022-08-31 15:02:43 +02:00
|
|
|
return [
|
|
|
|
'id' => null,
|
|
|
|
'parent' => null,
|
|
|
|
'title' => $model->title()->value()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// the top-most page has been reached
|
|
|
|
// the missing id indicates that there's nothing above
|
|
|
|
if ($model->id() === $this->start()->id()) {
|
|
|
|
return [
|
|
|
|
'id' => null,
|
|
|
|
'parent' => null,
|
|
|
|
'title' => $model->title()->value()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// the model is a regular page
|
|
|
|
return [
|
|
|
|
'id' => $model->id(),
|
|
|
|
'parent' => $model->parentModel()->id(),
|
|
|
|
'title' => $model->title()->value()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search all pages for the picker
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function items(): Pages|null
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
// cache
|
|
|
|
if ($this->items !== null) {
|
|
|
|
return $this->items;
|
|
|
|
}
|
|
|
|
|
|
|
|
// no query? simple parent-based search for pages
|
|
|
|
if (empty($this->options['query']) === true) {
|
|
|
|
$items = $this->itemsForParent();
|
|
|
|
|
2025-04-21 18:57:21 +02:00
|
|
|
// when subpage navigation is enabled, a parent
|
|
|
|
// might be passed in addition to the query.
|
|
|
|
// The parent then takes priority.
|
2022-08-31 15:02:43 +02:00
|
|
|
} elseif ($this->options['subpages'] === true && empty($this->options['parent']) === false) {
|
|
|
|
$items = $this->itemsForParent();
|
|
|
|
|
2025-04-21 18:57:21 +02:00
|
|
|
// search by query
|
2022-08-31 15:02:43 +02:00
|
|
|
} else {
|
|
|
|
$items = $this->itemsForQuery();
|
|
|
|
}
|
|
|
|
|
2025-04-21 18:57:21 +02:00
|
|
|
// filter protected and hidden pages
|
|
|
|
$items = $items->filter('isListable', true);
|
2022-08-31 15:02:43 +02:00
|
|
|
|
|
|
|
// search
|
|
|
|
$items = $this->search($items);
|
|
|
|
|
|
|
|
// paginate the result
|
|
|
|
return $this->items = $this->paginate($items);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search for pages by parent
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function itemsForParent(): Pages
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
return $this->parent()->children();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search for pages by query string
|
|
|
|
*
|
|
|
|
* @throws \Kirby\Exception\InvalidArgumentException
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function itemsForQuery(): Pages
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
// cache
|
|
|
|
if ($this->itemsForQuery !== null) {
|
|
|
|
return $this->itemsForQuery;
|
|
|
|
}
|
|
|
|
|
|
|
|
$model = $this->options['model'];
|
|
|
|
$items = $model->query($this->options['query']);
|
|
|
|
|
|
|
|
// help mitigate some typical query usage issues
|
|
|
|
// by converting site and page objects to proper
|
|
|
|
// pages by returning their children
|
2022-12-19 14:56:05 +01:00
|
|
|
$items = match (true) {
|
|
|
|
$items instanceof Site,
|
|
|
|
$items instanceof Page => $items->children(),
|
|
|
|
$items instanceof Pages => $items,
|
|
|
|
|
|
|
|
default => throw new InvalidArgumentException('Your query must return a set of pages')
|
|
|
|
};
|
2022-08-31 15:02:43 +02:00
|
|
|
|
|
|
|
return $this->itemsForQuery = $items;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the parent model.
|
|
|
|
* The model will be used to fetch
|
|
|
|
* subpages unless there's a specific
|
|
|
|
* query to find pages instead.
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function parent(): Page|Site
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
2025-04-21 18:57:21 +02:00
|
|
|
return $this->parent ??= $this->kirby->page($this->options['parent']) ?? $this->site;
|
2022-08-31 15:02:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates the top-most model (page or site)
|
|
|
|
* that can be accessed when navigating
|
|
|
|
* through pages.
|
|
|
|
*/
|
2025-04-21 18:57:21 +02:00
|
|
|
public function start(): Page|Site
|
2022-08-31 15:02:43 +02:00
|
|
|
{
|
|
|
|
if (empty($this->options['query']) === false) {
|
2022-12-19 14:56:05 +01:00
|
|
|
return $this->itemsForQuery()?->parent() ?? $this->site;
|
2022-08-31 15:02:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->site;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an associative array
|
|
|
|
* with all information for the picker.
|
|
|
|
* This will be passed directly to the API.
|
|
|
|
*/
|
|
|
|
public function toArray(): array
|
|
|
|
{
|
|
|
|
$array = parent::toArray();
|
|
|
|
$array['model'] = $this->modelToArray($this->model());
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
}
|
2022-06-17 17:51:59 +02:00
|
|
|
}
|