Skip to content
Snippets Groups Projects
common.php 30.9 KiB
Newer Older
andi's avatar
andi committed
/**
 * Common DokuWiki functions
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Andreas Gohr <andi@splitbrain.org>
 */
andi's avatar
andi committed

if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../').'/');
Andreas Gohr's avatar
Andreas Gohr committed
require_once(DOKU_CONF.'dokuwiki.php');
require_once(DOKU_INC.'inc/io.php');
require_once(DOKU_INC.'inc/changelog.php');
require_once(DOKU_INC.'inc/utf8.php');
require_once(DOKU_INC.'inc/mail.php');
require_once(DOKU_INC.'inc/parserutils.php');
require_once(DOKU_INC.'inc/infoutils.php');
andi's avatar
andi committed

Andreas Gohr's avatar
Andreas Gohr committed
/**
 * These constants are used with the recents function
 */
define('RECENTS_SKIP_DELETED',2);
define('RECENTS_SKIP_MINORS',4);
define('RECENTS_SKIP_SUBSPACES',8);

/**
 * Wrapper around htmlspecialchars()
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @see    htmlspecialchars()
 */
function hsc($string){
  return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

/**
 * print a newline terminated string
 *
 * You can give an indention as optional parameter
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function ptln($string,$indent=0){
  echo str_repeat(' ', $indent)."$string\n";
}

/**
 * strips control characters (<32) from the given string
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function stripctl($string){
  return preg_replace('/[\x00-\x1F]+/s','',$string);
/**
 * Return a secret token to be used for CSRF attack prevention
 *
 * @author  Andreas Gohr <andi@splitbrain.org>
 * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
 * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
 * @return  string
 */
function getSecurityToken(){
  return md5(auth_cookiesalt().session_id());
}

/**
 * Check the secret CSRF token
 */
function checkSecurityToken($token=null){
  if(is_null($token)) $token = $_REQUEST['sectok'];
  if(getSecurityToken() != $token){
    msg('Security Token did not match. Possible CSRF attack.',-1);
    return false;
  }
  return true;
}

/**
 * Print a hidden form field with a secret CSRF token
 *
 * @author  Andreas Gohr <andi@splitbrain.org>
 */
function formSecurityToken($print=true){
  $ret = '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'."\n";
  if($print){
    echo $ret;
  }else{
    return $ret;
  }
}

andi's avatar
andi committed
/**
andi's avatar
andi committed
 * Return info about the current document as associative
andi's avatar
andi committed
 * array.
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function pageinfo(){
  global $ID;
  global $REV;
  global $USERINFO;
  global $conf;

  // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
  // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
  $info['id'] = $ID;
  $info['rev'] = $REV;

andi's avatar
andi committed
  if($_SERVER['REMOTE_USER']){
    $info['userinfo']     = $USERINFO;
    $info['perm']         = auth_quickaclcheck($ID);
    $info['subscribed']   = is_subscribed($ID,$_SERVER['REMOTE_USER'],false);
    $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true);
    $info['client']       = $_SERVER['REMOTE_USER'];
Andreas Gohr's avatar
Andreas Gohr committed
    // set info about manager/admin status
    $info['isadmin']   = false;
    $info['ismanager'] = false;
    if($info['perm'] == AUTH_ADMIN){
      $info['isadmin']   = true;
      $info['ismanager'] = true;
    }elseif(auth_ismanager()){
      $info['ismanager'] = true;
    }

    // if some outside auth were used only REMOTE_USER is set
    if(!$info['userinfo']['name']){
      $info['userinfo']['name'] = $_SERVER['REMOTE_USER'];
    }
andi's avatar
andi committed
  }else{
    $info['perm']       = auth_aclcheck($ID,'',null);
    $info['subscribed'] = false;
Andreas Gohr's avatar
Andreas Gohr committed
    $info['client']     = clientIP(true);
andi's avatar
andi committed
  }

  $info['namespace'] = getNS($ID);
  $info['locked']    = checklock($ID);
  $info['filepath']  = fullpath(wikiFN($ID));
andi's avatar
andi committed
  $info['exists']    = @file_exists($info['filepath']);
andi's avatar
andi committed
    //check if current revision was meant
    if($info['exists'] && (@filemtime($info['filepath'])==$REV)){
andi's avatar
andi committed
      $REV = '';
    }else{
      //really use old revision
      $info['filepath'] = fullpath(wikiFN($ID,$REV));
      $info['exists']   = @file_exists($info['filepath']);
andi's avatar
andi committed
    }
  }
  $info['rev'] = $REV;
andi's avatar
andi committed
  if($info['exists']){
    $info['writable'] = (is_writable($info['filepath']) &&
                         ($info['perm'] >= AUTH_EDIT));
  }else{
    $info['writable'] = ($info['perm'] >= AUTH_CREATE);
  }
  $info['editable']  = ($info['writable'] && empty($info['lock']));
  $info['lastmod']   = @filemtime($info['filepath']);

Ben Coburn's avatar
Ben Coburn committed
  //load page meta data
  $info['meta'] = p_get_metadata($ID);

andi's avatar
andi committed
  //who's the editor
  if($REV){
Ben Coburn's avatar
Ben Coburn committed
    $revinfo = getRevisionInfo($ID, $REV, 1024);
andi's avatar
andi committed
  }else{
    if (is_array($info['meta']['last_change'])) {
       $revinfo = $info['meta']['last_change'];
    } else {
      $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024);
      // cache most recent changelog line in metadata if missing and still valid
      if ($revinfo!==false) {
        $info['meta']['last_change'] = $revinfo;
        p_set_metadata($ID, array('last_change' => $revinfo));
      }
    }
  }
  //and check for an external edit
  if($revinfo!==false && $revinfo['date']!=$info['lastmod']){
    // cached changelog line no longer valid
    $revinfo = false;
    $info['meta']['last_change'] = $revinfo;
    p_set_metadata($ID, array('last_change' => $revinfo));
andi's avatar
andi committed
  $info['ip']     = $revinfo['ip'];
  $info['user']   = $revinfo['user'];
  $info['sum']    = $revinfo['sum'];
Ben Coburn's avatar
Ben Coburn committed
  // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
  // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
  if($revinfo['user']){
    $info['editor'] = $revinfo['user'];
  }else{
    $info['editor'] = $revinfo['ip'];
  }
Andreas Gohr's avatar
Andreas Gohr committed
  // draft
  $draft = getCacheName($info['client'].$ID,'.draft');
  if(@file_exists($draft)){
    if(@filemtime($draft) < @filemtime(wikiFN($ID))){
      // remove stale draft
      @unlink($draft);
    }else{
      $info['draft'] = $draft;
    }
  }

andi's avatar
andi committed
  return $info;
}

/**
 * Build an string of URL parameters
 *
 * @author Andreas Gohr
 */
function buildURLparams($params, $sep='&amp;'){
  $url = '';
  $amp = false;
  foreach($params as $key => $val){
    $url .= rawurlencode($val);
    $amp = true;
  }
  return $url;
}

/**
 * Build an string of html tag attributes
 *
 * Skips keys starting with '_', values get HTML encoded
 *
 * @author Andreas Gohr
 */
function buildAttributes($params,$skipempty=false){
  $url = '';
  foreach($params as $key => $val){
    if($key{0} == '_') continue;
    if($val === '' && $skipempty) continue;
    $url .= $key.'="';
    $url .= htmlspecialchars ($val);
    $url .= '" ';
  }
  return $url;
}


andi's avatar
andi committed
/**
andi's avatar
andi committed
 * This builds the breadcrumb trail and returns it as array
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function breadcrumbs(){
andi's avatar
andi committed
  // we prepare the breadcrumbs early for quick session closing
  static $crumbs = null;
  if($crumbs != null) return $crumbs;

andi's avatar
andi committed
  global $ID;
  global $ACT;
  global $conf;
  $crumbs = $_SESSION[DOKU_COOKIE]['bc'];
andi's avatar
andi committed
  //first visit?
  if (!is_array($crumbs)){
    $crumbs = array();
  }
  //we only save on show and existing wiki documents
  $file = wikiFN($ID);
  if($ACT != 'show' || !@file_exists($file)){
    $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
andi's avatar
andi committed
    return $crumbs;
  }

  // page names
  if ($conf['useheading']) {
    // get page title
    $title = p_get_first_heading($ID,true);
    if ($title) {
      $name = $title;
    }
  }

andi's avatar
andi committed
  //remove ID from array
  if (isset($crumbs[$ID])) {
    unset($crumbs[$ID]);
andi's avatar
andi committed
  }

  //add to array
  $crumbs[$ID] = $name;
andi's avatar
andi committed
  //reduce size
  while(count($crumbs) > $conf['breadcrumbs']){
    array_shift($crumbs);
  }
  //save to session
  $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
andi's avatar
andi committed
  return $crumbs;
}

/**
andi's avatar
andi committed
 * Filter for page IDs
 *
andi's avatar
andi committed
 * This is run on a ID before it is outputted somewhere
 * currently used to replace the colon with something else
 * on Windows systems and to have proper URL encoding
andi's avatar
andi committed
 *
 * Urlencoding is ommitted when the second parameter is false
 *
andi's avatar
andi committed
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function idfilter($id,$ue=true){
andi's avatar
andi committed
  global $conf;
  if ($conf['useslash'] && $conf['userewrite']){
    $id = strtr($id,':','/');
  }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
      $conf['userewrite']) {
    $id = strtr($id,':',';');
  }
  if($ue){
    $id = rawurlencode($id);
    $id = str_replace('%3A',':',$id); //keep as colon
    $id = str_replace('%2F','/',$id); //keep as slash
  }
andi's avatar
andi committed
  return $id;
}

/**
 * This builds a link to a wikipage
andi's avatar
andi committed
 *
andi's avatar
andi committed
 * It handles URL rewriting and adds additional parameter if
 * given in $more
 *
andi's avatar
andi committed
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function wl($id='',$more='',$abs=false,$sep='&amp;'){
andi's avatar
andi committed
  global $conf;
  if(is_array($more)){
    $more = buildURLparams($more,$sep);
  }else{
    $more = str_replace(',',$sep,$more);
andi's avatar
andi committed

  $id    = idfilter($id);
  if($abs){
    $xlink = DOKU_URL;
  }else{
    $xlink = DOKU_BASE;
  }
andi's avatar
andi committed

andi's avatar
andi committed
  if($conf['userewrite'] == 2){
    $xlink .= DOKU_SCRIPT.'/'.$id;
    if($more) $xlink .= '?'.$more;
  }elseif($conf['userewrite']){
andi's avatar
andi committed
    $xlink .= $id;
    if($more) $xlink .= '?'.$more;
andi's avatar
andi committed
    $xlink .= DOKU_SCRIPT.'?id='.$id;
  }else{
    $xlink .= DOKU_SCRIPT;
    if($more) $xlink .= '?'.$more;
andi's avatar
andi committed
  }
andi's avatar
andi committed
  return $xlink;
}

Ben Coburn's avatar
Ben Coburn committed
/**
 * This builds a link to an alternate page format
 *
 * Handles URL rewriting if enabled. Follows the style of wl().
 *
 * @author Ben Coburn <btcoburn@silicodon.net>
 */
function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&amp;'){
  global $conf;
  if(is_array($more)){
    $more = buildURLparams($more,$sep);
  }else{
    $more = str_replace(',',$sep,$more);
  }

  $format = rawurlencode($format);
  $id = idfilter($id);
  if($abs){
    $xlink = DOKU_URL;
  }else{
    $xlink = DOKU_BASE;
  }

  if($conf['userewrite'] == 2){
    $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
    if($more) $xlink .= $sep.$more;
  }elseif($conf['userewrite'] == 1){
    $xlink .= '_export/'.$format.'/'.$id;
    if($more) $xlink .= '?'.$more;
  }else{
    $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
    if($more) $xlink .= $sep.$more;
  }

  return $xlink;
}

