Completed
Push — emailsignature ( acb389...774514 )
by Gerrit
04:26
created

common.php ➔ pageinfo()   F

Complexity

Conditions 18
Paths 2304

Size

Total Lines 103
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 66
nc 2304
nop 0
dl 0
loc 103
rs 2
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
    $file     = wikiFN($id);
1207
    $old      = @filemtime($file); // from page
1208
    $pagelog  = new PageChangeLog($id, 1024);
1209
    $oldRev   = $pagelog->getRevisions(-1, 1); // from changelog
1210
    $oldRev   = (int) (empty($oldRev) ? 0 : $oldRev[0]);
1211
1212
    if(!file_exists(wikiFN($id, $old)) && file_exists($file) && $old >= $oldRev) {
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($old > $oldRev) {
1217
            addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=> true));
1218
            // remove soon to be stale instructions
1219
            $cache = new cache_instructions($id, $file);
1220
            $cache->removeCache();
1221
        }
1222
    }
1223
}
1224
1225
/**
1226
 * Saves a wikitext by calling io_writeWikiPage.
1227
 * Also directs changelog and attic updates.
1228
 *
1229
 * @author Andreas Gohr <[email protected]>
1230
 * @author Ben Coburn <[email protected]>
1231
 *
1232
 * @param string $id       page id
1233
 * @param string $text     wikitext being saved
1234
 * @param string $summary  summary of text update
1235
 * @param bool   $minor    mark this saved version as minor update
1236
 */
1237
function saveWikiText($id, $text, $summary, $minor = false) {
1238
    /* Note to developers:
1239
       This code is subtle and delicate. Test the behavior of
1240
       the attic and changelog with dokuwiki and external edits
1241
       after any changes. External edits change the wiki page
1242
       directly without using php or dokuwiki.
1243
     */
1244
    global $conf;
1245
    global $lang;
1246
    global $REV;
1247
    /* @var Input $INPUT */
1248
    global $INPUT;
1249
1250
    // prepare data for event
1251
    $svdta = array();
1252
    $svdta['id']             = $id;
1253
    $svdta['file']           = wikiFN($id);
1254
    $svdta['revertFrom']     = $REV;
1255
    $svdta['oldRevision']    = @filemtime($svdta['file']);
1256
    $svdta['newRevision']    = 0;
1257
    $svdta['newContent']     = $text;
1258
    $svdta['oldContent']     = rawWiki($id);
1259
    $svdta['summary']        = $summary;
1260
    $svdta['contentChanged'] = ($svdta['newContent'] != $svdta['oldContent']);
1261
    $svdta['changeInfo']     = '';
1262
    $svdta['changeType']     = DOKU_CHANGE_TYPE_EDIT;
1263
1264
    // select changelog line type
1265
    if($REV) {
1266
        $svdta['changeType']  = DOKU_CHANGE_TYPE_REVERT;
1267
        $svdta['changeInfo'] = $REV;
1268
    } else if(!file_exists($svdta['file'])) {
1269
        $svdta['changeType'] = DOKU_CHANGE_TYPE_CREATE;
1270
    } else if(trim($text) == '') {
1271
        // empty or whitespace only content deletes
1272
        $svdta['changeType'] = DOKU_CHANGE_TYPE_DELETE;
1273
        // autoset summary on deletion
1274
        if(blank($svdta['summary'])) $svdta['summary'] = $lang['deleted'];
1275
    } else if($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER')) {
1276
        //minor edits only for logged in users
1277
        $svdta['changeType'] = DOKU_CHANGE_TYPE_MINOR_EDIT;
1278
    }
1279
1280
    $event = new Doku_Event('COMMON_WIKIPAGE_SAVE', $svdta);
1281
    if(!$event->advise_before()) return;
1282
1283
    // if the content has not been changed, no save happens (plugins may override this)
1284
    if(!$svdta['contentChanged']) return;
1285
1286
    detectExternalEdit($id);
1287
    if($svdta['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
1288
        // Send "update" event with empty data, so plugins can react to page deletion
1289
        $data = array(array($svdta['file'], '', false), getNS($id), noNS($id), false);
1290
        trigger_event('IO_WIKIPAGE_WRITE', $data);
1291
        // pre-save deleted revision
1292
        @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...
1293
        clearstatcache();
1294
        $data['newRevision'] = saveOldRevision($id);
1295
        // remove empty file
1296
        @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...
1297
        // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata...
1298
        // purge non-persistant meta data
1299
        p_purge_metadata($id);
1300
        // remove empty namespaces
1301
        io_sweepNS($id, 'datadir');
1302
        io_sweepNS($id, 'mediadir');
1303
    } else {
1304
        // save file (namespace dir is created in io_writeWikiPage)
1305
        io_writeWikiPage($svdta['file'], $text, $id);
1306
        // pre-save the revision, to keep the attic in sync
1307
        $svdta['newRevision'] = saveOldRevision($id);
1308
    }
1309
1310
    $event->advise_after();
1311
1312
    addLogEntry($svdta['newRevision'], $svdta['id'], $svdta['changeType'], $svdta['summary'], $svdta['changeInfo']);
1313
    // send notify mails
1314
    notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor);
1315
    notify($svdta['id'], 'subscribers', $svdta['oldRevision'], $svdta['summary'], $minor);
1316
1317
    // update the purgefile (timestamp of the last time anything within the wiki was changed)
1318
    io_saveFile($conf['cachedir'].'/purgefile', time());
1319
1320
    // if useheading is enabled, purge the cache of all linking pages
1321
    if(useHeading('content')) {
1322
        $pages = ft_backlinks($id, true);
1323
        foreach($pages as $page) {
1324
            $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
1325
            $cache->removeCache();
1326
        }
1327
    }
1328
}
1329
1330
/**
1331
 * moves the current version to the attic and returns its
1332
 * revision date
1333
 *
1334
 * @author Andreas Gohr <[email protected]>
1335
 *
1336
 * @param string $id page id
1337
 * @return int|string revision timestamp
1338
 */
