Completed
Push — syntaxtableclasses ( 0c4c02...2e0ebe )
by Andreas
05:40
created

common.php ➔ ml()   F

Complexity

Conditions 26
Paths 4488

Size

Total Lines 80
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 55
nc 4488
nop 5
dl 0
loc 80
rs 2.294
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Common DokuWiki functions
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')) die('meh.');
10
11
/**
12
 * These constants are used with the recents function
13
 */
14
define('RECENTS_SKIP_DELETED', 2);
15
define('RECENTS_SKIP_MINORS', 4);
16
define('RECENTS_SKIP_SUBSPACES', 8);
17
define('RECENTS_MEDIA_CHANGES', 16);
18
define('RECENTS_MEDIA_PAGES_MIXED', 32);
19
20
/**
21
 * Wrapper around htmlspecialchars()
22
 *
23
 * @author Andreas Gohr <[email protected]>
24
 * @see    htmlspecialchars()
25
 *
26
 * @param string $string the string being converted
27
 * @return string converted string
28
 */
29
function hsc($string) {
30
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
31
}
32
33
/**
34
 * Checks if the given input is blank
35
 *
36
 * This is similar to empty() but will return false for "0".
37
 *
38
 * Please note: when you pass uninitialized variables, they will implicitly be created
39
 * with a NULL value without warning.
40
 *
41
 * To avoid this it's recommended to guard the call with isset like this:
42
 *
43
 * (isset($foo) && !blank($foo))
44
 * (!isset($foo) || blank($foo))
45
 *
46
 * @param $in
47
 * @param bool $trim Consider a string of whitespace to be blank
48
 * @return bool
49
 */
50
function blank(&$in, $trim = false) {
51
    if(is_null($in)) return true;
52
    if(is_array($in)) return empty($in);
53
    if($in === "\0") return true;
54
    if($trim && trim($in) === '') return true;
55
    if(strlen($in) > 0) return false;
56
    return empty($in);
57
}
58
59
/**
60
 * print a newline terminated string
61
 *
62
 * You can give an indention as optional parameter
63
 *
64
 * @author Andreas Gohr <[email protected]>
65
 *
66
 * @param string $string  line of text
67
 * @param int    $indent  number of spaces indention
68
 */
69
function ptln($string, $indent = 0) {
70
    echo str_repeat(' ', $indent)."$string\n";
71
}
72
73
/**
74
 * strips control characters (<32) from the given string
75
 *
76
 * @author Andreas Gohr <[email protected]>
77
 *
78
 * @param string $string being stripped
79
 * @return string
80
 */
81
function stripctl($string) {
82
    return preg_replace('/[\x00-\x1F]+/s', '', $string);
83
}
84
85
/**
86
 * Return a secret token to be used for CSRF attack prevention
87
 *
88
 * @author  Andreas Gohr <[email protected]>
89
 * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
90
 * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
91
 *
92
 * @return  string
93
 */
94
function getSecurityToken() {
95
    /** @var Input $INPUT */
96
    global $INPUT;
97
    return PassHash::hmac('md5', session_id().$INPUT->server->str('REMOTE_USER'), auth_cookiesalt());
0 ignored issues
show
Bug introduced by
It seems like auth_cookiesalt() can also be of type boolean; however, PassHash::hmac() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
98
}
99
100
/**
101
 * Check the secret CSRF token
102
 *
103
 * @param null|string $token security token or null to read it from request variable
104
 * @return bool success if the token matched
105
 */
106
function checkSecurityToken($token = null) {
107
    /** @var Input $INPUT */
108
    global $INPUT;
109
    if(!$INPUT->server->str('REMOTE_USER')) return true; // no logged in user, no need for a check
110
111
    if(is_null($token)) $token = $INPUT->str('sectok');
112
    if(getSecurityToken() != $token) {
113
        msg('Security Token did not match. Possible CSRF attack.', -1);
114
        return false;
115
    }
116
    return true;
117
}
118
119
/**
120
 * Print a hidden form field with a secret CSRF token
121
 *
122
 * @author  Andreas Gohr <[email protected]>
123
 *
124
 * @param bool $print  if true print the field, otherwise html of the field is returned
125
 * @return string html of hidden form field
126
 */
127
function formSecurityToken($print = true) {
128
    $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n";
129
    if($print) echo $ret;
130
    return $ret;
131
}
132
133
/**
134
 * Determine basic information for a request of $id
135
 *
136
 * @author Andreas Gohr <[email protected]>
137
 * @author Chris Smith <[email protected]>
138
 *
139
 * @param string $id         pageid
140
 * @param bool   $htmlClient add info about whether is mobile browser
141
 * @return array with info for a request of $id
142
 *
143
 */
144
function basicinfo($id, $htmlClient=true){
145
    global $USERINFO;
146
    /* @var Input $INPUT */
147
    global $INPUT;
148
149
    // set info about manager/admin status.
150
    $info = array();
151
    $info['isadmin']   = false;
152
    $info['ismanager'] = false;
153
    if($INPUT->server->has('REMOTE_USER')) {
154
        $info['userinfo']   = $USERINFO;
155
        $info['perm']       = auth_quickaclcheck($id);
156
        $info['client']     = $INPUT->server->str('REMOTE_USER');
157
158
        if($info['perm'] == AUTH_ADMIN) {
159
            $info['isadmin']   = true;
160
            $info['ismanager'] = true;
161
        } elseif(auth_ismanager()) {
162
            $info['ismanager'] = true;
163
        }
164
165
        // if some outside auth were used only REMOTE_USER is set
166
        if(!$info['userinfo']['name']) {
167
            $info['userinfo']['name'] = $INPUT->server->str('REMOTE_USER');
168
        }
169
170
    } else {
171
        $info['perm']       = auth_aclcheck($id, '', null);
172
        $info['client']     = clientIP(true);
173
    }
174
175
    $info['namespace'] = getNS($id);
176
177
    // mobile detection
178
    if ($htmlClient) {
179
        $info['ismobile'] = clientismobile();
180
    }
181
182
    return $info;
183
 }
184
185
/**
186
 * Return info about the current document as associative
187
 * array.
188
 *
189
 * @author Andreas Gohr <[email protected]>
190
 *
191
 * @return array with info about current document
192
 */
193
function pageinfo() {
194
    global $ID;
195
    global $REV;
196
    global $RANGE;
197
    global $lang;
198
    /* @var Input $INPUT */
199
    global $INPUT;
200
201
    $info = basicinfo($ID);
202
203
    // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
204
    // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
205
    $info['id']  = $ID;
206
    $info['rev'] = $REV;
207
208
    if($INPUT->server->has('REMOTE_USER')) {
209
        $sub = new Subscription();
210
        $info['subscribed'] = $sub->user_subscription();
211
    } else {
212
        $info['subscribed'] = false;
213
    }
214
215
    $info['locked']     = checklock($ID);
216
    $info['filepath']   = fullpath(wikiFN($ID));
217
    $info['exists']     = file_exists($info['filepath']);
218
    $info['currentrev'] = @filemtime($info['filepath']);
219
    if($REV) {
220
        //check if current revision was meant
221
        if($info['exists'] && ($info['currentrev'] == $REV)) {
222
            $REV = '';
223
        } elseif($RANGE) {
224
            //section editing does not work with old revisions!
225
            $REV   = '';
226
            $RANGE = '';
227
            msg($lang['nosecedit'], 0);
228
        } else {
229
            //really use old revision
230
            $info['filepath'] = fullpath(wikiFN($ID, $REV));
231
            $info['exists']   = file_exists($info['filepath']);
232
        }
233
    }
234
    $info['rev'] = $REV;
235
    if($info['exists']) {
236
        $info['writable'] = (is_writable($info['filepath']) &&
237
            ($info['perm'] >= AUTH_EDIT));
238
    } else {
239
        $info['writable'] = ($info['perm'] >= AUTH_CREATE);
240
    }
241
    $info['editable'] = ($info['writable'] && empty($info['locked']));
242
    $info['lastmod']  = @filemtime($info['filepath']);
243
244
    //load page meta data
245
    $info['meta'] = p_get_metadata($ID);
246
247
    //who's the editor
248
    $pagelog = new PageChangeLog($ID, 1024);
249
    if($REV) {
250
        $revinfo = $pagelog->getRevisionInfo($REV);
251
    } else {
252
        if(!empty($info['meta']['last_change']) && is_array($info['meta']['last_change'])) {
253
            $revinfo = $info['meta']['last_change'];
254
        } else {
255
            $revinfo = $pagelog->getRevisionInfo($info['lastmod']);
256
            // cache most recent changelog line in metadata if missing and still valid
257
            if($revinfo !== false) {
258
                $info['meta']['last_change'] = $revinfo;
259
                p_set_metadata($ID, array('last_change' => $revinfo));
260
            }
261
        }
262
    }
263
    //and check for an external edit
264
    if($revinfo !== false && $revinfo['date'] != $info['lastmod']) {
265
        // cached changelog line no longer valid
266
        $revinfo                     = false;
267
        $info['meta']['last_change'] = $revinfo;
268
        p_set_metadata($ID, array('last_change' => $revinfo));
269
    }
270
271
    $info['ip']   = $revinfo['ip'];
272
    $info['user'] = $revinfo['user'];
273
    $info['sum']  = $revinfo['sum'];
274
    // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
275
    // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
276
277
    if($revinfo['user']) {
278
        $info['editor'] = $revinfo['user'];
279
    } else {
280
        $info['editor'] = $revinfo['ip'];
281
    }
282
283
    // draft
284
    $draft = getCacheName($info['client'].$ID, '.draft');
285
    if(file_exists($draft)) {
286
        if(@filemtime($draft) < @filemtime(wikiFN($ID))) {
287
            // remove stale draft
288
            @unlink($draft);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
289
        } else {
290
            $info['draft'] = $draft;
291
        }
292
    }
293
294
    return $info;
295
}
296
297
/**
298
 * Return information about the current media item as an associative array.
299
 *
300
 * @return array with info about current media item
301
 */
