diff --git a/_test/conf/tpl/dokuwiki/css/_tests.less b/_test/conf/tpl/dokuwiki/css/_tests.less new file mode 100644 index 000000000..f1839f03c --- /dev/null +++ b/_test/conf/tpl/dokuwiki/css/_tests.less @@ -0,0 +1 @@ +/* dummy file */ diff --git a/_test/conf/tpl/dokuwiki/style.ini b/_test/conf/tpl/dokuwiki/style.ini new file mode 100644 index 000000000..7eab6e1ec --- /dev/null +++ b/_test/conf/tpl/dokuwiki/style.ini @@ -0,0 +1,7 @@ +[stylesheets] +css/_tests.less = screen + +[replacements] +__background__ = "#f2ecec" +__custom_variable__ = "#5e4040" +__custom_variable_two__ = "url(test/foo.png)" diff --git a/_test/tests/inc/styleutils_cssstyleini.test.php b/_test/tests/inc/styleutils_cssstyleini.test.php new file mode 100644 index 000000000..bbb10c3c6 --- /dev/null +++ b/_test/tests/inc/styleutils_cssstyleini.test.php @@ -0,0 +1,38 @@ + + array ( + 'screen' => + array ( + DOKU_CONF . 'tpl/dokuwiki/css/_tests.less' => '/./', + DOKU_INC . 'lib/tpl/dokuwiki/css/content.less' => '/./lib/tpl/dokuwiki/', + ), + ), + 'replacements' => + array ( + '__text__' => '#333', + '__background__' => '#f2ecec', + '__custom_variable__' => '#5e4040', + '__custom_variable_two__' => 'url(' . DOKU_BASE . 'test/foo.png)', + ), + ); + + $actual = $util->cssStyleini(); + + // check that all stylesheet levels are present + $this->assertArrayHasKey('all', $actual['stylesheets']); + $this->assertArrayHasKey('print', $actual['stylesheets']); + + // check an original stylesheet and an additional one + $this->assertArraySubset($expected['stylesheets']['screen'], $actual['stylesheets']['screen']); + + // merged config has an original value (text), an overridden value (background) and a new custom replacement (custom_variable) + $this->assertArraySubset($expected['replacements'], $actual['replacements']); + } +} diff --git a/inc/Manifest.php b/inc/Manifest.php index 0df9c2b81..843c67265 100644 --- a/inc/Manifest.php +++ b/inc/Manifest.php @@ -29,7 +29,7 @@ class Manifest } $styleUtil = new \dokuwiki\StyleUtils(); - $styleIni = $styleUtil->cssStyleini($conf['template']); + $styleIni = $styleUtil->cssStyleini(); $replacements = $styleIni['replacements']; if (empty($manifest['background_color'])) { diff --git a/inc/StyleUtils.php b/inc/StyleUtils.php index e584942c0..ec5ad346f 100644 --- a/inc/StyleUtils.php +++ b/inc/StyleUtils.php @@ -4,6 +4,43 @@ namespace dokuwiki; class StyleUtils { + + /** @var string current template */ + protected $tpl; + /** @var bool reinitialize styles config */ + protected $reinit; + /** @var bool $preview preview mode */ + protected $preview; + /** @var array default replacements to be merged with custom style configs */ + protected $defaultReplacements = array( + '__text__' => "#000", + '__background__' => "#fff", + '__text_alt__' => "#999", + '__background_alt__' => "#eee", + '__text_neu__' => "#666", + '__background_neu__' => "#ddd", + '__border__' => "#ccc", + '__highlight__' => "#ff9", + '__link__' => "#00f", + ); + + /** + * StyleUtils constructor. + * @param string $tpl template name: if not passed as argument, the default value from $conf will be used + * @param bool $preview + * @param bool $reinit whether static style conf should be reinitialized + */ + public function __construct($tpl = '', $preview = false, $reinit = false) + { + if (!$tpl) { + global $conf; + $tpl = $conf['conf']; + } + $this->tpl = $tpl; + $this->reinit = $reinit; + $this->preview = $preview; + } + /** * Load style ini contents * @@ -11,94 +48,118 @@ class StyleUtils * the stylesheet modes * * @author Andreas Gohr + * @author Anna Dabrowska * - * @param string $tpl the used template - * @param bool $preview load preview replacements * @return array with keys 'stylesheets' and 'replacements' */ - public function cssStyleini($tpl, $preview=false) { - global $conf; - - $stylesheets = array(); // mode, file => base - // guaranteed placeholder => value - $replacements = array( - '__text__' => "#000", - '__background__' => "#fff", - '__text_alt__' => "#999", - '__background_alt__' => "#eee", - '__text_neu__' => "#666", - '__background_neu__' => "#ddd", - '__border__' => "#ccc", - '__highlight__' => "#ff9", - '__link__' => "#00f", - ); - - // load template's style.ini - $incbase = tpl_incdir($tpl); - $webbase = tpl_basedir($tpl); - $ini = $incbase.'style.ini'; - if(file_exists($ini)){ - $data = parse_ini_file($ini, true); - - // stylesheets - if(is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){ - if (!file_exists($incbase . $file)) { - list($extension, $basename) = array_map('strrev', explode('.', strrev($file), 2)); - $newExtension = $extension === 'css' ? 'less' : 'css'; - if (file_exists($incbase . $basename . '.' . $newExtension)) { - $stylesheets[$mode][$incbase . $basename . '.' . $newExtension] = $webbase; - if ($conf['allowdebug']) { - msg("Stylesheet $file not found, using $basename.$newExtension instead. Please contact developer of \"{$conf['template']}\" template.", 2); - } - continue; - } - } - $stylesheets[$mode][$incbase . $file] = $webbase; - } - - // replacements - if(is_array($data['replacements'])){ - $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'],$webbase)); - } + public function cssStyleini() + { + static $combined = []; + if (!empty($combined) && !$this->reinit) { + return $combined; } - // load configs's style.ini - $webbase = DOKU_BASE; - $ini = DOKU_CONF."tpl/$tpl/style.ini"; - $incbase = dirname($ini).'/'; - if(file_exists($ini)){ - $data = parse_ini_file($ini, true); + global $conf; + global $config_cascade; + $stylesheets = array(); // mode, file => base - // stylesheets - if(isset($data['stylesheets']) && is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){ - $stylesheets[$mode][$incbase.$file] = $webbase; - } + // guaranteed placeholder => value + $replacements = $this->defaultReplacements; - // replacements - if(isset($data['replacements']) && is_array($data['replacements'])){ - $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'],$webbase)); - } + // merge all styles from config cascade + if (!is_array($config_cascade['styleini'])) { + trigger_error('Missing config cascade for styleini', E_USER_WARNING); } // allow replacement overwrites in preview mode - if($preview) { - $webbase = DOKU_BASE; - $ini = $conf['cachedir'].'/preview.ini'; - if(file_exists($ini)) { - $data = parse_ini_file($ini, true); - // replacements - if(is_array($data['replacements'])) { - $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'], $webbase)); + if ($this->preview) { + $config_cascade['styleini']['local'][] = $conf['cachedir'] . '/preview.ini'; + } + + $combined['stylesheets'] = []; + $combined['replacements'] = []; + + foreach (array('default', 'local', 'protected') as $config_group) { + if (empty($config_cascade['styleini'][$config_group])) continue; + + // set proper server dirs + $webbase = $this->getWebbase($config_group); + + foreach ($config_cascade['styleini'][$config_group] as $inifile) { + // replace the placeholder with the name of the current template + $inifile = str_replace('%TEMPLATE%', $this->tpl, $inifile); + + $incbase = dirname($inifile) . '/'; + + if (file_exists($inifile)) { + $config = parse_ini_file($inifile, true); + + if (is_array($config['stylesheets'])) { + + foreach ($config['stylesheets'] as $inifile => $mode) { + // validate and include style files + $stylesheets = array_merge($stylesheets, $this->getValidatedStyles($stylesheets, $inifile, $mode, $incbase, $webbase)); + $combined['stylesheets'] = array_merge($combined['stylesheets'], $stylesheets); + } + } + + if (is_array($config['replacements'])) { + $replacements = array_replace($replacements, $this->cssFixreplacementurls($config['replacements'], $webbase)); + $combined['replacements'] = array_merge($combined['replacements'], $replacements); + } } } } - return array( - 'stylesheets' => $stylesheets, - 'replacements' => $replacements - ); + + return $combined; } + /** + * Checks if configured style files exist and, if necessary, adjusts file extensions in config + * + * @param array $stylesheets + * @param string $file + * @param string $mode + * @param string $incbase + * @param string $webbase + * @return mixed + */ + protected function getValidatedStyles($stylesheets, $file, $mode, $incbase, $webbase) + { + global $conf; + if (!file_exists($incbase . $file)) { + list($extension, $basename) = array_map('strrev', explode('.', strrev($file), 2)); + $newExtension = $extension === 'css' ? 'less' : 'css'; + if (file_exists($incbase . $basename . '.' . $newExtension)) { + $stylesheets[$mode][$incbase . $basename . '.' . $newExtension] = $webbase; + if ($conf['allowdebug']) { + msg("Stylesheet $file not found, using $basename.$newExtension instead. Please contact developer of \"$this->tpl\" template.", 2); + } + } elseif ($conf['allowdebug']) { + msg("Stylesheet $file not found, please contact the developer of \"$this->tpl\" template.", 2); + } + } + $stylesheets[$mode][fullpath($incbase . $file)] = $webbase; + return $stylesheets; + } + + /** + * Returns the web base path for the given level/group in config cascade. + * Style resources are relative to the template directory for the main (default) styles + * but relative to DOKU_BASE for everything else" + * + * @param string $config_group + * @return string + */ + protected function getWebbase($config_group) + { + if ($config_group === 'default') { + return tpl_basedir($this->tpl); + } else { + return DOKU_BASE; + } + } /** * Amend paths used in replacement relative urls, refer FS#2879 @@ -109,9 +170,10 @@ class StyleUtils * @param string $location * @return array */ - protected function cssFixreplacementurls($replacements, $location) { - foreach($replacements as $key => $value) { - $replacements[$key] = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#','\\1'.$location,$value); + protected function cssFixreplacementurls($replacements, $location) + { + foreach ($replacements as $key => $value) { + $replacements[$key] = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#', '\\1' . $location, $value); } return $replacements; } diff --git a/inc/config_cascade.php b/inc/config_cascade.php index f0aa6cc7e..61d099c6e 100644 --- a/inc/config_cascade.php +++ b/inc/config_cascade.php @@ -61,6 +61,10 @@ $config_cascade = array_merge( 'userscript' => array( 'default' => array(DOKU_CONF . 'userscript.js') ), + 'styleini' => array( + 'default' => array(DOKU_INC . 'lib/tpl/%TEMPLATE%/' . 'style.ini'), + 'local' => array(DOKU_CONF . 'tpl/%TEMPLATE%/' . 'style.ini') + ), 'acl' => array( 'default' => DOKU_CONF . 'acl.auth.php', ), diff --git a/inc/template.php b/inc/template.php index 03c7bf96f..93e0efdd0 100644 --- a/inc/template.php +++ b/inc/template.php @@ -247,7 +247,7 @@ function tpl_metaheaders($alt = true) { } $styleUtil = new \dokuwiki\StyleUtils(); - $styleIni = $styleUtil->cssStyleini($conf['template']); + $styleIni = $styleUtil->cssStyleini(); $replacements = $styleIni['replacements']; if (!empty($replacements['__theme_color__'])) { $head['meta'][] = array('name' => 'theme-color', 'content' => $replacements['__theme_color__']); diff --git a/lib/exe/css.php b/lib/exe/css.php index 943f3f4c2..19ae5570e 100644 --- a/lib/exe/css.php +++ b/lib/exe/css.php @@ -45,8 +45,8 @@ function css_out(){ if(!$tpl) $tpl = $conf['template']; // load style.ini - $styleUtil = new \dokuwiki\StyleUtils(); - $styleini = $styleUtil->cssStyleini($tpl, $INPUT->bool('preview')); + $styleUtil = new \dokuwiki\StyleUtils($tpl, $INPUT->bool('preview')); + $styleini = $styleUtil->cssStyleini(); // cache influencers $tplinc = tpl_incdir($tpl); diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index 055ac2279..e02f06604 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -58,8 +58,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { global $conf; global $ID; - $styleUtil = new \dokuwiki\StyleUtils(); - $styleini = $styleUtil->cssStyleini($conf['template'], true); + $styleUtil = new \dokuwiki\StyleUtils($conf['template'], true); + $styleini = $styleUtil->cssStyleini(); $replacements = $styleini['replacements']; if($this->ispopup) { diff --git a/lib/tpl/index.php b/lib/tpl/index.php index d6f00343b..8b021511c 100644 --- a/lib/tpl/index.php +++ b/lib/tpl/index.php @@ -46,10 +46,10 @@ require_once(DOKU_INC.'inc/init.php'); // get merged style.ini define('SIMPLE_TEST', true); // hack to prevent css output and headers require_once(DOKU_INC.'lib/exe/css.php'); -$styleUtils = new \dokuwiki\StyleUtils(); -$ini = $styleUtils->cssStyleini($conf['template']); +$styleUtils = new \dokuwiki\StyleUtils($conf['template']); +$ini = $styleUtils->cssStyleini(); -if ($ini) { +if (!empty($ini)) { echo ''; echo ""; foreach($ini['replacements'] as $key => $val){
".hsc($conf['template'])."'s style.ini