mirror of
https://github.com/getgrav/grav.git
synced 2025-02-20 19:56:53 +01:00
Added Flex Pages classes
This commit is contained in:
parent
565f26db8e
commit
efd4e52571
|
|
@ -1,6 +1,8 @@
|
|||
# v1.7.0-rc.1
|
||||
## mm/dd/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added Flex Pages classes
|
||||
1. [](#bugfix)
|
||||
* Fixed `Page::untranslatedLanguages()` not being symmetrical to `Page::translatedLanguages()`
|
||||
* Fixed `Flex Pages` not calling `onPageProcessed` event when cached
|
||||
|
|
|
|||
465
system/src/Grav/Common/Page/Flex/PageCollection.php
Normal file
465
system/src/Grav/Common/Page/Flex/PageCollection.php
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Flex\Pages\FlexPageCollection;
|
||||
|
||||
/**
|
||||
* Class GravPageCollection
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* Incompatibilities with Grav\Common\Page\Collection:
|
||||
* $page = $collection->key() will not work at all
|
||||
* $clone = clone $collection does not clone objects inside the collection, does it matter?
|
||||
* $string = (string)$collection returns collection id instead of comma separated list
|
||||
* $collection->add() incompatible method signature
|
||||
* $collection->remove() incompatible method signature
|
||||
* $collection->filter() incompatible method signature (takes closure instead of callable)
|
||||
* $collection->prev() does not rewind the internal pointer
|
||||
* AND most methods are immutable; they do not update the current collection, but return updated one
|
||||
*/
|
||||
class PageCollection extends FlexPageCollection implements PageCollectionInterface
|
||||
{
|
||||
protected $_params;
|
||||
|
||||
/**
|
||||
* @return PageInterface
|
||||
*/
|
||||
public function getRoot()
|
||||
{
|
||||
return $this->getIndex()->getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->_params ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->_params = $this->_params ? array_merge($this->_params, $params) : $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params(): array
|
||||
{
|
||||
return $this->getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addPage(PageInterface $page)
|
||||
{
|
||||
if (!$page instanceof FlexObjectInterface) {
|
||||
throw new \InvalidArgumentException('$page is not a flex page.');
|
||||
}
|
||||
|
||||
// FIXME: support other keys.
|
||||
$this->set($page->getKey(), $page);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return self
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return self
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous item.
|
||||
*
|
||||
* @return PageInterface|false
|
||||
*/
|
||||
public function prev()
|
||||
{
|
||||
// FIXME: this method does not rewind the internal pointer!
|
||||
$key = $this->key();
|
||||
$prev = $this->prevSibling($key);
|
||||
|
||||
return $prev !== $this->current() ? $prev : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return nth item.
|
||||
* @param int $key
|
||||
*
|
||||
* @return PageInterface|bool
|
||||
*/
|
||||
public function nth($key)
|
||||
{
|
||||
return $this->slice($key, 1)[0] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick one or more random entries.
|
||||
*
|
||||
* @param int $num Specifies how many entries should be picked.
|
||||
* @return self
|
||||
*/
|
||||
public function random($num = 1)
|
||||
{
|
||||
return $this->createFrom($this->shuffle()->slice(0, $num));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append new elements to the list.
|
||||
*
|
||||
* @param array $items Items to be appended. Existing keys will be overridden with the new values.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function append($items)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return self[]
|
||||
*/
|
||||
public function batch($size): array
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where end date is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param string|bool $endDate
|
||||
* @param string|null $field
|
||||
*
|
||||
* @return self
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null)
|
||||
{
|
||||
$start = Utils::date2timestamp($startDate);
|
||||
$end = $endDate ? Utils::date2timestamp($endDate) : false;
|
||||
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if (!$object) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$date = $field ? strtotime($object->getNestedProperty($field)) : $object->date();
|
||||
|
||||
if ($date >= $start && (!$end || $date <= $end)) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return self The collection with only visible pages
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && $object->visible()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return self The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->visible()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return self The collection with only modular pages
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && $object->modular()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return self The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->modular()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return self The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && $object->published()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return self The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->published()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return self The collection with only routable pages
|
||||
*/
|
||||
public function routable()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && $object->routable()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return self The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->routable()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return self The collection
|
||||
*/
|
||||
public function ofType($type)
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && $object->template() === $type) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
*
|
||||
* @return self The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types)
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && \in_array($object->template(), $types, true)) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
*
|
||||
* @return self The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels)
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && isset($object->header()->access)) {
|
||||
if (\is_array($object->header()->access)) {
|
||||
//Multiple values for access
|
||||
$valid = false;
|
||||
|
||||
foreach ($object->header()->access as $index => $accessLevel) {
|
||||
if (\is_array($accessLevel)) {
|
||||
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
|
||||
if (\in_array($innerAccessLevel, $accessLevels)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (\in_array($index, $accessLevels)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($valid) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
} else {
|
||||
//Single value for access
|
||||
if (\in_array($object->header()->access, $accessLevels)) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toExtendedArray(): array
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object) {
|
||||
$entries[$object->route()] = $object->toArray();
|
||||
}
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
}
|
||||
143
system/src/Grav/Common/Page/Flex/PageIndex.php
Normal file
143
system/src/Grav/Common/Page/Flex/PageIndex.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex;
|
||||
|
||||
use Grav\Common\File\CompiledJsonFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
|
||||
use Grav\Framework\Flex\Pages\FlexPageIndex;
|
||||
|
||||
/**
|
||||
* Class GravPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*/
|
||||
class PageIndex extends FlexPageIndex
|
||||
{
|
||||
const VERSION = parent::VERSION . '.5';
|
||||
const ORDER_LIST_REGEX = '/(\/\d+)\.[^\/]+/u';
|
||||
const PAGE_ROUTE_REGEX = '/\/\d+\./u';
|
||||
|
||||
/** @var FlexObjectInterface */
|
||||
protected $_root;
|
||||
protected $_params;
|
||||
|
||||
public function __construct(array $entries = [], FlexDirectory $directory = null)
|
||||
{
|
||||
// Remove root if it's taken.
|
||||
if (isset($entries[''])) {
|
||||
$this->_root = $entries[''];
|
||||
unset($entries['']);
|
||||
}
|
||||
|
||||
parent::__construct($entries, $directory);
|
||||
}
|
||||
|
||||
protected function createFrom(array $entries, string $keyField = null)
|
||||
{
|
||||
/** @var static $index */
|
||||
$index = parent::createFrom($entries, $keyField);
|
||||
$index->_root = $this->getRoot();
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FlexStorageInterface $storage
|
||||
* @return array
|
||||
*/
|
||||
public static function loadEntriesFromStorage(FlexStorageInterface $storage) : array
|
||||
{
|
||||
// Load saved index.
|
||||
$index = static::loadIndex($storage);
|
||||
|
||||
$timestamp = $index['timestamp'] ?? 0;
|
||||
if ($timestamp > time() - 2) {
|
||||
return $index['index'];
|
||||
}
|
||||
|
||||
// Load up to date index.
|
||||
$entries = parent::loadEntriesFromStorage($storage);
|
||||
|
||||
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true]);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
if (mb_strpos($key, '|') !== false) {
|
||||
[$key, $params] = explode('|', $key, 2);
|
||||
}
|
||||
|
||||
$element = parent::get($key);
|
||||
if (isset($params)) {
|
||||
$element = $element->getTranslation(ltrim($params, '.'));
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
public function getRoot()
|
||||
{
|
||||
$root = $this->_root;
|
||||
if (is_array($root)) {
|
||||
$this->_root = $this->getFlexDirectory()->createObject(['__META' => $root], '/');
|
||||
}
|
||||
|
||||
return $this->_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->_params ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->_params = $this->_params ? array_merge($this->_params, $params) : $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params(): array
|
||||
{
|
||||
return $this->getParams();
|
||||
}
|
||||
|
||||
protected static function getIndexFile(FlexStorageInterface $storage)
|
||||
{
|
||||
// Load saved index file.
|
||||
$grav = Grav::instance();
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$filename = $locator->findResource('user-data://flex/indexes/pages.json', true, true);
|
||||
|
||||
return CompiledJsonFile::instance($filename);
|
||||
}
|
||||
}
|
||||
532
system/src/Grav/Common/Page/Flex/PageObject.php
Normal file
532
system/src/Grav/Common/Page/Flex/PageObject.php
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Flex\Traits\PageContentTrait;
|
||||
use Grav\Common\Page\Flex\Traits\PageLegacyTrait;
|
||||
use Grav\Common\Page\Flex\Traits\PageRoutableTrait;
|
||||
use Grav\Common\Page\Flex\Traits\PageTranslateTrait;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Pages\FlexPageObject;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Route\RouteFactory;
|
||||
use Grav\Plugin\Admin\Admin;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Class GravPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* @property string $name
|
||||
* @property string $route
|
||||
* @property string $folder
|
||||
* @property int|false $order
|
||||
* @property string $template
|
||||
* @property string $language
|
||||
*/
|
||||
class PageObject extends FlexPageObject
|
||||
{
|
||||
use PageContentTrait;
|
||||
use PageLegacyTrait;
|
||||
use PageRoutableTrait;
|
||||
use PageTranslateTrait;
|
||||
|
||||
/** @var string Language code, eg: 'en' */
|
||||
protected $language;
|
||||
|
||||
/** @var string File format, eg. 'md' */
|
||||
protected $format;
|
||||
|
||||
private $_initialized = false;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getCachedMethods(): array
|
||||
{
|
||||
return [
|
||||
'path' => true,
|
||||
'full_order' => true
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
if (!$this->_initialized) {
|
||||
Grav::instance()->fireEvent('onPageProcessed', new Event(['page' => $this]));
|
||||
$this->_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $query
|
||||
* @return Route
|
||||
*/
|
||||
public function getRoute($query = []): Route
|
||||
{
|
||||
$route = RouteFactory::createFromString($this->route());
|
||||
if (\is_array($query)) {
|
||||
foreach ($query as $key => $value) {
|
||||
$route = $route->withQueryParam($key, $value);
|
||||
}
|
||||
} else {
|
||||
$route = $route->withAddedPath($query);
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc PageInterface
|
||||
*/
|
||||
public function getFormValue(string $name, $default = null, string $separator = null)
|
||||
{
|
||||
$test = new \stdClass();
|
||||
|
||||
$value = $this->pageContentValue($name, $test);
|
||||
if ($value !== $test) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
case 'name':
|
||||
// TODO: this should not be template!
|
||||
return $this->getProperty('template');
|
||||
case 'route':
|
||||
$key = dirname($this->hasKey() ? '/' . $this->getKey() : '/');
|
||||
return $key !== '/' ? $key : null;
|
||||
case 'full_route':
|
||||
return $this->hasKey() ? '/' . $this->getKey() : '';
|
||||
case 'full_order':
|
||||
return $this->full_order();
|
||||
case 'lang':
|
||||
return $this->getLanguage() ?? '';
|
||||
case 'translations':
|
||||
return $this->getLanguages();
|
||||
}
|
||||
|
||||
return parent::getFormValue($name, $default, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
|
||||
*/
|
||||
public function save($reorder = true)
|
||||
{
|
||||
// Reorder siblings.
|
||||
if ($reorder === true) {
|
||||
$reorder = $this->_reorder ?: false;
|
||||
}
|
||||
$siblings = is_array($reorder) ? $this->reorderSiblings($reorder) : [];
|
||||
|
||||
/** @var static $instance */
|
||||
$instance = parent::save();
|
||||
|
||||
foreach ($siblings as $sibling) {
|
||||
$sibling->save(false);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
protected function reorderSiblings(array $ordering)
|
||||
{
|
||||
$storageKey = $this->getStorageKey();
|
||||
$oldParentKey = ltrim(dirname("/$storageKey"), '/');
|
||||
$newParentKey = $this->getProperty('parent_key');
|
||||
|
||||
$slug = basename($this->getKey());
|
||||
$order = $oldParentKey === $newParentKey ? $this->order() : false;
|
||||
$k = $slug !== '' ? array_search($slug, $ordering, true) : false;
|
||||
if ($order === false) {
|
||||
if ($k !== false) {
|
||||
unset($ordering[$k]);
|
||||
}
|
||||
} elseif ($k === false) {
|
||||
$ordering[999999] = $slug;
|
||||
}
|
||||
$ordering = array_values($ordering);
|
||||
|
||||
$parent = $this->parent();
|
||||
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $parent ? $parent->children()->withVisible()->getCollection() : [];
|
||||
if ($siblings) {
|
||||
$ordering = array_flip($ordering);
|
||||
if ($storageKey !== null) {
|
||||
$siblings->remove($storageKey);
|
||||
if (isset($ordering[$slug])) {
|
||||
$siblings->set($storageKey, $this);
|
||||
}
|
||||
}
|
||||
$count = count($ordering);
|
||||
foreach ($siblings as $sibling) {
|
||||
$newOrder = $ordering[basename($sibling->getKey())] ?? null;
|
||||
$oldOrder = $sibling->order();
|
||||
$sibling->order(null !== $newOrder ? $newOrder + 1 : $oldOrder + $count);
|
||||
}
|
||||
$siblings = $siblings->orderBy(['order' => 'ASC']);
|
||||
$siblings->removeElement($this);
|
||||
}
|
||||
|
||||
return $siblings;
|
||||
}
|
||||
|
||||
public function full_order(): string
|
||||
{
|
||||
$path = $this->path();
|
||||
|
||||
return preg_replace(PageIndex::ORDER_LIST_REGEX, '\\1', $path . '/' . $this->folder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
*/
|
||||
protected function doGetBlueprint(string $name = ''): Blueprint
|
||||
{
|
||||
try {
|
||||
// Make sure that pages has been initialized.
|
||||
Pages::getTypes();
|
||||
|
||||
if ($name === 'raw') {
|
||||
// Admin RAW mode.
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'] ?? null;
|
||||
if ($admin) {
|
||||
$template = $this->modular() ? 'modular_raw' : 'raw';
|
||||
|
||||
return $admin->blueprints("admin/pages/{$template}");
|
||||
}
|
||||
}
|
||||
|
||||
$template = $this->getProperty('template') . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
} catch (\RuntimeException $e) {
|
||||
$template = 'default' . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
public function getLevelListing(array $options): array
|
||||
{
|
||||
$default_filters = [
|
||||
'type' => ['root', 'dir'],
|
||||
'name' => null,
|
||||
'extension' => null,
|
||||
];
|
||||
|
||||
$filters = ($options['filters'] ?? []) + $default_filters;
|
||||
$filter_type = (array)$filters['type'];
|
||||
|
||||
$field = $options['field'] ?? null;
|
||||
$route = $options['route'] ?? null;
|
||||
$leaf_route = $options['leaf_route'] ?? null;
|
||||
$sortby = $options['sortby'] ?? null;
|
||||
$order = $options['order'] ?? SORT_ASC;
|
||||
$language = $options['lang'] ?? null;
|
||||
|
||||
$status = 'error';
|
||||
$msg = null;
|
||||
$response = [];
|
||||
$children = null;
|
||||
$sub_route = null;
|
||||
$extra = null;
|
||||
|
||||
// Handle leaf_route
|
||||
$leaf = null;
|
||||
if ($leaf_route && $route !== $leaf_route) {
|
||||
$nodes = explode('/', $leaf_route);
|
||||
$sub_route = '/' . implode('/', array_slice($nodes, 1, $options['level']++));
|
||||
$options['route'] = $sub_route;
|
||||
|
||||
[$status,,$leaf,$extra] = $this->getLevelListing($options);
|
||||
}
|
||||
|
||||
/** @var PageCollection|PageIndex $collection */
|
||||
$collection = $this->getFlexDirectory()->getIndex();
|
||||
|
||||
// Handle no route, assume page tree root
|
||||
if (!$route) {
|
||||
$page = $collection->getRoot();
|
||||
} else {
|
||||
$page = $collection->get(trim($route, '/'));
|
||||
}
|
||||
$path = $page ? $page->path() : null;
|
||||
|
||||
if ($field) {
|
||||
$settings = $this->getBlueprint()->schema()->getProperty($field);
|
||||
$filters = array_merge([], $filters, $settings['filters'] ?? []);
|
||||
$filter_type = $filters['type'] ?? $filter_type;
|
||||
}
|
||||
|
||||
if ($page) {
|
||||
if ($page->root() && (!$filters['type'] || in_array('root', $filter_type, true))) {
|
||||
if ($field) {
|
||||
$response[] = [
|
||||
'name' => '<root>',
|
||||
'value' => '/',
|
||||
'item-key' => '',
|
||||
'filename' => '.',
|
||||
'extension' => '',
|
||||
'type' => 'root',
|
||||
'modified' => $page->modified(),
|
||||
'size' => 0,
|
||||
'symlink' => false
|
||||
];
|
||||
} else {
|
||||
$response[] = [
|
||||
'item-key' => '',
|
||||
'icon' => 'root',
|
||||
'title' => '<root>',
|
||||
'route' => '/',
|
||||
'raw_route' => null,
|
||||
'modified' => $page->modified(),
|
||||
'child_count' => 0,
|
||||
'extras' => [
|
||||
'template' => null,
|
||||
'langs' => [],
|
||||
'published' => false,
|
||||
'published_date' => null,
|
||||
'unpublished_date' => null,
|
||||
'visible' => false,
|
||||
'routable' => false,
|
||||
'tags' => ['non-routable'],
|
||||
'actions' => [],
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$status = 'success';
|
||||
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
|
||||
|
||||
$children = $page->children();
|
||||
|
||||
/** @var PageInterface|PageObject $child */
|
||||
foreach ($children as $child) {
|
||||
if ($field) {
|
||||
$payload = [
|
||||
'name' => $child->title(),
|
||||
'value' => $child->rawRoute(),
|
||||
'item-key' => basename($child->rawRoute()),
|
||||
'filename' => $child->folder(),
|
||||
'extension' => $child->extension(),
|
||||
'type' => 'dir',
|
||||
'modified' => $child->modified(),
|
||||
'size' => count($child->children()),
|
||||
'symlink' => false
|
||||
];
|
||||
|
||||
// filter types
|
||||
if ($filter_type && !in_array($payload['type'], $filter_type, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Simple filter for name or extension
|
||||
if (($filters['name'] && Utils::contains($payload['basename'], $filters['name']))
|
||||
|| ($filters['extension'] && Utils::contains($payload['extension'], $filters['extension']))) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if ($child->home()) {
|
||||
$icon = 'home';
|
||||
} elseif ($child->modular()) {
|
||||
$icon = 'modular';
|
||||
} elseif ($child->visible()) {
|
||||
$icon = 'visible';
|
||||
} else {
|
||||
$icon = 'page';
|
||||
}
|
||||
$tags = [
|
||||
$child->published() ? 'published' : 'non-published',
|
||||
$child->visible() ? 'visible' : 'non-visible',
|
||||
$child->routable() ? 'routable' : 'non-routable'
|
||||
];
|
||||
$lang = $child->findTranslation($language) ?? 'n/a';
|
||||
$extras = [
|
||||
'template' => $child->template(),
|
||||
'lang' => $lang ?: null,
|
||||
'translated' => $lang ? $child->hasTranslation($language, false) : null,
|
||||
'langs' => $child->getAllLanguages(true) ?: null,
|
||||
'published' => $child->published(),
|
||||
'published_date' => $this->jsDate($child->publishDate()),
|
||||
'unpublished_date' => $this->jsDate($child->unpublishDate()),
|
||||
'visible' => $child->visible(),
|
||||
'routable' => $child->routable(),
|
||||
'tags' => $tags,
|
||||
'actions' => null,
|
||||
];
|
||||
$extras = array_filter($extras, static function($v) { return $v !== null; });
|
||||
$payload = [
|
||||
'item-key' => basename($child->rawRoute()),
|
||||
'icon' => $icon,
|
||||
'title' => $child->title(),
|
||||
'route' => [
|
||||
'display' => $child->getRoute()->toString(false) ?: '/',
|
||||
'raw' => $child->rawRoute(),
|
||||
],
|
||||
'modified' => $this->jsDate($child->modified()),
|
||||
'child_count' => count($child->children()) ?: null,
|
||||
'extras' => $extras
|
||||
];
|
||||
$payload = array_filter($payload, static function($v) { return $v !== null; });
|
||||
}
|
||||
|
||||
// Add children if any
|
||||
if (\is_array($leaf) && !empty($leaf) && $child->path() === $extra) {
|
||||
$payload['children'] = array_values($leaf);
|
||||
}
|
||||
|
||||
$response[] = $payload;
|
||||
}
|
||||
} else {
|
||||
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND';
|
||||
}
|
||||
|
||||
// Sorting
|
||||
if ($sortby) {
|
||||
$response = Utils::sortArrayByKey($response, $sortby, $order);
|
||||
}
|
||||
|
||||
if ($field) {
|
||||
$temp_array = [];
|
||||
foreach ($response as $index => $item) {
|
||||
$temp_array[$item['type']][$index] = $item;
|
||||
}
|
||||
|
||||
$sorted = Utils::sortArrayByArray($temp_array, $filter_type);
|
||||
$response = Utils::arrayFlatten($sorted);
|
||||
}
|
||||
|
||||
return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
|
||||
}
|
||||
|
||||
private function jsDate(int $timestamp = null)
|
||||
{
|
||||
if (!$timestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
$dateFormat = $config->get('system.pages.dateformat.long');
|
||||
|
||||
return date($dateFormat, $timestamp);
|
||||
}
|
||||
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
$list = parent::__debugInfo();
|
||||
|
||||
return $list + [
|
||||
'_content_meta:private' => $this->getContentMeta(),
|
||||
'_content:private' => $this->getRawContent()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param bool $extended
|
||||
*/
|
||||
protected function filterElements(array &$elements, bool $extended = false): void
|
||||
{
|
||||
// Deal with ordering=bool and order=page1,page2,page3.
|
||||
if (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) {
|
||||
$ordering = (bool)($elements['ordering'] ?? false);
|
||||
$slug = preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->getProperty('folder'));
|
||||
$list = !empty($elements['order']) ? explode(',', $elements['order']) : [];
|
||||
if ($ordering) {
|
||||
$order = array_search($slug, $list, true);
|
||||
if ($order !== false) {
|
||||
$order++;
|
||||
} else {
|
||||
$order = $this->getProperty('order') ?: 1;
|
||||
}
|
||||
} else {
|
||||
$order = false;
|
||||
}
|
||||
|
||||
$this->_reorder = $list;
|
||||
$elements['order'] = $order;
|
||||
}
|
||||
|
||||
// Change storage location if needed.
|
||||
if (array_key_exists('route', $elements) && isset($elements['folder'], $elements['name'])) {
|
||||
$elements['template'] = $elements['name'];
|
||||
$parentRoute = $elements['route'];
|
||||
|
||||
// Figure out storage path to the new route.
|
||||
$parentKey = trim($parentRoute, '/');
|
||||
if ($parentKey !== '') {
|
||||
// Make sure page isn't being moved under itself.
|
||||
$key = $this->getKey();
|
||||
if ($key === $parentKey || strpos($parentKey, $key . '/') === 0) {
|
||||
throw new \RuntimeException(sprintf('Page %s cannot be moved to %s', '/' . $key, $parentRoute));
|
||||
}
|
||||
|
||||
$parent = $this->getFlexDirectory()->getObject($parentKey);
|
||||
if (!$parent) {
|
||||
// Page cannot be moved to non-existing location.
|
||||
throw new \RuntimeException(sprintf('Page %s cannot be moved to non-existing path %s', '/' . $key, $parentRoute));
|
||||
}
|
||||
|
||||
// If parent changes and page is visible, move it to be the last item.
|
||||
if ($parent && !empty($elements['order']) && $parent !== $this->parent()) {
|
||||
$elements['order'] = ((int)$parent->children()->visible()->sort(['order' => 'ASC'])->last()->order()) + 1;
|
||||
}
|
||||
|
||||
$parentKey = $parent->getStorageKey();
|
||||
}
|
||||
|
||||
$elements['parent_key'] = $parentKey;
|
||||
}
|
||||
parent::filterElements($elements, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function prepareStorage(): array
|
||||
{
|
||||
$meta = $this->getMetaData();
|
||||
$oldLang = $meta['lang'] ?? '';
|
||||
$newLang = $this->getProperty('lang');
|
||||
|
||||
// Always clone the page to the new language.
|
||||
if ($oldLang !== $newLang) {
|
||||
$meta['clone'] = true;
|
||||
}
|
||||
|
||||
// Make sure that certain elements are always sent to the storage layer.
|
||||
$elements = [
|
||||
'__META' => $meta,
|
||||
'storage_key' => $this->getStorageKey(),
|
||||
'parent_key' => $this->getProperty('parent_key'),
|
||||
'order' => $this->getProperty('order'),
|
||||
'folder' => preg_replace('|^\d+\.|', '', $this->getProperty('folder')),
|
||||
'template' => preg_replace('|modular/|', '', $this->getProperty('template')),
|
||||
'lang' => $newLang
|
||||
] + parent::prepareStorage();
|
||||
|
||||
return $elements;
|
||||
}
|
||||
}
|
||||
568
system/src/Grav/Common/Page/Flex/PageStorage.php
Normal file
568
system/src/Grav/Common/Page/Flex/PageStorage.php
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Flex\Storage\FolderStorage;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Class GravPageStorage
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*/
|
||||
class PageStorage extends FolderStorage
|
||||
{
|
||||
protected $ignore_files;
|
||||
protected $ignore_folders;
|
||||
protected $ignore_hidden;
|
||||
protected $recurse;
|
||||
protected $base_path;
|
||||
|
||||
protected $flags;
|
||||
protected $regex;
|
||||
|
||||
protected function initOptions(array $options): void
|
||||
{
|
||||
parent::initOptions($options);
|
||||
|
||||
$this->flags = \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
|
||||
| \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$config = $grav['config'];
|
||||
$this->ignore_hidden = (bool)$config->get('system.pages.ignore_hidden');
|
||||
$this->ignore_files = (array)$config->get('system.pages.ignore_files');
|
||||
$this->ignore_folders = (array)$config->get('system.pages.ignore_folders');
|
||||
$this->recurse = $options['recurse'] ?? true;
|
||||
$this->regex = '/(\.([\w\d_-]+))?\.md$/D';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param bool $variations
|
||||
* @return array
|
||||
*/
|
||||
public function parseKey(string $key, bool $variations = true): array
|
||||
{
|
||||
if (mb_strpos($key, '|') !== false) {
|
||||
[$key, $params] = explode('|', $key, 2);
|
||||
} else {
|
||||
$params = '';
|
||||
}
|
||||
$key = ltrim($key, '/');
|
||||
|
||||
$keys = parent::parseKey($key, false) + ['params' => $params];
|
||||
|
||||
if ($variations) {
|
||||
$keys += $this->parseParams($key, $params);
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
public function readFrontmatter(string $key): string
|
||||
{
|
||||
$path = $this->getPathFromKey($key);
|
||||
$file = $this->getFile($path);
|
||||
try {
|
||||
$frontmatter = $file->frontmatter();
|
||||
} catch (\RuntimeException $e) {
|
||||
$frontmatter = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
return $frontmatter;
|
||||
}
|
||||
|
||||
public function readRaw(string $key): string
|
||||
{
|
||||
$path = $this->getPathFromKey($key);
|
||||
$file = $this->getFile($path);
|
||||
try {
|
||||
$raw = $file->raw();
|
||||
} catch (\RuntimeException $e) {
|
||||
$raw = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param bool $includeParams
|
||||
* @return string
|
||||
*/
|
||||
public function buildStorageKey(array $keys, bool $includeParams = true): string
|
||||
{
|
||||
$key = $keys['key'] ?? null;
|
||||
if (null === $key) {
|
||||
$key = $keys['parent_key'] ?? '';
|
||||
if ($key !== '') {
|
||||
$key .= '/';
|
||||
}
|
||||
$order = $keys['order'] ?? 0;
|
||||
$folder = $keys['folder'] ?? 'undefined';
|
||||
$key .= $order ? sprintf('%02d.%s', $order, $folder) : $folder;
|
||||
}
|
||||
|
||||
$params = $includeParams ? $this->buildStorageKeyParams($keys) : '';
|
||||
|
||||
return $params ? "{$key}|{$params}" : $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return string
|
||||
*/
|
||||
public function buildStorageKeyParams(array $keys): string
|
||||
{
|
||||
$params = $keys['template'] ?? '';
|
||||
$language = $keys['lang'] ?? '';
|
||||
if ($language) {
|
||||
$params .= '.' . $language;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return string
|
||||
*/
|
||||
public function buildFolder(array $keys): string
|
||||
{
|
||||
return $this->dataFolder . '/' . $this->buildStorageKey($keys, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return string
|
||||
*/
|
||||
public function buildFilename(array $keys): string
|
||||
{
|
||||
$file = $this->buildStorageKeyParams($keys);
|
||||
|
||||
// Template is optional; if it is missing, we need to have to load the object metadata.
|
||||
if ($file && $file[0] === '.') {
|
||||
$meta = $this->getObjectMeta($this->buildStorageKey($keys, false));
|
||||
$file = ($meta['template'] ?? 'folder') . $file;
|
||||
}
|
||||
|
||||
return $file . $this->dataExt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return string
|
||||
*/
|
||||
public function buildFilepath(array $keys): string
|
||||
{
|
||||
return $this->buildFolder($keys) . '/' . $this->buildFilename($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
public function extractKeysFromRow(array $row): array
|
||||
{
|
||||
$meta = $row['__META'] ?? null;
|
||||
$storageKey = $row['storage_key'] ?? $meta['storage_key'] ?? '';
|
||||
$keyMeta = $storageKey !== '' ? $this->extractKeysFromStorageKey($storageKey) : null;
|
||||
$parentKey = $row['parent_key'] ?? $meta['parent_key'] ?? $keyMeta['parent_key'] ?? '';
|
||||
$order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? '';
|
||||
$folder = $row['folder'] ?? $meta['folder'] ?? $keyMeta['folder'] ?? '';
|
||||
$template = $row['template'] ?? $meta['template'] ?? $keyMeta['template'] ?? '';
|
||||
$lang = $row['lang'] ?? $meta['lang'] ?? $keyMeta['lang'] ?? '';
|
||||
|
||||
$keys = [
|
||||
'key' => null,
|
||||
'params' => null,
|
||||
'parent_key' => $parentKey,
|
||||
'order' => (int)$order,
|
||||
'folder' => $folder,
|
||||
'template' => $template,
|
||||
'lang' => $lang
|
||||
];
|
||||
|
||||
$keys['key'] = $this->buildStorageKey($keys, false);
|
||||
$keys['params'] = $this->buildStorageKeyParams($keys);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function extractKeysFromStorageKey(string $key): array
|
||||
{
|
||||
if (mb_strpos($key, '|') !== false) {
|
||||
[$key, $params] = explode('|', $key, 2);
|
||||
[$template, $language] = mb_strpos($params, '.') !== false ? explode('.', $params, 2) : [$params, ''];
|
||||
} else {
|
||||
$params = $template = $language = '';
|
||||
}
|
||||
$objectKey = basename($key);
|
||||
if (preg_match('|^(\d+)\.(.+)$|', $objectKey, $matches)) {
|
||||
[, $order, $folder] = $matches;
|
||||
} else {
|
||||
[$order, $folder] = ['', $objectKey];
|
||||
}
|
||||
$parentKey = ltrim(dirname('/' . $key), '/');
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'params' => $params,
|
||||
'parent_key' => $parentKey,
|
||||
'order' => (int)$order,
|
||||
'folder' => $folder,
|
||||
'template' => $template,
|
||||
'lang' => $language
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $params
|
||||
* @return array
|
||||
*/
|
||||
protected function parseParams(string $key, string $params): array
|
||||
{
|
||||
if (mb_strpos($params, '.') !== false) {
|
||||
[$template, $language] = explode('.', $params, 2);
|
||||
} else {
|
||||
$template = $params;
|
||||
$language = '';
|
||||
}
|
||||
|
||||
if ($template === '') {
|
||||
$meta = $this->getObjectMeta($key);
|
||||
$template = $meta['template'] ?? 'folder';
|
||||
}
|
||||
|
||||
return [
|
||||
'file' => $template . ($language ? '.' . $language : ''),
|
||||
'template' => $template,
|
||||
'lang' => $language
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the row for saving and returns the storage key for the record.
|
||||
*
|
||||
* @param array $row
|
||||
*/
|
||||
protected function prepareRow(array &$row): void
|
||||
{
|
||||
// Remove keys used in the filesystem.
|
||||
unset($row['parent_key'], $row['order'], $row['folder'], $row['template'], $row['lang']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page storage supports moving and copying the pages and their languages.
|
||||
*
|
||||
* $row['__META']['copy'] = true Use this if you want to copy the whole folder, otherwise it will be moved
|
||||
* $row['__META']['clone'] = true Use this if you want to clone the file, otherwise it will be renamed
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function saveRow(string $key, array $row): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $grav['debugger'];
|
||||
|
||||
try {
|
||||
// Initialize all key-related variables.
|
||||
$newKeys = $this->extractKeysFromRow($row);
|
||||
$newKey = $this->buildStorageKey($newKeys);
|
||||
$newFolder = $this->buildFolder($newKeys);
|
||||
$newFilename = $this->buildFilename($newKeys);
|
||||
$newFilepath = "{$newFolder}/{$newFilename}";
|
||||
|
||||
$debugger->addMessage("Save page: {$newKey}", 'debug');
|
||||
|
||||
// Check if the row already exists.
|
||||
$oldKey = $row['__META']['storage_key'] ?? null;
|
||||
if (is_string($oldKey)) {
|
||||
// Initialize all old key-related variables.
|
||||
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']]);
|
||||
$oldFolder = $this->buildFolder($oldKeys);
|
||||
$oldFilename = $this->buildFilename($oldKeys);
|
||||
|
||||
// Check if folder has changed.
|
||||
if ($oldFolder !== $newFolder && file_exists($oldFolder)) {
|
||||
$isCopy = $row['__META']['copy'] ?? false;
|
||||
if ($isCopy) {
|
||||
$this->copyRow($oldKey, $newKey);
|
||||
$debugger->addMessage("Page copied: {$oldFolder} => {$newFolder}", 'debug');
|
||||
} else {
|
||||
$this->renameRow($oldKey, $newKey);
|
||||
$debugger->addMessage("Page moved: {$oldFolder} => {$newFolder}", 'debug');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if filename has changed.
|
||||
if ($oldFilename !== $newFilename) {
|
||||
// Get instance of the old file (we have already copied/moved it).
|
||||
$oldFilepath = "{$newFolder}/{$oldFilename}";
|
||||
$file = $this->getFile($oldFilepath);
|
||||
|
||||
// Rename the file if we aren't supposed to clone it.
|
||||
$isClone = $row['__META']['clone'] ?? false;
|
||||
if (!$isClone && $file->exists()) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
$toPath = $locator->isStream($newFilepath) ? $locator->findResource($newFilepath, true, true) : $newFilepath;
|
||||
$success = $file->rename($toPath);
|
||||
if (!$success) {
|
||||
throw new \RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}");
|
||||
}
|
||||
$debugger->addMessage("Page template changed: {$oldFilename} => {$newFilename}", 'debug');
|
||||
} else {
|
||||
$file = null;
|
||||
$debugger->addMessage("Page template created: {$newFilename}", 'debug');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the data to be saved.
|
||||
$this->prepareRow($row);
|
||||
unset($row['__META'], $row['__ERROR']);
|
||||
|
||||
if (!isset($file)) {
|
||||
$file = $this->getFile($newFilepath);
|
||||
}
|
||||
|
||||
// Compare existing file content to the new one and save the file only if content has been changed.
|
||||
$file->free();
|
||||
$oldRaw = $file->raw();
|
||||
$file->content($row);
|
||||
$newRaw = $file->raw();
|
||||
if ($oldRaw !== $newRaw) {
|
||||
$file->save($row);
|
||||
$debugger->addMessage("Page content saved: {$newFilepath}", 'debug');
|
||||
} else {
|
||||
$debugger->addMessage('Page content has not been changed, do not update the file', 'debug');
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($newFolder)) {
|
||||
$locator->clearCache();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException(sprintf('Flex saveRow(%s): %s', $file->filename(), $e->getMessage()));
|
||||
}
|
||||
|
||||
$row['__META'] = $this->getObjectMeta($newKey, true);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
protected function canDeleteFolder(string $key): bool
|
||||
{
|
||||
$keys = $this->extractKeysFromStorageKey($key);
|
||||
if ($keys['lang']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key from the filesystem path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromPath(string $path): string
|
||||
{
|
||||
if ($this->base_path) {
|
||||
$path = $this->base_path . '/' . $path;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all stored keys in [key => timestamp] pairs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function buildIndex(): array
|
||||
{
|
||||
return $this->getIndexMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param bool $reload
|
||||
* @return array
|
||||
*/
|
||||
protected function getObjectMeta(string $key, bool $reload = false): array
|
||||
{
|
||||
$keys = $this->extractKeysFromStorageKey($key);
|
||||
$key = $keys['key'];
|
||||
|
||||
if ($reload || !isset($this->meta[$key])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if (mb_strpos($key, '@@') === false) {
|
||||
$path = $locator->findResource($this->getStoragePath($key), true, true);
|
||||
} else {
|
||||
$path = null;
|
||||
}
|
||||
|
||||
$modified = 0;
|
||||
$markdown = [];
|
||||
$children = [];
|
||||
|
||||
if ($path && file_exists($path)) {
|
||||
$modified = filemtime($path);
|
||||
$iterator = new \FilesystemIterator($path, $this->flags);
|
||||
|
||||
/** @var \SplFileInfo $info */
|
||||
foreach ($iterator as $k => $info) {
|
||||
// Ignore all hidden files if set.
|
||||
if ($k === '' || ($this->ignore_hidden && $k[0] === '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($info->isDir()) {
|
||||
// Ignore all folders in ignore list.
|
||||
if ($this->ignore_folders && \in_array($k, $this->ignore_folders, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$children[$k] = false;
|
||||
} else {
|
||||
// Ignore all files in ignore list.
|
||||
if ($this->ignore_files && \in_array($k, $this->ignore_files, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $info->getMTime();
|
||||
|
||||
// Page is the one that matches to $page_extensions list with the lowest index number.
|
||||
if (preg_match($this->regex, $k, $matches)) {
|
||||
$mark = $matches[2] ?? '';
|
||||
$ext = $matches[1] ?? '';
|
||||
$ext .= $this->dataExt;
|
||||
$markdown[$mark][basename($k, $ext)] = $timestamp;
|
||||
}
|
||||
|
||||
$modified = max($modified, $timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rawRoute = trim(preg_replace(PageIndex::PAGE_ROUTE_REGEX, '/', "/{$key}"), '/');
|
||||
$route = PageIndex::normalizeRoute($rawRoute);
|
||||
|
||||
ksort($markdown, SORT_NATURAL);
|
||||
ksort($children, SORT_NATURAL);
|
||||
|
||||
$file = array_key_first($markdown[''] ?? reset($markdown) ?: []);
|
||||
|
||||
$meta = [
|
||||
'key' => $route,
|
||||
'storage_key' => $key,
|
||||
'template' => $file,
|
||||
'storage_timestamp' => $modified,
|
||||
];
|
||||
if ($markdown) {
|
||||
$meta['markdown'] = $markdown;
|
||||
}
|
||||
if ($children) {
|
||||
$meta['children'] = $children;
|
||||
}
|
||||
$meta['checksum'] = md5(json_encode($meta));
|
||||
|
||||
// Cache meta as copy.
|
||||
$this->meta[$key] = $meta;
|
||||
} else {
|
||||
$meta = $this->meta[$key];
|
||||
}
|
||||
|
||||
$params = $keys['params'];
|
||||
if ($params) {
|
||||
$language = $keys['lang'];
|
||||
$template = $keys['template'] ?: array_key_first($meta['markdown'][$language]) ?? $meta['template'];
|
||||
$meta['exists'] = ($template && !empty($meta['children'])) || isset($meta['markdown'][$language][$template]);
|
||||
$meta['storage_key'] .= '|' . $params;
|
||||
$meta['template'] = $template;
|
||||
$meta['lang'] = $language;
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
protected function getIndexMeta(): array
|
||||
{
|
||||
$queue = [''];
|
||||
$list = [];
|
||||
do {
|
||||
$current = array_pop($queue);
|
||||
$meta = $this->getObjectMeta($current);
|
||||
$storage_key = $meta['storage_key'];
|
||||
|
||||
if (!empty($meta['children'])) {
|
||||
$prefix = $storage_key . ($storage_key !== '' ? '/' : '');
|
||||
|
||||
foreach ($meta['children'] as $child => $value) {
|
||||
$queue[] = $prefix . $child;
|
||||
}
|
||||
}
|
||||
|
||||
$list[$storage_key] = $meta;
|
||||
} while ($queue);
|
||||
|
||||
ksort($list, SORT_NATURAL);
|
||||
|
||||
// Update parent timestamps.
|
||||
foreach (array_reverse($list) as $storage_key => $meta) {
|
||||
if ($storage_key !== '') {
|
||||
$parentKey = dirname($storage_key);
|
||||
if ($parentKey === '.') {
|
||||
$parentKey = '';
|
||||
}
|
||||
|
||||
$parent = &$list[$parentKey];
|
||||
$basename = basename($storage_key);
|
||||
|
||||
if (isset($parent['children'][$basename])) {
|
||||
$timestamp = $meta['storage_timestamp'];
|
||||
$parent['children'][$basename] = $timestamp;
|
||||
if ($basename && $basename[0] === '_') {
|
||||
$parent['storage_timestamp'] = max($parent['storage_timestamp'], $timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getNewKey(): string
|
||||
{
|
||||
throw new \RuntimeException('Generating random key is disabled for pages');
|
||||
}
|
||||
}
|
||||
73
system/src/Grav/Common/Page/Flex/Traits/PageContentTrait.php
Normal file
73
system/src/Grav/Common/Page/Flex/Traits/PageContentTrait.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex\Traits;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Implements PageContentInterface.
|
||||
*/
|
||||
trait PageContentTrait
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function id($var = null): string
|
||||
{
|
||||
$property = 'id';
|
||||
$value = null === $var ? $this->getProperty($property) : null;
|
||||
if (null === $value) {
|
||||
$value = $this->language() . ($var ?? ($this->modified() . md5($this->filePath())));
|
||||
|
||||
$this->setProperty($property, $value);
|
||||
if ($this->doHasProperty($property)) {
|
||||
$value = $this->getProperty($property);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function date($var = null): int
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'date',
|
||||
$var,
|
||||
function($value) {
|
||||
$value = $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : false;
|
||||
|
||||
if (!$value) {
|
||||
// Get the specific translation updated date.
|
||||
$meta = $this->getMetaData();
|
||||
$language = $meta['lang'] ?? '';
|
||||
$template = $this->getProperty('template');
|
||||
$value = $meta['markdown'][$language][$template] ?? 0;
|
||||
}
|
||||
|
||||
return $value ?: $this->modified();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isPage(): bool
|
||||
{
|
||||
// FIXME: needs to be better
|
||||
return !$this->exists() || !empty($this->getLanguages()) || $this->modular();
|
||||
}
|
||||
}
|
||||
234
system/src/Grav/Common/Page/Flex/Traits/PageLegacyTrait.php
Normal file
234
system/src/Grav/Common/Page/Flex/Traits/PageLegacyTrait.php
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Implements PageLegacyInterface.
|
||||
*/
|
||||
trait PageLegacyTrait
|
||||
{
|
||||
/**
|
||||
* Returns children of this page.
|
||||
*
|
||||
* @return PageCollectionInterface|Collection
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::children();
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
return $pages->children($this->path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in an array of sub-pages.
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::isFirst();
|
||||
}
|
||||
|
||||
$parent = $this->parent();
|
||||
$collection = $parent ? $parent->collection('content', false) : null;
|
||||
if ($collection instanceof PageCollectionInterface) {
|
||||
return $collection->isFirst($this->path());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in an array of sub-pages.
|
||||
*
|
||||
* @return bool True if item is last
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::isLast();
|
||||
}
|
||||
|
||||
$parent = $this->parent();
|
||||
$collection = $parent ? $parent->collection('content', false) : null;
|
||||
if ($collection instanceof PageCollectionInterface) {
|
||||
return $collection->isLast($this->path());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param int $direction either -1 or +1
|
||||
*
|
||||
* @return PageInterface|bool the sibling page
|
||||
*/
|
||||
public function adjacentSibling($direction = 1)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::adjacentSibling($direction);
|
||||
}
|
||||
|
||||
$parent = $this->parent();
|
||||
$collection = $parent ? $parent->collection('content', false) : null;
|
||||
if ($collection instanceof PageCollectionInterface) {
|
||||
return $collection->adjacentSibling($this->path(), $direction);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return an ancestor page.
|
||||
*
|
||||
* @param bool $lookup Name of the parent folder
|
||||
*
|
||||
* @return PageInterface|null page you were looking for if it exists
|
||||
*/
|
||||
public function ancestor($lookup = null)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::ancestor($lookup);
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
return $pages->ancestor($this->getProperty('parent_route'), $lookup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that contains shared logic for inherited() and inheritedField()
|
||||
*
|
||||
* @param string $field Name of the parent folder
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getInheritedParams($field): array
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::getInheritedParams($field);
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$inherited = $pages->inherited($this->getProperty('parent_route'), $field);
|
||||
$inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : [];
|
||||
$currentParams = (array)$this->value('header.' . $field);
|
||||
if ($inheritedParams && is_array($inheritedParams)) {
|
||||
$currentParams = array_replace_recursive($inheritedParams, $currentParams);
|
||||
}
|
||||
|
||||
return [$inherited, $currentParams];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return a page.
|
||||
*
|
||||
* @param string $url the url of the page
|
||||
* @param bool $all
|
||||
*
|
||||
* @return PageInterface|null page you were looking for if it exists
|
||||
*/
|
||||
public function find($url, $all = false)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::find($url, $all);
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
return $pages->find($url, $all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of pages in the current context.
|
||||
*
|
||||
* @param string|array $params
|
||||
* @param bool $pagination
|
||||
*
|
||||
* @return Collection
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function collection($params = 'content', $pagination = true)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::collection($params, $pagination);
|
||||
}
|
||||
|
||||
if (is_string($params)) {
|
||||
// Look into a page header field.
|
||||
$params = (array)$this->value('header.' . $params);
|
||||
} elseif (!is_array($params)) {
|
||||
throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
|
||||
}
|
||||
|
||||
if (!$pagination) {
|
||||
$params['pagination'] = false;
|
||||
}
|
||||
$context = [
|
||||
'pagination' => $pagination,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $value
|
||||
* @param bool $only_published
|
||||
* @return Collection
|
||||
*/
|
||||
public function evaluate($value, $only_published = true)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::collection($value, $only_published);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'items' => $value,
|
||||
'published' => $only_published
|
||||
];
|
||||
$context = [
|
||||
'event' => false,
|
||||
'pagination' => false,
|
||||
'url_taxonomy_filters' => false,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
}
|
||||
136
system/src/Grav/Common/Page/Flex/Traits/PageRoutableTrait.php
Normal file
136
system/src/Grav/Common/Page/Flex/Traits/PageRoutableTrait.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Implements PageRoutableInterface.
|
||||
*/
|
||||
trait PageRoutableTrait
|
||||
{
|
||||
/**
|
||||
* 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 The route for the Page.
|
||||
*/
|
||||
public function route($var = null): string
|
||||
{
|
||||
if (null !== $var) {
|
||||
if ($var !== '/' && $var !== Grav::instance()['config']->get('system.home.alias')) {
|
||||
throw new \RuntimeException(__METHOD__ . '(\'' . $var . '\'): Not Implemented');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->home()) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
// TODO: implement rest of the routing:
|
||||
return $this->rawRoute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and Sets the parent object for this page
|
||||
*
|
||||
* @param PageInterface $var the parent page object
|
||||
*
|
||||
* @return PageInterface|null the parent page object if it exists.
|
||||
*/
|
||||
|
||||
public function parent(PageInterface $var = null)
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::parent();
|
||||
}
|
||||
|
||||
if (null !== $var) {
|
||||
throw new \RuntimeException('Not Implemented');
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
// FIXME: this does not work, needs to use $pages->get() with cached parent id!
|
||||
$key = $this->getKey();
|
||||
$parent_route = dirname('/' . $key);
|
||||
|
||||
return $parent_route !== '/' ? $pages->find($parent_route) : $pages->root();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return $collection->currentPosition($this->path()) ?? null;
|
||||
}
|
||||
|
||||
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();
|
||||
$uri = $grav['uri'];
|
||||
$pages = $grav['pages'];
|
||||
$uri_path = rtrim(urldecode($uri->path()), '/');
|
||||
$routes = $pages->routes();
|
||||
|
||||
if (isset($routes[$uri_path])) {
|
||||
/** @var PageInterface $child_page */
|
||||
$child_page = $pages->dispatch($uri->route(), false, false)->parent();
|
||||
if ($child_page) {
|
||||
while (!$child_page->root()) {
|
||||
if ($this->path() === $child_page->path()) {
|
||||
return true;
|
||||
}
|
||||
$child_page = $child_page->parent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Flex\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Implements PageTranslateInterface
|
||||
*/
|
||||
trait PageTranslateTrait
|
||||
{
|
||||
/**
|
||||
* Return an array with the routes of other translated languages
|
||||
*
|
||||
* @param bool $onlyPublished only return published translations
|
||||
*
|
||||
* @return array the page translated languages
|
||||
*/
|
||||
public function translatedLanguages($onlyPublished = false): array
|
||||
{
|
||||
if (Utils::isAdminPlugin()) {
|
||||
return parent::translatedLanguages();
|
||||
}
|
||||
|
||||
$translated = $this->getLanguageTemplates();
|
||||
if (!$translated) {
|
||||
return $translated;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$languages[] = '';
|
||||
$defaultCode = $language->getDefault();
|
||||
|
||||
if (isset($translated[$defaultCode])) {
|
||||
unset($translated['']);
|
||||
}
|
||||
|
||||
foreach ($translated as $key => &$template) {
|
||||
$template .= $key !== '' ? ".{$key}.md" : '.md';
|
||||
}
|
||||
unset($template);
|
||||
|
||||
$translated = array_intersect_key($translated, array_flip($languages));
|
||||
|
||||
$folder = $this->getStorageFolder();
|
||||
if (!$folder) {
|
||||
return [];
|
||||
}
|
||||
$folder = $locator($folder);
|
||||
|
||||
$list = array_fill_keys($languages, null);
|
||||
foreach ($translated as $languageCode => $languageFile) {
|
||||
$languageExtension = $languageCode ? ".{$languageCode}.md" : '.md';
|
||||
$path = "{$folder}/{$languageFile}";
|
||||
|
||||
// FIXME: use flex, also rawRoute() does not fully work?
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $languageExtension);
|
||||
if ($onlyPublished && !$aPage->published()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
|
||||
if (!$route) {
|
||||
$route = $aPage->route();
|
||||
}
|
||||
|
||||
$list[$languageCode ?: $defaultCode] = $route ?? '';
|
||||
}
|
||||
|
||||
return array_filter($list, static function($var) { return null !== $var; });
|
||||
}
|
||||
}
|
||||
177
system/src/Grav/Framework/Flex/Pages/FlexPageCollection.php
Normal file
177
system/src/Grav/Framework/Flex/Pages/FlexPageCollection.php
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Flex\Pages;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Framework\Flex\FlexCollection;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
|
||||
/**
|
||||
* Class FlexPageCollection
|
||||
* @package Grav\Plugin\FlexObjects\Types\FlexPages
|
||||
*/
|
||||
class FlexPageCollection extends FlexCollection
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getCachedMethods(): array
|
||||
{
|
||||
return [
|
||||
'withPublished' => true,
|
||||
'withVisible' => true,
|
||||
'isFirst' => true,
|
||||
'isLast' => true,
|
||||
'currentPosition' => true,
|
||||
'getNextOrder' => false,
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollection|FlexPageCollection
|
||||
*/
|
||||
public function withPublished(bool $bool = true): FlexCollectionInterface
|
||||
{
|
||||
$list = array_keys(array_filter($this->call('isPublished', [$bool])));
|
||||
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
public function withVisible(bool $bool = true): FlexCollectionInterface
|
||||
{
|
||||
$list = array_keys(array_filter($this->call('isVisible', [$bool])));
|
||||
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path): bool
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
$first = reset($keys);
|
||||
|
||||
return $path === $first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path): bool
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
$last = end($keys);
|
||||
|
||||
return $path === $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The previous item.
|
||||
*/
|
||||
public function prevSibling($path)
|
||||
{
|
||||
return $this->adjacentSibling($path, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The next item.
|
||||
*/
|
||||
public function nextSibling($path)
|
||||
{
|
||||
return $this->adjacentSibling($path, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $direction either -1 or +1
|
||||
*
|
||||
* @return PageInterface|PageCollectionInterface|null The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
$pos = \array_search($path, $keys, true);
|
||||
|
||||
if ($pos !== false) {
|
||||
$pos += $direction;
|
||||
if (isset($keys[$pos])) {
|
||||
return $this[$keys[$pos]];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path): ?int
|
||||
{
|
||||
$pos = \array_search($path, $this->getKeys(), true);
|
||||
|
||||
return $pos !== false ? $pos : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNextOrder()
|
||||
{
|
||||
$directory = $this->getFlexDirectory();
|
||||
|
||||
/** @var FlexPageObject $last */
|
||||
$collection = $directory->getIndex();
|
||||
$keys = $collection->getStorageKeys();
|
||||
|
||||
// Assign next free order.
|
||||
$last = null;
|
||||
$order = 0;
|
||||
foreach ($keys as $folder => $key) {
|
||||
preg_match(FlexPageIndex::ORDER_PREFIX_REGEX, $folder, $test);
|
||||
$test = $test[0] ?? null;
|
||||
if ($test && $test > $order) {
|
||||
$order = $test;
|
||||
$last = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$last = $collection[$last];
|
||||
|
||||
return sprintf('%d.', $last ? $last->value('order') + 1 : 1);
|
||||
}
|
||||
}
|
||||
78
system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php
Normal file
78
system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Flex\Pages;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Flex\FlexIndex;
|
||||
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
|
||||
|
||||
/**
|
||||
* Class FlexPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\FlexPages
|
||||
*/
|
||||
class FlexPageIndex extends FlexIndex
|
||||
{
|
||||
const ORDER_PREFIX_REGEX = '/^\d+\./u';
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
static public function normalizeRoute(string $route)
|
||||
{
|
||||
static $case_insensitive;
|
||||
|
||||
if (null === $case_insensitive) {
|
||||
$case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls', false);
|
||||
}
|
||||
|
||||
return $case_insensitive ? mb_strtolower($route) : $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexPageIndex
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
return $this->withVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexPageIndex
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
return $this->withVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexPageIndex
|
||||
*/
|
||||
public function withVisible(bool $bool = true)
|
||||
{
|
||||
$keys = $this->getIndexMap('key');
|
||||
$list = [];
|
||||
foreach ($keys as $key => $test) {
|
||||
$keyBase = basename($key);
|
||||
if ((int)$key > 0) {
|
||||
$testBase = basename($test);
|
||||
if (mb_strlen($keyBase) !== mb_strlen($testBase)) {
|
||||
$list[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->select($list);
|
||||
}
|
||||
}
|
||||
396
system/src/Grav/Framework/Flex/Pages/FlexPageObject.php
Normal file
396
system/src/Grav/Framework/Flex/Pages/FlexPageObject.php
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Flex\Pages;
|
||||
|
||||
use DateTime;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Traits\PageFormTrait;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexTranslateInterface;
|
||||
use Grav\Framework\Flex\Pages\Traits\PageContentTrait;
|
||||
use Grav\Framework\Flex\Pages\Traits\PageLegacyTrait;
|
||||
use Grav\Framework\Flex\Pages\Traits\PageRoutableTrait;
|
||||
use Grav\Framework\Flex\Pages\Traits\PageTranslateTrait;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use Grav\Framework\Media\Interfaces\MediaManipulationInterface;
|
||||
|
||||
/**
|
||||
* Class FlexPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\FlexPages
|
||||
*/
|
||||
class FlexPageObject extends FlexObject implements PageInterface, MediaManipulationInterface, FlexTranslateInterface
|
||||
{
|
||||
use PageContentTrait;
|
||||
use PageFormTrait;
|
||||
use PageLegacyTrait;
|
||||
use PageTranslateTrait;
|
||||
use PageRoutableTrait;
|
||||
use FlexMediaTrait;
|
||||
|
||||
const PAGE_ORDER_REGEX = '/^(\d+)\.(.*)$/u';
|
||||
|
||||
/** @var array|null */
|
||||
protected $_reorder;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getCachedMethods(): array
|
||||
{
|
||||
return [
|
||||
// Page Content Interface
|
||||
'header' => false,
|
||||
'summary' => true,
|
||||
'content' => true,
|
||||
'value' => false,
|
||||
'media' => false,
|
||||
'title' => true,
|
||||
'menu' => true,
|
||||
'visible' => true,
|
||||
'published' => true,
|
||||
'publishDate' => true,
|
||||
'unpublishDate' => true,
|
||||
'process' => true,
|
||||
'slug' => true,
|
||||
'order' => true,
|
||||
'id' => true,
|
||||
'modified' => true,
|
||||
'lastModified' => true,
|
||||
'folder' => true,
|
||||
'date' => true,
|
||||
'dateformat' => true,
|
||||
'taxonomy' => true,
|
||||
'shouldProcess' => true,
|
||||
'isPage' => true,
|
||||
'isDir' => true,
|
||||
'folderExists' => true,
|
||||
|
||||
// Page
|
||||
'isPublished' => true,
|
||||
'isVisible' => true,
|
||||
'getCreated_Timestamp' => true,
|
||||
'getPublish_Timestamp' => true,
|
||||
'getUnpublish_Timestamp' => true,
|
||||
'getUpdated_Timestamp' => true,
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublished(): bool
|
||||
{
|
||||
$time = time();
|
||||
$start = $this->getPublish_Timestamp();
|
||||
$stop = $this->getUnpublish_Timestamp();
|
||||
|
||||
return $this->published() && $start <= $time && (!$stop || $time <= $stop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isVisible(): bool
|
||||
{
|
||||
return $this->visible();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCreated_Timestamp(): int
|
||||
{
|
||||
return $this->getFieldTimestamp('created_date') ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPublish_Timestamp(): int
|
||||
{
|
||||
return $this->getFieldTimestamp('publish_date') ?? $this->getCreated_Timestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getUnpublish_Timestamp(): ?int
|
||||
{
|
||||
return $this->getFieldTimestamp('unpublish_date');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUpdated_Timestamp(): int
|
||||
{
|
||||
return $this->getFieldTimestamp('updated_date') ?? $this->getPublish_Timestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getFormValue(string $name, $default = null, string $separator = null)
|
||||
{
|
||||
$test = new \stdClass();
|
||||
|
||||
$value = $this->pageContentValue($name, $test);
|
||||
if ($value !== $test) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
case 'name':
|
||||
return $this->getProperty('template');
|
||||
case 'route':
|
||||
return $this->hasKey() ? '/' . $this->getKey() : null;
|
||||
}
|
||||
|
||||
return parent::getFormValue($name, $default, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexObjectInterface::getCacheKey()
|
||||
*/
|
||||
public function getCacheKey(): string
|
||||
{
|
||||
return $this->hasKey() ? $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getKey() . $this->getLanguage() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $key
|
||||
* @return FlexObjectInterface
|
||||
*/
|
||||
public function createCopy(string $key = null)
|
||||
{
|
||||
$this->copy();
|
||||
|
||||
return parent::createCopy($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
|
||||
*/
|
||||
public function save($reorder = true)
|
||||
{
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display order for the associated media.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMediaOrder(): array
|
||||
{
|
||||
$order = $this->getNestedProperty('header.media_order');
|
||||
|
||||
if (is_array($order)) {
|
||||
return $order;
|
||||
}
|
||||
|
||||
if (!$order) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map('trim', explode(',', $order));
|
||||
}
|
||||
|
||||
// Overrides for header properties.
|
||||
|
||||
/**
|
||||
* Common logic to load header properties.
|
||||
*
|
||||
* @param string $property
|
||||
* @param mixed $var
|
||||
* @param callable $filter
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function loadHeaderProperty(string $property, $var, callable $filter)
|
||||
{
|
||||
// We have to use parent methods in order to avoid loops.
|
||||
$value = null === $var ? parent::getProperty($property) : null;
|
||||
if (null === $value) {
|
||||
$value = $filter($var ?? $this->getProperty('header')->get($property));
|
||||
|
||||
parent::setProperty($property, $value);
|
||||
if ($this->doHasProperty($property)) {
|
||||
$value = parent::getProperty($property);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common logic to load header properties.
|
||||
*
|
||||
* @param string $property
|
||||
* @param mixed $var
|
||||
* @param callable $filter
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function loadProperty(string $property, $var, callable $filter)
|
||||
{
|
||||
// We have to use parent methods in order to avoid loops.
|
||||
$value = null === $var ? parent::getProperty($property) : null;
|
||||
if (null === $value) {
|
||||
$value = $filter($var);
|
||||
|
||||
parent::setProperty($property, $value);
|
||||
if ($this->doHasProperty($property)) {
|
||||
$value = parent::getProperty($property);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getProperty($property, $default = null)
|
||||
{
|
||||
$method = static::$headerProperties[$property] ??static::$calculatedProperties[$property] ?? null;
|
||||
if ($method && method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
return parent::getProperty($property, $default);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param string $property
|
||||
* @param mixed $default
|
||||
*/
|
||||
public function setProperty($property, $value)
|
||||
{
|
||||
$method = static::$headerProperties[$property] ?? static::$calculatedProperties[$property] ?? null;
|
||||
if ($method && method_exists($this, $method)) {
|
||||
$this->{$method}($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
parent::setProperty($property, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNestedProperty($property, $value, $separator = null)
|
||||
{
|
||||
if (strpos($property, 'header.') === 0) {
|
||||
$this->getProperty('header')->set(str_replace('header.', '', $property), $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
parent::setNestedProperty($property, $value, $separator);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unsetNestedProperty($property, $separator = null)
|
||||
{
|
||||
if (strpos($property, 'header.') === 0) {
|
||||
$this->getProperty('header')->undef(str_replace('header.', '', $property));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
parent::unsetNestedProperty($property, $separator);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param bool $extended
|
||||
*/
|
||||
protected function filterElements(array &$elements, bool $extended = false): void
|
||||
{
|
||||
// Markdown storage conversion to page structure.
|
||||
if (isset($elements['content'])) {
|
||||
$elements['markdown'] = $elements['content'];
|
||||
unset($elements['content']);
|
||||
}
|
||||
|
||||
// RAW frontmatter support.
|
||||
if (isset($elements['frontmatter'])) {
|
||||
$formatter = new YamlFormatter();
|
||||
try {
|
||||
// Replace the whole header except for media order, which is used in admin.
|
||||
$media_order = $elements['media_order'] ?? null;
|
||||
$elements['header'] = $formatter->decode($elements['frontmatter']);
|
||||
if ($media_order) {
|
||||
$elements['header']['media_order'] = $media_order;
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException('Badly formatted markdown');
|
||||
}
|
||||
|
||||
unset($elements['frontmatter']);
|
||||
}
|
||||
|
||||
if (!$extended) {
|
||||
$folder = !empty($elements['folder']) ? trim($elements['folder']) : '';
|
||||
|
||||
if ($folder) {
|
||||
$order = !empty($elements['order']) ? (int)$elements['order'] : null;
|
||||
// TODO: broken
|
||||
$elements['storage_key'] = $order ? sprintf('%02d.%s', $order, $folder) : $folder;
|
||||
}
|
||||
}
|
||||
|
||||
parent::filterElements($elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getFieldTimestamp(string $field): ?int
|
||||
{
|
||||
$date = $this->getFieldDateTime($field);
|
||||
|
||||
return $date ? $date->getTimestamp() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return DateTime|null
|
||||
*/
|
||||
protected function getFieldDateTime(string $field): ?DateTime
|
||||
{
|
||||
try {
|
||||
$value = $this->getProperty($field);
|
||||
if (is_numeric($value)) {
|
||||
$value = '@' . $value;
|
||||
}
|
||||
$date = $value ? new DateTime($value) : null;
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
$date = null;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
772
system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php
Normal file
772
system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php
Normal file
|
|
@ -0,0 +1,772 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 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\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Page\Header;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Implements PageContentInterface.
|
||||
*/
|
||||
trait PageContentTrait
|
||||
{
|
||||
static protected $headerProperties = [
|
||||
'slug' => 'slug',
|
||||
'routes' => false,
|
||||
'title' => 'title',
|
||||
'language' => 'language',
|
||||
'template' => 'template',
|
||||
'menu' => 'menu',
|
||||
'routable' => 'routable',
|
||||
'visible' => 'visible',
|
||||
'redirect' => 'redirect',
|
||||
'external_url' => false,
|
||||
'order_dir' => 'orderDir',
|
||||
'order_by' => 'orderBy',
|
||||
'order_manual' => 'orderManual',
|
||||
'dateformat' => 'dateformat',
|
||||
'date' => 'date',
|
||||
'markdown_extra' => false,
|
||||
'taxonomy' => 'taxonomy',
|
||||
'max_count' => 'maxCount',
|
||||
'process' => 'process',
|
||||
'published' => 'published',
|
||||
'publish_date' => 'publishDate',
|
||||
'unpublish_date' => 'unpublishDate',
|
||||
'expires' => 'expires',
|
||||
'cache_control' => 'cacheControl',
|
||||
'etag' => 'eTag',
|
||||
'last_modified' => 'lastModified',
|
||||
'ssl' => 'ssl',
|
||||
'template_format' => 'templateFormat',
|
||||
'debugger' => false,
|
||||
];
|
||||
|
||||
static protected $calculatedProperties = [
|
||||
'name' => 'name',
|
||||
'parent' => 'parent',
|
||||
'parent_key' => 'parentStorageKey',
|
||||
'folder' => 'folder',
|
||||
'order' => 'order',
|
||||
'template' => 'template',
|
||||
];
|
||||
|
||||
/** @var object */
|
||||
protected $header;
|
||||
|
||||
/** @var string */
|
||||
protected $_summary;
|
||||
|
||||
/** @var string */
|
||||
protected $_content;
|
||||
|
||||
/**
|
||||
* Method to normalize the route.
|
||||
*
|
||||
* @param string $route
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public static function normalizeRoute($route): string
|
||||
{
|
||||
$case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls');
|
||||
|
||||
return $case_insensitive ? mb_strtolower($route) : $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function header($var = null): Header
|
||||
{
|
||||
if (null !== $var) {
|
||||
$this->setProperty('header', $var);
|
||||
}
|
||||
|
||||
return $this->getProperty('header');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function summary($size = null, $textOnly = false): string
|
||||
{
|
||||
return $this->processSummary($size, $textOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setSummary($summary): void
|
||||
{
|
||||
$this->_summary = $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function content($var = null): string
|
||||
{
|
||||
if (null !== $var) {
|
||||
$this->_content = $var;
|
||||
}
|
||||
|
||||
return $this->_content ?? $this->processContent($this->getRawContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getRawContent(): string
|
||||
{
|
||||
return $this->_content ?? $this->getArrayProperty('markdown') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setRawContent($content): void
|
||||
{
|
||||
$this->_content = $content ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function rawMarkdown($var = null): string
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->setProperty('markdown', $var);
|
||||
}
|
||||
|
||||
return $this->getProperty('markdown') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* Implement by calling:
|
||||
*
|
||||
* $test = new \stdClass();
|
||||
* $value = $this->pageContentValue($name, $test);
|
||||
* if ($value !== $test) {
|
||||
* return $value;
|
||||
* }
|
||||
* return parent::value($name, $default);
|
||||
*/
|
||||
abstract public function value($name, $default = null, $separator = null);
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function media($var = null): Media
|
||||
{
|
||||
if ($var instanceof Media) {
|
||||
$this->setProperty('media', $var);
|
||||
}
|
||||
|
||||
return $this->getProperty('media');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function title($var = null): string
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'title',
|
||||
$var,
|
||||
function($value) {
|
||||
return trim($value ?? ($this->root() ? '<root>' : ucfirst($this->slug())));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function menu($var = null): string
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'menu',
|
||||
$var,
|
||||
function($value) {
|
||||
return trim($value ?: $this->title());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function visible($var = null): bool
|
||||
{
|
||||
$value = $this->loadHeaderProperty(
|
||||
'visible',
|
||||
$var,
|
||||
function($value) {
|
||||
return ($value ?? $this->order() !== false) && !$this->modular();
|
||||
}
|
||||
);
|
||||
|
||||
return $value && $this->published();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function published($var = null): bool
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'published',
|
||||
$var,
|
||||
static function($value) {
|
||||
return (bool)($value ?? true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function publishDate($var = null): ?int
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'publish_date',
|
||||
$var,
|
||||
function($value) {
|
||||
return $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function unpublishDate($var = null): ?int
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'unpublish_date',
|
||||
$var,
|
||||
function($value) {
|
||||
return $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function process($var = null): array
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'process',
|
||||
$var,
|
||||
function($value) {
|
||||
$value = array_replace(Grav::instance()['config']->get('system.pages.process', []), is_array($value) ? $value : []);
|
||||
foreach ($value as $process => $status) {
|
||||
$value[$process] = (bool)$status;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function slug($var = null): string
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'slug',
|
||||
$var,
|
||||
function($value) {
|
||||
return $value ?: static::normalizeRoute(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder()));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function order($var = null)
|
||||
{
|
||||
$property = $this->loadProperty(
|
||||
'order',
|
||||
$var,
|
||||
function($value) {
|
||||
if (null === $value) {
|
||||
preg_match(static::PAGE_ORDER_REGEX, $this->folder(), $order);
|
||||
|
||||
$value = $order[1] ?? false;
|
||||
}
|
||||
|
||||
$value = (int)$value;
|
||||
|
||||
return $value ?: false;
|
||||
}
|
||||
);
|
||||
|
||||
return $property ? sprintf('%02d.', $property) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function id($var = null): string
|
||||
{
|
||||
$property = 'id';
|
||||
$value = null === $var ? $this->getProperty($property) : null;
|
||||
if (null === $value) {
|
||||
$value = $this->language() . ($var ?? ($this->modified() . md5( 'flex-' . $this->getFlexType() . '-' . $this->getKey())));
|
||||
|
||||
$this->setProperty($property, $value);
|
||||
if ($this->doHasProperty($property)) {
|
||||
$value = $this->getProperty($property);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function modified($var = null): int
|
||||
{
|
||||
$property = 'modified';
|
||||
$value = null === $var ? $this->getProperty($property) : null;
|
||||
if (null === $value) {
|
||||
$value = (int)($var ?: $this->getTimestamp());
|
||||
|
||||
$this->setProperty($property, $value);
|
||||
if ($this->doHasProperty($property)) {
|
||||
$value = $this->getProperty($property);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function lastModified($var = null): bool
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'last_modified',
|
||||
$var,
|
||||
static function($value) {
|
||||
return (bool)($value ?? Grav::instance()['config']->get('system.pages.last_modified'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function date($var = null): int
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'date',
|
||||
$var,
|
||||
function($value) {
|
||||
$value = $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : false;
|
||||
|
||||
return $value ?: $this->modified();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function dateformat($var = null): ?string
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'dateformat',
|
||||
$var,
|
||||
static function($value) {
|
||||
return $value ?? null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function taxonomy($var = null): array
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'taxonomy',
|
||||
$var,
|
||||
static function($value) {
|
||||
if (is_array($value)) {
|
||||
// make sure first level are arrays
|
||||
array_walk($value, static function(&$val) {
|
||||
$val = (array) $val;
|
||||
});
|
||||
// make sure all values are strings
|
||||
array_walk_recursive($value, static function(&$val) {
|
||||
$val = (string) $val;
|
||||
});
|
||||
}
|
||||
|
||||
return $value ?? [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function shouldProcess($process): bool
|
||||
{
|
||||
$test = $this->process();
|
||||
|
||||
return !empty($test[$process]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isPage(): bool
|
||||
{
|
||||
return !in_array($this->template(), ['', 'folder'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isDir(): bool
|
||||
{
|
||||
return !$this->isPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
abstract public function exists(): bool;
|
||||
|
||||
abstract public function getProperty($property, $default = null);
|
||||
abstract public function setProperty($property, $value);
|
||||
abstract public function &getArrayProperty($property, $default = null, $doCreate = false);
|
||||
|
||||
|
||||
protected function offsetLoad_header($value)
|
||||
{
|
||||
if ($value instanceof Header) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
$value = [];
|
||||
} elseif ($value instanceof \stdClass) {
|
||||
$value = (array)$value;
|
||||
}
|
||||
|
||||
return new Header($value);
|
||||
}
|
||||
|
||||
protected function offsetPrepare_header($value)
|
||||
{
|
||||
return $this->offsetLoad_header($value);
|
||||
}
|
||||
|
||||
protected function offsetSerialize_header(?Header $value)
|
||||
{
|
||||
return $value ? $value->toArray() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
protected function pageContentValue($name, $default = null)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'frontmatter':
|
||||
$frontmatter = $this->getArrayProperty('frontmatter');
|
||||
if ($frontmatter === null) {
|
||||
$header = $this->prepareStorage()['header'] ?? null;
|
||||
if ($header) {
|
||||
$formatter = new YamlFormatter();
|
||||
$frontmatter = $formatter->encode($header);
|
||||
} else {
|
||||
$frontmatter = '';
|
||||
}
|
||||
}
|
||||
return $frontmatter;
|
||||
case 'content':
|
||||
return $this->getProperty('markdown');
|
||||
case 'order':
|
||||
return ((int)$this->order()) ?: '';
|
||||
case 'menu':
|
||||
return $this->menu();
|
||||
case 'ordering':
|
||||
return (bool)$this->order();
|
||||
case 'folder':
|
||||
return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder());
|
||||
case 'slug':
|
||||
return $this->slug();
|
||||
case 'published':
|
||||
return $this->published();
|
||||
case 'visible':
|
||||
return $this->visible();
|
||||
case 'media':
|
||||
return $this->media()->all();
|
||||
case 'media.file':
|
||||
return $this->media()->files();
|
||||
case 'media.video':
|
||||
return $this->media()->videos();
|
||||
case 'media.image':
|
||||
return $this->media()->images();
|
||||
case 'media.audio':
|
||||
return $this->media()->audios();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $size
|
||||
* @param bool $textOnly
|
||||
* @return string
|
||||
*/
|
||||
protected function processSummary($size = null, $textOnly = false): string
|
||||
{
|
||||
$config = (array)Grav::instance()['config']->get('site.summary');
|
||||
$config_page = (array)$this->getNestedProperty('header.summary');
|
||||
if ($config_page) {
|
||||
$config = array_merge($config, $config_page);
|
||||
}
|
||||
|
||||
// Summary is not enabled, return the whole content.
|
||||
if (empty($config['enabled'])) {
|
||||
return $this->content();
|
||||
}
|
||||
|
||||
$content = $this->_summary ?? $this->content();
|
||||
if ($textOnly) {
|
||||
$content = strip_tags($content);
|
||||
}
|
||||
$content_size = mb_strwidth($content, 'utf-8');
|
||||
$summary_size = $this->_summary !== null ? $content_size : $this->getProperty('summary_size');
|
||||
|
||||
// Return calculated summary based on summary divider's position.
|
||||
$format = $config['format'] ?? '';
|
||||
|
||||
// Return entire page content on wrong/unknown format.
|
||||
if ($format !== 'long' && $format !== 'short') {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ($format === 'short' && null !== $summary_size) {
|
||||
// Slice the string on breakpoint.
|
||||
if ($content_size > $summary_size) {
|
||||
return mb_substr($content, 0, $summary_size);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
// If needed, get summary size from the config.
|
||||
$size = $size ?? $config['size'] ?? null;
|
||||
|
||||
// Return calculated summary based on defaults.
|
||||
$size = is_numeric($size) ? (int)$size : -1;
|
||||
if ($size < 0) {
|
||||
$size = 300;
|
||||
}
|
||||
|
||||
// If the size is zero or smaller than the summary limit, return the entire page content.
|
||||
if ($size === 0 || $content_size <= $size) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Only return string but not html, wrap whatever html tag you want when using.
|
||||
if ($textOnly) {
|
||||
return mb_strimwidth($content, 0, $size, '...', 'utf-8');
|
||||
}
|
||||
|
||||
$summary = Utils::truncateHTML($content, $size);
|
||||
|
||||
return html_entity_decode($summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and Sets the content based on content portion of the .md file
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processContent($content): string
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
$process_markdown = $this->shouldProcess('markdown');
|
||||
$process_twig = $this->shouldProcess('twig') || $this->modularTwig();
|
||||
$cache_enable = $this->getNestedProperty('header.cache_enable') ?? $config->get('system.cache.enabled', true);
|
||||
|
||||
$twig_first = $this->getNestedProperty('header.twig_first') ?? $config->get('system.pages.twig_first', true);
|
||||
$never_cache_twig = $this->getNestedProperty('header.never_cache_twig') ?? $config->get('system.pages.never_cache_twig', false);
|
||||
|
||||
$cached = null;
|
||||
if ($cache_enable) {
|
||||
$cache = $this->getCache('render');
|
||||
$key = md5($this->getCacheKey() . '-content');
|
||||
$cached = $cache->get($key);
|
||||
if ($cached && $cached['checksum'] === $this->getCacheChecksum()) {
|
||||
$this->_content = $cached['content'] ?? '';
|
||||
$this->_content_meta = $cached['content_meta'] ?? null;
|
||||
|
||||
if ($process_twig && $never_cache_twig) {
|
||||
$this->_content = $this->processTwig($this->_content);
|
||||
}
|
||||
} else {
|
||||
$cached = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$cached) {
|
||||
$markdown_options = [];
|
||||
if ($process_markdown) {
|
||||
// Build markdown options.
|
||||
$markdown_options = (array)$config->get('system.pages.markdown');
|
||||
$markdown_page_options = (array)$this->getNestedProperty('header.markdown');
|
||||
if ($markdown_page_options) {
|
||||
$markdown_options = array_merge($markdown_options, $markdown_page_options);
|
||||
}
|
||||
|
||||
// pages.markdown_extra is deprecated, but still check it...
|
||||
if (!isset($markdown_options['extra'])) {
|
||||
$extra = $this->getNestedProperty('header.markdown_extra') ?? $config->get('system.pages.markdown_extra');
|
||||
if (null !== $extra) {
|
||||
user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
|
||||
|
||||
$markdown_options['extra'] = $extra;
|
||||
}
|
||||
}
|
||||
}
|
||||
$options = [
|
||||
'markdown' => $markdown_options,
|
||||
'images' => $config->get('system.images', [])
|
||||
];
|
||||
|
||||
$this->_content = $content;
|
||||
$grav->fireEvent('onPageContentRaw', new Event(['page' => $this]));
|
||||
|
||||
if ($twig_first && !$never_cache_twig) {
|
||||
if ($process_twig) {
|
||||
$this->_content = $this->processTwig($this->_content);
|
||||
}
|
||||
|
||||
if ($process_markdown) {
|
||||
$this->_content = $this->processMarkdown($this->_content, $options);
|
||||
}
|
||||
|
||||
// Content Processed but not cached yet
|
||||
$grav->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
} else {
|
||||
if ($process_markdown) {
|
||||
$this->_content = $this->processMarkdown($this->_content, $options);
|
||||
}
|
||||
|
||||
// Content Processed but not cached yet
|
||||
$grav->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
if ($cache_enable && $never_cache_twig) {
|
||||
$this->cachePageContent();
|
||||
}
|
||||
|
||||
if ($process_twig) {
|
||||
$this->_content = $this->processTwig($this->_content);
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache_enable && !$never_cache_twig) {
|
||||
$this->cachePageContent();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle summary divider
|
||||
$delimiter = $config->get('site.summary.delimiter', '===');
|
||||
$divider_pos = mb_strpos($this->_content, "<p>{$delimiter}</p>");
|
||||
if ($divider_pos !== false) {
|
||||
$this->setProperty('summary_size', $divider_pos);
|
||||
$this->_content = str_replace("<p>{$delimiter}</p>", '', $this->_content);
|
||||
}
|
||||
|
||||
// Fire event when Page::content() is called
|
||||
$grav->fireEvent('onPageContent', new Event(['page' => $this]));
|
||||
|
||||
return $this->_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the Twig page content.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function processTwig($content): string
|
||||
{
|
||||
/** @var Twig $twig */
|
||||
$twig = Grav::instance()['twig'];
|
||||
|
||||
/** @var PageInterface $this */
|
||||
return $twig->processPage($this, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the Markdown content.
|
||||
*
|
||||
* Uses Parsedown or Parsedown Extra depending on configuration.
|
||||
*
|
||||
* @param string $content
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processMarkdown($content, array $options = []): string
|
||||
{
|
||||
/** @var PageInterface $this */
|
||||
$excerpts = new Excerpts($this, $options);
|
||||
|
||||
// Initialize the preferred variant of markdown parser.
|
||||
if (isset($defaults['extra'])) {
|
||||
$parsedown = new ParsedownExtra($excerpts);
|
||||
} else {
|
||||
$parsedown = new Parsedown($excerpts);
|
||||
}
|
||||
|
||||
return $parsedown->text($content);
|
||||
}
|
||||
|
||||
abstract protected function loadHeaderProperty(string $property, $var, callable $filter);
|
||||
}
|
||||
1098
system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php
Normal file
1098
system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,463 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 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 RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Implements PageRoutableInterface
|
||||
*/
|
||||
trait PageRoutableTrait
|
||||
{
|
||||
/**
|
||||
* 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 $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,
|
||||
function($value) {
|
||||
return ($value ?? true) && $this->published() && $this->getLanguages(true);
|
||||
}
|
||||
);
|
||||
|
||||
return $value && $this->published() && !$this->modular();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// trim trailing / if not root
|
||||
if ($url !== '/') {
|
||||
$url = rtrim($url, '/');
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// TODO:
|
||||
if (null !== $var) {
|
||||
throw new \RuntimeException(__METHOD__ . '(string): Not Implemented');
|
||||
}
|
||||
|
||||
// TODO: implement rest of the routing:
|
||||
return $this->rawRoute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
// TODO: missing full implementation
|
||||
return '/' . $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route aliases for the page based on page headers.
|
||||
*
|
||||
* @param array $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);
|
||||
}
|
||||
|
||||
// FIXME: check route() logic of Page
|
||||
return (array)$this->getNestedProperty('header.routes.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
|
||||
*/
|
||||
public function routeCanonical($var = null): string
|
||||
{
|
||||
if (null !== $var) {
|
||||
$this->setNestedProperty('header.routes.canonical', (array)$var);
|
||||
}
|
||||
|
||||
return $this->getNestedProperty('header.routes.canonical', $this->route());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirect set in the header.
|
||||
*
|
||||
* @param string $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'];
|
||||
|
||||
return $locator->findResource($folder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $var the path
|
||||
*
|
||||
* @return string|null the path
|
||||
*/
|
||||
public function path($var = null): ?string
|
||||
{
|
||||
// TODO:
|
||||
if (null !== $var) {
|
||||
throw new \RuntimeException(__METHOD__ . '(string): Not Implemented');
|
||||
}
|
||||
|
||||
if ($this->root()) {
|
||||
$folder = $this->getFlexDirectory()->getStorageFolder();
|
||||
} else {
|
||||
$folder = $this->getStorageFolder();
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
return $folder ? $locator($folder) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the folder.
|
||||
*
|
||||
* @param string $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->getStorageKey(true) ?: $this->getKey();
|
||||
}
|
||||
|
||||
return basename($value) ?: null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the folder.
|
||||
*
|
||||
* @param string $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) {
|
||||
$value = $this->getStorageKey(true) ?: $this->getKey();
|
||||
$value = ltrim(dirname("/{$value}"), '/') ?: '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and Sets the parent object for this page
|
||||
*
|
||||
* @param PageInterface $var the parent page object
|
||||
*
|
||||
* @return PageInterface|null the parent page object if it exists.
|
||||
*/
|
||||
public function parent(PageInterface $var = null)
|
||||
{
|
||||
// TODO:
|
||||
if (null !== $var) {
|
||||
throw new \RuntimeException(__METHOD__ . '(PageInterface): Not Implemented');
|
||||
}
|
||||
|
||||
$parentKey = ltrim(dirname("/{$this->getKey()}"), '/');
|
||||
|
||||
return $parentKey ? $this->getFlexDirectory()->getObject($parentKey) : $this->getFlexDirectory()->getIndex()->getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top parent object for this page
|
||||
*
|
||||
* @return PageInterface|null the top parent page object if it exists.
|
||||
*/
|
||||
public function topParent()
|
||||
{
|
||||
$topParent = $this->parent();
|
||||
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) {
|
||||
return $collection->currentPosition($this->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();
|
||||
$uri = $grav['uri'];
|
||||
$pages = $grav['pages'];
|
||||
$uri_path = rtrim(urldecode($uri->path()), '/');
|
||||
$routes = $pages->routes();
|
||||
|
||||
if (isset($routes[$uri_path])) {
|
||||
/** @var PageInterface $child_page */
|
||||
$child_page = $pages->dispatch($uri->route())->parent();
|
||||
if ($child_page) {
|
||||
while (!$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.
|
||||
*
|
||||
* @return bool True if it is the root
|
||||
*/
|
||||
public function root(): bool
|
||||
{
|
||||
return $this->getKey() === '/';
|
||||
}
|
||||
|
||||
abstract protected function loadHeaderProperty(string $property, $var, callable $filter);
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Flex\Pages\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
|
||||
/**
|
||||
* Implements PageTranslateInterface
|
||||
*/
|
||||
trait PageTranslateTrait
|
||||
{
|
||||
/** @var array|null */
|
||||
private $_languages;
|
||||
|
||||
/** @var PageInterface[] */
|
||||
private $_translations = [];
|
||||
|
||||
/**
|
||||
* @param string|null $languageCode
|
||||
* @param bool|null $fallback
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTranslation(string $languageCode = null, bool $fallback = null): bool
|
||||
{
|
||||
$code = $this->findTranslation($languageCode, $fallback);
|
||||
|
||||
return null !== $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $languageCode
|
||||
* @param bool|null $fallback
|
||||
* @return static|null
|
||||
*/
|
||||
public function getTranslation(string $languageCode = null, bool $fallback = null)
|
||||
{
|
||||
$code = $this->findTranslation($languageCode, $fallback);
|
||||
if (null === $code) {
|
||||
$object = null;
|
||||
} elseif ('' === $code) {
|
||||
$object = $this->getLanguage() ? $this->getFlexDirectory()->getObject($this->getStorageKey(true), 'storage_key') : $this;
|
||||
} else {
|
||||
$key = $this->getStorageKey() . '|.' . $code;
|
||||
$meta = ['storage_key' => $key, 'lang' => $code] + $this->getMetaData();
|
||||
$object = $this->getFlexDirectory()->loadObjects([$key => $meta])[$key] ?? null;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeDefault
|
||||
* @return array
|
||||
*/
|
||||
public function getAllLanguages(bool $includeDefault = false): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
$languages = $language->getLanguages();
|
||||
if (!$languages) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$translated = $this->getLanguageTemplates();
|
||||
|
||||
if ($includeDefault) {
|
||||
$languages[] = '';
|
||||
} else {
|
||||
unset($translated['']);
|
||||
}
|
||||
|
||||
$languages = array_fill_keys($languages, false);
|
||||
$translated = array_fill_keys(array_keys($translated), true);
|
||||
|
||||
return array_replace($languages, $translated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeDefault
|
||||
* @return array
|
||||
*/
|
||||
public function getLanguages(bool $includeDefault = false): array
|
||||
{
|
||||
$languages = $this->getLanguageTemplates();
|
||||
if (!$includeDefault) {
|
||||
unset($languages['']);
|
||||
}
|
||||
|
||||
return array_keys($this->getLanguageTemplates());
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->language() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $languageCode
|
||||
* @param array|null $fallback
|
||||
* @return string|null
|
||||
*/
|
||||
protected function findTranslation(string $languageCode = null, bool $fallback = null): ?string
|
||||
{
|
||||
$translated = $this->getLanguageTemplates();
|
||||
|
||||
// If there's no translations (including default), we have an empty folder.
|
||||
if (!$translated) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// FIXME: only published is not implemented...
|
||||
$languages = $this->getFallbackLanguages($languageCode, $fallback);
|
||||
|
||||
$language = null;
|
||||
foreach ($languages as $code) {
|
||||
if (isset($translated[$code])) {
|
||||
$language = $code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with the routes of other translated languages
|
||||
*
|
||||
* @param bool $onlyPublished only return published translations
|
||||
*
|
||||
* @return array the page translated languages
|
||||
*/
|
||||
public function translatedLanguages($onlyPublished = false): array
|
||||
{
|
||||
// FIXME: only published is not implemented...
|
||||
$translated = $this->getLanguageTemplates();
|
||||
if (!$translated) {
|
||||
return $translated;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
$languages = $language->getLanguages();
|
||||
$languages[] = '';
|
||||
|
||||
$translated = array_intersect_key($translated, array_flip($languages));
|
||||
$list = array_fill_keys($languages, null);
|
||||
foreach ($translated as $languageCode => $languageFile) {
|
||||
$path = ($languageCode ? '/' : '') . $languageCode;
|
||||
$list[$languageCode] = "{$path}/{$this->getKey()}";
|
||||
}
|
||||
|
||||
return array_filter($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array listing untranslated languages available
|
||||
*
|
||||
* @param bool $includeUnpublished also list unpublished translations
|
||||
*
|
||||
* @return array the page untranslated languages
|
||||
*/
|
||||
public function untranslatedLanguages($includeUnpublished = false): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$translated = array_keys($this->translatedLanguages(!$includeUnpublished));
|
||||
|
||||
return array_values(array_diff($languages, $translated));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page language
|
||||
*
|
||||
* @param $var
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function language($var = null): ?string
|
||||
{
|
||||
return $this->loadHeaderProperty(
|
||||
'lang',
|
||||
$var,
|
||||
function($value) {
|
||||
$value = $value ?? $this->getMetaData()['lang'] ?? '';
|
||||
|
||||
return trim($value) ?: null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getLanguageTemplates(): array
|
||||
{
|
||||
if (null === $this->_languages) {
|
||||
$template = $this->getProperty('template');
|
||||
$storage = $this->getStorage();
|
||||
$translations = $storage['markdown'] ?? [];
|
||||
$list = [];
|
||||
foreach ($translations as $code => $search) {
|
||||
if (isset($search[$template])) {
|
||||
// Use main template if possible.
|
||||
$list[$code] = $template;
|
||||
} elseif (!empty($search)) {
|
||||
// Fall back to first matching template.
|
||||
$list[$code] = key($search);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_languages = $list;
|
||||
}
|
||||
|
||||
return $this->_languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $languageCode
|
||||
* @param bool|null $fallback
|
||||
* @return array
|
||||
*/
|
||||
protected function getFallbackLanguages(string $languageCode = null, bool $fallback = null): array
|
||||
{
|
||||
$fallback = $fallback ?? true;
|
||||
if (!$fallback && null !== $languageCode) {
|
||||
return [$languageCode];
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
$languageCode = $languageCode ?? $language->getLanguage();
|
||||
if ($languageCode === '' && $fallback) {
|
||||
return $language->getFallbackLanguages(null, true);
|
||||
}
|
||||
|
||||
return $fallback ? $language->getFallbackLanguages($languageCode, true) : [$languageCode];
|
||||
}
|
||||
|
||||
abstract protected function loadHeaderProperty(string $property, $var, callable $filter);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user