302
function mediainfo(){
303
    global $NS;
304
    global $IMG;
305
306
    $info = basicinfo("$NS:*");
307
    $info['image'] = $IMG;
308
309
    return $info;
310
}
311
312
/**
313
 * Build an string of URL parameters
314
 *
315
 * @author Andreas Gohr
316
 *
317
 * @param array  $params    array with key-value pairs
318
 * @param string $sep       series of pairs are separated by this character
319
 * @return string query string
320
 */
321
function buildURLparams($params, $sep = '&amp;') {
322
    $url = '';
323
    $amp = false;
324
    foreach($params as $key => $val) {
325
        if($amp) $url .= $sep;
326
327
        $url .= rawurlencode($key).'=';
328
        $url .= rawurlencode((string) $val);
329
        $amp = true;
330
    }
331
    return $url;
332
}
333
334
/**
335
 * Build an string of html tag attributes
336
 *
337
 * Skips keys starting with '_', values get HTML encoded
338
 *
339
 * @author Andreas Gohr
340
 *
341
 * @param array $params    array with (attribute name-attribute value) pairs
342
 * @param bool  $skipempty skip empty string values?
343
 * @return string
344
 */
345
function buildAttributes($params, $skipempty = false) {
346
    $url   = '';
347
    $white = false;
348
    foreach($params as $key => $val) {
349
        if($key{0} == '_') continue;
350
        if($val === '' && $skipempty) continue;
351
        if($white) $url .= ' ';
352
353
        $url .= $key.'="';
354
        $url .= htmlspecialchars($val);
355
        $url .= '"';
356
        $white = true;
357
    }
358
    return $url;
359
}
360
361
/**
362
 * This builds the breadcrumb trail and returns it as array
363
 *
364
 * @author Andreas Gohr <[email protected]>
365
 *
366
 * @return string[] with the data: array(pageid=>name, ... )
367
 */
368
function breadcrumbs() {
369
    // we prepare the breadcrumbs early for quick session closing
370
    static $crumbs = null;
371
    if($crumbs != null) return $crumbs;
372
373
    global $ID;
374
    global $ACT;
375
    global $conf;
376
377
    //first visit?
378
    $crumbs = isset($_SESSION[DOKU_COOKIE]['bc']) ? $_SESSION[DOKU_COOKIE]['bc'] : array();
379
    //we only save on show and existing wiki documents
380
    $file = wikiFN($ID);
381
    if($ACT != 'show' || !file_exists($file)) {
382
        $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
383
        return $crumbs;
384
    }
385
386
    // page names
387
    $name = noNSorNS($ID);
388
    if(useHeading('navigation')) {
389
        // get page title
390
        $title = p_get_first_heading($ID, METADATA_RENDER_USING_SIMPLE_CACHE);
391
        if($title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
392
            $name = $title;
393
        }
394
    }
395
396
    //remove ID from array
397
    if(isset($crumbs[$ID])) {
398
        unset($crumbs[$ID]);
399
    }
400
401
    //add to array
402
    $crumbs[$ID] = $name;
403
    //reduce size
404
    while(count($crumbs) > $conf['breadcrumbs']) {
405
        array_shift($crumbs);
406
    }
407
    //save to session
408
    $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
409
    return $crumbs;
410
}
411
412
/**
413
 * Filter for page IDs
414
 *
415
 * This is run on a ID before it is outputted somewhere
416
 * currently used to replace the colon with something else
417
 * on Windows (non-IIS) systems and to have proper URL encoding
418
 *
419
 * See discussions at https://github.com/splitbrain/dokuwiki/pull/84 and
420
 * https://github.com/splitbrain/dokuwiki/pull/173 why we use a whitelist of
421
 * unaffected servers instead of blacklisting affected servers here.
422
 *
423
 * Urlencoding is ommitted when the second parameter is false
424
 *
425
 * @author Andreas Gohr <[email protected]>
426
 *
427
 * @param string $id pageid being filtered
428
 * @param bool   $ue apply urlencoding?
429
 * @return string
430
 */
431
function idfilter($id, $ue = true) {
432
    global $conf;
433
    /* @var Input $INPUT */
434
    global $INPUT;
435
436
    if($conf['useslash'] && $conf['userewrite']) {
437
        $id = strtr($id, ':', '/');
438
    } elseif(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
439
        $conf['userewrite'] &&
440
        strpos($INPUT->server->str('SERVER_SOFTWARE'), 'Microsoft-IIS') === false
441
    ) {
442
        $id = strtr($id, ':', ';');
443
    }
444
    if($ue) {
445
        $id = rawurlencode($id);
446
        $id = str_replace('%3A', ':', $id); //keep as colon
447
        $id = str_replace('%3B', ';', $id); //keep as semicolon
448
        $id = str_replace('%2F', '/', $id); //keep as slash
449
    }
450
    return $id;
451
}
452
453
/**
454
 * This builds a link to a wikipage
455
 *
456
 * It handles URL rewriting and adds additional parameters
457
 *
458
 * @author Andreas Gohr <[email protected]>
459
 *
460
 * @param string       $id             page id, defaults to start page
461
 * @param string|array $urlParameters  URL parameters, associative array recommended
462
 * @param bool         $absolute       request an absolute URL instead of relative
463
 * @param string       $separator      parameter separator
464
 * @return string
465
 */
466
function wl($id = '', $urlParameters = '', $absolute = false, $separator = '&amp;') {
467
    global $conf;
468
    if(is_array($urlParameters)) {
469
        if(isset($urlParameters['rev']) && !$urlParameters['rev']) unset($urlParameters['rev']);
470
        if(isset($urlParameters['at']) && $conf['date_at_format']) $urlParameters['at'] = date($conf['date_at_format'],$urlParameters['at']);
471
        $urlParameters = buildURLparams($urlParameters, $separator);
472
    } else {
473
        $urlParameters = str_replace(',', $separator, $urlParameters);
474
    }
475
    if($id === '') {
476
        $id = $conf['start'];
477
    }
478
    $id = idfilter($id);
479
    if($absolute) {
480
        $xlink = DOKU_URL;
481
    } else {
482
        $xlink = DOKU_BASE;
483
    }
484
485
    if($conf['userewrite'] == 2) {
486
        $xlink .= DOKU_SCRIPT.'/'.$id;
487
        if($urlParameters) $xlink .= '?'.$urlParameters;
488
    } elseif($conf['userewrite']) {
489
        $xlink .= $id;
490
        if($urlParameters) $xlink .= '?'.$urlParameters;
491
    } elseif($id) {
492
        $xlink .= DOKU_SCRIPT.'?id='.$id;
493
        if($urlParameters) $xlink .= $separator.$urlParameters;
494
    } else {
495
        $xlink .= DOKU_SCRIPT;
496
        if($urlParameters) $xlink .= '?'.$urlParameters;
497
    }
498
499
    return $xlink;
500
}
501
502
/**
503
 * This builds a link to an alternate page format
504
 *
505
 * Handles URL rewriting if enabled. Follows the style of wl().
506
 *
507
 * @author Ben Coburn <[email protected]>
508
 * @param string       $id             page id, defaults to start page
509
 * @param string       $format         the export renderer to use
510
 * @param string|array $urlParameters  URL parameters, associative array recommended
511
 * @param bool         $abs            request an absolute URL instead of relative
512
 * @param string       $sep            parameter separator
513
 * @return string
514
 */
515
function exportlink($id = '', $format = 'raw', $urlParameters = '', $abs = false, $sep = '&amp;') {
516
    global $conf;
517
    if(is_array($urlParameters)) {
518
        $urlParameters = buildURLparams($urlParameters, $sep);
519
    } else {
520
        $urlParameters = str_replace(',', $sep, $urlParameters);
521
    }
522
523
    $format = rawurlencode($format);
524
    $id     = idfilter($id);
525
    if($abs) {
526
        $xlink = DOKU_URL;
527
    } else {
528
        $xlink = DOKU_BASE;
529
    }
530
531
    if($conf['userewrite'] == 2) {
532
        $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
533
        if($urlParameters) $xlink .= $sep.$urlParameters;
534
    } elseif($conf['userewrite'] == 1) {
535
        $xlink .= '_export/'.$format.'/'.$id;
536
        if($urlParameters) $xlink .= '?'.$urlParameters;
537
    } else {
538
        $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
539
        if($urlParameters) $xlink .= $sep.$urlParameters;
540
    }
541
542
    return $xlink;
543
}
544
545
/**
546
 * Build a link to a media file
547
 *
548
 * Will return a link to the detail page if $direct is false
549
 *
550
 * The $more parameter should always be given as array, the function then
551
 * will strip default parameters to produce even cleaner URLs
552
 *
553
 * @param string  $id     the media file id or URL
554
 * @param mixed   $more   string or array with additional parameters
555
 * @param bool    $direct link to detail page if false
556
 * @param string  $sep    URL parameter separator
557
 * @param bool    $abs    Create an absolute URL
558
 * @return string
559
 */