/**
 * Build a link to a media file
 *
 * Will return a link to the detail page if $direct is false
 */
function ml($id='',$more='',$direct=true,$sep='&amp;',$abs=false){
  global $conf;
  if(is_array($more)){
    $more = buildURLparams($more,$sep);
  }else{
    $more = str_replace(',',$sep,$more);
  if($abs){
    $xlink = DOKU_URL;
  }else{
    $xlink = DOKU_BASE;
  }

  // external URLs are always direct without rewriting
  if(preg_match('#^(https?|ftp)://#i',$id)){
    $xlink .= 'lib/exe/fetch.php';
    if($more){
      $xlink .= '?'.$more;
      $xlink .= $sep.'media='.rawurlencode($id);
    }else{
      $xlink .= '?media='.rawurlencode($id);
    }
    return $xlink;

  $id = idfilter($id);

  // decide on scriptname
  if($direct){
    if($conf['userewrite'] == 1){
      $script = '_media';
    }else{
      $script = 'lib/exe/fetch.php';
    }
  }else{
    if($conf['userewrite'] == 1){
      $script = '_detail';
    }else{
      $script = 'lib/exe/detail.php';
    }
  }

  // build URL based on rewrite mode
   if($conf['userewrite']){
     $xlink .= $script.'/'.$id;
     if($more) $xlink .= '?'.$more;
   }else{
     if($more){
       $xlink .= $script.'?'.$more;
     }else{
       $xlink .= $script.'?media='.$id;
     }
   }
andi's avatar
andi committed
/**
 * Just builds a link to a script
andi's avatar
andi committed
 *
 * @todo   maybe obsolete
andi's avatar
andi committed
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function script($script='doku.php'){
#  $link = getBaseURL();
#  $link .= $script;
#  return $link;
  return DOKU_BASE.DOKU_SCRIPT;
andi's avatar
andi committed
}

/**
andi's avatar
andi committed
 * Spamcheck against wordlist
 *
andi's avatar
andi committed
 * Checks the wikitext against a list of blocked expressions
 * returns true if the text contains any bad words
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function checkwordblock(){
  global $TEXT;
  global $conf;

  if(!$conf['usewordblock']) return false;

Andreas Gohr's avatar
Andreas Gohr committed
  // we prepare the text a tiny bit to prevent spammers circumventing URL checks
  $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$TEXT);

  $wordblocks = getWordblocks();
  //how many lines to read at once (to work around some PCRE limits)
  if(version_compare(phpversion(),'4.3.0','<')){
    //old versions of PCRE define a maximum of parenthesises even if no
    //backreferences are used - the maximum is 99
    //this is very bad performancewise and may even be too high still
    //read file in chunks of 200 - this should work around the
    //MAX_PATTERN_SIZE in modern PCRE
    $chunksize = 200;
  while($blocks = array_splice($wordblocks,0,$chunksize)){
    $re = array();
    #build regexp from blocks
    foreach($blocks as $block){
      $block = preg_replace('/#.*$/','',$block);
      $block = trim($block);
      if(empty($block)) continue;
      $re[]  = $block;
    }
    if(count($re) && preg_match('#('.join('|',$re).')#si',$text)) {
andi's avatar
andi committed
  }
  return false;
}

/**
andi's avatar
andi committed
 * Return the IP of the client
 *
 * Honours X-Forwarded-For and X-Real-IP Proxy Headers
andi's avatar
andi committed
 *
 * It returns a comma separated list of IPs if the above mentioned
 * headers are set. If the single parameter is set, it tries to return
 * a routable public address, prefering the ones suplied in the X
 * headers
 *
 * @param  boolean $single If set only a single IP is returned
andi's avatar
andi committed
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function clientIP($single=false){
  $ip = array();
  $ip[] = $_SERVER['REMOTE_ADDR'];
  if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
  if(!empty($_SERVER['HTTP_X_REAL_IP']))
    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP']));

  // remove any non-IP stuff
  $cnt = count($ip);
  $match = array();
  for($i=0; $i<$cnt; $i++){
    if(preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$ip[$i],$match)) {
      $ip[$i] = $match[0];
    } else {
      $ip[$i] = '';
    }
    if(empty($ip[$i])) unset($ip[$i]);
  }
  $ip = array_values(array_unique($ip));
  if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP

  if(!$single) return join(',',$ip);

  // decide which IP to use, trying to avoid local addresses
  $ip = array_reverse($ip);
  foreach($ip as $i){
    if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){
      continue;
    }else{
      return $i;
    }
andi's avatar
andi committed
  }
  // still here? just use the first (last) address
  return $ip[0];
andi's avatar
andi committed
}

/**
 * Convert one or more comma separated IPs to hostnames
 *
 * @author Glen Harris <astfgl@iamnota.org>
 * @returns a comma separated list of hostnames
 */
function gethostsbyaddrs($ips){
  $hosts = array();
  $ips = explode(',',$ips);
Andreas Gohr's avatar
Andreas Gohr committed
    foreach($ips as $ip){
      $hosts[] = gethostbyaddr(trim($ip));
    }
    return join(',',$hosts);
  } else {
    return gethostbyaddr(trim($ips));
andi's avatar
andi committed
/**
andi's avatar
andi committed
 * Checks if a given page is currently locked.
 *
andi's avatar
andi committed
 * removes stale lockfiles
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function checklock($id){
  global $conf;
  $lock = wikiLockFN($id);
andi's avatar
andi committed
  //no lockfile
  if(!@file_exists($lock)) return false;
andi's avatar
andi committed
  //lockfile expired
  if((time() - filemtime($lock)) > $conf['locktime']){
Ben Coburn's avatar
Ben Coburn committed
    @unlink($lock);
andi's avatar
andi committed
    return false;
  }
andi's avatar
andi committed
  //my own lock
  $ip = io_readFile($lock);
  if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
    return false;
  }
andi's avatar
andi committed
  return $ip;
}

/**
andi's avatar
andi committed
 * Lock a page for editing
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function lock($id){
  $lock = wikiLockFN($id);
andi's avatar
andi committed
  if($_SERVER['REMOTE_USER']){
    io_saveFile($lock,$_SERVER['REMOTE_USER']);
  }else{
    io_saveFile($lock,clientIP());
  }
}

/**
andi's avatar
andi committed
 * Unlock a page if it was locked by the user
andi's avatar
andi committed
 *
andi's avatar
andi committed
 * @author Andreas Gohr <andi@splitbrain.org>
 * @return bool true if a lock was removed
andi's avatar
andi committed
 */
function unlock($id){
  $lock = wikiLockFN($id);
andi's avatar
andi committed
  if(@file_exists($lock)){
    $ip = io_readFile($lock);
    if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
      @unlink($lock);
      return true;
    }
  }
  return false;
}

/**
 * convert line ending to unix format
 *
andi's avatar
andi committed
 * @see    formText() for 2crlf conversion
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function cleanText($text){
  $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
  return $text;
}

/**
 * Prepares text for print in Webforms by encoding special chars.
 * It also converts line endings to Windows format which is
 * pseudo standard for webforms.
andi's avatar
andi committed
 *
andi's avatar
andi committed
 * @see    cleanText() for 2unix conversion
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function formText($text){
  $text = str_replace("\012","\015\012",$text);
andi's avatar
andi committed
  return htmlspecialchars($text);
}

/**
andi's avatar
andi committed
 * Returns the specified local text in raw format
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function rawLocale($id){
  return io_readFile(localeFN($id));
}

/**
 * Returns the raw WikiText
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function rawWiki($id,$rev=''){
Ben Coburn's avatar
Ben Coburn committed
  return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
andi's avatar
andi committed
}

Andreas Gohr's avatar
Andreas Gohr committed
/**
 * Returns the pagetemplate contents for the ID's namespace
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function pageTemplate($data){
  $id = $data[0];
  global $conf;
  global $INFO;

  $path = dirname(wikiFN($id));

  if(@file_exists($path.'/_template.txt')){
    $tpl = io_readFile($path.'/_template.txt');
  }else{
    // search upper namespaces for templates
    $len = strlen(rtrim($conf['datadir'],'/'));
    while (strlen($path) >= $len){
      if(@file_exists($path.'/__template.txt')){
        $tpl = io_readFile($path.'/__template.txt');
        break;
      }
      $path = substr($path, 0, strrpos($path, '/'));
    }
  }
  if(!$tpl) return '';

  // replace placeholders
  $tpl = str_replace('@ID@',$id,$tpl);
  $tpl = str_replace('@NS@',getNS($id),$tpl);
  $tpl = str_replace('@PAGE@',strtr(noNS($id),'_',' '),$tpl);
  $tpl = str_replace('@USER@',$_SERVER['REMOTE_USER'],$tpl);
  $tpl = str_replace('@NAME@',$INFO['userinfo']['name'],$tpl);
  $tpl = str_replace('@MAIL@',$INFO['userinfo']['mail'],$tpl);
  $tpl = str_replace('@DATE@',$conf['dformat'],$tpl);
andi's avatar
andi committed
/**
andi's avatar
andi committed
 * Returns the raw Wiki Text in three slices.
 *
 * The range parameter needs to have the form "from-to"
andi's avatar
andi committed
 * and gives the range of the section in bytes - no
 * UTF-8 awareness is needed.
andi's avatar
andi committed
 * The returned order is prefix, section and suffix.
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function rawWikiSlices($range,$id,$rev=''){
  list($from,$to) = split('-',$range,2);
Ben Coburn's avatar
Ben Coburn committed
  $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
andi's avatar
andi committed
  if(!$from) $from = 0;
andi's avatar
andi committed
  if(!$to)   $to   = strlen($text)+1;
andi's avatar
andi committed

andi's avatar
andi committed
  $slices[0] = substr($text,0,$from-1);
  $slices[1] = substr($text,$from-1,$to-$from);
  $slices[2] = substr($text,$to);
andi's avatar
andi committed

  return $slices;
}

/**
andi's avatar
andi committed
 * Joins wiki text slices
 *
andi's avatar
andi committed
 * function to join the text slices with correct lineendings again.
 * When the pretty parameter is set to true it adds additional empty
 * lines between sections if needed (used on saving).
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function con($pre,$text,$suf,$pretty=false){

  if($pretty){
    if($pre && substr($pre,-1) != "\n") $pre .= "\n";
    if($suf && substr($text,-1) != "\n") $text .= "\n";
  }

  if($pre) $pre .= "\n";
  if($suf) $text .= "\n";
  return $pre.$text.$suf;
}

/**
 * Saves a wikitext by calling io_writeWikiPage.
 * Also directs changelog and attic updates.
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
Ben Coburn's avatar
Ben Coburn committed
 * @author Ben Coburn <btcoburn@silicodon.net>
andi's avatar
andi committed
 */
Andreas Gohr's avatar
Andreas Gohr committed
function saveWikiText($id,$text,$summary,$minor=false){
  /* Note to developers:
     This code is subtle and delicate. Test the behavior of
     the attic and changelog with dokuwiki and external edits
     after any changes. External edits change the wiki page
     directly without using php or dokuwiki.
  */
andi's avatar
andi committed
  global $conf;
  global $lang;
Ben Coburn's avatar
Ben Coburn committed
  global $REV;
andi's avatar
andi committed
  // ignore if no changes were made
  if($text == rawWiki($id,'')){
    return;
  }

  $file = wikiFN($id);
  $old = @filemtime($file); // from page
Ben Coburn's avatar
Ben Coburn committed
  $wasRemoved = empty($text);
Ben Coburn's avatar
Ben Coburn committed
  $wasCreated = !@file_exists($file);
Ben Coburn's avatar
Ben Coburn committed
  $wasReverted = ($REV==true);
Ben Coburn's avatar
Ben Coburn committed
  $newRev = false;
  $oldRev = getRevisions($id, -1, 1, 1024); // from changelog
  $oldRev = (int)(empty($oldRev)?0:$oldRev[0]);
  if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) {
    // add old revision to the attic if missing
    saveOldRevision($id);
    // add a changelog entry if this edit came from outside dokuwiki
      addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true));
      // remove soon to be stale instructions
      $cache = new cache_instructions($id, $file);
      $cache->removeCache();
    }
  }
