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 module’s 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'; if(!wireInstanceOf($this->wire()->process, array('ProcessPageLister', 'ProcessUser'))) { $classes[] = '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 = "
$event->return
"; } } /** * 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