560
function ml($id = '', $more = '', $direct = true, $sep = '&amp;', $abs = false) {
561
    global $conf;
562
    $isexternalimage = media_isexternal($id);
563
    if(!$isexternalimage) {
564
        $id = cleanID($id);
565
    }
566
567
    if(is_array($more)) {
568
        // add token for resized images
569
        if(!empty($more['w']) || !empty($more['h']) || $isexternalimage){
570
            $more['tok'] = media_get_token($id,$more['w'],$more['h']);
571
        }
572
        // strip defaults for shorter URLs
573
        if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']);
574
        if(empty($more['w'])) unset($more['w']);
575
        if(empty($more['h'])) unset($more['h']);
576
        if(isset($more['id']) && $direct) unset($more['id']);
577
        if(isset($more['rev']) && !$more['rev']) unset($more['rev']);
578
        $more = buildURLparams($more, $sep);
579
    } else {
580
        $matches = array();
581
        if (preg_match_all('/\b(w|h)=(\d*)\b/',$more,$matches,PREG_SET_ORDER) || $isexternalimage){
582
            $resize = array('w'=>0, 'h'=>0);
583
            foreach ($matches as $match){
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
584
                $resize[$match[1]] = $match[2];
585
            }
586
            $more .= $more === '' ? '' : $sep;
587
            $more .= 'tok='.media_get_token($id,$resize['w'],$resize['h']);
588
        }
589
        $more = str_replace('cache=cache', '', $more); //skip default
590
        $more = str_replace(',,', ',', $more);
591
        $more = str_replace(',', $sep, $more);
592
    }
593
594
    if($abs) {
595
        $xlink = DOKU_URL;
596
    } else {
597
        $xlink = DOKU_BASE;
598
    }
599
600
    // external URLs are always direct without rewriting
601
    if($isexternalimage) {
602
        $xlink .= 'lib/exe/fetch.php';
603
        $xlink .= '?'.$more;
604
        $xlink .= $sep.'media='.rawurlencode($id);
605
        return $xlink;
606
    }
607
608
    $id = idfilter($id);
609
610
    // decide on scriptname
611
    if($direct) {
612
        if($conf['userewrite'] == 1) {
613
            $script = '_media';
614
        } else {
615
            $script = 'lib/exe/fetch.php';
616
        }
617
    } else {
618
        if($conf['userewrite'] == 1) {
619
            $script = '_detail';
620
        } else {
621
            $script = 'lib/exe/detail.php';
622
        }
623
    }
624
625
    // build URL based on rewrite mode
626
    if($conf['userewrite']) {
627
        $xlink .= $script.'/'.$id;
628
        if($more) $xlink .= '?'.$more;
629
    } else {
630
        if($more) {
631
            $xlink .= $script.'?'.$more;
632
            $xlink .= $sep.'media='.$id;
633
        } else {
634
            $xlink .= $script.'?media='.$id;
635
        }
636
    }
637
638
    return $xlink;
639
}
640
641
/**
642
 * Returns the URL to the DokuWiki base script
643
 *
644
 * Consider using wl() instead, unless you absoutely need the doku.php endpoint
645
 *
646
 * @author Andreas Gohr <[email protected]>
647
 *
648
 * @return string
649
 */
650
function script() {
651
    return DOKU_BASE.DOKU_SCRIPT;
652
}
653
654
/**
655
 * Spamcheck against wordlist
656
 *
657
 * Checks the wikitext against a list of blocked expressions
658
 * returns true if the text contains any bad words
659
 *
660
 * Triggers COMMON_WORDBLOCK_BLOCKED
661
 *
662
 *  Action Plugins can use this event to inspect the blocked data
663
 *  and gain information about the user who was blocked.
664
 *
665
 *  Event data:
666
 *    data['matches']  - array of matches
667
 *    data['userinfo'] - information about the blocked user
668
 *      [ip]           - ip address
669
 *      [user]         - username (if logged in)
670
 *      [mail]         - mail address (if logged in)
671
 *      [name]         - real name (if logged in)
672
 *
673
 * @author Andreas Gohr <[email protected]>
674
 * @author Michael Klier <[email protected]>
675
 *
676
 * @param  string $text - optional text to check, if not given the globals are used
677
 * @return bool         - true if a spam word was found
678
 */
679
function checkwordblock($text = '') {
680
    global $TEXT;
681
    global $PRE;
682
    global $SUF;
683
    global $SUM;
684
    global $conf;
685
    global $INFO;
686
    /* @var Input $INPUT */
687
    global $INPUT;
688
689
    if(!$conf['usewordblock']) return false;
690
691
    if(!$text) $text = "$PRE $TEXT $SUF $SUM";
692
693
    // we prepare the text a tiny bit to prevent spammers circumventing URL checks
694
    $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', '\1http://\2 \2\3', $text);
695
696
    $wordblocks = getWordblocks();
697
    // how many lines to read at once (to work around some PCRE limits)
698
    if(version_compare(phpversion(), '4.3.0', '<')) {
699
        // old versions of PCRE define a maximum of parenthesises even if no
700
        // backreferences are used - the maximum is 99
701
        // this is very bad performancewise and may even be too high still
702
        $chunksize = 40;
703
    } else {
704
        // read file in chunks of 200 - this should work around the
705
        // MAX_PATTERN_SIZE in modern PCRE
706
        $chunksize = 200;
707
    }
708
    while($blocks = array_splice($wordblocks, 0, $chunksize)) {
709
        $re = array();
710
        // build regexp from blocks
711
        foreach($blocks as $block) {
712
            $block = preg_replace('/#.*$/', '', $block);
713
            $block = trim($block);
714
            if(empty($block)) continue;
715
            $re[] = $block;
716
        }
717
        if(count($re) && preg_match('#('.join('|', $re).')#si', $text, $matches)) {
718
            // prepare event data
719
            $data = array();
720
            $data['matches']        = $matches;
721
            $data['userinfo']['ip'] = $INPUT->server->str('REMOTE_ADDR');
722
            if($INPUT->server->str('REMOTE_USER')) {
723
                $data['userinfo']['user'] = $INPUT->server->str('REMOTE_USER');
724
                $data['userinfo']['name'] = $INFO['userinfo']['name'];
725
                $data['userinfo']['mail'] = $INFO['userinfo']['mail'];
726
            }
727
            $callback = create_function('', 'return true;');
728
            return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true);
729
        }
730
    }
731
    return false;
732
}
733
734
/**
735
 * Return the IP of the client
736
 *
737
 * Honours X-Forwarded-For and X-Real-IP Proxy Headers
738
 *
739
 * It returns a comma separated list of IPs if the above mentioned
740
 * headers are set. If the single parameter is set, it tries to return
741
 * a routable public address, prefering the ones suplied in the X
742
 * headers
743
 *
744
 * @author Andreas Gohr <[email protected]>
745
 *
746
 * @param  boolean $single If set only a single IP is returned
747
 * @return string
748
 */
749
function clientIP($single = false) {
750
    /* @var Input $INPUT */
751
    global $INPUT;
752
753
    $ip   = array();
754
    $ip[] = $INPUT->server->str('REMOTE_ADDR');
755
    if($INPUT->server->str('HTTP_X_FORWARDED_FOR')) {
756
        $ip = array_merge($ip, explode(',', str_replace(' ', '', $INPUT->server->str('HTTP_X_FORWARDED_FOR'))));
757
    }
758
    if($INPUT->server->str('HTTP_X_REAL_IP')) {
759
        $ip = array_merge($ip, explode(',', str_replace(' ', '', $INPUT->server->str('HTTP_X_REAL_IP'))));
760
    }
761
762
    // some IPv4/v6 regexps borrowed from Feyd
763
    // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479
764
    $dec_octet   = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])';
765
    $hex_digit   = '[A-Fa-f0-9]';
766
    $h16         = "{$hex_digit}{1,4}";
767
    $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet";
768
    $ls32        = "(?:$h16:$h16|$IPv4Address)";
769
    $IPv6Address =
770
        "(?:(?:{$IPv4Address})|(?:".
771
            "(?:$h16:){6}$ls32".
772
            "|::(?:$h16:){5}$ls32".
773
            "|(?:$h16)?::(?:$h16:){4}$ls32".
774
            "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32".
775
            "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32".
776
            "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32".
777
            "|(?:(?:$h16:){0,4}$h16)?::$ls32".
778
            "|(?:(?:$h16:){0,5}$h16)?::$h16".