andi's avatar
andi committed

Ben Coburn's avatar
Ben Coburn committed
  if ($wasRemoved){
    // Send "update" event with empty data, so plugins can react to page deletion
    $data = array(array($file, '', false), getNS($id), noNS($id), false);
    trigger_event('IO_WIKIPAGE_WRITE', $data);
Ben Coburn's avatar
Ben Coburn committed
    // pre-save deleted revision
    @touch($file);
    clearstatcache();
Ben Coburn's avatar
Ben Coburn committed
    $newRev = saveOldRevision($id);
    // remove empty file
andi's avatar
andi committed
    @unlink($file);
Ben Coburn's avatar
Ben Coburn committed
    // remove old meta info...
    $mfiles = metaFiles($id);
Ben Coburn's avatar
Ben Coburn committed
    $changelog = metaFN($id, '.changes');
    foreach ($mfiles as $mfile) {
Ben Coburn's avatar
Ben Coburn committed
      // but keep per-page changelog to preserve page history
Ben Coburn's avatar
Ben Coburn committed
      if (@file_exists($mfile) && $mfile!==$changelog) { @unlink($mfile); }
Steven Danz's avatar
Steven Danz committed
    }
andi's avatar
andi committed
    $del = true;
    // autoset summary on deletion
    if(empty($summary)) $summary = $lang['deleted'];
    // remove empty namespaces
Ben Coburn's avatar
Ben Coburn committed
    io_sweepNS($id, 'datadir');
    io_sweepNS($id, 'mediadir');
andi's avatar
andi committed
  }else{
Ben Coburn's avatar
Ben Coburn committed
    // save file (namespace dir is created in io_writeWikiPage)
    io_writeWikiPage($file, $text, $id);
    // pre-save the revision, to keep the attic in sync
    $newRev = saveOldRevision($id);
andi's avatar
andi committed
    $del = false;
  }