1339
function saveOldRevision($id) {
1340
    $oldf = wikiFN($id);
1341
    if(!file_exists($oldf)) return '';
1342
    $date = filemtime($oldf);
1343
    $newf = wikiFN($id, $date);
1344
    io_writeWikiPage($newf, rawWiki($id), $id, $date);
1345
    return $date;
1346
}
1347
1348
/**
1349
 * Sends a notify mail on page change or registration
1350
 *
1351
 * @param string     $id       The changed page
1352
 * @param string     $who      Who to notify (admin|subscribers|register)
1353
 * @param int|string $rev Old page revision
1354
 * @param string     $summary  What changed
1355
 * @param boolean    $minor    Is this a minor edit?
1356
 * @param string[]   $replace  Additional string substitutions, @KEY@ to be replaced by value
1357
 * @return bool
1358
 *
1359
 * @author Andreas Gohr <[email protected]>
1360
 */
1361
function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array()) {
1362
    global $conf;
1363
    /* @var Input $INPUT */
1364
    global $INPUT;
1365
1366
    // decide if there is something to do, eg. whom to mail
1367
    if($who == 'admin') {
1368
        if(empty($conf['notify'])) return false; //notify enabled?
1369
        $tpl = 'mailtext';
1370
        $to  = $conf['notify'];
1371
    } elseif($who == 'subscribers') {
1372
        if(!actionOK('subscribe')) return false; //subscribers enabled?
1373
        if($conf['useacl'] && $INPUT->server->str('REMOTE_USER') && $minor) return false; //skip minors
1374
        $data = array('id' => $id, 'addresslist' => '', 'self' => false, 'replacements' => $replace);
1375
        trigger_event(
1376
            'COMMON_NOTIFY_ADDRESSLIST', $data,
1377
            array(new Subscription(), 'notifyaddresses')
1378
        );
1379
        $to = $data['addresslist'];
1380
        if(empty($to)) return false;
1381
        $tpl = 'subscr_single';
1382
    } else {
1383
        return false; //just to be safe
1384
    }
1385
1386
    // prepare content
1387
    $subscription = new Subscription();
1388
    return $subscription->send_diff($to, $tpl, $id, $rev, $summary);
1389
}
1390
1391
/**
1392
 * extracts the query from a search engine referrer
1393
 *
1394
 * @author Andreas Gohr <[email protected]>
1395
 * @author Todd Augsburger <[email protected]>
1396
 *
1397
 * @return array|string
1398
 */
