first commit

This commit is contained in:
2019-09-30 16:21:51 +02:00
commit 5f00dd9b15
9746 changed files with 905481 additions and 0 deletions
+630
View File
@@ -0,0 +1,630 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page;
use Grav\Common\Grav;
use Grav\Common\Iterator;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
class Collection extends Iterator
{
/**
* @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 ? $pages : Grav::instance()->offsetGet('pages');
}
/**
* Get the collection params
*
* @return array
*/
public function params()
{
return $this->params;
}
/**
* 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 Collection $collection
* @return $this
*/
public function merge(Collection $collection)
{
foreach($collection as $page) {
$this->addPage($page);
}
return $this;
}
/**
* Intersect another collection with the current collection
*
* @param Collection $collection
* @return $this
*/
public function intersect(Collection $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 parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* 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 mixed $offset The offset to retrieve.
*
* @return mixed Can return all value types.
*/
public function offsetGet($offset)
{
return $this->pages->get($offset) ?: null;
}
/**
* Split collection into array of smaller collections.
*
* @param int $size
* @return array|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 $manual
* @param string $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)
{
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)
{
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 the index of the current page.
*/
public function currentPosition($path)
{
return \array_search($path, \array_keys($this->items), true);
}
/**
* Returns the items between a set of date ranges of either the page date field (default) or
* an arbitrary datetime page field where end date is optional
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param string $startDate
* @param bool $endDate
* @param string|null $field
*
* @return $this
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false, $field = null)
{
$start = Utils::date2timestamp($startDate);
$end = $endDate ? Utils::date2timestamp($endDate) : false;
$date_range = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null) {
$date = $field ? strtotime($page->value($field)) : $page->date();
if ($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 modular pages
*
* @return Collection The collection with only modular pages
*/
public function modular()
{
$modular = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null && $page->modular()) {
$modular[$path] = $slug;
}
}
$this->items = $modular;
return $this;
}
/**
* Creates new collection with only non-modular pages
*
* @return Collection The collection with only non-modular pages
*/
public function nonModular()
{
$modular = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null && !$page->modular()) {
$modular[$path] = $slug;
}
}
$this->items = $modular;
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)) {
$valid = true;
}
}
} else {
if (\in_array($index, $accessLevels)) {
$valid = true;
}
}
}
if ($valid) {
$items[$path] = $slug;
}
} else {
//Single value for access
if (\in_array($page->header()->access, $accessLevels)) {
$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;
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page;
use RocketTheme\Toolbox\ArrayTraits\Constructor;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
class Header implements \ArrayAccess
{
use NestedArrayAccess, Constructor;
}
@@ -0,0 +1,267 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Interfaces;
use Grav\Common\Page\Media;
/**
* 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 $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 $size Max summary size.
*
* @param bool $textOnly Only count text size.
*
* @return string
*/
public function summary($size = null, $textOnly = false);
/**
* Gets and Sets the content based on content portion of the .md file
*
* @param string $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 $content
*/
public function setRawContent($content);
/**
* Gets and Sets the Page raw content
*
* @param string|null $var
*
* @return null
*/
public function rawMarkdown($var = null);
/**
* Get value from a page variable (used mostly for creating edit forms).
*
* @param string $name Variable name.
* @param mixed $default
*
* @return mixed
*/
public function value($name, $default = null);
/**
* Gets and sets the associated media as found in the page folder.
*
* @param Media $var Representation of associated media.
*
* @return Media 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 $var the 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 $var the 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 $var true if the page is visible
*
* @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 $var true if the page is published
*
* @return bool true if the page is published
*/
public function published($var = null);
/**
* Gets and Sets the Page publish date
*
* @param string $var string representation of a date
*
* @return int unix timestamp representation of the date
*/
public function publishDate($var = null);
/**
* Gets and Sets the Page unpublish date
*
* @param string $var string representation of a 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 $var an Array of name value pairs where the name is the process and value is true or false
*
* @return array an 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 $var the slug, e.g. 'my-blog'
*
* @return string the slug
*/
public function slug($var = null);
/**
* Get/set order number of this page.
*
* @param int $var
*
* @return int|bool
*/
public function order($var = null);
/**
* Gets and sets the identifier for this Page object.
*
* @param string $var the identifier
*
* @return string the identifier
*/
public function id($var = null);
/**
* Gets and sets the modified timestamp.
*
* @param int $var 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 boolean $var show last_modified header
*
* @return boolean show last_modified header
*/
public function lastModified($var = null);
/**
* Get/set the folder.
*
* @param string $var Optional path
*
* @return string|null
*/
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 $var 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 $var 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 $var an 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, eg "twig" or "markdown"
*
* @return bool whether or not the processing method is enabled for this Page
*/
public function shouldProcess($process);
/**
* 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,19 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Interfaces;
use Grav\Common\Media\Interfaces\MediaInterface;
/**
* Class implements page interface.
*/
interface PageInterface extends PageContentInterface, PageRoutableInterface, PageTranslateInterface, MediaInterface, PageLegacyInterface
{
}
@@ -0,0 +1,496 @@
<?php
namespace Grav\Common\Page\Interfaces;
use Exception;
use Grav\Common\Data\Blueprint;
use Grav\Common\Page\Collection;
use RocketTheme\Toolbox\File\MarkdownFile;
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 $extension
*
* @return $this
*/
public function init(\SplFileInfo $file, $extension = null);
/**
* Gets and Sets the raw data
*
* @param string $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();
public function httpHeaders();
/**
* Sets the summary of the page
*
* @param string $summary Summary
*/
public function setSummary($summary);
/**
* 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 string $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 $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 $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 null $var
*
* @return null
*/
public function templateFormat($var = null);
/**
* Gets and sets the extension field.
*
* @param null $var
*
* @return null|string
*/
public function extension($var = null);
/**
* Gets and sets the expires field. If not set will return the default
*
* @param int $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 null $var
* @return null
*/
public function cacheControl($var = null);
public function ssl($var = null);
/**
* Returns the state of the debugger override etting for this page
*
* @return mixed
*/
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 $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 $var show etag header
*
* @return bool show etag header
*/
public function eTag($var = null);
/**
* Gets and sets the path to the .md file for this Page object.
*
* @param string $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 $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 $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 $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 $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 $var true if modular_twig
*
* @return bool true if modular_twig
*/
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 $var true if modular_twig
*
* @return bool true if modular_twig
*/
public function modularTwig($var = null);
/**
* Returns children of this page.
*
* @return \Grav\Common\Page\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|bool the sibling page
*/
public function adjacentSibling($direction = 1);
/**
* Helper method to return an ancestor page.
*
* @param bool $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 mixed
* @internal
*/
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,188 @@
<?php
namespace 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 $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 $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 $var Set new default route.
*
* @return string 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 $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 $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 $var the path
*
* @return string|null the path
*/
public function path($var = null);
/**
* Get/set the folder.
*
* @param string $var Optional path
*
* @return string|null
*/
public function folder($var = null);
/**
* Gets and Sets the parent object for this page
*
* @param PageInterface $var the parent page object
*
* @return PageInterface|null the parent page object if it exists.
*/
public function parent(PageInterface $var = null);
/**
* Gets the top parent object for this page
*
* @return PageInterface|null the top parent page object if it exists.
*/
public function topParent();
/**
* Returns the item in the current position.
*
* @return int 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,32 @@
<?php
namespace 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 $var
*
* @return mixed
*/
public function language($var = null);
}
@@ -0,0 +1,329 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Markdown;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Medium\Link;
use Grav\Common\Uri;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Utils;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Excerpts
{
/** @var PageInterface */
protected $page;
/** @var array */
protected $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;
}
public function getPage(): PageInterface
{
return $this->page;
}
public function getConfig(): array
{
return $this->config;
}
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
{
$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 = ['rel', 'target', 'id', 'class', 'classes'];
// Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) {
unset($actions['noprocess']);
} else {
// Loop through actions for the image and call them.
foreach ($actions as $attrib => $value) {
$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, null, '&', 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.
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$grav = Grav::instance();
$url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['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();
// 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'] . $grav['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
/** @var PageInterface $ext_page */
$ext_page = $grav['pages']->dispatch($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;
},
[]
);
}
$config = $this->getConfig();
if (!empty($config['images']['auto_fix_orientation'])) {
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
}
$defaults = $config['images']['defaults'] ?? [];
if (count($defaults)) {
foreach ($defaults as $method => $params) {
$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|bool
*/
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;
}
/**
* @param string $url
* @return bool|string
*/
protected function resolveStream(string $url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($url)) {
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
}
return $url;
}
}
+222
View File
@@ -0,0 +1,222 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page;
use Grav\Common\Grav;
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;
class Media extends AbstractMedia
{
protected static $global;
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
/**
* @param string $path
* @param array $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 (!isset(static::$global)) {
// Add fallback to global media.
static::$global = new GlobalMedia();
}
}
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
}
/**
* @param mixed $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
return parent::offsetGet($offset) ?: static::$global[$offset];
}
/**
* Initialize class.
*/
protected function init()
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$config = Grav::instance()['config'];
$locator = Grav::instance()['locator'];
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
// Handle special cases where page doesn't exist in filesystem.
if (!is_dir($this->getPath())) {
return;
}
$iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
continue;
}
// Find out what type we're dealing with
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
if (!\in_array(strtolower($ext), $media_types, true)) {
continue;
}
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
} else {
$media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
}
}
foreach ($media as $name => $types) {
// First prepare the alternatives in case there is no base medium
if (!empty($types['alternative'])) {
foreach ($types['alternative'] as $ratio => &$alt) {
$alt['file'] = MediumFactory::fromFile($alt['file']);
if (!$alt['file']) {
unset($types['alternative'][$ratio]);
} else {
$alt['file']->set('size', $alt['size']);
}
}
}
$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 = MediumFactory::fromFile($types['base']['file']);
$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
* @deprecated 1.6 Use $this->getPath() instead.
*/
public function path()
{
return $this->getPath();
}
}
@@ -0,0 +1,244 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
use Grav\Common\Page\Page;
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;
abstract class AbstractMedia implements ExportInterface, MediaCollectionInterface
{
use ArrayAccess;
use Countable;
use Iterator;
use Export;
protected $items = [];
protected $path;
protected $images = [];
protected $videos = [];
protected $audios = [];
protected $files = [];
protected $media_order;
/**
* Return media path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
public function setPath(?string $path)
{
$this->path = $path;
}
/**
* Get medium by filename.
*
* @param string $filename
* @return Medium|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)
{
/** @var Medium $instance */
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 $file
*/
public function add($name, $file)
{
if (!$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;
}
}
/**
* Order the media based on the page's media_order
*
* @param array $media
* @return array
*/
protected function orderMedia($media)
{
if (null === $this->media_order) {
/** @var Page $page */
$page = Grav::instance()['pages']->get($this->getPath());
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;
}
/**
* 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 array($name, $extension, $type, $extra);
}
}
@@ -0,0 +1,148 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
class AudioMedium extends Medium
{
use StaticResizeTrait;
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
$location = $this->url($reset);
return [
'name' => 'audio',
'text' => '<source src="' . $location . '">Your browser does not support the audio tag.',
'attributes' => $attributes
];
}
/**
* Allows to set or remove the HTML5 default controls
*
* @param bool $display
* @return $this
*/
public function controls($display = true)
{
if($display) {
$this->attributes['controls'] = true;
} else {
unset($this->attributes['controls']);
}
return $this;
}
/**
* Allows to set the preload behaviour
*
* @param string $preload
* @return $this
*/
public function preload($preload)
{
$validPreloadAttrs = ['auto', 'metadata', 'none'];
if (\in_array($preload, $validPreloadAttrs, true)) {
$this->attributes['preload'] = $preload;
}
return $this;
}
/**
* Allows to set the controlsList behaviour
* Separate multiple values with a hyphen
*
* @param string $controlsList
* @return $this
*/
public function controlsList($controlsList)
{
$controlsList = str_replace('-', ' ', $controlsList);
$this->attributes['controlsList'] = $controlsList;
return $this;
}
/**
* Allows to set the muted attribute
*
* @param bool $status
* @return $this
*/
public function muted($status = false)
{
if($status) {
$this->attributes['muted'] = true;
} else {
unset($this->attributes['muted']);
}
return $this;
}
/**
* Allows to set the loop attribute
*
* @param bool $status
* @return $this
*/
public function loop($status = false)
{
if($status) {
$this->attributes['loop'] = true;
} else {
unset($this->attributes['loop']);
}
return $this;
}
/**
* Allows to set the autoplay attribute
*
* @param bool $status
* @return $this
*/
public function autoplay($status = false)
{
if($status) {
$this->attributes['autoplay'] = true;
} else {
unset($this->attributes['autoplay']);
}
return $this;
}
/**
* Reset medium.
*
* @return $this
*/
public function reset()
{
parent::reset();
$this->attributes['controls'] = true;
return $this;
}
}
@@ -0,0 +1,128 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class GlobalMedia extends AbstractMedia
{
/**
* Return media path.
*
* @return null
*/
public function getPath()
{
return null;
}
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
}
/**
* @param mixed $offset
*
* @return mixed
*/
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'];
return $locator->isStream($filename) ? ($locator->findResource($filename) ?: null) : null;
}
/**
* @param string $stream
* @return Medium|null
*/
protected function addMedium($stream)
{
$filename = $this->resolveStream($stream);
if (!$filename) {
return null;
}
$path = dirname($filename);
list($basename, $ext,, $extra) = $this->getFileParts(basename($filename));
$medium = MediumFactory::fromFile($filename);
if (empty($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,145 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use Gregwar\Image\Exceptions\GenerationError;
use Gregwar\Image\Image;
use Gregwar\Image\Source;
use RocketTheme\Toolbox\Event\Event;
class ImageFile extends Image
{
public function __destruct()
{
$this->getAdapter()->deinit();
}
/**
* Clear previously applied operations
*/
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);
// Seo friendly image names
$seofriendly = Grav::instance()['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 = Grav::instance()['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
$this->getAdapter()->setSource(new Source\File($file));
$this->getAdapter()->deinit();
if ($actual) {
return $file;
}
return $this->getFilename($file);
}
/**
* Gets the hash.
* @param string $type
* @param int $quality
* @param [] $extras
* @return null
*/
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();
$datas = array(
$inputInfos,
$this->serializeOperations(),
$type,
$quality,
$extras
);
$this->hash = sha1(serialize($datas));
}
}
@@ -0,0 +1,663 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class ImageMedium extends Medium
{
/**
* @var array
*/
protected $thumbnailTypes = ['page', 'media', 'default'];
/**
* @var ImageFile
*/
protected $image;
/**
* @var string
*/
protected $format = 'guess';
/**
* @var int
*/
protected $quality;
/**
* @var int
*/
protected $default_quality;
/**
* @var bool
*/
protected $debug_watermarked = false;
/**
* @var array
*/
public static $magic_actions = [
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive',
'rotate', 'flip', 'fixOrientation', 'gaussianBlur'
];
/**
* @var array
*/
public static $magic_resize_actions = [
'resize' => [0, 1],
'forceResize' => [0, 1],
'cropResize' => [0, 1],
'crop' => [0, 1, 2, 3],
'zoomCrop' => [0, 1]
];
/**
* @var string
*/
protected $sizes = '100vw';
/**
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
*/
public function __construct($items = [], Blueprint $blueprint = null)
{
parent::__construct($items, $blueprint);
$config = Grav::instance()['config'];
$path = $this->get('filepath');
if (!$path || !file_exists($path) || !filesize($path)) {
return;
}
$image_info = getimagesize($path);
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
$this->def('debug', $config->get('system.images.debug'));
$this->set('thumbnails.media', $this->get('filepath'));
$this->default_quality = $config->get('system.images.default_image_quality', 85);
$this->reset();
if ($config->get('system.images.cache_all', false)) {
$this->cache();
}
}
public function __destruct()
{
unset($this->image);
}
public function __clone()
{
$this->image = $this->image ? clone $this->image : null;
parent::__clone();
}
/**
* 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;
}
/**
* Clear out the alternatives
*/
public function clearAlternatives()
{
$this->alternatives = [];
}
/**
* 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)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
$saved_image_path = $this->saveImage();
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
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::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
}
/**
* Simply processes with no extra methods. Useful for triggering events.
*
* @return $this
*/
public function cache()
{
if (!$this->image) {
$this->image();
}
return $this;
}
/**
* 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);
}
/**
* Allows the ability to override the image's pretty name stored in cache
*
* @param string $name
*/
public function setImagePrettyName($name)
{
$this->set('prettyname', $name);
if ($this->image) {
$this->image->setPrettyName($name);
}
}
public function getImagePrettyName()
{
if ($this->get('prettyname')) {
return $this->get('prettyname');
}
$basename = $this->get('basename');
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
$basename = $matches[1];
}
return $basename;
}
/**
* Generate alternative image widths, using either an array of integers, or
* a min width, a max width, and a step parameter to fill out the necessary
* widths. Existing image alternatives won't be overwritten.
*
* @param int|int[] $min_width
* @param int $max_width
* @param int $step
* @return $this
*/
public function derivatives($min_width, $max_width = 2500, $step = 200)
{
if (!empty($this->alternatives)) {
$max = max(array_keys($this->alternatives));
$base = $this->alternatives[$max];
} else {
$base = $this;
}
$widths = [];
if (func_num_args() === 1) {
foreach ((array) func_get_arg(0) as $width) {
if ($width < $base->get('width')) {
$widths[] = $width;
}
}
} else {
$max_width = min($max_width, $base->get('width'));
for ($width = $min_width; $width < $max_width; $width = $width + $step) {
$widths[] = $width;
}
}
foreach ($widths as $width) {
// Only generate image alternatives that don't already exist
if (array_key_exists((int) $width, $this->alternatives)) {
continue;
}
$derivative = MediumFactory::fromFile($base->get('filepath'));
// It's possible that MediumFactory::fromFile returns null if the
// original image file no longer exists and this class instance was
// retrieved from the page cache
if (null !== $derivative) {
$index = 2;
$alt_widths = array_keys($this->alternatives);
sort($alt_widths);
foreach ($alt_widths as $i => $key) {
if ($width > $key) {
$index += max($i, 1);
}
}
$basename = preg_replace('/(@\d+x){0,1}$/', "@{$width}w", $base->get('basename'), 1);
$derivative->setImagePrettyName($basename);
$ratio = $base->get('width') / $width;
$height = $derivative->get('height') / $ratio;
$derivative->resize($width, $height);
$derivative->set('width', $width);
$derivative->set('height', $height);
$this->addAlternative($ratio, $derivative);
}
}
return $this;
}
/**
* 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();
}
return ['name' => 'img', 'attributes' => $attributes];
}
/**
* 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;
return $this;
}
/**
* Turn the current Medium into a Link
*
* @param bool $reset
* @param array $attributes
* @return Link
*/
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 Link
*/
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);
}
/**
* Sets or gets the quality of the image
*
* @param int $quality 0-100 quality
* @return int|$this
*/
public function quality($quality = null)
{
if ($quality) {
if (!$this->image) {
$this->image();
}
$this->quality = $quality;
return $this;
}
return $this->quality;
}
/**
* Sets image output format.
*
* @param string $format
* @return $this
*/
public function format($format)
{
if (!$this->image) {
$this->image();
}
$this->format = $format;
return $this;
}
/**
* Set or get sizes parameter for srcset media action
*
* @param string $sizes
* @return string
*/
public function sizes($sizes = null)
{
if ($sizes) {
$this->sizes = $sizes;
return $this;
}
return empty($this->sizes) ? '100vw' : $this->sizes;
}
/**
* Allows to set the width attribute from Markdown or Twig
* Examples: ![Example](myimg.png?width=200&height=400)
* ![Example](myimg.png?resize=100,200&width=100&height=200)
* ![Example](myimg.png?width=auto&height=auto)
* ![Example](myimg.png?width&height)
* {{ page.media['myimg.png'].width().height().html }}
* {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
*
* @param mixed $value A value or 'auto' or empty to use the width of the image
* @return $this
*/
public function width($value = 'auto')
{
if (!$value || $value === 'auto') {
$this->attributes['width'] = $this->get('width');
} else {
$this->attributes['width'] = $value;
}
return $this;
}
/**
* Allows to set the height attribute from Markdown or Twig
* Examples: ![Example](myimg.png?width=200&height=400)
* ![Example](myimg.png?resize=100,200&width=100&height=200)
* ![Example](myimg.png?width=auto&height=auto)
* ![Example](myimg.png?width&height)
* {{ page.media['myimg.png'].width().height().html }}
* {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
*
* @param mixed $value A value or 'auto' or empty to use the height of the image
* @return $this
*/
public function height($value = 'auto')
{
if (!$value || $value === 'auto') {
$this->attributes['height'] = $this->get('height');
} else {
$this->attributes['height'] = $value;
}
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 ($method === 'cropZoom') {
$method = 'zoomCrop';
}
if (!\in_array($method, self::$magic_actions, true)) {
return parent::__call($method, $args);
}
// Always initialize image.
if (!$this->image) {
$this->image();
}
try {
call_user_func_array([$this->image, $method], $args);
foreach ($this->alternatives as $medium) {
if (!$medium->image) {
$medium->image();
}
$args_copy = $args;
// regular image: resize 400x400 -> 200x200
// --> @2x: resize 800x800->400x400
if (isset(self::$magic_resize_actions[$method])) {
foreach (self::$magic_resize_actions[$method] as $param) {
if (isset($args_copy[$param])) {
$args_copy[$param] *= $medium->get('ratio');
}
}
}
call_user_func_array([$medium, $method], $args_copy);
}
} catch (\BadFunctionCallException $e) {
}
return $this;
}
/**
* Gets medium image, resets image manipulation operations.
*
* @return $this
*/
protected function image()
{
$locator = Grav::instance()['locator'];
$file = $this->get('filepath');
// Use existing cache folder or if it doesn't exist, create it.
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
// Make sure we free previous image.
unset($this->image);
$this->image = ImageFile::open($file)
->setCacheDir($cacheDir)
->setActualCacheDir($cacheDir)
->setPrettyName($this->getImagePrettyName());
return $this;
}
/**
* Save the image with cache.
*
* @return string
*/
protected function saveImage()
{
if (!$this->image) {
return parent::path(false);
}
$this->filter();
if (isset($this->result)) {
return $this->result;
}
if (!$this->debug_watermarked && $this->get('debug')) {
$ratio = $this->get('ratio');
if (!$ratio) {
$ratio = 1;
}
$locator = Grav::instance()['locator'];
$overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png');
$this->image->merge(ImageFile::open($overlay));
}
return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
}
/**
* Filter image by using user defined filter parameters.
*
* @param string $filter Filter to be used.
*/
public function filter($filter = 'image.filters.default')
{
$filters = (array) $this->get($filter, []);
foreach ($filters as $params) {
$params = (array) $params;
$method = array_shift($params);
$this->__call($method, $params);
}
}
/**
* Return the image higher quality version
*
* @return ImageMedium the alternative version with higher quality
*/
public function higherQualityAlternative()
{
if ($this->alternatives) {
$max = reset($this->alternatives);
foreach($this->alternatives as $alternative)
{
if($alternative->quality() > $max->quality())
{
$max = $alternative;
}
}
return $max;
}
return $this;
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
class Link implements RenderableInterface
{
use ParsedownHtmlTrait;
/**
* @var array
*/
protected $attributes = [];
protected $source;
/**
* Construct.
* @param array $attributes
* @param Medium $medium
*/
public function __construct(array $attributes, Medium $medium)
{
$this->attributes = $attributes;
$this->source = $medium->reset()->thumbnail('auto')->display('thumbnail');
$this->source->linked = true;
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $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_string($innerElement) ? 'line' : 'element',
'text' => $innerElement
];
}
/**
* Forward the call to the source element
*
* @param string $method
* @param mixed $args
* @return mixed
*/
public function __call($method, $args)
{
$this->source = call_user_func_array(array($this->source, $method), $args);
// Don't start nesting links, if user has multiple link calls in his
// actions, we will drop the previous links.
return $this->source instanceof Link ? $this->source : $this;
}
}
@@ -0,0 +1,679 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\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\MediaObjectInterface;
use Grav\Common\Utils;
/**
* Class Medium
* @package Grav\Common\Page\Medium
*
* @property string $mime
*/
class Medium extends Data implements RenderableInterface, MediaObjectInterface
{
use ParsedownHtmlTrait;
/**
* @var string
*/
protected $mode = 'source';
/**
* @var Medium
*/
protected $_thumbnail = null;
/**
* @var array
*/
protected $thumbnailTypes = ['page', 'default'];
protected $thumbnailType = null;
/**
* @var Medium[]
*/
protected $alternatives = [];
/**
* @var array
*/
protected $attributes = [];
/**
* @var array
*/
protected $styleAttributes = [];
/**
* @var array
*/
protected $metadata = [];
/**
* @var array
*/
protected $medium_querystring = [];
protected $timestamp;
/**
* Construct.
*
* @param array $items
* @param Blueprint $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');
$this->reset();
}
public function __clone()
{
// Allows future compatibility as parent::__clone() works.
}
/**
* Create a copy of this media object
*
* @return Medium
*/
public function copy()
{
return clone $this;
}
/**
* Return just metadata from the Medium object
*
* @return Data
*/
public function meta()
{
return new Data($this->items);
}
/**
* Check if this medium exists or not
*
* @return bool
*/
public function exists()
{
$path = $this->get('filepath');
if (file_exists($path)) {
return true;
}
return false;
}
/**
* Get file modification time for the medium.
*
* @return int|null
*/
public function modified()
{
$path = $this->get('filepath');
if (!file_exists($path)) {
return null;
}
return filemtime($path) ?: null;
}
/**
* @return int
*/
public function size()
{
$path = $this->get('filepath');
if (!file_exists($path)) {
return 0;
}
return filesize($path) ?: 0;
}
/**
* Set querystring to file modification timestamp (or value provided as a parameter).
*
* @param string|int|null $timestamp
* @return $this
*/
public function setTimestamp($timestamp = null)
{
$this->timestamp = (string)($timestamp ?? $this->modified());
return $this;
}
/**
* Returns an array containing just the metadata
*
* @return array
*/
public function metadata()
{
return $this->metadata;
}
/**
* Add meta file for the medium.
*
* @param string $filepath
*/
public function addMetaFile($filepath)
{
$this->metadata = (array)CompiledYamlFile::instance($filepath)->content();
$this->merge($this->metadata);
}
/**
* Add alternative Medium to this Medium.
*
* @param int|float $ratio
* @param Medium $alternative
*/
public function addAlternative($ratio, Medium $alternative)
{
if (!is_numeric($ratio) || $ratio === 0) {
return;
}
$alternative->set('ratio', $ratio);
$width = $alternative->get('width');
$this->alternatives[$width] = $alternative;
}
/**
* Return string representation of the object (html).
*
* @return string
*/
public function __toString()
{
return $this->html();
}
/**
* Return PATH to file.
*
* @param bool $reset
* @return string path to file
*/
public function path($reset = true)
{
if ($reset) {
$this->reset();
}
return $this->get('filepath');
}
/**
* Return the relative path to file
*
* @param bool $reset
* @return mixed
*/
public function relativePath($reset = true)
{
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
$locator = Grav::instance()['locator'];
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if ($reset) {
$this->reset();
}
return str_replace(GRAV_ROOT, '', $output);
}
/**
* Return URL to file.
*
* @param bool $reset
* @return string
*/
public function url($reset = true)
{
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
$locator = Grav::instance()['locator'];
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if ($reset) {
$this->reset();
}
return trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
}
/**
* Get/set querystring for the file's url
*
* @param string $querystring
* @param bool $withQuestionmark
* @return string
*/
public function querystring($querystring = null, $withQuestionmark = true)
{
if (null !== $querystring) {
$this->medium_querystring[] = ltrim($querystring, '?&');
foreach ($this->alternatives as $alt) {
$alt->querystring($querystring, $withQuestionmark);
}
}
if (empty($this->medium_querystring)) {
return '';
}
// join the strings
$querystring = implode('&', $this->medium_querystring);
// explode all strings
$query_parts = explode('&', $querystring);
// Join them again now ensure the elements are unique
$querystring = implode('&', array_unique($query_parts));
return $withQuestionmark ? ('?' . $querystring) : $querystring;
}
/**
* Get the URL with full querystring
*
* @param string $url
* @return string
*/
public function urlQuerystring($url)
{
$querystring = $this->querystring();
if (isset($this->timestamp) && !Utils::contains($querystring, $this->timestamp)) {
$querystring = empty($querystring) ? ('?' . $this->timestamp) : ($querystring . '&' . $this->timestamp);
}
return ltrim($url . $querystring . $this->urlHash(), '/');
}
/**
* Get/set hash for the file's url
*
* @param string $hash
* @param bool $withHash
* @return string
*/
public function urlHash($hash = null, $withHash = true)
{
if ($hash) {
$this->set('urlHash', ltrim($hash, '#'));
}
$hash = $this->get('urlHash', '');
return $withHash && !empty($hash) ? '#' . $hash : $hash;
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param bool $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
{
$attributes = $this->attributes;
$style = '';
foreach ($this->styleAttributes as $key => $value) {
if (is_numeric($key)) // Special case for inline style attributes, refer to style() method
$style .= $value;
else
$style .= $key . ': ' . $value . ';';
}
if ($style) {
$attributes['style'] = $style;
}
if (empty($attributes['title'])) {
if (!empty($title)) {
$attributes['title'] = $title;
} elseif (!empty($this->items['title'])) {
$attributes['title'] = $this->items['title'];
}
}
if (empty($attributes['alt'])) {
if (!empty($alt)) {
$attributes['alt'] = $alt;
} elseif (!empty($this->items['alt'])) {
$attributes['alt'] = $this->items['alt'];
} elseif (!empty($this->items['alt_text'])) {
$attributes['alt'] = $this->items['alt_text'];
} else {
$attributes['alt'] = '';
}
}
if (empty($attributes['class'])) {
if (!empty($class)) {
$attributes['class'] = $class;
} elseif (!empty($this->items['class'])) {
$attributes['class'] = $this->items['class'];
}
}
if (empty($attributes['id'])) {
if (!empty($id)) {
$attributes['id'] = $id;
} elseif (!empty($this->items['id'])) {
$attributes['id'] = $this->items['id'];
}
}
switch ($this->mode) {
case 'text':
$element = $this->textParsedownElement($attributes, false);
break;
case 'thumbnail':
$element = $this->getThumbnail()->sourceParsedownElement($attributes, false);
break;
case 'source':
$element = $this->sourceParsedownElement($attributes, false);
break;
default:
$element = [];
}
if ($reset) {
$this->reset();
}
$this->display('source');
return $element;
}
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
return $this->textParsedownElement($attributes, $reset);
}
/**
* Parsedown element for text display mode
*
* @param array $attributes
* @param bool $reset
* @return array
*/
protected function textParsedownElement(array $attributes, $reset = true)
{
$text = empty($attributes['title']) ? empty($attributes['alt']) ? $this->get('filename') : $attributes['alt'] : $attributes['title'];
$element = [
'name' => 'p',
'attributes' => $attributes,
'text' => $text
];
if ($reset) {
$this->reset();
}
return $element;
}
/**
* Reset medium.
*
* @return $this
*/
public function reset()
{
$this->attributes = [];
return $this;
}
/**
* Switch display mode.
*
* @param string $mode
*
* @return $this
*/
public function display($mode = 'source')
{
if ($this->mode === $mode) {
return $this;
}
$this->mode = $mode;
return $mode === 'thumbnail' ? ($this->getThumbnail() ? $this->getThumbnail()->reset() : null) : $this->reset();
}
/**
* Helper method to determine if this media item has a thumbnail or not
*
* @param string $type;
*
* @return bool
*/
public function thumbnailExists($type = 'page')
{
$thumbs = $this->get('thumbnails');
if (isset($thumbs[$type])) {
return true;
}
return false;
}
/**
* Switch thumbnail.
*
* @param string $type
*
* @return $this
*/
public function thumbnail($type = 'auto')
{
if ($type !== 'auto' && !\in_array($type, $this->thumbnailTypes, true)) {
return $this;
}
if ($this->thumbnailType !== $type) {
$this->_thumbnail = null;
}
$this->thumbnailType = $type;
return $this;
}
/**
* Turn the current Medium into a Link
*
* @param bool $reset
* @param array $attributes
* @return Link
*/
public function link($reset = true, array $attributes = [])
{
if ($this->mode !== 'source') {
$this->display('source');
}
foreach ($this->attributes as $key => $value) {
empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value;
}
empty($attributes['href']) && $attributes['href'] = $this->url();
return new Link($attributes, $this);
}
/**
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height
* @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
{
$attributes = ['rel' => 'lightbox'];
if ($width && $height) {
$attributes['data-width'] = $width;
$attributes['data-height'] = $height;
}
return $this->link($reset, $attributes);
}
/**
* Add a class to the element from Markdown or Twig
* Example: ![Example](myimg.png?classes=float-left) or ![Example](myimg.png?classes=myclass1,myclass2)
*
* @return $this
*/
public function classes()
{
$classes = func_get_args();
if (!empty($classes)) {
$this->attributes['class'] = implode(',', $classes);
}
return $this;
}
/**
* Add an id to the element from Markdown or Twig
* Example: ![Example](myimg.png?id=primary-img)
*
* @param string $id
* @return $this
*/
public function id($id)
{
if (is_string($id)) {
$this->attributes['id'] = trim($id);
}
return $this;
}
/**
* Allows to add an inline style attribute from Markdown or Twig
* Example: ![Example](myimg.png?style=float:left)
*
* @param string $style
* @return $this
*/
public function style($style)
{
$this->styleAttributes[] = rtrim($style, ';') . ';';
return $this;
}
/**
* Allow any action to be called on this medium from twig or markdown
*
* @param string $method
* @param mixed $args
* @return $this
*/
public function __call($method, $args)
{
$qs = $method;
if (\count($args) > 1 || (\count($args) === 1 && !empty($args[0]))) {
$qs .= '=' . implode(',', array_map(function ($a) {
if (is_array($a)) {
$a = '[' . implode(',', $a) . ']';
}
return rawurlencode($a);
}, $args));
}
if (!empty($qs)) {
$this->querystring($this->querystring(null, false) . '&' . $qs);
}
return $this;
}
/**
* Get the thumbnail Medium object
*
* @return ThumbnailImageMedium
*/
protected function getThumbnail()
{
if (!$this->_thumbnail) {
$types = $this->thumbnailTypes;
if ($this->thumbnailType !== 'auto') {
array_unshift($types, $this->thumbnailType);
}
foreach ($types as $type) {
$thumb = $this->get('thumbnails.' . $type, false);
if ($thumb) {
$thumb = $thumb instanceof ThumbnailImageMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
$thumb->parent = $this;
}
if ($thumb) {
$this->_thumbnail = $thumb;
break;
}
}
}
return $this->_thumbnail;
}
}
@@ -0,0 +1,192 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use Grav\Common\Data\Blueprint;
use Grav\Framework\Form\FormFlashFile;
class MediumFactory
{
/**
* Create Medium from a file
*
* @param string $file
* @param array $params
* @return Medium
*/
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 = $config->get('media.types.' . strtolower($ext));
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' => 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 FormFlashFile $uploadedFile
* @param array $params
* @return Medium
*/
public static function fromUploadedFile(FormFlashFile $uploadedFile, array $params = [])
{
$parts = pathinfo($uploadedFile->getClientFilename());
$filename = $parts['basename'];
$ext = $parts['extension'];
$basename = $parts['filename'];
$file = $uploadedFile->getTmpFile();
$path = dirname($file);
$config = Grav::instance()['config'];
$media_params = $config->get('media.types.' . strtolower($ext));
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' => 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 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 ImageMedium $medium
* @param int $from
* @param int $to
* @return Medium|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,42 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Page\Markdown\Excerpts;
trait ParsedownHtmlTrait
{
/**
* @var \Grav\Common\Markdown\Parsedown
*/
protected $parsedown;
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $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,36 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
interface RenderableInterface
{
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param bool $reset
* @return string
*/
public function html($title = null, $alt = null, $class = null, $reset = true);
/**
* Return Parsedown Element from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param bool $reset
* @return string
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true);
}
@@ -0,0 +1,29 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
class StaticImageMedium extends Medium
{
use StaticResizeTrait;
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
empty($attributes['src']) && $attributes['src'] = $this->url($reset);
return ['name' => 'img', 'attributes' => $attributes];
}
}
@@ -0,0 +1,28 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
trait StaticResizeTrait
{
/**
* Resize media by setting attributes
*
* @param int $width
* @param int $height
* @return $this
*/
public function resize($width = null, $height = null)
{
$this->styleAttributes['width'] = $width . 'px';
$this->styleAttributes['height'] = $height . 'px';
return $this;
}
}
@@ -0,0 +1,132 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
class ThumbnailImageMedium extends ImageMedium
{
/**
* @var Medium
*/
public $parent = null;
/**
* @var bool
*/
public $linked = false;
/**
* Return srcset string for this Medium and its alternatives.
*
* @param bool $reset
* @return string
*/
public function srcset($reset = true)
{
return '';
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param bool $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
{
return $this->bubble('parsedownElement', [$title, $alt, $class, $id, $reset]);
}
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param bool $reset
* @return string
*/
public function html($title = null, $alt = null, $class = null, $id = null, $reset = true)
{
return $this->bubble('html', [$title, $alt, $class, $id, $reset]);
}
/**
* Switch display mode.
*
* @param string $mode
*
* @return $this
*/
public function display($mode = 'source')
{
return $this->bubble('display', [$mode], false);
}
/**
* Switch thumbnail.
*
* @param string $type
*
* @return $this
*/
public function thumbnail($type = 'auto')
{
$this->bubble('thumbnail', [$type], false);
return $this->bubble('getThumbnail', [], false);
}
/**
* Turn the current Medium into a Link
*
* @param bool $reset
* @param array $attributes
* @return Link
*/
public function link($reset = true, array $attributes = [])
{
return $this->bubble('link', [$reset, $attributes], false);
}
/**
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height
* @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
{
return $this->bubble('lightbox', [$width, $height, $reset], false);
}
/**
* Bubble a function call up to either the superclass function or the parent Medium instance
*
* @param string $method
* @param array $arguments
* @param bool $testLinked
* @return Medium
*/
protected function bubble($method, array $arguments = [], $testLinked = true)
{
if (!$testLinked || $this->linked) {
return $this->parent ? call_user_func_array(array($this->parent, $method), $arguments) : $this;
}
return call_user_func_array(array($this, 'parent::' . $method), $arguments);
}
}
@@ -0,0 +1,162 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
class VideoMedium extends Medium
{
use StaticResizeTrait;
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
$location = $this->url($reset);
return [
'name' => 'video',
'text' => '<source src="' . $location . '">Your browser does not support the video tag.',
'attributes' => $attributes
];
}
/**
* Allows to set or remove the HTML5 default controls
*
* @param bool $display
* @return $this
*/
public function controls($display = true)
{
if($display) {
$this->attributes['controls'] = true;
} else {
unset($this->attributes['controls']);
}
return $this;
}
/**
* Allows to set the video's poster image
*
* @param string $urlImage
* @return $this
*/
public function poster($urlImage)
{
$this->attributes['poster'] = $urlImage;
return $this;
}
/**
* Allows to set the loop attribute
*
* @param bool $status
* @return $this
*/
public function loop($status = false)
{
if($status) {
$this->attributes['loop'] = true;
} else {
unset($this->attributes['loop']);
}
return $this;
}
/**
* Allows to set the autoplay attribute
*
* @param bool $status
* @return $this
*/
public function autoplay($status = false)
{
if ($status) {
$this->attributes['autoplay'] = '';
} else {
unset($this->attributes['autoplay']);
}
return $this;
}
/**
* Allows ability to set the preload option
*
* @param null $status
* @return $this
*/
public function preload($status = null)
{
if ($status) {
$this->attributes['preload'] = $status;
} else {
unset($this->attributes['preload']);
}
return $this;
}
/**
* Allows to set the playsinline attribute
*
* @param bool $status
* @return $this
*/
public function playsinline($status = false)
{
if($status) {
$this->attributes['playsinline'] = true;
} else {
unset($this->attributes['playsinline']);
}
return $this;
}
/**
* Allows to set the muted attribute
*
* @param bool $status
* @return $this
*/
public function muted($status = false)
{
if($status) {
$this->attributes['muted'] = true;
} else {
unset($this->attributes['muted']);
}
return $this;
}
/**
* Reset medium.
*
* @return $this
*/
public function reset()
{
parent::reset();
$this->attributes['controls'] = true;
return $this;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
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;
class Types implements \ArrayAccess, \Iterator, \Countable
{
use ArrayAccess, Constructor, Iterator, Countable, Export;
protected $items;
protected $systemBlueprints;
public function register($type, $blueprint = null)
{
if (!isset($this->items[$type])) {
$this->items[$type] = [];
} elseif (!$blueprint) {
return;
}
if (!$blueprint && $this->systemBlueprints) {
$blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'];
}
if ($blueprint) {
array_unshift($this->items[$type], $blueprint);
}
}
public function scanBlueprints($uri)
{
if (!\is_string($uri)) {
throw new \InvalidArgumentException('First parameter must be URI');
}
if (!$this->systemBlueprints) {
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
// Register default by default.
$this->register('default');
$this->register('external');
}
foreach ($this->findBlueprints($uri) as $type => $blueprint) {
$this->register($type, $blueprint);
}
}
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);
}
}
}
public function pageSelect()
{
$list = [];
foreach ($this->items as $name => $file) {
if (strpos($name, '/')) {
continue;
}
$list[$name] = ucfirst(str_replace('_', ' ', $name));
}
ksort($list);
return $list;
}
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;
}
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);
}
}