Ben Coburn's avatar
Ben Coburn committed
  // select changelog line type
  $extra = '';
  $type = DOKU_CHANGE_TYPE_EDIT;
Ben Coburn's avatar
Ben Coburn committed
  if ($wasReverted) {
    $type = DOKU_CHANGE_TYPE_REVERT;
Ben Coburn's avatar
Ben Coburn committed
    $extra = $REV;
  }
  else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; }
  else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; }
  else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users
Ben Coburn's avatar
Ben Coburn committed
  addLogEntry($newRev, $id, $type, $summary, $extra);
  // send notify mails
  notify($id,'admin',$old,$summary,$minor);
  notify($id,'subscribers',$old,$summary,$minor);
  // update the purgefile (timestamp of the last time anything within the wiki was changed)
  io_saveFile($conf['cachedir'].'/purgefile',time());
andi's avatar
andi committed
}

/**
 * moves the current version to the attic and returns its
 * revision date
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function saveOldRevision($id){
andi's avatar
andi committed
  $oldf = wikiFN($id);
  if(!@file_exists($oldf)) return '';
  $date = filemtime($oldf);
  $newf = wikiFN($id,$date);
Ben Coburn's avatar
Ben Coburn committed
  io_writeWikiPage($newf, rawWiki($id), $id, $date);
andi's avatar
andi committed
  return $date;
}

/**
 * Sends a notify mail on page change
 *
 * @param  string  $id       The changed page
 * @param  string  $who      Who to notify (admin|subscribers)
 * @param  int     $rev      Old page revision
 * @param  string  $summary  What changed
 * @param  boolean $minor    Is this a minor edit?
 * @param  array   $replace  Additional string substitutions, @KEY@ to be replaced by value
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
andi's avatar
andi committed
 */