779
            "|(?:(?:$h16:){0,6}$h16)?::".
780
            ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)";
781
782
    // remove any non-IP stuff
783
    $cnt   = count($ip);
784
    $match = array();
785
    for($i = 0; $i < $cnt; $i++) {
786
        if(preg_match("/^$IPv4Address$/", $ip[$i], $match) || preg_match("/^$IPv6Address$/", $ip[$i], $match)) {
787
            $ip[$i] = $match[0];
788
        } else {
789
            $ip[$i] = '';
790
        }
791
        if(empty($ip[$i])) unset($ip[$i]);
792
    }
793
    $ip = array_values(array_unique($ip));
794
    if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
795
796
    if(!$single) return join(',', $ip);
797
798
    // decide which IP to use, trying to avoid local addresses
799
    $ip = array_reverse($ip);
800
    foreach($ip as $i) {
801
        if(preg_match('/^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/', $i)) {
802
            continue;
803
        } else {
804
            return $i;
805
        }
806
    }
807
    // still here? just use the first (last) address
808
    return $ip[0];
809
}
810
811
/**
812
 * Check if the browser is on a mobile device
813
 *
814
 * Adapted from the example code at url below
815
 *
816
 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code
817
 *
818
 * @return bool if true, client is mobile browser; otherwise false
819
 */
820
function clientismobile() {
821
    /* @var Input $INPUT */
822
    global $INPUT;
823
824
    if($INPUT->server->has('HTTP_X_WAP_PROFILE')) return true;
825
826
    if(preg_match('/wap\.|\.wap/i', $INPUT->server->str('HTTP_ACCEPT'))) return true;
827
828
    if(!$INPUT->server->has('HTTP_USER_AGENT')) return false;
829
830
    $uamatches = 'midp|j2me|avantg|docomo|novarra|palmos|palmsource|240x320|opwv|chtml|pda|windows ce|mmp\/|blackberry|mib\/|symbian|wireless|nokia|hand|mobi|phone|cdm|up\.b|audio|SIE\-|SEC\-|samsung|HTC|mot\-|mitsu|sagem|sony|alcatel|lg|erics|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|\d\d\di|moto';
831
832
    if(preg_match("/$uamatches/i", $INPUT->server->str('HTTP_USER_AGENT'))) return true;
833
834
    return false;
835
}
836
837
/**
838
 * Convert one or more comma separated IPs to hostnames
839
 *
840
 * If $conf['dnslookups'] is disabled it simply returns the input string
841
 *
842
 * @author Glen Harris <[email protected]>
843
 *
844
 * @param  string $ips comma separated list of IP addresses
845
 * @return string a comma separated list of hostnames
846
 */
847
function gethostsbyaddrs($ips) {
848
    global $conf;
849
    if(!$conf['dnslookups']) return $ips;
850
851
    $hosts = array();
852
    $ips   = explode(',', $ips);
853
854
    if(is_array($ips)) {
855
        foreach($ips as $ip) {
856
            $hosts[] = gethostbyaddr(trim($ip));
857
        }
858
        return join(',', $hosts);
859
    } else {
860
        return gethostbyaddr(trim($ips));
861
    }
862
}
863
864
/**
865
 * Checks if a given page is currently locked.
866
 *
867
 * removes stale lockfiles
868
 *
869
 * @author Andreas Gohr <[email protected]>
870
 *
871
 * @param string $id page id
872
 * @return bool page is locked?
873
 */
874
function checklock($id) {
875
    global $conf;
876
    /* @var Input $INPUT */
877
    global $INPUT;
878
879
    $lock = wikiLockFN($id);
880
881
    //no lockfile
882
    if(!file_exists($lock)) return false;
883
884
    //lockfile expired
885
    if((time() - filemtime($lock)) > $conf['locktime']) {
886
        @unlink($lock);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
887
        return false;
888
    }
889
890
    //my own lock
891
    @list($ip, $session) = explode("\n", io_readFile($lock));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
892
    if($ip == $INPUT->server->str('REMOTE_USER') || $ip == clientIP() || (session_id() && $session == session_id())) {
893
        return false;
894
    }
895
896
    return $ip;
897
}
898
899
/**
900
 * Lock a page for editing
901
 *
902
 * @author Andreas Gohr <[email protected]>
903
 *
904
 * @param string $id page id to lock
905
 */
906
function lock($id) {
907
    global $conf;
908
    /* @var Input $INPUT */
909
    global $INPUT;
910
911
    if($conf['locktime'] == 0) {
912
        return;
913
    }
914
915
    $lock = wikiLockFN($id);
916
    if($INPUT->server->str('REMOTE_USER')) {
917
        io_saveFile($lock, $INPUT->server->str('REMOTE_USER'));
918
    } else {
919
        io_saveFile($lock, clientIP()."\n".session_id());
920
    }
921
}
922
923
/**
924
 * Unlock a page if it was locked by the user
925
 *
926
 * @author Andreas Gohr <[email protected]>
927
 *
928
 * @param string $id page id to unlock
929
 * @return bool true if a lock was removed
930
 */
931
function unlock($id) {
932
    /* @var Input $INPUT */
933
    global $INPUT;
934
935
    $lock = wikiLockFN($id);
936
    if(file_exists($lock)) {
937
        @list($ip, $session) = explode("\n", io_readFile($lock));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
938
        if($ip == $INPUT->server->str('REMOTE_USER') || $ip == clientIP() || $session == session_id()) {
939
            @unlink($lock);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
940
            return true;
941
        }
942
    }
943
    return false;
944
}
945
946
/**
947
 * convert line ending to unix format
948
 *
949
 * also makes sure the given text is valid UTF-8
950
 *
951
 * @see    formText() for 2crlf conversion
952
 * @author Andreas Gohr <[email protected]>
953
 *
954
 * @param string $text
955
 * @return string
956
 */
957
function cleanText($text) {
958
    $text = preg_replace("/(\015\012)|(\015)/", "\012", $text);
959
960
    // if the text is not valid UTF-8 we simply assume latin1
961
    // this won't break any worse than it breaks with the wrong encoding
962
    // but might actually fix the problem in many cases
963
    if(!utf8_check($text)) $text = utf8_encode($text);
964
965
    return $text;
966
}
967
968
/**
969
 * Prepares text for print in Webforms by encoding special chars.
970
 * It also converts line endings to Windows format which is
971
 * pseudo standard for webforms.
972
 *
973
 * @see    cleanText() for 2unix conversion
974
 * @author Andreas Gohr <[email protected]>
975
 *
976
 * @param string $text
977
 * @return string
978
 */
979
function formText($text) {
980
    $text = str_replace("\012", "\015\012", $text);
981
    return htmlspecialchars($text);
982
}
983
984
/**
985
 * Returns the specified local text in raw format
986
 *
987
 * @author Andreas Gohr <[email protected]>
988
 *
989
 * @param string $id   page id
990
 * @param string $ext  extension of file being read, default 'txt'
991
 * @return string
992
 */
993
function rawLocale($id, $ext = 'txt') {
994
    return io_readFile(localeFN($id, $ext));
995
}
996
997
/**
998
 * Returns the raw WikiText
999
 *
1000
 * @author Andreas Gohr <[email protected]>
1001
 *
1002
 * @param string $id   page id
1003
 * @param string|int $rev  timestamp when a revision of wikitext is desired
1004
 * @return string
1005
 */
1006
function rawWiki($id, $rev = '') {
1007
    return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
1008
}
1009
1010
/**
1011
 * Returns the pagetemplate contents for the ID's namespace
1012
 *
1013
 * @triggers COMMON_PAGETPL_LOAD
1014
 * @author Andreas Gohr <[email protected]>
1015
 *
1016
 * @param string $id the id of the page to be created
1017
 * @return string parsed pagetemplate content
1018
 */
1019
function pageTemplate($id) {
1020
    global $conf;
1021
1022
    if(is_array($id)) $id = $id[0];
1023
1024
    // prepare initial event data
1025
    $data = array(
1026
        'id'        => $id, // the id of the page to be created
1027
        'tpl'       => '', // the text used as template
1028
        'tplfile'   => '', // the file above text was/should be loaded from
1029
        'doreplace' => true // should wildcard replacements be done on the text?
1030
    );
1031
1032
    $evt = new Doku_Event('COMMON_PAGETPL_LOAD', $data);
1033
    if($evt->advise_before(true)) {
1034
        // the before event might have loaded the content already
1035
        if(empty($data['tpl'])) {
1036
            // if the before event did not set a template file, try to find one
1037
            if(empty($data['tplfile'])) {
1038
                $path = dirname(wikiFN($id));
1039
                if(file_exists($path.'/_template.txt')) {
1040
                    $data['tplfile'] = $path.'/_template.txt';
1041
                } else {
1042
                    // search upper namespaces for templates
1043
                    $len = strlen(rtrim($conf['datadir'], '/'));
1044
                    while(strlen($path) >= $len) {
1045
                        if(file_exists($path.'/__template.txt')) {
1046
                            $data['tplfile'] = $path.'/__template.txt';
1047
                            break;
1048
                        }
1049
                        $path = substr($path, 0, strrpos($path, '/'));
1050
                    }
1051
                }
1052
            }
1053
            // load the content
1054
            $data['tpl'] = io_readFile($data['tplfile']);
1055
        }
1056
        if($data['doreplace']) parsePageTemplate($data);
1057
    }
1058
    $evt->advise_after();
1059
    unset($evt);
1060
1061
    return $data['tpl'];
1062
}
1063
1064
/**
1065
 * Performs common page template replacements
1066
 * This works on data from COMMON_PAGETPL_LOAD
1067
 *
1068
 * @author Andreas Gohr <[email protected]>
1069
 *
1070
 * @param array $data array with event data
1071
 * @return string
1072
 */
