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