Failed Conditions
Push — refactorCachePSR2 ( 0db577 )
by Michael
05:36 queued 02:56
created

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