From 3522b8941470b9415b4574a937e442100ffb7c2a Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 27 May 2021 13:17:56 -0400 Subject: [PATCH] PR #189 - Update AdminThemeUikit to support custom LESS files. Authored by @BernhardBaumrock with some additions by @ryancramerdesign Co-authored-by: BernhardBaumrock --- .../AdminThemeUikit/AdminThemeUikit.module | 45 ++-- .../AdminThemeUikit/AdminThemeUikitCss.php | 241 ++++++++++++++++++ .../AdminTheme/AdminThemeUikit/_masthead.php | 2 +- .../AdminTheme/AdminThemeUikit/_offcanvas.php | 2 +- .../AdminTheme/AdminThemeUikit/config.php | 7 +- .../AdminThemeUikit/install-head.inc | 4 +- .../AdminTheme/AdminThemeUikit/interfaces.php | 50 ++++ 7 files changed, 330 insertions(+), 21 deletions(-) create mode 100644 wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikitCss.php create mode 100644 wire/modules/AdminTheme/AdminThemeUikit/interfaces.php diff --git a/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikit.module b/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikit.module index 6ef06712..ff723164 100644 --- a/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikit.module +++ b/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikit.module @@ -20,6 +20,7 @@ * @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. * * @method string getUikitCSS() * @@ -30,10 +31,9 @@ class AdminThemeUikit extends AdminThemeFramework implements Module, Configurabl public static function getModuleInfo() { return array( 'title' => 'Uikit', - 'version' => 31, + 'version' => 32, 'summary' => 'Uikit v3 admin theme', 'autoload' => 'template=admin', - 'requires' => 'ProcessWire>=3.0.100' ); } @@ -45,11 +45,17 @@ class AdminThemeUikit extends AdminThemeFramework implements Module, Configurabl */ const dev = false; + /** + * Set to true when upgrading Uikit version + * + */ + const upgrade = false; + /** * Default logo image file (relative to this dir) * */ - const logo = 'uikit/custom/images/pw-mark.png'; + const logo = 'uikit-pw/images/pw-mark.png'; /** * sidenavType: primary navigation on left sidebar @@ -86,6 +92,7 @@ class AdminThemeUikit extends AdminThemeFramework implements Module, Configurabl $this->set('groupNotices', true); $this->set('inputSize', 'm'); // m=medium (default), s=small, l=large $this->set('ukGrid', false); + $this->set('configPhpHash', ''); $this->setClasses(array( 'input' => 'uk-input', 'input-small' => 'uk-input uk-form-small', @@ -104,7 +111,7 @@ class AdminThemeUikit extends AdminThemeFramework implements Module, Configurabl public function wired() { parent::wired(); - $this->addHookAfter('InputfieldSelector::ajaxReady', $this, 'hookInputfieldSelectorAjax'); + $this->addHookAfter('InputfieldSelector::ajaxReady', $this, 'hookInputfieldSelectorAjax'); } /** @@ -1065,25 +1072,31 @@ class AdminThemeUikit extends AdminThemeFramework implements Module, Configurabl } /** - * Get the primary Uikit CSS file to use + * 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'); - $moduleInfo = self::getModuleInfo(); - $version = $moduleInfo['version']; - if($cssURL) { - if(strpos($cssURL, '//') === false) $cssURL = $config->urls->root . ltrim($cssURL, '/'); - return $this->wire('sanitizer')->entities($cssURL); - } else if(self::dev && strpos(__FILE__, '/wire/modules/') === false) { - return $this->url() . 'uikit/custom/pw.css?v=' . $version; - } else { - return $this->url() . 'uikit/dist/css/uikit.pw.min.css?v=' . $version; + + $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(); + if(self::upgrade) $settings['upgrade'] = true; + + $css = new AdminThemeUikitCss($this, $settings); + + return $css->getCssFile(); } /** diff --git a/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikitCss.php b/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikitCss.php new file mode 100644 index 00000000..c6b87a18 --- /dev/null +++ b/wire/modules/AdminTheme/AdminThemeUikit/AdminThemeUikitCss.php @@ -0,0 +1,241 @@ +AdminThemeUikit settings. + * @property string $configPhpName Name of property in $config that holds custom settings (default='AdminThemeUikit'). + * + * Settings that may be specified in $config->AdminThemeUikit array: + * + * @property string $style Configured style name to use, one of blank (for default), 'reno' or 'rock'. + * @property bool $recompile Recompile all LESS to CSS now? (set to true for 1 request only) + * @property bool $compress Compress compiled CSS? (default=true) + * @property array $customLessFiles Custom .less file(s) to include, relative to PW root. + * @property string $customCssFile Custom target .css file to compile custom .less file(s) to, relative to PW root. + * + * @since 3.0.179 + * + */ +class AdminThemeUikitCss extends WireData { + + /** + * @var AdminTheme + * + */ + protected $adminTheme; + + /** + * Construct + * + * @param AdminTheme $adminTheme + * @param array $options + * + */ + public function __construct(AdminTheme $adminTheme, array $options = array()) { + $this->adminTheme = $adminTheme; + $adminTheme->wire($this); + $this->setArray(array_merge($this->getDefaults(), $options)); + parent::__construct(); + } + + /** + * @return array + * + */ + public function getDefaults() { + return array( + 'baseStyles' => array('reno', 'rock'), + 'defaultStyle' => 'reno', + 'defaultCssFile' => 'uikit-pw/pw.min.css', + 'styleDir' => 'uikit-pw/styles/', + 'style' => '', + 'upgrade' => false, + 'recompile' => false, + 'compress' => true, + 'customLessFiles' => array('/site/templates/admin.less'), + 'customCssFile' => '/site/assets/admin.css', + 'configPhpName' => $this->adminTheme->className(), + 'configPhpHash' => $this->adminTheme->get('configPhpHash'), + ); + } + + /** + * Get the primary Uikit CSS file URL to use (whether default or custom) + * + * @param bool $getPath Get disk path rather than URL? + * @return string + * + */ + public function getCssFile($getPath = false) { + + $modules = $this->wire()->modules; + + if(!$modules->isInstalled('Less')) return $this->getDefaultCssFile($getPath); + + if($this->upgrade) { + $cssFile = $this->getDefaultCssFile(true); + $cssTime = filemtime($cssFile); + $lessFiles = array(); + $recompile = true; + } else { + $lessFiles = array(); + $lessTime = 0; + + foreach($this->customLessFiles as $file) { + $file = $this->customFile($file, 'less'); + if(!$file || !is_file($file)) continue; + $lessFiles[] = $file; + $mtime = filemtime($file); + if($mtime > $lessTime) $lessTime = $mtime; + } + + if(!count($lessFiles)) return $this->getDefaultCssFile($getPath); + + $cssFile = $this->customFile($this->customCssFile, 'css'); + if(!$cssFile) return $this->getDefaultCssFile(); + $cssTime = is_file($cssFile) ? (int) filemtime($cssFile) : 0; + $recompile = $lessTime > $cssTime || $this->configPhpSettingsChanged(); + } + + if($recompile) try { + /** @var AdminThemeUikitLessInterface $less */ + $less = $modules->get('Less'); + $less->setOption('compress', $this->compress); + $less->addFile($this->getAdminLessFile()); + $less->addFiles($lessFiles); + if(!$less->saveCss($cssFile)) throw new WireException("Compile error: $cssFile"); + $this->message(sprintf($this->_('Compiled: %s'), $cssFile), Notice::noGroup); + $cssTime = filemtime($cssFile); + } catch(\Exception $e) { + $this->error('LESS - ' . $e->getMessage(), Notice::noGroup); + } + + return $getPath ? $cssFile : $this->fileToUrl($cssFile) . "?v=$cssTime"; + } + + /** + * Get URL for given full path/file + * + * @param string $file + * @return string + * + */ + protected function fileToUrl($file) { + $config = $this->wire()->config; + return $config->urls->root . substr($file, strlen($config->paths->root)); + } + + /** + * Get default Uikit CSS file URL or disk path + * + * @param bool $getPath + * @return string + * + */ + public function getDefaultCssFile($getPath = false) { + $config = $this->wire()->config; + $file = $this->defaultCssFile; + $path = $config->paths($this->adminTheme) . $file; + if($getPath) return $path; + $v = filemtime($path); + $url = $config->urls($this->adminTheme) . "$file?v=$v" ; + return $url; + } + + /** + * Have the $config->AdminThemeUikit settings changed? + * + * @return bool + * @throws WireException + * + */ + public function configPhpSettingsChanged() { + $settings = $this->wire()->config->get($this->configPhpName); + unset($settings['recompile']); // recompile is runtime only setting + $hashNow = md5(print_r($settings, true)); + $hashThen = $this->get('configPhpHash'); + if($hashNow === $hashThen) return false; + $this->wire()->modules->saveConfig($this->adminTheme, 'configPhpHash', $hashNow); + return true; + } + + /** + * Apply custom file/path replacements + * + * @param string $file + * @param string $requireExtension Extension to require on given file + * @return string + * + */ + protected function customFile($file, $requireExtension = '') { + + $paths = $this->wire()->config->paths; + $file = $this->wire()->files->unixFileName($file); + + $replacements = array( + '/site/assets/' => $paths->assets, + '/site/templates/' => $paths->templates, + '/site/modules/' => $paths->siteModules, + ); + + if($requireExtension) { + $ext = pathinfo($file, PATHINFO_EXTENSION); + if(strtolower($ext) !== strtolower($requireExtension)) return ''; + } + + foreach($replacements as $find => $replace) { + if(strpos($file, $find) === 0) $file = str_replace($find, $replace, $file); + } + + if($file && strpos($file, $paths->root) !== 0) { + $file = $paths->root . ltrim($file, '/'); + } + + return $file; + } + + /** + * Get admin base less file to use + * + * @return string + * + */ + public function getAdminLessFile() { + + $config = $this->wire()->config; + $files = $this->wire()->files; + $path = $config->paths($this->adminTheme) . $this->styleDir; + $defaultFile = $path . $this->defaultStyle . '.less'; + $baseStyle = $this->upgrade ? $this->defaultStyle : $this->style; + + if(empty($baseStyle) || $baseStyle === $this->defaultStyle) return $defaultFile; + + if(stripos($baseStyle, '.')) { + // style is file name relative to installation root path + $file = $this->customFile($baseStyle, 'less'); + if($file && is_file($file) && $files->allowPath($file, $config->paths->root)) return $file; + } + + $file = $path . basename($baseStyle) . '.less'; + if(in_array($baseStyle, $this->baseStyles) || is_file($file)) return $file; + + $this->warning( + "config.{$this->configPhpName}[style]: " . + sprintf($this->_('Admin base style - file not found: %s'), $file), + Notice::debug + ); + + return $defaultFile; + } + +} \ No newline at end of file diff --git a/wire/modules/AdminTheme/AdminThemeUikit/_masthead.php b/wire/modules/AdminTheme/AdminThemeUikit/_masthead.php index aa54770a..d70c49b0 100644 --- a/wire/modules/AdminTheme/AdminThemeUikit/_masthead.php +++ b/wire/modules/AdminTheme/AdminThemeUikit/_masthead.php @@ -20,7 +20,7 @@ $logoOptions = array('height' => '40px'); -
+