1399
function getGoogleQuery() {
1400
    /* @var Input $INPUT */
1401
    global $INPUT;
1402
1403
    if(!$INPUT->server->has('HTTP_REFERER')) {
1404
        return '';
1405
    }
1406
    $url = parse_url($INPUT->server->str('HTTP_REFERER'));
1407
1408
    // only handle common SEs
1409
    if(!preg_match('/(google|bing|yahoo|ask|duckduckgo|babylon|aol|yandex)/',$url['host'])) return '';
1410
1411
    $query = array();
1412
    // temporary workaround against PHP bug #49733
1413
    // see http://bugs.php.net/bug.php?id=49733
1414
    if(UTF8_MBSTRING) $enc = mb_internal_encoding();
1415
    parse_str($url['query'], $query);
1416
    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...
1417
1418
    $q = '';
1419
    if(isset($query['q'])){
1420
        $q = $query['q'];
1421
    }elseif(isset($query['p'])){
1422
        $q = $query['p'];
1423
    }elseif(isset($query['query'])){
1424
        $q = $query['query'];
1425
    }
1426
    $q = trim($q);
1427
1428
    if(!$q) return '';
1429
    $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/', $q, -1, PREG_SPLIT_NO_EMPTY);
1430
    return $q;
1431
}
1432
1433
/**
1434
 * Return the human readable size of a file
1435
 *
1436
 * @param int $size A file size
1437
 * @param int $dec A number of decimal places
1438
 * @return string human readable size
1439
 *
1440
 * @author      Martin Benjamin <[email protected]>
1441
 * @author      Aidan Lister <[email protected]>
1442
 * @version     1.0.0
1443
 */
1444
function filesize_h($size, $dec = 1) {
1445
    $sizes = array('B', 'KB', 'MB', 'GB');
1446
    $count = count($sizes);
1447
    $i     = 0;
1448
1449
    while($size >= 1024 && ($i < $count - 1)) {
1450
        $size /= 1024;
1451
        $i++;
1452
    }
1453
1454
    return round($size, $dec).' '.$sizes[$i];
1455
}
1456
1457
/**
1458
 * Return the given timestamp as human readable, fuzzy age
1459
 *
1460
 * @author Andreas Gohr <[email protected]>
1461
 *
1462
 * @param int $dt timestamp
1463
 * @return string
1464
 */
1465
function datetime_h($dt) {
1466
    global $lang;
1467
1468
    $ago = time() - $dt;
1469
    if($ago > 24 * 60 * 60 * 30 * 12 * 2) {
1470
        return sprintf($lang['years'], round($ago / (24 * 60 * 60 * 30 * 12)));
1471
    }
1472
    if($ago > 24 * 60 * 60 * 30 * 2) {
1473
        return sprintf($lang['months'], round($ago / (24 * 60 * 60 * 30)));
1474
    }
1475
    if($ago > 24 * 60 * 60 * 7 * 2) {
1476
        return sprintf($lang['weeks'], round($ago / (24 * 60 * 60 * 7)));
1477
    }
1478
    if($ago > 24 * 60 * 60 * 2) {
1479
        return sprintf($lang['days'], round($ago / (24 * 60 * 60)));
1480
    }
1481
    if($ago > 60 * 60 * 2) {
1482
        return sprintf($lang['hours'], round($ago / (60 * 60)));
1483
    }
1484
    if($ago > 60 * 2) {
1485
        return sprintf($lang['minutes'], round($ago / (60)));
1486
    }
1487
    return sprintf($lang['seconds'], $ago);
1488
}
1489
1490
/**
1491
 * Wraps around strftime but provides support for fuzzy dates
1492
 *
1493
 * The format default to $conf['dformat']. It is passed to
1494
 * strftime - %f can be used to get the value from datetime_h()
1495
 *
1496
 * @see datetime_h
1497
 * @author Andreas Gohr <[email protected]>
1498
 *
1499
 * @param int|null $dt      timestamp when given, null will take current timestamp
1500
 * @param string   $format  empty default to $conf['dformat'], or provide format as recognized by strftime()
1501
 * @return string
1502
 */
1503
function dformat($dt = null, $format = '') {
1504
    global $conf;
1505
1506
    if(is_null($dt)) $dt = time();
1507
    $dt = (int) $dt;
1508
    if(!$format) $format = $conf['dformat'];
1509
1510
    $format = str_replace('%f', datetime_h($dt), $format);
1511
    return strftime($format, $dt);
1512
}
1513
1514
/**
1515
 * Formats a timestamp as ISO 8601 date
1516
 *
1517
 * @author <ungu at terong dot com>
1518
 * @link http://www.php.net/manual/en/function.date.php#54072
1519
 *
1520
 * @param int $int_date current date in UNIX timestamp
1521
 * @return string
1522
 */
