Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/exe/css.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
use dokuwiki\Cache\Cache;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Cache.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use dokuwiki\Extension\Event;
11
12
if(!defined('DOKU_INC')) define('DOKU_INC', __DIR__ .'/../../');
13
if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching)
14
if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here
15
if(!defined('NL')) define('NL',"\n");
16
require_once(DOKU_INC.'inc/init.php');
17
18
// Main (don't run when UNIT test)
19
if(!defined('SIMPLE_TEST')){
20
    header('Content-Type: text/css; charset=utf-8');
21
    css_out();
22
}
23
24
25
// ---------------------- functions ------------------------------
26
27
/**
28
 * Output all needed Styles
29
 *
30
 * @author Andreas Gohr <[email protected]>
31
 */
32
function css_out(){
33
    global $conf;
34
    global $lang;
35
    global $config_cascade;
36
    global $INPUT;
37
38
    if ($INPUT->str('s') == 'feed') {
39
        $mediatypes = array('feed');
40
        $type = 'feed';
41
    } else {
42
        $mediatypes = array('screen', 'all', 'print', 'speech');
43
        $type = '';
44
    }
45
46
    // decide from where to get the template
47
    $tpl = trim(preg_replace('/[^\w-]+/','',$INPUT->str('t')));
48
    if(!$tpl) $tpl = $conf['template'];
49
50
    // load style.ini
51
    $styleUtil = new \dokuwiki\StyleUtils($tpl, $INPUT->bool('preview'));
52
    $styleini = $styleUtil->cssStyleini();
53
54
    // cache influencers
55
    $tplinc = tpl_incdir($tpl);
56
    $cache_files = getConfigFiles('main');
57
    $cache_files[] = $tplinc.'style.ini';
58
    $cache_files[] = DOKU_CONF."tpl/$tpl/style.ini";
59
    $cache_files[] = __FILE__;
60
    if($INPUT->bool('preview')) $cache_files[] = $conf['cachedir'].'/preview.ini';
61
62
    // Array of needed files and their web locations, the latter ones
63
    // are needed to fix relative paths in the stylesheets
64
    $media_files = array();
65
    foreach($mediatypes as $mediatype) {
66
        $files = array();
67
68
        // load core styles
69
        $files[DOKU_INC.'lib/styles/'.$mediatype.'.css'] = DOKU_BASE.'lib/styles/';
70
71
        // load jQuery-UI theme
72
        if ($mediatype == 'screen') {
73
            $files[DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] =
74
                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($styleini['stylesheets'][$mediatype])) {
80
            $files = array_merge($files, $styleini['stylesheets'][$mediatype]);
81
        }
82
        // load user styles
83
        if(isset($config_cascade['userstyle'][$mediatype]) and is_array($config_cascade['userstyle'][$mediatype])) {
84
            foreach($config_cascade['userstyle'][$mediatype] as $userstyle) {
85
                $files[$userstyle] = DOKU_BASE;
86
            }
87
        }
88
89
        // Let plugins decide to either put more styles here or to remove some
90
        $media_files[$mediatype] = css_filewrapper($mediatype, $files);
91
        $CSSEvt = new Event('CSS_STYLES_INCLUDED', $media_files[$mediatype]);
92
93
        // Make it preventable.
94
        if ( $CSSEvt->advise_before() ) {
95
            $cache_files = array_merge($cache_files, array_keys($media_files[$mediatype]['files']));
96
        } else {
97
            // unset if prevented. Nothing will be printed for this mediatype.
98
            unset($media_files[$mediatype]);
99
        }
100
101
        // finish event.
102
        $CSSEvt->advise_after();
103
    }
104
105
    // The generated script depends on some dynamic options
106
    $cache = new Cache(
107
        'styles' .
108
        $_SERVER['HTTP_HOST'] .
109
        $_SERVER['SERVER_PORT'] .
110
        $INPUT->bool('preview') .
111
        DOKU_BASE .
112
        $tpl .
113
        $type,
114
        '.css'
115
    );
116
    $cache->setEvent('CSS_CACHE_USE');
117
118
    // check cache age & handle conditional request
119
    // This may exit if a cache can be used
120
    $cache_ok = $cache->useCache(array('files' => $cache_files));
121
    http_cached($cache->cache, $cache_ok);
122
123
    // start output buffering
124
    ob_start();
125
126
    // Fire CSS_STYLES_INCLUDED for one last time to let the
127
    // plugins decide whether to include the DW default styles.
128
    // This can be done by preventing the Default.
129
    $media_files['DW_DEFAULT'] = css_filewrapper('DW_DEFAULT');
130
    Event::createAndTrigger('CSS_STYLES_INCLUDED', $media_files['DW_DEFAULT'], 'css_defaultstyles');
131
132
    // build the stylesheet
133
    foreach ($mediatypes as $mediatype) {
134
135
        // Check if there is a wrapper set for this type.
136
        if ( !isset($media_files[$mediatype]) ) {
137
            continue;
138
        }
139
140
        $cssData = $media_files[$mediatype];
141
142
        // Print the styles.
143
        print NL;
144
        if ( $cssData['encapsulate'] === true ) print $cssData['encapsulationPrefix'] . ' {';
145
        print '/* START '.$cssData['mediatype'].' styles */'.NL;
146
147
        // load files
148
        foreach($cssData['files'] as $file => $location){
149
            $display = str_replace(fullpath(DOKU_INC), '', fullpath($file));
150
            print "\n/* XXXXXXXXX $display XXXXXXXXX */\n";
151
            print css_loadfile($file, $location);
152
        }
153
154
        print NL;
155
        if ( $cssData['encapsulate'] === true ) print '} /* /@media ';
156
        else print '/*';
157
        print ' END '.$cssData['mediatype'].' styles */'.NL;
158
    }
159
160
    // end output buffering and get contents
161
    $css = ob_get_contents();
162
    ob_end_clean();
163
164
    // strip any source maps
165
    stripsourcemaps($css);
166
167
    // apply style replacements
168
    $css = css_applystyle($css, $styleini['replacements']);
169
170
    // parse less
171
    $css = css_parseless($css);
172
173
    // compress whitespace and comments
174
    if($conf['compress']){
175
        $css = css_compress($css);
176
    }
177
178
    // embed small images right into the stylesheet
179
    if($conf['cssdatauri']){
180
        $base = preg_quote(DOKU_BASE,'#');
181
        $css = preg_replace_callback('#(url\([ \'"]*)('.$base.')(.*?(?:\.(png|gif)))#i','css_datauri',$css);
182
    }
183
184
    http_cached_finish($cache->cache, $css);
185
}
186
187
/**
188
 * Uses phpless to parse LESS in our CSS
189
 *
190
 * most of this function is error handling to show a nice useful error when
191
 * LESS compilation fails
192
 *
193
 * @param string $css
194
 * @return string
195
 */