function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
andi's avatar
andi committed
  global $lang;
  global $conf;
Steven Danz's avatar
Steven Danz committed

  // decide if there is something to do
  if($who == 'admin'){
    if(empty($conf['notify'])) return; //notify enabled?
    $text = rawLocale('mailtext');
    $to   = $conf['notify'];
    $bcc  = '';
  }elseif($who == 'subscribers'){
    if(!$conf['subscribers']) return; //subscribers enabled?
    if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
    $bcc  = subscriber_addresslist($id);
    if(empty($bcc)) return;
    $to   = '';
    $text = rawLocale('subscribermail');
Sebastian Harl's avatar
Sebastian Harl committed
  }elseif($who == 'register'){
    if(empty($conf['registernotify'])) return;
    $text = rawLocale('registermail');
    $to   = $conf['registernotify'];
    $bcc  = '';
  }else{
    return; //just to be safe
  }
  $ip   = clientIP();
  $text = str_replace('@DATE@',strftime($conf['dformat']),$text);
andi's avatar
andi committed
  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
  $text = str_replace('@IPADDRESS@',$ip,$text);
  $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
  $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text);
  $text = str_replace('@PAGE@',$id,$text);
  $text = str_replace('@TITLE@',$conf['title'],$text);
  $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
andi's avatar
andi committed
  $text = str_replace('@SUMMARY@',$summary,$text);
  $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
  foreach ($replace as $key => $substitution) {
    $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
  }