1523
function date_iso8601($int_date) {
1524
    $date_mod     = date('Y-m-d\TH:i:s', $int_date);
1525
    $pre_timezone = date('O', $int_date);
1526
    $time_zone    = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2);
1527
    $date_mod .= $time_zone;
1528
    return $date_mod;
1529
}
1530
1531
/**
1532
 * return an obfuscated email address in line with $conf['mailguard'] setting
1533
 *
1534
 * @author Harry Fuecks <[email protected]>
1535
 * @author Christopher Smith <[email protected]>
1536
 *
1537
 * @param string $email email address
1538
 * @return string
1539
 */
1540
function obfuscate($email) {
1541
    global $conf;
1542
1543
    switch($conf['mailguard']) {
1544
        case 'visible' :
1545
            $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
1546
            return strtr($email, $obfuscate);
1547
1548
        case 'hex' :
1549
            $encode = '';
1550
            $len    = strlen($email);
1551
            for($x = 0; $x < $len; $x++) {
1552
                $encode .= '&#x'.bin2hex($email{$x}).';';
1553
            }
1554
            return $encode;
1555
1556
        case 'none' :
1557
        default :
1558
            return $email;
1559
    }
1560
}
1561
1562
/**
1563
 * Removes quoting backslashes
1564
 *
1565
 * @author Andreas Gohr <[email protected]>
1566
 *
1567
 * @param string $string
1568
 * @param string $char backslashed character
1569
 * @return string
1570
 */
1571
function unslash($string, $char = "'") {
1572
    return str_replace('\\'.$char, $char, $string);
1573
}
1574
1575
/**
1576
 * Convert php.ini shorthands to byte
1577
 *
1578
 * @author <gilthans dot NO dot SPAM at gmail dot com>
1579
 * @link   http://de3.php.net/manual/en/ini.core.php#79564
1580
 *
1581
 * @param string $v shorthands
1582
 * @return int|string
1583
 */
1584
function php_to_byte($v) {
1585
    $l   = substr($v, -1);
1586
    $ret = substr($v, 0, -1);
1587
    switch(strtoupper($l)) {
1588
        /** @noinspection PhpMissingBreakStatementInspection */
1589
        case 'P':
1590
            $ret *= 1024;
1591
        /** @noinspection PhpMissingBreakStatementInspection */
1592
        case 'T':
1593
            $ret *= 1024;
1594
        /** @noinspection PhpMissingBreakStatementInspection */
1595
        case 'G':
1596
            $ret *= 1024;
1597
        /** @noinspection PhpMissingBreakStatementInspection */
1598
        case 'M':
1599
            $ret *= 1024;
1600
        /** @noinspection PhpMissingBreakStatementInspection */
1601
        case 'K':
1602
            $ret *= 1024;
1603
            break;
1604
        default;
1605
            $ret *= 10;
1606
            break;
1607
    }
1608
    return $ret;
1609
}
1610
1611
/**
1612
 * Wrapper around preg_quote adding the default delimiter
1613
 *
1614
 * @param string $string
1615
 * @return string
1616
 */
1617
function preg_quote_cb($string) {
1618
    return preg_quote($string, '/');
1619
}
1620
1621
/**
1622
 * Shorten a given string by removing data from the middle
1623
 *
1624
 * You can give the string in two parts, the first part $keep
1625
 * will never be shortened. The second part $short will be cut
1626
 * in the middle to shorten but only if at least $min chars are
1627
 * left to display it. Otherwise it will be left off.
1628
 *
1629
 * @param string $keep   the part to keep
1630
 * @param string $short  the part to shorten
1631
 * @param int    $max    maximum chars you want for the whole string
1632
 * @param int    $min    minimum number of chars to have left for middle shortening
1633
 * @param string $char   the shortening character to use
1634
 * @return string
1635
 */
1636
function shorten($keep, $short, $max, $min = 9, $char = '…') {
1637
    $max = $max - utf8_strlen($keep);
1638
    if($max < $min) return $keep;
1639
    $len = utf8_strlen($short);
1640
    if($len <= $max) return $keep.$short;
1641
    $half = floor($max / 2);
1642
    return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half);
