962 lines
27 KiB
PHP
962 lines
27 KiB
PHP
<?php namespace RainLab\Pages\Classes;
|
|
|
|
use Cms;
|
|
use File;
|
|
use Lang;
|
|
use Cache;
|
|
use Event;
|
|
use Route;
|
|
use Config;
|
|
use Validator;
|
|
use RainLab\Pages\Classes\Router;
|
|
use RainLab\Pages\Classes\Snippet;
|
|
use RainLab\Pages\Classes\PageList;
|
|
use Cms\Classes\Theme;
|
|
use Cms\Classes\Layout;
|
|
use Cms\Classes\Content as ContentBase;
|
|
use Cms\Classes\ComponentManager;
|
|
use System\Helpers\View as ViewHelper;
|
|
use October\Rain\Support\Str;
|
|
use October\Rain\Router\Helper as RouterHelper;
|
|
use October\Rain\Parse\Bracket as TextParser;
|
|
use October\Rain\Parse\Syntax\Parser as SyntaxParser;
|
|
use ApplicationException;
|
|
use Twig\Node\Node as TwigNode;
|
|
|
|
/**
|
|
* Represents a static page.
|
|
*
|
|
* @package rainlab\pages
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
class Page extends ContentBase
|
|
{
|
|
public $implement = [
|
|
'@RainLab.Translate.Behaviors.TranslatablePageUrl',
|
|
'@RainLab.Translate.Behaviors.TranslatableCmsObject'
|
|
];
|
|
|
|
/**
|
|
* @var string The container name associated with the model, eg: pages.
|
|
*/
|
|
protected $dirName = 'content/static-pages';
|
|
|
|
/**
|
|
* @var bool Wrap code section in PHP tags.
|
|
*/
|
|
protected $wrapCode = false;
|
|
|
|
/**
|
|
* @var array Properties that can be set with fill()
|
|
*/
|
|
protected $fillable = [
|
|
'markup',
|
|
'settings',
|
|
'placeholders',
|
|
];
|
|
|
|
/**
|
|
* @var array List of attribute names which are not considered "settings".
|
|
*/
|
|
protected $purgeable = ['parsedMarkup', 'placeholders'];
|
|
|
|
/**
|
|
* @var array The rules to be applied to the data.
|
|
*/
|
|
public $rules = [
|
|
'title' => 'required',
|
|
'url' => ['required', 'regex:/^\/[a-z0-9\/_\-\.]*$/i', 'uniqueUrl']
|
|
];
|
|
|
|
/**
|
|
* @var array The array of custom attribute names.
|
|
*/
|
|
public $attributeNames = [
|
|
'title' => 'title',
|
|
'url' => 'url',
|
|
];
|
|
|
|
/**
|
|
* @var array Attributes that support translation, if available.
|
|
*/
|
|
public $translatable = [
|
|
'code',
|
|
'markup',
|
|
'viewBag[title]',
|
|
'viewBag[meta_title]',
|
|
'viewBag[meta_description]',
|
|
];
|
|
|
|
/**
|
|
* @var string Translation model used for translation, if available.
|
|
*/
|
|
public $translatableModel = 'RainLab\Translate\Classes\MLStaticPage';
|
|
|
|
/**
|
|
* @var string Contains the page parent file name.
|
|
* This property is used by the page editor internally.
|
|
*/
|
|
public $parentFileName;
|
|
|
|
protected static $menuTreeCache = null;
|
|
|
|
protected $parentCache = null;
|
|
|
|
protected $childrenCache = null;
|
|
|
|
protected $processedMarkupCache = false;
|
|
|
|
protected $processedBlockMarkupCache = [];
|
|
|
|
/**
|
|
* Creates an instance of the object and associates it with a CMS theme.
|
|
* @param array $attributes
|
|
*/
|
|
public function __construct(array $attributes = [])
|
|
{
|
|
parent::__construct($attributes);
|
|
|
|
$this->customMessages = [
|
|
'url.regex' => 'rainlab.pages::lang.page.invalid_url',
|
|
'url.unique_url' => 'rainlab.pages::lang.page.url_not_unique',
|
|
];
|
|
}
|
|
|
|
//
|
|
// CMS Object
|
|
//
|
|
|
|
/**
|
|
* Sets the object attributes.
|
|
* @param array $attributes A list of attributes to set.
|
|
*/
|
|
public function fill(array $attributes)
|
|
{
|
|
parent::fill($attributes);
|
|
|
|
/*
|
|
* When the page is saved, copy setting properties to the view bag.
|
|
* This is required for the back-end editors.
|
|
*/
|
|
if (array_key_exists('settings', $attributes) && array_key_exists('viewBag', $attributes['settings'])) {
|
|
$this->getViewBag()->setProperties($attributes['settings']['viewBag']);
|
|
$this->fillViewBagArray();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the attributes used for validation.
|
|
* @return array
|
|
*/
|
|
protected function getValidationAttributes()
|
|
{
|
|
return $this->getAttributes() + $this->viewBag;
|
|
}
|
|
|
|
/**
|
|
* Validates the object properties.
|
|
* Throws a ValidationException in case of an error.
|
|
*/
|
|
public function beforeValidate()
|
|
{
|
|
$pages = Page::listInTheme($this->theme, true);
|
|
|
|
Validator::extend('uniqueUrl', function($attribute, $value, $parameters) use ($pages) {
|
|
$value = trim(strtolower($value));
|
|
|
|
foreach ($pages as $existingPage) {
|
|
if (
|
|
$existingPage->getBaseFileName() !== $this->getBaseFileName() &&
|
|
strtolower($existingPage->getViewBag()->property('url')) == $value
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Triggered before a new object is saved.
|
|
*/
|
|
public function beforeCreate()
|
|
{
|
|
$this->fileName = $this->generateFilenameFromCode();
|
|
}
|
|
|
|
/**
|
|
* Triggered after a new object is saved.
|
|
*/
|
|
public function afterCreate()
|
|
{
|
|
$this->appendToMeta();
|
|
}
|
|
|
|
/**
|
|
* Adds this page to the meta index.
|
|
*/
|
|
protected function appendToMeta()
|
|
{
|
|
$pageList = new PageList($this->theme);
|
|
$pageList->appendPage($this);
|
|
}
|
|
|
|
/*
|
|
* Generate a file name based on the URL
|
|
*/
|
|
protected function generateFilenameFromCode()
|
|
{
|
|
$dir = rtrim($this->getFilePath(''), '/');
|
|
|
|
$fileName = trim(str_slug(str_replace('/', '-', $this->getViewBag()->property('url')), '-'));
|
|
if (strlen($fileName) > 200) {
|
|
$fileName = substr($fileName, 0, 200);
|
|
}
|
|
|
|
if (!strlen($fileName)) {
|
|
$fileName = 'index';
|
|
}
|
|
|
|
$curName = trim($fileName).'.htm';
|
|
$counter = 2;
|
|
|
|
while (File::exists($dir.'/'.$curName)) {
|
|
$curName = $fileName.'-'.$counter.'.htm';
|
|
$counter++;
|
|
}
|
|
|
|
return $curName;
|
|
}
|
|
|
|
/**
|
|
* Deletes the object from the disk.
|
|
* Recursively deletes subpages. Returns a list of file names of deleted pages.
|
|
* @return array
|
|
*/
|
|
public function delete()
|
|
{
|
|
$result = [];
|
|
|
|
/*
|
|
* Delete subpages
|
|
*/
|
|
foreach ($this->getChildren() as $subPage) {
|
|
$result = array_merge($result, $subPage->delete());
|
|
}
|
|
|
|
/*
|
|
* Delete the object
|
|
*/
|
|
$result = array_merge($result, [$this->getBaseFileName()]);
|
|
|
|
parent::delete();
|
|
|
|
/*
|
|
* Remove from meta
|
|
*/
|
|
$this->removeFromMeta();
|
|
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Removes this page to the meta index.
|
|
*/
|
|
protected function removeFromMeta()
|
|
{
|
|
$pageList = new PageList($this->theme);
|
|
$pageList->removeSubtree($this);
|
|
}
|
|
|
|
//
|
|
// Public API
|
|
//
|
|
|
|
/**
|
|
* Helper that makes a URL for a static page in the active theme.
|
|
*
|
|
* Guide for the page reference:
|
|
* - chairs -> content/static-pages/chairs.htm
|
|
*
|
|
* @param mixed $page Specifies the Content file name.
|
|
* @return string
|
|
*/
|
|
public static function url($name)
|
|
{
|
|
if (empty($name) || !$page = static::find($name)) {
|
|
return null;
|
|
}
|
|
|
|
$url = array_get($page->attributes, 'viewBag.url');
|
|
|
|
return Cms::url($url);
|
|
}
|
|
|
|
/**
|
|
* Determine the default layout for a new page
|
|
* @param \RainLab\Pages\Classes\Page $parentPage
|
|
*/
|
|
public function setDefaultLayout($parentPage)
|
|
{
|
|
// Check parent page for a defined child layout
|
|
if ($parentPage && $parentPage->layout) {
|
|
$layout = Layout::load($this->theme, $parentPage->layout);
|
|
$component = $layout ? $layout->getComponent('staticPage') : null;
|
|
$childLayoutName = $component ? $component->property('childLayout', null) : null;
|
|
if ($childLayoutName) {
|
|
$this->getViewBag()->setProperty('layout', $childLayoutName);
|
|
$this->fillViewBagArray();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check theme layouts for one marked as the default
|
|
foreach (Layout::listInTheme($this->theme) as $layout) {
|
|
$component = $layout->getComponent('staticPage');
|
|
if ($component && $component->property('default', false)) {
|
|
$this->getViewBag()->setProperty('layout', $layout->getBaseFileName());
|
|
$this->fillViewBagArray();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Getters
|
|
//
|
|
|
|
/**
|
|
* Returns the parent page that belongs to this one, or null.
|
|
* @return mixed
|
|
*/
|
|
public function getParent()
|
|
{
|
|
if ($this->parentCache !== null) {
|
|
return $this->parentCache;
|
|
}
|
|
|
|
$pageList = new PageList($this->theme);
|
|
|
|
$parent = null;
|
|
if ($fileName = $pageList->getPageParent($this)) {
|
|
$parent = static::load($this->theme, $fileName);
|
|
}
|
|
|
|
return $this->parentCache = $parent;
|
|
}
|
|
|
|
/**
|
|
* Returns all the child pages that belong to this one.
|
|
* @return array
|
|
*/
|
|
public function getChildren()
|
|
{
|
|
if ($this->childrenCache !== null) {
|
|
return $this->childrenCache;
|
|
}
|
|
|
|
$children = [];
|
|
$pageList = new PageList($this->theme);
|
|
|
|
$subtree = $pageList->getPageSubTree($this);
|
|
|
|
foreach ($subtree as $fileName => $subPages) {
|
|
$subPage = static::load($this->theme, $fileName);
|
|
if ($subPage) {
|
|
$children[] = $subPage;
|
|
}
|
|
}
|
|
|
|
return $this->childrenCache = $children;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of layouts available in the theme.
|
|
* This method is used by the form widget.
|
|
* @return array Returns an array of strings.
|
|
*/
|
|
public function getLayoutOptions()
|
|
{
|
|
$result = [];
|
|
$layouts = Layout::listInTheme($this->theme, true);
|
|
|
|
foreach ($layouts as $layout) {
|
|
if (!$layout->hasComponent('staticPage')) {
|
|
continue;
|
|
}
|
|
|
|
$baseName = $layout->getBaseFileName();
|
|
$result[$baseName] = strlen($layout->description) ? $layout->description : $baseName;
|
|
}
|
|
|
|
if (!$result) {
|
|
$result[null] = Lang::get('rainlab.pages::lang.page.layouts_not_found');
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Looks up the Layout Cms object for this page.
|
|
* @return Cms\Classes\Layout
|
|
*/
|
|
public function getLayoutObject()
|
|
{
|
|
$viewBag = $this->getViewBag();
|
|
$layout = $viewBag->property('layout');
|
|
|
|
if (!$layout) {
|
|
$layouts = $this->getLayoutOptions();
|
|
$layout = count($layouts) ? array_keys($layouts)[0] : null;
|
|
}
|
|
|
|
if (!$layout) {
|
|
return null;
|
|
}
|
|
|
|
$layout = Layout::load($this->theme, $layout);
|
|
if (!$layout) {
|
|
return null;
|
|
}
|
|
|
|
return $layout;
|
|
}
|
|
|
|
/**
|
|
* Returns the Twig content string
|
|
*/
|
|
public function getTwigContent()
|
|
{
|
|
return $this->code;
|
|
}
|
|
|
|
//
|
|
// Syntax field processing
|
|
//
|
|
|
|
public function listLayoutSyntaxFields()
|
|
{
|
|
if (!$layout = $this->getLayoutObject()) {
|
|
return [];
|
|
}
|
|
|
|
$syntax = SyntaxParser::parse($layout->markup, ['tagPrefix' => 'page:']);
|
|
$result = $syntax->toEditor();
|
|
|
|
return $result;
|
|
}
|
|
|
|
//
|
|
// Placeholder processing
|
|
//
|
|
|
|
/**
|
|
* Returns information about placeholders defined in the page layout.
|
|
* @return array Returns an associative array of the placeholder name and codes.
|
|
*/
|
|
public function listLayoutPlaceholders()
|
|
{
|
|
if (!$layout = $this->getLayoutObject()) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
$bodyNode = $layout->getTwigNodeTree()->getNode('body')->getNode(0);
|
|
$nodes = $this->flattenTwigNode($bodyNode);
|
|
|
|
foreach ($nodes as $node) {
|
|
if (!$node instanceof \Cms\Twig\PlaceholderNode) {
|
|
continue;
|
|
}
|
|
|
|
$title = $node->hasAttribute('title') ? trim($node->getAttribute('title')) : null;
|
|
if (!strlen($title)) {
|
|
$title = $node->getAttribute('name');
|
|
}
|
|
|
|
$type = $node->hasAttribute('type') ? trim($node->getAttribute('type')) : null;
|
|
$ignore = $node->hasAttribute('ignore') ? trim($node->getAttribute('ignore')) : false;
|
|
|
|
$placeholderInfo = [
|
|
'title' => $title,
|
|
'type' => $type ?: 'html',
|
|
'ignore' => $ignore
|
|
];
|
|
|
|
$result[$node->getAttribute('name')] = $placeholderInfo;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Recursively flattens a twig node and children
|
|
* @param $node
|
|
* @return array A flat array of twig nodes
|
|
*/
|
|
protected function flattenTwigNode($node)
|
|
{
|
|
$result = [];
|
|
if (!$node instanceof TwigNode) {
|
|
return $result;
|
|
}
|
|
|
|
foreach ($node as $subNode) {
|
|
$flatNodes = $this->flattenTwigNode($subNode);
|
|
$result = array_merge($result, [$subNode], $flatNodes);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Parses the page placeholder {% put %} tags and extracts the placeholder values.
|
|
* @return array Returns an associative array of the placeholder names and values.
|
|
*/
|
|
public function getPlaceholdersAttribute()
|
|
{
|
|
if (!strlen($this->code)) {
|
|
return [];
|
|
}
|
|
|
|
if ($placeholders = array_get($this->attributes, 'placeholders')) {
|
|
return $placeholders;
|
|
}
|
|
|
|
$bodyNode = $this->getTwigNodeTree($this->code)->getNode('body')->getNode(0);
|
|
if ($bodyNode instanceof \Cms\Twig\PutNode) {
|
|
$bodyNode = [$bodyNode];
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($bodyNode as $node) {
|
|
if (!$node instanceof \Cms\Twig\PutNode) {
|
|
continue;
|
|
}
|
|
|
|
$bodyNode = $node->getNode('body');
|
|
$result[$node->getAttribute('name')] = trim($bodyNode->getAttribute('data'));
|
|
}
|
|
|
|
$this->attributes['placeholders'] = $result;
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Takes an array of placeholder data (key: code, value: content) and renders
|
|
* it as a single string of Twig markup against the "code" attribute.
|
|
* @param array $value
|
|
* @return void
|
|
*/
|
|
public function setPlaceholdersAttribute($value)
|
|
{
|
|
if (!is_array($value)) {
|
|
return;
|
|
}
|
|
|
|
// Prune any attempt at setting a placeholder that
|
|
// is not actually defined by this pages layout.
|
|
$placeholders = array_intersect_key($value, $this->listLayoutPlaceholders());
|
|
|
|
$result = '';
|
|
|
|
foreach ($placeholders as $code => $content) {
|
|
if (!strlen(trim($content))) {
|
|
continue;
|
|
}
|
|
|
|
$result .= '{% put '.$code.' %}'.PHP_EOL;
|
|
$result .= $content.PHP_EOL;
|
|
$result .= '{% endput %}'.PHP_EOL;
|
|
$result .= PHP_EOL;
|
|
}
|
|
|
|
$this->attributes['code'] = trim($result);
|
|
$this->attributes['placeholders'] = $placeholders;
|
|
}
|
|
|
|
public function getProcessedMarkup()
|
|
{
|
|
if ($this->processedMarkupCache !== false) {
|
|
return $this->processedMarkupCache;
|
|
}
|
|
|
|
/*
|
|
* Process snippets
|
|
*/
|
|
$markup = Snippet::processPageMarkup(
|
|
$this->getFileName(),
|
|
$this->theme,
|
|
$this->markup
|
|
);
|
|
|
|
/*
|
|
* Inject global view variables
|
|
*/
|
|
$globalVars = ViewHelper::getGlobalVars();
|
|
if (!empty($globalVars)) {
|
|
$markup = TextParser::parse($markup, $globalVars);
|
|
}
|
|
|
|
return $this->processedMarkupCache = $markup;
|
|
}
|
|
|
|
public function getProcessedPlaceholderMarkup($placeholderName, $placeholderContents)
|
|
{
|
|
if (array_key_exists($placeholderName, $this->processedBlockMarkupCache)) {
|
|
return $this->processedBlockMarkupCache[$placeholderName];
|
|
}
|
|
|
|
/*
|
|
* Process snippets
|
|
*/
|
|
$markup = Snippet::processPageMarkup(
|
|
$this->getFileName().md5($placeholderName),
|
|
$this->theme,
|
|
$placeholderContents
|
|
);
|
|
|
|
/*
|
|
* Inject global view variables
|
|
*/
|
|
$globalVars = ViewHelper::getGlobalVars();
|
|
if (!empty($globalVars)) {
|
|
$markup = TextParser::parse($markup, $globalVars);
|
|
}
|
|
|
|
return $this->processedBlockMarkupCache[$placeholderName] = $markup;
|
|
}
|
|
|
|
//
|
|
// Snippets
|
|
//
|
|
|
|
/**
|
|
* Initializes CMS components associated with the page.
|
|
*/
|
|
public function initCmsComponents($cmsController)
|
|
{
|
|
$snippetComponents = Snippet::listPageComponents(
|
|
$this->getFileName(),
|
|
$this->theme,
|
|
$this->markup.$this->code
|
|
);
|
|
|
|
$componentManager = ComponentManager::instance();
|
|
foreach ($snippetComponents as $componentInfo) {
|
|
// Register components for snippet-based components
|
|
// if they're not defined yet. This is required because
|
|
// not all snippet components are registered as components,
|
|
// but it's safe to register them in render-time.
|
|
|
|
if (!$componentManager->hasComponent($componentInfo['class'])) {
|
|
$componentManager->registerComponent($componentInfo['class'], $componentInfo['alias']);
|
|
}
|
|
|
|
$cmsController->addComponent(
|
|
$componentInfo['class'],
|
|
$componentInfo['alias'],
|
|
$componentInfo['properties']
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Static Menu API
|
|
//
|
|
|
|
/**
|
|
* Returns a cache key for this record.
|
|
*/
|
|
protected static function getMenuCacheKey($theme)
|
|
{
|
|
$key = crc32($theme->getPath()).'static-page-menu';
|
|
/**
|
|
* @event pages.page.getMenuCacheKey
|
|
* Enables modifying the key used to reference cached RainLab.Pages menu trees
|
|
*
|
|
* Example usage:
|
|
*
|
|
* Event::listen('pages.page.getMenuCacheKey', function (&$key) {
|
|
* $key = $key . '-' . App::getLocale();
|
|
* });
|
|
*
|
|
*/
|
|
Event::fire('pages.page.getMenuCacheKey', [&$key]);
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the specified URLs are equal.
|
|
*/
|
|
protected static function urlsAreEqual($url, $other)
|
|
{
|
|
return rawurldecode($url) === rawurldecode($other);
|
|
}
|
|
|
|
/**
|
|
* Clears the menu item cache
|
|
* @param \Cms\Classes\Theme $theme Specifies the current theme.
|
|
*/
|
|
public static function clearMenuCache($theme)
|
|
{
|
|
Cache::forget(self::getMenuCacheKey($theme));
|
|
}
|
|
|
|
/**
|
|
* Handler for the pages.menuitem.getTypeInfo event.
|
|
* Returns a menu item type information. The type information is returned as array
|
|
* with the following elements:
|
|
* - references - a list of the item type reference options. The options are returned in the
|
|
* ["key"] => "title" format for options that don't have sub-options, and in the format
|
|
* ["key"] => ["title"=>"Option title", "items"=>[...]] for options that have sub-options. Optional,
|
|
* required only if the menu item type requires references.
|
|
* - nesting - Boolean value indicating whether the item type supports nested items. Optional,
|
|
* false if omitted.
|
|
* - dynamicItems - Boolean value indicating whether the item type could generate new menu items.
|
|
* Optional, false if omitted.
|
|
* - cmsPages - a list of CMS pages (objects of the Cms\Classes\Page class), if the item type requires a CMS page reference to
|
|
* resolve the item URL.
|
|
* @param string $type Specifies the menu item type
|
|
* @return array Returns an array
|
|
*/
|
|
public static function getMenuTypeInfo($type)
|
|
{
|
|
if ($type == 'all-static-pages') {
|
|
return [
|
|
'dynamicItems' => true
|
|
];
|
|
}
|
|
|
|
if ($type == 'static-page') {
|
|
return [
|
|
'references' => self::listStaticPageMenuOptions(),
|
|
'nesting' => true,
|
|
'dynamicItems' => true
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for the pages.menuitem.resolveItem event.
|
|
* Returns information about a menu item. The result is an array
|
|
* with the following keys:
|
|
* - url - the menu item URL. Not required for menu item types that return all available records.
|
|
* The URL should be returned relative to the website root and include the subdirectory, if any.
|
|
* Use the Cms::url() helper to generate the URLs.
|
|
* - isActive - determines whether the menu item is active. Not required for menu item types that
|
|
* return all available records.
|
|
* - items - an array of arrays with the same keys (url, isActive, items) + the title key.
|
|
* The items array should be added only if the $item's $nesting property value is TRUE.
|
|
* @param \RainLab\Pages\Classes\MenuItem $item Specifies the menu item.
|
|
* @param \Cms\Classes\Theme $theme Specifies the current theme.
|
|
* @param string $url Specifies the current page URL, normalized, in lower case
|
|
* The URL is specified relative to the website root, it includes the subdirectory name, if any.
|
|
* @return mixed Returns an array. Returns null if the item cannot be resolved.
|
|
*/
|
|
public static function resolveMenuItem($item, $url, $theme)
|
|
{
|
|
$tree = self::buildMenuTree($theme);
|
|
|
|
if ($item->type == 'static-page' && !isset($tree[$item->reference])) {
|
|
return;
|
|
}
|
|
|
|
$result = [];
|
|
|
|
if ($item->type == 'static-page') {
|
|
$pageInfo = $tree[$item->reference];
|
|
$result['url'] = Cms::url($pageInfo['url']);
|
|
$result['mtime'] = $pageInfo['mtime'];
|
|
$result['isActive'] = self::urlsAreEqual($result['url'], $url);
|
|
}
|
|
|
|
if ($item->nesting || $item->type == 'all-static-pages') {
|
|
$iterator = function($items) use (&$iterator, &$tree, $url) {
|
|
$branch = [];
|
|
|
|
foreach ($items as $itemName) {
|
|
if (!isset($tree[$itemName])) {
|
|
continue;
|
|
}
|
|
|
|
$itemInfo = $tree[$itemName];
|
|
|
|
if ($itemInfo['navigation_hidden']) {
|
|
continue;
|
|
}
|
|
|
|
$branchItem = [];
|
|
$branchItem['url'] = Cms::url($itemInfo['url']);
|
|
$branchItem['isActive'] = self::urlsAreEqual($branchItem['url'], $url);
|
|
$branchItem['title'] = $itemInfo['title'];
|
|
$branchItem['mtime'] = $itemInfo['mtime'];
|
|
|
|
if ($itemInfo['items']) {
|
|
$branchItem['items'] = $iterator($itemInfo['items']);
|
|
}
|
|
|
|
$branch[] = $branchItem;
|
|
}
|
|
|
|
return $branch;
|
|
};
|
|
|
|
$result['items'] = $iterator($item->type == 'static-page' ? $pageInfo['items'] : $tree['--root-pages--']);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Handler for the backend.richeditor.getTypeInfo event.
|
|
* Returns a menu item type information. The type information is returned as array
|
|
*
|
|
* @param string $type Specifies the page link type
|
|
* @return array Array of available link targets keyed by URL ['https://example.com/' => 'Homepage]
|
|
*/
|
|
public static function getRichEditorTypeInfo($type)
|
|
{
|
|
if ($type == 'static-page') {
|
|
|
|
$pages = self::listStaticPageMenuOptions();
|
|
|
|
$iterator = function($pages) use (&$iterator) {
|
|
$result = [];
|
|
foreach ($pages as $pageFile => $page) {
|
|
$url = self::url($pageFile);
|
|
|
|
if (is_array($page)) {
|
|
$result[$url] = [
|
|
'title' => array_get($page, 'title', []),
|
|
'links' => $iterator(array_get($page, 'items', []))
|
|
];
|
|
}
|
|
else {
|
|
$result[$url] = $page;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
};
|
|
|
|
return $iterator($pages);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Builds and caches a menu item tree.
|
|
* This method is used internally for menu items and breadcrumbs.
|
|
* @param \Cms\Classes\Theme $theme Specifies the current theme.
|
|
* @return array Returns an array containing the page information
|
|
*/
|
|
public static function buildMenuTree($theme)
|
|
{
|
|
if (self::$menuTreeCache !== null) {
|
|
return self::$menuTreeCache;
|
|
}
|
|
|
|
$key = self::getMenuCacheKey($theme);
|
|
|
|
$cached = Cache::get($key, false);
|
|
$unserialized = $cached ? @unserialize($cached) : false;
|
|
|
|
if ($unserialized !== false) {
|
|
return self::$menuTreeCache = $unserialized;
|
|
}
|
|
|
|
$menuTree = [
|
|
'--root-pages--' => []
|
|
];
|
|
|
|
$iterator = function($items, $parent, $level) use (&$menuTree, &$iterator) {
|
|
$result = [];
|
|
|
|
foreach ($items as $item) {
|
|
$viewBag = $item->page->viewBag;
|
|
$pageCode = $item->page->getBaseFileName();
|
|
$pageUrl = Str::lower(RouterHelper::normalizeUrl(array_get($viewBag, 'url')));
|
|
|
|
$itemData = [
|
|
'url' => $pageUrl,
|
|
'title' => array_get($viewBag, 'title'),
|
|
'mtime' => $item->page->mtime,
|
|
'items' => $iterator($item->subpages, $pageCode, $level+1),
|
|
'parent' => $parent,
|
|
'navigation_hidden' => array_get($viewBag, 'navigation_hidden')
|
|
];
|
|
|
|
if ($level == 0) {
|
|
$menuTree['--root-pages--'][] = $pageCode;
|
|
}
|
|
|
|
$result[] = $pageCode;
|
|
$menuTree[$pageCode] = $itemData;
|
|
}
|
|
|
|
return $result;
|
|
};
|
|
|
|
$pageList = new PageList($theme);
|
|
$iterator($pageList->getPageTree(), null, 0);
|
|
|
|
self::$menuTreeCache = $menuTree;
|
|
$expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10));
|
|
Cache::put($key, serialize($menuTree), $expiresAt);
|
|
|
|
return self::$menuTreeCache;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of options for the Reference drop-down menu in the
|
|
* menu item configuration form, when the Static Page item type is selected.
|
|
* @return array Returns an array
|
|
*/
|
|
protected static function listStaticPageMenuOptions()
|
|
{
|
|
$theme = Theme::getEditTheme();
|
|
|
|
$pageList = new PageList($theme);
|
|
$pageTree = $pageList->getPageTree(true);
|
|
|
|
$iterator = function($pages) use (&$iterator) {
|
|
$result = [];
|
|
|
|
foreach ($pages as $pageInfo) {
|
|
$pageName = $pageInfo->page->getViewBag()->property('title');
|
|
$fileName = $pageInfo->page->getBaseFileName();
|
|
|
|
if (!$pageInfo->subpages) {
|
|
$result[$fileName] = $pageName;
|
|
}
|
|
else {
|
|
$result[$fileName] = [
|
|
'title' => $pageName,
|
|
'items' => $iterator($pageInfo->subpages)
|
|
];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
};
|
|
|
|
return $iterator($pageTree);
|
|
}
|
|
|
|
/**
|
|
* Disables safe mode check for static pages.
|
|
*
|
|
* This allows developers to use placeholders in layouts even if safe mode is enabled.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function checkSafeMode()
|
|
{
|
|
}
|
|
}
|