944 lines
26 KiB
PHP
944 lines
26 KiB
PHP
<?php namespace ProcessWire;
|
||
|
||
/**
|
||
* AdminTheme Framework
|
||
*
|
||
* The methods in this class may eventually be merged to AdminTheme.php,
|
||
* but are isolated to this class during development.
|
||
*
|
||
* This file is licensed under the MIT license.
|
||
* https://processwire.com/about/license/mit/
|
||
*
|
||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||
* https://processwire.com
|
||
*
|
||
* @property bool $isSuperuser
|
||
* @property bool $isEditor
|
||
* @property bool $isLoggedIn
|
||
* @property bool|string $isModal
|
||
* @property bool|int $useAsLogin
|
||
* @property string $browserTitle Optional custom browser title for this request (3.0.217+)
|
||
* @method array getUserNavArray()
|
||
* @method array getPrimaryNavArray()
|
||
* @method string renderFile($basename, array $vars = [])
|
||
*
|
||
*/
|
||
abstract class AdminThemeFramework extends AdminTheme {
|
||
|
||
/**
|
||
* Is there currently a logged in user?
|
||
*
|
||
* @var bool
|
||
*
|
||
*/
|
||
protected $isLoggedIn = false;
|
||
|
||
/**
|
||
* Is user logged in with page-edit permission?
|
||
*
|
||
* @var bool
|
||
*
|
||
*/
|
||
protected $isEditor = false;
|
||
|
||
/**
|
||
* Is current user a superuser?
|
||
*
|
||
* @var bool
|
||
*
|
||
*/
|
||
protected $isSuperuser = false;
|
||
|
||
/**
|
||
* Is the current request a modal request?
|
||
*
|
||
* @var bool|string Either false, true, or "inline"
|
||
*
|
||
*/
|
||
protected $isModal = false;
|
||
|
||
/**
|
||
* @var Sanitizer
|
||
*
|
||
*/
|
||
protected $sanitizer;
|
||
|
||
/**
|
||
* Custom template path when it exists, i.e. /site/templates/AdminThemeUikit/
|
||
*
|
||
* @var string|null Null when not yet known, blank '' when it does not exist, populated when it exists
|
||
*
|
||
*/
|
||
protected $customTemplatePath = null;
|
||
|
||
/**
|
||
* Default template path
|
||
*
|
||
* @var string|null
|
||
*
|
||
*/
|
||
protected $defaultTemplatePath = null;
|
||
|
||
/**
|
||
* Is the renderFile() method hooked? Null when not known, bool when known
|
||
*
|
||
* @var bool|null
|
||
*
|
||
*/
|
||
protected $renderFileHooked = null;
|
||
|
||
/**
|
||
* Construct
|
||
*
|
||
*/
|
||
public function __construct() {
|
||
parent::__construct();
|
||
$this->set('useAsLogin', false);
|
||
$this->set('browserTitle', '');
|
||
}
|
||
|
||
public function wired() {
|
||
$this->sanitizer = $this->wire()->sanitizer;
|
||
$user = $this->wire()->user;
|
||
$this->isLoggedIn = $user && $user->isLoggedin();
|
||
parent::wired();
|
||
}
|
||
|
||
/**
|
||
* Override get() method from WireData to support additional properties
|
||
*
|
||
* @param string $key
|
||
* @return bool|int|mixed|null|string
|
||
*
|
||
*/
|
||
public function get($key) {
|
||
switch($key) {
|
||
case 'isSuperuser': $value = $this->isSuperuser; break;
|
||
case 'isEditor': $value = $this->isEditor; break;
|
||
case 'isLoggedIn': $value = $this->isLoggedIn; break;
|
||
case 'isModal': $value = $this->isModal; break;
|
||
default: $value = parent::get($key);
|
||
}
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Initialize and attach hooks
|
||
*
|
||
* Note: descending classes should call this after API ready
|
||
*
|
||
*/
|
||
public function init() {
|
||
|
||
$user = $this->wire()->user;
|
||
$input = $this->wire()->input;
|
||
|
||
if(!$this->isLoggedIn && $this->useAsLogin) $this->setCurrent();
|
||
parent::init();
|
||
|
||
// if this is not the current admin theme, exit now so no hooks are attached
|
||
if(!$this->isCurrent()) return;
|
||
|
||
$this->isSuperuser = $this->isLoggedIn && $user->isSuperuser();
|
||
$this->isEditor = $this->isLoggedIn && ($this->isSuperuser || $user->hasPermission('page-edit'));
|
||
$this->includeInitFile();
|
||
|
||
$modal = $input->get('modal');
|
||
if($modal) $this->isModal = $modal == 'inline' ? 'inline' : true;
|
||
|
||
// test notices when requested
|
||
if($input->get('test_notices') && $this->isLoggedIn) $this->testNotices();
|
||
}
|
||
|
||
/**
|
||
* Include the admin theme init file
|
||
*
|
||
*/
|
||
public function includeInitFile() {
|
||
$config = $this->wire()->config;
|
||
$initFile = $this->path() . 'init.php';
|
||
if(file_exists($initFile)) {
|
||
if(strpos($initFile, $config->paths->site) === 0) {
|
||
// admin themes in /site/modules/ may be compiled
|
||
$initFile = $this->wire()->files->compile($initFile);
|
||
}
|
||
/** @noinspection PhpIncludeInspection */
|
||
include_once($initFile);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Perform a translation, based on text from shared admin file: /wire/templates-admin/default.php
|
||
*
|
||
* @param string $text
|
||
* @return string
|
||
*
|
||
*/
|
||
public function _($text) {
|
||
static $translate = null;
|
||
static $context = null;
|
||
if($translate === null) $translate = $this->wire()->languages !== null;
|
||
if($translate === false) return $text;
|
||
if($context === null) $context = $this->wire()->config->paths->root . 'wire/templates-admin/default.php';
|
||
$value = __($text, $context);
|
||
if($value === $text) $value = parent::_($text);
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Get the current page headline
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getHeadline() {
|
||
$headline = $this->wire('processHeadline');
|
||
if(!$headline) $headline = $this->wire()->page->get('title|name');
|
||
if($headline !== 'en' && $this->wire()->languages) $headline = $this->_($headline);
|
||
return $this->sanitizer->entities1($headline);
|
||
}
|
||
|
||
/**
|
||
* Get navigation title for the given page, return blank if page should not be shown
|
||
*
|
||
* @param Page $p
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getPageTitle(Page $p) {
|
||
|
||
if($p->name == 'add' && $p->parent->name == 'page') {
|
||
|
||
$title = $this->getAddNewLabel();
|
||
|
||
} else {
|
||
$title = $this->_($p->title);
|
||
}
|
||
|
||
$title = $this->sanitizer->entities1($title);
|
||
|
||
return $title;
|
||
}
|
||
|
||
/**
|
||
* Get icon used by the given page
|
||
*
|
||
* @param Page $p
|
||
* @return mixed|null|string
|
||
*
|
||
*/
|
||
public function getPageIcon(Page $p) {
|
||
$icon = '';
|
||
if($p->template == 'admin') {
|
||
$info = $this->wire()->modules->getModuleInfo($p->process);
|
||
if(!empty($info['icon'])) $icon = $info['icon'];
|
||
}
|
||
// allow for option of an admin field overriding the module icon
|
||
$pageIcon = $p->get('page_icon');
|
||
if($pageIcon) $icon = $pageIcon;
|
||
if(!$icon) switch($p->id) {
|
||
case 22: $icon = 'gears'; break; // Setup
|
||
case 21: $icon = 'plug'; break; // Modules
|
||
case 28: $icon = 'key'; break; // Access
|
||
}
|
||
if(!$icon && $p->parent->id != $this->wire()->config->adminRootPageID) {
|
||
$icon = 'file-o ui-priority-secondary';
|
||
}
|
||
return $icon;
|
||
}
|
||
|
||
/**
|
||
* Get “Add New” button actions
|
||
*
|
||
* - Returns array of arrays, each with 'url', 'label' and 'icon' properties.
|
||
* - Returns empty array if Add New button should not be displayed.
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function getAddNewActions() {
|
||
|
||
$page = $this->wire()->page;
|
||
$process = $this->wire()->process;
|
||
$input = $this->wire()->input;
|
||
|
||
if(!$this->isEditor) return array();
|
||
if($page->name != 'page' && $page->name != 'list') return array();
|
||
if($input->urlSegment1 || $input->get('modal')) return array();
|
||
if(strpos($process, 'ProcessPageList') !== 0) return array();
|
||
|
||
/** @var ProcessPageAdd $module */
|
||
$module = $this->wire()->modules->getModule('ProcessPageAdd', array('noInit' => true));
|
||
$data = $module->executeNavJSON(array('getArray' => true));
|
||
$actions = array();
|
||
|
||
foreach($data['list'] as $item) {
|
||
$item['url'] = $data['url'] . $item['url'];
|
||
$actions[] = $item;
|
||
}
|
||
|
||
return $actions;
|
||
}
|
||
|
||
/**
|
||
* Get the translated “Add New” label that’s used in a couple spots
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getAddNewLabel() {
|
||
return $this->_('Add New');
|
||
}
|
||
|
||
/**
|
||
* Get the classes that will be used in the <body class=''> tag
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getBodyClass() {
|
||
|
||
$page = $this->wire()->page;
|
||
$process = $this->wire()->process;
|
||
|
||
$classes = array(
|
||
"id-{$page->id}",
|
||
"template-{$page->template->name}",
|
||
"pw-init",
|
||
parent::getBodyClass(),
|
||
);
|
||
|
||
if($this->isModal) $classes[] = 'modal';
|
||
if($this->isModal === 'inline') $classes[] = 'modal-inline';
|
||
if($this->wire()->input->urlSegment1) $classes[] = 'hasUrlSegments';
|
||
if($process) $classes[] = $process->className();
|
||
if(!$this->isLoggedIn) $classes[] = 'pw-guest';
|
||
|
||
return implode(' ', $classes);
|
||
}
|
||
|
||
/**
|
||
* Get Javascript that must be present in the document <head>
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getHeadJS() {
|
||
$config = $this->wire()->config;
|
||
return
|
||
"var ProcessWire = { config: " . wireEncodeJSON($config->js(), true, $config->debug) . " }; " .
|
||
"var config = ProcessWire.config;\n"; // legacy support
|
||
}
|
||
|
||
|
||
/**
|
||
* Allow the given Page to appear in admin theme navigation?
|
||
*
|
||
* @param Page $p Page to test
|
||
* @param PageArray|array $children Children of page, if applicable (optional)
|
||
* @param string|null $permission Specify required permission (optional)
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function allowPageInNav(Page $p, $children = array(), $permission = null) {
|
||
|
||
if($this->isSuperuser) return true;
|
||
|
||
$pageViewable = $p->viewable();
|
||
if(!$pageViewable) return false;
|
||
|
||
$allow = false;
|
||
$numChildren = count($children);
|
||
|
||
if($p->process == 'ProcessPageAdd') {
|
||
// ProcessPageAdd: avoid showing this menu item if there are no predefined family settings to use
|
||
$session = $this->wire()->session;
|
||
$numAddable = $session->getFor('ProcessPageAdd', 'numAddable');
|
||
if($numAddable === null) {
|
||
/** @var ProcessPageAdd $processPageAdd */
|
||
$processPageAdd = $this->wire()->modules->getModule('ProcessPageAdd', array('noInit' => true));
|
||
if($processPageAdd) {
|
||
$addData = $processPageAdd->executeNavJSON(array('getArray' => true));
|
||
$numAddable = $addData['list'];
|
||
$session->setFor('ProcessPageAdd', 'numAddable', $numAddable);
|
||
}
|
||
}
|
||
// no addable options, so do not show the "Add New" item
|
||
if(!$numAddable) return false;
|
||
|
||
} else if(empty($permission)) {
|
||
// no permission specified
|
||
|
||
if(!$p->process) {
|
||
// no process module present, so we delegate to just the page viewable state if no children to check
|
||
if(!$numChildren) return true;
|
||
|
||
} else if($p->process == 'ProcessList') {
|
||
// page just serves as a list for children
|
||
|
||
} else {
|
||
// determine permission from Process module, if present
|
||
$moduleInfo = $this->wire()->modules->getModuleInfo($p->process);
|
||
if(!empty($moduleInfo['permission'])) $permission = $moduleInfo['permission'];
|
||
}
|
||
}
|
||
|
||
if($permission) {
|
||
// specific permission required to determine view access
|
||
$allow = $this->wire()->user->hasPermission($permission);
|
||
|
||
} else if($p->parent_id == $this->wire()->config->adminRootPageID) {
|
||
// primary nav page requires that at least one child is viewable
|
||
foreach($children as $child) {
|
||
if($this->allowPageInNav($child)) {
|
||
$allow = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $allow;
|
||
}
|
||
|
||
/**
|
||
* Return nav array of primary navigation
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function ___getPrimaryNavArray() {
|
||
|
||
$items = array();
|
||
$config = $this->wire()->config;
|
||
$admin = $this->wire()->pages->get($config->adminRootPageID);
|
||
|
||
foreach($admin->children("check_access=0") as $p) {
|
||
$item = $this->pageToNavArray($p);
|
||
if($item) $items[] = $item;
|
||
}
|
||
|
||
return $items;
|
||
}
|
||
|
||
/**
|
||
* Get navigation array from a Process module
|
||
*
|
||
* @param array|Module|string $module Module info array or Module object or string
|
||
* @param Page $p Page upon which the Process module is contained
|
||
* @return array
|
||
*
|
||
*/
|
||
public function moduleToNavArray($module, Page $p) {
|
||
|
||
$config = $this->wire()->config;
|
||
$modules = $this->wire()->modules;
|
||
$user = $this->wire()->user;
|
||
|
||
$textdomain = str_replace($config->paths->root, '/', $modules->getModuleFile($p->process));
|
||
$navArray = array();
|
||
|
||
if(is_array($module)) {
|
||
$moduleInfo = $module;
|
||
} else {
|
||
$moduleInfo = $modules->getModuleInfo($module);
|
||
}
|
||
|
||
foreach($moduleInfo['nav'] as $navItem) {
|
||
|
||
$permission = empty($navItem['permission']) ? '' : $navItem['permission'];
|
||
if($permission && !$user->hasPermission($permission)) continue;
|
||
|
||
$navArray[] = array(
|
||
'id' => 0,
|
||
'parent_id' => $p->id,
|
||
'title' => $this->sanitizer->entities1(__($navItem['label'], $textdomain)), // translate from context of Process module
|
||
'name' => '',
|
||
'url' => $p->url . $navItem['url'],
|
||
'icon' => empty($navItem['icon']) ? '' : $navItem['icon'],
|
||
'children' => array(),
|
||
'navJSON' => empty($navItem['navJSON']) ? '' : $p->url . $navItem['navJSON'],
|
||
);
|
||
}
|
||
|
||
return $navArray;
|
||
}
|
||
|
||
/**
|
||
* Get a navigation array the given Page, or null if page not allowed in nav
|
||
*
|
||
* @param Page $p
|
||
* @return array|null
|
||
*
|
||
*/
|
||
public function pageToNavArray(Page $p) {
|
||
|
||
$children = $p->numChildren ? $p->children("check_access=0") : array();
|
||
|
||
if(!$this->allowPageInNav($p, $children)) return null;
|
||
|
||
$navArray = array(
|
||
'id' => $p->id,
|
||
'parent_id' => $p->parent_id,
|
||
'url' => $p->url,
|
||
'name' => $p->name,
|
||
'title' => $this->getPageTitle($p),
|
||
'icon' => $this->getPageIcon($p),
|
||
'children' => array(),
|
||
'navJSON' => '',
|
||
);
|
||
|
||
if(!count($children)) {
|
||
// no children available
|
||
if($p->template == 'admin' && $p->process) {
|
||
// see if process module defines its own navigation
|
||
$moduleInfo = $this->wire()->modules->getModuleInfo($p->process);
|
||
if(!empty($moduleInfo['nav'])) {
|
||
$navArray['children'] = $this->moduleToNavArray($moduleInfo, $p);
|
||
}
|
||
} else {
|
||
// The /page/ and /page/list/ are the same process, so just keep them on /page/ instead.
|
||
if(strpos($navArray['url'], '/page/list/') !== false) {
|
||
$navArray['url'] = str_replace('/page/list/', '/page/', $navArray['url']);
|
||
}
|
||
}
|
||
return $navArray;
|
||
}
|
||
|
||
// if we reach this point, then we have a PageArray of children
|
||
|
||
$modules = $this->wire()->modules;
|
||
|
||
foreach($children as $c) {
|
||
|
||
if(!$c->process) continue;
|
||
$moduleInfo = $modules->getModuleInfo($c->process);
|
||
$permission = empty($moduleInfo['permission']) ? '' : $moduleInfo['permission'];
|
||
if(!$this->allowPageInNav($c, array(), $permission)) continue;
|
||
|
||
$childItem = array(
|
||
'id' => $c->id,
|
||
'parent_id' => $c->parent_id,
|
||
'title' => $this->getPageTitle($c),
|
||
'name' => $c->name,
|
||
'url' => $c->url,
|
||
'icon' => $this->getPageIcon($c),
|
||
'children' => array(),
|
||
'navJSON' => empty($moduleInfo['useNavJSON']) ? '' : $c->url . 'navJSON/',
|
||
);
|
||
|
||
if(!empty($moduleInfo['nav'])) {
|
||
$childItem['children'] = $this->moduleToNavArray($moduleInfo, $c);
|
||
}
|
||
|
||
$navArray['children'][] = $childItem;
|
||
|
||
} // foreach
|
||
|
||
return $navArray;
|
||
}
|
||
|
||
/**
|
||
* Get navigation items for the “user” menu
|
||
*
|
||
* This is hookable so that something else could add stuff to it.
|
||
* See the method body for details on format used.
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function ___getUserNavArray() {
|
||
$urls = $this->wire()->urls;
|
||
$navArray = array();
|
||
|
||
$navArray[] = array(
|
||
'url' => $urls->root,
|
||
'title' => $this->_('View site'),
|
||
'target' => '_top',
|
||
'icon' => 'eye',
|
||
);
|
||
|
||
if($this->wire()->user->hasPermission('profile-edit')) $navArray[] = array(
|
||
'url' => $urls->admin . 'profile/',
|
||
'title' => $this->_('Profile'),
|
||
'icon' => 'user',
|
||
'permission' => 'profile-edit',
|
||
);
|
||
|
||
$navArray[] = array(
|
||
'url' => $urls->admin . 'login/logout/',
|
||
'title' => $this->_('Logout'),
|
||
'target' => '_top',
|
||
'icon' => 'power-off',
|
||
);
|
||
|
||
return $navArray;
|
||
}
|
||
|
||
/**
|
||
* Get the browser <title>
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function getBrowserTitle() {
|
||
|
||
$browserTitle = $this->browserTitle; // custom defined browser title
|
||
if(strlen($browserTitle)) return $this->sanitizer->entities($browserTitle);
|
||
|
||
$browserTitle = $this->wire('processBrowserTitle');
|
||
$modal = $this->wire()->input->get('modal');
|
||
|
||
if(!$browserTitle) {
|
||
if($modal) return $this->wire('processHeadline');
|
||
$browserTitle = $this->_(strip_tags($this->wire()->page->get('title|name'))) . ' • ProcessWire';
|
||
}
|
||
|
||
if(!$modal) {
|
||
$httpHost = $this->wire()->config->httpHost;
|
||
if(strpos($httpHost, 'www.') === 0) $httpHost = substr($httpHost, 4); // remove www
|
||
if(strpos($httpHost, ':')) $httpHost = preg_replace('/:\d+/', '', $httpHost); // remove port
|
||
$browserTitle .= " • $httpHost";
|
||
}
|
||
|
||
return $this->sanitizer->entities1($browserTitle);
|
||
}
|
||
|
||
/**
|
||
* Test all notice types
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function testNotices() {
|
||
if(!$this->wire()->user->isLoggedin()) return false;
|
||
$this->message('Message test');
|
||
$this->message('Message test debug', Notice::debug);
|
||
$this->message('Message test markup <a href="#">example</a>', Notice::allowMarkup);
|
||
$this->warning('Warning test');
|
||
$this->warning('Warning test debug', Notice::debug);
|
||
$this->warning('Warning test markup <a href="#">example</a>', Notice::allowMarkup);
|
||
$this->error('Error test');
|
||
$this->error('Error test debug', Notice::debug);
|
||
$this->error('Error test markup <a href="#">example</a>', Notice::allowMarkup);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Render runtime notices div#notices
|
||
*
|
||
* @param Notices|bool $notices Notices object or specify boolean true to return array of all available $options
|
||
* @param array $options See defaults in method
|
||
* @return string|array Returns string unless you specify true for $notices argument, then it returns an array.
|
||
*
|
||
*/
|
||
public function renderNotices($notices, array $options = array()) {
|
||
|
||
$defaults = array(
|
||
'messageClass' => 'NoticeMessage', // class for messages
|
||
'messageIcon' => 'check-square', // default icon to show with notices
|
||
'warningClass' => 'NoticeWarning', // class for warnings
|
||
'warningIcon' => 'exclamation-circle', // icon for warnings
|
||
'errorClass' => 'NoticeError', // class for errors
|
||
'errorIcon' => 'exclamation-triangle', // icon for errors
|
||
'debugClass' => 'NoticeDebug', // class for debug items (appended)
|
||
'debugIcon' => 'bug', // icon for debug notices
|
||
'closeClass' => 'pw-notice-remove notice-remove', // class for close notices link <a>
|
||
'closeIcon' => 'times', // icon for close notices link
|
||
'listMarkup' => "<ul class='pw-notices' id='notices'>{out}</ul><!--/notices-->",
|
||
'itemMarkup' => "<li class='{class}'>{remove}{icon}{text}</li>",
|
||
// the following apply only when groupByType==true
|
||
'groupByType' => true, // Group notices by type
|
||
'groupParentClass' => 'pw-notice-group-parent', // class for parent notices
|
||
'groupChildClass' => 'pw-notice-group-child', // class for children (of parent notices)
|
||
'groupToggleMarkup' => "<a class='pw-notice-group-toggle' href='#'>{label}" .
|
||
"<i class='fa fa-fw fa-bell-o' data-toggle='fa-bell-o fa-bell'></i>" .
|
||
"<i class='fa fa-fw fa-angle-right' data-toggle='fa-angle-right fa-angle-down'></i></a>",
|
||
'groupToggleLabel' => $this->_("+{n-1}"),
|
||
);
|
||
|
||
$options = array_merge($defaults, $options);
|
||
if($notices === true) return $options;
|
||
$config = $this->wire()->config;
|
||
$noticesArray = array();
|
||
$out = '';
|
||
|
||
$removeIcon = $this->renderIcon($options['closeIcon']);
|
||
$removeLabel = $this->_('Close all');
|
||
$removeLink = "<a class='$options[closeClass]' href='#' title='$removeLabel'>$removeIcon</a>";
|
||
|
||
if($this->isLoggedIn && $this->wire()->modules->isInstalled('SystemNotifications')) {
|
||
$defaults['groupByType'] = false;
|
||
//$systemNotifications = $this->wire('modules')->get('SystemNotifications');
|
||
//if(!$systemNotifications->placement) return '';
|
||
}
|
||
|
||
foreach($notices as $n => $notice) {
|
||
/** @var Notice $notice */
|
||
|
||
$text = $notice->text;
|
||
$allowMarkup = $notice->flags & Notice::allowMarkup;
|
||
$groupByType = $options['groupByType'] && !($notice->flags & Notice::noGroup) && !($notice instanceof NoticeError);
|
||
|
||
if($allowMarkup) {
|
||
// leave $text alone
|
||
} else {
|
||
// unencode + re-encode entities, just in case module already entity some or all of output
|
||
if(strpos($text, '&') !== false) $text = $this->sanitizer->unentities($text);
|
||
$text = $this->sanitizer->entities($text);
|
||
$text = nl2br($text);
|
||
}
|
||
|
||
if($notice instanceof NoticeError) {
|
||
$class = $options['errorClass'];
|
||
$icon = $options['errorIcon'];
|
||
$noticeType = 'errors';
|
||
|
||
} else if($notice instanceof NoticeWarning) {
|
||
$class = $options['warningClass'];
|
||
$icon = $options['warningIcon'];
|
||
$noticeType = 'warnings';
|
||
|
||
} else {
|
||
$class = $options['messageClass'];
|
||
$icon = $options['messageIcon'];
|
||
$noticeType = 'messages';
|
||
}
|
||
|
||
if($notice->flags & Notice::debug) {
|
||
$class .= " " . $options['debugClass'];
|
||
$icon = $options['debugIcon'];
|
||
// ensure non-debug version is set as well
|
||
if(!isset($noticesArray[$noticeType])) $noticesArray[$noticeType] = array();
|
||
$noticeType .= "-debug";
|
||
}
|
||
|
||
// indicate which class the notice originated from in debug mode
|
||
if($notice->class && $config->debug) $text = "{$notice->class}: $text";
|
||
|
||
$replacements = array(
|
||
'{class}' => $class,
|
||
'{remove}' => '',
|
||
'{icon}' => $this->renderNavIcon($notice->icon ? $notice->icon : $icon),
|
||
'{text}' => $text,
|
||
);
|
||
|
||
if($groupByType) {
|
||
if(!isset($noticesArray[$noticeType])) $noticesArray[$noticeType] = array();
|
||
$noticesArray[$noticeType][] = $replacements;
|
||
} else {
|
||
if($n === 0) $replacements['{remove}'] = $removeLink;
|
||
$out .= str_replace(array_keys($replacements), array_values($replacements), $options['itemMarkup']);
|
||
}
|
||
}
|
||
|
||
if($options['groupByType']) {
|
||
$cnt = 0;
|
||
foreach($noticesArray as $noticeType => $noticeReplacements) {
|
||
if(strpos($noticeType, '-debug')) continue;
|
||
if(isset($noticesArray["$noticeType-debug"])) {
|
||
$noticeReplacements = array_merge($noticeReplacements, $noticesArray["$noticeType-debug"]);
|
||
}
|
||
$n = count($noticeReplacements);
|
||
if($n > 1) {
|
||
$notice =& $noticeReplacements[0];
|
||
$label = str_replace(array('{n}', '{n-1}'), array($n, $n-1), $options['groupToggleLabel']);
|
||
$notice['{text}'] .= ' ' . str_replace(array('{label}'), array($label), $options['groupToggleMarkup']);
|
||
$notice['{class}'] .= ' ' . $options['groupParentClass'];
|
||
$childClass = $options['groupChildClass'];
|
||
} else {
|
||
$childClass = '';
|
||
}
|
||
foreach($noticeReplacements as $i => $replacements) {
|
||
if(!$cnt) $replacements['{remove}'] = $removeLink;
|
||
if($childClass && $i > 0) $replacements['{class}'] .= ' ' . $childClass;
|
||
$out .= str_replace(array_keys($replacements), array_values($replacements), $options['itemMarkup']);
|
||
$cnt++;
|
||
}
|
||
}
|
||
}
|
||
|
||
$out = str_replace('{out}', $out, $options['listMarkup']);
|
||
$out .= $this->renderExtraMarkup('notices');
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render markup for a font-awesome icon
|
||
*
|
||
* @param string $icon Name of icon to render, excluding the “fa-” prefix
|
||
* @param bool $fw Specify true to make fixed width (default=false).
|
||
* @return string
|
||
*
|
||
*/
|
||
public function renderIcon($icon, $fw = false) {
|
||
if($fw) $icon .= ' fa-fw';
|
||
return "<i class='fa fa-$icon'></i>";
|
||
}
|
||
|
||
/**
|
||
* Render markup for a font-awesome icon that precedes a navigation label
|
||
*
|
||
* This is the same as renderIcon() except that fixed-width is assumed and a "nav-nav-icon"
|
||
* class is added to it.
|
||
*
|
||
* @param string $icon Name of icon to render, excluding the “fa-” prefix
|
||
* @return string
|
||
*
|
||
*/
|
||
public function renderNavIcon($icon) {
|
||
return $this->renderIcon("$icon pw-nav-icon", true);
|
||
}
|
||
|
||
/**
|
||
* Render an extra markup region
|
||
*
|
||
* @param string $for
|
||
* @return mixed|string
|
||
*
|
||
*/
|
||
public function renderExtraMarkup($for) {
|
||
static $extras = array();
|
||
if(empty($extras)) $extras = $this->getExtraMarkup();
|
||
return isset($extras[$for]) ? $extras[$for] : '';
|
||
}
|
||
|
||
/**
|
||
* Render a admin theme template file
|
||
*
|
||
* This method is only used if it is hooked
|
||
*
|
||
* #pw-hooker
|
||
*
|
||
* @param string $file Full path and filename
|
||
* @param array $vars Associative array of variables to populate in rendered file
|
||
* @return string Returns blank string when $echo is true
|
||
* @since 3.0.196
|
||
*
|
||
*/
|
||
protected function ___renderFile($file, array $vars = array()) {
|
||
extract($vars);
|
||
ob_start();
|
||
include($file);
|
||
$out = ob_get_contents();
|
||
ob_end_clean();
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Include an admin theme file
|
||
*
|
||
* @param string $basename
|
||
* @param array $vars
|
||
* @since 3.0.196
|
||
*
|
||
*/
|
||
public function includeFile($basename, array $vars = array()) {
|
||
|
||
$file = '';
|
||
|
||
if($this->renderFileHooked === null) {
|
||
$this->renderFileHooked = $this->hasHook('renderFile()');
|
||
}
|
||
|
||
if($this->defaultTemplatePath === null) {
|
||
$this->defaultTemplatePath = $this->wire()->config->paths($this);
|
||
}
|
||
|
||
if($this->customTemplatePath === null) {
|
||
$path = $this->wire()->config->paths->templates . $this->className() . '/';
|
||
$this->customTemplatePath = is_dir($path) ? $path : '';
|
||
}
|
||
|
||
if($this->customTemplatePath) {
|
||
$file = $this->customTemplatePath . $basename;
|
||
if(!file_exists($file)) $file = '';
|
||
}
|
||
|
||
if($file === '') $file = $this->defaultTemplatePath . $basename;
|
||
|
||
$fuel = $this->wire()->fuel;
|
||
$vars['fuel'] = $fuel;
|
||
$vars = array_merge($fuel->getArray(), $vars);
|
||
unset($vars['file'], $vars['vars']); // just in case either was set
|
||
|
||
if($this->renderFileHooked) {
|
||
echo $this->renderFile($file, $vars);
|
||
} else {
|
||
extract($vars);
|
||
include($file);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set custom path for admin theme templates
|
||
*
|
||
* This is for modules to optionally set a custom template path. If not set then the default
|
||
* in /site/templates/AdminTheme[Class]/ is used.
|
||
*
|
||
* @param string $path
|
||
* @since 3.0.196
|
||
*
|
||
*/
|
||
public function setCustomTemplatePath($path) {
|
||
$this->customTemplatePath = $path;
|
||
}
|
||
|
||
/**
|
||
* Module Configuration
|
||
*
|
||
* @param InputfieldWrapper $inputfields
|
||
*
|
||
*/
|
||
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
|
||
$modules = $this->wire()->modules;
|
||
$input = $this->wire()->input;
|
||
$roles = $this->wire()->roles;
|
||
|
||
/** @var InputfieldCheckbox $f */
|
||
$f = $modules->get('InputfieldCheckbox');
|
||
$f->name = 'useAsLogin';
|
||
$f->label = $this->_('Use this admin theme for login screen?');
|
||
$f->description = $this->_('When checked, this admin theme will be used on the user login screen.');
|
||
$f->icon = 'sign-in';
|
||
$f->columnWidth = 50;
|
||
if($this->get('useAsLogin')) $f->attr('checked', 'checked');
|
||
$inputfields->add($f);
|
||
|
||
if($f->attr('checked') && $input->requestMethod('GET')) {
|
||
$themes = $modules->findByPrefix('AdminTheme');
|
||
$class = $this->className();
|
||
foreach($themes as $name) {
|
||
if($name === $class) continue;
|
||
$cfg = $modules->getConfig($name);
|
||
if(!empty($cfg['useAsLogin'])) {
|
||
unset($cfg['useAsLogin']);
|
||
$modules->saveConfig($name, $cfg);
|
||
$this->message("Removed 'useAsLogin' setting from $name", Notice::debug);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @var InputfieldSelect $f */
|
||
$f = $modules->get('InputfieldSelect');
|
||
$f->name = '_setAdminThemeRoleId';
|
||
$f->label = $this->_('Change users to this admin theme');
|
||
$f->description = $this->_('Select user role to update matching users to this admin theme.');
|
||
$f->columnWidth = 50;
|
||
$f->icon = 'users';
|
||
foreach($roles as $role) {
|
||
if($role->name === 'guest') continue;
|
||
$f->addOption($role->id, sprintf($this->_('Role: %s'), $role->name));
|
||
}
|
||
$inputfields->add($f);
|
||
|
||
$roleId = (int) $input->post('_setAdminThemeRoleId');
|
||
$role = $roleId ? $roles->get($roleId) : null;
|
||
if($role) {
|
||
$n = $this->wire()->users->setAdminThemeByRole($this, $role);
|
||
$this->message(sprintf($this->_('Set %d user(s) to have this admin theme'), $n), Notice::noGroup);
|
||
}
|
||
}
|
||
|
||
}
|