1643
}
1644
1645
/**
1646
 * Return the users real name or e-mail address for use
1647
 * in page footer and recent changes pages
1648
 *
1649
 * @param string|null $username or null when currently logged-in user should be used
1650
 * @param bool $textonly true returns only plain text, true allows returning html
1651
 * @return string html or plain text(not escaped) of formatted user name
1652
 *
1653
 * @author Andy Webber <dokuwiki AT andywebber DOT com>
1654
 */
1655
function editorinfo($username, $textonly = false) {
1656
    return userlink($username, $textonly);
1657
}
1658
1659
/**
1660
 * Returns users realname w/o link
1661
 *
1662
 * @param string|null $username or null when currently logged-in user should be used
1663
 * @param bool $textonly true returns only plain text, true allows returning html
1664
 * @return string html or plain text(not escaped) of formatted user name
1665
 *
1666
 * @triggers COMMON_USER_LINK
1667
 */
1668
function userlink($username = null, $textonly = false) {
1669
    global $conf, $INFO;
1670
    /** @var DokuWiki_Auth_Plugin $auth */
1671
    global $auth;
1672
    /** @var Input $INPUT */
1673
    global $INPUT;
1674
1675
    // prepare initial event data
1676
    $data = array(
1677
        'username' => $username, // the unique user name
1678
        'name' => '',
1679
        'link' => array( //setting 'link' to false disables linking
1680
                         'target' => '',
1681
                         'pre' => '',
1682
                         'suf' => '',
1683
                         'style' => '',
1684
                         'more' => '',
1685
                         'url' => '',
1686
                         'title' => '',
1687
                         'class' => ''
1688
        ),
1689
        'userlink' => '', // formatted user name as will be returned
1690
        'textonly' => $textonly
1691
    );
1692
    if($username === null) {
1693
        $data['username'] = $username = $INPUT->server->str('REMOTE_USER');
1694
        if($textonly){
1695
            $data['name'] = $INFO['userinfo']['name']. ' (' . $INPUT->server->str('REMOTE_USER') . ')';
1696
        }else {
1697
            $data['name'] = '<bdi>' . hsc($INFO['userinfo']['name']) . '</bdi> (<bdi>' . hsc($INPUT->server->str('REMOTE_USER')) . '</bdi>)';
1698
        }
1699
    }
1700
1701
    $evt = new Doku_Event('COMMON_USER_LINK', $data);
1702
    if($evt->advise_before(true)) {
1703
        if(empty($data['name'])) {
1704
            if($auth) $info = $auth->getUserData($username);
1705
            if($conf['showuseras'] != 'loginname' && isset($info) && $info) {
1706
                switch($conf['showuseras']) {
1707
                    case 'username':
1708
                    case 'username_link':
1709
                        $data['name'] = $textonly ? $info['name'] : hsc($info['name']);
1710
                        break;
1711
                    case 'email':
1712
                    case 'email_link':
1713
                        $data['name'] = obfuscate($info['mail']);
1714
                        break;
1715
                }
1716
            } else {
1717
                $data['name'] = $textonly ? $data['username'] : hsc($data['username']);
1718
            }
1719
        }
1720
1721
        /** @var Doku_Renderer_xhtml $xhtml_renderer */
1722
        static $xhtml_renderer = null;
1723
1724
        if(!$data['textonly'] && empty($data['link']['url'])) {
1725
1726
            if(in_array($conf['showuseras'], array('email_link', 'username_link'))) {
1727
                if(!isset($info)) {
1728
                    if($auth) $info = $auth->getUserData($username);
1729
                }
1730
                if(isset($info) && $info) {
1731
                    if($conf['showuseras'] == 'email_link') {
1732
                        $data['link']['url'] = 'mailto:' . obfuscate($info['mail']);
1733
                    } else {
1734
                        if(is_null($xhtml_renderer)) {
1735
                            $xhtml_renderer = p_get_renderer('xhtml');
1736
                        }
1737
                        if(empty($xhtml_renderer->interwiki)) {
1738
                            $xhtml_renderer->interwiki = getInterwiki();
1739
                        }
1740
                        $shortcut = 'user';
1741
                        $exists = null;
1742
                        $data['link']['url'] = $xhtml_renderer->_resolveInterWiki($shortcut, $username, $exists);
1743
                        $data['link']['class'] .= ' interwiki iw_user';
1744
                        if($exists !== null) {
1745
                            if($exists) {
1746
                                $data['link']['class'] .= ' wikilink1';
1747
                            } else {
1748
                                $data['link']['class'] .= ' wikilink2';
1749
                                $data['link']['rel'] = 'nofollow';
1750
                            }
1751
                        }
1752
                    }
1753
                } else {
1754
                    $data['textonly'] = true;
1755
                }
1756
1757
            } else {
1758
                $data['textonly'] = true;
1759
            }
1760
        }
1761
1762
        if($data['textonly']) {
1763
            $data['userlink'] = $data['name'];
1764
        } else {
1765
            $data['link']['name'] = $data['name'];
1766
            if(is_null($xhtml_renderer)) {
1767
                $xhtml_renderer = p_get_renderer('xhtml');
1768
            }
1769
            $data['userlink'] = $xhtml_renderer->_formatLink($data['link']);
1770
        }
1771
    }
1772
    $evt->advise_after();
1773
    unset($evt);
1774
1775
    return $data['userlink'];
1776
}
1777
1778
/**
1779
 * Returns the path to a image file for the currently chosen license.
1780
 * When no image exists, returns an empty string
1781
 *
1782
 * @author Andreas Gohr <[email protected]>
1783
 *
1784
 * @param  string $type - type of image 'badge' or 'button'
1785
 * @return string
1786
 */