196
function css_parseless($css) {
197
    global $conf;
198
199
    $less = new lessc();
200
    $less->importDir = array(DOKU_INC);
201
    $less->setPreserveComments(!$conf['compress']);
202
203
    if (defined('DOKU_UNITTEST')){
204
        $less->importDir[] = TMP_DIR;
205
    }
206
207
    try {
208
        return $less->compile($css);
209
    } catch(Exception $e) {
210
        // get exception message
211
        $msg = str_replace(array("\n", "\r", "'"), array(), $e->getMessage());
212
213
        // try to use line number to find affected file
214
        if(preg_match('/line: (\d+)$/', $msg, $m)){
215
            $msg = substr($msg, 0, -1* strlen($m[0])); //remove useless linenumber
216
            $lno = $m[1];
217
218
            // walk upwards to last include
219
            $lines = explode("\n", $css);
220
            for($i=$lno-1; $i>=0; $i--){
221
                if(preg_match('/\/(\* XXXXXXXXX )(.*?)( XXXXXXXXX \*)\//', $lines[$i], $m)){
222
                    // we found it, add info to message
223
                    $msg .= ' in '.$m[2].' at line '.($lno-$i);
224
                    break;
225
                }
226
            }
227
        }
228
229
        // something went wrong
230
        $error = 'A fatal error occured during compilation of the CSS files. '.
231
            'If you recently installed a new plugin or template it '.
232
            'might be broken and you should try disabling it again. ['.$msg.']';
233
234
        echo ".dokuwiki:before {
235
            content: '$error';
236
            background-color: red;
237
            display: block;
238
            background-color: #fcc;
239
            border-color: #ebb;
240
            color: #000;
241
            padding: 0.5em;
242
        }";
243
244
        exit;
245
    }