Sebastian Harl's avatar
Sebastian Harl committed
  if($who == 'register'){
    $subject = $lang['mail_new_user'].' '.$summary;
  }elseif($rev){
andi's avatar
andi committed
    $subject = $lang['mail_changed'].' '.$id;
    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
    require_once(DOKU_INC.'inc/DifferenceEngine.php');
andi's avatar
andi committed
    $df  = new Diff(split("\n",rawWiki($id,$rev)),
                    split("\n",rawWiki($id)));
    $dformat = new UnifiedDiffFormatter();
    $diff    = $dformat->format($df);
  }else{
    $subject=$lang['mail_newpage'].' '.$id;
    $text = str_replace('@OLDPAGE@','none',$text);
    $diff = rawWiki($id);
  }
  $text = str_replace('@DIFF@',$diff,$text);
  $subject = '['.$conf['title'].'] '.$subject;
andi's avatar
andi committed

  $from = $conf['mailfrom'];
  $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from);
  $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from);
  $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from);

  mail_send($to,$subject,$text,$from,'',$bcc);
andi's avatar
andi committed
}

/**
 * extracts the query from a search engine referrer
andi's avatar
andi committed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @author Todd Augsburger <todd@rollerorgans.com>
andi's avatar
andi committed
 */
function getGoogleQuery(){
  $url = parse_url($_SERVER['HTTP_REFERER']);
andi's avatar
andi committed
  if(!$url) return '';
andi's avatar
andi committed

  $query = array();
  parse_str($url['query'],$query);
  if(isset($query['q']))
    $q = $query['q'];        // google, live/msn, aol, ask, altavista, alltheweb, gigablast
  elseif(isset($query['p']))
  elseif(isset($query['query']))
    $q = $query['query'];    // lycos, netscape, clusty, hotbot
  elseif(preg_match("#a9\.com#i",$url['host'])) // a9
    $q = urldecode(ltrim($url['path'],'/'));