1787
function license_img($type) {
1788
    global $license;
1789
    global $conf;
1790
    if(!$conf['license']) return '';
1791
    if(!is_array($license[$conf['license']])) return '';
1792
    $try   = array();
1793
    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
1794
    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
1795
    if(substr($conf['license'], 0, 3) == 'cc-') {
1796
        $try[] = 'lib/images/license/'.$type.'/cc.png';
1797
    }
1798
    foreach($try as $src) {
1799
        if(file_exists(DOKU_INC.$src)) return $src;
1800
    }
1801
    return '';
1802
}
1803
1804
/**
1805
 * Checks if the given amount of memory is available
1806
 *
1807
 * If the memory_get_usage() function is not available the
1808
 * function just assumes $bytes of already allocated memory
1809
 *
1810
 * @author Filip Oscadal <[email protected]>
1811
 * @author Andreas Gohr <[email protected]>
1812
 *
1813
 * @param int  $mem    Size of memory you want to allocate in bytes
1814
 * @param int  $bytes  already allocated memory (see above)
1815
 * @return bool
1816
 */
1817
function is_mem_available($mem, $bytes = 1048576) {
1818
    $limit = trim(ini_get('memory_limit'));
1819
    if(empty($limit)) return true; // no limit set!
1820
1821
    // parse limit to bytes
1822
    $limit = php_to_byte($limit);
1823
1824
    // get used memory if possible
1825
    if(function_exists('memory_get_usage')) {
1826
        $used = memory_get_usage();
1827
    } else {
1828
        $used = $bytes;
1829
    }
1830
1831
    if($used + $mem > $limit) {
1832
        return false;
1833
    }
1834
1835
    return true;
1836
}
1837
1838
/**
1839
 * Send a HTTP redirect to the browser
1840
 *
1841
 * Works arround Microsoft IIS cookie sending bug. Exits the script.
1842
 *
1843
 * @link   http://support.microsoft.com/kb/q176113/
1844
 * @author Andreas Gohr <[email protected]>
1845
 *
1846
 * @param string $url url being directed to
1847
 */
1848
function send_redirect($url) {
1849
    $url = stripctl($url); // defend against HTTP Response Splitting
1850
1851
    /* @var Input $INPUT */
1852
    global $INPUT;
1853
1854
    //are there any undisplayed messages? keep them in session for display
1855
    global $MSG;
1856
    if(isset($MSG) && count($MSG) && !defined('NOSESSION')) {
1857
        //reopen session, store data and close session again
1858
        @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...
1859
        $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
1860
    }
1861
1862
    // always close the session
1863
    session_write_close();
1864
1865
    // check if running on IIS < 6 with CGI-PHP
1866
    if($INPUT->server->has('SERVER_SOFTWARE') && $INPUT->server->has('GATEWAY_INTERFACE') &&
1867
        (strpos($INPUT->server->str('GATEWAY_INTERFACE'), 'CGI') !== false) &&
1868
        (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($INPUT->server->str('SERVER_SOFTWARE')), $matches)) &&
1869
        $matches[1] < 6
1870
    ) {
1871
        header('Refresh: 0;url='.$url);
1872
    } else {
1873
        header('Location: '.$url);
1874
    }
