123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- <?php
- /**
- * @package Grav\Framework\Flex
- *
- * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Framework\Flex\Pages\Traits;
- use Grav\Common\Config\Config;
- use Grav\Common\Grav;
- use Grav\Common\Page\Interfaces\PageCollectionInterface;
- use Grav\Common\Page\Interfaces\PageInterface;
- use Grav\Common\Page\Pages;
- use Grav\Common\Uri;
- use Grav\Common\Utils;
- use Grav\Framework\Filesystem\Filesystem;
- use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
- use RuntimeException;
- use function is_string;
- /**
- * Implements PageRoutableInterface
- */
- trait PageRoutableTrait
- {
- /** @var bool */
- protected $root = false;
- /** @var string|null */
- private $_route;
- /** @var string|null */
- private $_path;
- /** @var PageInterface|null */
- private $_parentCache;
- /**
- * Returns the page extension, got from the page `url_extension` config and falls back to the
- * system config `system.pages.append_url_extension`.
- *
- * @return string The extension of this page. For example `.html`
- */
- public function urlExtension(): string
- {
- return $this->loadHeaderProperty(
- 'url_extension',
- null,
- function ($value) {
- if ($this->home()) {
- return '';
- }
- return $value ?? Grav::instance()['config']->get('system.pages.append_url_extension', '');
- }
- );
- }
- /**
- * Gets and Sets whether or not this Page is routable, ie you can reach it via a URL.
- * The page must be *routable* and *published*
- *
- * @param bool|null $var true if the page is routable
- * @return bool true if the page is routable
- */
- public function routable($var = null): bool
- {
- $value = $this->loadHeaderProperty(
- 'routable',
- $var,
- static function ($value) {
- return $value ?? true;
- }
- );
- return $value && $this->published() && !$this->isModule() && !$this->root() && $this->getLanguages(true);
- }
- /**
- * Gets the URL for a page - alias of url().
- *
- * @param bool $include_host
- * @return string the permalink
- */
- public function link($include_host = false): string
- {
- return $this->url($include_host);
- }
- /**
- * Gets the URL with host information, aka Permalink.
- * @return string The permalink.
- */
- public function permalink(): string
- {
- return $this->url(true, false, true, true);
- }
- /**
- * Returns the canonical URL for a page
- *
- * @param bool $include_lang
- * @return string
- */
- public function canonical($include_lang = true): string
- {
- return $this->url(true, true, $include_lang);
- }
- /**
- * Gets the url for the Page.
- *
- * @param bool $include_host Defaults false, but true would include http://yourhost.com
- * @param bool $canonical true to return the canonical URL
- * @param bool $include_base
- * @param bool $raw_route
- * @return string The url.
- */
- public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false): string
- {
- // Override any URL when external_url is set
- $external = $this->getNestedProperty('header.external_url');
- if ($external) {
- return $external;
- }
- $grav = Grav::instance();
- /** @var Pages $pages */
- $pages = $grav['pages'];
- /** @var Config $config */
- $config = $grav['config'];
- // get base route (multi-site base and language)
- $route = $include_base ? $pages->baseRoute() : '';
- // add full route if configured to do so
- if (!$include_host && $config->get('system.absolute_urls', false)) {
- $include_host = true;
- }
- if ($canonical) {
- $route .= $this->routeCanonical();
- } elseif ($raw_route) {
- $route .= $this->rawRoute();
- } else {
- $route .= $this->route();
- }
- /** @var Uri $uri */
- $uri = $grav['uri'];
- $url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
- return Uri::filterPath($url);
- }
- /**
- * Gets the route for the page based on the route headers if available, else from
- * the parents route and the current Page's slug.
- *
- * @param string $var Set new default route.
- * @return string|null The route for the Page.
- */
- public function route($var = null): ?string
- {
- if (null !== $var) {
- // TODO: not the best approach, but works...
- $this->setNestedProperty('header.routes.default', $var);
- }
- // Return default route if given.
- $default = $this->getNestedProperty('header.routes.default');
- if (is_string($default)) {
- return $default;
- }
- return $this->routeInternal();
- }
- /**
- * @return string|null
- */
- protected function routeInternal(): ?string
- {
- $route = $this->_route;
- if (null !== $route) {
- return $route;
- }
- if ($this->root()) {
- return null;
- }
- // Root and orphan nodes have no route.
- $parent = $this->parent();
- if (!$parent) {
- return null;
- }
- if ($parent->home()) {
- /** @var Config $config */
- $config = Grav::instance()['config'];
- $hide = (bool)$config->get('system.home.hide_in_urls', false);
- $route = '/' . ($hide ? '' : $parent->slug());
- } else {
- $route = $parent->route();
- }
- if ($route !== '' && $route !== '/') {
- $route .= '/';
- }
- if (!$this->home()) {
- $route .= $this->slug();
- }
- $this->_route = $route;
- return $route;
- }
- /**
- * Helper method to clear the route out so it regenerates next time you use it
- */
- public function unsetRouteSlug(): void
- {
- // TODO:
- throw new RuntimeException(__METHOD__ . '(): Not Implemented');
- }
- /**
- * Gets and Sets the page raw route
- *
- * @param string|null $var
- * @return string|null
- */
- public function rawRoute($var = null): ?string
- {
- if (null !== $var) {
- // TODO:
- throw new RuntimeException(__METHOD__ . '(string): Not Implemented');
- }
- if ($this->root()) {
- return null;
- }
- return '/' . $this->getKey();
- }
- /**
- * Gets the route aliases for the page based on page headers.
- *
- * @param array|null $var list of route aliases
- * @return array The route aliases for the Page.
- */
- public function routeAliases($var = null): array
- {
- if (null !== $var) {
- $this->setNestedProperty('header.routes.aliases', (array)$var);
- }
- $aliases = (array)$this->getNestedProperty('header.routes.aliases');
- $default = $this->getNestedProperty('header.routes.default');
- if ($default) {
- $aliases[] = $default;
- }
- return $aliases;
- }
- /**
- * Gets the canonical route for this page if its set. If provided it will use
- * that value, else if it's `true` it will use the default route.
- *
- * @param string|null $var
- * @return string|null
- */
- public function routeCanonical($var = null): ?string
- {
- if (null !== $var) {
- $this->setNestedProperty('header.routes.canonical', (array)$var);
- }
- $canonical = $this->getNestedProperty('header.routes.canonical');
- return is_string($canonical) ? $canonical : $this->route();
- }
- /**
- * Gets the redirect set in the header.
- *
- * @param string|null $var redirect url
- * @return string|null
- */
- public function redirect($var = null): ?string
- {
- return $this->loadHeaderProperty(
- 'redirect',
- $var,
- static function ($value) {
- return trim($value) ?: null;
- }
- );
- }
- /**
- * Returns the clean path to the page file
- *
- * Needed in admin for Page Media.
- */
- public function relativePagePath(): ?string
- {
- $folder = $this->getMediaFolder();
- if (!$folder) {
- return null;
- }
- /** @var UniformResourceLocator $locator */
- $locator = Grav::instance()['locator'];
- $path = $locator->isStream($folder) ? $locator->findResource($folder, false) : $folder;
- return is_string($path) ? $path : null;
- }
- /**
- * Gets and sets the path to the folder where the .md for this Page object resides.
- * This is equivalent to the filePath but without the filename.
- *
- * @param string|null $var the path
- * @return string|null the path
- */
- public function path($var = null): ?string
- {
- if (null !== $var) {
- // TODO:
- throw new RuntimeException(__METHOD__ . '(string): Not Implemented');
- }
- $path = $this->_path;
- if ($path) {
- return $path;
- }
- if ($this->root()) {
- $folder = $this->getFlexDirectory()->getStorageFolder();
- } else {
- $folder = $this->getStorageFolder();
- }
- if ($folder) {
- /** @var UniformResourceLocator $locator */
- $locator = Grav::instance()['locator'];
- $folder = $locator->isStream($folder) ? $locator->getResource($folder) : GRAV_ROOT . "/{$folder}";
- }
- return $this->_path = is_string($folder) ? $folder : null;
- }
- /**
- * Get/set the folder.
- *
- * @param string|null $var Optional path, including numeric prefix.
- * @return string|null
- */
- public function folder($var = null): ?string
- {
- return $this->loadProperty(
- 'folder',
- $var,
- function ($value) {
- if (null === $value) {
- $value = $this->getMasterKey() ?: $this->getKey();
- }
- return Utils::basename($value) ?: null;
- }
- );
- }
- /**
- * Get/set the folder.
- *
- * @param string|null $var Optional path, including numeric prefix.
- * @return string|null
- */
- public function parentStorageKey($var = null): ?string
- {
- return $this->loadProperty(
- 'parent_key',
- $var,
- function ($value) {
- if (null === $value) {
- $filesystem = Filesystem::getInstance(false);
- $value = $this->getMasterKey() ?: $this->getKey();
- $value = ltrim($filesystem->dirname("/{$value}"), '/') ?: '';
- }
- return $value;
- }
- );
- }
- /**
- * Gets and Sets the parent object for this page
- *
- * @param PageInterface|null $var the parent page object
- * @return PageInterface|null the parent page object if it exists.
- */
- public function parent(PageInterface $var = null)
- {
- if (null !== $var) {
- // TODO:
- throw new RuntimeException(__METHOD__ . '(PageInterface): Not Implemented');
- }
- if ($this->_parentCache || $this->root()) {
- return $this->_parentCache;
- }
- // Use filesystem as \dirname() does not work in Windows because of '/foo' becomes '\'.
- $filesystem = Filesystem::getInstance(false);
- $directory = $this->getFlexDirectory();
- $parentKey = ltrim($filesystem->dirname("/{$this->getKey()}"), '/');
- if ('' !== $parentKey) {
- $parent = $directory->getObject($parentKey);
- $language = $this->getLanguage();
- if ($language && $parent && method_exists($parent, 'getTranslation')) {
- $parent = $parent->getTranslation($language) ?? $parent;
- }
- $this->_parentCache = $parent;
- } else {
- $index = $directory->getIndex();
- $this->_parentCache = \is_callable([$index, 'getRoot']) ? $index->getRoot() : null;
- }
- return $this->_parentCache;
- }
- /**
- * Gets the top parent object for this page. Can return page itself.
- *
- * @return PageInterface The top parent page object.
- */
- public function topParent()
- {
- $topParent = $this;
- while ($topParent) {
- $parent = $topParent->parent();
- if (!$parent || !$parent->parent()) {
- break;
- }
- $topParent = $parent;
- }
- return $topParent;
- }
- /**
- * Returns the item in the current position.
- *
- * @return int|null the index of the current page.
- */
- public function currentPosition(): ?int
- {
- $parent = $this->parent();
- $collection = $parent ? $parent->collection('content', false) : null;
- if ($collection instanceof PageCollectionInterface && $path = $this->path()) {
- return $collection->currentPosition($path);
- }
- return 1;
- }
- /**
- * Returns whether or not this page is the currently active page requested via the URL.
- *
- * @return bool True if it is active
- */
- public function active(): bool
- {
- $grav = Grav::instance();
- $uri_path = rtrim(urldecode($grav['uri']->path()), '/') ?: '/';
- $routes = $grav['pages']->routes();
- return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path();
- }
- /**
- * Returns whether or not this URI's URL contains the URL of the active page.
- * Or in other words, is this page's URL in the current URL
- *
- * @return bool True if active child exists
- */
- public function activeChild(): bool
- {
- $grav = Grav::instance();
- /** @var Uri $uri */
- $uri = $grav['uri'];
- /** @var Pages $pages */
- $pages = $grav['pages'];
- $uri_path = rtrim(urldecode($uri->path()), '/');
- $routes = $pages->routes();
- if (isset($routes[$uri_path])) {
- $page = $pages->find($uri->route());
- /** @var PageInterface|null $child_page */
- $child_page = $page ? $page->parent() : null;
- while ($child_page && !$child_page->root()) {
- if ($this->path() === $child_page->path()) {
- return true;
- }
- $child_page = $child_page->parent();
- }
- }
- return false;
- }
- /**
- * Returns whether or not this page is the currently configured home page.
- *
- * @return bool True if it is the homepage
- */
- public function home(): bool
- {
- $home = Grav::instance()['config']->get('system.home.alias');
- return '/' . $this->getKey() === $home;
- }
- /**
- * Returns whether or not this page is the root node of the pages tree.
- *
- * @param bool|null $var
- * @return bool True if it is the root
- */
- public function root($var = null): bool
- {
- if (null !== $var) {
- $this->root = (bool)$var;
- }
- return $this->root === true || $this->getKey() === '/';
- }
- }
|