1073
function parsePageTemplate(&$data) {
1074
    /**
1075
     * @var string $id        the id of the page to be created
1076
     * @var string $tpl       the text used as template
1077
     * @var string $tplfile   the file above text was/should be loaded from
1078
     * @var bool   $doreplace should wildcard replacements be done on the text?
1079
     */
1080
    extract($data);
1081
1082
    global $USERINFO;
1083
    global $conf;
1084
    /* @var Input $INPUT */
1085
    global $INPUT;
1086
1087
    // replace placeholders
1088
    $file = noNS($id);
1089
    $page = strtr($file, $conf['sepchar'], ' ');
1090
1091
    $tpl = str_replace(
1092
        array(
1093
             '@ID@',
1094
             '@NS@',
1095
             '@FILE@',
1096
             '@!FILE@',
1097
             '@!FILE!@',
1098
             '@PAGE@',
1099
             '@!PAGE@',
1100
             '@!!PAGE@',
1101
             '@!PAGE!@',
1102
             '@USER@',
1103
             '@NAME@',
1104
             '@MAIL@',
1105
             '@DATE@',
1106
        ),
1107
        array(
1108
             $id,
1109
             getNS($id),
1110
             $file,
1111
             utf8_ucfirst($file),
1112
             utf8_strtoupper($file),
1113
             $page,
1114
             utf8_ucfirst($page),
1115
             utf8_ucwords($page),
1116
             utf8_strtoupper($page),
1117
             $INPUT->server->str('REMOTE_USER'),
1118
             $USERINFO['name'],
1119
             $USERINFO['mail'],
1120
             $conf['dformat'],
1121
        ), $tpl
1122
    );
1123
1124
    // we need the callback to work around strftime's char limit
1125
    $tpl         = preg_replace_callback('/%./', create_function('$m', 'return strftime($m[0]);'), $tpl);
1126
    $data['tpl'] = $tpl;
1127
    return $tpl;
1128
}
1129
1130
/**
1131
 * Returns the raw Wiki Text in three slices.
1132
 *
1133
 * The range parameter needs to have the form "from-to"
1134
 * and gives the range of the section in bytes - no
1135
 * UTF-8 awareness is needed.
1136
 * The returned order is prefix, section and suffix.
1137
 *
1138
 * @author Andreas Gohr <[email protected]>
1139
 *
1140
 * @param string $range in form "from-to"
1141
 * @param string $id    page id
1142
 * @param string $rev   optional, the revision timestamp
1143
 * @return string[] with three slices
1144
 */
1145
function rawWikiSlices($range, $id, $rev = '') {
1146
    $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
1147
1148
    // Parse range
1149
    list($from, $to) = explode('-', $range, 2);
1150
    // Make range zero-based, use defaults if marker is missing
1151
    $from = !$from ? 0 : ($from - 1);
1152
    $to   = !$to ? strlen($text) : ($to - 1);
1153
1154
    $slices = array();
1155
    $slices[0] = substr($text, 0, $from);
1156
    $slices[1] = substr($text, $from, $to - $from);
1157
    $slices[2] = substr($text, $to);
1158
    return $slices;
1159
}
1160
1161
/**
1162
 * Joins wiki text slices
1163
 *
1164
 * function to join the text slices.
1165
 * When the pretty parameter is set to true it adds additional empty
1166
 * lines between sections if needed (used on saving).
1167
 *
1168
 * @author Andreas Gohr <[email protected]>
1169
 *
1170
 * @param string $pre   prefix
1171
 * @param string $text  text in the middle
1172
 * @param string $suf   suffix
1173
 * @param bool $pretty add additional empty lines between sections
1174
 * @return string
1175
 */
1176
function con($pre, $text, $suf, $pretty = false) {
1177
    if($pretty) {
1178
        if($pre !== '' && substr($pre, -1) !== "\n" &&
1179
            substr($text, 0, 1) !== "\n"
1180
        ) {
1181
            $pre .= "\n";
1182
        }
1183
        if($suf !== '' && substr($text, -1) !== "\n" &&
1184
            substr($suf, 0, 1) !== "\n"
1185
        ) {
1186
            $text .= "\n";
1187
        }
1188
    }
1189
1190
    return $pre.$text.$suf;
1191
}
1192
1193
/**
1194
 * Checks if the current page version is newer than the last entry in the page's
1195
 * changelog. If so, we assume it has been an external edit and we create an
1196
 * attic copy and add a proper changelog line.
1197
 *
1198
 * This check is only executed when the page is about to be saved again from the
1199
 * wiki, triggered in @see saveWikiText()
1200
 *
1201
 * @param string $id the page ID
1202
 */
1203
function detectExternalEdit($id) {
1204
    global $lang;
1205
1206
    $fileLastMod = wikiFN($id);
1207
    $lastMod     = @filemtime($fileLastMod); // from page
1208
    $pagelog     = new PageChangeLog($id, 1024);
1209
    $lastRev     = $pagelog->getRevisions(-1, 1); // from changelog
1210
    $lastRev     = (int) (empty($lastRev) ? 0 : $lastRev[0]);
1211
1212
    if(!file_exists(wikiFN($id, $lastMod)) && file_exists($fileLastMod) && $lastMod >= $lastRev) {
1213
        // add old revision to the attic if missing
1214
        saveOldRevision($id);
1215
        // add a changelog entry if this edit came from outside dokuwiki
1216
        if($lastMod > $lastRev) {
1217
            $fileLastRev = wikiFN($id, $lastRev);
1218
            $revinfo = $pagelog->getRevisionInfo($lastRev);
1219
            if(empty($lastRev) || !file_exists($fileLastRev) || $revinfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
1220
                $filesize_old = 0;
1221
            } else {
1222
                $filesize_old = io_getSizeFile($fileLastRev);
1223
            }
1224
            $filesize_new = filesize($fileLastMod);
1225
            $sizechange = $filesize_new - $filesize_old;
1226
1227
            addLogEntry($lastMod, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=> true), $sizechange);
1228
            // remove soon to be stale instructions
1229
            $cache = new cache_instructions($id, $fileLastMod);
1230
            $cache->removeCache();
1231
        }
1232
    }
1233
}
1234
1235
/**
1236
 * Saves a wikitext by calling io_writeWikiPage.
1237
 * Also directs changelog and attic updates.
1238
 *
1239
 * @author Andreas Gohr <[email protected]>
1240
 * @author Ben Coburn <[email protected]>
1241
 *
1242
 * @param string $id       page id
1243
 * @param string $text     wikitext being saved
1244
 * @param string $summary  summary of text update
1245
 * @param bool   $minor    mark this saved version as minor update
1246
 */