246
}
247
248
/**
249
 * Does placeholder replacements in the style according to
250
 * the ones defined in a templates style.ini file
251
 *
252
 * This also adds the ini defined placeholders as less variables
253
 * (sans the surrounding __ and with a ini_ prefix)
254
 *
255
 * @author Andreas Gohr <[email protected]>
256
 *
257
 * @param string $css
258
 * @param array $replacements  array(placeholder => value)
259
 * @return string
260
 */
261
function css_applystyle($css, $replacements) {
262
    // we convert ini replacements to LESS variable names
263
    // and build a list of variable: value; pairs
264
    $less = '';
265
    foreach((array) $replacements as $key => $value) {
266
        $lkey = trim($key, '_');
267
        $lkey = '@ini_'.$lkey;
268
        $less .= "$lkey: $value;\n";
269
270
        $replacements[$key] = $lkey;
271
    }
272
273
    // we now replace all old ini replacements with LESS variables
274
    $css = strtr($css, $replacements);
275
276
    // now prepend the list of LESS variables as the very first thing
277
    $css = $less.$css;
278
    return $css;
279
}
280
281
/**
282
 * Wrapper for the files, content and mediatype for the event CSS_STYLES_INCLUDED
283
 *
284
 * @author Gerry Weißbach <[email protected]>
285
 *
286
 * @param string $mediatype type ofthe current media files/content set
287
 * @param array $files set of files that define the current mediatype
288
 * @return array
289
 */
290
function css_filewrapper($mediatype, $files=array()){
291
    return array(
292
            'files'                 => $files,
293
            'mediatype'             => $mediatype,
294
            'encapsulate'           => $mediatype != 'all',
295
            'encapsulationPrefix'   => '@media '.$mediatype
296
        );
297
}
298
299
/**
300
 * Prints the @media encapsulated default styles of DokuWiki
301
 *
302
 * @author Gerry Weißbach <[email protected]>
303
 *
304
 * This function is being called by a CSS_STYLES_INCLUDED event
305
 * The event can be distinguished by the mediatype which is:
306
 *   DW_DEFAULT
307
 */
308
function css_defaultstyles(){
309
    // print the default classes for interwiki links and file downloads
310
    print '@media screen {';
311
    css_interwiki();
312
    css_filetypes();
313
    print '}';
314
}
315
316
/**
317
 * Prints classes for interwikilinks
318
 *
319
 * Interwiki links have two classes: 'interwiki' and 'iw_$name>' where
320
 * $name is the identifier given in the config. All Interwiki links get
321
 * an default style with a default icon. If a special icon is available
322
 * for an interwiki URL it is set in it's own class. Both classes can be
323
 * overwritten in the template or userstyles.
324
 *
325
 * @author Andreas Gohr <[email protected]>
326
 */
