496 lines
13 KiB
PHP
496 lines
13 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package Grav\Framework\Flex
|
|
*
|
|
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
|
* @license MIT License; see LICENSE file for details.
|
|
*/
|
|
|
|
namespace Grav\Framework\Flex\Pages;
|
|
|
|
use DateTime;
|
|
use Exception;
|
|
use Grav\Common\Debugger;
|
|
use Grav\Common\Grav;
|
|
use Grav\Common\Page\Interfaces\PageInterface;
|
|
use Grav\Common\Page\Traits\PageFormTrait;
|
|
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
|
use Grav\Framework\Flex\FlexObject;
|
|
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
|
use Grav\Framework\Flex\Interfaces\FlexTranslateInterface;
|
|
use Grav\Framework\Flex\Pages\Traits\PageAuthorsTrait;
|
|
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 RuntimeException;
|
|
use stdClass;
|
|
use function array_key_exists;
|
|
use function is_array;
|
|
|
|
/**
|
|
* Class FlexPageObject
|
|
* @package Grav\Plugin\FlexObjects\Types\FlexPages
|
|
*/
|
|
class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateInterface
|
|
{
|
|
use PageAuthorsTrait;
|
|
use PageContentTrait;
|
|
use PageFormTrait;
|
|
use PageLegacyTrait;
|
|
use PageRoutableTrait;
|
|
use PageTranslateTrait;
|
|
use FlexMediaTrait;
|
|
|
|
public const PAGE_ORDER_REGEX = '/^(\d+)\.(.*)$/u';
|
|
public const PAGE_ORDER_PREFIX_REGEX = '/^[0-9]+\./u';
|
|
|
|
/** @var array|null */
|
|
protected $_reorder;
|
|
/** @var FlexPageObject|null */
|
|
protected $_original;
|
|
|
|
/**
|
|
* Clone page.
|
|
*/
|
|
public function __clone()
|
|
{
|
|
parent::__clone();
|
|
|
|
if (isset($this->header)) {
|
|
$this->header = clone($this->header);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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,
|
|
'isOrdered' => true,
|
|
'isVisible' => true,
|
|
'isRoutable' => true,
|
|
'getCreated_Timestamp' => true,
|
|
'getPublish_Timestamp' => true,
|
|
'getUnpublish_Timestamp' => true,
|
|
'getUpdated_Timestamp' => true,
|
|
] + parent::getCachedMethods();
|
|
}
|
|
|
|
/**
|
|
* @param bool $test
|
|
* @return bool
|
|
*/
|
|
public function isPublished(bool $test = true): bool
|
|
{
|
|
$time = time();
|
|
$start = $this->getPublish_Timestamp();
|
|
$stop = $this->getUnpublish_Timestamp();
|
|
|
|
return $this->published() && $start <= $time && (!$stop || $time <= $stop) === $test;
|
|
}
|
|
|
|
/**
|
|
* @param bool $test
|
|
* @return bool
|
|
*/
|
|
public function isOrdered(bool $test = true): bool
|
|
{
|
|
return ($this->order() !== false) === $test;
|
|
}
|
|
|
|
/**
|
|
* @param bool $test
|
|
* @return bool
|
|
*/
|
|
public function isVisible(bool $test = true): bool
|
|
{
|
|
return $this->visible() === $test;
|
|
}
|
|
|
|
/**
|
|
* @param bool $test
|
|
* @return bool
|
|
*/
|
|
public function isRoutable(bool $test = true): bool
|
|
{
|
|
return $this->routable() === $test;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
case 'header.permissions.groups':
|
|
$encoded = json_encode($this->getPermissions());
|
|
if ($encoded === false) {
|
|
throw new RuntimeException('json_encode(): failed to encode group permissions');
|
|
}
|
|
|
|
return json_decode($encoded, true);
|
|
}
|
|
|
|
return parent::getFormValue($name, $default, $separator);
|
|
}
|
|
|
|
/**
|
|
* Get master storage key.
|
|
*
|
|
* @return string
|
|
* @see FlexObjectInterface::getStorageKey()
|
|
*/
|
|
public function getMasterKey(): string
|
|
{
|
|
$key = (string)($this->storage_key ?? $this->getMetaData()['storage_key'] ?? null);
|
|
if (($pos = strpos($key, '|')) !== false) {
|
|
$key = substr($key, 0, $pos);
|
|
}
|
|
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* {@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|FlexObjectInterface
|
|
*/
|
|
public function save($reorder = true)
|
|
{
|
|
return parent::save();
|
|
}
|
|
|
|
/**
|
|
* Gets the Page Unmodified (original) version of the page.
|
|
*
|
|
* Assumes that object has been cloned before modifying it.
|
|
*
|
|
* @return FlexPageObject|null The original version of the page.
|
|
*/
|
|
public function getOriginal()
|
|
{
|
|
return $this->_original;
|
|
}
|
|
|
|
/**
|
|
* Store the Page Unmodified (original) version of the page.
|
|
*
|
|
* Can be called multiple times, only the first call matters.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function storeOriginal(): void
|
|
{
|
|
if (null === $this->_original) {
|
|
$this->_original = clone $this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 $value
|
|
* @return $this
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @param string $property
|
|
* @param mixed $value
|
|
* @param string|null $separator
|
|
* @return $this
|
|
*/
|
|
public function setNestedProperty($property, $value, $separator = null)
|
|
{
|
|
$separator = $separator ?: '.';
|
|
if (strpos($property, 'header' . $separator) === 0) {
|
|
$this->getProperty('header')->set(str_replace('header' . $separator, '', $property), $value, $separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
parent::setNestedProperty($property, $value, $separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string $property
|
|
* @param string|null $separator
|
|
* @return $this
|
|
*/
|
|
public function unsetNestedProperty($property, $separator = null)
|
|
{
|
|
$separator = $separator ?: '.';
|
|
if (strpos($property, 'header' . $separator) === 0) {
|
|
$this->getProperty('header')->undef(str_replace('header' . $separator, '', $property), $separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
parent::unsetNestedProperty($property, $separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param array $elements
|
|
* @param bool $extended
|
|
* @return void
|
|
*/
|
|
protected function filterElements(array &$elements, bool $extended = false): void
|
|
{
|
|
// Markdown storage conversion to page structure.
|
|
if (array_key_exists('content', $elements)) {
|
|
$elements['markdown'] = $elements['content'];
|
|
unset($elements['content']);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @return UserCollectionInterface|null
|
|
* @internal
|
|
*/
|
|
protected function loadAccounts()
|
|
{
|
|
return Grav::instance()['accounts'] ?? null;
|
|
}
|
|
}
|