* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ class PagePicker extends Picker { /** * @var \Kirby\Cms\Pages */ protected $items; /** * @var \Kirby\Cms\Pages */ protected $itemsForQuery; /** * @var \Kirby\Cms\Page|\Kirby\Cms\Site|null */ protected $parent; /** * Extends the basic defaults * * @return array */ 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. * * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null */ public function model() { // 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. * * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null */ public function modelForQuery() { if ($this->options['subpages'] === true && empty($this->options['parent']) === false) { return $this->parent(); } if ($items = $this->items()) { return $items->parent(); } return null; } /** * Returns basic information about the * parent model that is currently selected * in the page picker. * * @param \Kirby\Cms\Site|\Kirby\Cms\Page|null * @return array|null */ public function modelToArray($model = null): ?array { if ($model === null) { return null; } // the selected model is the site. there's nothing above if (is_a($model, 'Kirby\Cms\Site') === true) { 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 * * @return \Kirby\Cms\Pages|null */ public function items() { // 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(); // when subpage navigation is enabled, a parent // might be passed in addition to the query. // The parent then takes priority. } elseif ($this->options['subpages'] === true && empty($this->options['parent']) === false) { $items = $this->itemsForParent(); // search by query } else { $items = $this->itemsForQuery(); } // filter protected pages $items = $items->filter('isReadable', true); // search $items = $this->search($items); // paginate the result return $this->items = $this->paginate($items); } /** * Search for pages by parent * * @return \Kirby\Cms\Pages */ public function itemsForParent() { return $this->parent()->children(); } /** * Search for pages by query string * * @return \Kirby\Cms\Pages * @throws \Kirby\Exception\InvalidArgumentException */ public function itemsForQuery() { // 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 if (is_a($items, 'Kirby\Cms\Site') === true) { $items = $items->children(); } elseif (is_a($items, 'Kirby\Cms\Page') === true) { $items = $items->children(); } elseif (is_a($items, 'Kirby\Cms\Pages') === false) { throw new InvalidArgumentException('Your query must return a set of pages'); } 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. * * @return \Kirby\Cms\Page|\Kirby\Cms\Site */ public function parent() { if ($this->parent !== null) { return $this->parent; } return $this->parent = $this->kirby->page($this->options['parent']) ?? $this->site; } /** * Calculates the top-most model (page or site) * that can be accessed when navigating * through pages. * * @return \Kirby\Cms\Page|\Kirby\Cms\Site */ public function start() { if (empty($this->options['query']) === false) { if ($items = $this->itemsForQuery()) { return $items->parent(); } return $this->site; } return $this->site; } /** * Returns an associative array * with all information for the picker. * This will be passed directly to the API. * * @return array */ public function toArray(): array { $array = parent::toArray(); $array['model'] = $this->modelToArray($this->model()); return $array; } }