* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ class Site extends ModelWithContent { use HasChildren; use HasFiles; use HasMethods; use SiteActions; public const CLASS_ALIAS = 'site'; /** * The SiteBlueprint object */ protected SiteBlueprint|null $blueprint = null; /** * The error page object */ protected Page|null $errorPage = null; /** * The id of the error page, which is * fetched in the errorPage method */ protected string $errorPageId; /** * The home page object */ protected Page|null $homePage = null; /** * The id of the home page, which is * fetched in the errorPage method */ protected string $homePageId; /** * Cache for the inventory array */ protected array|null $inventory = null; /** * The current page object */ protected Page|null $page; /** * The absolute path to the site directory */ protected string $root; /** * The page url */ protected string|null $url; /** * Creates a new Site object */ public function __construct(array $props = []) { parent::__construct($props); $this->errorPageId = $props['errorPageId'] ?? 'error'; $this->homePageId = $props['homePageId'] ?? 'home'; $this->page = $props['page'] ?? null; $this->url = $props['url'] ?? null; $this->setBlueprint($props['blueprint'] ?? null); $this->setChildren($props['children'] ?? null); $this->setDrafts($props['drafts'] ?? null); $this->setFiles($props['files'] ?? null); } /** * Modified getter to also return fields * from the content */ public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { return $this->$method; } // site methods if ($this->hasMethod($method)) { return $this->callMethod($method, $arguments); } // return site content otherwise return $this->content()->get($method); } /** * Improved `var_dump` output * @codeCoverageIgnore */ public function __debugInfo(): array { return array_merge($this->toArray(), [ 'content' => $this->content(), 'children' => $this->children(), 'files' => $this->files(), ]); } /** * Makes it possible to convert the site model * to a string. Mostly useful for debugging. */ public function __toString(): string { return $this->url(); } /** * Returns the url to the api endpoint * @internal */ public function apiUrl(bool $relative = false): string { if ($relative === true) { return 'site'; } return $this->kirby()->url('api') . '/site'; } /** * Returns the blueprint object */ public function blueprint(): SiteBlueprint { if ($this->blueprint instanceof SiteBlueprint) { return $this->blueprint; } return $this->blueprint = SiteBlueprint::factory('site', null, $this); } /** * Builds a breadcrumb collection */ public function breadcrumb(): Pages { // get all parents and flip the order $crumb = $this->page()->parents()->flip(); // add the home page $crumb->prepend($this->homePage()->id(), $this->homePage()); // add the active page $crumb->append($this->page()->id(), $this->page()); return $crumb; } /** * Prepares the content for the write method * @internal */ public function contentFileData( array $data, string|null $languageCode = null ): array { return A::prepend($data, ['title' => $data['title'] ?? null]); } /** * Filename for the content file * @internal * @deprecated 4.0.0 * @todo Remove in v5 * @codeCoverageIgnore */ public function contentFileName(): string { Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return 'site'; } /** * Returns the error page object */ public function errorPage(): Page|null { return $this->errorPage ??= $this->find($this->errorPageId()); } /** * Returns the global error page id * @internal */ public function errorPageId(): string { return $this->errorPageId ?? 'error'; } /** * Checks if the site exists on disk */ public function exists(): bool { return is_dir($this->root()) === true; } /** * Returns the home page object */ public function homePage(): Page|null { return $this->homePage ??= $this->find($this->homePageId()); } /** * Returns the global home page id * @internal */ public function homePageId(): string { return $this->homePageId ?? 'home'; } /** * Creates an inventory of all files * and children in the site directory * @internal */ public function inventory(): array { if ($this->inventory !== null) { return $this->inventory; } $kirby = $this->kirby(); return $this->inventory = Dir::inventory( $this->root(), $kirby->contentExtension(), $kirby->contentIgnore(), $kirby->multilang() ); } /** * Compares the current object with the given site object * * @param mixed $site */ public function is($site): bool { if ($site instanceof self === false) { return false; } return $this === $site; } /** * Returns the root to the media folder for the site * @internal */ public function mediaRoot(): string { return $this->kirby()->root('media') . '/site'; } /** * The site's base url for any files * @internal */ public function mediaUrl(): string { return $this->kirby()->url('media') . '/site'; } /** * Gets the last modification date of all pages * in the content folder. */ public function modified( string|null $format = null, string|null $handler = null ): int|string { return Dir::modified($this->root(), $format, $handler); } /** * Returns the current page if `$path` * is not specified. Otherwise it will try * to find a page by the given path. * * If no current page is set with the page * prop, the home page will be returned if * it can be found. (see `Site::homePage()`) * * @param string|null $path omit for current page, * otherwise e.g. `notes/across-the-ocean` */ public function page(string|null $path = null): Page|null { if ($path !== null) { return $this->find($path); } if ($this->page instanceof Page) { return $this->page; } try { return $this->page = $this->homePage(); } catch (LogicException) { return $this->page = null; } } /** * Alias for `Site::children()` */ public function pages(): Pages { return $this->children(); } /** * Returns the panel info object */ public function panel(): Panel { return new Panel($this); } /** * Returns the permissions object for this site */ public function permissions(): SitePermissions { return new SitePermissions($this); } /** * Preview Url * @internal */ public function previewUrl(): string|null { $preview = $this->blueprint()->preview(); if ($preview === false) { return null; } if ($preview === true) { $url = $this->url(); } else { $url = $preview; } return $url; } /** * Returns the absolute path to the content directory */ public function root(): string { return $this->root ??= $this->kirby()->root('content'); } /** * Returns the SiteRules class instance * which is being used in various methods * to check for valid actions and input. */ protected function rules(): SiteRules { return new SiteRules(); } /** * Search all pages in the site */ public function search(string|null $query = null, string|array $params = []): Pages { return $this->index()->search($query, $params); } /** * Sets the Blueprint object * * @return $this */ protected function setBlueprint(array|null $blueprint = null): static { if ($blueprint !== null) { $blueprint['model'] = $this; $this->blueprint = new SiteBlueprint($blueprint); } return $this; } /** * Converts the most important site * properties to an array */ public function toArray(): array { return array_merge(parent::toArray(), [ 'children' => $this->children()->keys(), 'errorPage' => $this->errorPage()?->id() ?? false, 'files' => $this->files()->keys(), 'homePage' => $this->homePage()?->id() ?? false, 'page' => $this->page()?->id() ?? false, 'title' => $this->title()->value(), 'url' => $this->url(), ]); } /** * Returns the Url */ public function url(string|null $language = null): string { if ($language !== null || $this->kirby()->multilang() === true) { return $this->urlForLanguage($language); } return $this->url ?? $this->kirby()->url(); } /** * Returns the translated url * @internal */ public function urlForLanguage( string|null $languageCode = null, array|null $options = null ): string { if ($language = $this->kirby()->language($languageCode)) { return $language->url(); } return $this->kirby()->url(); } /** * Sets the current page by * id or page object and * returns the current page * @internal */ public function visit( string|Page $page, string|null $languageCode = null ): Page { if ($languageCode !== null) { $this->kirby()->setCurrentTranslation($languageCode); $this->kirby()->setCurrentLanguage($languageCode); } // convert ids to a Page object if (is_string($page) === true) { $page = $this->find($page); } // handle invalid pages if ($page instanceof Page === false) { throw new InvalidArgumentException('Invalid page object'); } // set and return the current active page return $this->page = $page; } /** * Checks if any content of the site has been * modified after the given unix timestamp * This is mainly used to auto-update the cache */ public function wasModifiedAfter(int $time): bool { return Dir::wasModifiedAfter($this->root(), $time); } }