praiadeseselle/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikit.module
2022-03-08 15:55:41 +01:00

1159 lines
34 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php namespace ProcessWire;
/**
* AdminThemeUikit
*
* @property bool $isSuperuser Is current user a superuser?
* @property bool $isEditor Does current user have page-edit permission?
* @property bool $isLoggedIn Is current user logged in?
* @property bool $useOffset Use offset/margin for all Inputfields?
* @property array $noBorderTypes Inputfield class names that should always use the noBorder option (when 100% width).
* @property array $cardTypes Inputfield class names that should always use the card option.
* @property array $offsetTypes Inputfield class names that should always use the offset option.
* @property string $logoURL URL to custom logo, relative to PW installation root.
* @property string $cssURL URL to custom CSS file, relative to PW installation root.
* @property string $layout Layout type (blank=default, sidenav=multi-pane, sidenav-tree=left-tree, sidenav-tree-alt=right-tree)
* @property int $logoAction Logo click action (0=admin root page list, 1=offcanvas nav)
* @property string $userLabel Text containing user {vars} to use for user label in masthead (default="{Name}")
* @property int $maxWidth Maximum layout width in pixels, or 0 for no max (default=1600).
* @property bool|int $groupNotices Whether or not notices should be grouped by type
* @property string $inputSize Size for input/select elements. One of "s" for small, "m" for medium (default), or "l" for large.
* @property bool|int $ukGrid When true, use uk-width classes for Inputfields (rather than CSS percentages).
* @property int $toggleBehavior (0=Standard, 1=Consistent)
* @property string $configPhpHash Hash used internally to detect changes to $config->AdminThemeUikit settings.
* @property int $cssVersion Current version number of core CSS/LESS files
*
* @method string renderBreadcrumbs()
* @method string getUikitCSS()
*
*
*/
class AdminThemeUikit extends AdminThemeFramework implements Module, ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => 'Uikit',
'version' => 33,
'summary' => 'Uikit v3 admin theme',
'autoload' => 'template=admin',
);
}
/**
* Development mode, to be used when developing this modules code
*
* Makes it use runtime/temporary compiled CSS files rather than the final ones.
*
*/
const dev = false;
/**
* Set to true when upgrading Uikit version
*
*/
const upgrade = false;
/**
* Required CSS/LESS files version
*
* Increment on core less file changes that will also require a recompile of /site/assets/admin.css
*
*/
const requireCssVersion = 1;
/**
* Default logo image file (relative to this dir)
*
*/
const logo = 'uikit-pw/images/pw-mark.png';
/**
* sidenavType: primary navigation on left sidebar
*
*/
const sidenavTypePrimary = 0;
/**
* sidenavType: tree navigation on left sidebar
*
*/
const sidenavTypeTree = 1;
/**
* Construct and establish default module config settings
*
*/
public function __construct() {
parent::__construct();
$this->set('useOffset', false);
$this->set('cardTypes', array());
$this->set('offsetTypes', array());
$this->set('logoURL', '');
$this->set('cssURL', '');
$this->set('layout', '');
$this->set('noBorderTypes', array()); // 'InputfieldCKEditor' is a good one for this
$this->set('logoAction', 0);
$this->set('toggleBehavior', 0);
$this->set('userLabel', '{Name}');
$this->set('userAvatar', 'icon.user-circle');
$this->set('maxWidth', 1600);
$this->set('groupNotices', true);
$this->set('inputSize', 'm'); // m=medium (default), s=small, l=large
$this->set('ukGrid', false);
$this->set('configPhpHash', '');
$this->set('cssVersion', 0);
$this->setClasses(array(
'input' => 'uk-input',
'input-small' => 'uk-input uk-form-small',
'input-checkbox' => 'uk-checkbox',
'input-radio' => 'uk-radio',
'input-password' => 'uk-input uk-form-width-medium',
'select' => 'uk-select',
'select-asm' => 'uk-select uk-form-small',
'select-small' => 'uk-select uk-form-small',
'textarea' => 'uk-textarea',
'table' => 'uk-table uk-table-divider uk-table-justify uk-table-small',
'dl' => 'uk-description-list uk-description-list-divider',
));
}
public function wired() {
parent::wired();
$this->addHookAfter('InputfieldSelector::ajaxReady', $this, 'hookInputfieldSelectorAjax');
}
/**
* Initialize and attach hooks
*
*/
public function init() {
parent::init();
// if this is not the current admin theme, exit now so no hooks are attached
if(!$this->isCurrent()) return;
/** @var Page $page */
$page = $this->wire('page');
/** @var Modules $modules */
$modules = $this->wire('modules');
/** @var Modules $modules */
$session = $this->wire('session');
$sidenav = strpos($this->layout, 'sidenav') === 0;
// disable sidebar layout if SystemNotifications is active
if($sidenav && $modules->isInstalled('SystemNotifications')) {
/** @var SystemNotifications $systemNotifications */
$systemNotifications = $modules->get('SystemNotifications');
if(!$systemNotifications->disabled) {
$this->layout = '';
$sidenav = false;
}
}
if(!$page || $page->template != 'admin') {
// front-end
if($sidenav) {
// ensure that page edit links on front-end load the sidenav-init
$session->setFor('Page', 'appendEditUrl', "&layout=sidenav-init");
}
return;
}
$inputSize = $this->get('inputSize');
if($inputSize && $inputSize != 'm') {
$inputClass = $inputSize === 'l' ? 'uk-form-large' : 'uk-form-small';
foreach(array('input', 'select', 'textarea') as $name) {
$this->addClass($name, $inputClass);
}
}
if(!$this->ukGrid) {
$this->addClass('body', 'AdminThemeUikitNoGrid');
}
if($this->className() !== 'AdminThemeUikit') {
$this->addBodyClass('AdminThemeUikit');
}
$session->removeFor('Page', 'appendEditUrl');
/** @var JqueryUI $jqueryUI */
$jqueryUI = $modules->get('JqueryUI');
$jqueryUI->use('panel');
// add rendering hooks
$this->addHookBefore('Inputfield::render', $this, 'hookBeforeRenderInputfield');
$this->addHookBefore('Inputfield::renderValue', $this, 'hookBeforeRenderInputfield');
$this->addHookAfter('Inputfield::getConfigInputfields', $this, 'hookAfterInputfieldGetConfigInputfields');
$this->addHookAfter('Inputfield::getConfigAllowContext', $this, 'hookAfterInputfieldGetConfigAllowContext');
$this->addHookAfter('MarkupAdminDataTable::render', $this, 'hookAfterTableRender');
// hooks and settings specific to sidebar layouts
if($sidenav) {
$this->addHookAfter('ProcessLogin::afterLoginURL', $this, 'hookAfterLoginURL');
if(strpos($this->layout, 'sidenav-tree') === 0) {
// page-edit breadcrumbs go to page editor when page tree is always in sidebar
$this->wire('config')->pageEdit('editCrumbs', true);
}
}
// add cache clearing hooks
$this->wire('pages')->addHookAfter('saved', $this, 'hookClearCaches');
$modules->addHookAfter('refresh', $this, 'hookClearCaches');
}
/**
* Render an extra markup region
*
* @param string $for
* @return mixed|string
*
*/
public function renderExtraMarkup($for) {
$out = parent::renderExtraMarkup($for);
if($for === 'notices') {
}
return $out;
}
/**
* Test all notice types
*
* @return bool
*
*/
public function testNotices() {
if(parent::testNotices()) {
$v = $this->wire('input')->get('test_notices');
if($v === 'group-off') $this->groupNotices = false;
if($v === 'group-on') $this->groupNotices = true;
return true;
}
return false;
}
/**
* Get Uikit uk-width-* class for given column width
*
* @param int $columnWidth
* @param array $widths
* @return string
*
*/
protected function getUkWidthClass($columnWidth, array $widths) {
static $minColumnWidth = null;
$ukWidthClass = '1-1';
if($minColumnWidth === null) {
$widthKeys = array_keys($widths);
sort($widthKeys, SORT_NATURAL);
$minColumnWidth = (int) reset($widthKeys);
}
if($columnWidth < 10) {
// use uk-width-1-1
} else if($columnWidth && $columnWidth < 100) {
if($columnWidth < $minColumnWidth) $columnWidth = $minColumnWidth;
// determine column width class
foreach($widths as $pct => $uk) {
$pct = (int) $pct;
if($columnWidth >= $pct) {
$ukWidthClass = $uk;
break;
}
}
}
if($ukWidthClass === '1-1') {
return "uk-width-1-1";
} else {
return "uk-width-$ukWidthClass@m";
}
}
/*******************************************************************************************
* HOOKS
*
*/
/**
* Hook called before each Inputfield::render
*
* This updates the Inputfield classes and settings for Uikit.
*
* - themeBorder: none, hide, card, line
* - themeOffset: s, m, l
* - themeInputSize: s, m, l
* - themeInputWidth: xs, s, m, l, f
* - themeColor: primary, secondary, warning, danger, success, highlight, none
* - themeBlank: no input borders, true or false
*
* @param HookEvent $event
*
*/
public function hookBeforeRenderInputfield(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->object;
$class = $inputfield->className();
$formSettings = $event->wire('config')->get('InputfieldForm');
$widths = $formSettings['ukGridWidths'];
$columnWidth = (int) $inputfield->getSetting('columnWidth');
$field = $inputfield->hasField;
$isFieldset = $inputfield instanceof InputfieldFieldset;
$isMarkup = $inputfield instanceof InputfieldMarkup;
$isWrapper = $inputfield instanceof InputfieldWrapper && !$isFieldset && !$isMarkup;
$ukWidthClass = 'uk-width-1-1';
$globalInputSize = $this->get('inputSize');
$ukGrid = $this->get('ukGrid');
$themeColor = '';
$themeBorder = '';
$themeOffset = '';
$themeInputSize = '';
$themeInputWidth = '';
$themeBlank = false;
$wrapClasses = array();
$inputClasses = array();
$removeInputClasses = array();
if($inputfield instanceof InputfieldForm) {
if($globalInputSize == 's') {
$inputfield->addClass('InputfieldFormSmallInputs');
} else if($globalInputSize == 'l') {
$inputfield->addClass('InputfieldFormLargeInputs');
}
return;
} else if($inputfield instanceof InputfieldSubmit) {
// button
$inputfield->addClass('uk-width-auto uk-margin-top', 'wrapClass');
return; // no further settings needed for button
}
if($ukGrid) {
$ukWidthClass = $this->getUkWidthClass($columnWidth, $widths);
if($ukWidthClass) $wrapClasses[] = $ukWidthClass;
}
if($isWrapper) {
if($ukWidthClass != 'uk-width-1-1') $inputfield->addClass($ukWidthClass, 'wrapClass');
return;
} else if($inputfield instanceof InputfieldTextarea) {
$inputClasses[] = $this->getClass('textarea');
} else if($inputfield instanceof InputfieldPassword) {
$inputClasses[] = $this->getClass('input-password');
} else if($inputfield instanceof InputfieldText) {
$inputClasses[] = $this->getClass('input');
} else if($inputfield instanceof InputfieldInteger) {
$inputClasses[] = $this->getClass('input');
} else if($inputfield instanceof InputfieldDatetime) {
$inputClasses[] = $this->getClass('input');
} else if($inputfield instanceof InputfieldCheckboxes || $inputfield instanceof InputfieldCheckbox) {
$inputClasses[] = $this->getClass('input-checkbox');
$inputfield->addClass('uk-form-controls-text', 'contentClass');
} else if($inputfield instanceof InputfieldRadios) {
$inputClasses[] = $this->getClass('input-radio');
$inputfield->addClass('uk-form-controls-text', 'contentClass');
} else if($inputfield instanceof InputfieldAsmSelect) {
$inputClasses[] = $this->getClass('select-asm');
} else if($inputfield instanceof InputfieldSelect && !$inputfield instanceof InputfieldHasArrayValue) {
$inputClasses[] = $this->getClass('select');
} else if($inputfield instanceof InputfieldFile) {
$themeColor = 'secondary';
}
if($field) {
// pull optional uikit settings from Field object
$themeBorder = $field->get('themeBorder');
$themeOffset = $field->get('themeOffset');
$themeInputSize = $field->get('themeInputSize');
$themeInputWidth = $field->get('themeInputWidth');
$themeColor = $field->get('themeColor') ? $field->get('themeColor') : $themeColor;
$themeBlank = $field->get('themeBlank');
}
// determine custom settings which may be defined with Inputfield
if(!$themeBorder) $themeBorder = $inputfield->getSetting('themeBorder');
if(!$themeOffset) $themeOffset = $inputfield->getSetting('themeOffset'); // || in_array($class, $this->offsetTypes);
if(!$themeColor) $themeColor = $inputfield->getSetting('themeColor');
if(!$themeInputSize) $themeInputSize = $inputfield->getSetting('themeInputSize');
if(!$themeInputWidth) $themeInputWidth = $inputfield->getSetting('themeInputWidth');
if(!$themeBlank) $themeBlank = $inputfield->getSetting('themeBlank');
if(!$themeBorder) {
if($formSettings['useBorders'] === false || in_array($class, $this->noBorderTypes)) {
$themeBorder = (!$columnWidth || $columnWidth == 100) ? 'none' : 'hide';
} else if(in_array($class, $this->cardTypes)) {
$themeBorder = 'card';
} else {
$themeBorder = 'line';
}
}
if($themeInputSize && $globalInputSize != $themeInputSize) {
if($globalInputSize === 's') {
$removeInputClasses[] = 'uk-form-small';
} else if($globalInputSize === 'l') {
$removeInputClasses[] = 'uk-form-large';
}
if($themeInputSize === 'm') {
$inputClasses[] = 'uk-form-medium';
} else if($themeInputSize === 's') {
$inputClasses[] = 'uk-form-small';
} else if($themeInputSize === 'l') {
$inputClasses[] = 'uk-form-large';
}
}
if($themeInputWidth) {
$inputWidthClasses = array(
'xs' => 'uk-form-width-xsmall',
's' => 'uk-form-width-small',
'm' => 'uk-form-width-medium',
'l' => 'uk-form-width-large',
'f' => 'InputfieldMaxWidth',
);
$inputfield->removeClass($inputWidthClasses);
if(isset($inputWidthClasses[$themeInputWidth])) {
$inputClasses[] = $inputWidthClasses[$themeInputWidth];
if($themeInputWidth != 'f') $inputClasses[] = 'InputfieldSetWidth';
}
}
if($themeBlank) {
$inputClasses[] = 'uk-form-blank';
}
if($themeColor) {
$wrapClasses[] = 'InputfieldIsColor';
}
switch($themeColor) {
case 'primary': $wrapClasses[] = 'InputfieldIsPrimary'; break;
case 'secondary': $wrapClasses[] = 'InputfieldIsSecondary'; break;
case 'warning': $wrapClasses[] = 'InputfieldIsWarning'; break;
case 'danger': $wrapClasses[] = 'InputfieldIsError'; break;
case 'success': $wrapClasses[] = 'InputfieldIsSuccess'; break;
case 'highlight': $wrapClasses[] = 'InputfieldIsHighlight'; break;
case 'none': break;
}
switch($themeBorder) {
case 'none': $wrapClasses[] = 'InputfieldNoBorder'; break;
case 'hide': $wrapClasses[] = 'InputfieldHideBorder'; break;
case 'card': $wrapClasses[] = 'uk-card uk-card-default'; break;
}
if($themeOffset && $themeOffset !== 'none') {
$wrapClasses[] = 'InputfieldIsOffset';
if($themeOffset === 's') {
$wrapClasses[] = 'InputfieldIsOffsetSm';
} else if($themeOffset === 'l') {
$wrapClasses[] = 'InputfieldIsOffsetLg';
}
}
if(count($inputClasses)) {
$inputfield->addClass(implode(' ', $inputClasses));
}
if(count($removeInputClasses)) {
$inputfield->removeClass($removeInputClasses);
}
if(count($wrapClasses)) {
$inputfield->addClass(implode(' ', $wrapClasses), 'wrapClass');
}
}
/**
* Hook after Inputfield::getConfigInputfields() to add theme-specific configuration settings
*
* @param HookEvent $event
*
*/
public function hookAfterInputfieldGetConfigInputfields(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->object;
if($inputfield instanceof InputfieldWrapper) return;
/** @var InputfieldWrapper $inputfields */
$inputfields = $event->return;
if(!$inputfields instanceof InputfieldWrapper) return;
include_once(dirname(__FILE__) . '/config.php');
$configHelper = new AdminThemeUikitConfigHelper($this);
$configHelper->configInputfield($inputfield, $inputfields);
}
/**
* Get fields allowed for field/template context configuration
*
* @param HookEvent $event
*
*/
public function hookAfterInputfieldGetConfigAllowContext(HookEvent $event) {
$names = $event->return;
$names[] = '_adminTheme';
$names[] = 'themeOffset';
$names[] = 'themeBorder';
$names[] = 'themeColor';
$names[] = 'themeInputSize';
$names[] = 'themeInputWidth';
$names[] = 'themeBlank';
$event->return = $names;
}
/**
* Hook after MarkupAdminDataTable::render
*
* This is primarily to add support for Uikit horizontal scrolling responsive tables,
* which is used instead of the default MarkupAdminDataTable responsive table.
*
* @param HookEvent $event
*
*/
public function hookAfterTableRender(HookEvent $event) {
/** @var MarkupAdminDataTable $table */
$table = $event->object;
$classes = array();
if($table->responsive) $classes[] = 'pw-table-responsive uk-overflow-auto';
if($table->sortable) $classes[] = 'pw-table-sortable';
if($table->resizable) $classes[] = 'pw-table-resizable';
if(count($classes)) {
$class = implode(' ', $classes);
$event->return = "<div class='$class'>$event->return</div>";
}
}
/**
* Event called when a page is saved or modules refreshed to clear caches
*
* @param HookEvent $event
*
*/
public function hookClearCaches(HookEvent $event) {
/** @var Page|User|null $page */
$page = $event->arguments(0);
/** @var array $changes */
$changes = $event->arguments(1);
/** @var User $user */
$user = $this->wire('user');
if($page !== null && !($page instanceof Page)) return;
if(!is_array($changes)) $changes = array();
if($page === null || $page->template == 'admin' || ($page->id === $user->id && in_array('language', $changes))) {
/** @var Session $session */
$session = $this->wire('session');
$session->removeFor($this, 'prnav');
$session->removeFor($this, 'sidenav');
$session->message("Cleared the admin theme navigation cache (primary nav)", Notice::debug);
}
}
/**
* Hook to ProcessLogin::afterLoginURL()
*
* @param HookEvent $event
*
*/
public function hookAfterLoginURL(HookEvent $event) {
$layout = $this->layout;
if(!$layout) return;
$url = $event->return;
$url .= (strpos($url, '?') !== false ? '&' : '?') . "layout=$this->layout-init";
$event->return = $url;
}
/*******************************************************************************************
* MARKUP RENDERING METHODS
*
*/
/**
* Render a list of breadcrumbs (list items), excluding the containing <ul>
*
* @return string
*
*/
public function ___renderBreadcrumbs() {
if(!$this->isLoggedIn || $this->isModal) return '';
$process = $this->wire('page')->process;
if($process == 'ProcessPageList') return '';
$breadcrumbs = $this->wire('breadcrumbs');
$out = '';
// don't show breadcrumbs if only one of them (subjective)
if(count($breadcrumbs) < 2 && $process != 'ProcessPageEdit') return '';
if(strpos($this->layout, 'sidenav') === false) {
$out = "<li>" . $this->renderQuickTreeLink() . "</li>";
}
foreach($breadcrumbs as $breadcrumb) {
$title = $breadcrumb->get('titleMarkup');
if(!$title) $title = $this->wire('sanitizer')->entities1($this->_($breadcrumb->title));
$out .= "<li><a href='$breadcrumb->url'>$title</a></li>";
}
if($out) $out = "<ul class='uk-breadcrumb'>$out</ul>";
return $out;
}
/**
* Render the populated “Add New” head button, or blank when not applicable
*
* @return string
*
*/
public function renderAddNewButton() {
$items = array();
foreach($this->getAddNewActions() as $item) {
$icon = $this->renderNavIcon($item['icon']);
$items[] = "<li><a href='$item[url]'>$icon$item[label]</a></li>";
}
if(!count($items)) return '';
$out = implode('', $items);
$label = $this->getAddNewLabel();
$icon = $this->renderIcon('angle-down');
$out =
"<button class='ui-button pw-dropdown-toggle'>$label $icon</button>" .
"<ul class='pw-dropdown-menu' data-at='right bottom+1'>$out</ul>";
return $out;
}
/**
* Render runtime notices div#notices
*
* @param Notices|bool $notices
* @param array $options See defaults in method
* @return string|array
*
*/
public function renderNotices($notices, array $options = array()) {
$defaults = array(
'groupByType' => $this->groupNotices ? true : false,
'messageClass' => 'NoticeMessage uk-alert uk-alert-primary', // class for messages
'messageIcon' => 'check-square', // default icon to show with notices
'warningClass' => 'NoticeWarning uk-alert uk-alert-warning', // class for warnings
'warningIcon' => 'exclamation-circle', // icon for warnings
'errorClass' => 'NoticeError uk-alert uk-alert-danger', // class for errors
'errorIcon' => 'exclamation-triangle', // icon for errors
'debugClass' => 'NoticeDebug uk-alert', // 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}'>" .
"<div class='pw-container uk-container uk-container-expand'>{remove}{icon}{text}</div>" .
"</li>"
);
$options = array_merge($defaults, $options);
return parent::renderNotices($notices, $options);
}
/**
* Render a single top navigation item for the given page
*
* This function designed primarily to be called by the renderPrimaryNavItems() function.
*
* @param array $item
* @return string
*
*/
protected function renderPrimaryNavItem(array $item) {
$title = $item['title'];
$out = "<li class='page-$item[id]-'>";
if(!count($item['children'])) {
$out .= "<a href='$item[url]'>$title</a></li>";
return $out;
}
$out .=
"<a href='$item[url]' " .
"id='prnav-page-$item[id]' " .
"data-from='prnav-page-$item[parent_id]' " .
"class='pw-dropdown-toggle'>" .
"$title</a>";
$my = 'left-1 top';
if(in_array($item['name'], array('access', 'page', 'module'))) $my = 'left top';
$out .=
"<ul class='pw-dropdown-menu prnav' data-my='$my' data-at='left bottom'>" .
$this->renderPrimaryNavItemChildren($item['children']) .
"</ul>" .
"</li>";
return $out;
}
/**
* Renders <li> items navigation from given nav array
*
* @param array $items
* @return string
*
*/
protected function renderPrimaryNavItemChildren(array $items) {
$out = '';
foreach($items as $item) {
$icon = empty($item['icon']) ? '' : $this->renderNavIcon($item['icon']);
$title = $item['title'];
$out .= "<li class='page-$item[id]-'>";
if(!empty($item['children'])) {
$out .=
"<a class='pw-has-items' data-from='prnav-page-$item[parent_id]' href='$item[url]'>$icon$title</a>" .
"<ul>" . $this->renderPrimaryNavItemChildren($item['children']) . "</ul>";
} else if(!empty($item['navJSON'])) {
$item['navJSON'] = $this->wire('sanitizer')->entities($item['navJSON']);
$out .=
"<a class='pw-has-items pw-has-ajax-items' " .
"data-from='prnav-page-$item[parent_id]' " .
"data-json='$item[navJSON]' " .
"href='$item[url]'>$icon$title" .
"</a>" .
"<ul></ul>";
} else {
$out .= "<a href='$item[url]'>$icon$title</a>";
}
}
$out .= "</li>";
return $out;
}
/**
* Render all top navigation items, ready to populate in ul#prnav
*
* @return string
*
*/
public function renderPrimaryNavItems() {
$cache = self::dev ? '' : $this->wire('session')->getFor($this, 'prnav');
if($cache) {
$this->markCurrentNavItems($cache);
return $cache;
}
$out = '';
$items = $this->getPrimaryNavArray();
foreach($items as $item) {
$out .= $this->renderPrimaryNavItem($item);
}
if(!self::dev) $this->wire('session')->setFor($this, 'prnav', $out);
$this->markCurrentNavItems($out);
return $out;
}
/**
* Render sidebar navigation that uses uk-nav
*
* The contents is the same as the Primary nav, except that output is prepared for sidebar.
* This method uses a session-cached version. To clear the cache, logout then log back in.
*
* @return string
*
*/
public function renderSidebarNavItems() {
// see if we can get it from the cache
$out = self::dev ? '' : $this->wire('session')->getFor($this, 'sidenav');
if(empty($out)) {
$out = $this->renderSidebarNavCache();
$this->wire('session')->setFor($this, 'sidenav', $out);
}
$out = str_replace('<!--pw-user-nav-label-->', $this->renderUserNavLabel(), $out);
$this->markCurrentNavItems($out);
return $out;
}
/**
* Re-renders the sidebar nav to be cached
*
* @return string
*
*/
protected function renderSidebarNavCache() {
$out = '';
$items = $this->getPrimaryNavArray();
$ulAttrs = "class='uk-nav-sub uk-nav-default uk-nav-parent-icon' data-uk-nav='animation: false; multiple: true;'";
foreach($items as $item) {
$class = "page-$item[id]-";
$subnav = '';
foreach($item['children'] as $child) {
$icon = $child['icon'] ? $this->renderNavIcon($child['icon']) : '';
$childClass = "page-$child[id]-";
$childAttr = "";
$childNav = '';
if(count($child['children'])) {
$childClass .= ' uk-parent';
$childNavList = $this->renderPrimaryNavItemChildren($child['children']);
$childIcon = $this->renderNavIcon('arrow-circle-right');
$childNav =
"<ul $ulAttrs>" .
"<li class='pw-nav-dup'><a href='$child[url]'>$childIcon$child[title]</a></li>" .
$childNavList .
"</ul>";
} else if($child['navJSON']) {
$child['navJSON'] = $this->wire('sanitizer')->entities($child['navJSON']);
$childClass .= ' uk-parent';
$childAttr = " class='pw-has-items pw-has-ajax-items' data-json='$child[navJSON]'";
$childNav = "<ul $ulAttrs></ul>";
}
$subnav .= "<li class='$childClass'><a$childAttr href='$child[url]'>$icon$child[title]</a>";
$subnav .= $childNav . "</li>";
}
if($subnav) {
$icon = $this->renderNavIcon($item['icon']);
$class .= " uk-parent";
$subnav =
"<ul $ulAttrs>" .
"<li class='pw-nav-dup'><a href='$item[url]'>$icon$item[title]</a></li>" .
$subnav .
"</ul>";
}
$out .=
"<li class='$class'><a href='$item[url]' id='sidenav-page-$item[id]'>$item[title]</a>" .
$subnav .
"</li>";
}
// render user nav
$out .=
"<li class='uk-parent'>" .
"<a href='#'><!--pw-user-nav-label--></a>" .
"<ul $ulAttrs>" . $this->renderUserNavItems() . "</ul>" .
"</li>";
return $out;
}
/**
* Identify current items in the primary nav and add appropriate classes to them
*
* This presumes that navigation items in given $out markup use "page-[id]-" classes,
* which will be updated consistent with the current $page.
*
* @param $out
*
*/
protected function markCurrentNavItems(&$out) {
$page = $this->wire('page');
foreach($page->parents()->and($page) as $p) {
$out = str_replace("page-$p-", "page-$p- uk-active", $out);
}
}
/**
* Render label for user masthead dropdown nav item
*
* @return string
*
*/
public function renderUserNavLabel() {
/** @var User $user */
$user = $this->wire('user');
$userLabel = $this->get('userLabel');
$userAvatar = $this->get('userAvatar');
$defaultIcon = 'user-circle';
if(strpos($userLabel, '{') !== false) {
if(strpos($userLabel, '{Name}') !== false) {
$userLabel = str_replace('{Name}', ucfirst($user->name), $userLabel);
} else if(strpos($userLabel, '{name}') !== false) {
$userLabel = str_replace('{name}', $user->name, $userLabel);
}
if(strpos($userLabel, '{') !== false) {
$userLabel = $user->getText($userLabel, true, true);
}
} else {
$userLabel = $this->wire('sanitizer')->entities($userLabel);
}
if($userAvatar) {
if($userAvatar === 'gravatar') {
if($user->email) {
$url = "https://www.gravatar.com/avatar/" . md5(strtolower(trim($user->email))) . "?s=80&d=mm&r=g";
$userAvatar = "<img class='pw-avatar' src='$url' alt='$user->name' />&nbsp;";
} else {
$userAvatar = $this->renderNavIcon("$defaultIcon fa-lg");
}
} else if(strpos($userAvatar, 'icon.') === 0) {
list(,$icon) = explode('.', $userAvatar);
$userAvatar = $this->renderNavIcon("$icon fa-lg");
} else if(strpos($userAvatar, ':')) {
list($fieldID, $fieldName) = explode(':', $userAvatar);
$field = $this->wire('fields')->get($fieldName);
if(!$field || !$field->type instanceof FieldtypeImage) {
$field = $this->wire('fields')->get((int) $fieldID);
}
if($field && $field->type instanceof FieldtypeImage) {
$value = $user->get($field->name);
if($value instanceof Pageimages) $value = $value->first();
if($value instanceof Pageimage) {
$value = $value->size(60, 60);
$userAvatar = "<img class='pw-avatar' src='$value->url' alt='$user->name' />&nbsp;";
} else {
$userAvatar = $this->renderNavIcon("$defaultIcon fa-lg");
}
} else {
$userAvatar = '';
}
}
}
if($userAvatar) $userLabel = $userAvatar . $userLabel;
return $userLabel;
}
/**
* Render navigation for the “user” menu
*
* @return string
*
*/
public function renderUserNavItems() {
$items = $this->getUserNavArray();
$out = '';
foreach($items as $item) {
$label = $this->wire('sanitizer')->entities($item['title']);
$icon = isset($item['icon']) ? $this->renderNavIcon($item['icon']) : ' ';
$target = isset($item['target']) ? " target='$item[target]'" : '';
$out .= "<li><a$target href='$item[url]'>$icon$label</a></li>";
}
return $out;
}
/**
* Render link that opens the quick page-tree panel
*
* @param string $icon Icon to use for link (default=sitemap)
* @param string $text Optional text to accompany icon (default=empty)
* @return string
*
*/
public function renderQuickTreeLink($icon = 'tree', $text = '') {
$tree = $this->_('Tree');
$url = $this->wire('urls')->admin . 'page/';
return
"<a class='pw-panel' href='$url' data-tab-text='$tree' data-tab-icon='$icon' title='$tree'>" .
$this->renderNavIcon($icon) . $text .
"</a>";
}
/**
* Get the URL to the ProcessWire or brand logo (or <img> tag)
*
* @param array $options
* - `getURL` (bool): Return only the URL? (default=false)
* - `getNative` (bool): Return only the ProcessWire brand logo? (default=false)
* - `alt` (string): Alt attribute for <img> tag (default=auto)
* - `height` (string): Height style to use for SVG images (default='')
* @return string
*
*/
public function getLogo(array $options = array()) {
/** @var Config $config */
$config = $this->wire('config');
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
$defaults = array(
'getURL' => false,
'getNative' => false,
'alt' => '',
'height' => '',
);
$options = array_merge($defaults, $options);
$logoURL = $this->get('logoURL');
$logoQS = '';
$svg = false;
if(empty($logoURL) || $options['getNative'] || strpos($logoURL, '//') !== false) {
$native = true;
$logoURL = $this->url() . self::logo;
} else {
if(strpos($logoURL, '?')) list($logoURL, $logoQS) = explode('?', $logoURL, 2);
$logoURL = $config->urls->root . ltrim($logoURL, '/');
$logoURL = $sanitizer->entities($logoURL);
$native = false;
$svg = strtolower(pathinfo($logoURL, PATHINFO_EXTENSION)) === 'svg';
}
$alt = $options['alt'];
if(empty($alt) && $this->wire()->user->isLoggedin()) {
$alt = "ProcessWire $config->version";
}
$class = 'pw-logo ' . ($native ? 'pw-logo-native' : 'pw-logo-custom');
$attr = "class='$class' alt='$alt' ";
if($svg) {
if($options['height']) $attr .= "style='height:$options[height]' ";
if(strpos($logoQS, 'uk-svg') === 0) {
// if logo has "?uk-svg" query string, add uk-svg attribute which makes it styleable via CSS/LESS (PR#77)
$attr .= 'uk-svg ';
$logoQS = str_replace(array('uk-svg&', 'uk-svg'), '', $logoQS);
}
}
if($logoQS) $logoURL .= '?' . $sanitizer->entities($logoQS);
$img = "<img src='$logoURL' $attr/>";
return $options['getURL'] ? $logoURL : $img;
}
/**
* Get the URL to the ProcessWire or brand logo
*
* @return string
*
*/
public function getLogoURL() {
return $this->getLogo(array('getURL' => true));
}
/**
* Get URL to this admin theme
*
* @return string
* @since 3.0.171
*
*/
public function url() {
return $this->wire()->config->urls->modules . 'AdminTheme/AdminThemeUikit/';
}
/**
* Get disk path to this admin theme
*
* @return string
* @since 3.0.171
*
*/
public function path() {
return __DIR__ . '/';
}
/**
* Get the primary Uikit CSS URL to use
*
* @return string
* @since 3.0.178 Was not hookable in prior versions
*
*/
public function ___getUikitCSS() {
$config = $this->wire()->config;
$cssUrl = $this->get('cssURL');
if($cssUrl) { // a custom css URL was set in the theme config
if(strpos($cssUrl, '//') === false) $cssUrl = $config->urls->root . ltrim($cssUrl, '/');
return $this->wire()->sanitizer->entities($cssUrl);
}
require_once(__DIR__ . '/AdminThemeUikitCss.php');
$settings = $config->AdminThemeUikit;
if(!is_array($settings)) $settings = array();
$settings['requireCssVersion'] = self::requireCssVersion;
if(self::upgrade) {
$settings['upgrade'] = true;
$settings['replacements'] = array('../pw/images/' => 'images/');
} else {
if(empty($settings['customCssFile'])) $settings['customCssFile'] = '/site/assets/admin.css';
$path = 'wire/modules/AdminTheme/AdminThemeUikit/uikit-pw/images/';
$back = str_repeat('../', substr_count(trim($settings['customCssFile'], '/'), '/'));
$settings['replacements'] = array(
'url(../pw/images/' => "url($back$path",
'url("../pw/images/' => "url(\"$back$path",
'url(pw/images/' => "url($back$path",
'url("pw/images/' => "url(\"$back$path"
);
}
$css = new AdminThemeUikitCss($this, $settings);
return $css->getCssFile();
}
/**
* Get Javascript that must be present in the document <head>
*
* @return string
*
*/
public function getHeadJS() {
$config = $this->wire()->config;
$data = $config->js('adminTheme');
if(!is_array($data)) $data = array();
$data['logoAction'] = (int) $this->logoAction;
$data['toggleBehavior'] = (int) $this->toggleBehavior;
$config->js('adminTheme', $data);
return parent::getHeadJS();
}
/**
* Module configuration
*
* @param InputfieldWrapper $inputfields
*
*/
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
parent::getModuleConfigInputfields($inputfields);
include_once(__DIR__ . '/config.php');
$configHelper = new AdminThemeUikitConfigHelper($this);
$configHelper->configModule($inputfields);
}
}