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