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) 11require_once(DOKU_INC.'inc/init.php'); 12require_once(DOKU_INC.'inc/pageutils.php'); 13require_once(DOKU_INC.'inc/io.php'); 14require_once(DOKU_INC.'inc/confutils.php'); 15 16// Main (don't run when UNIT test) 17if(!defined('SIMPLE_TEST')){ 18 header('Content-Type: text/css; charset=utf-8'); 19 css_out(); 20} 21 22 23// ---------------------- functions ------------------------------ 24 25/** 26 * Output all needed Styles 27 * 28 * @author Andreas Gohr <andi@splitbrain.org> 29 */ 30function css_out(){ 31 global $conf; 32 global $lang; 33 switch ($_REQUEST['s']) { 34 case 'all': 35 case 'print': 36 case 'feed': 37 $style = $_REQUEST['s']; 38 break; 39 default: 40 $style = ''; 41 break; 42 } 43 44 $tpl = trim(preg_replace('/[^\w-]+/','',$_REQUEST['t'])); 45 if($tpl){ 46 $tplinc = DOKU_INC.'lib/tpl/'.$tpl.'/'; 47 $tpldir = DOKU_BASE.'lib/tpl/'.$tpl.'/'; 48 }else{ 49 $tplinc = DOKU_TPLINC; 50 $tpldir = DOKU_TPL; 51 } 52 53 // The generated script depends on some dynamic options 54 $cache = getCacheName('styles'.DOKU_BASE.$tplinc.$style,'.css'); 55 56 // load template styles 57 $tplstyles = array(); 58 if(@file_exists($tplinc.'style.ini')){ 59 $ini = parse_ini_file($tplinc.'style.ini',true); 60 foreach($ini['stylesheets'] as $file => $mode){ 61 $tplstyles[$mode][$tplinc.$file] = $tpldir; 62 } 63 } 64 65 // Array of needed files and their web locations, the latter ones 66 // are needed to fix relative paths in the stylesheets 67 $files = array(); 68 //if (isset($tplstyles['all'])) $files = array_merge($files, $tplstyles['all']); 69 if(!empty($style)){ 70 $files[DOKU_INC.'lib/styles/'.$style.'.css'] = DOKU_BASE.'lib/styles/'; 71 // load plugin, template, user styles 72 $files = array_merge($files, css_pluginstyles($style)); 73 if (isset($tplstyles[$style])) $files = array_merge($files, $tplstyles[$style]); 74 $files[DOKU_CONF.'user'.$style.'.css'] = DOKU_BASE; 75 }else{ 76 $files[DOKU_INC.'lib/styles/style.css'] = DOKU_BASE.'lib/styles/'; 77 if($conf['spellchecker']){ 78 $files[DOKU_INC.'lib/styles/spellcheck.css'] = DOKU_BASE.'lib/styles/'; 79 } 80 // load plugin, template, user styles 81 $files = array_merge($files, css_pluginstyles('screen')); 82 if (isset($tplstyles['screen'])) $files = array_merge($files, $tplstyles['screen']); 83 if($lang['direction'] == 'rtl'){ 84 if (isset($tplstyles['rtl'])) $files = array_merge($files, $tplstyles['rtl']); 85 } 86 $files[DOKU_CONF.'userstyle.css'] = DOKU_BASE; 87 } 88 89 // check cache age & handle conditional request 90 header('Cache-Control: public, max-age=3600'); 91 header('Pragma: public'); 92 if(css_cacheok($cache,array_keys($files),$tplinc)){ 93 http_conditionalRequest(filemtime($cache)); 94 if($conf['allowdebug']) header("X-CacheUsed: $cache"); 95 readfile($cache); 96 return; 97 } else { 98 http_conditionalRequest(time()); 99 } 100 101 // start output buffering and build the stylesheet 102 ob_start(); 103 104 // print the default classes for interwiki links and file downloads 105 css_interwiki(); 106 css_filetypes(); 107 108 // load files 109 foreach($files as $file => $location){ 110 print css_loadfile($file, $location); 111 } 112 113 // end output buffering and get contents 114 $css = ob_get_contents(); 115 ob_end_clean(); 116 117 // apply style replacements 118 $css = css_applystyle($css,$tplinc); 119 120 // compress whitespace and comments 121 if($conf['compress']){ 122 $css = css_compress($css); 123 } 124 125 // save cache file 126 io_saveFile($cache,$css); 127 128 // finally send output 129 print $css; 130} 131 132/** 133 * Checks if a CSS Cache file still is valid 134 * 135 * @author Andreas Gohr <andi@splitbrain.org> 136 */ 137function css_cacheok($cache,$files,$tplinc){ 138 if($_REQUEST['purge']) return false; //support purge request 139 140 $ctime = @filemtime($cache); 141 if(!$ctime) return false; //There is no cache 142 143 // some additional files to check 144 $files[] = DOKU_CONF.'dokuwiki.php'; 145 $files[] = DOKU_CONF.'local.php'; 146 $files[] = $tplinc.'style.ini'; 147 $files[] = __FILE__; 148 149 // now walk the files 150 foreach($files as $file){ 151 if(@filemtime($file) > $ctime){ 152 return false; 153 } 154 } 155 return true; 156} 157 158/** 159 * Does placeholder replacements in the style according to 160 * the ones defined in a templates style.ini file 161 * 162 * @author Andreas Gohr <andi@splitbrain.org> 163 */ 164function css_applystyle($css,$tplinc){ 165 if(@file_exists($tplinc.'style.ini')){ 166 $ini = parse_ini_file($tplinc.'style.ini',true); 167 $css = strtr($css,$ini['replacements']); 168 } 169 return $css; 170} 171 172/** 173 * Prints classes for interwikilinks 174 * 175 * Interwiki links have two classes: 'interwiki' and 'iw_$name>' where 176 * $name is the identifier given in the config. All Interwiki links get 177 * an default style with a default icon. If a special icon is available 178 * for an interwiki URL it is set in it's own class. Both classes can be 179 * overwritten in the template or userstyles. 180 * 181 * @author Andreas Gohr <andi@splitbrain.org> 182 */ 183function css_interwiki(){ 184 185 // default style 186 echo 'a.interwiki {'; 187 echo ' background: transparent url('.DOKU_BASE.'lib/images/interwiki.png) 0px 1px no-repeat;'; 188 echo ' padding-left: 16px;'; 189 echo '}'; 190 191 // additional styles when icon available 192 $iwlinks = getInterwiki(); 193 foreach(array_keys($iwlinks) as $iw){ 194 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$iw); 195 if(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.png')){ 196 echo "a.iw_$class {"; 197 echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.png)'; 198 echo '}'; 199 }elseif(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.gif')){ 200 echo "a.iw_$class {"; 201 echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.gif)'; 202 echo '}'; 203 } 204 } 205} 206 207/** 208 * Prints classes for file download links 209 * 210 * @author Andreas Gohr <andi@splitbrain.org> 211 */ 212function css_filetypes(){ 213 214 // default style 215 echo 'a.mediafile {'; 216 echo ' background: transparent url('.DOKU_BASE.'lib/images/fileicons/file.png) 0px 1px no-repeat;'; 217 echo ' padding-left: 18px;'; 218 echo ' padding-bottom: 1px;'; 219 echo '}'; 220 221 // additional styles when icon available 222 $mimes = getMimeTypes(); 223 foreach(array_keys($mimes) as $mime){ 224 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$mime); 225 if(@file_exists(DOKU_INC.'lib/images/fileicons/'.$mime.'.png')){ 226 echo "a.mf_$class {"; 227 echo ' background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$mime.'.png)'; 228 echo '}'; 229 }elseif(@file_exists(DOKU_INC.'lib/images/fileicons/'.$mime.'.gif')){ 230 echo "a.mf_$class {"; 231 echo ' background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$mime.'.gif)'; 232 echo '}'; 233 } 234 } 235} 236 237/** 238 * Loads a given file and fixes relative URLs with the 239 * given location prefix 240 */ 241function css_loadfile($file,$location=''){ 242 if(!@file_exists($file)) return ''; 243 $css = io_readFile($file); 244 if(!$location) return $css; 245 246 $css = preg_replace('#(url\([ \'"]*)((?!/|http://|https://| |\'|"))#','\\1'.$location.'\\3',$css); 247 return $css; 248} 249 250 251/** 252 * Returns a list of possible Plugin Styles (no existance check here) 253 * 254 * @author Andreas Gohr <andi@splitbrain.org> 255 */ 256function css_pluginstyles($mode='screen'){ 257 global $lang; 258 $list = array(); 259 $plugins = plugin_list(); 260 foreach ($plugins as $p){ 261 if($mode == 'all'){ 262 $list[DOKU_PLUGIN."$p/all.css"] = DOKU_BASE."lib/plugins/$p/"; 263 }elseif($mode == 'print'){ 264 $list[DOKU_PLUGIN."$p/print.css"] = DOKU_BASE."lib/plugins/$p/"; 265 }elseif($mode == 'feed'){ 266 $list[DOKU_PLUGIN."$p/feed.css"] = DOKU_BASE."lib/plugins/$p/"; 267 }else{ 268 $list[DOKU_PLUGIN."$p/style.css"] = DOKU_BASE."lib/plugins/$p/"; 269 $list[DOKU_PLUGIN."$p/screen.css"] = DOKU_BASE."lib/plugins/$p/"; 270 } 271 if($lang['direction'] == 'rtl'){ 272 $list[DOKU_PLUGIN."$p/rtl.css"] = DOKU_BASE."lib/plugins/$p/"; 273 } 274 } 275 return $list; 276} 277 278/** 279 * Very simple CSS optimizer 280 * 281 * @author Andreas Gohr <andi@splitbrain.org> 282 */ 283function css_compress($css){ 284 //strip comments through a callback 285 $css = preg_replace_callback('#(/\*)(.*?)(\*/)#s','css_comment_cb',$css); 286 287 //strip (incorrect but common) one line comments 288 $css = preg_replace('/(?<!:)\/\/.*$/m','',$css); 289 290 // strip whitespaces 291 $css = preg_replace('![\r\n\t ]+!',' ',$css); 292 $css = preg_replace('/ ?([:;,{}\/]) ?/','\\1',$css); 293 294 // shorten colors 295 $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); 296 297 return $css; 298} 299 300/** 301 * Callback for css_compress() 302 * 303 * Keeps short comments (< 5 chars) to maintain typical browser hacks 304 * 305 * @author Andreas Gohr <andi@splitbrain.org> 306 */ 307function css_comment_cb($matches){ 308 if(strlen($matches[2]) > 4) return ''; 309 return $matches[0]; 310} 311 312//Setup VIM: ex: et ts=4 enc=utf-8 : 313?> 314