327
function css_interwiki(){
328
329
    // default style
330
    echo 'a.interwiki {';
331
    echo ' background: transparent url('.DOKU_BASE.'lib/images/interwiki.png) 0px 1px no-repeat;';
332
    echo ' padding: 1px 0px 1px 16px;';
333
    echo '}';
334
335
    // additional styles when icon available
336
    $iwlinks = getInterwiki();
337
    foreach(array_keys($iwlinks) as $iw){
338
        $class = preg_replace('/[^_\-a-z0-9]+/i','_',$iw);
339
        if(file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.png')){
340
            echo "a.iw_$class {";
341
            echo '  background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.png)';
342
            echo '}';
343
        }elseif(file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.gif')){
344
            echo "a.iw_$class {";
345
            echo '  background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.gif)';
346
            echo '}';
347
        }
348
    }
349
}
350
351
/**
352
 * Prints classes for file download links
353
 *
354
 * @author Andreas Gohr <[email protected]>
355
 */
356
function css_filetypes(){
357
358
    // default style
359
    echo '.mediafile {';
360
    echo ' background: transparent url('.DOKU_BASE.'lib/images/fileicons/svg/file.svg) 0px 1px no-repeat;';
361
    echo ' background-size: 1.2em;';
362
    echo ' padding-left: 1.5em;';
363
    echo '}';
364
365
    // additional styles when icon available
366
    // scan directory for all icons
367
    $exts = array();
368
    if($dh = opendir(DOKU_INC.'lib/images/fileicons/svg')){
369
        while(false !== ($file = readdir($dh))){
370
            if(preg_match('/(.*?)\.svg$/i',$file, $match)){
371
                $exts[] = strtolower($match[1]);
372
            }
373
        }
374
        closedir($dh);
375
    }
376
    foreach($exts as $ext){
377
        $class = preg_replace('/[^_\-a-z0-9]+/','_',$ext);
378
        echo ".mf_$class {";
379
        echo '  background-image: url('.DOKU_BASE.'lib/images/fileicons/svg/'.$ext.'.svg)';
380
        echo '}';
381
    }
382
}
383
384
/**
385
 * Loads a given file and fixes relative URLs with the
386
 * given location prefix
387
 *
388
 * @param string $file file system path
389
 * @param string $location
390
 * @return string
391
 */
392
function css_loadfile($file,$location=''){
393
    $css_file = new DokuCssFile($file);
394
    return $css_file->load($location);
395
}
396
397
/**
398
 *  Helper class to abstract loading of css/less files
399
 *
400
 *  @author Chris Smith <[email protected]>
401
 */
402
class DokuCssFile {
403
404
    protected $filepath;             // file system path to the CSS/Less file
405
    protected $location;             // base url location of the CSS/Less file
406
    protected $relative_path = null;
407
408
    public function __construct($file) {
409
        $this->filepath = $file;
410
    }
411
412
    /**
413
     * Load the contents of the css/less file and adjust any relative paths/urls (relative to this file) to be
414
     * relative to the dokuwiki root: the web root (DOKU_BASE) for most files; the file system root (DOKU_INC)
415
     * for less files.
416
     *
417
     * @param   string   $location   base url for this file
418
     * @return  string               the CSS/Less contents of the file
419
     */
420
    public function load($location='') {
421
        if (!file_exists($this->filepath)) return '';
422
423
        $css = io_readFile($this->filepath);
424
        if (!$location) return $css;
425
426
        $this->location = $location;
427
428
        $css = preg_replace_callback('#(url\( *)([\'"]?)(.*?)(\2)( *\))#',array($this,'replacements'),$css);
429
        $css = preg_replace_callback('#(@import\s+)([\'"])(.*?)(\2)#',array($this,'replacements'),$css);
430
431
        return $css;
432
    }
433
434
    /**
435
     * Get the relative file system path of this file, relative to dokuwiki's root folder, DOKU_INC
436
     *
437
     * @return string   relative file system path
438
     */
439
    protected function getRelativePath(){
440
441
        if (is_null($this->relative_path)) {
442
            $basedir = array(DOKU_INC);
443
444
            // during testing, files may be found relative to a second base dir, TMP_DIR
445
            if (defined('DOKU_UNITTEST')) {
446
                $basedir[] = realpath(TMP_DIR);
447
            }
448
449
            $basedir = array_map('preg_quote_cb', $basedir);
450
            $regex = '/^('.join('|',$basedir).')/';
451
            $this->relative_path = preg_replace($regex, '', dirname($this->filepath));
452
        }
453
454
        return $this->relative_path;
455
    }
456
457
    /**
458
     * preg_replace callback to adjust relative urls from relative to this file to relative
459
     * to the appropriate dokuwiki root location as described in the code
460
     *
461
     * @param  array    see http://php.net/preg_replace_callback
462
     * @return string   see http://php.net/preg_replace_callback
463
     */
464
    public function replacements($match) {
465
466
        if (preg_match('#^(/|data:|https?://)#', $match[3])) { // not a relative url? - no adjustment required
467
            return $match[0];
468
        } elseif (substr($match[3], -5) == '.less') { // a less file import? - requires a file system location
469
            if ($match[3][0] != '/') {
470
                $match[3] = $this->getRelativePath() . '/' . $match[3];
471
            }
472
        } else { // everything else requires a url adjustment
473
            $match[3] = $this->location . $match[3];
474
        }
475
476
        return join('',array_slice($match,1));
477
    }
478
}
479
480
/**
481
 * Convert local image URLs to data URLs if the filesize is small
482
 *
483
 * Callback for preg_replace_callback
484
 *
485
 * @param array $match
486
 * @return string
487
 */
