838 lines
27 KiB
Text
838 lines
27 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* ProcessWire Page List Process
|
||
|
*
|
||
|
* Generates the ajax/js hierarchical page lists used throughout ProcessWire
|
||
|
*
|
||
|
* For more details about how Process modules work, please see:
|
||
|
* /wire/core/Process.php
|
||
|
*
|
||
|
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
|
||
|
* https://processwire.com
|
||
|
*
|
||
|
* @property bool $showRootPage Whether root page (like home) should be shown.
|
||
|
* @property string $pageLabelField Field name or field names (space separated) that should be used for page label.
|
||
|
* @property int $limit Items to show per pagination.
|
||
|
* @property int $speed Animation speed (in ms)
|
||
|
* @property bool|int $useHoverActions Whether or not to use hover mode for action links.
|
||
|
* @property int $hoverActionDelay Milliseconds delay between hover and showing of actions.
|
||
|
* @property int $hoverActionFade Milliseconds to spend fading in or out actions.
|
||
|
* @property bool|int $useBookmarks Allow use of PageList bookmarks?
|
||
|
* @property bool|int $useTrash Allow non-superusers to use Trash?
|
||
|
* @property string $qtyType What to show in children quantity label? 'children', 'total', 'children/total', 'total/children', or 'id'
|
||
|
* @property array $hidePages Page IDs to hide from root page list. (3.0.202+)
|
||
|
* @property array $hidePagesNot Values of 'debug', 'advanced', 'superuser' to not hide above pages when in that state. (3.0.202+)
|
||
|
*
|
||
|
* @method string ajaxAction($action)
|
||
|
* @method PageArray find($selectorString, Page $page)
|
||
|
*
|
||
|
* @todo Option to configure whether "Pub" action should appear for non-superusers
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
class ProcessPageList extends Process implements ConfigurableModule {
|
||
|
|
||
|
/**
|
||
|
* Module information
|
||
|
*
|
||
|
* @return array
|
||
|
*
|
||
|
*/
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => 'Page List',
|
||
|
'summary' => 'List pages in a hierarchical tree structure',
|
||
|
'version' => 124,
|
||
|
'permanent' => true,
|
||
|
'permission' => 'page-edit',
|
||
|
'icon' => 'sitemap',
|
||
|
'useNavJSON' => true,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @var Page|null
|
||
|
*
|
||
|
*/
|
||
|
protected $page;
|
||
|
|
||
|
/**
|
||
|
* @var int
|
||
|
*
|
||
|
*/
|
||
|
protected $id;
|
||
|
|
||
|
/**
|
||
|
* @var Page|null
|
||
|
*
|
||
|
*/
|
||
|
protected $openPage;
|
||
|
|
||
|
/**
|
||
|
* @var int
|
||
|
*
|
||
|
*/
|
||
|
protected $start;
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*
|
||
|
*/
|
||
|
protected $trashLabel;
|
||
|
|
||
|
/**
|
||
|
* @var string|null i.e. "JSON"
|
||
|
*
|
||
|
*/
|
||
|
protected $render;
|
||
|
|
||
|
/**
|
||
|
* @var array
|
||
|
*
|
||
|
*/
|
||
|
protected $allowRenderTypes = array(
|
||
|
'JSON' => 'ProcessPageListRenderJSON'
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Default max pages to show before pagination (configurable in the module editor)
|
||
|
*
|
||
|
*/
|
||
|
const defaultLimit = 50;
|
||
|
|
||
|
/**
|
||
|
* Default animation speed (in ms) for the PageList
|
||
|
*
|
||
|
*/
|
||
|
const defaultSpeed = 200;
|
||
|
|
||
|
/**
|
||
|
* Construct and establish default config values
|
||
|
*
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
$this->set('showRootPage', true);
|
||
|
$this->set('pageLabelField', 'title');
|
||
|
$this->set('limit', self::defaultLimit);
|
||
|
$this->set('useHoverActions', false);
|
||
|
$this->set('useBookmarks', false);
|
||
|
$this->set('useTrash', false);
|
||
|
$this->set('bookmarks', array());
|
||
|
$this->set('qtyType', '');
|
||
|
parent::set('hidePages', array(404));
|
||
|
parent::set('hidePagesNot', array());
|
||
|
parent::__construct();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize the Page List
|
||
|
*
|
||
|
*/
|
||
|
public function init() {
|
||
|
|
||
|
parent::init();
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
$input = $this->wire()->input;
|
||
|
$isAjax = $config->ajax;
|
||
|
$limit = (int) $input->get->int('limit');
|
||
|
$render = $input->get('render');
|
||
|
$settings = $config->pageList;
|
||
|
|
||
|
$this->start = (int) $input->get->int('start');
|
||
|
$this->limit = $limit > 0 && $limit < $this->limit ? $limit : $this->limit;
|
||
|
$this->render = $render ? strtoupper($this->wire()->sanitizer->name($render)) : '';
|
||
|
|
||
|
if($isAjax && !$this->render && !$input->get('renderInputfieldAjax')) $this->render = 'JSON';
|
||
|
if(strlen($this->render) && !isset($this->allowRenderTypes[$this->render])) $this->render = null;
|
||
|
|
||
|
if(is_array($settings)) {
|
||
|
if(!empty($settings['useHoverActions'])) $this->set('useHoverActions', true);
|
||
|
$this->set('hoverActionDelay', isset($settings['hoverActionDelay']) ? (int) $settings['hoverActionDelay'] : 100);
|
||
|
$this->set('hoverActionFade', isset($settings['hoverActionFade']) ? (int) $settings['hoverActionFade'] : 100);
|
||
|
if($this->speed == self::defaultSpeed) $this->set('speed', isset($settings['speed']) ? (int) $settings['speed'] : self::defaultSpeed);
|
||
|
if($this->limit == self::defaultLimit) $this->set('limit', isset($settings['limit']) ? (int) $settings['limit'] : self::defaultLimit);
|
||
|
}
|
||
|
|
||
|
if(!$isAjax) {
|
||
|
$modules = $this->wire()->modules;
|
||
|
$jQuery = $modules->get('JqueryCore'); /** @var JqueryCore $jQuery */
|
||
|
$jQueryUI = $modules->get('JqueryUI'); /** @var JqueryUI $jQueryUI */
|
||
|
$jQuery->use('cookie');
|
||
|
$jQuery->use('longclick');
|
||
|
$jQueryUI->use('modal');
|
||
|
$jQueryUI->use('vex');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute the Page List
|
||
|
*
|
||
|
* @return string
|
||
|
* @throws WireException|Wire404Exception|WirePermissionException
|
||
|
*
|
||
|
*/
|
||
|
public function ___execute() {
|
||
|
|
||
|
$pages = $this->wire()->pages;
|
||
|
$input = $this->wire()->input;
|
||
|
$config = $this->wire()->config;
|
||
|
$session = $this->wire()->session;
|
||
|
|
||
|
$ajax = $config->ajax;
|
||
|
$langID = (int) $input->get('lang');
|
||
|
|
||
|
if($langID) {
|
||
|
$languages = $this->wire()->languages;
|
||
|
if($languages) $this->wire()->user->language = $languages->get($langID);
|
||
|
}
|
||
|
|
||
|
$id = $input->get('id');
|
||
|
if($id === 'bookmark') $session->location('./bookmarks/');
|
||
|
$id = (int) $id;
|
||
|
if(!$this->id && $id > 0) $this->id = $id;
|
||
|
|
||
|
$this->trashLabel = $this->_('Trash'); // Label for 'Trash' page in PageList // Overrides page title if used
|
||
|
|
||
|
$openID = (int) $input->get('open');
|
||
|
$this->openPage = $openID ? $pages->get($openID) : $pages->newNullPage();
|
||
|
if($this->openPage->id && $this->speed > 50) $this->speed = floor($this->speed / 2);
|
||
|
$this->page = $pages->get("id=" . ($this->id > 0 ? $this->id : 1) . ", status<" . Page::statusMax);
|
||
|
|
||
|
if(!$this->page) {
|
||
|
throw new Wire404Exception("Unable to load page $this->id", Wire404Exception::codeSecondary);
|
||
|
}
|
||
|
|
||
|
if(!$this->page->listable()) {
|
||
|
throw new WirePermissionException("You don't have access to list page {$this->page->url}");
|
||
|
}
|
||
|
|
||
|
$this->page->setOutputFormatting(false);
|
||
|
|
||
|
$action = $input->post('action');
|
||
|
if($ajax && $action) {
|
||
|
return $this->ajaxAction($this->wire()->sanitizer->name($action));
|
||
|
}
|
||
|
|
||
|
$p = $this->wire()->page;
|
||
|
if($p->name === 'list' && "$p->process" === "$this") {
|
||
|
// ensure that we use the page's title is always consistent in the admin (i.e. 'Pages' not 'Page List')
|
||
|
$p->title = $p->parent->title;
|
||
|
}
|
||
|
|
||
|
if($ajax && $this->id > 1 && "$p->process" === "$this" && $input->get('mode') != 'select') {
|
||
|
// remember last requested id
|
||
|
$session->setFor($this, 'lastID', $this->id);
|
||
|
}
|
||
|
|
||
|
return $this->render();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render the Page List
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function render() {
|
||
|
|
||
|
$this->setupBreadcrumbs();
|
||
|
|
||
|
if($this->render) {
|
||
|
return $this->getPageListRender($this->page)->render();
|
||
|
}
|
||
|
|
||
|
$session = $this->wire()->session;
|
||
|
$input = $this->wire()->input;
|
||
|
$isAjax = $this->wire()->config->ajax;
|
||
|
$tokenName = $session->CSRF->getTokenName();
|
||
|
$tokenValue = $session->CSRF->getTokenValue();
|
||
|
$class = $this->id ? "PageListContainerPage" : "PageListContainerRoot";
|
||
|
|
||
|
$this->renderReady();
|
||
|
|
||
|
if($isAjax && $input->get('renderInputfieldAjax')) {
|
||
|
$script = 'script';
|
||
|
$script = "<$script>ProcessPageListInit();</$script>";
|
||
|
} else {
|
||
|
$script = '';
|
||
|
}
|
||
|
|
||
|
return "\n" .
|
||
|
"<div id='PageListContainer' " .
|
||
|
"class='$class' " .
|
||
|
"data-token-name='$tokenName' " . // CSRF tokens
|
||
|
"data-token-value='$tokenValue'>" .
|
||
|
"</div>$script";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setup for render
|
||
|
*
|
||
|
*/
|
||
|
public function renderReady() {
|
||
|
|
||
|
$input = $this->wire()->input;
|
||
|
$config = $this->wire()->config;
|
||
|
|
||
|
$urls = $config->urls;
|
||
|
$isAjax = $config->ajax;
|
||
|
|
||
|
$openPageIDs = array();
|
||
|
$openPageData = array();
|
||
|
|
||
|
if($this->openPage) {
|
||
|
$page = $this->wire()->page;
|
||
|
if($this->openPage->id > 1) {
|
||
|
$openPageIDs[] = $this->openPage->id;
|
||
|
foreach($this->openPage->parents() as $parent) {
|
||
|
if($parent->id > 1 && $parent->id != $this->id) $openPageIDs[] = $parent->id;
|
||
|
}
|
||
|
} else if(!$isAjax && ((string) $page->process) == "$this") {
|
||
|
if($this->id) {
|
||
|
// leave openPageIDs as empty array
|
||
|
} else {
|
||
|
$openPageIDs = $input->cookie->array('pagelist_open');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!$isAjax && count($openPageIDs)) {
|
||
|
$pages = $this->wire()->pages;
|
||
|
$render = $this->render;
|
||
|
$this->render = 'JSON';
|
||
|
foreach($openPageIDs as $key => $openPageID) {
|
||
|
if(strpos($openPageID, '-')) {
|
||
|
list($openPageID, $openPageStart) = explode('-', $openPageID);
|
||
|
$openPageStart = (int) $openPageStart;
|
||
|
} else {
|
||
|
$openPageStart = 0;
|
||
|
}
|
||
|
$openPageID = (int) $openPageID;
|
||
|
$openPageIDs[$key] = "$openPageID-$openPageStart";
|
||
|
$p = $pages->get($openPageID);
|
||
|
if(!$p->id || !$p->listable()) continue;
|
||
|
$renderer = $this->getPageListRender($p, $this->limit, $openPageStart);
|
||
|
$openPageData["$openPageID-$openPageStart"] = $renderer->setOption('getArray', true)->render();
|
||
|
}
|
||
|
$this->render = $render;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$defaults = array(
|
||
|
'containerID' => 'PageListContainer',
|
||
|
'ajaxURL' => $urls->admin . "page/list/",
|
||
|
'ajaxMoveURL' => $urls->admin . "page/sort/",
|
||
|
'rootPageID' => $this->id,
|
||
|
'openPageIDs' => $openPageIDs,
|
||
|
'openPageData' => $openPageData,
|
||
|
'openPagination' => (int) $input->get('n'),
|
||
|
'paginationClass' => 'PageListPagination',
|
||
|
'showRootPage' => $this->showRootPage ? true : false,
|
||
|
'limit' => $this->limit,
|
||
|
'start' => $this->start,
|
||
|
'speed' => ($this->speed !== null ? (int) $this->speed : self::defaultSpeed),
|
||
|
'qtyType' => $this->qtyType,
|
||
|
'useHoverActions' => $this->useHoverActions ? true : false,
|
||
|
'hoverActionDelay' => (int) $this->hoverActionDelay,
|
||
|
'hoverActionFade' => (int) $this->hoverActionFade,
|
||
|
'selectStartLabel' => $this->_('Change'), // Change a page selection
|
||
|
'selectCancelLabel' => $this->_('Cancel'), // Cancel a page selection
|
||
|
'selectSelectLabel' => $this->_('Select'), // Select a page
|
||
|
'selectUnselectLabel' => $this->_('Unselect'), // Unselect a page
|
||
|
'moreLabel' => $this->_('More'), // Show more pages
|
||
|
'moveInstructionLabel' => $this->_('Click and drag to move'), // Instruction on how to move a page
|
||
|
'trashLabel' => $this->trashLabel,
|
||
|
'ajaxNetworkError' => $this->_('Network error, please try again later'), // Network error during AJAX request
|
||
|
'ajaxUnknownError' => $this->_('Unknown error, please try again later'), // Unknown error during AJAX request
|
||
|
);
|
||
|
|
||
|
$settings = $config->ProcessPageList;
|
||
|
$settings = is_array($settings) ? array_merge($defaults, $settings) : $defaults;
|
||
|
|
||
|
$config->js('ProcessPageList', $settings);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the appropriate PageListRender class
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @param null|int $limit
|
||
|
* @param null|int $start
|
||
|
* @return ProcessPageListRender
|
||
|
*
|
||
|
*/
|
||
|
protected function getPageListRender(Page $page, $limit = null, $start = null) {
|
||
|
|
||
|
require_once(dirname(__FILE__) . '/ProcessPageListRender.php');
|
||
|
|
||
|
if(!$this->render || !isset($this->allowRenderTypes[$this->render])) $this->render = 'JSON';
|
||
|
|
||
|
$class = $this->allowRenderTypes[$this->render];
|
||
|
$className = wireClassName($class, true);
|
||
|
|
||
|
$user = $this->wire()->user;
|
||
|
$superuser = $user->isSuperuser();
|
||
|
|
||
|
if(!class_exists($className, false)) require_once(dirname(__FILE__) . "/$class.php");
|
||
|
|
||
|
if(is_null($limit)) $limit = $this->limit;
|
||
|
if(is_null($start)) $start = $this->start;
|
||
|
|
||
|
if($limit) {
|
||
|
$selector = "start=$start, limit=$limit, status<" . Page::statusMax;
|
||
|
|
||
|
if($this->useTrash && !$superuser) {
|
||
|
$trashID = $this->wire()->config->trashPageID;
|
||
|
if($page->id == $trashID && $user->hasPermission('page-edit') && $page->listable()) {
|
||
|
$selector .= ", check_access=0";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$children = $this->find($selector, $page);
|
||
|
} else {
|
||
|
$children = $this->wire()->pages->newPageArray();
|
||
|
}
|
||
|
|
||
|
/** @var ProcessPageListRender $renderer */
|
||
|
$renderer = $this->wire(new $className($page, $children));
|
||
|
$renderer->setStart($start);
|
||
|
$renderer->setLimit($limit);
|
||
|
$renderer->setPageLabelField($this->getPageLabelField());
|
||
|
$renderer->setLabel('trash', $this->trashLabel);
|
||
|
$renderer->setUseTrash($this->useTrash || $superuser);
|
||
|
$renderer->setQtyType($this->qtyType);
|
||
|
$renderer->setHidePages($this->hidePages, $this->hidePagesNot);
|
||
|
|
||
|
return $renderer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the page label field
|
||
|
*
|
||
|
* @param $name
|
||
|
* @param $pageLabelField
|
||
|
*
|
||
|
*/
|
||
|
public function setPageLabelField($name, $pageLabelField) {
|
||
|
$this->wire()->session->setFor($this, $name, $pageLabelField);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the page label field
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function getPageLabelField() {
|
||
|
|
||
|
$pageLabelField = '';
|
||
|
$name = $this->wire()->input->get('labelName');
|
||
|
|
||
|
if($name) {
|
||
|
$name = $this->wire()->sanitizer->fieldName($name);
|
||
|
if($name) $pageLabelField = $this->wire()->session->getFor($this, $name);
|
||
|
if($pageLabelField) $pageLabelField = '!' . $pageLabelField; // "!" means it may not be overridden by template
|
||
|
}
|
||
|
|
||
|
if(empty($pageLabelField)) {
|
||
|
$pageLabelField = $this->pageLabelField;
|
||
|
}
|
||
|
|
||
|
return $pageLabelField;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process an AJAX action and return JSON string
|
||
|
*
|
||
|
* @param string $action
|
||
|
* @return string
|
||
|
* @throws WireException
|
||
|
*
|
||
|
*/
|
||
|
public function ___ajaxAction($action) {
|
||
|
|
||
|
$input = $this->wire()->input;
|
||
|
$session = $this->wire()->session;
|
||
|
|
||
|
if(!$this->page->editable()) throw new WireException("Page not editable");
|
||
|
if($this->page->id != $input->post('id')) throw new WireException("GET id does not match POST id");
|
||
|
|
||
|
$tokenName = $session->CSRF->getTokenName();
|
||
|
$tokenValue = $session->CSRF->getTokenValue();
|
||
|
$postTokenValue = $input->post($tokenName);
|
||
|
if($postTokenValue === null || $postTokenValue !== $tokenValue) throw new WireException("CSRF token does not match");
|
||
|
|
||
|
$renderer = $this->getPageListRender($this->page, 0);
|
||
|
$result = $renderer->actions()->processAction($this->page, $action);
|
||
|
|
||
|
if(!empty($result['updateItem'])) {
|
||
|
$result['child'] = $renderer->renderChild($this->page);
|
||
|
unset($result['updateItem']);
|
||
|
}
|
||
|
|
||
|
if(!empty($result['appendItem'])) {
|
||
|
$newChild = $this->wire()->pages->get((int) $result['appendItem']);
|
||
|
$result['newChild'] = $renderer->renderChild($newChild);
|
||
|
unset($result['appendItem']);
|
||
|
}
|
||
|
|
||
|
header("Content-type: application/json");
|
||
|
|
||
|
return json_encode($result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $selectorString
|
||
|
* @param Page $page
|
||
|
* @return PageArray
|
||
|
*
|
||
|
*/
|
||
|
public function ___find($selectorString, Page $page) {
|
||
|
if($page->id === $this->wire()->config->trashPageID && !preg_match('/\bsort=/', $selectorString)) {
|
||
|
$sortfield = $page->sortfield();
|
||
|
if(!$sortfield || $sortfield === 'sort') {
|
||
|
$selectorString = trim("$selectorString,sort=-modified", ',');
|
||
|
}
|
||
|
}
|
||
|
return $page->children($selectorString);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a value to this Page List (see WireData)
|
||
|
*
|
||
|
* @param string $key
|
||
|
* @param mixed $value
|
||
|
* @return Process|ProcessPageList
|
||
|
*
|
||
|
*/
|
||
|
public function set($key, $value) {
|
||
|
if($key === 'id') { // allow setting by other modules, overrides $_GET value of ID
|
||
|
$this->id = (int) $value;
|
||
|
return $this;
|
||
|
}
|
||
|
return parent::set($key, $value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setup the Breadcrumbs for the UI
|
||
|
*
|
||
|
*/
|
||
|
public function setupBreadcrumbs() {
|
||
|
$process = $this->wire()->process;
|
||
|
if("$process" !== "$this" || !$this->wire()->breadcrumbs) return;
|
||
|
if($this->wire()->input->urlSegment1) return;
|
||
|
$url = $this->wire()->config->urls->admin . 'page/list/?id=';
|
||
|
foreach($this->page->parents() as $p) {
|
||
|
$this->breadcrumb($url . $p->id, $p->get('title|name'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get an instance of PageBookmarks (to be phased out)
|
||
|
*
|
||
|
* @return PageBookmarks
|
||
|
*
|
||
|
*/
|
||
|
protected function getPageBookmarks() {
|
||
|
static $bookmarks = null;
|
||
|
if(is_null($bookmarks)) {
|
||
|
require_once($this->wire()->config->paths('ProcessPageEdit') . 'PageBookmarks.php');
|
||
|
$bookmarks = $this->wire(new PageBookmarks($this));
|
||
|
}
|
||
|
return $bookmarks;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Output JSON list of navigation items for this module's bookmarks
|
||
|
*
|
||
|
* @param array $options
|
||
|
* @return string|array
|
||
|
*
|
||
|
*/
|
||
|
public function ___executeNavJSON(array $options = array()) {
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
$urls = $this->wire()->urls;
|
||
|
|
||
|
if($this->useBookmarks) {
|
||
|
$bookmarks = $this->getPageBookmarks();
|
||
|
$options['edit'] = $urls->admin . 'page/?id={id}';
|
||
|
$options = $bookmarks->initNavJSON($options);
|
||
|
return parent::___executeNavJSON($options);
|
||
|
}
|
||
|
|
||
|
$parentID = (int) $this->wire()->input->get('parent_id');
|
||
|
if(!$parentID) $parentID = 1;
|
||
|
|
||
|
$parent = $this->wire()->pages->get($parentID);
|
||
|
$parentViewable = $parent->viewable(false);
|
||
|
$renderer = $this->getPageListRender($parent);
|
||
|
$items = $parentViewable ? $renderer->getChildren() : new PageArray();
|
||
|
|
||
|
if($parentID === 1 && $parentViewable) $items->prepend($parent);
|
||
|
$skipPageIDs = array($config->trashPageID, $config->adminRootPageID);
|
||
|
$maxLabelLength = 40;
|
||
|
|
||
|
$data = array(
|
||
|
'url' => $urls->admin . 'page/list/navJSON/',
|
||
|
'label' => '',
|
||
|
'icon' => 'sitemap',
|
||
|
'list' => array(),
|
||
|
);
|
||
|
|
||
|
$data = array_merge($options, $data);
|
||
|
|
||
|
foreach($items as $page) {
|
||
|
|
||
|
$id = $page->id;
|
||
|
if(in_array($id, $skipPageIDs)) continue;
|
||
|
$url = '';
|
||
|
$editable = false;
|
||
|
|
||
|
if(!$page->listable()) {
|
||
|
continue;
|
||
|
} else if($page->editable()) {
|
||
|
$url = $page->editUrl();
|
||
|
$editable = true;
|
||
|
} else if($page->viewable()) {
|
||
|
// do not show view URLs per #818
|
||
|
// $url = $page->url();
|
||
|
}
|
||
|
|
||
|
$numChildren = $id > 1 ? $renderer->numChildren($page) : 0;
|
||
|
$label = $renderer->getPageLabel($page, array('noTags' => true, 'noIcon' => true));
|
||
|
|
||
|
if(strlen($label) > $maxLabelLength) {
|
||
|
$label = substr($label, 0, $maxLabelLength);
|
||
|
$pos = strrpos($label, ' ');
|
||
|
if($pos !== false) $label = substr($label, 0, $pos);
|
||
|
$label .= ' …';
|
||
|
}
|
||
|
|
||
|
$labelClasses = array();
|
||
|
if($page->isUnpublished()) $labelClasses[] = 'PageListStatusUnpublished';
|
||
|
if($page->isHidden()) $labelClasses[] = 'PageListStatusHidden';
|
||
|
if($page->hasStatus(Page::statusLocked)) $labelClasses[] = 'PageListStatusLocked';
|
||
|
if($page->hasStatus(Page::statusDraft)) $labelClasses[] = 'PageListStatusDraft';
|
||
|
if(!$editable) $labelClasses[] = 'PageListStatusNotEditable';
|
||
|
if(count($labelClasses)) {
|
||
|
$label = "<span class='" . implode(' ', $labelClasses) . "'>$label</span>";
|
||
|
}
|
||
|
if($numChildren) $label .= " <small>$numChildren</small>";
|
||
|
$label .= ' ';
|
||
|
|
||
|
$a = array(
|
||
|
'url' => $url,
|
||
|
'id' => $id,
|
||
|
'label' => $label,
|
||
|
'icon' => $page->getIcon(),
|
||
|
'edit' => $editable
|
||
|
);
|
||
|
|
||
|
if($numChildren) {
|
||
|
$a['navJSON'] = $data['url'] . "?parent_id=$page->id";
|
||
|
}
|
||
|
|
||
|
$data['list'][] = $a;
|
||
|
}
|
||
|
|
||
|
if($items->getTotal() > $items->count()) {
|
||
|
$data['list'][] = array(
|
||
|
'url' => $urls->admin . "page/?open=$parentID",
|
||
|
'label' => $this->_('Show All') . ' ' .
|
||
|
'<small>' . sprintf($this->_('(%d pages)'), $items->getTotal()) . '</small>',
|
||
|
'icon' => 'arrow-circle-right',
|
||
|
'className' => 'separator pw-pagelist-show-all',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if($parent->addable()) {
|
||
|
$data['list'][] = array(
|
||
|
'url' => $urls->admin . "page/add/?parent_id=$parentID",
|
||
|
'label' => __('Add New', '/wire/templates-admin/default.php'),
|
||
|
'icon' => 'plus-circle',
|
||
|
'className' => 'separator pw-nav-add',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if($config->ajax) header("Content-Type: application/json");
|
||
|
|
||
|
return json_encode($data);
|
||
|
}
|
||
|
|
||
|
public function ___executeOpen() {
|
||
|
$input = $this->wire()->input;
|
||
|
$id = (int) $input->urlSegment2;
|
||
|
$input->get->set('open', $id);
|
||
|
$this->wire()->breadcrumbs->removeAll();
|
||
|
return $this->execute();
|
||
|
}
|
||
|
|
||
|
public function ___executeId() {
|
||
|
$input = $this->wire()->input;
|
||
|
$id = (int) $input->urlSegment2;
|
||
|
$input->get->set('id', $id);
|
||
|
return $this->execute();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute the Page Bookmarks (to be phased out)
|
||
|
*
|
||
|
* @return string
|
||
|
* @throws WireException
|
||
|
* @throws WirePermissionException
|
||
|
*
|
||
|
*/
|
||
|
public function ___executeBookmarks() {
|
||
|
$bookmarks = $this->getPageBookmarks();
|
||
|
return $bookmarks->editBookmarks();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* URL to redirect to after non-authenticated user is logged-in, or false if module does not support
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @return string
|
||
|
* @sine 3.0.167
|
||
|
*
|
||
|
*/
|
||
|
public static function getAfterLoginUrl(Page $page) {
|
||
|
$url = $page->url();
|
||
|
$input = $page->wire()->input;
|
||
|
list($s1, $s2) = array($input->urlSegment1, $input->urlSegment2);
|
||
|
if(ctype_digit($s2) && ($s1 === 'id' || $s1 === 'open')) {
|
||
|
return $url . "$s1/" . (int) $s2; // i.e. /id/123 or /open/456
|
||
|
} else {
|
||
|
$intVars = array('limit', 'start', 'lang', 'open', 'id', 'n');
|
||
|
$data = array();
|
||
|
foreach($intVars as $name) {
|
||
|
$value = (int) $input->get($name);
|
||
|
if($value > 0) $data[$name] = $value;
|
||
|
}
|
||
|
$render = $input->get->name('render');
|
||
|
if($render) $data['render'] = strtoupper($render);
|
||
|
if(count($data)) $url .= "?" . implode('&', $data);
|
||
|
}
|
||
|
return $url;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Build a form allowing configuration of this Module
|
||
|
*
|
||
|
* @param array $data
|
||
|
* @return InputfieldWrapper
|
||
|
*
|
||
|
*/
|
||
|
public function getModuleConfigInputfields(array $data) {
|
||
|
|
||
|
/** @var InputfieldWrapper $fields */
|
||
|
$fields = $this->wire(new InputfieldWrapper());
|
||
|
$modules = $this->wire()->modules;
|
||
|
|
||
|
/** @var InputfieldPageListSelectMultiple $field */
|
||
|
$field = $modules->get('InputfieldPageListSelectMultiple');
|
||
|
$field->attr('name', 'hidePages');
|
||
|
$field->label = $this->_('Hide these pages in page list(s)');
|
||
|
$field->description = $this->_('Select one or more pages that you do not want to appear in page list(s).');
|
||
|
$field->val($this->hidePages);
|
||
|
$field->columnWidth = 60;
|
||
|
$field->icon = 'eye-slash';
|
||
|
$fields->add($field);
|
||
|
|
||
|
/** @var InputfieldCheckboxes $field */
|
||
|
$field = $modules->get('InputfieldCheckboxes');
|
||
|
$field->attr('name', 'hidePagesNot');
|
||
|
$field->label = $this->_('Except when (AND condition)');
|
||
|
$field->addOption('debug', $this->_('System in debug mode'));
|
||
|
$field->addOption('advanced', $this->_('System in advanced mode'));
|
||
|
$field->addOption('superuser', $this->_('Current user is superuser'));
|
||
|
$field->showIf = 'hidePages.count>0';
|
||
|
$field->val($this->hidePagesNot);
|
||
|
$field->icon = 'eye-slash';
|
||
|
$field->columnWidth = 40;
|
||
|
$fields->add($field);
|
||
|
|
||
|
/** @var InputfieldCheckbox $field */
|
||
|
$field = $modules->get('InputfieldCheckbox');
|
||
|
$field->attr('name', 'useTrash');
|
||
|
$field->label = $this->_('Allow non-superuser editors to use Trash?');
|
||
|
$field->icon = 'trash-o';
|
||
|
$field->description =
|
||
|
$this->_('When checked, users will be able to see pages in the trash (only pages they have access to).') . ' ' .
|
||
|
$this->_('This will also enable the “Trash” and “Restore” actions, where access control allows.');
|
||
|
if(!empty($data['useTrash'])) $field->attr('checked', 'checked');
|
||
|
$fields->append($field);
|
||
|
|
||
|
/** @var InputfieldText $field */
|
||
|
$field = $modules->get("InputfieldText");
|
||
|
$field->attr('name', 'pageLabelField');
|
||
|
$field->attr('value', !empty($data['pageLabelField']) ? $data['pageLabelField'] : 'title');
|
||
|
$field->label = $this->_("Name of page field to display");
|
||
|
$field->description = $this->_('Every page in a PageList is identified by a label, typically a title or headline field. You may specify which field it should use here. To specify multiple fields, separate each field name with a space, or use your own format string with field names surrounded in {brackets}. If the field resolves to an object (like another page), then specify the property with a dot, i.e. {anotherpage.title}. Note that if the format you specify resolves to a blank value then ProcessWire will use the page "name" field.'); // pageLabelField description
|
||
|
$field->notes = $this->_('You may optionally override this setting on a per-template basis in each template "advanced" settings.'); // pageLabelField notes
|
||
|
$fields->append($field);
|
||
|
|
||
|
if(!empty($data['useBookmarks'])) {
|
||
|
// support bookmarks only if already in use as bookmarks for ProcessPageList to be phased out
|
||
|
$bookmarks = $this->getPageBookmarks();
|
||
|
$bookmarks->addConfigInputfields($fields);
|
||
|
$pages = $this->wire()->pages;
|
||
|
$admin = $pages->get($this->wire()->config->adminRootPageID);
|
||
|
$page = $pages->get($admin->path . 'page/list/');
|
||
|
$bookmarks->checkProcessPage($page);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
$settings = $this->wire('config')->pageList;
|
||
|
if(empty($settings['useHoverActions'])) {
|
||
|
$field = $modules->get('InputfieldCheckbox');
|
||
|
$field->attr('name', 'useHoverActions');
|
||
|
$field->label = __('Show page actions on hover?');
|
||
|
$field->description = __('By default, actions for a page appear after a click (at least in the default admin theme). To make them appear on hover instead, check this box.'); // useHoverActions description
|
||
|
$field->notes = __('For more options here, see the $config->pageList setting in /wire/config.php. You may copy those settings to /site/config.php and override them.'); // useHoverActions notes
|
||
|
if(!empty($data['useHoverActions'])) $field->attr('checked', 'checked');
|
||
|
$fields->append($field);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
$defaultNote1 = $this->_('Default value is %d.');
|
||
|
$defaultNote2 = $this->_('If left at the default value, this setting can also be specified in the $config->pageList array.');
|
||
|
|
||
|
/** @var InputfieldInteger $field */
|
||
|
$field = $modules->get("InputfieldInteger");
|
||
|
$field->attr('name', 'limit');
|
||
|
$field->attr('value', !empty($data['limit']) ? (int) $data['limit'] : self::defaultLimit);
|
||
|
$field->label = $this->_('Number of pages to display before pagination');
|
||
|
$field->notes = sprintf($defaultNote1, self::defaultLimit) . ' ' . $defaultNote2;
|
||
|
$fields->append($field);
|
||
|
|
||
|
/** @var InputfieldInteger $field */
|
||
|
$field = $modules->get("InputfieldInteger");
|
||
|
$field->attr('name', 'speed');
|
||
|
$field->attr('value', array_key_exists('speed', $data) ? (int) $data['speed'] : self::defaultSpeed);
|
||
|
$field->label = $this->_('Animation Speed (in ms)');
|
||
|
$field->description = $this->_('This is the speed at which each branch in the page tree animates up or down. Lower numbers are faster but less visible. For no animation specify 0.'); // Animation speed description
|
||
|
$field->notes = sprintf($defaultNote1, self::defaultSpeed) . ' ' . $defaultNote2;
|
||
|
$fields->append($field);
|
||
|
|
||
|
/** @var InputfieldRadios $field */
|
||
|
$field = $modules->get('InputfieldRadios');
|
||
|
$field->attr('name', 'qtyType');
|
||
|
$field->label = $this->_('Children quantity type');
|
||
|
$field->description = $this->_('In the page list, a quantity of children is shown next to each page when applicable. What type of quantity should it show?');
|
||
|
$field->notes = $this->_('When showing descendants, the quantity includes all descendants, whether listable to the user or not.');
|
||
|
$field->addOption('', $this->_('Immediate children (default)'));
|
||
|
$field->addOption('total', $this->_('Descendants: children, grandchildren, great-grandchildren, and on…'));
|
||
|
$field->addOption('children/total', $this->_('Both: children/descendants'));
|
||
|
$field->addOption('total/children', $this->_('Both: descendants/children'));
|
||
|
$field->addOption('id', $this->_('Show Page ID number instead'));
|
||
|
$field->attr('value', empty($data['qtyType']) ? '' : $data['qtyType']);
|
||
|
$fields->append($field);
|
||
|
|
||
|
return $fields;
|
||
|
}
|
||
|
|
||
|
}
|