xref: /dokuwiki/inc/common.php (revision 1015a57dff9a6f85b8e0534d280aa1e09945a598)
1<?php
2/**
3 * Common DokuWiki functions
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <andi@splitbrain.org>
7 */
8
9if(!defined('DOKU_INC')) die('meh.');
10
11/**
12 * These constants are used with the recents function
13 */
14define('RECENTS_SKIP_DELETED', 2);
15define('RECENTS_SKIP_MINORS', 4);
16define('RECENTS_SKIP_SUBSPACES', 8);
17define('RECENTS_MEDIA_CHANGES', 16);
18define('RECENTS_MEDIA_PAGES_MIXED', 32);
19
20/**
21 * Wrapper around htmlspecialchars()
22 *
23 * @author Andreas Gohr <andi@splitbrain.org>
24 * @see    htmlspecialchars()
25 */
26function hsc($string) {
27    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
28}
29
30/**
31 * print a newline terminated string
32 *
33 * You can give an indention as optional parameter
34 *
35 * @author Andreas Gohr <andi@splitbrain.org>
36 */
37function ptln($string, $indent = 0) {
38    echo str_repeat(' ', $indent)."$string\n";
39}
40
41/**
42 * strips control characters (<32) from the given string
43 *
44 * @author Andreas Gohr <andi@splitbrain.org>
45 */
46function stripctl($string) {
47    return preg_replace('/[\x00-\x1F]+/s', '', $string);
48}
49
50/**
51 * Return a secret token to be used for CSRF attack prevention
52 *
53 * @author  Andreas Gohr <andi@splitbrain.org>
54 * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
55 * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
56 * @return  string
57 */
58function getSecurityToken() {
59    return md5(auth_cookiesalt().session_id().$_SERVER['REMOTE_USER']);
60}
61
62/**
63 * Check the secret CSRF token
64 */
65function checkSecurityToken($token = null) {
66    global $INPUT;
67    if(!$_SERVER['REMOTE_USER']) return true; // no logged in user, no need for a check
68
69    if(is_null($token)) $token = $INPUT->str('sectok');
70    if(getSecurityToken() != $token) {
71        msg('Security Token did not match. Possible CSRF attack.', -1);
72        return false;
73    }
74    return true;
75}
76
77/**
78 * Print a hidden form field with a secret CSRF token
79 *
80 * @author  Andreas Gohr <andi@splitbrain.org>
81 */
82function formSecurityToken($print = true) {
83    $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n";
84    if($print) echo $ret;
85    return $ret;
86}
87
88/**
89 * Determine basic information for a request of $id
90 *
91 * @param unknown_type $id
92 * @param unknown_type $httpClient
93 */
94function basicinfo($id, $htmlClient=true){
95    global $USERINFO;
96
97    // set info about manager/admin status.
98    $info['isadmin']   = false;
99    $info['ismanager'] = false;
100    if(isset($_SERVER['REMOTE_USER'])) {
101        $info['userinfo']   = $USERINFO;
102        $info['perm']       = auth_quickaclcheck($id);
103        $info['client']     = $_SERVER['REMOTE_USER'];
104
105        if($info['perm'] == AUTH_ADMIN) {
106            $info['isadmin']   = true;
107            $info['ismanager'] = true;
108        } elseif(auth_ismanager()) {
109            $info['ismanager'] = true;
110        }
111
112        // if some outside auth were used only REMOTE_USER is set
113        if(!$info['userinfo']['name']) {
114            $info['userinfo']['name'] = $_SERVER['REMOTE_USER'];
115        }
116
117    } else {
118        $info['perm']       = auth_aclcheck($id, '', null);
119        $info['subscribed'] = false;
120        $info['client']     = clientIP(true);
121    }
122
123    $info['namespace'] = getNS($id);
124
125    // mobile detection
126    if ($htmlClient) {
127        $info['ismobile'] = clientismobile();
128    }
129
130    return $info;
131 }
132
133/**
134 * Return info about the current document as associative
135 * array.
136 *
137 * @author Andreas Gohr <andi@splitbrain.org>
138 */
139function pageinfo() {
140    global $ID;
141    global $REV;
142    global $RANGE;
143    global $lang;
144
145    $info = basicinfo($ID);
146
147    // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
148    // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
149    $info['id']  = $ID;
150    $info['rev'] = $REV;
151
152    $info['locked']    = checklock($ID);
153    $info['filepath']  = fullpath(wikiFN($ID));
154    $info['exists']    = @file_exists($info['filepath']);
155    if($REV) {
156        //check if current revision was meant
157        if($info['exists'] && (@filemtime($info['filepath']) == $REV)) {
158            $REV = '';
159        } elseif($RANGE) {
160            //section editing does not work with old revisions!
161            $REV   = '';
162            $RANGE = '';
163            msg($lang['nosecedit'], 0);
164        } else {
165            //really use old revision
166            $info['filepath'] = fullpath(wikiFN($ID, $REV));
167            $info['exists']   = @file_exists($info['filepath']);
168        }
169    }
170    $info['rev'] = $REV;
171    if($info['exists']) {
172        $info['writable'] = (is_writable($info['filepath']) &&
173            ($info['perm'] >= AUTH_EDIT));
174    } else {
175        $info['writable'] = ($info['perm'] >= AUTH_CREATE);
176    }
177    $info['editable'] = ($info['writable'] && empty($info['locked']));
178    $info['lastmod']  = @filemtime($info['filepath']);
179
180    //load page meta data
181    $info['meta'] = p_get_metadata($ID);
182
183    //who's the editor
184    if($REV) {
185        $revinfo = getRevisionInfo($ID, $REV, 1024);
186    } else {
187        if(is_array($info['meta']['last_change'])) {
188            $revinfo = $info['meta']['last_change'];
189        } else {
190            $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024);
191            // cache most recent changelog line in metadata if missing and still valid
192            if($revinfo !== false) {
193                $info['meta']['last_change'] = $revinfo;
194                p_set_metadata($ID, array('last_change' => $revinfo));
195            }
196        }
197    }
198    //and check for an external edit
199    if($revinfo !== false && $revinfo['date'] != $info['lastmod']) {
200        // cached changelog line no longer valid
201        $revinfo                     = false;
202        $info['meta']['last_change'] = $revinfo;
203        p_set_metadata($ID, array('last_change' => $revinfo));
204    }
205
206    $info['ip']   = $revinfo['ip'];
207    $info['user'] = $revinfo['user'];
208    $info['sum']  = $revinfo['sum'];
209    // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
210    // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
211
212    if($revinfo['user']) {
213        $info['editor'] = $revinfo['user'];
214    } else {
215        $info['editor'] = $revinfo['ip'];
216    }
217
218    // draft
219    $draft = getCacheName($info['client'].$ID, '.draft');
220    if(@file_exists($draft)) {
221        if(@filemtime($draft) < @filemtime(wikiFN($ID))) {
222            // remove stale draft
223            @unlink($draft);
224        } else {
225            $info['draft'] = $draft;
226        }
227    }
228
229    return $info;
230}
231
232/**
233 * Return information about the current media item as an associative array.
234 */
235function mediainfo(){
236    global $NS;
237    global $IMG;
238
239    $info = basicinfo("$NS:*");
240    $info['image'] = $IMG;
241
242    return $info;
243}
244
245/**
246 * Build an string of URL parameters
247 *
248 * @author Andreas Gohr
249 */
250function buildURLparams($params, $sep = '&amp;') {
251    $url = '';
252    $amp = false;
253    foreach($params as $key => $val) {
254        if($amp) $url .= $sep;
255
256        $url .= rawurlencode($key).'=';
257        $url .= rawurlencode((string) $val);
258        $amp = true;
259    }
260    return $url;
261}
262
263/**
264 * Build an string of html tag attributes
265 *
266 * Skips keys starting with '_', values get HTML encoded
267 *
268 * @author Andreas Gohr
269 */
270function buildAttributes($params, $skipempty = false) {
271    $url   = '';
272    $white = false;
273    foreach($params as $key => $val) {
274        if($key{0} == '_') continue;
275        if($val === '' && $skipempty) continue;
276        if($white) $url .= ' ';
277
278        $url .= $key.'="';
279        $url .= htmlspecialchars($val);
280        $url .= '"';
281        $white = true;
282    }
283    return $url;
284}
285
286/**
287 * This builds the breadcrumb trail and returns it as array
288 *
289 * @author Andreas Gohr <andi@splitbrain.org>
290 */
291function breadcrumbs() {
292    // we prepare the breadcrumbs early for quick session closing
293    static $crumbs = null;
294    if($crumbs != null) return $crumbs;
295
296    global $ID;
297    global $ACT;
298    global $conf;
299
300    //first visit?
301    $crumbs = isset($_SESSION[DOKU_COOKIE]['bc']) ? $_SESSION[DOKU_COOKIE]['bc'] : array();
302    //we only save on show and existing wiki documents
303    $file = wikiFN($ID);
304    if($ACT != 'show' || !@file_exists($file)) {
305        $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
306        return $crumbs;
307    }
308
309    // page names
310    $name = noNSorNS($ID);
311    if(useHeading('navigation')) {
312        // get page title
313        $title = p_get_first_heading($ID, METADATA_RENDER_USING_SIMPLE_CACHE);
314        if($title) {
315            $name = $title;
316        }
317    }
318
319    //remove ID from array
320    if(isset($crumbs[$ID])) {
321        unset($crumbs[$ID]);
322    }
323
324    //add to array
325    $crumbs[$ID] = $name;
326    //reduce size
327    while(count($crumbs) > $conf['breadcrumbs']) {
328        array_shift($crumbs);
329    }
330    //save to session
331    $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
332    return $crumbs;
333}
334
335/**
336 * Filter for page IDs
337 *
338 * This is run on a ID before it is outputted somewhere
339 * currently used to replace the colon with something else
340 * on Windows (non-IIS) systems and to have proper URL encoding
341 *
342 * See discussions at https://github.com/splitbrain/dokuwiki/pull/84 and
343 * https://github.com/splitbrain/dokuwiki/pull/173 why we use a whitelist of
344 * unaffected servers instead of blacklisting affected servers here.
345 *
346 * Urlencoding is ommitted when the second parameter is false
347 *
348 * @author Andreas Gohr <andi@splitbrain.org>
349 */
350function idfilter($id, $ue = true) {
351    global $conf;
352    if($conf['useslash'] && $conf['userewrite']) {
353        $id = strtr($id, ':', '/');
354    } elseif(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
355        $conf['userewrite'] &&
356        strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false
357    ) {
358        $id = strtr($id, ':', ';');
359    }
360    if($ue) {
361        $id = rawurlencode($id);
362        $id = str_replace('%3A', ':', $id); //keep as colon
363        $id = str_replace('%2F', '/', $id); //keep as slash
364    }
365    return $id;
366}
367
368/**
369 * This builds a link to a wikipage
370 *
371 * It handles URL rewriting and adds additional parameter if
372 * given in $more
373 *
374 * @author Andreas Gohr <andi@splitbrain.org>
375 */
376function wl($id = '', $urlParameters = '', $absolute = false, $separator = '&amp;') {
377    global $conf;
378    if(is_array($urlParameters)) {
379        $urlParameters = buildURLparams($urlParameters, $separator);
380    } else {
381        $urlParameters = str_replace(',', $separator, $urlParameters);
382    }
383    if($id === '') {
384        $id = $conf['start'];
385    }
386    $id = idfilter($id);
387    if($absolute) {
388        $xlink = DOKU_URL;
389    } else {
390        $xlink = DOKU_BASE;
391    }
392
393    if($conf['userewrite'] == 2) {
394        $xlink .= DOKU_SCRIPT.'/'.$id;
395        if($urlParameters) $xlink .= '?'.$urlParameters;
396    } elseif($conf['userewrite']) {
397        $xlink .= $id;
398        if($urlParameters) $xlink .= '?'.$urlParameters;
399    } elseif($id) {
400        $xlink .= DOKU_SCRIPT.'?id='.$id;
401        if($urlParameters) $xlink .= $separator.$urlParameters;
402    } else {
403        $xlink .= DOKU_SCRIPT;
404        if($urlParameters) $xlink .= '?'.$urlParameters;
405    }
406
407    return $xlink;
408}
409
410/**
411 * This builds a link to an alternate page format
412 *
413 * Handles URL rewriting if enabled. Follows the style of wl().
414 *
415 * @author Ben Coburn <btcoburn@silicodon.net>
416 */
417function exportlink($id = '', $format = 'raw', $more = '', $abs = false, $sep = '&amp;') {
418    global $conf;
419    if(is_array($more)) {
420        $more = buildURLparams($more, $sep);
421    } else {
422        $more = str_replace(',', $sep, $more);
423    }
424
425    $format = rawurlencode($format);
426    $id     = idfilter($id);
427    if($abs) {
428        $xlink = DOKU_URL;
429    } else {
430        $xlink = DOKU_BASE;
431    }
432
433    if($conf['userewrite'] == 2) {
434        $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
435        if($more) $xlink .= $sep.$more;
436    } elseif($conf['userewrite'] == 1) {
437        $xlink .= '_export/'.$format.'/'.$id;
438        if($more) $xlink .= '?'.$more;
439    } else {
440        $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
441        if($more) $xlink .= $sep.$more;
442    }
443
444    return $xlink;
445}
446
447/**
448 * Build a link to a media file
449 *
450 * Will return a link to the detail page if $direct is false
451 *
452 * The $more parameter should always be given as array, the function then
453 * will strip default parameters to produce even cleaner URLs
454 *
455 * @param string  $id     the media file id or URL
456 * @param mixed   $more   string or array with additional parameters
457 * @param bool    $direct link to detail page if false
458 * @param string  $sep    URL parameter separator
459 * @param bool    $abs    Create an absolute URL
460 * @return string
461 */
462function ml($id = '', $more = '', $direct = true, $sep = '&amp;', $abs = false) {
463    global $conf;
464    if(is_array($more)) {
465        // strip defaults for shorter URLs
466        if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']);
467        if(!$more['w']) unset($more['w']);
468        if(!$more['h']) unset($more['h']);
469        if(isset($more['id']) && $direct) unset($more['id']);
470        $more = buildURLparams($more, $sep);
471    } else {
472        $more = str_replace('cache=cache', '', $more); //skip default
473        $more = str_replace(',,', ',', $more);
474        $more = str_replace(',', $sep, $more);
475    }
476
477    if($abs) {
478        $xlink = DOKU_URL;
479    } else {
480        $xlink = DOKU_BASE;
481    }
482
483    // external URLs are always direct without rewriting
484    if(preg_match('#^(https?|ftp)://#i', $id)) {
485        $xlink .= 'lib/exe/fetch.php';
486        // add hash:
487        $xlink .= '?hash='.substr(md5(auth_cookiesalt().$id), 0, 6);
488        if($more) {
489            $xlink .= $sep.$more;
490            $xlink .= $sep.'media='.rawurlencode($id);
491        } else {
492            $xlink .= $sep.'media='.rawurlencode($id);
493        }
494        return $xlink;
495    }
496
497    $id = idfilter($id);
498
499    // decide on scriptname
500    if($direct) {
501        if($conf['userewrite'] == 1) {
502            $script = '_media';
503        } else {
504            $script = 'lib/exe/fetch.php';
505        }
506    } else {
507        if($conf['userewrite'] == 1) {
508            $script = '_detail';
509        } else {
510            $script = 'lib/exe/detail.php';
511        }
512    }
513
514    // build URL based on rewrite mode
515    if($conf['userewrite']) {
516        $xlink .= $script.'/'.$id;
517        if($more) $xlink .= '?'.$more;
518    } else {
519        if($more) {
520            $xlink .= $script.'?'.$more;
521            $xlink .= $sep.'media='.$id;
522        } else {
523            $xlink .= $script.'?media='.$id;
524        }
525    }
526
527    return $xlink;
528}
529
530/**
531 * Returns the URL to the DokuWiki base script
532 *
533 * Consider using wl() instead, unless you absoutely need the doku.php endpoint
534 *
535 * @author Andreas Gohr <andi@splitbrain.org>
536 */
537function script() {
538    return DOKU_BASE.DOKU_SCRIPT;
539}
540
541/**
542 * Spamcheck against wordlist
543 *
544 * Checks the wikitext against a list of blocked expressions
545 * returns true if the text contains any bad words
546 *
547 * Triggers COMMON_WORDBLOCK_BLOCKED
548 *
549 *  Action Plugins can use this event to inspect the blocked data
550 *  and gain information about the user who was blocked.
551 *
552 *  Event data:
553 *    data['matches']  - array of matches
554 *    data['userinfo'] - information about the blocked user
555 *      [ip]           - ip address
556 *      [user]         - username (if logged in)
557 *      [mail]         - mail address (if logged in)
558 *      [name]         - real name (if logged in)
559 *
560 * @author Andreas Gohr <andi@splitbrain.org>
561 * @author Michael Klier <chi@chimeric.de>
562 * @param  string $text - optional text to check, if not given the globals are used
563 * @return bool         - true if a spam word was found
564 */
565function checkwordblock($text = '') {
566    global $TEXT;
567    global $PRE;
568    global $SUF;
569    global $conf;
570    global $INFO;
571
572    if(!$conf['usewordblock']) return false;
573
574    if(!$text) $text = "$PRE $TEXT $SUF";
575
576    // we prepare the text a tiny bit to prevent spammers circumventing URL checks
577    $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', '\1http://\2 \2\3', $text);
578
579    $wordblocks = getWordblocks();
580    // how many lines to read at once (to work around some PCRE limits)
581    if(version_compare(phpversion(), '4.3.0', '<')) {
582        // old versions of PCRE define a maximum of parenthesises even if no
583        // backreferences are used - the maximum is 99
584        // this is very bad performancewise and may even be too high still
585        $chunksize = 40;
586    } else {
587        // read file in chunks of 200 - this should work around the
588        // MAX_PATTERN_SIZE in modern PCRE
589        $chunksize = 200;
590    }
591    while($blocks = array_splice($wordblocks, 0, $chunksize)) {
592        $re = array();
593        // build regexp from blocks
594        foreach($blocks as $block) {
595            $block = preg_replace('/#.*$/', '', $block);
596            $block = trim($block);
597            if(empty($block)) continue;
598            $re[] = $block;
599        }
600        if(count($re) && preg_match('#('.join('|', $re).')#si', $text, $matches)) {
601            // prepare event data
602            $data['matches']        = $matches;
603            $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR'];
604            if($_SERVER['REMOTE_USER']) {
605                $data['userinfo']['user'] = $_SERVER['REMOTE_USER'];
606                $data['userinfo']['name'] = $INFO['userinfo']['name'];
607                $data['userinfo']['mail'] = $INFO['userinfo']['mail'];
608            }
609            $callback = create_function('', 'return true;');
610            return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true);
611        }
612    }
613    return false;
614}
615
616/**
617 * Return the IP of the client
618 *
619 * Honours X-Forwarded-For and X-Real-IP Proxy Headers
620 *
621 * It returns a comma separated list of IPs if the above mentioned
622 * headers are set. If the single parameter is set, it tries to return
623 * a routable public address, prefering the ones suplied in the X
624 * headers
625 *
626 * @author Andreas Gohr <andi@splitbrain.org>
627 * @param  boolean $single If set only a single IP is returned
628 * @return string
629 */
630function clientIP($single = false) {
631    $ip   = array();
632    $ip[] = $_SERVER['REMOTE_ADDR'];
633    if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
634        $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_FORWARDED_FOR'])));
635    if(!empty($_SERVER['HTTP_X_REAL_IP']))
636        $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_REAL_IP'])));
637
638    // some IPv4/v6 regexps borrowed from Feyd
639    // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479
640    $dec_octet   = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])';
641    $hex_digit   = '[A-Fa-f0-9]';
642    $h16         = "{$hex_digit}{1,4}";
643    $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet";
644    $ls32        = "(?:$h16:$h16|$IPv4Address)";
645    $IPv6Address =
646        "(?:(?:{$IPv4Address})|(?:".
647            "(?:$h16:){6}$ls32".
648            "|::(?:$h16:){5}$ls32".
649            "|(?:$h16)?::(?:$h16:){4}$ls32".
650            "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32".
651            "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32".
652            "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32".
653            "|(?:(?:$h16:){0,4}$h16)?::$ls32".
654            "|(?:(?:$h16:){0,5}$h16)?::$h16".
655            "|(?:(?:$h16:){0,6}$h16)?::".
656            ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)";
657
658    // remove any non-IP stuff
659    $cnt   = count($ip);
660    $match = array();
661    for($i = 0; $i < $cnt; $i++) {
662        if(preg_match("/^$IPv4Address$/", $ip[$i], $match) || preg_match("/^$IPv6Address$/", $ip[$i], $match)) {
663            $ip[$i] = $match[0];
664        } else {
665            $ip[$i] = '';
666        }
667        if(empty($ip[$i])) unset($ip[$i]);
668    }
669    $ip = array_values(array_unique($ip));
670    if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
671
672    if(!$single) return join(',', $ip);
673
674    // decide which IP to use, trying to avoid local addresses
675    $ip = array_reverse($ip);
676    foreach($ip as $i) {
677        if(preg_match('/^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/', $i)) {
678            continue;
679        } else {
680            return $i;
681        }
682    }
683    // still here? just use the first (last) address
684    return $ip[0];
685}
686
687/**
688 * Check if the browser is on a mobile device
689 *
690 * Adapted from the example code at url below
691 *
692 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code
693 */
694function clientismobile() {
695
696    if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true;
697
698    if(preg_match('/wap\.|\.wap/i', $_SERVER['HTTP_ACCEPT'])) return true;
699
700    if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
701
702    $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';
703
704    if(preg_match("/$uamatches/i", $_SERVER['HTTP_USER_AGENT'])) return true;
705
706    return false;
707}
708
709/**
710 * Convert one or more comma separated IPs to hostnames
711 *
712 * If $conf['dnslookups'] is disabled it simply returns the input string
713 *
714 * @author Glen Harris <astfgl@iamnota.org>
715 * @param  string $ips comma separated list of IP addresses
716 * @return string a comma separated list of hostnames
717 */
718function gethostsbyaddrs($ips) {
719    global $conf;
720    if(!$conf['dnslookups']) return $ips;
721
722    $hosts = array();
723    $ips   = explode(',', $ips);
724
725    if(is_array($ips)) {
726        foreach($ips as $ip) {
727            $hosts[] = gethostbyaddr(trim($ip));
728        }
729        return join(',', $hosts);
730    } else {
731        return gethostbyaddr(trim($ips));
732    }
733}
734
735/**
736 * Checks if a given page is currently locked.
737 *
738 * removes stale lockfiles
739 *
740 * @author Andreas Gohr <andi@splitbrain.org>
741 */
742function checklock($id) {
743    global $conf;
744    $lock = wikiLockFN($id);
745
746    //no lockfile
747    if(!@file_exists($lock)) return false;
748
749    //lockfile expired
750    if((time() - filemtime($lock)) > $conf['locktime']) {
751        @unlink($lock);
752        return false;
753    }
754
755    //my own lock
756    list($ip, $session) = explode("\n", io_readFile($lock));
757    if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) {
758        return false;
759    }
760
761    return $ip;
762}
763
764/**
765 * Lock a page for editing
766 *
767 * @author Andreas Gohr <andi@splitbrain.org>
768 */
769function lock($id) {
770    global $conf;
771
772    if($conf['locktime'] == 0) {
773        return;
774    }
775
776    $lock = wikiLockFN($id);
777    if($_SERVER['REMOTE_USER']) {
778        io_saveFile($lock, $_SERVER['REMOTE_USER']);
779    } else {
780        io_saveFile($lock, clientIP()."\n".session_id());
781    }
782}
783
784/**
785 * Unlock a page if it was locked by the user
786 *
787 * @author Andreas Gohr <andi@splitbrain.org>
788 * @param string $id page id to unlock
789 * @return bool true if a lock was removed
790 */
791function unlock($id) {
792    $lock = wikiLockFN($id);
793    if(@file_exists($lock)) {
794        list($ip, $session) = explode("\n", io_readFile($lock));
795        if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) {
796            @unlink($lock);
797            return true;
798        }
799    }
800    return false;
801}
802
803/**
804 * convert line ending to unix format
805 *
806 * @see    formText() for 2crlf conversion
807 * @author Andreas Gohr <andi@splitbrain.org>
808 */
809function cleanText($text) {
810    $text = preg_replace("/(\015\012)|(\015)/", "\012", $text);
811    return $text;
812}
813
814/**
815 * Prepares text for print in Webforms by encoding special chars.
816 * It also converts line endings to Windows format which is
817 * pseudo standard for webforms.
818 *
819 * @see    cleanText() for 2unix conversion
820 * @author Andreas Gohr <andi@splitbrain.org>
821 */
822function formText($text) {
823    $text = str_replace("\012", "\015\012", $text);
824    return htmlspecialchars($text);
825}
826
827/**
828 * Returns the specified local text in raw format
829 *
830 * @author Andreas Gohr <andi@splitbrain.org>
831 */
832function rawLocale($id, $ext = 'txt') {
833    return io_readFile(localeFN($id, $ext));
834}
835
836/**
837 * Returns the raw WikiText
838 *
839 * @author Andreas Gohr <andi@splitbrain.org>
840 */
841function rawWiki($id, $rev = '') {
842    return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
843}
844
845/**
846 * Returns the pagetemplate contents for the ID's namespace
847 *
848 * @triggers COMMON_PAGETPL_LOAD
849 * @author Andreas Gohr <andi@splitbrain.org>
850 */
851function pageTemplate($id) {
852    global $conf;
853
854    if(is_array($id)) $id = $id[0];
855
856    // prepare initial event data
857    $data = array(
858        'id'        => $id, // the id of the page to be created
859        'tpl'       => '', // the text used as template
860        'tplfile'   => '', // the file above text was/should be loaded from
861        'doreplace' => true // should wildcard replacements be done on the text?
862    );
863
864    $evt = new Doku_Event('COMMON_PAGETPL_LOAD', $data);
865    if($evt->advise_before(true)) {
866        // the before event might have loaded the content already
867        if(empty($data['tpl'])) {
868            // if the before event did not set a template file, try to find one
869            if(empty($data['tplfile'])) {
870                $path = dirname(wikiFN($id));
871                if(@file_exists($path.'/_template.txt')) {
872                    $data['tplfile'] = $path.'/_template.txt';
873                } else {
874                    // search upper namespaces for templates
875                    $len = strlen(rtrim($conf['datadir'], '/'));
876                    while(strlen($path) >= $len) {
877                        if(@file_exists($path.'/__template.txt')) {
878                            $data['tplfile'] = $path.'/__template.txt';
879                            break;
880                        }
881                        $path = substr($path, 0, strrpos($path, '/'));
882                    }
883                }
884            }
885            // load the content
886            $data['tpl'] = io_readFile($data['tplfile']);
887        }
888        if($data['doreplace']) parsePageTemplate($data);
889    }
890    $evt->advise_after();
891    unset($evt);
892
893    return $data['tpl'];
894}
895
896/**
897 * Performs common page template replacements
898 * This works on data from COMMON_PAGETPL_LOAD
899 *
900 * @author Andreas Gohr <andi@splitbrain.org>
901 */
902function parsePageTemplate(&$data) {
903    /**
904     * @var string $id        the id of the page to be created
905     * @var string $tpl       the text used as template
906     * @var string $tplfile   the file above text was/should be loaded from
907     * @var bool   $doreplace should wildcard replacements be done on the text?
908     */
909    extract($data);
910
911    global $USERINFO;
912    global $conf;
913
914    // replace placeholders
915    $file = noNS($id);
916    $page = strtr($file, $conf['sepchar'], ' ');
917
918    $tpl = str_replace(
919        array(
920             '@ID@',
921             '@NS@',
922             '@FILE@',
923             '@!FILE@',
924             '@!FILE!@',
925             '@PAGE@',
926             '@!PAGE@',
927             '@!!PAGE@',
928             '@!PAGE!@',
929             '@USER@',
930             '@NAME@',
931             '@MAIL@',
932             '@DATE@',
933        ),
934        array(
935             $id,
936             getNS($id),
937             $file,
938             utf8_ucfirst($file),
939             utf8_strtoupper($file),
940             $page,
941             utf8_ucfirst($page),
942             utf8_ucwords($page),
943             utf8_strtoupper($page),
944             $_SERVER['REMOTE_USER'],
945             $USERINFO['name'],
946             $USERINFO['mail'],
947             $conf['dformat'],
948        ), $tpl
949    );
950
951    // we need the callback to work around strftime's char limit
952    $tpl         = preg_replace_callback('/%./', create_function('$m', 'return strftime($m[0]);'), $tpl);
953    $data['tpl'] = $tpl;
954    return $tpl;
955}
956
957/**
958 * Returns the raw Wiki Text in three slices.
959 *
960 * The range parameter needs to have the form "from-to"
961 * and gives the range of the section in bytes - no
962 * UTF-8 awareness is needed.
963 * The returned order is prefix, section and suffix.
964 *
965 * @author Andreas Gohr <andi@splitbrain.org>
966 */
967function rawWikiSlices($range, $id, $rev = '') {
968    $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
969
970    // Parse range
971    list($from, $to) = explode('-', $range, 2);
972    // Make range zero-based, use defaults if marker is missing
973    $from = !$from ? 0 : ($from - 1);
974    $to   = !$to ? strlen($text) : ($to - 1);
975
976    $slices[0] = substr($text, 0, $from);
977    $slices[1] = substr($text, $from, $to - $from);
978    $slices[2] = substr($text, $to);
979    return $slices;
980}
981
982/**
983 * Joins wiki text slices
984 *
985 * function to join the text slices.
986 * When the pretty parameter is set to true it adds additional empty
987 * lines between sections if needed (used on saving).
988 *
989 * @author Andreas Gohr <andi@splitbrain.org>
990 */
991function con($pre, $text, $suf, $pretty = false) {
992    if($pretty) {
993        if($pre !== '' && substr($pre, -1) !== "\n" &&
994            substr($text, 0, 1) !== "\n"
995        ) {
996            $pre .= "\n";
997        }
998        if($suf !== '' && substr($text, -1) !== "\n" &&
999            substr($suf, 0, 1) !== "\n"
1000        ) {
1001            $text .= "\n";
1002        }
1003    }
1004
1005    return $pre.$text.$suf;
1006}
1007
1008/**
1009 * Saves a wikitext by calling io_writeWikiPage.
1010 * Also directs changelog and attic updates.
1011 *
1012 * @author Andreas Gohr <andi@splitbrain.org>
1013 * @author Ben Coburn <btcoburn@silicodon.net>
1014 */
1015function saveWikiText($id, $text, $summary, $minor = false) {
1016    /* Note to developers:
1017       This code is subtle and delicate. Test the behavior of
1018       the attic and changelog with dokuwiki and external edits
1019       after any changes. External edits change the wiki page
1020       directly without using php or dokuwiki.
1021     */
1022    global $conf;
1023    global $lang;
1024    global $REV;
1025    // ignore if no changes were made
1026    if($text == rawWiki($id, '')) {
1027        return;
1028    }
1029
1030    $file        = wikiFN($id);
1031    $old         = @filemtime($file); // from page
1032    $wasRemoved  = (trim($text) == ''); // check for empty or whitespace only
1033    $wasCreated  = !@file_exists($file);
1034    $wasReverted = ($REV == true);
1035    $newRev      = false;
1036    $oldRev      = getRevisions($id, -1, 1, 1024); // from changelog
1037    $oldRev      = (int) (empty($oldRev) ? 0 : $oldRev[0]);
1038    if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old >= $oldRev) {
1039        // add old revision to the attic if missing
1040        saveOldRevision($id);
1041        // add a changelog entry if this edit came from outside dokuwiki
1042        if($old > $oldRev) {
1043            addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=> true));
1044            // remove soon to be stale instructions
1045            $cache = new cache_instructions($id, $file);
1046            $cache->removeCache();
1047        }
1048    }
1049
1050    if($wasRemoved) {
1051        // Send "update" event with empty data, so plugins can react to page deletion
1052        $data = array(array($file, '', false), getNS($id), noNS($id), false);
1053        trigger_event('IO_WIKIPAGE_WRITE', $data);
1054        // pre-save deleted revision
1055        @touch($file);
1056        clearstatcache();
1057        $newRev = saveOldRevision($id);
1058        // remove empty file
1059        @unlink($file);
1060        // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata...
1061        // purge non-persistant meta data
1062        p_purge_metadata($id);
1063        $del = true;
1064        // autoset summary on deletion
1065        if(empty($summary)) $summary = $lang['deleted'];
1066        // remove empty namespaces
1067        io_sweepNS($id, 'datadir');
1068        io_sweepNS($id, 'mediadir');
1069    } else {
1070        // save file (namespace dir is created in io_writeWikiPage)
1071        io_writeWikiPage($file, $text, $id);
1072        // pre-save the revision, to keep the attic in sync
1073        $newRev = saveOldRevision($id);
1074        $del    = false;
1075    }
1076
1077    // select changelog line type
1078    $extra = '';
1079    $type  = DOKU_CHANGE_TYPE_EDIT;
1080    if($wasReverted) {
1081        $type  = DOKU_CHANGE_TYPE_REVERT;
1082        $extra = $REV;
1083    } else if($wasCreated) {
1084        $type = DOKU_CHANGE_TYPE_CREATE;
1085    } else if($wasRemoved) {
1086        $type = DOKU_CHANGE_TYPE_DELETE;
1087    } else if($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) {
1088        $type = DOKU_CHANGE_TYPE_MINOR_EDIT;
1089    } //minor edits only for logged in users
1090
1091    addLogEntry($newRev, $id, $type, $summary, $extra);
1092    // send notify mails
1093    notify($id, 'admin', $old, $summary, $minor);
1094    notify($id, 'subscribers', $old, $summary, $minor);
1095
1096    // update the purgefile (timestamp of the last time anything within the wiki was changed)
1097    io_saveFile($conf['cachedir'].'/purgefile', time());
1098
1099    // if useheading is enabled, purge the cache of all linking pages
1100    if(useHeading('content')) {
1101        $pages = ft_backlinks($id);
1102        foreach($pages as $page) {
1103            $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
1104            $cache->removeCache();
1105        }
1106    }
1107}
1108
1109/**
1110 * moves the current version to the attic and returns its
1111 * revision date
1112 *
1113 * @author Andreas Gohr <andi@splitbrain.org>
1114 */
1115function saveOldRevision($id) {
1116    global $conf;
1117    $oldf = wikiFN($id);
1118    if(!@file_exists($oldf)) return '';
1119    $date = filemtime($oldf);
1120    $newf = wikiFN($id, $date);
1121    io_writeWikiPage($newf, rawWiki($id), $id, $date);
1122    return $date;
1123}
1124
1125/**
1126 * Sends a notify mail on page change or registration
1127 *
1128 * @param string     $id       The changed page
1129 * @param string     $who      Who to notify (admin|subscribers|register)
1130 * @param int|string $rev Old page revision
1131 * @param string     $summary  What changed
1132 * @param boolean    $minor    Is this a minor edit?
1133 * @param array      $replace  Additional string substitutions, @KEY@ to be replaced by value
1134 *
1135 * @return bool
1136 * @author Andreas Gohr <andi@splitbrain.org>
1137 */
1138function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array()) {
1139    global $conf;
1140
1141    // decide if there is something to do, eg. whom to mail
1142    if($who == 'admin') {
1143        if(empty($conf['notify'])) return false; //notify enabled?
1144        $tpl = 'mailtext';
1145        $to  = $conf['notify'];
1146    } elseif($who == 'subscribers') {
1147        if(!actionOK('subscribe')) return false; //subscribers enabled?
1148        if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return false; //skip minors
1149        $data = array('id' => $id, 'addresslist' => '', 'self' => false);
1150        trigger_event(
1151            'COMMON_NOTIFY_ADDRESSLIST', $data,
1152            array(new Subscription(), 'notifyaddresses')
1153        );
1154        $to = $data['addresslist'];
1155        if(empty($to)) return false;
1156        $tpl = 'subscr_single';
1157    } else {
1158        return false; //just to be safe
1159    }
1160
1161    // prepare content
1162    $subscription = new Subscription();
1163    return $subscription->send_diff($to, $tpl, $id, $rev, $summary);
1164}
1165
1166/**
1167 * extracts the query from a search engine referrer
1168 *
1169 * @author Andreas Gohr <andi@splitbrain.org>
1170 * @author Todd Augsburger <todd@rollerorgans.com>
1171 */
1172function getGoogleQuery() {
1173    if(!isset($_SERVER['HTTP_REFERER'])) {
1174        return '';
1175    }
1176    $url = parse_url($_SERVER['HTTP_REFERER']);
1177
1178    // only handle common SEs
1179    if(!preg_match('/(google|bing|yahoo|ask|duckduckgo|babylon|aol|yandex)/',$url['host'])) return '';
1180
1181    $query = array();
1182    // temporary workaround against PHP bug #49733
1183    // see http://bugs.php.net/bug.php?id=49733
1184    if(UTF8_MBSTRING) $enc = mb_internal_encoding();
1185    parse_str($url['query'], $query);
1186    if(UTF8_MBSTRING) mb_internal_encoding($enc);
1187
1188    $q = '';
1189    if(isset($query['q'])){
1190        $q = $query['q'];
1191    }elseif(isset($query['p'])){
1192        $q = $query['p'];
1193    }elseif(isset($query['query'])){
1194        $q = $query['query'];
1195    }
1196    $q = trim($q);
1197
1198    if(!$q) return '';
1199    $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/', $q, -1, PREG_SPLIT_NO_EMPTY);
1200    return $q;
1201}
1202
1203/**
1204 * Try to set correct locale
1205 *
1206 * @deprecated No longer used
1207 * @author     Andreas Gohr <andi@splitbrain.org>
1208 */
1209function setCorrectLocale() {
1210    global $conf;
1211    global $lang;
1212
1213    $enc = strtoupper($lang['encoding']);
1214    foreach($lang['locales'] as $loc) {
1215        //try locale
1216        if(@setlocale(LC_ALL, $loc)) return;
1217        //try loceale with encoding
1218        if(@setlocale(LC_ALL, "$loc.$enc")) return;
1219    }
1220    //still here? try to set from environment
1221    @setlocale(LC_ALL, "");
1222}
1223
1224/**
1225 * Return the human readable size of a file
1226 *
1227 * @param       int    $size   A file size
1228 * @param       int    $dec    A number of decimal places
1229 * @author      Martin Benjamin <b.martin@cybernet.ch>
1230 * @author      Aidan Lister <aidan@php.net>
1231 * @version     1.0.0
1232 */
1233function filesize_h($size, $dec = 1) {
1234    $sizes = array('B', 'KB', 'MB', 'GB');
1235    $count = count($sizes);
1236    $i     = 0;
1237
1238    while($size >= 1024 && ($i < $count - 1)) {
1239        $size /= 1024;
1240        $i++;
1241    }
1242
1243    return round($size, $dec).' '.$sizes[$i];
1244}
1245
1246/**
1247 * Return the given timestamp as human readable, fuzzy age
1248 *
1249 * @author Andreas Gohr <gohr@cosmocode.de>
1250 */
1251function datetime_h($dt) {
1252    global $lang;
1253
1254    $ago = time() - $dt;
1255    if($ago > 24 * 60 * 60 * 30 * 12 * 2) {
1256        return sprintf($lang['years'], round($ago / (24 * 60 * 60 * 30 * 12)));
1257    }
1258    if($ago > 24 * 60 * 60 * 30 * 2) {
1259        return sprintf($lang['months'], round($ago / (24 * 60 * 60 * 30)));
1260    }
1261    if($ago > 24 * 60 * 60 * 7 * 2) {
1262        return sprintf($lang['weeks'], round($ago / (24 * 60 * 60 * 7)));
1263    }
1264    if($ago > 24 * 60 * 60 * 2) {
1265        return sprintf($lang['days'], round($ago / (24 * 60 * 60)));
1266    }
1267    if($ago > 60 * 60 * 2) {
1268        return sprintf($lang['hours'], round($ago / (60 * 60)));
1269    }
1270    if($ago > 60 * 2) {
1271        return sprintf($lang['minutes'], round($ago / (60)));
1272    }
1273    return sprintf($lang['seconds'], $ago);
1274}
1275
1276/**
1277 * Wraps around strftime but provides support for fuzzy dates
1278 *
1279 * The format default to $conf['dformat']. It is passed to
1280 * strftime - %f can be used to get the value from datetime_h()
1281 *
1282 * @see datetime_h
1283 * @author Andreas Gohr <gohr@cosmocode.de>
1284 */
1285function dformat($dt = null, $format = '') {
1286    global $conf;
1287
1288    if(is_null($dt)) $dt = time();
1289    $dt = (int) $dt;
1290    if(!$format) $format = $conf['dformat'];
1291
1292    $format = str_replace('%f', datetime_h($dt), $format);
1293    return strftime($format, $dt);
1294}
1295
1296/**
1297 * Formats a timestamp as ISO 8601 date
1298 *
1299 * @author <ungu at terong dot com>
1300 * @link http://www.php.net/manual/en/function.date.php#54072
1301 * @param int $int_date: current date in UNIX timestamp
1302 * @return string
1303 */
1304function date_iso8601($int_date) {
1305    $date_mod     = date('Y-m-d\TH:i:s', $int_date);
1306    $pre_timezone = date('O', $int_date);
1307    $time_zone    = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2);
1308    $date_mod .= $time_zone;
1309    return $date_mod;
1310}
1311
1312/**
1313 * return an obfuscated email address in line with $conf['mailguard'] setting
1314 *
1315 * @author Harry Fuecks <hfuecks@gmail.com>
1316 * @author Christopher Smith <chris@jalakai.co.uk>
1317 */
1318function obfuscate($email) {
1319    global $conf;
1320
1321    switch($conf['mailguard']) {
1322        case 'visible' :
1323            $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
1324            return strtr($email, $obfuscate);
1325
1326        case 'hex' :
1327            $encode = '';
1328            $len    = strlen($email);
1329            for($x = 0; $x < $len; $x++) {
1330                $encode .= '&#x'.bin2hex($email{$x}).';';
1331            }
1332            return $encode;
1333
1334        case 'none' :
1335        default :
1336            return $email;
1337    }
1338}
1339
1340/**
1341 * Removes quoting backslashes
1342 *
1343 * @author Andreas Gohr <andi@splitbrain.org>
1344 */
1345function unslash($string, $char = "'") {
1346    return str_replace('\\'.$char, $char, $string);
1347}
1348
1349/**
1350 * Convert php.ini shorthands to byte
1351 *
1352 * @author <gilthans dot NO dot SPAM at gmail dot com>
1353 * @link   http://de3.php.net/manual/en/ini.core.php#79564
1354 */
1355function php_to_byte($v) {
1356    $l   = substr($v, -1);
1357    $ret = substr($v, 0, -1);
1358    switch(strtoupper($l)) {
1359        case 'P':
1360            $ret *= 1024;
1361        case 'T':
1362            $ret *= 1024;
1363        case 'G':
1364            $ret *= 1024;
1365        case 'M':
1366            $ret *= 1024;
1367        case 'K':
1368            $ret *= 1024;
1369            break;
1370        default;
1371            $ret *= 10;
1372            break;
1373    }
1374    return $ret;
1375}
1376
1377/**
1378 * Wrapper around preg_quote adding the default delimiter
1379 */
1380function preg_quote_cb($string) {
1381    return preg_quote($string, '/');
1382}
1383
1384/**
1385 * Shorten a given string by removing data from the middle
1386 *
1387 * You can give the string in two parts, the first part $keep
1388 * will never be shortened. The second part $short will be cut
1389 * in the middle to shorten but only if at least $min chars are
1390 * left to display it. Otherwise it will be left off.
1391 *
1392 * @param string $keep   the part to keep
1393 * @param string $short  the part to shorten
1394 * @param int    $max    maximum chars you want for the whole string
1395 * @param int    $min    minimum number of chars to have left for middle shortening
1396 * @param string $char   the shortening character to use
1397 * @return string
1398 */
1399function shorten($keep, $short, $max, $min = 9, $char = '…') {
1400    $max = $max - utf8_strlen($keep);
1401    if($max < $min) return $keep;
1402    $len = utf8_strlen($short);
1403    if($len <= $max) return $keep.$short;
1404    $half = floor($max / 2);
1405    return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half);
1406}
1407
1408/**
1409 * Return the users realname or e-mail address for use
1410 * in page footer and recent changes pages
1411 *
1412 * @author Andy Webber <dokuwiki AT andywebber DOT com>
1413 */
1414function editorinfo($username) {
1415    global $conf;
1416    global $auth;
1417
1418    switch($conf['showuseras']) {
1419        case 'username':
1420        case 'email':
1421        case 'email_link':
1422            if($auth) $info = $auth->getUserData($username);
1423            break;
1424        default:
1425            return hsc($username);
1426    }
1427
1428    if(isset($info) && $info) {
1429        switch($conf['showuseras']) {
1430            case 'username':
1431                return hsc($info['name']);
1432            case 'email':
1433                return obfuscate($info['mail']);
1434            case 'email_link':
1435                $mail = obfuscate($info['mail']);
1436                return '<a href="mailto:'.$mail.'">'.$mail.'</a>';
1437            default:
1438                return hsc($username);
1439        }
1440    } else {
1441        return hsc($username);
1442    }
1443}
1444
1445/**
1446 * Returns the path to a image file for the currently chosen license.
1447 * When no image exists, returns an empty string
1448 *
1449 * @author Andreas Gohr <andi@splitbrain.org>
1450 * @param  string $type - type of image 'badge' or 'button'
1451 * @return string
1452 */
1453function license_img($type) {
1454    global $license;
1455    global $conf;
1456    if(!$conf['license']) return '';
1457    if(!is_array($license[$conf['license']])) return '';
1458    $lic   = $license[$conf['license']];
1459    $try   = array();
1460    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
1461    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
1462    if(substr($conf['license'], 0, 3) == 'cc-') {
1463        $try[] = 'lib/images/license/'.$type.'/cc.png';
1464    }
1465    foreach($try as $src) {
1466        if(@file_exists(DOKU_INC.$src)) return $src;
1467    }
1468    return '';
1469}
1470
1471/**
1472 * Checks if the given amount of memory is available
1473 *
1474 * If the memory_get_usage() function is not available the
1475 * function just assumes $bytes of already allocated memory
1476 *
1477 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
1478 * @author Andreas Gohr <andi@splitbrain.org>
1479 *
1480 * @param  int $mem  Size of memory you want to allocate in bytes
1481 * @param int  $bytes
1482 * @internal param int $used already allocated memory (see above)
1483 * @return bool
1484 */
1485function is_mem_available($mem, $bytes = 1048576) {
1486    $limit = trim(ini_get('memory_limit'));
1487    if(empty($limit)) return true; // no limit set!
1488
1489    // parse limit to bytes
1490    $limit = php_to_byte($limit);
1491
1492    // get used memory if possible
1493    if(function_exists('memory_get_usage')) {
1494        $used = memory_get_usage();
1495    } else {
1496        $used = $bytes;
1497    }
1498
1499    if($used + $mem > $limit) {
1500        return false;
1501    }
1502
1503    return true;
1504}
1505
1506/**
1507 * Send a HTTP redirect to the browser
1508 *
1509 * Works arround Microsoft IIS cookie sending bug. Exits the script.
1510 *
1511 * @link   http://support.microsoft.com/kb/q176113/
1512 * @author Andreas Gohr <andi@splitbrain.org>
1513 */
1514function send_redirect($url) {
1515    //are there any undisplayed messages? keep them in session for display
1516    global $MSG;
1517    if(isset($MSG) && count($MSG) && !defined('NOSESSION')) {
1518        //reopen session, store data and close session again
1519        @session_start();
1520        $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
1521    }
1522
1523    // always close the session
1524    session_write_close();
1525
1526    // work around IE bug
1527    // http://www.ianhoar.com/2008/11/16/internet-explorer-6-and-redirected-anchor-links/
1528    list($url, $hash) = explode('#', $url);
1529    if($hash) {
1530        if(strpos($url, '?')) {
1531            $url = $url.'&#'.$hash;
1532        } else {
1533            $url = $url.'?&#'.$hash;
1534        }
1535    }
1536
1537    // check if running on IIS < 6 with CGI-PHP
1538    if(isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) &&
1539        (strpos($_SERVER['GATEWAY_INTERFACE'], 'CGI') !== false) &&
1540        (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) &&
1541        $matches[1] < 6
1542    ) {
1543        header('Refresh: 0;url='.$url);
1544    } else {
1545        header('Location: '.$url);
1546    }
1547    exit;
1548}
1549
1550/**
1551 * Validate a value using a set of valid values
1552 *
1553 * This function checks whether a specified value is set and in the array
1554 * $valid_values. If not, the function returns a default value or, if no
1555 * default is specified, throws an exception.
1556 *
1557 * @param string $param        The name of the parameter
1558 * @param array  $valid_values A set of valid values; Optionally a default may
1559 *                             be marked by the key “default”.
1560 * @param array  $array        The array containing the value (typically $_POST
1561 *                             or $_GET)
1562 * @param string $exc          The text of the raised exception
1563 *
1564 * @throws Exception
1565 * @return mixed
1566 * @author Adrian Lang <lang@cosmocode.de>
1567 */
1568function valid_input_set($param, $valid_values, $array, $exc = '') {
1569    if(isset($array[$param]) && in_array($array[$param], $valid_values)) {
1570        return $array[$param];
1571    } elseif(isset($valid_values['default'])) {
1572        return $valid_values['default'];
1573    } else {
1574        throw new Exception($exc);
1575    }
1576}
1577
1578/**
1579 * Read a preference from the DokuWiki cookie
1580 * (remembering both keys & values are urlencoded)
1581 */
1582function get_doku_pref($pref, $default) {
1583    $enc_pref = urlencode($pref);
1584    if(strpos($_COOKIE['DOKU_PREFS'], $enc_pref) !== false) {
1585        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1586        $cnt   = count($parts);
1587        for($i = 0; $i < $cnt; $i += 2) {
1588            if($parts[$i] == $enc_pref) {
1589                return urldecode($parts[$i + 1]);
1590            }
1591        }
1592    }
1593    return $default;
1594}
1595
1596/**
1597 * Add a preference to the DokuWiki cookie
1598 * (remembering $_COOKIE['DOKU_PREFS'] is urlencoded)
1599 */
1600function set_doku_pref($pref, $val) {
1601    global $conf;
1602    $orig = get_doku_pref($pref, false);
1603    $cookieVal = '';
1604
1605    if($orig && ($orig != $val)) {
1606        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
1607        $cnt   = count($parts);
1608        // urlencode $pref for the comparison
1609        $enc_pref = rawurlencode($pref);
1610        for($i = 0; $i < $cnt; $i += 2) {
1611            if($parts[$i] == $enc_pref) {
1612                $parts[$i + 1] = rawurlencode($val);
1613                break;
1614            }
1615        }
1616        $cookieVal = implode('#', $parts);
1617    } else if (!$orig) {
1618        $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val);
1619    }
1620
1621    if (!empty($cookieVal)) {
1622        setcookie('DOKU_PREFS', $cookieVal, time()+365*24*3600, DOKU_BASE, '', ($conf['securecookie'] && is_ssl()));
1623    }
1624}
1625
1626//Setup VIM: ex: et ts=2 :
1627