commit fonctionne ou pas ?
This commit is contained in:
@@ -0,0 +1,707 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Collection
|
||||
* @package Grav\Common\Page
|
||||
*/
|
||||
class Collection extends Iterator implements PageCollectionInterface
|
||||
{
|
||||
/** @var Pages */
|
||||
protected $pages;
|
||||
/** @var array */
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* Collection constructor.
|
||||
*
|
||||
* @param array $items
|
||||
* @param array $params
|
||||
* @param Pages|null $pages
|
||||
*/
|
||||
public function __construct($items = [], array $params = [], Pages $pages = null)
|
||||
{
|
||||
parent::__construct($items);
|
||||
|
||||
$this->params = $params;
|
||||
$this->pages = $pages ?: Grav::instance()->offsetGet('pages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @return $this
|
||||
*/
|
||||
public function addPage(PageInterface $page)
|
||||
{
|
||||
$this->items[$page->path()] = ['slug' => $page->slug()];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a page with path and slug
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $slug
|
||||
* @return $this
|
||||
*/
|
||||
public function add($path, $slug)
|
||||
{
|
||||
$this->items[$path] = ['slug' => $slug];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return new static($this->items, $this->params, $this->pages);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
foreach ($collection as $page) {
|
||||
$this->addPage($page);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
$array1 = $this->items;
|
||||
$array2 = $collection->toArray();
|
||||
|
||||
$this->items = array_uintersect($array1, $array2, function ($val1, $val2) {
|
||||
return strcmp($val1['slug'], $val2['slug']);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current page.
|
||||
*/
|
||||
public function setCurrent(string $path): void
|
||||
{
|
||||
reset($this->items);
|
||||
|
||||
while (($key = key($this->items)) !== null && $key !== $path) {
|
||||
next($this->items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current page.
|
||||
*
|
||||
* @return PageInterface
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$current = parent::key();
|
||||
|
||||
return $this->pages->get($current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current slug.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
$current = parent::current();
|
||||
|
||||
return $current['slug'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return PageInterface|null
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->pages->get($offset) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return Collection[]
|
||||
*/
|
||||
public function batch($size)
|
||||
{
|
||||
$chunks = array_chunk($this->items, $size, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($chunks as $chunk) {
|
||||
$list[] = new static($chunk, $this->params, $this->pages);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param PageInterface|string|null $key
|
||||
* @return $this
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function remove($key = null)
|
||||
{
|
||||
if ($key instanceof PageInterface) {
|
||||
$key = $key->path();
|
||||
} elseif (null === $key) {
|
||||
$key = (string)key($this->items);
|
||||
}
|
||||
if (!is_string($key)) {
|
||||
throw new InvalidArgumentException('Invalid argument $key.');
|
||||
}
|
||||
|
||||
parent::remove($key);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array|null $manual
|
||||
* @param string|null $sort_flags
|
||||
* @return $this
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
|
||||
{
|
||||
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[count($this->items) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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|Collection The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
$values = array_keys($this->items);
|
||||
$keys = array_flip($values);
|
||||
|
||||
if (array_key_exists($path, $keys)) {
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, array_keys($this->items), true);
|
||||
|
||||
return $pos !== false ? $pos : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where start date and end date are optional
|
||||
* Dates must be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string|null $startDate
|
||||
* @param string|null $endDate
|
||||
* @param string|null $field
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dateRange($startDate = null, $endDate = null, $field = null)
|
||||
{
|
||||
$start = $startDate ? Utils::date2timestamp($startDate) : null;
|
||||
$end = $endDate ? Utils::date2timestamp($endDate) : null;
|
||||
|
||||
$date_range = [];
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$date = $field ? strtotime($page->value($field)) : $page->date();
|
||||
|
||||
if ((!$start || $date >= $start) && (!$end || $date <= $end)) {
|
||||
$date_range[$path] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $date_range;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return Collection The collection with only visible pages
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
$visible = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && $page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return Collection The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
$visible = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && !$page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages
|
||||
*
|
||||
* @return Collection The collection with only pages
|
||||
*/
|
||||
public function pages()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && !$page->isModule()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modules
|
||||
*
|
||||
* @return Collection The collection with only modules
|
||||
*/
|
||||
public function modules()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && $page->isModule()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of pages()
|
||||
*
|
||||
* @return Collection The collection with only non-module pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
$this->pages();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of modules()
|
||||
*
|
||||
* @return Collection The collection with only modules
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
$this->modules();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only translated pages
|
||||
*
|
||||
* @return Collection The collection with only published pages
|
||||
* @internal
|
||||
*/
|
||||
public function translated()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && $page->translated()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only untranslated pages
|
||||
*
|
||||
* @return Collection The collection with only non-published pages
|
||||
* @internal
|
||||
*/
|
||||
public function nonTranslated()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && !$page->translated()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return Collection The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && $page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return Collection The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && !$page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return Collection The collection with only routable pages
|
||||
*/
|
||||
public function routable()
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
|
||||
if ($page !== null && $page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $routable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return Collection The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && !$page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $routable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
* @return Collection The collection
|
||||
*/
|
||||
public function ofType($type)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && $page->template() === $type) {
|
||||
$items[$path] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
* @return Collection The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page !== null && in_array($page->template(), $types, true)) {
|
||||
$items[$path] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
* @return Collection The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
|
||||
if ($page !== null && isset($page->header()->access)) {
|
||||
if (is_array($page->header()->access)) {
|
||||
//Multiple values for access
|
||||
$valid = false;
|
||||
|
||||
foreach ($page->header()->access as $index => $accessLevel) {
|
||||
if (is_array($accessLevel)) {
|
||||
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
|
||||
if (in_array($innerAccessLevel, $accessLevels, false)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (in_array($index, $accessLevels, false)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($valid) {
|
||||
$items[$path] = $slug;
|
||||
}
|
||||
} else {
|
||||
//Single value for access
|
||||
if (in_array($page->header()->access, $accessLevels, false)) {
|
||||
$items[$path] = $slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toExtendedArray()
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
|
||||
if ($page !== null) {
|
||||
$items[$page->route()] = $page->toArray();
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonSerializable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
|
||||
|
||||
/**
|
||||
* Class Header
|
||||
* @package Grav\Common\Page
|
||||
*/
|
||||
class Header implements ArrayAccess, ExportInterface, JsonSerializable
|
||||
{
|
||||
use NestedArrayAccessWithGetters, Constructor, Export;
|
||||
|
||||
/** @var array */
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Serializable;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Interface PageCollectionInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PageCollectionInterface extends Traversable, ArrayAccess, Countable, Serializable
|
||||
{
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params();
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params);
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @return $this
|
||||
*/
|
||||
public function addPage(PageInterface $page);
|
||||
|
||||
/**
|
||||
* Add a page with path and slug
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $slug
|
||||
* @return $this
|
||||
*/
|
||||
//public function add($path, $slug);
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy();
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return PageCollectionInterface
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return PageCollectionInterface
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return PageCollectionInterface[]
|
||||
*/
|
||||
public function batch($size);
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param PageInterface|string|null $key
|
||||
* @return PageCollectionInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
//public function remove($key = null);
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array|null $manual
|
||||
* @param string|null $sort_flags
|
||||
* @return PageCollectionInterface
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
* @return PageInterface The previous item.
|
||||
*/
|
||||
public function prevSibling($path);
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
* @return PageInterface The next item.
|
||||
*/
|
||||
public function nextSibling($path);
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $direction either -1 or +1
|
||||
* @return PageInterface|PageCollectionInterface|false The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where start date and end date are optional
|
||||
* Dates must be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string|null $startDate
|
||||
* @param string|null $endDate
|
||||
* @param string|null $field
|
||||
* @return PageCollectionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dateRange($startDate = null, $endDate = null, $field = null);
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only visible pages
|
||||
*/
|
||||
public function visible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only pages
|
||||
*/
|
||||
public function pages();
|
||||
|
||||
/**
|
||||
* Creates new collection with only modules
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modules
|
||||
*/
|
||||
public function modules();
|
||||
|
||||
/**
|
||||
* Creates new collection with only modules
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modules
|
||||
* @deprecated 1.7 Use $this->modules() instead
|
||||
*/
|
||||
public function modular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-module pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-module pages
|
||||
* @deprecated 1.7 Use $this->pages() instead
|
||||
*/
|
||||
public function nonModular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only published pages
|
||||
*/
|
||||
public function published();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished();
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only routable pages
|
||||
*/
|
||||
public function routable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofType($type);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels);
|
||||
|
||||
/**
|
||||
* Converts collection into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toExtendedArray();
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
|
||||
/**
|
||||
* Methods currently implemented in Flex Page emulation layer.
|
||||
*/
|
||||
interface PageContentInterface
|
||||
{
|
||||
/**
|
||||
* Gets and Sets the header based on the YAML configuration at the top of the .md file
|
||||
*
|
||||
* @param object|array|null $var a YAML object representing the configuration for the file
|
||||
* @return object the current YAML configuration
|
||||
*/
|
||||
public function header($var = null);
|
||||
|
||||
/**
|
||||
* Get the summary.
|
||||
*
|
||||
* @param int|null $size Max summary size.
|
||||
* @param bool $textOnly Only count text size.
|
||||
* @return string
|
||||
*/
|
||||
public function summary($size = null, $textOnly = false);
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $summary Summary
|
||||
*/
|
||||
public function setSummary($summary);
|
||||
|
||||
/**
|
||||
* Gets and Sets the content based on content portion of the .md file
|
||||
*
|
||||
* @param string|null $var Content
|
||||
* @return string Content
|
||||
*/
|
||||
public function content($var = null);
|
||||
|
||||
/**
|
||||
* Needed by the onPageContentProcessed event to get the raw page content
|
||||
*
|
||||
* @return string the current page content
|
||||
*/
|
||||
public function getRawContent();
|
||||
|
||||
/**
|
||||
* Needed by the onPageContentProcessed event to set the raw page content
|
||||
*
|
||||
* @param string|null $content
|
||||
*/
|
||||
public function setRawContent($content);
|
||||
|
||||
/**
|
||||
* Gets and Sets the Page raw content
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string
|
||||
*/
|
||||
public function rawMarkdown($var = null);
|
||||
|
||||
/**
|
||||
* Get value from a page variable (used mostly for creating edit forms).
|
||||
*
|
||||
* @param string $name Variable name.
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function value($name, $default = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the associated media as found in the page folder.
|
||||
*
|
||||
* @param MediaCollectionInterface|null $var New media object.
|
||||
* @return MediaCollectionInterface Representation of associated media.
|
||||
*/
|
||||
public function media($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
|
||||
*
|
||||
* @param string|null $var New title of the Page
|
||||
* @return string The title of the Page
|
||||
*/
|
||||
public function title($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the menu name for this Page. This is the text that can be used specifically for navigation.
|
||||
* If no menu field is set, it will use the title()
|
||||
*
|
||||
* @param string|null $var New menu field for the page
|
||||
* @return string The menu field for the page
|
||||
*/
|
||||
public function menu($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets whether or not this Page is visible for navigation
|
||||
*
|
||||
* @param bool|null $var New value
|
||||
* @return bool True if the page is visible
|
||||
*/
|
||||
public function visible($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets whether or not this Page is considered published
|
||||
*
|
||||
* @param bool|null $var New value
|
||||
* @return bool True if the page is published
|
||||
*/
|
||||
public function published($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the Page publish date
|
||||
*
|
||||
* @param string|null $var String representation of the new date
|
||||
* @return int Unix timestamp representation of the date
|
||||
*/
|
||||
public function publishDate($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the Page unpublish date
|
||||
*
|
||||
* @param string|null $var String representation of the new date
|
||||
* @return int|null Unix timestamp representation of the date
|
||||
*/
|
||||
public function unpublishDate($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of
|
||||
* a simple array of arrays with the form array("markdown"=>true) for example
|
||||
*
|
||||
* @param array|null $var New array of name value pairs where the name is the process and value is true or false
|
||||
* @return array Array of name value pairs where the name is the process and value is true or false
|
||||
*/
|
||||
public function process($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
|
||||
* the parent folder from the path
|
||||
*
|
||||
* @param string|null $var New slug, e.g. 'my-blog'
|
||||
* @return string The slug
|
||||
*/
|
||||
public function slug($var = null);
|
||||
|
||||
/**
|
||||
* Get/set order number of this page.
|
||||
*
|
||||
* @param int|null $var New order as a number
|
||||
* @return string|bool Order in a form of '02.' or false if not set
|
||||
*/
|
||||
public function order($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the identifier for this Page object.
|
||||
*
|
||||
* @param string|null $var New identifier
|
||||
* @return string The identifier
|
||||
*/
|
||||
public function id($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the modified timestamp.
|
||||
*
|
||||
* @param int|null $var New modified unix timestamp
|
||||
* @return int Modified unix timestamp
|
||||
*/
|
||||
public function modified($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the option to show the last_modified header for the page.
|
||||
*
|
||||
* @param bool|null $var New last_modified header value
|
||||
* @return bool Show last_modified header
|
||||
*/
|
||||
public function lastModified($var = null);
|
||||
|
||||
/**
|
||||
* Get/set the folder.
|
||||
*
|
||||
* @param string|null $var New folder
|
||||
* @return string|null The folder
|
||||
*/
|
||||
public function folder($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the date for this Page object. This is typically passed in via the page headers
|
||||
*
|
||||
* @param string|null $var New string representation of a date
|
||||
* @return int Unix timestamp representation of the date
|
||||
*/
|
||||
public function date($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the date format for this Page object. This is typically passed in via the page headers
|
||||
* using typical PHP date string structure - http://php.net/manual/en/function.date.php
|
||||
*
|
||||
* @param string|null $var New string representation of a date format
|
||||
* @return string String representation of a date format
|
||||
*/
|
||||
public function dateformat($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with.
|
||||
*
|
||||
* @param array|null $var New array of taxonomies
|
||||
* @return array An array of taxonomies
|
||||
*/
|
||||
public function taxonomy($var = null);
|
||||
|
||||
/**
|
||||
* Gets the configured state of the processing method.
|
||||
*
|
||||
* @param string $process The process name, eg "twig" or "markdown"
|
||||
* @return bool Whether or not the processing method is enabled for this Page
|
||||
*/
|
||||
public function shouldProcess($process);
|
||||
|
||||
/**
|
||||
* Returns true if page is a module.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isModule(): bool;
|
||||
|
||||
/**
|
||||
* Returns whether or not this Page object has a .md file associated with it or if its just a directory.
|
||||
*
|
||||
* @return bool True if its a page with a .md file associated
|
||||
*/
|
||||
public function isPage();
|
||||
|
||||
/**
|
||||
* Returns whether or not this Page object is a directory or a page.
|
||||
*
|
||||
* @return bool True if its a directory
|
||||
*/
|
||||
public function isDir();
|
||||
|
||||
/**
|
||||
* Returns whether the page exists in the filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface PageFormInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PageFormInterface
|
||||
{
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
//public function getForms(): array;
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new/*, $override = true*/);
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms();//: array;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
|
||||
/**
|
||||
* Class implements page interface.
|
||||
*/
|
||||
interface PageInterface extends
|
||||
PageContentInterface,
|
||||
PageFormInterface,
|
||||
PageRoutableInterface,
|
||||
PageTranslateInterface,
|
||||
MediaInterface,
|
||||
PageLegacyInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Page\Collection;
|
||||
use InvalidArgumentException;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* Interface PageLegacyInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PageLegacyInterface
|
||||
{
|
||||
/**
|
||||
* Initializes the page instance variables based on a file
|
||||
*
|
||||
* @param SplFileInfo $file The file information for the .md file that the page represents
|
||||
* @param string|null $extension
|
||||
* @return $this
|
||||
*/
|
||||
public function init(SplFileInfo $file, $extension = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the raw data
|
||||
*
|
||||
* @param string|null $var Raw content string
|
||||
* @return string Raw content string
|
||||
*/
|
||||
public function raw($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the page frontmatter
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string
|
||||
*/
|
||||
public function frontmatter($var = null);
|
||||
|
||||
/**
|
||||
* Modify a header value directly
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function modifyHeader($key, $value);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function httpResponseCode();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function httpHeaders();
|
||||
|
||||
/**
|
||||
* Get the contentMeta array and initialize content first if it's not already
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function contentMeta();
|
||||
|
||||
/**
|
||||
* Add an entry to the page's contentMeta array
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addContentMeta($name, $value);
|
||||
|
||||
/**
|
||||
* Return the whole contentMeta array as it currently stands
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContentMeta($name = null);
|
||||
|
||||
/**
|
||||
* Sets the whole content meta array in one shot
|
||||
*
|
||||
* @param array $content_meta
|
||||
* @return array
|
||||
*/
|
||||
public function setContentMeta($content_meta);
|
||||
|
||||
/**
|
||||
* Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page
|
||||
*/
|
||||
public function cachePageContent();
|
||||
|
||||
/**
|
||||
* Get file object to the page.
|
||||
*
|
||||
* @return MarkdownFile|null
|
||||
*/
|
||||
public function file();
|
||||
|
||||
/**
|
||||
* Save page if there's a file assigned to it.
|
||||
*
|
||||
* @param bool|mixed $reorder Internal use.
|
||||
*/
|
||||
public function save($reorder = true);
|
||||
|
||||
/**
|
||||
* Prepare move page to new location. Moves also everything that's under the current page.
|
||||
*
|
||||
* You need to call $this->save() in order to perform the move.
|
||||
*
|
||||
* @param PageInterface $parent New parent page.
|
||||
* @return $this
|
||||
*/
|
||||
public function move(PageInterface $parent);
|
||||
|
||||
/**
|
||||
* Prepare a copy from the page. Copies also everything that's under the current page.
|
||||
*
|
||||
* Returns a new Page object for the copy.
|
||||
* You need to call $this->save() in order to perform the move.
|
||||
*
|
||||
* @param PageInterface $parent New parent page.
|
||||
* @return $this
|
||||
*/
|
||||
public function copy(PageInterface $parent);
|
||||
|
||||
/**
|
||||
* Get blueprints for the page.
|
||||
*
|
||||
* @return Blueprint
|
||||
*/
|
||||
public function blueprints();
|
||||
|
||||
/**
|
||||
* Get the blueprint name for this page. Use the blueprint form field if set
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function blueprintName();
|
||||
|
||||
/**
|
||||
* Validate page header.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validate();
|
||||
|
||||
/**
|
||||
* Filter page header from illegal contents.
|
||||
*/
|
||||
public function filter();
|
||||
|
||||
/**
|
||||
* Get unknown header variables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extra();
|
||||
|
||||
/**
|
||||
* Convert page to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
|
||||
/**
|
||||
* Convert page to YAML encoded string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toYaml();
|
||||
|
||||
/**
|
||||
* Convert page to JSON encoded string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson();
|
||||
|
||||
/**
|
||||
* Returns normalized list of name => form pairs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms();
|
||||
|
||||
/**
|
||||
* @param array $new
|
||||
*/
|
||||
public function addForms(array $new);
|
||||
|
||||
/**
|
||||
* Gets and sets the name field. If no name field is set, it will return 'default.md'.
|
||||
*
|
||||
* @param string|null $var The name of this page.
|
||||
* @return string The name of this page.
|
||||
*/
|
||||
public function name($var = null);
|
||||
|
||||
/**
|
||||
* Returns child page type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function childType();
|
||||
|
||||
/**
|
||||
* Gets and sets the template field. This is used to find the correct Twig template file to render.
|
||||
* If no field is set, it will return the name without the .md extension
|
||||
*
|
||||
* @param string|null $var the template name
|
||||
* @return string the template name
|
||||
*/
|
||||
public function template($var = null);
|
||||
|
||||
/**
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string
|
||||
*/
|
||||
public function templateFormat($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the extension field.
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string|null
|
||||
*/
|
||||
public function extension($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the expires field. If not set will return the default
|
||||
*
|
||||
* @param int|null $var The new expires value.
|
||||
* @return int The expires value
|
||||
*/
|
||||
public function expires($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the cache-control property. If not set it will return the default value (null)
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string|null
|
||||
*/
|
||||
public function cacheControl($var = null);
|
||||
|
||||
/**
|
||||
* @param bool|null $var
|
||||
* @return bool
|
||||
*/
|
||||
public function ssl($var = null);
|
||||
|
||||
/**
|
||||
* Returns the state of the debugger override etting for this page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function debugger();
|
||||
|
||||
/**
|
||||
* Function to merge page metadata tags and build an array of Metadata objects
|
||||
* that can then be rendered in the page.
|
||||
*
|
||||
* @param array|null $var an Array of metadata values to set
|
||||
* @return array an Array of metadata values for the page
|
||||
*/
|
||||
public function metadata($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the option to show the etag header for the page.
|
||||
*
|
||||
* @param bool|null $var show etag header
|
||||
* @return bool show etag header
|
||||
*/
|
||||
public function eTag($var = null): bool;
|
||||
|
||||
/**
|
||||
* Gets and sets the path to the .md file for this Page object.
|
||||
*
|
||||
* @param string|null $var the file path
|
||||
* @return string|null the file path
|
||||
*/
|
||||
public function filePath($var = null);
|
||||
|
||||
/**
|
||||
* Gets the relative path to the .md file
|
||||
*
|
||||
* @return string The relative file path
|
||||
*/
|
||||
public function filePathClean();
|
||||
|
||||
/**
|
||||
* Gets and sets the order by which any sub-pages should be sorted.
|
||||
*
|
||||
* @param string|null $var the order, either "asc" or "desc"
|
||||
* @return string the order, either "asc" or "desc"
|
||||
* @deprecated 1.6
|
||||
*/
|
||||
public function orderDir($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the order by which the sub-pages should be sorted.
|
||||
*
|
||||
* default - is the order based on the file system, ie 01.Home before 02.Advark
|
||||
* title - is the order based on the title set in the pages
|
||||
* date - is the order based on the date set in the pages
|
||||
* folder - is the order based on the name of the folder with any numerics omitted
|
||||
*
|
||||
* @param string|null $var supported options include "default", "title", "date", and "folder"
|
||||
* @return string supported options include "default", "title", "date", and "folder"
|
||||
* @deprecated 1.6
|
||||
*/
|
||||
public function orderBy($var = null);
|
||||
|
||||
/**
|
||||
* Gets the manual order set in the header.
|
||||
*
|
||||
* @param string|null $var supported options include "default", "title", "date", and "folder"
|
||||
* @return array
|
||||
* @deprecated 1.6
|
||||
*/
|
||||
public function orderManual($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the maxCount field which describes how many sub-pages should be displayed if the
|
||||
* sub_pages header property is set for this page object.
|
||||
*
|
||||
* @param int|null $var the maximum number of sub-pages
|
||||
* @return int the maximum number of sub-pages
|
||||
* @deprecated 1.6
|
||||
*/
|
||||
public function maxCount($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the modular var that helps identify this page is a modular child
|
||||
*
|
||||
* @param bool|null $var true if modular_twig
|
||||
* @return bool true if modular_twig
|
||||
* @deprecated 1.7 Use ->isModule() or ->modularTwig() method instead.
|
||||
*/
|
||||
public function modular($var = null);
|
||||
|
||||
/**
|
||||
* Gets and sets the modular_twig var that helps identify this page as a modular child page that will need
|
||||
* twig processing handled differently from a regular page.
|
||||
*
|
||||
* @param bool|null $var true if modular_twig
|
||||
* @return bool true if modular_twig
|
||||
*/
|
||||
public function modularTwig($var = null);
|
||||
|
||||
/**
|
||||
* Returns children of this page.
|
||||
*
|
||||
* @return PageCollectionInterface|Collection
|
||||
*/
|
||||
public function children();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @return PageInterface the previous Page item
|
||||
*/
|
||||
public function prevSibling();
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @return PageInterface the next Page item
|
||||
*/
|
||||
public function nextSibling();
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param int $direction either -1 or +1
|
||||
* @return PageInterface|false the sibling page
|
||||
*/
|
||||
public function adjacentSibling($direction = 1);
|
||||
|
||||
/**
|
||||
* Helper method to return an ancestor page.
|
||||
*
|
||||
* @param bool|null $lookup Name of the parent folder
|
||||
* @return PageInterface page you were looking for if it exists
|
||||
*/
|
||||
public function ancestor($lookup = null);
|
||||
|
||||
/**
|
||||
* Helper method to return an ancestor page to inherit from. The current
|
||||
* page object is returned.
|
||||
*
|
||||
* @param string $field Name of the parent folder
|
||||
* @return PageInterface
|
||||
*/
|
||||
public function inherited($field);
|
||||
|
||||
/**
|
||||
* Helper method to return an ancestor field only to inherit from. The
|
||||
* first occurrence of an ancestor field will be returned if at all.
|
||||
*
|
||||
* @param string $field Name of the parent folder
|
||||
* @return array
|
||||
*/
|
||||
public function inheritedField($field);
|
||||
|
||||
/**
|
||||
* Helper method to return a page.
|
||||
*
|
||||
* @param string $url the url of the page
|
||||
* @param bool $all
|
||||
* @return PageInterface page you were looking for if it exists
|
||||
*/
|
||||
public function find($url, $all = false);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* @param string|array $value
|
||||
* @param bool $only_published
|
||||
* @return PageCollectionInterface|Collection
|
||||
*/
|
||||
public function evaluate($value, $only_published = true);
|
||||
|
||||
/**
|
||||
* Returns whether or not the current folder exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function folderExists();
|
||||
|
||||
/**
|
||||
* Gets the Page Unmodified (original) version of the page.
|
||||
*
|
||||
* @return PageInterface The original version of the page.
|
||||
*/
|
||||
public function getOriginal();
|
||||
|
||||
/**
|
||||
* Gets the action.
|
||||
*
|
||||
* @return string The Action string.
|
||||
*/
|
||||
public function getAction();
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface PageRoutableInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PageRoutableInterface
|
||||
{
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Gets and Sets whether or not this Page is routable, ie you can reach it
|
||||
* via a URL.
|
||||
* The page must be *routable* and *published*
|
||||
*
|
||||
* @param bool|null $var true if the page is routable
|
||||
* @return bool true if the page is routable
|
||||
*/
|
||||
public function routable($var = null);
|
||||
|
||||
/**
|
||||
* Gets the URL for a page - alias of url().
|
||||
*
|
||||
* @param bool|null $include_host
|
||||
* @return string the permalink
|
||||
*/
|
||||
public function link($include_host = false);
|
||||
|
||||
/**
|
||||
* Gets the URL with host information, aka Permalink.
|
||||
* @return string The permalink.
|
||||
*/
|
||||
public function permalink();
|
||||
|
||||
/**
|
||||
* Returns the canonical URL for a page
|
||||
*
|
||||
* @param bool $include_lang
|
||||
* @return string
|
||||
*/
|
||||
public function canonical($include_lang = true);
|
||||
|
||||
/**
|
||||
* 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_lang
|
||||
* @param bool $raw_route
|
||||
* @return string The url.
|
||||
*/
|
||||
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false);
|
||||
|
||||
/**
|
||||
* 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|null $var Set new default route.
|
||||
* @return string|null The route for the Page.
|
||||
*/
|
||||
public function route($var = null);
|
||||
|
||||
/**
|
||||
* Helper method to clear the route out so it regenerates next time you use it
|
||||
*/
|
||||
public function unsetRouteSlug();
|
||||
|
||||
/**
|
||||
* Gets and Sets the page raw route
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return string
|
||||
*/
|
||||
public function rawRoute($var = null);
|
||||
|
||||
/**
|
||||
* Gets the route aliases for the page based on page headers.
|
||||
*
|
||||
* @param array|null $var list of route aliases
|
||||
* @return array The route aliases for the Page.
|
||||
*/
|
||||
public function routeAliases($var = null);
|
||||
|
||||
/**
|
||||
* 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 bool|string
|
||||
*/
|
||||
public function routeCanonical($var = null);
|
||||
|
||||
/**
|
||||
* Gets the redirect set in the header.
|
||||
*
|
||||
* @param string|null $var redirect url
|
||||
* @return string
|
||||
*/
|
||||
public function redirect($var = null);
|
||||
|
||||
/**
|
||||
* Returns the clean path to the page file
|
||||
*/
|
||||
public function relativePagePath();
|
||||
|
||||
/**
|
||||
* Gets and sets the path to the folder where the .md for this Page object resides.
|
||||
* This is equivalent to the filePath but without the filename.
|
||||
*
|
||||
* @param string|null $var the path
|
||||
* @return string|null the path
|
||||
*/
|
||||
public function path($var = null);
|
||||
|
||||
/**
|
||||
* Get/set the folder.
|
||||
*
|
||||
* @param string|null $var Optional path
|
||||
* @return string|null
|
||||
*/
|
||||
public function folder($var = null);
|
||||
|
||||
/**
|
||||
* Gets and Sets the parent object for this page
|
||||
*
|
||||
* @param PageInterface|null $var the parent page object
|
||||
* @return PageInterface|null the parent page object if it exists.
|
||||
*/
|
||||
public function parent(PageInterface $var = null);
|
||||
|
||||
/**
|
||||
* Gets the top parent object for this page. Can return page itself.
|
||||
*
|
||||
* @return PageInterface The top parent page object.
|
||||
*/
|
||||
public function topParent();
|
||||
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @return int|null The index of the current page.
|
||||
*/
|
||||
public function currentPosition();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Returns whether or not this page is the currently configured home page.
|
||||
*
|
||||
* @return bool True if it is the homepage
|
||||
*/
|
||||
public function 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();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface PageTranslateInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PageTranslateInterface
|
||||
{
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Get page language
|
||||
*
|
||||
* @param string|null $var
|
||||
* @return mixed
|
||||
*/
|
||||
public function language($var = null);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface PagesSourceInterface
|
||||
* @package Grav\Common\Page\Interfaces
|
||||
*/
|
||||
interface PagesSourceInterface // extends \Iterator
|
||||
{
|
||||
/**
|
||||
* Get timestamp for the page source.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTimestamp(): int;
|
||||
|
||||
/**
|
||||
* Get checksum for the page source.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getChecksum(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the source contains a page for the given route.
|
||||
*
|
||||
* @param string $route
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $route): bool;
|
||||
|
||||
/**
|
||||
* Get the page for the given route.
|
||||
*
|
||||
* @param string $route
|
||||
* @return PageInterface|null
|
||||
*/
|
||||
public function get(string $route): ?PageInterface;
|
||||
|
||||
/**
|
||||
* Get the children for the given route.
|
||||
*
|
||||
* @param string $route
|
||||
* @param array|null $options
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren(string $route, array $options = null): array;
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Medium\Link;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function array_key_exists;
|
||||
use function call_user_func_array;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function in_array;
|
||||
use function is_bool;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Excerpts
|
||||
* @package Grav\Common\Page\Markdown
|
||||
*/
|
||||
class Excerpts
|
||||
{
|
||||
/** @var PageInterface|null */
|
||||
protected $page;
|
||||
/** @var array */
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Excerpts constructor.
|
||||
* @param PageInterface|null $page
|
||||
* @param array|null $config
|
||||
*/
|
||||
public function __construct(PageInterface $page = null, array $config = null)
|
||||
{
|
||||
$this->page = $page ?? Grav::instance()['page'] ?? null;
|
||||
|
||||
// Add defaults to the configuration.
|
||||
if (null === $config || !isset($config['markdown'], $config['images'])) {
|
||||
$c = Grav::instance()['config'];
|
||||
$config = $config ?? [];
|
||||
$config += [
|
||||
'markdown' => $c->get('system.pages.markdown', []),
|
||||
'images' => $c->get('system.images', [])
|
||||
];
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageInterface|null
|
||||
*/
|
||||
public function getPage(): ?PageInterface
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $markdown
|
||||
* @return void
|
||||
*/
|
||||
public function fireInitializedEvent($markdown): void
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = $grav['config']->get('system.pages.markdown.valid_link_attributes') ?? [];
|
||||
|
||||
$skip = [];
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
$skip = is_bool($actions['noprocess']) ? $actions : explode(',', $actions['noprocess']);
|
||||
unset($actions['noprocess']);
|
||||
}
|
||||
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
if (!in_array($attrib, $skip)) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes, true)) {
|
||||
// support both class and classes.
|
||||
if ($attrib === 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, '', '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
if ($type === 'link' && $locator->isStream($url)) {
|
||||
$path = $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
|
||||
$url_parts['path'] = $grav['base_url_relative'] . '/' . $path;
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($this->page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @return array
|
||||
*/
|
||||
public function processImageExcerpt(array $excerpt): array
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
|
||||
|
||||
$media = $this->page->getMedia();
|
||||
} else {
|
||||
$grav = Grav::instance();
|
||||
/** @var Pages $pages */
|
||||
$pages = $grav['pages'];
|
||||
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($this->page && $folder === $this->page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $this->page->getMedia();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim($grav['base_url_relative'] . $pages->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
$ext_page = $pages->find($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = $this->processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = $element_excerpt['alt'] ?? '';
|
||||
$title = $element_excerpt['title'] ?? '';
|
||||
$class = $element_excerpt['class'] ?? '';
|
||||
$id = $element_excerpt['id'] ?? '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process media actions
|
||||
*
|
||||
* @param Medium $medium
|
||||
* @param string|array $url
|
||||
* @return Medium|Link
|
||||
*/
|
||||
public function processMediaActions($medium, $url)
|
||||
{
|
||||
$url_parts = is_string($url) ? $this->parseUrl($url) : $url;
|
||||
$actions = [];
|
||||
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = $parts[1] ?? null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
$defaults = $this->config['images']['defaults'] ?? [];
|
||||
if (count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
if (array_search($method, array_column($actions, 'method')) === false) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array
|
||||
*/
|
||||
protected function parseUrl(string $url)
|
||||
{
|
||||
$url_parts = Utils::multibyteParseUrl($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Page\Medium\AbstractMedia;
|
||||
use Grav\Common\Page\Medium\GlobalMedia;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class Media
|
||||
* @package Grav\Common\Page
|
||||
*/
|
||||
class Media extends AbstractMedia
|
||||
{
|
||||
/** @var GlobalMedia */
|
||||
protected static $global;
|
||||
|
||||
/** @var array */
|
||||
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param array|null $media_order
|
||||
* @param bool $load
|
||||
*/
|
||||
public function __construct($path, array $media_order = null, $load = true)
|
||||
{
|
||||
$this->setPath($path);
|
||||
$this->media_order = $media_order;
|
||||
|
||||
$this->__wakeup();
|
||||
if ($load) {
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize static variables on unserialize.
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
if (null === static::$global) {
|
||||
// Add fallback to global media.
|
||||
static::$global = GlobalMedia::getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return MediaObjectInterface|null
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return parent::offsetGet($offset) ?: static::$global[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$config = Grav::instance()['config'];
|
||||
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
|
||||
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
|
||||
$path = $this->getPath();
|
||||
|
||||
// Handle special cases where page doesn't exist in filesystem.
|
||||
if (!$path || !is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator = new FilesystemIterator($path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
$media = [];
|
||||
|
||||
foreach ($iterator as $file => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
$filename = $info->getFilename();
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || strpos($filename, '.') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find out what type we're dealing with
|
||||
[$basename, $ext, $type, $extra] = $this->getFileParts($filename);
|
||||
|
||||
if (!in_array(strtolower($ext), $media_types, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'alternative') {
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()];
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($media as $name => $types) {
|
||||
// First prepare the alternatives in case there is no base medium
|
||||
if (!empty($types['alternative'])) {
|
||||
/**
|
||||
* @var string|int $ratio
|
||||
* @var array $alt
|
||||
*/
|
||||
foreach ($types['alternative'] as $ratio => &$alt) {
|
||||
$alt['file'] = $this->createFromFile($alt['file']);
|
||||
|
||||
if (empty($alt['file'])) {
|
||||
unset($types['alternative'][$ratio]);
|
||||
} else {
|
||||
$alt['file']->set('size', $alt['size']);
|
||||
}
|
||||
}
|
||||
unset($alt);
|
||||
}
|
||||
|
||||
$file_path = null;
|
||||
|
||||
// Create the base medium
|
||||
if (empty($types['base'])) {
|
||||
if (!isset($types['alternative'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$max = max(array_keys($types['alternative']));
|
||||
$medium = $types['alternative'][$max]['file'];
|
||||
$file_path = $medium->path();
|
||||
$medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file'];
|
||||
} else {
|
||||
$medium = $this->createFromFile($types['base']['file']);
|
||||
if ($medium) {
|
||||
$medium->set('size', $types['base']['size']);
|
||||
$file_path = $medium->path();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($medium)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// metadata file
|
||||
$meta_path = $file_path . '.meta.yaml';
|
||||
|
||||
if (file_exists($meta_path)) {
|
||||
$types['meta']['file'] = $meta_path;
|
||||
} elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) {
|
||||
$meta = $exif_reader->read($file_path);
|
||||
|
||||
if ($meta) {
|
||||
$meta_data = $meta->getData();
|
||||
$meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
|
||||
if ($meta_trimmed) {
|
||||
if ($locator->isStream($meta_path)) {
|
||||
$file = File::instance($locator->findResource($meta_path, true, true));
|
||||
} else {
|
||||
$file = File::instance($meta_path);
|
||||
}
|
||||
$file->save(Yaml::dump($meta_trimmed));
|
||||
$types['meta']['file'] = $meta_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($types['meta'])) {
|
||||
$medium->addMetaFile($types['meta']['file']);
|
||||
}
|
||||
|
||||
if (!empty($types['thumb'])) {
|
||||
// We will not turn it into medium yet because user might never request the thumbnail
|
||||
// not wasting any resources on that, maybe we should do this for medium in general?
|
||||
$medium->set('thumbnails.page', $types['thumb']['file']);
|
||||
}
|
||||
|
||||
// Build missing alternatives
|
||||
if (!empty($types['alternative'])) {
|
||||
$alternatives = $types['alternative'];
|
||||
$max = max(array_keys($alternatives));
|
||||
|
||||
for ($i=$max; $i > 1; $i--) {
|
||||
if (isset($alternatives[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
|
||||
}
|
||||
|
||||
foreach ($types['alternative'] as $altMedium) {
|
||||
if ($altMedium['file'] != $medium) {
|
||||
$altWidth = $altMedium['file']->get('width');
|
||||
$medWidth = $medium->get('width');
|
||||
if ($altWidth && $medWidth) {
|
||||
$ratio = $altWidth / $medWidth;
|
||||
$medium->addAlternative($ratio, $altMedium['file']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->add($name, $medium);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
* @deprecated 1.6 Use $this->getPath() instead.
|
||||
*/
|
||||
public function path(): ?string
|
||||
{
|
||||
return $this->getPath();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaUploadInterface;
|
||||
use Grav\Common\Media\Traits\MediaUploadTrait;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccess;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Iterator;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class AbstractMedia
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
abstract class AbstractMedia implements ExportInterface, MediaCollectionInterface, MediaUploadInterface
|
||||
{
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use Export;
|
||||
use MediaUploadTrait;
|
||||
|
||||
/** @var array */
|
||||
protected $items = [];
|
||||
/** @var string|null */
|
||||
protected $path;
|
||||
/** @var array */
|
||||
protected $images = [];
|
||||
/** @var array */
|
||||
protected $videos = [];
|
||||
/** @var array */
|
||||
protected $audios = [];
|
||||
/** @var array */
|
||||
protected $files = [];
|
||||
/** @var array|null */
|
||||
protected $media_order;
|
||||
|
||||
/**
|
||||
* Return media path.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $path
|
||||
* @return void
|
||||
*/
|
||||
public function setPath(?string $path): void
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return MediaObjectInterface|null
|
||||
*/
|
||||
public function get($filename)
|
||||
{
|
||||
return $this->offsetGet($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call object as function to get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke($filename)
|
||||
{
|
||||
return $this->offsetGet($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file modification timestamps (query params) for all the media files.
|
||||
*
|
||||
* @param string|int|null $timestamp
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimestamps($timestamp = null)
|
||||
{
|
||||
foreach ($this->items as $instance) {
|
||||
$instance->setTimestamp($timestamp);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all media.
|
||||
*
|
||||
* @return MediaObjectInterface[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$this->items = $this->orderMedia($this->items);
|
||||
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all image media.
|
||||
*
|
||||
* @return MediaObjectInterface[]
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
$this->images = $this->orderMedia($this->images);
|
||||
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all video media.
|
||||
*
|
||||
* @return MediaObjectInterface[]
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
$this->videos = $this->orderMedia($this->videos);
|
||||
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return MediaObjectInterface[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
$this->audios = $this->orderMedia($this->audios);
|
||||
|
||||
return $this->audios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
* @return MediaObjectInterface[]
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
$this->files = $this->orderMedia($this->files);
|
||||
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param MediaObjectInterface|null $file
|
||||
* @return void
|
||||
*/
|
||||
public function add($name, $file)
|
||||
{
|
||||
if (null === $file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->offsetSet($name, $file);
|
||||
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$name] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$name] = $file;
|
||||
break;
|
||||
case 'audio':
|
||||
$this->audios[$name] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function hide($name)
|
||||
{
|
||||
$this->offsetUnset($name);
|
||||
|
||||
unset($this->images[$name], $this->videos[$name], $this->audios[$name], $this->files[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Medium from a file.
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $params
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function createFromFile($file, array $params = [])
|
||||
{
|
||||
return MediumFactory::fromFile($file, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Medium from array of parameters
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint|null $blueprint
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function createFromArray(array $items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
return MediumFactory::fromArray($items, $blueprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MediaObjectInterface $mediaObject
|
||||
* @return ImageFile
|
||||
*/
|
||||
public function getImageFileObject(MediaObjectInterface $mediaObject): ImageFile
|
||||
{
|
||||
return ImageFile::open($mediaObject->get('filepath'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Order the media based on the page's media_order
|
||||
*
|
||||
* @param array $media
|
||||
* @return array
|
||||
*/
|
||||
protected function orderMedia($media)
|
||||
{
|
||||
if (null === $this->media_order) {
|
||||
$path = $this->getPath();
|
||||
if (null !== $path) {
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
$page = $pages->get($path);
|
||||
if ($page && isset($page->header()->media_order)) {
|
||||
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->media_order) && is_array($this->media_order)) {
|
||||
$media = Utils::sortArrayByArray($media, $this->media_order);
|
||||
} else {
|
||||
ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
protected function fileExists(string $filename, string $destination): bool
|
||||
{
|
||||
return file_exists("{$destination}/{$filename}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename, extension and meta part.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
protected function getFileParts($filename)
|
||||
{
|
||||
if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
|
||||
$name = $matches[1];
|
||||
$extension = $matches[3];
|
||||
$extra = (int) $matches[2];
|
||||
$type = 'alternative';
|
||||
|
||||
if ($extra === 1) {
|
||||
$type = 'base';
|
||||
$extra = null;
|
||||
}
|
||||
} else {
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$extension = null;
|
||||
$extra = null;
|
||||
$type = 'base';
|
||||
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part !== 'meta' && $part !== 'thumb') {
|
||||
if (null !== $extension) {
|
||||
$name .= '.' . $extension;
|
||||
}
|
||||
$extension = $part;
|
||||
} else {
|
||||
$type = $part;
|
||||
$extra = '.' . $part . '.' . implode('.', $fileParts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$name, $extension, $type, $extra];
|
||||
}
|
||||
|
||||
protected function getGrav(): Grav
|
||||
{
|
||||
return Grav::instance();
|
||||
}
|
||||
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->getGrav()['config'];
|
||||
}
|
||||
|
||||
protected function getLanguage(): Language
|
||||
{
|
||||
return $this->getGrav()['language'];
|
||||
}
|
||||
|
||||
protected function clearCache(): void
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->getGrav()['locator'];
|
||||
$locator->clearCache();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Media\Interfaces\AudioMediaInterface;
|
||||
use Grav\Common\Media\Traits\AudioMediaTrait;
|
||||
|
||||
/**
|
||||
* Class AudioMedium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class AudioMedium extends Medium implements AudioMediaInterface
|
||||
{
|
||||
use AudioMediaTrait;
|
||||
|
||||
/**
|
||||
* Reset medium.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
$this->resetPlayer();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function dirname;
|
||||
|
||||
/**
|
||||
* Class GlobalMedia
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class GlobalMedia extends AbstractMedia
|
||||
{
|
||||
/** @var self */
|
||||
protected static $instance;
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media path.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return MediaObjectInterface|null
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return parent::offsetGet($offset) ?: $this->addMedium($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return string|null
|
||||
*/
|
||||
protected function resolveStream($filename)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if (!$locator->isStream($filename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $locator->findResource($filename) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stream
|
||||
* @return MediaObjectInterface|null
|
||||
*/
|
||||
protected function addMedium($stream)
|
||||
{
|
||||
$filename = $this->resolveStream($stream);
|
||||
if (!$filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname($filename);
|
||||
[$basename, $ext,, $extra] = $this->getFileParts(basename($filename));
|
||||
$medium = MediumFactory::fromFile($filename);
|
||||
|
||||
if (null === $medium) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$medium->set('size', filesize($filename));
|
||||
$scale = (int) ($extra ?: 1);
|
||||
|
||||
if ($scale !== 1) {
|
||||
$altMedium = $medium;
|
||||
|
||||
// Create scaled down regular sized image.
|
||||
$medium = MediumFactory::scaledFromMedium($altMedium, $scale, 1)['file'];
|
||||
|
||||
if (empty($medium)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add original sized image as alternative.
|
||||
$medium->addAlternative($scale, $altMedium['file']);
|
||||
|
||||
// Locate or generate smaller retina images.
|
||||
for ($i = $scale-1; $i > 1; $i--) {
|
||||
$altFilename = "{$path}/{$basename}@{$i}x.{$ext}";
|
||||
|
||||
if (file_exists($altFilename)) {
|
||||
$scaled = MediumFactory::fromFile($altFilename);
|
||||
} else {
|
||||
$scaled = MediumFactory::scaledFromMedium($altMedium, $scale, $i)['file'];
|
||||
}
|
||||
|
||||
if ($scaled) {
|
||||
$medium->addAlternative($i, $scaled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$meta = "{$path}/{$basename}.{$ext}.yaml";
|
||||
if (file_exists($meta)) {
|
||||
$medium->addMetaFile($meta);
|
||||
}
|
||||
$meta = "{$path}/{$basename}.{$ext}.meta.yaml";
|
||||
if (file_exists($meta)) {
|
||||
$medium->addMetaFile($meta);
|
||||
}
|
||||
|
||||
$thumb = "{$path}/{$basename}.thumb.{$ext}";
|
||||
if (file_exists($thumb)) {
|
||||
$medium->set('thumbnails.page', $thumb);
|
||||
}
|
||||
|
||||
$this->add($stream, $medium);
|
||||
|
||||
return $medium;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Gregwar\Image\Exceptions\GenerationError;
|
||||
use Gregwar\Image\Image;
|
||||
use Gregwar\Image\Source;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function extension_loaded;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class ImageFile
|
||||
* @package Grav\Common\Page\Medium
|
||||
*
|
||||
* @method Image applyExifOrientation($exif_orienation)
|
||||
*/
|
||||
class ImageFile extends Image
|
||||
{
|
||||
/**
|
||||
* Destruct also image object.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$adapter = $this->adapter;
|
||||
if ($adapter) {
|
||||
$adapter->deinit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear previously applied operations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearOperations()
|
||||
{
|
||||
$this->operations = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
|
||||
*
|
||||
* @param string $type the image type
|
||||
* @param int $quality the quality (for JPEG)
|
||||
* @param bool $actual
|
||||
* @param array $extras
|
||||
* @return string
|
||||
*/
|
||||
public function cacheFile($type = 'jpg', $quality = 80, $actual = false, $extras = [])
|
||||
{
|
||||
if ($type === 'guess') {
|
||||
$type = $this->guessType();
|
||||
}
|
||||
|
||||
if (!$this->forceCache && !count($this->operations) && $type === $this->guessType()) {
|
||||
return $this->getFilename($this->getFilePath());
|
||||
}
|
||||
|
||||
// Computes the hash
|
||||
$this->hash = $this->getHash($type, $quality, $extras);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
// Seo friendly image names
|
||||
$seofriendly = $config->get('system.images.seofriendly', false);
|
||||
|
||||
if ($seofriendly) {
|
||||
$mini_hash = substr($this->hash, 0, 4) . substr($this->hash, -4);
|
||||
$cacheFile = "{$this->prettyName}-{$mini_hash}";
|
||||
} else {
|
||||
$cacheFile = "{$this->hash}-{$this->prettyName}";
|
||||
}
|
||||
|
||||
$cacheFile .= '.' . $type;
|
||||
|
||||
// If the files does not exists, save it
|
||||
$image = $this;
|
||||
|
||||
// Target file should be younger than all the current image
|
||||
// dependencies
|
||||
$conditions = array(
|
||||
'younger-than' => $this->getDependencies()
|
||||
);
|
||||
|
||||
// The generating function
|
||||
$generate = function ($target) use ($image, $type, $quality) {
|
||||
$result = $image->save($target, $type, $quality);
|
||||
|
||||
if ($result !== $target) {
|
||||
throw new GenerationError($result);
|
||||
}
|
||||
|
||||
Grav::instance()->fireEvent('onImageMediumSaved', new Event(['image' => $target]));
|
||||
};
|
||||
|
||||
// Asking the cache for the cacheFile
|
||||
try {
|
||||
$perms = $config->get('system.images.cache_perms', '0755');
|
||||
$perms = octdec($perms);
|
||||
$file = $this->getCacheSystem()->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
|
||||
} catch (GenerationError $e) {
|
||||
$file = $e->getNewFile();
|
||||
}
|
||||
|
||||
// Nulling the resource
|
||||
$adapter = $this->getAdapter();
|
||||
$adapter->setSource(new Source\File($file));
|
||||
$adapter->deinit();
|
||||
|
||||
if ($actual) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->getFilename($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param array $extras
|
||||
* @return string
|
||||
*/
|
||||
public function getHash($type = 'guess', $quality = 80, $extras = [])
|
||||
{
|
||||
if (null === $this->hash) {
|
||||
$this->generateHash($type, $quality, $extras);
|
||||
}
|
||||
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the hash.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param array $extras
|
||||
*/
|
||||
public function generateHash($type = 'guess', $quality = 80, $extras = [])
|
||||
{
|
||||
$inputInfos = $this->source->getInfos();
|
||||
|
||||
$data = [
|
||||
$inputInfos,
|
||||
$this->serializeOperations(),
|
||||
$type,
|
||||
$quality,
|
||||
$extras
|
||||
];
|
||||
|
||||
$this->hash = sha1(serialize($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif rotation from file and apply it.
|
||||
*/
|
||||
public function fixOrientation()
|
||||
{
|
||||
if (!extension_loaded('exif')) {
|
||||
throw new RuntimeException('You need to EXIF PHP Extension to use this function');
|
||||
}
|
||||
|
||||
if (!in_array(exif_imagetype($this->source->getInfos()), [IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM], true)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// resolve any streams
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$filepath = $this->source->getInfos();
|
||||
if ($locator->isStream($filepath)) {
|
||||
$filepath = $locator->findResource($this->source->getInfos(), true, true);
|
||||
}
|
||||
|
||||
// Make sure file exists
|
||||
if (!file_exists($filepath)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
try {
|
||||
$exif = @exif_read_data($filepath);
|
||||
} catch (Exception $e) {
|
||||
Grav::instance()['log']->error($filepath . ' - ' . $e->getMessage());
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($exif === false || !array_key_exists('Orientation', $exif)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->applyExifOrientation($exif['Orientation']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use BadFunctionCallException;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Media\Interfaces\ImageManipulateInterface;
|
||||
use Grav\Common\Media\Interfaces\ImageMediaInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaLinkInterface;
|
||||
use Grav\Common\Media\Traits\ImageLoadingTrait;
|
||||
use Grav\Common\Media\Traits\ImageMediaTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Gregwar\Image\Image;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function func_get_args;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class ImageMedium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulateInterface
|
||||
{
|
||||
use ImageMediaTrait;
|
||||
use ImageLoadingTrait;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
private $saved_image_path;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint|null $blueprint
|
||||
*/
|
||||
public function __construct($items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
$config = $this->getGrav()['config'];
|
||||
|
||||
$this->thumbnailTypes = ['page', 'media', 'default'];
|
||||
$this->default_quality = $config->get('system.images.default_image_quality', 85);
|
||||
$this->def('debug', $config->get('system.images.debug'));
|
||||
|
||||
$path = $this->get('filepath');
|
||||
if (!$path || !file_exists($path) || !filesize($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set('thumbnails.media', $path);
|
||||
|
||||
if (!($this->offsetExists('width') && $this->offsetExists('height') && $this->offsetExists('mime'))) {
|
||||
$image_info = getimagesize($path);
|
||||
if ($image_info) {
|
||||
$this->def('width', $image_info[0]);
|
||||
$this->def('height', $image_info[1]);
|
||||
$this->def('mime', $image_info['mime']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
|
||||
if ($config->get('system.images.cache_all', false)) {
|
||||
$this->cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
return [
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
] + parent::getMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Also unset the image on destruct.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
unset($this->image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Also clone image.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->image) {
|
||||
$this->image = clone $this->image;
|
||||
}
|
||||
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
if ($this->image) {
|
||||
$this->image();
|
||||
$this->medium_querystring = [];
|
||||
$this->filter();
|
||||
$this->clearAlternatives();
|
||||
}
|
||||
|
||||
$this->format = 'guess';
|
||||
$this->quality = $this->default_quality;
|
||||
|
||||
$this->debug_watermarked = false;
|
||||
|
||||
$config = $this->getGrav()['config'];
|
||||
// Set CLS configuration
|
||||
$this->auto_sizes = $config->get('system.images.cls.auto_sizes', false);
|
||||
$this->aspect_ratio = $config->get('system.images.cls.aspect_ratio', false);
|
||||
$this->retina_scale = $config->get('system.images.cls.retina_scale', 1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return $this
|
||||
*/
|
||||
public function addMetaFile($filepath)
|
||||
{
|
||||
parent::addMetaFile($filepath);
|
||||
|
||||
// Apply filters in meta file
|
||||
$this->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PATH to image.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string path to image
|
||||
*/
|
||||
public function path($reset = true)
|
||||
{
|
||||
$output = $this->saveImage();
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to image.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function url($reset = true)
|
||||
{
|
||||
$grav = $this->getGrav();
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
$image_path = (string)($locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true));
|
||||
$saved_image_path = $this->saved_image_path = $this->saveImage();
|
||||
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path) ?: $saved_image_path;
|
||||
|
||||
if ($locator->isStream($output)) {
|
||||
$output = (string)($locator->findResource($output, false) ?: $locator->findResource($output, false, true));
|
||||
}
|
||||
|
||||
if (Utils::startsWith($output, $image_path)) {
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
|
||||
}
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return trim($grav['base_url'] . '/' . $this->urlQuerystring($output), '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return srcset string for this Medium and its alternatives.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function srcset($reset = true)
|
||||
{
|
||||
if (empty($this->alternatives)) {
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$srcset = [];
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
|
||||
}
|
||||
$srcset[] = str_replace(' ', '%20', $this->url($reset)) . ' ' . $this->get('width') . 'w';
|
||||
|
||||
return implode(', ', $srcset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $reset
|
||||
* @return array
|
||||
*/
|
||||
public function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
empty($attributes['src']) && $attributes['src'] = $this->url(false);
|
||||
|
||||
$srcset = $this->srcset($reset);
|
||||
if ($srcset) {
|
||||
empty($attributes['srcset']) && $attributes['srcset'] = $srcset;
|
||||
$attributes['sizes'] = $this->sizes();
|
||||
}
|
||||
|
||||
if ($this->saved_image_path && $this->auto_sizes) {
|
||||
if (!array_key_exists('height', $this->attributes) && !array_key_exists('width', $this->attributes)) {
|
||||
$info = getimagesize($this->saved_image_path);
|
||||
$width = intval($info[0]);
|
||||
$height = intval($info[1]);
|
||||
|
||||
$scaling_factor = $this->retina_scale > 0 ? $this->retina_scale : 1;
|
||||
$attributes['width'] = intval($width / $scaling_factor);
|
||||
$attributes['height'] = intval($height / $scaling_factor);
|
||||
|
||||
if ($this->aspect_ratio) {
|
||||
$style = ($attributes['style'] ?? ' ') . "--aspect-ratio: $width/$height;";
|
||||
$attributes['style'] = trim($style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['name' => 'img', 'attributes' => $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
* @param bool $reset
|
||||
* @param array $attributes
|
||||
* @return MediaLinkInterface
|
||||
*/
|
||||
public function link($reset = true, array $attributes = [])
|
||||
{
|
||||
$attributes['href'] = $this->url(false);
|
||||
$srcset = $this->srcset(false);
|
||||
if ($srcset) {
|
||||
$attributes['data-srcset'] = $srcset;
|
||||
}
|
||||
|
||||
return parent::link($reset, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param bool $reset
|
||||
* @return MediaLinkInterface
|
||||
*/
|
||||
public function lightbox($width = null, $height = null, $reset = true)
|
||||
{
|
||||
if ($this->mode !== 'source') {
|
||||
$this->display('source');
|
||||
}
|
||||
|
||||
if ($width && $height) {
|
||||
$this->__call('cropResize', [$width, $height]);
|
||||
}
|
||||
|
||||
return parent::lightbox($width, $height, $reset);
|
||||
}
|
||||
|
||||
public function autoSizes($enabled = 'true')
|
||||
{
|
||||
$enabled = $enabled === 'true' ?: false;
|
||||
$this->auto_sizes = $enabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function aspectRatio($enabled = 'true')
|
||||
{
|
||||
$enabled = $enabled === 'true' ?: false;
|
||||
$this->aspect_ratio = $enabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function retinaScale($scale = 1)
|
||||
{
|
||||
$this->retina_scale = intval($scale);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function watermark($image = null, $position = null, $scale = null)
|
||||
{
|
||||
$grav = $this->getGrav();
|
||||
|
||||
$locator = $grav['locator'];
|
||||
$config = $grav['config'];
|
||||
|
||||
$args = func_get_args();
|
||||
|
||||
$file = $args[0] ?? '1'; // using '1' because of markdown. doing  returns $args[0]='1';
|
||||
$file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0];
|
||||
|
||||
$watermark = $locator->findResource($file);
|
||||
$watermark = ImageFile::open($watermark);
|
||||
|
||||
// Scaling operations
|
||||
$scale = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100;
|
||||
$wwidth = $this->get('width') * $scale;
|
||||
$wheight = $this->get('height') * $scale;
|
||||
$watermark->resize($wwidth, $wheight);
|
||||
|
||||
// Position operations
|
||||
$position = !empty($args[1]) ? explode('-', $args[1]) : ['center', 'center']; // todo change to config
|
||||
$positionY = $position[0] ?? $config->get('system.images.watermark.position_y', 'center');
|
||||
$positionX = $position[1] ?? $config->get('system.images.watermark.position_x', 'center');
|
||||
|
||||
switch ($positionY)
|
||||
{
|
||||
case 'top':
|
||||
$positionY = 0;
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
$positionY = $this->get('height')-$wheight;
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
$positionY = ($this->get('height')/2) - ($wheight/2);
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($positionX)
|
||||
{
|
||||
case 'left':
|
||||
$positionX = 0;
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
$positionX = $this->get('width')-$wwidth;
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
$positionX = ($this->get('width')/2) - ($wwidth/2);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->__call('merge', [$watermark,$positionX, $positionY]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle this commonly used variant
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cropZoom()
|
||||
{
|
||||
$this->__call('zoomCrop', func_get_args());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a frame to image
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addFrame(int $border = 10, string $color = '0x000000')
|
||||
{
|
||||
if(is_int(intval($border)) && $border>0 && preg_match('/^0x[a-f0-9]{6}$/i', $color)) { // $border must be an integer and bigger than 0; $color must be formatted as an HEX value (0x??????).
|
||||
$image = ImageFile::open($this->path());
|
||||
}
|
||||
else {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$dst_width = $image->width()+2*$border;
|
||||
$dst_height = $image->height()+2*$border;
|
||||
|
||||
$frame = ImageFile::create($dst_width, $dst_height);
|
||||
|
||||
$frame->__call('fill', [$color]);
|
||||
|
||||
$this->image = $frame;
|
||||
|
||||
$this->__call('merge', [$image, $border, $border]);
|
||||
|
||||
$this->saveImage();
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the image processing method.
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this|mixed
|
||||
*/
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (!in_array($method, static::$magic_actions, true)) {
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->image->{$method}(...$args);
|
||||
|
||||
/** @var ImageMediaInterface $medium */
|
||||
foreach ($this->alternatives as $medium) {
|
||||
$args_copy = $args;
|
||||
|
||||
// regular image: resize 400x400 -> 200x200
|
||||
// --> @2x: resize 800x800->400x400
|
||||
if (isset(static::$magic_resize_actions[$method])) {
|
||||
foreach (static::$magic_resize_actions[$method] as $param) {
|
||||
if (isset($args_copy[$param])) {
|
||||
$args_copy[$param] *= $medium->get('ratio');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do the same call for alternative media.
|
||||
$medium->__call($method, $args_copy);
|
||||
}
|
||||
} catch (BadFunctionCallException $e) {
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Grav\Common\Media\Interfaces\MediaLinkInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use RuntimeException;
|
||||
use function call_user_func_array;
|
||||
use function get_class;
|
||||
use function is_array;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Class Link
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class Link implements RenderableInterface, MediaLinkInterface
|
||||
{
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
/** @var array */
|
||||
protected $attributes = [];
|
||||
/** @var MediaObjectInterface */
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
* @param array $attributes
|
||||
* @param MediaObjectInterface $medium
|
||||
*/
|
||||
public function __construct(array $attributes, MediaObjectInterface $medium)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
$source = $medium->reset()->thumbnail('auto')->display('thumbnail');
|
||||
if (!$source instanceof MediaObjectInterface) {
|
||||
throw new RuntimeException('Media has no thumbnail set');
|
||||
}
|
||||
|
||||
$source->set('linked', true);
|
||||
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element (is array) that can be rendered by the Parsedown engine
|
||||
*
|
||||
* @param string|null $title
|
||||
* @param string|null $alt
|
||||
* @param string|null $class
|
||||
* @param string|null $id
|
||||
* @param bool $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
$innerElement = $this->source->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
return [
|
||||
'name' => 'a',
|
||||
'attributes' => $this->attributes,
|
||||
'handler' => is_array($innerElement) ? 'element' : 'line',
|
||||
'text' => $innerElement
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the source element
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$object = $this->source;
|
||||
$callable = [$object, $method];
|
||||
if (!is_callable($callable)) {
|
||||
throw new BadMethodCallException(get_class($object) . '::' . $method . '() not found.');
|
||||
}
|
||||
|
||||
$object = call_user_func_array($callable, $args);
|
||||
if (!$object instanceof MediaLinkInterface) {
|
||||
// Don't start nesting links, if user has multiple link calls in his
|
||||
// actions, we will drop the previous links.
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->source = $object;
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Media\Interfaces\MediaFileInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaLinkInterface;
|
||||
use Grav\Common\Media\Traits\MediaFileTrait;
|
||||
use Grav\Common\Media\Traits\MediaObjectTrait;
|
||||
|
||||
/**
|
||||
* Class Medium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*
|
||||
* @property string $mime
|
||||
*/
|
||||
class Medium extends Data implements RenderableInterface, MediaFileInterface
|
||||
{
|
||||
use MediaObjectTrait;
|
||||
use MediaFileTrait;
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint|null $blueprint
|
||||
*/
|
||||
public function __construct($items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if (Grav::instance()['config']->get('system.media.enable_media_timestamp', true)) {
|
||||
$this->timestamp = Grav::instance()['cache']->getKey();
|
||||
}
|
||||
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
|
||||
if (!$this->offsetExists('size')) {
|
||||
$path = $this->get('filepath');
|
||||
$this->def('size', filesize($path));
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone medium.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
// Allows future compatibility as parent::__clone() works.
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
* @param string $filepath
|
||||
*/
|
||||
public function addMetaFile($filepath)
|
||||
{
|
||||
$this->metadata = (array)CompiledYamlFile::instance($filepath)->content();
|
||||
$this->merge($this->metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
return [
|
||||
'mime' => $this->mime,
|
||||
'size' => $this->size,
|
||||
'modified' => $this->modified,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->html();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $thumb
|
||||
*/
|
||||
protected function createThumbnail($thumb)
|
||||
{
|
||||
return MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $attributes
|
||||
* @return MediaLinkInterface
|
||||
*/
|
||||
protected function createLink(array $attributes)
|
||||
{
|
||||
return new Link($attributes, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Grav
|
||||
*/
|
||||
protected function getGrav(): Grav
|
||||
{
|
||||
return Grav::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Media\Interfaces\ImageMediaInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use function dirname;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class MediumFactory
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class MediumFactory
|
||||
{
|
||||
/**
|
||||
* Create Medium from a file
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $params
|
||||
* @return Medium|null
|
||||
*/
|
||||
public static function fromFile($file, array $params = [])
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = pathinfo($file);
|
||||
$path = $parts['dirname'];
|
||||
$filename = $parts['basename'];
|
||||
$ext = $parts['extension'] ?? '';
|
||||
$basename = $parts['filename'];
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$media_params = $ext ? $config->get('media.types.' . strtolower($ext)) : null;
|
||||
if (!is_array($media_params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove empty 'image' attribute
|
||||
if (isset($media_params['image']) && empty($media_params['image'])) {
|
||||
unset($media_params['image']);
|
||||
}
|
||||
|
||||
$params += $media_params;
|
||||
|
||||
// Add default settings for undefined variables.
|
||||
$params += (array)$config->get('media.types.defaults');
|
||||
$params += [
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'filepath' => $file,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $path,
|
||||
'modified' => filemtime($file),
|
||||
'thumbnails' => []
|
||||
];
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$file = $locator->findResource("image://{$params['thumb']}");
|
||||
if ($file) {
|
||||
$params['thumbnails']['default'] = $file;
|
||||
}
|
||||
|
||||
return static::fromArray($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Medium from an uploaded file
|
||||
*
|
||||
* @param UploadedFileInterface $uploadedFile
|
||||
* @param array $params
|
||||
* @return Medium|null
|
||||
*/
|
||||
public static function fromUploadedFile(UploadedFileInterface $uploadedFile, array $params = [])
|
||||
{
|
||||
// For now support only FormFlashFiles, which exist over multiple requests. Also ignore errored and moved media.
|
||||
if (!$uploadedFile instanceof FormFlashFile || $uploadedFile->getError() !== \UPLOAD_ERR_OK || $uploadedFile->isMoved()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$clientName = $uploadedFile->getClientFilename();
|
||||
if (!$clientName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = pathinfo($clientName);
|
||||
$filename = $parts['basename'];
|
||||
$ext = $parts['extension'] ?? '';
|
||||
$basename = $parts['filename'];
|
||||
$file = $uploadedFile->getTmpFile();
|
||||
$path = $file ? dirname($file) : '';
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$media_params = $ext ? $config->get('media.types.' . strtolower($ext)) : null;
|
||||
if (!is_array($media_params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$params += $media_params;
|
||||
|
||||
// Add default settings for undefined variables.
|
||||
$params += (array)$config->get('media.types.defaults');
|
||||
$params += [
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'filepath' => $file,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $path,
|
||||
'modified' => $file ? filemtime($file) : 0,
|
||||
'thumbnails' => []
|
||||
];
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$file = $locator->findResource("image://{$params['thumb']}");
|
||||
if ($file) {
|
||||
$params['thumbnails']['default'] = $file;
|
||||
}
|
||||
|
||||
return static::fromArray($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Medium from array of parameters
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint|null $blueprint
|
||||
* @return Medium
|
||||
*/
|
||||
public static function fromArray(array $items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
$type = $items['type'] ?? null;
|
||||
|
||||
switch ($type) {
|
||||
case 'image':
|
||||
return new ImageMedium($items, $blueprint);
|
||||
case 'thumbnail':
|
||||
return new ThumbnailImageMedium($items, $blueprint);
|
||||
case 'animated':
|
||||
case 'vector':
|
||||
return new StaticImageMedium($items, $blueprint);
|
||||
case 'video':
|
||||
return new VideoMedium($items, $blueprint);
|
||||
case 'audio':
|
||||
return new AudioMedium($items, $blueprint);
|
||||
default:
|
||||
return new Medium($items, $blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ImageMedium by scaling another ImageMedium object.
|
||||
*
|
||||
* @param ImageMediaInterface|MediaObjectInterface $medium
|
||||
* @param int $from
|
||||
* @param int $to
|
||||
* @return ImageMediaInterface|MediaObjectInterface|array
|
||||
*/
|
||||
public static function scaledFromMedium($medium, $from, $to)
|
||||
{
|
||||
if (!$medium instanceof ImageMedium) {
|
||||
return $medium;
|
||||
}
|
||||
|
||||
if ($to > $from) {
|
||||
return $medium;
|
||||
}
|
||||
|
||||
$ratio = $to / $from;
|
||||
$width = $medium->get('width') * $ratio;
|
||||
$height = $medium->get('height') * $ratio;
|
||||
|
||||
$prev_basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename);
|
||||
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
$medium->setImagePrettyName($basename);
|
||||
|
||||
$file = $medium->resize($width, $height)->path();
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
$medium->setImagePrettyName($prev_basename);
|
||||
|
||||
$size = filesize($file);
|
||||
|
||||
$medium = self::fromFile($file);
|
||||
if ($medium) {
|
||||
$medium->set('size', $size);
|
||||
}
|
||||
|
||||
return ['file' => $medium, 'size' => $size];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
/**
|
||||
* Trait ParsedownHtmlTrait
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
trait ParsedownHtmlTrait
|
||||
{
|
||||
/** @var Parsedown|null */
|
||||
protected $parsedown;
|
||||
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string|null $title
|
||||
* @param string|null $alt
|
||||
* @param string|null $class
|
||||
* @param string|null $id
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
$element = $this->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(new Excerpts());
|
||||
}
|
||||
|
||||
return $this->parsedown->elementToHtml($element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
/**
|
||||
* Interface RenderableInterface
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
interface RenderableInterface
|
||||
{
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string|null $title
|
||||
* @param string|null $alt
|
||||
* @param string|null $class
|
||||
* @param string|null $id
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $id = null, $reset = true);
|
||||
|
||||
/**
|
||||
* Return Parsedown Element from the medium.
|
||||
*
|
||||
* @param string|null $title
|
||||
* @param string|null $alt
|
||||
* @param string|null $class
|
||||
* @param string|null $id
|
||||
* @param bool $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Media\Interfaces\ImageMediaInterface;
|
||||
use Grav\Common\Media\Traits\ImageLoadingTrait;
|
||||
use Grav\Common\Media\Traits\StaticResizeTrait;
|
||||
|
||||
/**
|
||||
* Class StaticImageMedium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class StaticImageMedium extends Medium implements ImageMediaInterface
|
||||
{
|
||||
use StaticResizeTrait;
|
||||
use ImageLoadingTrait;
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $reset
|
||||
* @return array
|
||||
*/
|
||||
protected function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
if (empty($attributes['src'])) {
|
||||
$attributes['src'] = $this->url($reset);
|
||||
}
|
||||
|
||||
return ['name' => 'img', 'attributes' => $attributes];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Media\Traits\StaticResizeTrait as NewResizeTrait;
|
||||
|
||||
user_error('Grav\Common\Page\Medium\StaticResizeTrait is deprecated since Grav 1.7, use Grav\Common\Media\Traits\StaticResizeTrait instead', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Trait StaticResizeTrait
|
||||
* @package Grav\Common\Page\Medium
|
||||
* @deprecated 1.7 Use `Grav\Common\Media\Traits\StaticResizeTrait` instead
|
||||
*/
|
||||
trait StaticResizeTrait
|
||||
{
|
||||
use NewResizeTrait;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Media\Traits\ThumbnailMediaTrait;
|
||||
|
||||
/**
|
||||
* Class ThumbnailImageMedium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class ThumbnailImageMedium extends ImageMedium
|
||||
{
|
||||
use ThumbnailMediaTrait;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Media\Interfaces\VideoMediaInterface;
|
||||
use Grav\Common\Media\Traits\VideoMediaTrait;
|
||||
|
||||
/**
|
||||
* Class VideoMedium
|
||||
* @package Grav\Common\Page\Medium
|
||||
*/
|
||||
class VideoMedium extends Medium implements VideoMediaInterface
|
||||
{
|
||||
use VideoMediaTrait;
|
||||
|
||||
/**
|
||||
* Reset medium.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
$this->resetPlayer();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\Page\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Trait PageFormTrait
|
||||
* @package Grav\Common\Page\Traits
|
||||
*/
|
||||
trait PageFormTrait
|
||||
{
|
||||
/** @var array|null */
|
||||
private $_forms;
|
||||
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getForms(): array
|
||||
{
|
||||
if (null === $this->_forms) {
|
||||
$header = $this->header();
|
||||
|
||||
// Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
|
||||
|
||||
$rules = $header->rules ?? null;
|
||||
if (!is_array($rules)) {
|
||||
$rules = [];
|
||||
}
|
||||
|
||||
$forms = [];
|
||||
|
||||
// First grab page.header.form
|
||||
$form = $this->normalizeForm($header->form ?? null, null, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
|
||||
// Append page.header.forms (override singular form if it clashes)
|
||||
$headerForms = $header->forms ?? null;
|
||||
if (is_array($headerForms)) {
|
||||
foreach ($headerForms as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_forms = $forms;
|
||||
}
|
||||
|
||||
return $this->_forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @param bool $override
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new, $override = true)
|
||||
{
|
||||
// Initialize forms.
|
||||
$this->forms();
|
||||
|
||||
foreach ($new as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name);
|
||||
$name = $form['name'] ?? null;
|
||||
if ($name && ($override || !isset($this->_forms[$name]))) {
|
||||
$this->_forms[$name] = $form;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms(): array
|
||||
{
|
||||
return $this->getForms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $form
|
||||
* @param string|null $name
|
||||
* @param array $rules
|
||||
* @return array|null
|
||||
*/
|
||||
protected function normalizeForm($form, $name = null, array $rules = []): ?array
|
||||
{
|
||||
if (!is_array($form)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore numeric indexes on name.
|
||||
if (!$name || (string)(int)$name === (string)$name) {
|
||||
$name = null;
|
||||
}
|
||||
|
||||
$name = $name ?? $form['name'] ?? $this->slug();
|
||||
|
||||
$formRules = $form['rules'] ?? null;
|
||||
if (!is_array($formRules)) {
|
||||
$formRules = [];
|
||||
}
|
||||
|
||||
return ['name' => $name, 'rules' => $rules + $formRules] + $form;
|
||||
}
|
||||
|
||||
abstract public function header($var = null);
|
||||
abstract public function slug($var = null);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use InvalidArgumentException;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccess;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Iterator;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Types
|
||||
* @package Grav\Common\Page
|
||||
*/
|
||||
class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
{
|
||||
use ArrayAccess, Constructor, Iterator, Countable, Export;
|
||||
|
||||
/** @var array */
|
||||
protected $items;
|
||||
/** @var array */
|
||||
protected $systemBlueprints = [];
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param Blueprint|null $blueprint
|
||||
* @return void
|
||||
*/
|
||||
public function register($type, $blueprint = null)
|
||||
{
|
||||
if (!isset($this->items[$type])) {
|
||||
$this->items[$type] = [];
|
||||
} elseif (null === $blueprint) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $blueprint) {
|
||||
$blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'] ?? null;
|
||||
}
|
||||
|
||||
if ($blueprint) {
|
||||
array_unshift($this->items[$type], $blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (empty($this->systemBlueprints)) {
|
||||
// Register all blueprints from the blueprints stream.
|
||||
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
|
||||
foreach ($this->systemBlueprints as $type => $blueprint) {
|
||||
$this->register($type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return void
|
||||
*/
|
||||
public function scanBlueprints($uri)
|
||||
{
|
||||
if (!is_string($uri)) {
|
||||
throw new InvalidArgumentException('First parameter must be URI');
|
||||
}
|
||||
|
||||
foreach ($this->findBlueprints($uri) as $type => $blueprint) {
|
||||
$this->register($type, $blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return void
|
||||
*/
|
||||
public function scanTemplates($uri)
|
||||
{
|
||||
if (!is_string($uri)) {
|
||||
throw new InvalidArgumentException('First parameter must be URI');
|
||||
}
|
||||
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.html\.twig$|',
|
||||
'filters' => [
|
||||
'value' => '|\.html\.twig$|'
|
||||
],
|
||||
'value' => 'Filename',
|
||||
'recursive' => false
|
||||
];
|
||||
|
||||
foreach (Folder::all($uri, $options) as $type) {
|
||||
$this->register($type);
|
||||
}
|
||||
|
||||
$modular_uri = rtrim($uri, '/') . '/modular';
|
||||
if (is_dir($modular_uri)) {
|
||||
foreach (Folder::all($modular_uri, $options) as $type) {
|
||||
$this->register('modular/' . $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function pageSelect()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->items as $name => $file) {
|
||||
if (strpos($name, '/')) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = ucfirst(str_replace('_', ' ', $name));
|
||||
}
|
||||
ksort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function modularSelect()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->items as $name => $file) {
|
||||
if (strpos($name, 'modular/') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = ucfirst(trim(str_replace('_', ' ', basename($name))));
|
||||
}
|
||||
ksort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return array
|
||||
*/
|
||||
private function findBlueprints($uri)
|
||||
{
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|'
|
||||
],
|
||||
'key' => 'SubPathName',
|
||||
'value' => 'PathName',
|
||||
];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($uri)) {
|
||||
$options['value'] = 'Url';
|
||||
}
|
||||
|
||||
return Folder::all($uri, $options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user