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