1247
function saveWikiText($id, $text, $summary, $minor = false) {
1248
    /* Note to developers:
1249
       This code is subtle and delicate. Test the behavior of
1250
       the attic and changelog with dokuwiki and external edits
1251
       after any changes. External edits change the wiki page
1252
       directly without using php or dokuwiki.
1253
     */
1254
    global $conf;
1255
    global $lang;
1256
    global $REV;
1257
    /* @var Input $INPUT */
1258
    global $INPUT;
1259
1260
    // prepare data for event
1261
    $svdta = array();
1262
    $svdta['id']             = $id;
1263
    $svdta['file']           = wikiFN($id);
1264
    $svdta['revertFrom']     = $REV;
1265
    $svdta['oldRevision']    = @filemtime($svdta['file']);
1266
    $svdta['newRevision']    = 0;
1267
    $svdta['newContent']     = $text;
1268
    $svdta['oldContent']     = rawWiki($id);
1269
    $svdta['summary']        = $summary;
1270
    $svdta['contentChanged'] = ($svdta['newContent'] != $svdta['oldContent']);
1271
    $svdta['changeInfo']     = '';
1272
    $svdta['changeType']     = DOKU_CHANGE_TYPE_EDIT;
1273
    $svdta['sizechange']     = null;
1274
1275
    // select changelog line type
1276
    if($REV) {
1277
        $svdta['changeType']  = DOKU_CHANGE_TYPE_REVERT;
1278
        $svdta['changeInfo'] = $REV;
1279
    } else if(!file_exists($svdta['file'])) {
1280
        $svdta['changeType'] = DOKU_CHANGE_TYPE_CREATE;
1281
    } else if(trim($text) == '') {
1282
        // empty or whitespace only content deletes
1283
        $svdta['changeType'] = DOKU_CHANGE_TYPE_DELETE;
1284
        // autoset summary on deletion
1285
        if(blank($svdta['summary'])) {
1286
            $svdta['summary'] = $lang['deleted'];
1287
        }
1288
    } else if($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER')) {
1289
        //minor edits only for logged in users
1290
        $svdta['changeType'] = DOKU_CHANGE_TYPE_MINOR_EDIT;
1291
    }
1292
1293
    $event = new Doku_Event('COMMON_WIKIPAGE_SAVE', $svdta);
1294
    if(!$event->advise_before()) return;
1295
1296
    // if the content has not been changed, no save happens (plugins may override this)
1297
    if(!$svdta['contentChanged']) return;
1298
1299
    detectExternalEdit($id);
1300
1301
    if(
1302
        $svdta['changeType'] == DOKU_CHANGE_TYPE_CREATE ||
1303
        ($svdta['changeType'] == DOKU_CHANGE_TYPE_REVERT && !file_exists($svdta['file']))
1304
    ) {
1305
        $filesize_old = 0;
1306
    } else {
1307
        $filesize_old = filesize($svdta['file']);
1308
    }
1309
    if($svdta['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
1310
        // Send "update" event with empty data, so plugins can react to page deletion
1311
        $data = array(array($svdta['file'], '', false), getNS($id), noNS($id), false);
1312
        trigger_event('IO_WIKIPAGE_WRITE', $data);
1313
        // pre-save deleted revision
1314
        @touch($svdta['file']);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1315
        clearstatcache();
1316
        $data['newRevision'] = saveOldRevision($id);
1317
        // remove empty file
1318
        @unlink($svdta['file']);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1319
        $filesize_new = 0;
1320
        // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata...
1321
        // purge non-persistant meta data
1322
        p_purge_metadata($id);
1323
        // remove empty namespaces
1324
        io_sweepNS($id, 'datadir');
1325
        io_sweepNS($id, 'mediadir');
1326
    } else {
1327
        // save file (namespace dir is created in io_writeWikiPage)
1328
        io_writeWikiPage($svdta['file'], $text, $id);
1329
        // pre-save the revision, to keep the attic in sync
1330
        $svdta['newRevision'] = saveOldRevision($id);
1331
        $filesize_new = filesize($svdta['file']);
1332
    }
1333
    $svdta['sizechange'] = $filesize_new - $filesize_old;
1334
1335
    $event->advise_after();
1336
1337
    addLogEntry($svdta['newRevision'], $svdta['id'], $svdta['changeType'], $svdta['summary'], $svdta['changeInfo'], null, $svdta['sizechange']);
1338
1339
    // send notify mails
1340
    notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor);
1341
    notify($svdta['id'], 'subscribers', $svdta['oldRevision'], $svdta['summary'], $minor);
1342
1343
    // update the purgefile (timestamp of the last time anything within the wiki was changed)
1344
    io_saveFile($conf['cachedir'].'/purgefile', time());
1345
1346
    // if useheading is enabled, purge the cache of all linking pages
1347
    if(useHeading('content')) {
1348
        $pages = ft_backlinks($id, true);
1349
        foreach($pages as $page) {
1350
            $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
1351
            $cache->removeCache();
1352
        }
1353
    }
1354
}
1355
1356
/**
1357
 * moves the current version to the attic and returns its
1358
 * revision date
1359
 *
1360
 * @author Andreas Gohr <[email protected]>
1361
 *
1362
 * @param string $id page id
1363
 * @return int|string revision timestamp
1364
 */
1365
function saveOldRevision($id) {
1366
    $oldf = wikiFN($id);
1367
    if(!file_exists($oldf)) return '';
1368
    $date = filemtime($oldf);
1369
    $newf = wikiFN($id, $date);
1370
    io_writeWikiPage($newf, rawWiki($id), $id, $date);
1371
    return $date;
1372
}
1373
1374
/**
1375
 * Sends a notify mail on page change or registration
1376
 *
1377
 * @param string     $id       The changed page
1378
 * @param string     $who      Who to notify (admin|subscribers|register)
1379
 * @param int|string $rev Old page revision
1380
 * @param string     $summary  What changed
1381
 * @param boolean    $minor    Is this a minor edit?
1382
 * @param string[]   $replace  Additional string substitutions, @KEY@ to be replaced by value
1383
 * @return bool
1384
 *
1385
 * @author Andreas Gohr <[email protected]>
1386
 */
1387
function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array()) {
1388
    global $conf;
1389
    /* @var Input $INPUT */
1390
    global $INPUT;
1391
1392
    // decide if there is something to do, eg. whom to mail
1393
    if($who == 'admin') {
1394
        if(empty($conf['notify'])) return false; //notify enabled?
1395
        $tpl = 'mailtext';
1396
        $to  = $conf['notify'];
1397
    } elseif($who == 'subscribers') {
1398
        if(!actionOK('subscribe')) return false; //subscribers enabled?
1399
        if($conf['useacl'] && $INPUT->server->str('REMOTE_USER') && $minor) return false; //skip minors
1400
        $data = array('id' => $id, 'addresslist' => '', 'self' => false, 'replacements' => $replace);
1401
        trigger_event(
1402
            'COMMON_NOTIFY_ADDRESSLIST', $data,
1403
            array(new Subscription(), 'notifyaddresses')
1404
        );
1405
        $to = $data['addresslist'];
1406
        if(empty($to)) return false;
1407
        $tpl = 'subscr_single';
1408
    } else {
1409
        return false; //just to be safe
1410
    }
1411
1412
    // prepare content
1413
    $subscription = new Subscription();
1414
    return $subscription->send_diff($to, $tpl, $id, $rev, $summary);
1415
}
1416
1417
/**
1418
 * extracts the query from a search engine referrer
1419
 *
1420
 * @author Andreas Gohr <[email protected]>
1421
 * @author Todd Augsburger <[email protected]>
1422
 *
1423
 * @return array|string
1424
 */
1425
function getGoogleQuery() {
1426
    /* @var Input $INPUT */
1427
    global $INPUT;
1428
1429
    if(!$INPUT->server->has('HTTP_REFERER')) {
1430
        return '';
1431
    }
1432
    $url = parse_url($INPUT->server->str('HTTP_REFERER'));
1433
1434
    // only handle common SEs
1435
    if(!preg_match('/(google|bing|yahoo|ask|duckduckgo|babylon|aol|yandex)/',$url['host'])) return '';
1436
1437
    $query = array();
1438
    // temporary workaround against PHP bug #49733
1439
    // see http://bugs.php.net/bug.php?id=49733
1440
    if(UTF8_MBSTRING) $enc = mb_internal_encoding();
1441
    parse_str($url['query'], $query);
1442
    if(UTF8_MBSTRING) mb_internal_encoding($enc);
0 ignored issues
show
Bug introduced by
The variable $enc does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1443
1444
    $q = '';
1445
    if(isset($query['q'])){
1446
        $q = $query['q'];
1447
    }elseif(isset($query['p'])){
1448
        $q = $query['p'];
1449
    }elseif(isset($query['query'])){
1450
        $q = $query['query'];
1451
    }
1452
    $q = trim($q);
1453
1454
    if(!$q) return '';
1455
    $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/', $q, -1, PREG_SPLIT_NO_EMPTY);
1456
    return $q;
1457
}
1458
1459
/**
1460
 * Return the human readable size of a file
1461
 *
1462
 * @param int $size A file size
1463
 * @param int $dec A number of decimal places
1464
 * @return string human readable size
1465
 *
1466
 * @author      Martin Benjamin <[email protected]>
1467
 * @author      Aidan Lister <[email protected]>
1468
 * @version     1.0.0
1469
 */
1470
function filesize_h($size, $dec = 1) {
1471
    $sizes = array('B', 'KB', 'MB', 'GB');
1472
    $count = count($sizes);
1473
    $i     = 0;
1474
1475
    while($size >= 1024 && ($i < $count - 1)) {
1476
        $size /= 1024;
1477
        $i++;
1478
    }
1479
1480
    return round($size, $dec).' '.$sizes[$i];
1481
}
1482
1483
/**
1484
 * Return the given timestamp as human readable, fuzzy age
1485
 *
1486
 * @author Andreas Gohr <[email protected]>
1487
 *
1488
 * @param int $dt timestamp
1489
 * @return string
1490
 */
1491
function datetime_h($dt) {
1492
    global $lang;
1493
1494
    $ago = time() - $dt;
1495
    if($ago > 24 * 60 * 60 * 30 * 12 * 2) {
1496
        return sprintf($lang['years'], round($ago / (24 * 60 * 60 * 30 * 12)));
1497
    }
1498
    if($ago > 24 * 60 * 60 * 30 * 2) {
1499
        return sprintf($lang['months'], round($ago / (24 * 60 * 60 * 30)));
1500
    }
1501
    if($ago > 24 * 60 * 60 * 7 * 2) {
1502
        return sprintf($lang['weeks'], round($ago / (24 * 60 * 60 * 7)));
1503
    }
1504
    if($ago > 24 * 60 * 60 * 2) {
1505
        return sprintf($lang['days'], round($ago / (24 * 60 * 60)));
1506
    }
1507
    if($ago > 60 * 60 * 2) {
1508
        return sprintf($lang['hours'], round($ago / (60 * 60)));
1509
    }
1510
    if($ago > 60 * 2) {
1511
        return sprintf($lang['minutes'], round($ago / (60)));
1512
    }
1513
    return sprintf($lang['seconds'], $ago);
1514
}
1515
1516
/**
1517
 * Wraps around strftime but provides support for fuzzy dates
1518
 *
1519
 * The format default to $conf['dformat']. It is passed to
1520
 * strftime - %f can be used to get the value from datetime_h()
1521
 *
1522
 * @see datetime_h
1523
 * @author Andreas Gohr <[email protected]>
1524
 *
1525
 * @param int|null $dt      timestamp when given, null will take current timestamp
1526
 * @param string   $format  empty default to $conf['dformat'], or provide format as recognized by strftime()
1527
 * @return string
1528
 */