488
function css_datauri($match){
489
    global $conf;
490
491
    $pre   = unslash($match[1]);
492
    $base  = unslash($match[2]);
493
    $url   = unslash($match[3]);
494
    $ext   = unslash($match[4]);
495
496
    $local = DOKU_INC.$url;
497
    $size  = @filesize($local);
498
    if($size && $size < $conf['cssdatauri']){
499
        $data = base64_encode(file_get_contents($local));
500
    }
501
    if (!empty($data)){
502
        $url = 'data:image/'.$ext.';base64,'.$data;
503
    }else{
504
        $url = $base.$url;
505
    }
506
    return $pre.$url;
507
}
508
509
510
/**
511
 * Returns a list of possible Plugin Styles (no existance check here)
512
 *
513
 * @author Andreas Gohr <[email protected]>
514
 *
515
 * @param string $mediatype
516
 * @return array
517
 */
518
function css_pluginstyles($mediatype='screen'){
519
    $list = array();
520
    $plugins = plugin_list();
521
    foreach ($plugins as $p){
522
        $list[DOKU_PLUGIN."$p/$mediatype.css"]  = DOKU_BASE."lib/plugins/$p/";
523
        $list[DOKU_PLUGIN."$p/$mediatype.less"]  = DOKU_BASE."lib/plugins/$p/";
524
        // alternative for screen.css
525
        if ($mediatype=='screen') {
526
            $list[DOKU_PLUGIN."$p/style.css"]  = DOKU_BASE."lib/plugins/$p/";
527
            $list[DOKU_PLUGIN."$p/style.less"]  = DOKU_BASE."lib/plugins/$p/";
528
        }
529
    }
530
    return $list;
531
}
532
533
/**
534
 * Very simple CSS optimizer
535
 *
536
 * @author Andreas Gohr <[email protected]>
537
 *
538
 * @param string $css
539
 * @return string
540
 */