1875
1876
    if(defined('DOKU_UNITTEST')) return; // no exits during unit tests
1877
    exit;
1878
}
1879
1880
/**
1881
 * Validate a value using a set of valid values
1882
 *
1883
 * This function checks whether a specified value is set and in the array
1884
 * $valid_values. If not, the function returns a default value or, if no
1885
 * default is specified, throws an exception.
1886
 *
1887
 * @param string $param        The name of the parameter
1888
 * @param array  $valid_values A set of valid values; Optionally a default may
1889
 *                             be marked by the key “default”.
1890
 * @param array  $array        The array containing the value (typically $_POST
1891
 *                             or $_GET)
1892
 * @param string $exc          The text of the raised exception
1893
 *
1894
 * @throws Exception
1895
 * @return mixed
1896
 * @author Adrian Lang <[email protected]>
1897
 */
1898
function valid_input_set($param, $valid_values, $array, $exc = '') {
1899
    if(isset($array[$param]) && in_array($array[$param], $valid_values)) {
1900
        return $array[$param];
1901
    } elseif(isset($valid_values['default'])) {
1902
        return $valid_values['default'];
1903
    } else {
1904
        throw new Exception($exc);
1905
    }
1906
}
1907
1908
/**
1909
 * Read a preference from the DokuWiki cookie
1910
 * (remembering both keys & values are urlencoded)
1911
 *
1912
 * @param string $pref     preference key
1913
 * @param mixed  $default  value returned when preference not found
1914
 * @return string preference value
1915
 */
1916
function get_doku_pref($pref, $default) {
1917
    $enc_pref = urlencode($pref);
1918
    if(isset($_COOKIE['DOKU_PREFS']) && strpos($_COOKIE['DOKU_PREFS'], $enc_pref) !== false) {
1919
        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1920
        $cnt   = count($parts);
1921
        for($i = 0; $i < $cnt; $i += 2) {
1922
            if($parts[$i] == $enc_pref) {
1923
                return urldecode($parts[$i + 1]);
1924
            }
1925
        }
1926
    }
1927
    return $default;
1928
}
1929
1930
/**
1931
 * Add a preference to the DokuWiki cookie
1932
 * (remembering $_COOKIE['DOKU_PREFS'] is urlencoded)
1933
 * Remove it by setting $val to false
1934
 *
1935
 * @param string $pref  preference key
1936
 * @param string $val   preference value
1937
 */
1938
function set_doku_pref($pref, $val) {
1939
    global $conf;
1940
    $orig = get_doku_pref($pref, false);
1941
    $cookieVal = '';
1942
1943
    if($orig && ($orig != $val)) {
1944
        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1945
        $cnt   = count($parts);
1946
        // urlencode $pref for the comparison
1947
        $enc_pref = rawurlencode($pref);
1948
        for($i = 0; $i < $cnt; $i += 2) {
1949
            if($parts[$i] == $enc_pref) {
1950
                if ($val !== false) {
1951
                    $parts[$i + 1] = rawurlencode($val);
1952
                } else {
1953
                    unset($parts[$i]);
1954
                    unset($parts[$i + 1]);
1955
                }
1956
                break;
1957
            }
1958
        }
1959
        $cookieVal = implode('#', $parts);
1960
    } else if (!$orig && $val !== false) {
1961
        $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val);
1962
    }
1963
1964
    if (!empty($cookieVal)) {
1965
        $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
1966
        setcookie('DOKU_PREFS', $cookieVal, time()+365*24*3600, $cookieDir, '', ($conf['securecookie'] && is_ssl()));
1967
    }
1968
}
1969
1970
/**
1971
 * Strips source mapping declarations from given text #601
1972
 *
1973
 * @param string &$text reference to the CSS or JavaScript code to clean
1974
 */
1975
function stripsourcemaps(&$text){
1976
    $text = preg_replace('/^(\/\/|\/\*)[@#]\s+sourceMappingURL=.*?(\*\/)?$/im', '\\1\\2', $text);
1977
}
1978
1979
//Setup VIM: ex: et ts=2 :
1980