1<?php 2/** 3 * DokuWiki StyleSheet creator 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); 10if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) 11if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here 12if(!defined('NL')) define('NL',"\n"); 13require_once(DOKU_INC.'inc/init.php'); 14 15// Main (don't run when UNIT test) 16if(!defined('SIMPLE_TEST')){ 17 header('Content-Type: text/css; charset=utf-8'); 18 css_out(); 19} 20 21 22// ---------------------- functions ------------------------------ 23 24/** 25 * Output all needed Styles 26 * 27 * @author Andreas Gohr <andi@splitbrain.org> 28 */ 29function css_out(){ 30 global $conf; 31 global $lang; 32 global $config_cascade; 33 global $INPUT; 34 35 if ($INPUT->str('s') == 'feed') { 36 $mediatypes = array('feed'); 37 $type = 'feed'; 38 } else { 39 $mediatypes = array('screen', 'all', 'print'); 40 $type = ''; 41 } 42 43 $tpl = trim(preg_replace('/[^\w-]+/','',$INPUT->str('t'))); 44 if($tpl){ 45 $tplinc = DOKU_INC.'lib/tpl/'.$tpl.'/'; 46 $tpldir = DOKU_BASE.'lib/tpl/'.$tpl.'/'; 47 }else{ 48 $tplinc = tpl_incdir(); 49 $tpldir = tpl_basedir(); 50 } 51 52 // The generated script depends on some dynamic options 53 $cache = new cache('styles'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].DOKU_BASE.$tplinc.$type,'.css'); 54 55 // load template styles 56 $tplstyles = array(); 57 if(@file_exists($tplinc.'style.ini')){ 58 $ini = parse_ini_file($tplinc.'style.ini',true); 59 foreach($ini['stylesheets'] as $file => $mode){ 60 $tplstyles[$mode][$tplinc.$file] = $tpldir; 61 } 62 } 63 64 // if old 'default' userstyle setting exists, make it 'screen' userstyle for backwards compatibility 65 if (isset($config_cascade['userstyle']['default'])) { 66 $config_cascade['userstyle']['screen'] = $config_cascade['userstyle']['default']; 67 } 68 69 // start output buffering 70 ob_start(); 71 72 // Array of needed files and their web locations, the latter ones 73 // are needed to fix relative paths in the stylesheets 74 $files = array(); 75 76 $cache_files = getConfigFiles('main'); 77 $cache_files[] = $tplinc.'style.ini'; 78 $cache_files[] = __FILE__; 79 80 foreach($mediatypes as $mediatype) { 81 $files[$mediatype] = array(); 82 // load core styles 83 $files[$mediatype][DOKU_INC.'lib/styles/'.$mediatype.'.css'] = DOKU_BASE.'lib/styles/'; 84 // load jQuery-UI theme 85 if ($mediatype == 'screen') { 86 $files[$mediatype][DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] = DOKU_BASE.'lib/scripts/jquery/jquery-ui-theme/'; 87 } 88 // load plugin styles 89 $files[$mediatype] = array_merge($files[$mediatype], css_pluginstyles($mediatype)); 90 // load template styles 91 if (isset($tplstyles[$mediatype])) { 92 $files[$mediatype] = array_merge($files[$mediatype], $tplstyles[$mediatype]); 93 } 94 // load user styles 95 if(isset($config_cascade['userstyle'][$mediatype])){ 96 $files[$mediatype][$config_cascade['userstyle'][$mediatype]] = DOKU_BASE; 97 } 98 // load rtl styles 99 // note: this adds the rtl styles only to the 'screen' media type 100 // @deprecated 2012-04-09: rtl will cease to be a mode of its own, 101 // please use "[dir=rtl]" in any css file in all, screen or print mode instead 102 if ($mediatype=='screen') { 103 if($lang['direction'] == 'rtl'){ 104 if (isset($tplstyles['rtl'])) $files[$mediatype] = array_merge($files[$mediatype], $tplstyles['rtl']); 105 } 106 } 107 108 $cache_files = array_merge($cache_files, array_keys($files[$mediatype])); 109 } 110 111 // check cache age & handle conditional request 112 // This may exit if a cache can be used 113 http_cached($cache->cache, 114 $cache->useCache(array('files' => $cache_files))); 115 116 // build the stylesheet 117 foreach ($mediatypes as $mediatype) { 118 119 // print the default classes for interwiki links and file downloads 120 if ($mediatype == 'screen') { 121 css_interwiki(); 122 css_filetypes(); 123 } 124 125 // load files 126 $css_content = ''; 127 foreach($files[$mediatype] as $file => $location){ 128 $css_content .= css_loadfile($file, $location); 129 } 130 switch ($mediatype) { 131 case 'screen': 132 print NL.'@media screen { /* START screen styles */'.NL.$css_content.NL.'} /* /@media END screen styles */'.NL; 133 break; 134 case 'print': 135 print NL.'@media print { /* START print styles */'.NL.$css_content.NL.'} /* /@media END print styles */'.NL; 136 break; 137 case 'all': 138 case 'feed': 139 default: 140 print NL.'/* START rest styles */ '.NL.$css_content.NL.'/* END rest styles */'.NL; 141 break; 142 } 143 } 144 // end output buffering and get contents 145 $css = ob_get_contents(); 146 ob_end_clean(); 147 148 // apply style replacements 149 $css = css_applystyle($css,$tplinc); 150 151 // place all @import statements at the top of the file 152 $css = css_moveimports($css); 153 154 // compress whitespace and comments 155 if($conf['compress']){ 156 $css = css_compress($css); 157 } 158 159 // embed small images right into the stylesheet 160 if($conf['cssdatauri']){ 161 $base = preg_quote(DOKU_BASE,'#'); 162 $css = preg_replace_callback('#(url\([ \'"]*)('.$base.')(.*?(?:\.(png|gif)))#i','css_datauri',$css); 163 } 164 165 http_cached_finish($cache->cache, $css); 166} 167 168/** 169 * Does placeholder replacements in the style according to 170 * the ones defined in a templates style.ini file 171 * 172 * @author Andreas Gohr <andi@splitbrain.org> 173 */ 174function css_applystyle($css,$tplinc){ 175 if(@file_exists($tplinc.'style.ini')){ 176 $ini = parse_ini_file($tplinc.'style.ini',true); 177 $css = strtr($css,$ini['replacements']); 178 } 179 return $css; 180} 181 182/** 183 * Prints classes for interwikilinks 184 * 185 * Interwiki links have two classes: 'interwiki' and 'iw_$name>' where 186 * $name is the identifier given in the config. All Interwiki links get 187 * an default style with a default icon. If a special icon is available 188 * for an interwiki URL it is set in it's own class. Both classes can be 189 * overwritten in the template or userstyles. 190 * 191 * @author Andreas Gohr <andi@splitbrain.org> 192 */ 193function css_interwiki(){ 194 195 // default style 196 echo 'a.interwiki {'; 197 echo ' background: transparent url('.DOKU_BASE.'lib/images/interwiki.png) 0px 1px no-repeat;'; 198 echo ' padding: 1px 0px 1px 16px;'; 199 echo '}'; 200 201 // additional styles when icon available 202 $iwlinks = getInterwiki(); 203 foreach(array_keys($iwlinks) as $iw){ 204 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$iw); 205 if(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.png')){ 206 echo "a.iw_$class {"; 207 echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.png)'; 208 echo '}'; 209 }elseif(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.gif')){ 210 echo "a.iw_$class {"; 211 echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.gif)'; 212 echo '}'; 213 } 214 } 215} 216 217/** 218 * Prints classes for file download links 219 * 220 * @author Andreas Gohr <andi@splitbrain.org> 221 */ 222function css_filetypes(){ 223 224 // default style 225 echo '.mediafile {'; 226 echo ' background: transparent url('.DOKU_BASE.'lib/images/fileicons/file.png) 0px 1px no-repeat;'; 227 echo ' padding-left: 18px;'; 228 echo ' padding-bottom: 1px;'; 229 echo '}'; 230 231 // additional styles when icon available 232 // scan directory for all icons 233 $exts = array(); 234 if($dh = opendir(DOKU_INC.'lib/images/fileicons')){ 235 while(false !== ($file = readdir($dh))){ 236 if(preg_match('/([_\-a-z0-9]+(?:\.[_\-a-z0-9]+)*?)\.(png|gif)/i',$file,$match)){ 237 $ext = strtolower($match[1]); 238 $type = '.'.strtolower($match[2]); 239 if($ext!='file' && (!isset($exts[$ext]) || $type=='.png')){ 240 $exts[$ext] = $type; 241 } 242 } 243 } 244 closedir($dh); 245 } 246 foreach($exts as $ext=>$type){ 247 $class = preg_replace('/[^_\-a-z0-9]+/','_',$ext); 248 echo ".mf_$class {"; 249 echo ' background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$ext.$type.')'; 250 echo '}'; 251 } 252} 253 254/** 255 * Loads a given file and fixes relative URLs with the 256 * given location prefix 257 */ 258function css_loadfile($file,$location=''){ 259 if(!@file_exists($file)) return ''; 260 $css = io_readFile($file); 261 if(!$location) return $css; 262 263 $css = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#','\\1'.$location,$css); 264 $css = preg_replace('#(@import\s+[\'"])(?!/|data:|http://|https://)#', '\\1'.$location, $css); 265 266 return $css; 267} 268 269/** 270 * Converte local image URLs to data URLs if the filesize is small 271 * 272 * Callback for preg_replace_callback 273 */ 274function css_datauri($match){ 275 global $conf; 276 277 $pre = unslash($match[1]); 278 $base = unslash($match[2]); 279 $url = unslash($match[3]); 280 $ext = unslash($match[4]); 281 282 $local = DOKU_INC.$url; 283 $size = @filesize($local); 284 if($size && $size < $conf['cssdatauri']){ 285 $data = base64_encode(file_get_contents($local)); 286 } 287 if($data){ 288 $url = 'data:image/'.$ext.';base64,'.$data; 289 }else{ 290 $url = $base.$url; 291 } 292 return $pre.$url; 293} 294 295 296/** 297 * Returns a list of possible Plugin Styles (no existance check here) 298 * 299 * @author Andreas Gohr <andi@splitbrain.org> 300 */ 301function css_pluginstyles($mediatype='screen'){ 302 global $lang; 303 $list = array(); 304 $plugins = plugin_list(); 305 foreach ($plugins as $p){ 306 $list[DOKU_PLUGIN."$p/$mediatype.css"] = DOKU_BASE."lib/plugins/$p/"; 307 // alternative for screen.css 308 if ($mediatype=='screen') { 309 $list[DOKU_PLUGIN."$p/style.css"] = DOKU_BASE."lib/plugins/$p/"; 310 } 311 // @deprecated 2012-04-09: rtl will cease to be a mode of its own, 312 // please use "[dir=rtl]" in any css file in all, screen or print mode instead 313 if($lang['direction'] == 'rtl'){ 314 $list[DOKU_PLUGIN."$p/rtl.css"] = DOKU_BASE."lib/plugins/$p/"; 315 } 316 } 317 return $list; 318} 319 320/** 321 * Move all @import statements in a combined stylesheet to the top so they 322 * aren't ignored by the browser. 323 * 324 * @author Gabriel Birke <birke@d-scribe.de> 325 */ 326function css_moveimports($css) 327{ 328 if(!preg_match_all('/@import\s+(?:url\([^)]+\)|"[^"]+")\s*[^;]*;\s*/', $css, $matches, PREG_OFFSET_CAPTURE)) { 329 return $css; 330 } 331 $newCss = ""; 332 $imports = ""; 333 $offset = 0; 334 foreach($matches[0] as $match) { 335 $newCss .= substr($css, $offset, $match[1] - $offset); 336 $imports .= $match[0]; 337 $offset = $match[1] + strlen($match[0]); 338 } 339 $newCss .= substr($css, $offset); 340 return $imports.$newCss; 341} 342 343/** 344 * Very simple CSS optimizer 345 * 346 * @author Andreas Gohr <andi@splitbrain.org> 347 */ 348function css_compress($css){ 349 //strip comments through a callback 350 $css = preg_replace_callback('#(/\*)(.*?)(\*/)#s','css_comment_cb',$css); 351 352 //strip (incorrect but common) one line comments 353 $css = preg_replace('/(?<!:)\/\/.*$/m','',$css); 354 355 // strip whitespaces 356 $css = preg_replace('![\r\n\t ]+!',' ',$css); 357 $css = preg_replace('/ ?([;,{}\/]) ?/','\\1',$css); 358 $css = preg_replace('/ ?: /',':',$css); 359 360 // shorten colors 361 $css = preg_replace("/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3/", "#\\1\\2\\3",$css); 362 363 return $css; 364} 365 366/** 367 * Callback for css_compress() 368 * 369 * Keeps short comments (< 5 chars) to maintain typical browser hacks 370 * 371 * @author Andreas Gohr <andi@splitbrain.org> 372 */ 373function css_comment_cb($matches){ 374 if(strlen($matches[2]) > 4) return ''; 375 return $matches[0]; 376} 377 378//Setup VIM: ex: et ts=4 : 379