'Page List', 'summary' => 'List pages in a hierarchal tree structure', 'version' => 107, 'permanent' => true, 'permission' => 'page-edit', 'icon' => 'sitemap', ); } public function __construct() { $this->showRootPage = true; $this->pageLabelField = 'title'; $this->limit = self::defaultLimit; } /** * Initialize the Page List * */ public function init() { parent::init(); $this->start = isset($_GET['start']) ? (int) $_GET['start'] : 0; $this->limit = (isset($_GET['limit']) && $_GET['limit'] < $this->limit) ? (int) $_GET['limit'] : $this->limit; $this->render = isset($_GET['render']) ? strtoupper($this->sanitizer->name($_GET['render'])) : ''; } /** * Execute the Page List * */ public function ___execute() { $langID = (int) $this->wire('input')->get->lang; if($langID) $this->wire('user')->language = $this->languages->get($langID); $this->trashLabel = $this->_('Trash'); // Label for 'Trash' page in PageList // Overrides page title if used if(!$this->id) $this->id = isset($_GET['id']) ? (int) $_GET['id'] : 0; $this->openPage = $this->input->get->open ? $this->pages->get((int) $this->input->get->open) : new NullPage(); $this->page = $this->pages->get("id=" . ($this->id ? $this->id : 1) . ", status<" . Page::statusMax); if(!$this->page) throw new Wire404Exception("Unable to load page {$this->id}"); if(!$this->page->listable()) throw new WirePermissionException("You don't have access to list page {$this->page->url}"); $this->page->setOutputFormatting(false); $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($p->process == $this && $this->wire('input')->get('mode') != 'select') { // store information about the last page list so that it can later be linked to again by ProcessPageEdit $n = 1; if($this->start >= $this->limit) $n = ($this->start / $this->limit)+1; $lastPageList = $this->session->get('lastPageList'); if(!is_array($lastPageList)) $lastPageList = array(); else $lastPageList = array_slice($lastPageList, 0, 10, true); $lastPageList[$this->page->id] = array( 'title' => (string) $this->page->get('title|name'), 'url' => (string) $this->wire('page')->url, 'n' => $n ); $this->session->set('lastPageList', $lastPageList); } return $this->render(); } /** * Render the Page List * */ protected function render() { $this->setupBreadcrumbs(); if(!$this->render) { $openPageIDs = array(); if($this->openPage->id > 1) { $openPageIDs[] = $this->openPage->id; foreach($this->openPage->parents() as $parent) { if($parent->id > 1) $openPageIDs[] = $parent->id; } } $this->fuel('config')->js('ProcessPageList', array( 'containerID' => 'PageListContainer', 'ajaxURL' => $this->config->urls->admin . "page/list/", 'ajaxMoveURL' => $this->config->urls->admin . "page/sort/", 'rootPageID' => $this->id, 'openPageIDs' => $openPageIDs, 'openPagination' => (int) $this->input->get->n, // @todo: make it openPaginations and correspond to openPageIDs 'showRootPage' => $this->showRootPage ? true : false, 'limit' => $this->limit, 'start' => $this->start, 'speed' => ($this->speed !== null ? (int) $this->speed : self::defaultSpeed), '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, )); $tokenName = $this->session->CSRF->getTokenName(); $tokenValue = $this->session->CSRF->getTokenValue(); return "\n
"; // CSRF tokens } $class = "ProcessPageListRender" . $this->render; if(!class_exists($class)) throw new WireException("Requested PageList renderer does not exist"); if($this->limit) $children = $this->find("start={$this->start}, limit={$this->limit}, status<" . Page::statusMax, $this->page); else $children = new PageArray(); $renderer = new $class($this->id, $this->page, $children); $renderer->setStart($this->start); $renderer->setLimit($this->limit); $renderer->setPageLabelField($this->pageLabelField); $renderer->setLabel('trash', $this->trashLabel); return $renderer->render(); } public function ___find($selectorString, Page $page) { return $page->children($selectorString); } /** * Set a value to this Page List (see WireData) * */ 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() { if($this->fuel->process != $this || !$this->fuel->breadcrumbs) return; foreach($this->page->parents() as $p) { $this->fuel->breadcrumbs->add(new Breadcrumb($this->config->urls->admin . "page/list/?id=" . $p->id, $p->get("title|name"))); } } /** * Build a form allowing configuration of this Module * */ static public function getModuleConfigInputfields(array $data) { $fields = new InputfieldWrapper(); $modules = Wire::getFuel('modules'); $field = $modules->get("InputfieldText"); $field->attr('name', 'pageLabelField'); $field->attr('value', !empty($data['pageLabelField']) ? $data['pageLabelField'] : 'title'); $field->label = __("Name of page field to display"); $field->description = __('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. 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 field you specify resolves to a blank value then ProcessWire will use the page "name" field.'); // pageLabelField description $field->notes = __('You may optionally override this setting on a per-template basis in each template "advanced" settings.'); // pageLabelField notes $fields->append($field); $field = $modules->get("InputfieldInteger"); $field->attr('name', 'limit'); $field->attr('value', !empty($data['limit']) ? (int) $data['limit'] : self::defaultLimit); $field->label = __('Number of pages to display before pagination'); $fields->append($field); $field = $modules->get("InputfieldInteger"); $field->attr('name', 'speed'); $field->attr('value', array_key_exists('speed', $data) ? (int) $data['speed'] : self::defaultSpeed); $field->label = __('Animation Speed (in ms)'); $field->description = __('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 $fields->append($field); return $fields; } } /** * Base class for Page List rendering * */ abstract class ProcessPageListRender extends Wire { protected $id; protected $page; protected $children; protected $start; protected $limit; protected $pageLabelField = 'title'; protected $actionLabels = array(); protected $superuser = false; protected $labels = array(); public function __construct($id, Page $page, PageArray $children) { $this->id = $id; $this->page = $page; $this->children = $children; $this->start = 0; $this->limit = 0; $this->superuser = $this->wire('user')->isSuperuser(); } public function setStart($n) { $this->start = (int) $n; } public function setLimit($n) { $this->limit = (int) $n; } public function setLabel($key, $value) { $this->labels[$key] = $value; } public function setPageLabelField($pageLabelField) { $this->pageLabelField = $pageLabelField; } /** * Get an array of available Page actions, indexed by $label => $url * * @param Page $page * @return array of $label => $url * */ public function ___getPageActions(Page $page) { $actions = array(); $adminUrl = $this->config->urls->admin; if(!count($this->actionLabels)) $this->actionLabels = array( 'edit' => $this->_('Edit'), // Edit page action 'view' => $this->_('View'), // View page action 'add' => $this->_('New'), // New page action 'move' => $this->_('Move'), // Move page action 'empty' => $this->_('Empty'), // Empty trash page action ); // Note: 'cn' is short for 'className' if($page->id == $this->config->trashPageID) { if($this->superuser) $actions[] = array( 'cn' => 'Empty', 'name' => $this->actionLabels['empty'], 'url' => "{$adminUrl}page/trash/" ); } else { if($page->editable()) $actions[] = array( 'cn' => 'Edit', 'name' => $this->actionLabels['edit'], 'url' => "{$adminUrl}page/edit/?id={$page->id}" ); if($page->viewable()) $actions[] = array( 'cn' => 'View', 'name' => $this->actionLabels['view'], 'url' => $page->httpUrl ); if($page->addable()) $actions[] = array( 'cn' => 'New', 'name' => $this->actionLabels['add'], 'url' => "{$adminUrl}page/add/?parent_id={$page->id}" ); if($page->id > 1 && ($page->moveable() || ($page->sortable() && $page->sortfield == 'sort'))) $actions[] = array( 'cn' => 'Move', 'name' => $this->actionLabels['move'], 'url' => '#' ); } return $actions; } /** * Return the Page's label text, whether that originates from the Page's name, headline, title, etc. * * @param Page $page * @return string * */ public function ___getPageLabel(Page $page) { $value = ''; $icon = ''; // if the page's template specifies a pageLabelField, use that $pageLabelField = trim($page->template->pageLabelField); // otherwise use the one specified with this instance if(!strlen($pageLabelField)) $pageLabelField = $this->pageLabelField; // convert to array if(strpos($pageLabelField, ' ')) $fields = explode(' ', $pageLabelField); else $fields = array($pageLabelField); //if($page->is(Page::statusTemp)) $icon = ""; foreach($fields as $field) { if(strpos($field, ".")) { list($field, $subfield) = explode(".", $field); } else if(strpos($field, 'icon-') === 0 || strpos($field, 'fa-') === 0) { $field = str_replace(array('icon-', 'fa-'), 'fa-', $field); $field = $this->wire('sanitizer')->name($field); if(!$icon) $icon = ""; continue; } else { $subfield = ''; } $v = $page->get($field); if($subfield && is_object($v)) { if($v instanceof WireArray && count($v)) $v = $v->first(); $v = $v->get($subfield); } else if(($field == 'created' || $field == 'modified') && ctype_digit("$v")) { $v = date($this->fuel('config')->dateFormat, (int) $v); } if(!strlen("$v")) continue; $value .= "" . htmlspecialchars(strip_tags("$v"), ENT_QUOTES, "UTF-8", false) . ""; } if(!strlen($value)) $value = $page->get("name"); return $icon . trim($value); } abstract public function render(); public function getRenderName() { return str_replace('ProcessPageListRender', '', $this->className()); } public function getMoreURL() { if($this->limit && ($this->page->numChildren(1) > ($this->start + $this->limit))) { $start = $this->start + $this->limit; return $this->config->urls->admin . "page/list/?&id={$this->page->id}&start=$start&render=" . $this->getRenderName(); } return ''; } } /** * JSON implementation of the Page List rendering * */ class ProcessPageListRenderJSON extends ProcessPageListRender { protected $systemIDs = array(); public function __construct($id, Page $page, PageArray $children) { parent::__construct($id, $page, $children); $this->systemIDs = array( $this->config->http404PageID, $this->config->adminRootPageID, $this->config->trashPageID, $this->config->loginPageID, ); } protected function renderChild(Page $page) { $outputFormatting = $page->outputFormatting; $page->setOutputFormatting(true); $class = ''; $type = ''; $note = ''; $label = ''; $icon = ''; if($page->id == $this->config->trashPageID) { $note = "< " . $this->_("Trash open: drag pages below here to trash them"); // Message that appears next to the Trash page when open $icon = 'trash-o'; } if(in_array($page->id, $this->systemIDs)) { $type = 'System'; if($page->id == $this->config->http404PageID) $label = $this->_('404 Page Not Found'); // Label for '404 Page Not Found' page in PageList // Overrides page title if used else if($page->id == $this->config->adminRootPageID) $label = $this->_('Admin'); // Label for 'Admin' page in PageList // Overrides page title if used else if($page->id == $this->config->trashPageID && isset($this->labels['trash'])) $label = $this->labels['trash']; // Label for 'Trash' page in PageList // Overrides page title if used // if label is not overridden by a language pack, make $label blank to use the page title instead if(in_array($label, array('Trash', 'Admin', '404 Page Not Found'))) $label = ''; } if(!$label) $label = $this->getPageLabel($page); if($page->getAccessParent() === $page && $page->parent->id) { if($page->getAccessTemplate()->hasRole('guest')) { if(!$page->parent->getAccessTemplate()->hasRole('guest')) { $class .= ' PageListAccessOn'; if(!$icon) $icon = 'unlock-alt'; } } else if($page->parent->getAccessTemplate()->hasRole('guest')) { $class .= ' PageListAccessOff'; if(!$icon) $icon = 'lock'; } } if(!$icon && $page->is(Page::statusTemp)) $icon = 'bolt'; if($icon) $label = $label . ""; $a = array( 'id' => $page->id, 'label' => $label, 'status' => $page->status, 'numChildren' => $page->numChildren(1), 'path' => $page->path(), 'template' => $page->template->name, 'rm' => $this->superuser && $page->trashable(), 'actions' => $this->getPageActions($page), ); if($class) $a['addClass'] = trim($class); if($type) $a['type'] = $type; if($note) $a['note'] = $note; $page->setOutputFormatting($outputFormatting); return $a; } public function render() { $children = array(); $extraPages = array(); // pages forced to bottom of list foreach($this->children as $page) { if(in_array($page->id, $this->systemIDs)) { $extraPages[] = $page; continue; } $child = $this->renderChild($page); $children[] = $child; } if($this->superuser) foreach($extraPages as $page) { $children[] = $this->renderChild($page); } $json = array( 'page' => $this->renderChild($this->page), 'children' => $children, 'start' => $this->start, 'limit' => $this->limit, ); return json_encode($json); } } /** * XHTML implementation of the Page List rendering * * No longer applicable, but kept here in comments for future reference * * class ProcessPageListRenderXHTML extends ProcessPageListRender { public function render() { if($this->id == 0) return "