/**
* Common DokuWiki functions
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr
*/
require_once("conf/dokuwiki.php");
require_once("inc/io.php");
require_once('inc/utf8.php');
require_once('inc/mail.php');
//set up error reporting to sane values
error_reporting(E_ALL ^ E_NOTICE);
//make session rewrites XHTML compliant
//ini_set('arg_separator.output', '&');
//init session
session_name("DokuWiki");
session_start();
//kill magic quotes
if (get_magic_quotes_gpc()) {
if (!empty($_GET)) remove_magic_quotes($_GET);
if (!empty($_POST)) remove_magic_quotes($_POST);
if (!empty($_COOKIE)) remove_magic_quotes($_COOKIE);
if (!empty($_REQUEST)) remove_magic_quotes($_REQUEST);
if (!empty($_SESSION)) remove_magic_quotes($_SESSION);
// ini_set('magic_quotes_gpc', 0);
}
set_magic_quotes_runtime(0);
// ini_set('magic_quotes_sybase',0);
//disable gzip if not available
if($conf['usegzip'] && !function_exists('gzopen')){
$conf['usegzip'] = 0;
}
//remember original umask
$conf['oldumask'] = umask();
//make absolute mediaweb
if(!preg_match('#^(https?://|/)#i',$conf['mediaweb'])){
$conf['mediaweb'] = getBaseURL().$conf['mediaweb'];
}
/**
* remove magic quotes recursivly
*
* @author Andreas Gohr
*/
function remove_magic_quotes(&$array) {
foreach (array_keys($array) as $key) {
if (is_array($array[$key])) {
remove_magic_quotes($array[$key]);
}else {
$array[$key] = stripslashes($array[$key]);
}
}
}
/**
* Returns the full absolute URL to the directory where
* DokuWiki is installed in (includes a trailing slash)
*
* @author Andreas Gohr
*/
function getBaseURL($abs=false){
global $conf;
//if canonical url enabled always return absolute
if($conf['canonical']) $abs = true;
$dir = dirname($_SERVER['PHP_SELF']).'/';
$dir = str_replace('\\','/',$dir); #bugfix for weird WIN behaviour
$dir = preg_replace('#//+#','/',$dir);
//finish here for relative URLs
if(!$abs) return $dir;
$port = ':'.$_SERVER['SERVER_PORT'];
//remove port from hostheader as sent by IE
$host = preg_replace('/:.*$/','',$_SERVER['HTTP_HOST']);
// see if HTTPS is enabled - apache leaves this empty when not available,
// IIS sets it to 'off', 'false' and 'disabled' are just guessing
if (preg_match('/^(|off|false|disabled)$/i',$_SERVER['HTTPS'])){
$proto = 'http://';
if ($_SERVER['SERVER_PORT'] == '80') {
$port='';
}
}else{
$proto = 'https://';
if ($_SERVER['SERVER_PORT'] == '443') {
$port='';
}
}
return $proto.$host.$port.$dir;
}
/**
* Return info about the current document as associative
* array.
*
* @author Andreas Gohr
*/
function pageinfo(){
global $ID;
global $REV;
global $USERINFO;
global $conf;
if($GLOBALS['xoopsUser']){
$info['user'] = $GLOBALS['xoopsUser']->uname();
$info['userinfo'] = $USERINFO;
$info['perm'] = auth_quickaclcheck($ID);
}else{
$info['user'] = '';
$info['perm'] = auth_aclcheck($ID,'',null);
}
$info['namespace'] = getNS($ID);
$info['locked'] = checklock($ID);
$info['filepath'] = realpath(wikiFN($ID,$REV));
$info['exists'] = @file_exists($info['filepath']);
if($REV && !$info['exists']){
//check if current revision was meant
$cur = wikiFN($ID);
if(@file_exists($cur) && (@filemtime($cur) == $REV)){
$info['filepath'] = realpath($cur);
$info['exists'] = true;
$REV = '';
}
}
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']);
//who's the editor
if($REV){
$revinfo = getRevisionInfo($ID,$REV);
}else{
$revinfo = getRevisionInfo($ID,$info['lastmod']);
}
$info['ip'] = $revinfo['ip'];
$info['user'] = $revinfo['user'];
$info['sum'] = $revinfo['sum'];
$info['editor'] = $revinfo['ip'];
if($revinfo['user']) $info['editor'].= ' ('.$revinfo['user'].')';
return $info;
}
/**
* print a message
*
* If HTTP headers were not sent yet the message is added
* to the global message array else it's printed directly
* using html_msgarea()
*
*
* Levels can be:
*
* -1 error
* 0 info
* 1 success
*
* @author Andreas Gohr
* @see html_msgarea
*/
function msg($message,$lvl=0){
global $MSG;
$errors[-1] = 'error';
$errors[0] = 'info';
$errors[1] = 'success';
if(!headers_sent()){
if(!isset($MSG)) $MSG = array();
$MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
}else{
$MSG = array();
$MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
html_msgarea();
}
}
/**
* This builds the breadcrumb trail and returns it as array
*
* @author Andreas Gohr
*/
function breadcrumbs(){
global $ID;
global $ACT;
global $conf;
$crumbs = $_SESSION[$conf['title']]['bc'];
//first visit?
if (!is_array($crumbs)){
$crumbs = array();
}
//we only save on show and existing wiki documents
if($ACT != 'show' || !@file_exists(wikiFN($ID))){
$_SESSION[$conf['title']]['bc'] = $crumbs;
return $crumbs;
}
//remove ID from array
$pos = array_search($ID,$crumbs);
if($pos !== false && $pos !== null){
array_splice($crumbs,$pos,1);
}
//add to array
$crumbs[] =$ID;
//reduce size
while(count($crumbs) > $conf['breadcrumbs']){
array_shift($crumbs);
}
//save to session
$_SESSION[$conf['title']]['bc'] = $crumbs;
return $crumbs;
}
/**
* Filter for page IDs
*
* 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
*
* Urlencoding is ommitted when the second parameter is false
*
* @author Andreas Gohr
*/
function idfilter($id,$ue=true){
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 = urlencode($id);
$id = str_replace('%3A',':',$id); //keep as colon
$id = str_replace('%2F','/',$id); //keep as slash
}
return $id;
}
/**
* This builds a link to a wikipage (using getBaseURL)
*
* @author Andreas Gohr
*/
function wl($id='',$more='',$script='doku.php',$canonical=false){
global $conf;
$more = str_replace(',','&',$more);
$id = idfilter($id);
$xlink = getBaseURL($canonical);
if(!$conf['userewrite']){
$xlink .= $script;
$xlink .= '?id='.$id;
if($more) $xlink .= '&'.$more;
}else{
$xlink .= $id;
if($more) $xlink .= '?'.$more;
}
return $xlink;
}
/**
* Just builds a link to a script
*
* @author Andreas Gohr
*/
function script($script='doku.php'){
$link = getBaseURL();
$link .= $script;
return $link;
}
/**
* Return namespacepart of a wiki ID
*
* @author Andreas Gohr
*/
function getNS($id){
if(strpos($id,':')!==false){
return substr($id,0,strrpos($id,':'));
}
return false;
}
/**
* Returns the ID without the namespace
*
* @author Andreas Gohr
*/
function noNS($id){
return preg_replace('/.*:/','',$id);
}
/**
* Spamcheck against wordlist
*
* Checks the wikitext against a list of blocked expressions
* returns true if the text contains any bad words
*
* @author Andreas Gohr
*/
function checkwordblock(){
global $TEXT;
global $conf;
if(!$conf['usewordblock']) return false;
$blockfile = file('conf/wordblock.conf');
//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
$chunksize = 40;
}else{
//read file in chunks of 600 - this should work around the
//MAX_PATTERN_SIZE in modern PCRE
$chunksize = 600;
}
while($blocks = array_splice($blockfile,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(preg_match('#('.join('|',$re).')#si',$TEXT)) return true;
}
return false;
}
/**
* Return the IP of the client
*
* Honours X-Forwarded-For Proxy Headers
*
* @author Andreas Gohr
*/
function clientIP(){
$my = $_SERVER['REMOTE_ADDR'];
if($_SERVER['HTTP_X_FORWARDED_FOR']){
$my .= ' ('.$_SERVER['HTTP_X_FORWARDED_FOR'].')';
}
return $my;
}
/**
* Checks if a given page is currently locked.
*
* removes stale lockfiles
*
* @author Andreas Gohr
*/
function checklock($id){
global $conf;
$lock = wikiFN($id).'.lock';
//no lockfile
if(!@file_exists($lock)) return false;
//lockfile expired
if((time() - filemtime($lock)) > $conf['locktime']){
unlink($lock);
return false;
}
//my own lock
$ip = io_readFile($lock);
if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
return false;
}
return $ip;
}
/**
* Lock a page for editing
*
* @author Andreas Gohr
*/
function lock($id){
$lock = wikiFN($id).'.lock';
if($_SERVER['REMOTE_USER']){
io_saveFile($lock,$_SERVER['REMOTE_USER']);
}else{
io_saveFile($lock,clientIP());
}
}
/**
* Unlock a page if it was locked by the user
*
* @author Andreas Gohr
* @return bool true if a lock was removed
*/
function unlock($id){
$lock = wikiFN($id).'.lock';
if(@file_exists($lock)){
$ip = io_readFile($lock);
if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
@unlink($lock);
return true;
}
}
return false;
}
/**
* Remove unwanted chars from ID
*
* Cleans a given ID to only use allowed characters. Accented characters are
* converted to unaccented ones
*
* @author Andreas Gohr
*/
function cleanID($id){
global $conf;
global $lang;
$id = trim($id);
$id = utf8_strtolower($id);
//alternative namespace seperator
$id = strtr($id,';',':');
if($conf['useslash']){
$id = strtr($id,'/',':');
}else{
$id = strtr($id,'/','_');
}
if($conf['deaccent']) $id = utf8_deaccent($id,-1);
//remove specials
//$id = preg_replace('#[\x00-\x20 ¡!"§$%&()\[\]{}¿\\?`\'\#~*+=,<>\|^°@µ¹²³Â1/4Â1/2¬]#u','_',$id);
$id = utf8_stripspecials($id,'_','_:.-');
//clean up
$id = preg_replace('#__#','_',$id);
$id = preg_replace('#:+#',':',$id);
$id = trim($id,':._-');
$id = preg_replace('#:[:\._\-]+#',':',$id);
return($id);
}
/**
* returns the full path to the datafile specified by ID and
* optional revision
*
* The filename is URL encoded to protect Unicode chars
*
* @author Andreas Gohr
*/
function wikiFN($id,$rev=''){
global $conf;
$id = cleanID($id);
$id = str_replace(':','/',$id);
if(empty($rev)){
$fn = $conf['datadir'].'/'.$id.'.txt';
}else{
$fn = $conf['olddir'].'/'.$id.'.'.$rev.'.txt';
if($conf['usegzip'] && !@file_exists($fn)){
//return gzip if enabled and plaintext doesn't exist
$fn .= '.gz';
}
}
$fn = utf8_encodeFN($fn);
return $fn;
}
/**
* Returns the full filepath to a localized textfile if local
* version isn't found the english one is returned
*
* @author Andreas Gohr
*/
function localeFN($id){
global $conf;
$file = './lang/'.$conf['lang'].'/'.$id.'.txt';
if(!@file_exists($file)){
//fall back to english
$file = './lang/en/'.$id.'.txt';
}
return cleanText($file);
}
/**
* convert line ending to unix format
*
* @see formText() for 2crlf conversion
* @author Andreas Gohr
*/
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.
*
* @see cleanText() for 2unix conversion
* @author Andreas Gohr
*/
function formText($text){
$text = preg_replace("/\012/","\015\012",$text);
return htmlspecialchars($text);
}
/**
* Returns the specified local text in parsed format
*
* @author Andreas Gohr
*/
function parsedLocale($id){
//disable section editing
global $parser;
$se = $parser['secedit'];
$parser['secedit'] = false;
//fetch parsed locale
$html = io_cacheParse(localeFN($id));
//reset section editing
$parser['secedit'] = $se;
return $html;
}
/**
* Returns the specified local text in raw format
*
* @author Andreas Gohr
*/
function rawLocale($id){
return io_readFile(localeFN($id));
}
/**
* Returns the parsed Wikitext for the given id and revision.
*
* If $excuse is true an explanation is returned if the file
* wasn't found
*
* @author Andreas Gohr
*/
function parsedWiki($id,$rev='',$excuse=true){
$file = wikiFN($id,$rev);
$ret = '';
//ensure $id is in global $ID (needed for parsing)
global $ID;
$ID = $id;
if($rev){
if(@file_exists($file)){
$ret = parse(io_readFile($file));
}elseif($excuse){
$ret = parsedLocale('norev');
}
}else{
if(@file_exists($file)){
$ret = io_cacheParse($file);
}elseif($excuse){
$ret = parsedLocale('newpage');
}
}
return $ret;
}
/**
* Returns the raw WikiText
*
* @author Andreas Gohr
*/
function rawWiki($id,$rev=''){
return io_readFile(wikiFN($id,$rev));
}
/**
* Returns the raw Wiki Text in three slices.
*
* The range parameter needs to have the form "from-to"
* and gives the range of the section.
* The returned order is prefix, section and suffix.
*
* @author Andreas Gohr
*/
function rawWikiSlices($range,$id,$rev=''){
list($from,$to) = split('-',$range,2);
$text = io_readFile(wikiFN($id,$rev));
$text = split("\n",$text);
if(!$from) $from = 0;
if(!$to) $to = count($text);
$slices[0] = join("\n",array_slice($text,0,$from));
$slices[1] = join("\n",array_slice($text,$from,$to + 1 - $from));
$slices[2] = join("\n",array_slice($text,$to+1));
return $slices;
}
/**
* Joins wiki text slices
*
* 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).
*
* @author Andreas Gohr
*/
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;
}
/**
* print debug messages
*
* little function to print the content of a var
*
* @author Andreas Gohr
*/
function dbg($msg,$hidden=false){
(!$hidden) ? print '' : print "";
}
/**
* Add's an entry to the changelog
*
* @author Andreas Gohr
*/
function addLogEntry($date,$id,$summary=""){
global $conf;
$id = cleanID($id);//FIXME not needed anymore?
if(!@is_writable($conf['changelog'])){
msg($conf['changelog'].' is not writable!',-1);
return;
}
if(!$date) $date = time(); //use current time if none supplied
$remote = $_SERVER['REMOTE_ADDR'];
$user = $_SERVER['REMOTE_USER'];
$logline = join("\t",array($date,$remote,$id,$user,$summary))."\n";
//FIXME: use adjusted io_saveFile instead
$fh = fopen($conf['changelog'],'a');
if($fh){
fwrite($fh,$logline);
fclose($fh);
}
}
/**
* returns an array of recently changed files using the
* changelog
*
* @author Andreas Gohr
*/
function getRecents($num=0,$incdel=false){
global $conf;
$recent = array();
if(!$num) $num = $conf['recent'];
if(!@is_readable($conf['changelog'])){
msg($conf['changelog'].' is not readable',-1);
return $recent;
}
$loglines = file($conf['changelog']);
rsort($loglines); //reverse sort on timestamp
foreach ($loglines as $line){
$line = rtrim($line); //remove newline
if(empty($line)) continue; //skip empty lines
$info = split("\t",$line); //split into parts
//add id if not in yet and file still exists and is allowed to read
if(!$recent[$info[2]] &&
(@file_exists(wikiFN($info[2])) || $incdel) &&
(auth_quickaclcheck($info[2]) >= AUTH_READ)
){
$recent[$info[2]]['date'] = $info[0];
$recent[$info[2]]['ip'] = $info[1];
$recent[$info[2]]['user'] = $info[3];
$recent[$info[2]]['sum'] = $info[4];
$recent[$info[2]]['del'] = !@file_exists(wikiFN($info[2]));
}
if(count($recent) >= $num){
break; //finish if enough items found
}
}
return $recent;
}
/**
* gets additonal informations for a certain pagerevison
* from the changelog
*
* @author Andreas Gohr
*/
function getRevisionInfo($id,$rev){
global $conf;
$info = array();
if(!@is_readable($conf['changelog'])){
msg($conf['changelog'].' is not readable',-1);
return $recent;
}
$loglines = file($conf['changelog']);
$loglines = preg_grep("/$rev\t\d+\.\d+\.\d+\.\d+\t$id\t/",$loglines);
rsort($loglines); //reverse sort on timestamp (shouldn't be needed)
$line = split("\t",$loglines[0]);
$info['date'] = $line[0];
$info['ip'] = $line[1];
$info['user'] = $line[3];
$info['sum'] = $line[4];
return $info;
}
/**
* Saves a wikitext by calling io_saveFile
*
* @author Andreas Gohr
*/
function saveWikiText($id,$text,$summary){
global $conf;
global $lang;
umask($conf['umask']);
// ignore if no changes were made
if($text == rawWiki($id,'')){
return;
}
$file = wikiFN($id);
$old = saveOldRevision($id);
if (empty($text)){
// remove empty files
@unlink($file);
$del = true;
//autoset summary on deletion
if(empty($summary)) $summary = $lang['deleted'];
}else{
// save file (datadir is created in io_saveFile)
io_saveFile($file,$text);
$del = false;
}
addLogEntry(@filemtime($file),$id,$summary);
notify($id,$old,$summary);
//purge cache on add by updating the purgefile
if($conf['purgeonadd'] && (!$old || $del)){
io_saveFile($conf['datadir'].'/.cache/purgefile',time());
}
}
/**
* moves the current version to the attic and returns its
* revision date
*
* @author Andreas Gohr
*/
function saveOldRevision($id){
global $conf;
umask($conf['umask']);
$oldf = wikiFN($id);
if(!@file_exists($oldf)) return '';
$date = filemtime($oldf);
$newf = wikiFN($id,$date);
if(substr($newf,-3)=='.gz'){
io_saveFile($newf,rawWiki($id));
}else{
io_makeFileDir($newf);
copy($oldf, $newf);
}
return $date;
}
/**
* Sends a notify mail to the wikiadmin when a page was
* changed
*
* @author Andreas Gohr
*/
function notify($id,$rev="",$summary=""){
global $lang;
global $conf;
$hdrs ='';
if(empty($conf['notify'])) return; //notify enabled?
$text = rawLocale('mailtext');
$text = str_replace('@DATE@',date($conf['dformat']),$text);
$text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
$text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
$text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
$text = str_replace('@NEWPAGE@',wl($id,'','doku.php',true),$text);
$text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
$text = str_replace('@SUMMARY@',$summary,$text);
$text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
if($rev){
$subject = $lang['mail_changed'].' '.$id;
$text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",'doku.php',true),$text);
require_once("inc/DifferenceEngine.php");
$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);
mail_send($conf['notify'],$subject,$text,$conf['mailfrom']);
}
/**
* Return a list of available page revisons
*
* @author Andreas Gohr
*/
function getRevisions($id){
$revd = dirname(wikiFN($id,'foo'));
$revs = array();
$clid = cleanID($id);
if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path
if (is_dir($revd) && $dh = opendir($revd)) {
while (($file = readdir($dh)) !== false) {
if (is_dir($revd.'/'.$file)) continue;
if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){
$revs[]=$match[1];
}
}
closedir($dh);
}
rsort($revs);
return $revs;
}
/**
* downloads a file from the net and saves it to the given location
*
* @author Andreas Gohr
*/
function download($url,$file){
$fp = @fopen($url,"rb");
if(!$fp) return false;
while(!feof($fp)){
$cont.= fread($fp,1024);
}
fclose($fp);
$fp2 = @fopen($file,"w");
if(!$fp2) return false;
fwrite($fp2,$cont);
fclose($fp2);
return true;
}
/**
* extracts the query from a google referer
*
* @author Andreas Gohr
*/
function getGoogleQuery(){
$url = parse_url($_SERVER['HTTP_REFERER']);
if(!preg_match("#google\.#i",$url['host'])) return '';
$query = array();
parse_str($url['query'],$query);
return $query['q'];
}
/**
* Try to set correct locale
*
* @deprecated No longer used
* @author Andreas Gohr
*/
function setCorrectLocale(){
global $conf;
global $lang;
$enc = strtoupper($lang['encoding']);
foreach ($lang['locales'] as $loc){
//try locale
if(@setlocale(LC_ALL,$loc)) return;
//try loceale with encoding
if(@setlocale(LC_ALL,"$loc.$enc")) return;
}
//still here? try to set from environment
@setlocale(LC_ALL,"");
}
/**
* Return the human readable size of a file
*
* @param int $size A file size
* @param int $dec A number of decimal places
* @author Martin Benjamin
* @author Aidan Lister
* @version 1.0.0
*/
function filesize_h($size, $dec = 1){
$sizes = array('B', 'KB', 'MB', 'GB');
$count = count($sizes);
$i = 0;
while ($size >= 1024 && ($i < $count - 1)) {
$size /= 1024;
$i++;
}
return round($size, $dec) . ' ' . $sizes[$i];
}
/**
* Run a few sanity checks
*
* @author Andreas Gohr
*/
function getVersion(){
//import version string
if(@file_exists('VERSION')){
//official release
return 'Release '.io_readfile('VERSION');
}elseif(is_dir('_darcs')){
//darcs checkout
$inv = file('_darcs/inventory');
$inv = preg_grep('#andi@splitbrain\.org\*\*\d{14}#',$inv);
$cur = array_pop($inv);
preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches);
return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3];
}else{
return 'snapshot?';
}
}
/**
* Run a few sanity checks
*
* @author Andreas Gohr
*/
function check(){
global $conf;
global $INFO;
msg('DokuWiki version: '.getVersion(),1);
if(version_compare(phpversion(),'4.3.0','<')){
msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1);
}elseif(version_compare(phpversion(),'4.3.10','<')){
msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0);
}else{
msg('PHP version '.phpversion(),1);
}
if(is_writable($conf['changelog'])){
msg('Changelog is writable',1);
}else{
msg('Changelog is not writable',-1);
}
if(is_writable($conf['datadir'])){
msg('Datadir is writable',1);
}else{
msg('Datadir is not writable',-1);
}
if(is_writable($conf['olddir'])){
msg('Attic is writable',1);
}else{
msg('Attic is not writable',-1);
}
if(is_writable($conf['mediadir'])){
msg('Mediadir is writable',1);
}else{
msg('Mediadir is not writable',-1);
}
if(is_writable('conf/users.auth')){
msg('conf/users.auth is writable',1);
}else{
msg('conf/users.auth is not writable',0);
}
if(function_exists('mb_strpos')){
if(defined('UTF8_NOMBSTRING')){
msg('mb_string extension is available but will not be used',0);
}else{
msg('mb_string extension is available and will be used',1);
}
}else{
msg('mb_string extension not available - PHP only replacements will be used',0);
}
msg('Your current permission for this page is '.$INFO['perm'],0);
if(is_writable($INFO['filepath'])){
msg('The current page is writable by the webserver',0);
}else{
msg('The current page is not writable by the webserver',0);
}
if($INFO['writable']){
msg('The current page is writable by you',0);
}else{
msg('The current page is not writable you',0);
}
}
?>
/**
* The DokuWiki parser
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr
*/
include_once("inc/common.php");
include_once("inc/html.php");
include_once("inc/format.php");
require_once("lang/en/lang.php");
require_once("lang/".$conf['lang']."/lang.php");
/**
* The main parser function.
*
* Accepts raw data and returns valid xhtml
*
* @author Andreas Gohr
*/
function parse($text){
global $parser;
global $conf;
$table = array();
$hltable = array();
//preparse
$text = preparse($text,$table,$hltable);
//padding with a newline
$text = "\n".$text."\n";
#for link matching
$urls = '(https?|telnet|gopher|file|wais|ftp|ed2k|irc)';
$ltrs = '\w';
$gunk = '/\#~:.?+=&%@!\-';
$punc = '.:?\-;,';
$host = $ltrs.$punc;
$any = $ltrs.$gunk.$punc;
/* first pass */
//preformated texts
firstpass($table,$text,"#(.*?)#se","preformat('\\1','nowiki')");
firstpass($table,$text,"#%%(.*?)%%#se","preformat('\\1','nowiki')");
firstpass($table,$text,"#(.*?)
#se","preformat('\\3','code','\\2')");
firstpass($table,$text,"#(.*?)#se","preformat('\\1','file')");
// html and php includes
firstpass($table,$text,"#(.*?)#se","preformat('\\1','html')");
firstpass($table,$text,"#(.*?)#se","preformat('\\1','php')");
// codeblocks
firstpass($table,$text,"/(\n( {2,}|\t)[^\*\-\n ][^\n]+)(\n( {2,}|\t)[^\n]*)*/se","preformat('\\0','block')","\n");
//check if toc is wanted
if(!isset($parser['toc'])){
if(strpos($text,'~~NOTOC~~')!== false){
$text = str_replace('~~NOTOC~~','',$text);
$parser['toc'] = false;
}else{
$parser['toc'] = true;
}
}
//check if this file may be cached
if(!isset($parser['cache'])){
if(strpos($text,'~~NOCACHE~~')!=false){
$text = str_replace('~~NOCACHE~~','',$text);
$parser['cache'] = false;
}else{
$parser['cache'] = true;
}
}
//headlines
format_headlines($table,$hltable,$text);
//links
firstpass($table,$text,"#\[\[([^\]]+?)\]\]#ie","linkformat('\\1')");
//media
firstpass($table,$text,"/\{\{([^\}]+)\}\}/se","mediaformat('\\1')");
//match full URLs (adapted from Perl cookbook)
firstpass($table,$text,"#(\b)($urls://[$any]+?)([$punc]*[^$any])#ie","linkformat('\\2')",'\1','\4');
//short www URLs
firstpass($table,$text,"#(\b)(www\.[$host]+?\.[$host]+?[$any]+?)([$punc]*[^$any])#ie","linkformat('http://\\2|\\2')",'\1','\3');
//windows shares
firstpass($table,$text,"#([$gunk$punc\s])(\\\\\\\\[$host]+?\\\\[$any]+?)([$punc]*[^$any])#ie","linkformat('\\2')",'\1','\3');
//short ftp URLs
firstpass($table,$text,"#(\b)(ftp\.[$host]+?\.[$host]+?[$any]+?)([$punc]*[^$any])#ie","linkformat('ftp://\\2')",'\1','\3');
// email@domain.tld
firstpass($table,$text,"#<([\w0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)>#ie", "linkformat('\\1@\\2')");
//CamelCase if wanted
if($conf['camelcase']){
firstpass($table,$text,"#(\b)([A-Z]+[a-z]+[A-Z][A-Za-z]*)(\b)#se","linkformat('\\2')",'\1','\3');
}
$text = htmlspecialchars($text);
//smileys
smileys($table,$text);
//acronyms
acronyms($table,$text);
/* second pass for simple formating */
$text = simpleformat($text);
/* third pass - insert the matches from 1st pass */
reset($table);
while (list($key, $val) = each($table)) {
$text = str_replace($key,$val,$text);
}
/* remove empty paragraphs */
$text = preg_replace('"\n*
"','',$text);
/* remove padding */
$text = trim($text);
return $text;
}
/**
* Line by line preparser
*
* This preparses the text by walking it line by line. This
* is the only place where linenumbers are still available (needed
* for section edit. Some precautions have to be taken to not change
* any noparse block.
*
* @author Andreas Gohr
*/
function preparse($text,&$table,&$hltable){
$lines = split("\n",$text);
//prepare a tokens for paragraphs
$po = mkToken();
$table[$po] = "";
$pc = mkToken();
$table[$pc] = "
";
for ($l=0; $l(.*?)#","",$line);
$line = preg_replace("#%%(.*?)%%#","",$line);
$line = preg_replace("#(.*?)
#","",$line);
$line = preg_replace("#(.*?)#","",$line);
$line = preg_replace("#(.*?)#","",$line);
$line = preg_replace("#(.*?)#","",$line);
//check for start of multiline noparse areas
if(preg_match('#^.*?<(nowiki|code|php|html|file)( (\w+))?>#',$line,$matches)){
list($noparse) = split(" ",$matches[1]); //remove options
$noparse = ''.$noparse.'>';
continue;
}elseif(preg_match('#^.*?%%#',$line)){
$noparse = '%%';
continue;
}
}
//handle headlines
if(preg_match('/^(\s)*(==+)(.+?)(==+)(\s*)$/',$lines[$l],$matches)){
//get token
$tk = tokenize_headline($hltable,$matches[2],$matches[3],$l);
//replace line with token
$lines[$l] = $tk;
}
//handle paragraphs
if(empty($lines[$l])){
$lines[$l] = "$pc\n$po";
}
}
//reassemble full text
$text = join("\n",$lines);
//open first and close last paragraph
$text = "$po\n$text\n$pc";
return $text;
}
/**
* Build TOC lookuptable
*
* This function adds some information about the given headline
* to a lookuptable to be processed later. Returns a unique token
* that idetifies the headline later
*
* @author Andreas Gohr
*/
function tokenize_headline(&$hltable,$pre,$hline,$lno){
switch (strlen($pre)){
case 2:
$lvl = 5;
break;
case 3:
$lvl = 4;
break;
case 4:
$lvl = 3;
break;
case 5:
$lvl = 2;
break;
default:
$lvl = 1;
break;
}
$token = mkToken();
$hltable[] = array( 'name' => htmlspecialchars(trim($hline)),
'level' => $lvl,
'line' => $lno,
'token' => $token );
return $token;
}
/**
* Headline formatter
*
* @author Andreas Gohr
*/
function format_headlines(&$table,&$hltable,&$text){
global $parser;
global $conf;
global $lang;
global $ID;
// walk the headline table prepared in preparsing
$last = 0;
$cnt = 0;
$hashs = array();
foreach($hltable as $hl){
$cnt++;
//make unique headlinehash
$hash = cleanID($hl['name']);
$i=2;
while(in_array($hash,$hashs))
$hash = cleanID($hl['name']).$i++;
$hashs[] = $hash;
// build headline
$headline = "
\n"; //close paragraph
if($cnt - 1) $headline .= ''; //no close on first HL
$headline .= '';
$headline .= '';
$headline .= $hl['name'];
$headline .= '';
$headline .= '';
$headline .= "\n
"; //open new paragraph
//remember for autoTOC
if($hl['level'] <= $conf['maxtoclevel']){
$content[] = array('id' => $hash,
'name' => $hl['name'],
'level' => $hl['level']);
}
//add link for section edit for HLs 1, and 3
if( ($hl['level'] <= $conf['maxseclevel']) &&
($hl['line'] - $last > 1)){
$secedit = '';
$headline = $secedit.$headline;
$last = $hl['line'];
}
//put headline into firstpasstable
$table[$hl['token']] = $headline;
}
//add link for editing the last section
if($last){
$secedit = '';
$token = mktoken();
$text .= $token;
$table[$token] = $secedit;
}
//close last div
if ($cnt){
$token = mktoken();
$text .= $token;
$table[$token] = '
';
}
//prepend toc
if ($parser['toc'] && count($content) > 2){
$token = mktoken();
$text = $token.$text;
$table[$token] = html_toc($content);
}
}
/**
* Formats various link types using the functions from format.php
*
* @author Andreas Gohr
*/
function linkformat($match){
global $conf;
//unescape
$match = str_replace('\\"','"',$match);
//prepare variables for the formaters
$link = array();
list($link['url'],$link['name']) = split('\|',$match,2);
$link['url'] = trim($link['url']);
$link['name'] = trim($link['name']);
$link['class'] = '';
$link['target'] = '';
$link['style'] = '';
$link['pre'] = '';
$link['suf'] = '';
$link['more'] = '';
//save real name for image check
$realname = $link['name'];
/* put it into the right formater */
if(strpos($link['url'],'>')){
// InterWiki
$link = format_link_interwiki($link);
}elseif(preg_match('#^([a-z0-9]+?){1}://#i',$link['url'])){
// external URL
$link = format_link_externalurl($link);
}elseif(preg_match("/^\\\\\\\\([a-z0-9\-_.]+)\\\\(.+)$/",$link['url'])){
// windows shares
$link = format_link_windows($link);
}elseif(preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link['url'])){
// email
$link = format_link_email($link);
}else{
// wiki link
$link = format_link_wiki($link);
}
//is realname an image? use media formater
if(preg_match('#^{{.*?\.(gif|png|jpe?g)(\?.*?)?\s*(\|.*?)?}}$#',$realname)){
$link['name'] = substr($realname,2,-2);
$link = format_link_media($link);
}
// build the replacement with the variables set by the formaters
return format_link_build($link);
}
/**
* Simple text formating and typography is done here
*
* @author Andreas Gohr
*/
function simpleformat($text){
global $conf;
$text = preg_replace('/__(.+?)__/s','\1',$text); //underline
$text = preg_replace('/\/\/(.+?)\/\//s','\1',$text); //emphasize
$text = preg_replace('/\*\*(.+?)\*\*/s','\1',$text); //bold
$text = preg_replace('/\'\'(.+?)\'\'/s','\1
',$text); //code
$text = preg_replace('#<del>(.*?)</del>#is','\1',$text); //deleted
$text = preg_replace('/^(\s)*----+(\s*)$/m',"\n
\n",$text); //hr
//sub and superscript
$text = preg_replace('#<sub>(.*?)</sub>#is','\1',$text);
$text = preg_replace('#<sup>(.*?)</sup>#is','\1',$text);
//do quoting
$text = preg_replace("/\n((>)[^\n]*?\n)+/se","'\n'.quoteformat('\\0').'\n'",$text);
// Typography
if($conf['typography']){
$text = preg_replace('/([^-])--([^-])/s','\1–\2',$text); //endash
$text = preg_replace('/([^-])---([^-])/s','\1—\2',$text); //emdash
$text = preg_replace('/"([^\"]+?)"/s','“\1”',$text); //curly quotes
$text = preg_replace('/(\s)\'(\S)/m','\1‘\2',$text); //single open quote
$text = preg_replace('/(\S)\'/','\1’',$text); //single closing quote or apostroph
$text = preg_replace('/\.\.\./','\1…\2',$text); //ellipse
$text = preg_replace('/(\d+)x(\d+)/i','\1×\2',$text); //640x480
$text = preg_replace('/>>/i','»',$text); // >>
$text = preg_replace('/<</i','«',$text); // <<
$text = preg_replace('/<->/i','↔',$text); // <->
$text = preg_replace('/<-/i','←',$text); // <-
$text = preg_replace('/->/i','→',$text); // ->
$text = preg_replace('/<=>/i','⇔',$text); // <=>
$text = preg_replace('/<=/i','⇐',$text); // <=
$text = preg_replace('/=>/i','⇒',$text); // =>
$text = preg_replace('/\(c\)/i','©',$text); // copyrigtht
$text = preg_replace('/\(r\)/i','®',$text); // registered
$text = preg_replace('/\(tm\)/i','™',$text); // trademark
}
//forced linebreaks
$text = preg_replace('#\\\\\\\\(\s)#',"
\\1",$text);
// lists (blocks leftover after blockformat)
$text = preg_replace("/(\n( {2,}|\t)[\*\-][^\n]+)(\n( {2,}|\t)[^\n]*)*/se","\"\\n\".listformat('\\0')",$text);
// tables
$text = preg_replace("/\n(([\|\^][^\n]*?)+[\|\^] *\n)+/se","\"\\n\".tableformat('\\0')",$text);
// footnotes
$text = footnotes($text);
// run custom text replacements
$text = customs($text);
return $text;
}
/**
* Footnote formating
*
* @author Andreas Gohr
*/
function footnotes($text){
$num = 0;
while (preg_match('/\(\((.+?)\)\)/s',$text,$match)){
$num++;
$fn = $match[1];
$linkt = ''.$num.')';
$linkb = ''.$num.')';
$text = preg_replace('/ ?\(\((.+?)\)\)/s',$linkt,$text,1);
if($num == 1) $text .= '';
return $text;
}
/**
* Replace smileys with their graphic equivalents
*
* @author Andreas Gohr
*/
function smileys(&$table,&$text){
$smileys = file('conf/smileys.conf');
foreach($smileys as $smiley){
$smiley = preg_replace('/#.*$/','',$smiley); //ignore comments
$smiley = trim($smiley);
if(empty($smiley)) continue;
$sm = preg_split('/\s+/',$smiley,2);
$sm[1] = '
';
$sm[0] = preg_quote($sm[0],'/');
firstpass($table,$text,'/(\W)'.$sm[0].'(\W)/s',$sm[1],"\\1","\\2");
}
}
/**
* Add acronym tags to known acronyms
*
* @author Andreas Gohr
*/
function acronyms(&$table,&$text){
$acronyms = file('conf/acronyms.conf');
foreach($acronyms as $acro){
$acro = preg_replace('/#.*$/','',$acro); //ignore comments
$acro = trim($acro);
if(empty($acro)) continue;
list($ac,$desc) = preg_split('/\s+/',$acro,2);
$ac = preg_quote($ac,'/');
firstpass($table,$text,'/(\b)('.$ac.')(\b)/s',"\\2","\\1","\\3");
}
}
/**
* Apply custom text replacements
*
* @author Andreas Gohr
*/
function customs($text){
$reps = file ('conf/custom.conf');
foreach($reps as $rep){
//strip comments only outside a regexp
$rep = preg_replace('/#[^\/]*$/','',$rep); //ignore comments
$rep = trim($rep);
if(empty($rep)) continue;
if(preg_match('#^(/.+/\w*)\s+\'(.*)\'$#',$rep,$matches)){
$text = preg_replace($matches[1],$matches[2],$text);
}
}
return $text;
}
/**
* Replace regexp with token
*
* @author Andreas Gohr
*/
function firstpass(&$table,&$text,$regexp,$replace,$lpad='',$rpad=''){
//extended regexps have to be disabled for inserting the token
//and later reenabled when handling the actual code:
$ext='';
if(substr($regexp,-1) == 'e'){
$ext='e';
$regexp = substr($regexp,0,-1);
}
while(preg_match($regexp,$text,$matches)){
$token = mkToken();
$match = $matches[0];
$text = preg_replace($regexp,$lpad.$token.$rpad,$text,1);
$table[$token] = preg_replace($regexp.$ext,$replace,$match);
}
}
/**
* create a random and hopefully unique token
*
* @author Andreas Gohr
*/
function mkToken(){
return '~'.md5(uniqid(rand(), true)).'~';
}
/**
* Do quote blocks
*
* @author Andreas Gohr
*/
function quoteformat($block){
$block = trim($block);
$lines = split("\n",$block);
$lvl = 0;
$ret = "";
foreach ($lines as $line){
//remove '>' and count them
$cnt = 0;
while(substr($line,0,4) == '>'){
$line = substr($line,4);
$cnt++;
}
//compare to last level and open or close new divs if needed
if($cnt > $lvl){
$ret .= "
\n";
for ($i=0; $i< $cnt - $lvl; $i++){
$ret .= '';
}
$ret .= "\n
";
}elseif($cnt < $lvl){
$ret .= "\n
";
for ($i=0; $i< $lvl - $cnt; $i++){
$ret .= "
\n";
}
$ret .= "\n";
}elseif(empty($line)){
$ret .= "
\n";
}
//keep rest of line but trim left whitespaces
$ret .= ltrim($line)."\n";
//remember level
$lvl = $cnt;
}
//close remaining divs
$ret .= "
\n";
for ($i=0; $i< $lvl; $i++){
$ret .= "\n";
}
$ret .= "\n";
return "$ret";
}
/**
* format inline tables
*
* @author Andreas Gohr
* @author Aaron Evans
*/
function tableformat($block) {
$block = trim($block);
$lines = split("\n",$block);
$ret = "";
//build a row array
$rows = array();
for($r=0; $r < count($lines); $r++){
$line = $lines[$r];
//remove last seperator and trailing whitespace
$line = preg_replace('/[\|\^]\s*$/', '', $line);
$c = -1; //prepare colcounter)
for($chr=0; $chr < strlen($line); $chr++){
if($line[$chr] == '^'){
$c++;
$rows[$r][$c]['head'] = true;
$rows[$r][$c]['data'] = '';
}elseif($line[$chr] == '|'){
$c++;
$rows[$r][$c]['head'] = false;
$rows[$r][$c]['data'] = '';
}else{
$rows[$r][$c]['data'].= $line[$chr];
}
}
}
//build table
$ret .= "
\n\n";
for($r=0; $r < count($rows); $r++){
$ret .= " \n";
for ($c=0; $c < count($rows[$r]); $c++){
$cspan=1;
$data = $rows[$r][$c]['data'];
$head = $rows[$r][$c]['head'];
//join cells if next is empty
while($c < count($rows[$r])-1 && $rows[$r][$c+1]['data'] == ''){
$c++;
$cspan++;
}
if($cspan > 1){
$cspan = 'colspan="'.$cspan.'"';
}else{
$cspan = '';
}
//determine alignment from whitespace
if (preg_match('/^\s\s/', $data)) { // right indentation
$td_class = "rightalign";
if (preg_match('/\s\s$/', $data)) { // both left and right indentation
$td_class = "centeralign";
}
} else { // left indentation (default)
$td_class = "leftalign";
}
$data = trim($data);
if ($head) {
$ret .= " $data | \n"; // set css class for alignment
} else {
$ret .= " $data | \n"; // set css class for alignment
}
}
$ret .= "
\n";
}
$ret .= "
\n";
return $ret;
}
/**
* format lists
*
* @author Andreas Gohr
*/
function listformat($block){
//remove 1st newline
$block = substr($block,1);
//unescape
$block = str_replace('\\"','"',$block);
//dbg($block);
//walk line by line
$ret='';
$lst=0;
$lvl=0;
$enc=0;
$lines = split("\n",$block);
//build an item array
$cnt=0;
$items = array();
foreach ($lines as $line){
//get intendion level
$lvl = 0;
$lvl += floor(strspn($line,' ')/2);
$lvl += strspn($line,"\t");
//remove indents
$line = preg_replace('/^[ \t]+/','',$line);
//get type of list
(substr($line,0,1) == '-') ? $type='ol' : $type='ul';
// remove bullet and following spaces
$line = preg_replace('/^[*\-]\s*/','',$line);
//add item to the list
$items[$cnt]['level'] = $lvl;
$items[$cnt]['type'] = $type;
$items[$cnt]['text'] = $line;
//increase counter
$cnt++;
}
$level = 0;
$opens = array();
foreach ($items as $item){
if( $item['level'] > $level ){
//open new list
$ret .= "\n<".$item['type'].">\n";
array_push($opens,$item['type']);
}elseif( $item['level'] < $level ){
//close last item
$ret .= "\n";
for ($i=0; $i<($level - $item['level']); $i++){
//close higher lists
$ret .= ''.array_pop($opens).">\n\n";
}
}elseif($item['type'] != $opens[count($opens)-1]){
//close last list and open new
$ret .= ''.array_pop($opens).">\n\n";
$ret .= "\n<".$item['type'].">\n";
array_push($opens,$item['type']);
}else{
//close last item
$ret .= "\n";
}
//remember current level and type
$level = $item['level'];
//print item
$ret .= '';
$ret .= ''.$item['text'].'';
}
//close remaining items and lists
while ($open = array_pop($opens)){
$ret .= "\n";
$ret .= ''.$open.">\n";
}
return "
\n".$ret."\n";
}
/**
* Handle preformatted blocks
*
* Uses GeSHi for syntax highlighting
*
* @author Andreas Gohr
*/
function preformat($text,$type,$option=''){
global $conf;
global $lang;
//unescape
$text = str_replace('\\"','"',$text);
if($type == 'php' && !$conf['phpok']) $type='file';
if($type == 'html' && !$conf['htmlok']) $type='file';
switch ($type){
case 'php':
ob_start();
eval($text);
$text = ob_get_contents();
ob_end_clean();
break;
case 'html':
break;
case 'nowiki':
$text = htmlspecialchars($text);
break;
case 'file':
$text = htmlspecialchars($text);
$text = "
\n".$text."
\n";
break;
case 'code':
if(empty($option)){
$text = htmlspecialchars($text);
$text = '
'.$text.'
';
}else{
//strip leading blank line
$text = preg_replace('/^\s*?\n/','',$text);
//use geshi for highlighting
require_once("inc/geshi.php");
$geshi = new GeSHi($text, strtolower($option), "inc/geshi");
$geshi->set_encoding($lang['encoding']);
$geshi->enable_classes();
$geshi->set_header_type(GESHI_HEADER_PRE);
$geshi->set_overall_class('code');
$geshi->set_link_target($conf['target']['extern']);
$text = $geshi->parse_code();
}
$text = "\n".$text."\n";
break;
case 'block':
$text = substr($text,1); //remove 1st newline
$lines = split("\n",$text); //break into lines
$text = '';
foreach($lines as $line){
$text .= substr($line,2)."\n"; //remove indents
}
$text = htmlspecialchars($text);
$text = "
\n".$text."
\n";
break;
}
return $text;
}
/**
* Format embedded media (images)
*
* @author Andreas Gohr
*/
function mediaformat($text){
global $conf;
//unescape
$text = str_replace('\\"','"',$text);
// format RSS
if(substr($text,0,4) == 'rss>'){
return format_rss(substr($text,4));
}
//handle normal media stuff
$link = array();
$link['name'] = $text;
$link = format_link_media($link);
return format_link_build($link);
}
?>
/**
* Authentication library
*
* Including this file will automatically try to login
* a user by calling auth_login()
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr
*/
require_once("inc/common.php");
require_once("inc/io.php");
require_once("inc/blowfish.php");
require_once("inc/mail.php");
// load the the auth functions
require_once('inc/auth_'.$conf['authtype'].'.php');
// some ACL level defines
define('AUTH_NONE',0);
define('AUTH_READ',1);
define('AUTH_EDIT',2);
define('AUTH_CREATE',4);
define('AUTH_UPLOAD',8);
define('AUTH_GRANT',255);
if($conf['useacl']){
auth_login($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r']);
// load ACL into a global array
$AUTH_ACL = split("\n", $xoopsModuleConfig['authdata']); }
/**
* This tries to login the user based on the sent auth credentials
*
* The authentication works like this: if a username was given
* a new login is assumed and user/password are checked. If they
* are correct the password is encrypted with blowfish and stored
* together with the username in a cookie - the same info is stored
* in the session, too. Additonally a browserID is stored in the
* session.
*
* If no username was given the cookie is checked: if the username,
* crypted password and browserID match between session and cookie
* no further testing is done and the user is accepted
*
* If a cookie was found but no session info was availabe the
* blowish encrypted password from the cookie is decrypted and
* together with username rechecked by calling this function again.
*
* On a successful login $_SERVER[REMOTE_USER] and $USERINFO
* are set.
*
* @author Andreas Gohr
*
* @param string $user Username
* @param string $pass Cleartext Password
* @param bool $sticky Cookie should not expire
* @return bool true on successful auth
*/
function auth_login($user,$pass,$sticky=false){
global $USERINFO;
global $conf;
global $lang;
$sticky ? $sticky = true : $sticky = false; //sanity check
if(isset($user)){
//usual login
if (auth_checkPass($user,$pass)){
// make logininfo globally available
$_SERVER['REMOTE_USER'] = $user;
$USERINFO = auth_getUserData($user); //FIXME move all references to session
// set cookie
$pass = PMA_blowfish_encrypt($pass,auth_cookiesalt());
$cookie = base64_encode("$user|$sticky|$pass");
if($sticky) $time = time()+60*60*24*365; //one year
setcookie('DokuWikiAUTH',$cookie,$time);
// set session
$_SESSION[$conf['title']]['auth']['user'] = $user;
$_SESSION[$conf['title']]['auth']['pass'] = $pass;
$_SESSION[$conf['title']]['auth']['buid'] = auth_browseruid();
$_SESSION[$conf['title']]['auth']['info'] = $USERINFO;
return true;
}else{
//invalid credentials - log off
msg($lang['badlogin'],-1);
auth_logoff();
return false;
}
}else{
// read cookie information
$cookie = base64_decode($_COOKIE['DokuWikiAUTH']);
list($user,$sticky,$pass) = split('\|',$cookie,3);
// get session info
$session = $_SESSION[$conf['title']]['auth'];
if($user && $pass){
// we got a cookie - see if we can trust it
if(isset($session) &&
($session['user'] == $user) &&
($session['pass'] == $pass) && //still crypted
($session['buid'] == auth_browseruid()) ){
// he has session, cookie and browser right - let him in
$_SERVER['REMOTE_USER'] = $user;
$USERINFO = $session['info']; //FIXME move all references to session
return true;
}
// no we don't trust it yet - recheck pass
$pass = PMA_blowfish_decrypt($pass,auth_cookiesalt());
return auth_login($user,$pass,$sticky);
}
}
//just to be sure
auth_logoff();
return false;
}
/**
* Builds a pseudo UID from browserdata
*
* This is neither unique nor unfakable - still it adds some
* security
*
* @author Andreas Gohr
*
* @return string a MD5 sum of various browser headers
*/
function auth_browseruid(){
$uid = '';
$uid .= $_SERVER['HTTP_USER_AGENT'];
$uid .= $_SERVER['HTTP_ACCEPT_ENCODING'];
$uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$uid .= $_SERVER['HTTP_ACCEPT_CHARSET'];
return md5($uid);
}
/**
* Creates a random key to encrypt the password in cookies
*
* This function tries to read the password for encrypting
* cookies from $conf['datadir'].'/.cache/.htcookiesalt'
* if no such file is found a random key is created and
* and stored in this file.
*
* @author Andreas Gohr
*
* @return string
*/
function auth_cookiesalt(){
global $conf;
$file = $conf['datadir'].'/.cache/.htcookiesalt';
$salt = io_readFile($file);
if(empty($salt)){
$salt = uniqid(rand(),true);
io_saveFile($file,$salt);
}
return $salt;
}
/**
* This clears all authenticationdata and thus log the user
* off
*
* @author Andreas Gohr
*/
function auth_logoff(){
global $conf;
global $USERINFO;
unset($_SESSION[$conf['title']]['auth']['user']);
unset($_SESSION[$conf['title']]['auth']['pass']);
unset($_SESSION[$conf['title']]['auth']['info']);
unset($_SERVER['REMOTE_USER']);
$USERINFO=null; //FIXME
setcookie('DokuWikiAUTH','',time()-3600);
}
/**
* Convinience function for auth_aclcheck()
*
* This checks the permissions for the current user
*
* @author Andreas Gohr
*
* @param string $id page ID
* @return int permission level
*/
function auth_quickaclcheck($id){
global $conf;
global $USERINFO;
# if no ACL is used always return upload rights
# if no ACL is used always return upload rights
if(!$conf['useacl']) return AUTH_UPLOAD;
if ($GLOBALS['xoopsUser'])
return auth_aclcheck($id,$GLOBALS['xoopsUser']->uname(),$GLOBALS['xoopsUser']->groups());
else
return auth_aclcheck($id, null, null);
}
/**
* Returns the maximum rights a user has for
* the given ID or its namespace
*
* @author Andreas Gohr
*
* @param string $id page ID
* @param string $user Username
* @param array $groups Array of groups the user is in
* @return int permission level
*/
function auth_aclcheck($id,$user,$groups){
global $conf;
global $AUTH_ACL;
# if no ACL is used always return upload rights
if(!$conf['useacl']) return AUTH_UPLOAD;
$ns = getNS($id);
$perm = -1;
if($user){
// Getting the group names
$groupNames = array();
foreach ($groups as $group)
{
$xoopsGroup = $GLOBALS['member_handler']->getGroup($group);
$groupNames[] = $xoopsGroup->getVar('name');
}
$groups = $groupNames;
//prepend groups with @
for($i=0; $i $perm){
$perm = $acl[2];
}
}
if($perm > -1){
//we had a match - return it
return $perm;
}
}
//still here? do the namespace checks
if($ns){
$path = $ns.':\*';
}else{
$path = '\*'; //root document
}
do{
$matches = preg_grep('/^'.$path.'\s+('.$regexp.')\s+/',$AUTH_ACL);
if(count($matches)){
foreach($matches as $match){
$match = preg_replace('/#.*$/','',$match); //ignore comments
$acl = preg_split('/\s+/',$match);
if($acl[2] > $perm){
$perm = $acl[2];
}
}
//we had a match - return it
return $perm;
}
//get next higher namespace
$ns = getNS($ns);
if($path != '\*'){
$path = $ns.':\*';
if($path == ':\*') $path = '\*';
}else{
//we did this already
//looks like there is something wrong with the ACL
//break here
return $perm;
}
}while(1); //this should never loop endless
}
/**
* Create a pronouncable password
*
* @author Andreas Gohr
* @link http://www.phpbuilder.com/annotate/message.php3?id=1014451
*
* @return string pronouncable password
*/
function auth_pwgen(){
$pw = '';
$c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
$v = 'aeiou'; //vowels
$a = $c.$v; //both
//use two syllables...
for($i=0;$i < 2; $i++){
$pw .= $c[rand(0, strlen($c)-1)];
$pw .= $v[rand(0, strlen($v)-1)];
$pw .= $a[rand(0, strlen($a)-1)];
}
//... and add a nice number
$pw .= rand(10,99);
return $pw;
}
/**
* Sends a password to the given user
*
* @author Andreas Gohr
*
* @return bool true on success
*/
function auth_sendPassword($user,$password){
global $conf;
global $lang;
$hdrs = '';
$userinfo = auth_getUserData($user);
if(!$userinfo['mail']) return false;
$text = rawLocale('password');
$text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
$text = str_replace('@FULLNAME@',$userinfo['name'],$text);
$text = str_replace('@LOGIN@',$user,$text);
$text = str_replace('@PASSWORD@',$password,$text);
$text = str_replace('@TITLE@',$conf['title'],$text);
return mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
$lang['regpwmail'],
$text,
$conf['mailfrom']);
}
/**
* Register a new user
*
* This registers a new user - Data is read directly from $_POST
*
* @author Andreas Gohr
*
* @return bool true on success, false on any error
*/
function register(){
global $lang;
global $conf;
if(!$_POST['save']) return false;
if(!$conf['openregister']) return false;
//clean username
$_POST['login'] = preg_replace('/.*:/','',$_POST['login']);
$_POST['login'] = cleanID($_POST['login']);
//clean fullname and email
$_POST['fullname'] = trim(str_replace(':','',$_POST['fullname']));
$_POST['email'] = trim(str_replace(':','',$_POST['email']));
if( empty($_POST['login']) ||
empty($_POST['fullname']) ||
empty($_POST['email']) ){
msg($lang['regmissing'],-1);
return false;
}
//check mail
if(!mail_isvalid($_POST['email'])){
msg($lang['regbadmail'],-1);
return false;
}
//okay try to create the user
$pass = auth_createUser($_POST['login'],$_POST['fullname'],$_POST['email']);
if(empty($pass)){
msg($lang['reguexists'],-1);
return false;
}
//send him the password
if (auth_sendPassword($_POST['login'],$pass)){
msg($lang['regsuccess'],1);
return true;
}else{
msg($lang['regmailfail'],-1);
return false;
}
}
?>