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