541
function css_compress($css){
542
    // replace quoted strings with placeholder
543
    $quote_storage = [];
544
545
    $quote_cb = function ($match) use (&$quote_storage) {
546
        $quote_storage[] = $match[0];
547
        return '"STR'.(count($quote_storage)-1).'"';
548
    };
549
550
    $css = preg_replace_callback('/(([\'"]).*?(?<!\\\\)\2)/', $quote_cb, $css);
551
552
    // strip comments through a callback
553
    $css = preg_replace_callback('#(/\*)(.*?)(\*/)#s','css_comment_cb',$css);
554
555
    // strip (incorrect but common) one line comments
556
    $css = preg_replace_callback('/^.*\/\/.*$/m','css_onelinecomment_cb',$css);
557
558
    // strip whitespaces
559
    $css = preg_replace('![\r\n\t ]+!',' ',$css);
560
    $css = preg_replace('/ ?([;,{}\/]) ?/','\\1',$css);
561
    $css = preg_replace('/ ?: /',':',$css);
562
563
    // number compression
564
    $css = preg_replace(
565
        '/([: ])0+(\.\d+?)0*((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/',
566
        '$1$2$3',
567
        $css
568
    ); // "0.1em" to ".1em", "1.10em" to "1.1em"
569
    $css = preg_replace(
570
        '/([: ])\.(0)+((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/',
571
        '$1$2',
572
        $css
573
    ); // ".0em" to "0"
574
    $css = preg_replace(
575
        '/([: ]0)0*(\.0*)?((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
576
        '$1',
577
        $css
578
    ); // "0.0em" to "0"
579
    $css = preg_replace(
580
        '/([: ]\d+)(\.0*)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
581
        '$1$3',
582
        $css
583
    ); // "1.0em" to "1em"
584
    $css = preg_replace(
585
        '/([: ])0+(\d+|\d*\.\d+)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
586
        '$1$2$3',
587
        $css
588
    ); // "001em" to "1em"
589
590
    // shorten attributes (1em 1em 1em 1em -> 1em)
591
    $css = preg_replace(
592
        '/(?<![\w\-])((?:margin|padding|border|border-(?:width|radius)):)([\w\.]+)( \2)+(?=[;\}]| !)/',
593
        '$1$2',
594
        $css
595
    ); // "1em 1em 1em 1em" to "1em"
596
    $css = preg_replace(
597
        '/(?<![\w\-])((?:margin|padding|border|border-(?:width)):)([\w\.]+) ([\w\.]+) \2 \3(?=[;\}]| !)/',
598
        '$1$2 $3',
599
        $css
600
    ); // "1em 2em 1em 2em" to "1em 2em"
601
602
    // shorten colors
603
    $css = preg_replace(
604
        "/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3(?=[^\{]*[;\}])/",
605
        "#\\1\\2\\3",
606
        $css
607
    );
608
609
    // replace back protected strings
610
    $quote_back_cb = function ($match) use (&$quote_storage) {
611
        return $quote_storage[$match[1]];
612
    };
613
614
    $css = preg_replace_callback('/"STR(\d+)"/', $quote_back_cb, $css);
615
    $css = trim($css);
616
617
    return $css;
618
}
619
620
/**
621
 * Callback for css_compress()
622
 *
623
 * Keeps short comments (< 5 chars) to maintain typical browser hacks
624
 *
625
 * @author Andreas Gohr <[email protected]>
626
 *
627
 * @param array $matches
628
 * @return string
629
 */
630
function css_comment_cb($matches){
631
    if(strlen($matches[2]) > 4) return '';
632
    return $matches[0];
633
}
634
635
/**
636
 * Callback for css_compress()
637
 *
638
 * Strips one line comments but makes sure it will not destroy url() constructs with slashes
639
 *
640
 * @param array $matches
641
 * @return string
642
 */
643
function css_onelinecomment_cb($matches) {
644
    $line = $matches[0];
645
646
    $i = 0;
647
    $len = strlen($line);
648
649
    while ($i< $len){
650
        $nextcom = strpos($line, '//', $i);
651
        $nexturl = stripos($line, 'url(', $i);
652
653
        if($nextcom === false) {
654
            // no more comments, we're done
655
            $i = $len;
656
            break;
657
        }
658
659
        if($nexturl === false || $nextcom < $nexturl) {
660
            // no url anymore, strip comment and be done
661
            $i = $nextcom;
662
            break;
663
        }
664
665
        // we have an upcoming url
666
        $i = strpos($line, ')', $nexturl);
667
    }
668
669
    return substr($line, 0, $i);
670
}
671
672
//Setup VIM: ex: et ts=4 :
673