1<?php 2 3namespace dokuwiki; 4 5/** 6 * Class StyleUtils 7 * 8 * Reads and applies the template's style.ini settings 9 */ 10class StyleUtils 11{ 12 /** @var string current template */ 13 protected $tpl; 14 /** @var bool reinitialize styles config */ 15 protected $reinit; 16 /** @var bool $preview preview mode */ 17 protected $preview; 18 /** @var array default replacements to be merged with custom style configs */ 19 protected $defaultReplacements = [ 20 '__text__' => "#000", 21 '__background__' => "#fff", 22 '__text_alt__' => "#999", 23 '__background_alt__' => "#eee", 24 '__text_neu__' => "#666", 25 '__background_neu__' => "#ddd", 26 '__border__' => "#ccc", 27 '__highlight__' => "#ff9", 28 '__link__' => "#00f" 29 ]; 30 31 /** 32 * StyleUtils constructor. 33 * @param string $tpl template name: if not passed as argument, the default value from $conf will be used 34 * @param bool $preview 35 * @param bool $reinit whether static style conf should be reinitialized 36 */ 37 public function __construct($tpl = '', $preview = false, $reinit = false) 38 { 39 if (!$tpl) { 40 global $conf; 41 $tpl = $conf['template']; 42 } 43 $this->tpl = $tpl; 44 $this->reinit = $reinit; 45 $this->preview = $preview; 46 } 47 48 /** 49 * Load style ini contents 50 * 51 * Loads and merges style.ini files from template and config and prepares 52 * the stylesheet modes 53 * 54 * @author Andreas Gohr <andi@splitbrain.org> 55 * @author Anna Dabrowska <info@cosmocode.de> 56 * 57 * @return array with keys 'stylesheets' and 'replacements' 58 */ 59 public function cssStyleini() 60 { 61 static $combined = []; 62 if (!empty($combined) && !$this->reinit) { 63 return $combined; 64 } 65 66 global $conf; 67 global $config_cascade; 68 $stylesheets = []; // mode, file => base 69 70 // guaranteed placeholder => value 71 $replacements = $this->defaultReplacements; 72 73 // merge all styles from config cascade 74 if (!is_array($config_cascade['styleini'])) { 75 trigger_error('Missing config cascade for styleini', E_USER_WARNING); 76 } 77 78 // allow replacement overwrites in preview mode 79 if ($this->preview) { 80 $config_cascade['styleini']['local'][] = $conf['cachedir'] . '/preview.ini'; 81 } 82 83 $combined['stylesheets'] = []; 84 $combined['replacements'] = []; 85 86 foreach (['default', 'local', 'protected'] as $config_group) { 87 if (empty($config_cascade['styleini'][$config_group])) continue; 88 89 // set proper server dirs 90 $webbase = $this->getWebbase($config_group); 91 92 foreach ($config_cascade['styleini'][$config_group] as $inifile) { 93 // replace the placeholder with the name of the current template 94 $inifile = str_replace('%TEMPLATE%', $this->tpl, $inifile); 95 96 $incbase = dirname($inifile) . '/'; 97 98 if (file_exists($inifile)) { 99 $config = parse_ini_file($inifile, true); 100 101 if (isset($config['stylesheets']) && is_array($config['stylesheets'])) { 102 foreach ($config['stylesheets'] as $inifile => $mode) { 103 // validate and include style files 104 $stylesheets = array_merge( 105 $stylesheets, 106 $this->getValidatedStyles($stylesheets, $inifile, $mode, $incbase, $webbase) 107 ); 108 $combined['stylesheets'] = array_merge($combined['stylesheets'], $stylesheets); 109 } 110 } 111 112 if (isset($config['replacements']) && is_array($config['replacements'])) { 113 $replacements = array_replace( 114 $replacements, 115 $this->cssFixreplacementurls($config['replacements'], $webbase) 116 ); 117 $combined['replacements'] = array_merge($combined['replacements'], $replacements); 118 } 119 } 120 } 121 } 122 123 return $combined; 124 } 125 126 /** 127 * Checks if configured style files exist and, if necessary, adjusts file extensions in config 128 * 129 * @param array $stylesheets 130 * @param string $file 131 * @param string $mode 132 * @param string $incbase 133 * @param string $webbase 134 * @return mixed 135 */ 136 protected function getValidatedStyles($stylesheets, $file, $mode, $incbase, $webbase) 137 { 138 global $conf; 139 if (!file_exists($incbase . $file)) { 140 [$extension, $basename] = array_map('strrev', sexplode('.', strrev($file), 2, '')); 141 $newExtension = $extension === 'css' ? 'less' : 'css'; 142 if (file_exists($incbase . $basename . '.' . $newExtension)) { 143 $stylesheets[$mode][$incbase . $basename . '.' . $newExtension] = $webbase; 144 if ($conf['allowdebug']) { 145 msg("Stylesheet $file not found, using $basename.$newExtension instead. " . 146 "Please contact developer of \"$this->tpl\" template.", 2); 147 } 148 } elseif ($conf['allowdebug']) { 149 msg("Stylesheet $file not found, please contact the developer of \"$this->tpl\" template.", 2); 150 } 151 } 152 $stylesheets[$mode][fullpath($incbase . $file)] = $webbase; 153 return $stylesheets; 154 } 155 156 /** 157 * Returns the web base path for the given level/group in config cascade. 158 * Style resources are relative to the template directory for the main (default) styles 159 * but relative to DOKU_BASE for everything else" 160 * 161 * @param string $config_group 162 * @return string 163 */ 164 protected function getWebbase($config_group) 165 { 166 if ($config_group === 'default') { 167 return tpl_basedir($this->tpl); 168 } else { 169 return DOKU_BASE; 170 } 171 } 172 173 /** 174 * Amend paths used in replacement relative urls, refer FS#2879 175 * 176 * @author Chris Smith <chris@jalakai.co.uk> 177 * 178 * @param array $replacements with key-value pairs 179 * @param string $location 180 * @return array 181 */ 182 protected function cssFixreplacementurls($replacements, $location) 183 { 184 foreach ($replacements as $key => $value) { 185 $replacements[$key] = preg_replace( 186 '#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#', 187 '\\1' . $location, 188 $value 189 ); 190 } 191 return $replacements; 192 } 193} 194