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