1529
function dformat($dt = null, $format = '') {
1530
    global $conf;
1531
1532
    if(is_null($dt)) $dt = time();
1533
    $dt = (int) $dt;
1534
    if(!$format) $format = $conf['dformat'];
1535
1536
    $format = str_replace('%f', datetime_h($dt), $format);
1537
    return strftime($format, $dt);
1538
}
1539
1540
/**
1541
 * Formats a timestamp as ISO 8601 date
1542
 *
1543
 * @author <ungu at terong dot com>
1544
 * @link http://www.php.net/manual/en/function.date.php#54072
1545
 *
1546
 * @param int $int_date current date in UNIX timestamp
1547
 * @return string
1548
 */
1549
function date_iso8601($int_date) {
1550
    $date_mod     = date('Y-m-d\TH:i:s', $int_date);
1551
    $pre_timezone = date('O', $int_date);
1552
    $time_zone    = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2);
1553
    $date_mod .= $time_zone;
1554
    return $date_mod;
1555
}
1556
1557
/**
1558
 * return an obfuscated email address in line with $conf['mailguard'] setting
1559
 *
1560
 * @author Harry Fuecks <[email protected]>
1561
 * @author Christopher Smith <[email protected]>
1562
 *
1563
 * @param string $email email address
1564
 * @return string
1565
 */
1566
function obfuscate($email) {
1567
    global $conf;
1568
1569
    switch($conf['mailguard']) {
1570
        case 'visible' :
1571
            $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
1572
            return strtr($email, $obfuscate);
1573
1574
        case 'hex' :
1575
            $encode = '';
1576
            $len    = strlen($email);
1577
            for($x = 0; $x < $len; $x++) {
1578
                $encode .= '&#x'.bin2hex($email{$x}).';';
1579
            }
1580
            return $encode;
1581
1582
        case 'none' :
1583
        default :
1584
            return $email;
1585
    }
1586
}
1587
1588
/**
1589
 * Removes quoting backslashes
1590
 *
1591
 * @author Andreas Gohr <[email protected]>
1592
 *
1593
 * @param string $string
1594
 * @param string $char backslashed character
1595
 * @return string
1596
 */
1597
function unslash($string, $char = "'") {
1598
    return str_replace('\\'.$char, $char, $string);
1599
}
1600
1601
/**
1602
 * Convert php.ini shorthands to byte
1603
 *
1604
 * @author <gilthans dot NO dot SPAM at gmail dot com>
1605
 * @link   http://de3.php.net/manual/en/ini.core.php#79564
1606
 *
1607
 * @param string $v shorthands
1608
 * @return int|string
1609
 */
1610
function php_to_byte($v) {
1611
    $l   = substr($v, -1);
1612
    $ret = substr($v, 0, -1);
1613
    switch(strtoupper($l)) {
1614
        /** @noinspection PhpMissingBreakStatementInspection */
1615
        case 'P':
1616
            $ret *= 1024;
1617
        /** @noinspection PhpMissingBreakStatementInspection */
1618
        case 'T':
1619
            $ret *= 1024;
1620
        /** @noinspection PhpMissingBreakStatementInspection */
1621
        case 'G':
1622
            $ret *= 1024;
1623
        /** @noinspection PhpMissingBreakStatementInspection */
1624
        case 'M':
1625
            $ret *= 1024;
1626
        /** @noinspection PhpMissingBreakStatementInspection */
1627
        case 'K':
1628
            $ret *= 1024;
1629
            break;
1630
        default;
1631
            $ret *= 10;
1632
            break;
1633
    }
1634
    return $ret;
1635
}
1636
1637
/**
1638
 * Wrapper around preg_quote adding the default delimiter
1639
 *
1640
 * @param string $string
1641
 * @return string
1642
 */
1643
function preg_quote_cb($string) {
1644
    return preg_quote($string, '/');
1645
}
1646
1647
/**
1648
 * Shorten a given string by removing data from the middle
1649
 *
1650
 * You can give the string in two parts, the first part $keep
1651
 * will never be shortened. The second part $short will be cut
1652
 * in the middle to shorten but only if at least $min chars are
1653
 * left to display it. Otherwise it will be left off.
1654
 *
1655
 * @param string $keep   the part to keep
1656
 * @param string $short  the part to shorten
1657
 * @param int    $max    maximum chars you want for the whole string
1658
 * @param int    $min    minimum number of chars to have left for middle shortening
1659
 * @param string $char   the shortening character to use
1660
 * @return string
1661
 */
1662
function shorten($keep, $short, $max, $min = 9, $char = '…') {
1663
    $max = $max - utf8_strlen($keep);
1664
    if($max < $min) return $keep;
1665
    $len = utf8_strlen($short);
1666
    if($len <= $max) return $keep.$short;
1667
    $half = floor($max / 2);
1668
    return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half);
1669
}
1670
1671
/**
1672
 * Return the users real name or e-mail address for use
1673
 * in page footer and recent changes pages
1674
 *
1675
 * @param string|null $username or null when currently logged-in user should be used
1676
 * @param bool $textonly true returns only plain text, true allows returning html
1677
 * @return string html or plain text(not escaped) of formatted user name
1678
 *
1679
 * @author Andy Webber <dokuwiki AT andywebber DOT com>
1680
 */
1681
function editorinfo($username, $textonly = false) {
1682
    return userlink($username, $textonly);
1683
}
1684
1685
/**
1686
 * Returns users realname w/o link
1687
 *
1688
 * @param string|null $username or null when currently logged-in user should be used
1689
 * @param bool $textonly true returns only plain text, true allows returning html
1690
 * @return string html or plain text(not escaped) of formatted user name
1691
 *
1692
 * @triggers COMMON_USER_LINK
1693
 */
1694
function userlink($username = null, $textonly = false) {
1695
    global $conf, $INFO;
1696
    /** @var DokuWiki_Auth_Plugin $auth */
1697
    global $auth;
1698
    /** @var Input $INPUT */
1699
    global $INPUT;
1700
1701
    // prepare initial event data
1702
    $data = array(
1703
        'username' => $username, // the unique user name
1704
        'name' => '',
1705
        'link' => array( //setting 'link' to false disables linking
1706
                         'target' => '',
1707
                         'pre' => '',
1708
                         'suf' => '',
1709
                         'style' => '',
1710
                         'more' => '',
1711
                         'url' => '',
1712
                         'title' => '',
1713
                         'class' => ''
1714
        ),
1715
        'userlink' => '', // formatted user name as will be returned
1716
        'textonly' => $textonly
1717
    );
1718
    if($username === null) {
1719
        $data['username'] = $username = $INPUT->server->str('REMOTE_USER');
1720
        if($textonly){
1721
            $data['name'] = $INFO['userinfo']['name']. ' (' . $INPUT->server->str('REMOTE_USER') . ')';
1722
        }else {
1723
            $data['name'] = '<bdi>' . hsc($INFO['userinfo']['name']) . '</bdi> (<bdi>' . hsc($INPUT->server->str('REMOTE_USER')) . '</bdi>)';
1724
        }
1725
    }
1726
1727
    $evt = new Doku_Event('COMMON_USER_LINK', $data);
1728
    if($evt->advise_before(true)) {
1729
        if(empty($data['name'])) {
1730
            if($auth) $info = $auth->getUserData($username);
1731
            if($conf['showuseras'] != 'loginname' && isset($info) && $info) {
1732
                switch($conf['showuseras']) {
1733
                    case 'username':
1734
                    case 'username_link':
1735
                        $data['name'] = $textonly ? $info['name'] : hsc($info['name']);
1736
                        break;
1737
                    case 'email':
1738
                    case 'email_link':
1739
                        $data['name'] = obfuscate($info['mail']);
1740
                        break;
1741
                }
1742
            } else {
1743
                $data['name'] = $textonly ? $data['username'] : hsc($data['username']);
1744
            }
1745
        }
1746
1747
        /** @var Doku_Renderer_xhtml $xhtml_renderer */
1748
        static $xhtml_renderer = null;
1749
1750
        if(!$data['textonly'] && empty($data['link']['url'])) {
1751
1752
            if(in_array($conf['showuseras'], array('email_link', 'username_link'))) {
1753
                if(!isset($info)) {
1754
                    if($auth) $info = $auth->getUserData($username);
1755
                }
1756
                if(isset($info) && $info) {
1757
                    if($conf['showuseras'] == 'email_link') {
1758
                        $data['link']['url'] = 'mailto:' . obfuscate($info['mail']);
1759
                    } else {
1760
                        if(is_null($xhtml_renderer)) {
1761
                            $xhtml_renderer = p_get_renderer('xhtml');
1762
                        }
1763
                        if(empty($xhtml_renderer->interwiki)) {
1764
                            $xhtml_renderer->interwiki = getInterwiki();
1765
                        }
1766
                        $shortcut = 'user';
1767
                        $exists = null;
1768
                        $data['link']['url'] = $xhtml_renderer->_resolveInterWiki($shortcut, $username, $exists);
1769
                        $data['link']['class'] .= ' interwiki iw_user';
1770
                        if($exists !== null) {
1771
                            if($exists) {
1772
                                $data['link']['class'] .= ' wikilink1';
1773
                            } else {
1774
                                $data['link']['class'] .= ' wikilink2';
1775
                                $data['link']['rel'] = 'nofollow';
1776
                            }
1777
                        }
1778
                    }
1779
                } else {
1780
                    $data['textonly'] = true;
1781
                }
1782
1783
            } else {
1784
                $data['textonly'] = true;
1785
            }
1786
        }
1787
1788
        if($data['textonly']) {
1789
            $data['userlink'] = $data['name'];
1790
        } else {
1791
            $data['link']['name'] = $data['name'];
1792
            if(is_null($xhtml_renderer)) {
1793
                $xhtml_renderer = p_get_renderer('xhtml');
1794
            }
1795
            $data['userlink'] = $xhtml_renderer->_formatLink($data['link']);
1796
        }
1797
    }
1798
    $evt->advise_after();
1799
    unset($evt);
1800
1801
    return $data['userlink'];
1802
}
1803
1804
/**
1805
 * Returns the path to a image file for the currently chosen license.
1806
 * When no image exists, returns an empty string
1807
 *
1808
 * @author Andreas Gohr <[email protected]>
1809
 *
1810
 * @param  string $type - type of image 'badge' or 'button'
1811
 * @return string
1812
 */
