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