[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> common.php (source)

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


Generated: Mon Mar 22 03:00:34 2010 Cross-referenced by PHPXref 0.7
WikiForumIRCBugsGitXRefTranslate