1813
function license_img($type) {
1814
    global $license;
1815
    global $conf;
1816
    if(!$conf['license']) return '';
1817
    if(!is_array($license[$conf['license']])) return '';
1818
    $try   = array();
1819
    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
1820
    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
1821
    if(substr($conf['license'], 0, 3) == 'cc-') {
1822
        $try[] = 'lib/images/license/'.$type.'/cc.png';
1823
    }
1824
    foreach($try as $src) {
1825
        if(file_exists(DOKU_INC.$src)) return $src;
1826
    }
1827
    return '';
1828
}
1829
1830
/**
1831
 * Checks if the given amount of memory is available
1832
 *
1833
 * If the memory_get_usage() function is not available the
1834
 * function just assumes $bytes of already allocated memory
1835
 *
1836
 * @author Filip Oscadal <[email protected]>
1837
 * @author Andreas Gohr <[email protected]>
1838
 *
1839
 * @param int  $mem    Size of memory you want to allocate in bytes
1840
 * @param int  $bytes  already allocated memory (see above)
1841
 * @return bool
1842
 */
1843
function is_mem_available($mem, $bytes = 1048576) {
1844
    $limit = trim(ini_get('memory_limit'));
1845
    if(empty($limit)) return true; // no limit set!
1846
1847
    // parse limit to bytes
1848
    $limit = php_to_byte($limit);
1849
1850
    // get used memory if possible
1851
    if(function_exists('memory_get_usage')) {
1852
        $used = memory_get_usage();
1853
    } else {
1854
        $used = $bytes;
1855
    }
1856
1857
    if($used + $mem > $limit) {
1858
        return false;
1859
    }
1860
1861
    return true;
1862
}
1863
1864
/**
1865
 * Send a HTTP redirect to the browser
1866
 *
1867
 * Works arround Microsoft IIS cookie sending bug. Exits the script.
1868
 *
1869
 * @link   http://support.microsoft.com/kb/q176113/
1870
 * @author Andreas Gohr <[email protected]>
1871
 *
1872
 * @param string $url url being directed to
1873
 */
1874
function send_redirect($url) {
1875
    $url = stripctl($url); // defend against HTTP Response Splitting
1876
1877
    /* @var Input $INPUT */
1878
    global $INPUT;
1879
1880
    //are there any undisplayed messages? keep them in session for display
1881
    global $MSG;
1882
    if(isset($MSG) && count($MSG) && !defined('NOSESSION')) {
1883
        //reopen session, store data and close session again
1884
        @session_start();
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1885
        $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
1886
    }
1887
1888
    // always close the session
1889
    session_write_close();
1890
1891
    // check if running on IIS < 6 with CGI-PHP
1892
    if($INPUT->server->has('SERVER_SOFTWARE') && $INPUT->server->has('GATEWAY_INTERFACE') &&
1893
        (strpos($INPUT->server->str('GATEWAY_INTERFACE'), 'CGI') !== false) &&
1894
        (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($INPUT->server->str('SERVER_SOFTWARE')), $matches)) &&
1895
        $matches[1] < 6
1896
    ) {
1897
        header('Refresh: 0;url='.$url);
1898
    } else {
1899
        header('Location: '.$url);
1900
    }
1901
1902
    if(defined('DOKU_UNITTEST')) return; // no exits during unit tests
1903
    exit;
1904
}
1905
1906
/**
1907
 * Validate a value using a set of valid values
1908
 *
1909
 * This function checks whether a specified value is set and in the array
1910
 * $valid_values. If not, the function returns a default value or, if no
1911
 * default is specified, throws an exception.
1912
 *
1913
 * @param string $param        The name of the parameter
1914
 * @param array  $valid_values A set of valid values; Optionally a default may
1915
 *                             be marked by the key “default”.
1916
 * @param array  $array        The array containing the value (typically $_POST
1917
 *                             or $_GET)
1918
 * @param string $exc          The text of the raised exception
1919
 *
1920
 * @throws Exception
1921
 * @return mixed
1922
 * @author Adrian Lang <[email protected]>
1923
 */
1924
function valid_input_set($param, $valid_values, $array, $exc = '') {
1925
    if(isset($array[$param]) && in_array($array[$param], $valid_values)) {
1926
        return $array[$param];
1927
    } elseif(isset($valid_values['default'])) {
1928
        return $valid_values['default'];
1929
    } else {
1930
        throw new Exception($exc);
1931
    }
1932
}
1933
1934
/**
1935
 * Read a preference from the DokuWiki cookie
1936
 * (remembering both keys & values are urlencoded)
1937
 *
1938
 * @param string $pref     preference key
1939
 * @param mixed  $default  value returned when preference not found
1940
 * @return string preference value
1941
 */
1942
function get_doku_pref($pref, $default) {
1943
    $enc_pref = urlencode($pref);
1944
    if(isset($_COOKIE['DOKU_PREFS']) && strpos($_COOKIE['DOKU_PREFS'], $enc_pref) !== false) {
1945
        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1946
        $cnt   = count($parts);
1947
        for($i = 0; $i < $cnt; $i += 2) {
1948
            if($parts[$i] == $enc_pref) {
1949
                return urldecode($parts[$i + 1]);
1950
            }
1951
        }
1952
    }
1953
    return $default;
1954
}
1955
1956
/**
1957
 * Add a preference to the DokuWiki cookie
1958
 * (remembering $_COOKIE['DOKU_PREFS'] is urlencoded)
1959
 * Remove it by setting $val to false
1960
 *
1961
 * @param string $pref  preference key
1962
 * @param string $val   preference value
1963
 */
1964
function set_doku_pref($pref, $val) {
1965
    global $conf;
1966
    $orig = get_doku_pref($pref, false);
1967
    $cookieVal = '';
1968
1969
    if($orig && ($orig != $val)) {
1970
        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1971
        $cnt   = count($parts);
1972
        // urlencode $pref for the comparison
1973
        $enc_pref = rawurlencode($pref);
1974
        for($i = 0; $i < $cnt; $i += 2) {
1975
            if($parts[$i] == $enc_pref) {
1976
                if ($val !== false) {
1977
                    $parts[$i + 1] = rawurlencode($val);
1978
                } else {
1979
                    unset($parts[$i]);
1980
                    unset($parts[$i + 1]);
1981
                }
1982
                break;
1983
            }
1984
        }
1985
        $cookieVal = implode('#', $parts);
1986
    } else if (!$orig && $val !== false) {
1987
        $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val);
1988
    }
1989
1990
    if (!empty($cookieVal)) {
1991
        $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
1992
        setcookie('DOKU_PREFS', $cookieVal, time()+365*24*3600, $cookieDir, '', ($conf['securecookie'] && is_ssl()));
1993
    }
1994
}
1995
1996
/**
1997
 * Strips source mapping declarations from given text #601
1998
 *
1999
 * @param string &$text reference to the CSS or JavaScript code to clean
2000
 */
2001
function stripsourcemaps(&$text){
2002
    $text = preg_replace('/^(\/\/|\/\*)[@#]\s+sourceMappingURL=.*?(\*\/)?$/im', '\\1\\2', $text);
2003
}
2004
2005
//Setup VIM: ex: et ts=2 :
2006