root > WikiSense-trunk > common > WikiSense.php

WikiSense.php

application/x-php, 50434 bytes (load raw)
<?php
   
if (@$argv[1]==='--test=WikiSense' && !@$_SERVER['REQUEST_URI'] && !defined('WS_WEB')) {
    define('WS_CONSOLE',1);
    define('WS_TEST_WIKISENSE',1);
    require_once('WSInit.php');
}

if (!defined('WS_WEB')) die('bad entry point!');

require_once("$wsgDatabaseIncludePath/LBFactory.php");
require_once("$wsgDatabaseIncludePath/LoadBalancer.php");

define('SPACE_CHARS','( |&nbsp;|&thinsp;|\pZ)');

function wsfLoadClass( $class ) {
    if ( class_exists( $class ) ) return $class;
   
    @include_once( "$class.php" );
   
    if ( class_exists( $class ) ) return $class;
   
    return NULL;
}


function wsfGetExpertClass( $base, $dbname, $domain, $family, $lang, $is_multilang ) {
    $class= NULL;

    if (!$class) $class = wsfLoadClass( "{$base}_".str_replace('.','_',$domain) );
    if (!$class) $class = wsfLoadClass( "{$base}_{$dbname}" );
    if (!$class && !$is_multilang) $class = wsfLoadClass( "{$base}_{$lang}_{$family}" );
    if (!$class && !$is_multilang) $class = wsfLoadClass( "{$base}_{$lang}" );
    if (!$class) $class = wsfLoadClass( "{$base}_{$family}" );
    if (!$class && $is_multilang) $class = wsfLoadClass( "{$base}_{$lang}" );
    if (!$class) $class= $base;
   
    return $class;
}

function wsfLoadExpertConfig( $base, $var, $dbname, $domain, $family, $lang, $is_multilang ) {
    $$var= array();
   
    #TODO: cache values, use include_once!
   
    wsfLog("loading expert config for {$base} - {$lang} - {$family} - {$dbname} - {$domain}",LL_DEBUG);

    @include( $base.'.php' );
    if ($is_multilang) @include( "{$base}_{$lang}.php" );
    @include( "{$base}_{$family}.php" );
    if (!$is_multilang) @include( "{$base}_{$lang}.php" );
    if (!$is_multilang) @include( "{$base}_{$lang}_{$family}.php" );
    @include( "{$base}_{$dbname}.php" );
    @include( "{$base}_".str_replace('.','_',$domain).'.php' );
   
    return $$var;
}

function wsfConfigureObject( &$obj, $conf, $all=false ) {
    if (!$conf) return;

    $vars= NULL;
    if (!$all) $vars= array_keys(get_object_vars($obj));
   
    wsfLog("configuring ".get_class($obj)." object with ".sizeof($conf)." settings",LL_DEBUG);
   
    foreach ($conf as $k => $v) {
        if ($all || in_array($k,$vars)) {
            $obj->$k= $v;
           
            #print "\n<br/>CONFIGURING: ".get_class($obj)."::$k => $v <br/>\n";
        }
        #else print "\n<br/>SKIPPING: ".get_class($obj)."::$k <br/>\n";
    }
}

function dbFailFunction( &$db, $error ) {
  print "<p class='error'><b>Database Error: ".escapeHtml($error)." on ".escapeHtml($db->getProperty('mServer'))."/".escapeHtml($db->getProperty('mDBname'))."</b></p>";
}

function & openConnection( $uri, $transactions = NULL, $usecache = true ) {
        global $wsgDatabaseIncludePath;

        static $concache = array();
        global $wsgTransactionMode;
        if (is_null($transactions)) $transactions= @$wsgTransactionMode;

        if ( is_string($uri) && preg_match( '!^%(.*)$!', $uri, $matches ) ) {
            $u= $GLOBALS['wsgDatabases'][$matches[1]];
           
            if (!$u) {
                trigger_error('unknown database ID: '.$matches[1], E_USER_WARNING);
                print_r(array_keys($GLOBALS['wsgDatabases']));
                return false;
            }

            $logu = preg_replace('!//(.*?:)?.*?@!','//\1************@', $u);
            wsfLog("resolved DB ID $uri to $logu", LL_TRACE);
           
            $uri= $u;
        }

        $cachekey = is_array($uri) ? serialize($uri) : $uri;
        if ($transactions) $cachekey .=  '|' . $transactions;

        if ( is_array($uri) ) {
                $a = $uri;
                unset($a['password']);
                $loguri = serialize($a);

                extract($uri);
        }
        else {
                $loguri = preg_replace('!//(.*?:)?.*?@!','//\1************@', $uri);
       
                //pseudo-url, jdbc-style: driver://[user[:password]@]host/database
                if ( !preg_match( '!^([\w\d]+):(//(([^/@:]+)(:([^/@:]+))?@)?([\w\d-.:]+)/)?([^;&\?]+)$!', $uri, $matches ) ) {
                        $dummy=false;
                        return $dummy;
                }
               
                $type= $matches[1];
                $user= $matches[4];
                $password= $matches[6];
                $host= $matches[7];
                $dbname= $matches[8];
        }
       
        if ($usecache && isset($concache[$cachekey])) {
               $db = $concache[$cachekey];
               $db->ping();
               if ($db->isOpen()) {
                       wsfLog("reusing connection to DB $loguri", LL_DEBUG);
                       return $concache[$cachekey];
               }
               else {
                       unset($concache[$cachekey]);
                       unset($db);
               }
       }
       

        # Get class for this database type
        $type = ucfirst( $type );
        if ($type == "Mysql") $type = ""; #HACK: DatabaseMysql.php seems to be gone...

        $class = 'Database' . $type;
        if ( !class_exists( $class ) ) {
                require_once( "$wsgDatabaseIncludePath/$class.php" );
        }
       
        #print "==> connecting to $dbname on $host using $class<br/>\n";
        wsfLog("connecting to DB $loguri", LL_DEBUG);
       
        # Create object
        $db= new $class( $host, $user, $password, $dbname, 'dbFailFunction', 0 );
        if ( !$db->isOpen() ) {
          $dummy= false;
          return $dummy;
        }
       
        if ($usecache) $concache[$cachekey] = $db;
        $db->setOutputPage( $dummy = NULL );
       
        if ($transactions !== NULL) {
            if ($transactions===true) $transactions= 'REPEATABLE READ';
            else if ($transactions===false) $transactions= 'READ UNCOMMITTED';
           
            $db->query("SET SESSION TRANSACTION ISOLATION LEVEL $transactions");
        }
       
        $db->ping();
       
        return $db;
}

function & getWikiListDB() {
  global $wsgWikiListDB, $wsgWikiListTable;
  global $wsgWikiDB;
  global $wsgWikiListDBConnection;
 
  if (!$wsgWikiListDB && !$wsgWikiListDBConnection) {
      wsfLog('no WikiList database defined!',LL_ERROR);
      wfDebugDieBacktrace('no WikiList database defined!');
  }
 
  if ( !$wsgWikiListDBConnection ) {
    $u= $wsgWikiListDB;
    if (strpos($u,':')===false && strpos($u, '%')!==0) $u= "$wsgWikiDB/$wsgWikiListDB";
    $wsgWikiListDBConnection=& openConnection($u);
   
    if (!$wsgWikiListDBConnection) {
      wsfLog('failed to connect to WikiList database ',LL_ERROR);
      wfDebugDieBacktrace('failed to connect to WikiList database ');
    }
  }
 
  return $wsgWikiListDBConnection;
}

function getWikiInfo( $where, $limit= NULL, $usable = true ) {
  global $wsgWikiListTable, $wsgBrokenWikis;
 
  $db=& getWikiListDB();
 
  $w= '';
  $w.= "WHERE ";
 
  if ($where) {
    $i= 0;
    foreach ($where as $c => $v) {
      $w.= " $c = ".$db->addQuotes($v)." AND ";
      $i+= 1;
    }
  }
 
  if ($usable) $w.= " is_closed = 0 AND \"domain\" is not NULL "; //NOTE: beware mysql's fuckup wrt quoting and federated tables

  $sql= 'SELECT * FROM '.$wsgWikiListTable.'
        '
.$w;
       
  if ($limit) $w.= " LIMIT $limit";

  $res= $db->query($sql, 'getWikiInfo');
 
  $wikis= array();
  while ($w= $db->fetchObject($res)) {
    @$w->baseURL= "http://{$w->domain}{$w->scipt_path}index.php";
    @$w->dbURL= getWikiDB($w);
   
    if (isset($w->is_sensitive)) $w->ucFirst= !$w->is_sensitive;
   
    if (!isset($w->ucFirst)) {
        if ($w->family === 'wiktionary') $w->ucFirst= false; #FIXME: ugly hack!
        else $w->ucFirst= true;
    }
   
    if (!isset($w->root_category)) $w->root_category= NULL;
   
    if (in_array($w->domain, $wsgBrokenWikis)) $w->is_broken= true;
    else if (!isset($w->is_broken)) $w->is_broken= false;
   
    $wikis[]= $w;
  }
 
  $db->freeResult($res);
 
  return $wikis;
}

function getWikiDB( $info ) {
        global $wsgWikiDB, $wsgWikiClusterDBs;

        if (isset($info->server) && isset($wsgWikiClusterDBs[$info->server])) {
                $u = $wsgWikiClusterDBs[$info->server]."/".$info->dbname;
        }
        else {
                $u = $wsgWikiDB ? "$wsgWikiDB/{$info->dbname}" : NULL;
        }

        #print "getWikiDB({$info->server}|{$info->dbname}): $u\n";
        return $u;
}

function compareWikiInfoByDomain( $v, $w ) {
  if ($v->domain == $w->domain) return 0;
  else if ($v->domain < $w->domain) return -1;
  else return 1;
}

function getWikiInfoFromLang( $lang, $family = NULL ) {
  global $wsgWikiListDB, $wsgBrokenWikis;
 
  $lang= preg_replace('/wiki$/','', $lang);
 
  if ($lang=='simple') $lang= 'en-simple';
 
  if ($lang=='commons' && (!$family || $family=='wikimedia')) {
    $lang= 'en';
    $family= 'commons';
  }
 
  if (!$family) $family= 'wikipedia';
 
  if (!$wsgWikiListDB) {
    $w= new stdClass();
   
    @$w->lang= $lang;
    @$w->family= $family;
   
    #print "FAMILY: $family; LANG: $lang\n";
   
    if ( $family=='commons' ) @$w->domain= "commons.wikimedia.org";
    else @$w->domain= "$lang.$family.org";
   
    if ( $family=='commons' ) @$w->is_multilang= true;
    else @$w->is_multilang= false;
   
    if ( $family=='wikipedia' ) @$w->dbname= "{$lang}wiki";
    else if ( $family=='commons' ) @$w->dbname= "{$family}wiki";
    else @$w->dbname= "$lang$family";
   
    @$w->script_path= "/w/";
    @$w->baseURL= "http://{$w->domain}{$w->scipt_path}index.php";
    @$w->dbURL= getWikiDB($w);
   
    if ($w->family === 'wiktionary') $w->ucFirst= false; #FIXME: ugly hack!
    else $w->ucFirst= true;
   
    $w->root_category= NULL;
   
    $w->is_sensitive= !$w->ucFirst;
   
    if (in_array($w->domain, $wsgBrokenWikis)) $w->is_broken= true;
    else $w->is_broken= false;

    $w->server= 1;
   
    return $w;
  }
  else {
    $info= getWikiInfo( array( 'lang' => $lang, 'family' => $family ), 1 );
    if (!$info) return false;
    else return $info[0];
  }
}

function getWikiInfoFromDomain( $domain ) {
  global $wsgWikiListDB;
 
  #compatibility hack
  if (strpos($domain,'.')===false) {
    return getWikiInfoFromLang($domain);
  }
 
  if (!$wsgWikiListDB) {
    $match= array();
    if (!preg_match('/^(\w+)\.(\w+)(\.\w+)?$/',$domain,$match)) {
        return getWikiInfoFromLang($match);
    }
   
    $w= getWikiInfoFromLang($match[1], $match[2]);
    @$w->domain= $domain;
    return $w;
  }
  else {
    if (!preg_match('/.*\.org$/',$domain)) $domain.= '.org';
   
    $info= getWikiInfo( array( 'domain' => $domain ), 1 );
       
    if (!$info) return false;
    else return $info[0];
  }
}

function getLargestWikis($n, $family = NULL) {
  global $wsgWikiListDB, $wsgWikiDB, $wsgWikiBlacklist;
 
  if (!$wsgWikiListDB) {
    global $wsgLanguages;
   
    if (!$n) $l= $wsgLanguages;
    else $l= array_slice($wsgLanguages,0,$n);
   
    $wikis= array();
    foreach ($l as $lang) {
      $info= getWikiInfoFromLang($l);
      $wikis[$info['domain']]= $info;
    }
   
    return $wikis;
  }
  else {
    global $wsgWikiListTable, $wsgBrokenWikis;
   
    $db=& getWikiListDB();
   
    $sql= "select * from $wsgWikiListTable";

    //NOTE: mysql's federated tables require domain to be quoted using '"'
    //      backticks (`) do *not* work. this is completely fucked up!
    $sql.= " where \"domain\" is not null";
    $sql.= " and is_closed = 0";
   
    if ($family) $sql.= " and family = ".$db->addQuotes($family);
    $sql.= " order by size desc limit " . (int)$n .";";
   
    $wikis= array();
    $res= $db->query($sql,'getLargestWikis');
   
    while ( $w= $db->fetchObject($res) ) {
        if (!$w->domain) continue;
       
        if ($wsgWikiBlacklist) {
          if (in_array($w->domain,$wsgWikiBlacklist)) continue;
          if (in_array($w->dbname,$wsgWikiBlacklist)) continue;
        }
       
        @$w->baseURL= "http://{$w->domain}{$w->script_path}index.php";
        @$w->dbURL= getWikiDB($w);
       
        if (isset($w->is_sensitive)) $w->ucFirst= !$w->is_sensitive;
   
        if (!isset($w->ucFirst)) {
            if ($w->family === 'wiktionary') $w->ucFirst= false; #FIXME: ugly hack!
            else $w->ucFirst= true;
        }
       
        if (in_array($w->domain, $wsgBrokenWikis)) $w->is_broken= true;
        else if (!isset($w->is_broken)) $w->is_broken= false;
       
        $wikis[$w->domain]= $w;
    }
   
    $db->freeResult($res);
   
    return $wikis;
  }
}

$wsgNamespaceCache= NULL;
$wsgNamespaceCacheReverse= NULL;
$wsgNamespaceCacheComplete= false;

function getCustomNsIndex( &$wiki, $ns, $slurp = false ) {
    global $wsgNamespaceCache, $wsgNamespaceCacheReverse;

    if ($wiki && is_object($wiki)) {
        $domain = $wiki->domain;
        $ns= str_replace(' ','_',$wiki->lc(trim($ns)));
    }
    else {
        $domain = $wiki;
        $ns= str_replace(' ','_',WikiAccess::lc(trim($ns)));
    }
   
    if ($ns===false || !$domain) $k= NULL; //just load
    else $k= "$domain#$ns";
   
    #print "KEY $k;\n";
    #print_r($wsgNamespaceCacheReverse);

    if ($k && isset($wsgNamespaceCacheReverse[$k]) && (!$slurp || $wsgNamespaceCacheComplete)) return $wsgNamespaceCacheReverse[$k];
   
    getCustomNsText($wiki,false,$slurp); //load
   
    if (!$k) return NULL; //slurp only
   
    if (!isset($wsgNamespaceCacheReverse[$k])) $wsgNamespaceCacheReverse[$k]= NULL;
   
    return $wsgNamespaceCacheReverse[$k];
}

function getCustomNsText( &$wiki, $ns, $slurp = false ) {
    global $wsgWikiListDB;
    global $wsgNamespaceCache, $wsgNamespaceCacheReverse;
    global $wsgNamespaceCacheComplete, $wsgCustomNamespaceTable;

    if ($wiki && is_object($wiki)) $domain = $wiki->domain;
    else $domain = $wiki;
   
    if ($ns===false || !$domain) $k= NULL; //just load
    else $k= "$domain#$ns";
   
    if ($wsgNamespaceCacheReverse==NULL) $wsgNamespaceCacheReverse= array();
    if ($wsgNamespaceCache==NULL) $wsgNamespaceCache= array();
   
    if (($k && isset($wsgNamespaceCache[$k])) && (!$slurp || $wsgNamespaceCacheComplete)) return @$wsgNamespaceCache[$k];

    if ($wsgWikiListDB && !$wsgNamespaceCacheComplete && $wsgCustomNamespaceTable) {
        $db=& getWikiListDB();
   
        $sql= "SELECT * from $wsgCustomNamespaceTable";
        if (!$slurp) $sql.= " WHERE domain = ".$db->addQuotes($domain);
   
        #print "LOADING NS FOR $domain: $sql<br>";

        $res= $db->query($sql,'getCustomNamespaceName');
        while ( $w= $db->fetchObject($res) ) {
            if (!$w->domain) continue;

            $nn = $w->ns_name;
            if ($wiki && is_object($wiki)) {
                $nn= str_replace(' ','_',$wiki->lc(trim($nn)));
            }
            else {
                $nn= str_replace(' ','_',WikiAccess::lc(trim($nn)));
            }
   
            $wsgNamespaceCache[$w->domain.'#'.$w->ns_id]= $w->ns_name;
            $wsgNamespaceCacheReverse[$w->domain.'#'.$nn]= $w->ns_id;
        }
       
        #print_r($wsgNamespaceCache);
        #print_r($wsgNamespaceCacheReverse);

        if ($slurp) $wsgNamespaceCacheComplete= true;
    }
   
    if (!$k) return NULL; //load only
   
    if (!isset($wsgNamespaceCache[$k])) $wsgNamespaceCache[$k]= $ns;
   
    return $wsgNamespaceCache[$k];
}

define('LL_MUTE', -100);
define('LL_SILENT', -4);
define('LL_QUIET', -2);
define('LL_NORMAL', 0);
define('LL_VERBOSE', 10);
define('LL_NOISY', 100);

define('LL_TRACE', LL_NOISY);
define('LL_DEBUG', LL_VERBOSE);
define('LL_INFO', LL_NORMAL);
define('LL_WARN', LL_QUIET);
define('LL_ERROR', LL_SILENT);

function wsfToString($data, $depth=-1, $exclude = NULL) {
  if ($data === NULL) $data= "<null>";
  else if ($data===false) $data= "<false>";
  else if ($data===true) $data= "<true>";
  else if (is_object($data)) $data= get_object_vars($data);
 
  if (is_array($data)) {
    if ($depth!=0) {
        $s= "{";
        foreach ( $data as $k => $v ) {
                if ($exclude!==NULL && $exclude!==false) {
                        if (preg_match($exclude, $k)) continue;
                }

                $s.= "$k => ".wsfToString($v, $depth > 0 ? $depth-1 : $depth, $exclude).", ";
        }
       
        $s.= "}";
        $data= $s;
    }
    else {
        $data= "array(".sizeof($data).")";
    }
  }
 
  return $data;
}

$wsgLogLastSupressed= NULL;
$wsgLogRepostTimestamp= NULL;
$wsgLogRepostThreashold= NULL;

$wsgLogLastTimestamp= NULL;
$wsgLogTimestampThreashold= NULL;

function wsfLog( $msg, $level = LL_NORMAL, $data='@dummy@' ) {
        global $wsgLogLevel, $wsgLogHashCounter, $wsgLogSuffix, $wsgLogTag;
        global $wsgLogLastSupressed, $wsgLogRepostThreashold, $wsgLogRepostTimestamp,
               $wsgLogLastTimestamp, $wsgLogTimestampThreashold;
       
        if ($wsgLogRepostThreashold) {
            $now= time();
            $t= $now - $wsgLogRepostTimestamp;
           
            if ($wsgLogRepostTimestamp && $t > $wsgLogRepostThreashold && $wsgLogLastSupressed) {
                if ( $wsgLogHashCounter > 0 ) {
                  print $wsgLogSuffix;
                  $wsgLogHashCounter = 0;
                }
               
                if ($wsgLogTag) print "<$wsgLogTag>";
                print " - LAST MESSAGE SUPRESSED $t seconds ago: $wsgLogLastSupressed";
                if ($wsgLogTag) print "</$wsgLogTag>";
                print "\n";
               
                if ($level>$wsgLogLevel) { #make sure the current message gets printed, too.
                    $msg= "NEXT MESSAGE FORCED: $msg";
                    $level= $wsgLogLevel;
                }
               
                $wsgLogLastSupressed= false;
            }
           
            $wsgLogRepostTimestamp= $now;
        }
       
        if ($level>$wsgLogLevel) {
          if ($wsgLogRepostThreashold) {
              if ($data!=='@dummy@') $msg.= " (".wsfToString($data).")";
              $wsgLogLastSupressed= $msg;
          }

          if ($level>$wsgLogLevel+2) {
            #print "(($level>$wsgLogLevel+2))";
            return;
          }
          else {
            if ( $wsgLogHashCounter == 0 ) {
              print "   ";
            }
           
            if ($level<LL_NORMAL) print "!";
            else print ".";
           
            $wsgLogHashCounter+= 1;
           
            if ( $wsgLogHashCounter > 50 ) {
              print $wsgLogSuffix;
              $wsgLogHashCounter = 0;
            }
          }
        }
        else {
          if ( $wsgLogHashCounter > 0 ) {
            print $wsgLogSuffix;
            $wsgLogHashCounter = 0;
          }
         
          if ($data!=='@dummy@') $msg.= " (".wsfToString($data).")";
         
          if ($wsgLogTag) print "<$wsgLogTag>";
         
          if ( $wsgLogTimestampThreashold ) {
            if (!isset($now)) $now= time();
            $t= $now - $wsgLogLastTimestamp;
           
            if ($t > $wsgLogTimestampThreashold) {
                if ($wsgLogTag) print "<$wsgLogTag>";
               
                if ($wsgLogLastTimestamp) $t= "($t sec)";
                else $t= '';
               
                print " - TIME$t: ".wfTimestamp(TS_DB, $now);
                print $wsgLogSuffix;
               
                $wsgLogLastTimestamp= $now;
            }
          }
         
          print " - $msg";
          if ($wsgLogTag) print "</$wsgLogTag>";
          print "\n";
         
          $wsgLogLastSupressed= false;
        }
       
        flush();
}

function wsfDBTitle( $title ) {
    $title= trim( $title );
    $title= urldecode( $title ); #HACKish
    $title= Sanitizer::decodeCharReferences( $title );
    $title= preg_replace('/\s+/','_',$title);
    $title= ucfirst($title); //FIXME: unicode! //FIXME: optional!
    return $title;
}

function & wsfStripIntKeys( &$array ) {

        if ( $array === NULL || $array===false ) return $array;
       
        if (!is_array($array)) trigger_error("not an array: $array", E_USER_WARNING);
       
        foreach ( $array as $k => $v ) {
                if (is_int($k)) unset($array[$k]);
        }
       
        return $array;
}

function stripMarkup( $text ) {
 

  #print "\n---> $text\n";
  $text= preg_replace("!\"|'''?|<[\\w\\d]+.*?/?>|</[\\w\\d]+>!",'',$text);
  $text= preg_replace('!^[\s]+|[\s]+$!','',$text); #FIXME: unicode whitespace
  if (strlen($text>2)) $text= preg_replace('!^[\s"\'\]\[<>,;.:?\{\}]+|[\s"\'\]\[<>,,;.:?\{\}]+$!','',$text); #FIXME: unicode punctuation
  $text= Sanitizer::decodeCharReferences( $text );
  #print "===> $text\n";
  #FIXME: strip punctuation
  return $text;
}

function stripHTML( $text ) {
  $text= preg_replace("!<[\\w\\d]+.*?/?>|</[\\w\\d]+>!",'',$text);
  $text= preg_replace("/<!--.*?-->/",'',$text);
  return $text;
}

function endsWith( $haystack, $needle ) {
  if (strlen($haystack)<strlen($needle)) return false;
  else {
    $x= substr($haystack, strlen($haystack) - strlen($needle));
   
    if ($x === $needle) return true;
    else return false;
  }
}

function escapeChunkCallback( $value, $prefix, &$store ) {
    static $i;
    if (!isset($i)) $i= 0;
   
    $i+= 1;
    $k= "@@$prefix:$i:".rand(1,1000)."@@";
    $store[$k]= $value;
    return $k;
}

function escapeChunks( $prefix, $preg, &$text, &$store ) {
    $code= 'escapeChunkCallback(\'\0\',$prefix,$store)';
   
    $text= preg_replace($preg.'e',$code,$text);
   
    return $text;
}

function unescapeChunks( &$text, &$store ) {
    $text= str_replace(array_keys($store),array_values($store),$text);
    return $text;
}

$wscHtmlSpecial = array( '<', '>', '\'', '"', '&' );
$wscHtmlEscaped = array( '&lt;', '&gt;', '&#039;', '$quot;', '&amp;');

function escapeHtml( $text, $mode = ENT_QUOTES ) {
        return htmlspecialchars($text, $mode);
}

class PseudoLoadBalancer {
  var $db;
  function PseudoLoadBalancer( &$db ) {
    $this->db=& $db;
  }
 
  function & getConnection( $dummy1, $dummy2, $dummy3 ) {
    return $this->db;
  }
}

class PseudoOut {
  function disable() {}
  function sendCacheControl() {}
  function debug() {}
}

#global $wgOut;
if (!isset($wgOut) || !$wgOut) {
    $wgOut= new PseudoOut();
}

function printRadioButton($name, $value, $current= NULL, $a= '') {
    $s= $value!=$current ? '' : " checked='checked' ";
    print "<input type='radio' name='$name' value='$value'$s$a/>";
}

function printCheckbox($name, $value, $a= '') {
    $s= $value ? " checked='checked' " : '';
    print "<input type='checkbox' name='$name' $s$a/>";
}

function printOption($label, $value, $current= NULL, $a= '') {
    #print "<!-- value: $value; current: $current; -->";
    $s= $value!=$current ? '' : " selected='selected' ";
    print "<option value='$value'$s$a>$label</option>";
}

function printSelector($name, $options, $current= NULL) {
    print "\n<select name='$name'>";
   
    foreach ($options as $v => $label) {
        printOption($label, $v, $current);
    }
   
    print "\n</select>\n";
}

function addURLParam(&$url, $key, $value, $html=true) {
    if (isset($value) && $value !== NULL && $value!==false && $value!=='') {
        $value= urlencode($value);
       
        if ($html) $url.= '&amp;';
        else $url.= '&';
       
        $url.= $key;
        $url.= '=';
        $url.= $value;
    }
     
    return $url;
}

function joinURLParams($fields, $html=true) {
    $f= '';
   
    foreach ($fields as $k => $v) {
        if ($f!=='') {
            if ($html) $f.= '&amp;';
            else $f.= '&';
        }
       
        $f.= urlencode($k);
        $f.= '=';
        $f.= urlencode($v);
    }
   
    return $f;
}

function wsfHackingInProgress() {
  ?>
      <p style="border:1px solid black; background-color:yellow; margin:1ex; padding: 1ex;">
      <b>WARNING: <i>Hacking in Progress!</i></b><br/>
      This tool is currently being modified and debugged. You can try to use it, but you may
      get unexpected or wrong results. Or it may simply be broken for a while. Please try again
      later if you need to use it for anything serious.
      </p>
     
  <?
}

function wsfSubtitle( $name ) {
  global $wsgBugTrackerURL, $wsgToolHelpBase, $wsgToolTalkBase;

  $help= $wsgToolHelpBase.urlencode($name);
  $talk= $wsgToolTalkBase.urlencode($name);
  $bugs= $wsgBugTrackerURL;
 
  ?>
  <p style='font-size:70%;'>
    <a href='<?=$help?>'><?= wsfWikiSenseMessage('help_page_label') ?></a>
    <!--|
    <a href='<?=$talk?>'><?= wsfWikiSenseMessage('talk_page_label') ?></a>-->
    |
    <a href='<?=$bugs?>'><?= wsfWikiSenseMessage('bug_page_label') ?></a>
  </p>
  <?   
}

function wsfHeader( ) {
  global $wsgHeader;
  if ($wsgHeader) {
    print "<div id='headerbox'>";
    include($wsgHeader);
    print "</div>";
  }
}

function wsfFooter( ) {
  global $wsgFooter;
  if ($wsgFooter) {
    print "<div id='footerbox'>";
    include($wsgFooter);
    print "</div>";
  }
}

function wsfCachePut($uri, $data) {
    global $wsgCacheDir;
    if (!$wsgCacheDir) return false;
    if (!file_exists($wsgCacheDir)) return false;
    if (!is_dir($wsgCacheDir)) return false;
    if (!is_writable($wsgCacheDir) || !is_readable($wsgCacheDir)) return false;

    $f= $wsgCacheDir.'/'.str_replace(array('/','&','+','*','?',';',':',' '),'_',$uri).'.cache';
   
    $ok= file_put_contents($f,$data);
    return $ok;
}

function wsfCacheGet($uri, $duration, $sendLastModified=false) {
    global $wsgCacheDir;
    if (!$wsgCacheDir) return false;
    if (!file_exists($wsgCacheDir)) return false;
    if (!is_dir($wsgCacheDir)) return false;
    if (!is_writable($wsgCacheDir) || !is_readable($wsgCacheDir)) return false;

    $f= $wsgCacheDir.'/'.str_replace(array('/','&','+','*','?',';',':',' '),'_',$uri).'.cache';
   
    if (!file_exists($f)) return NULL;
    if (!is_file($f)) return NULL;
    if (!is_readable($f)) return NULL;
   
    $time= filemtime($f);
    if (!$duration || ((time() - $time) > $duration) ) { //cache is stale
        unlink($f);
        return NULL;
    }
   
    $data= file_get_contents($f);
   
    if ($data !== NULL && $data!==false && $sendLastModified && !headers_sent()) {
        header('Last-Modified: '.wfTimestamp(TS_RFC2822,$time));
    }
   
    return $data;
}

function wsfCacheStart($uri, $duration) {
    if ($duration===false) return false;
   
    $data= wsfCacheGet($uri, $duration, true);
    if ($data===false) return false;
   
    if ($data !== NULL) {
        if (!headers_sent()) header('X-WS-Cached: yes');
        print $data;
        return true;
    }
   
    ob_start();
    return NULL;
}

function wsfCacheEnd($uri) {
    if (!ob_get_level()) return false;
   
    $data= ob_get_contents();
    ob_end_flush();
   
    wsfCachePut($uri, $data);
}

if (!function_exists('array_combine')) {
    function array_combine( $keys, $values ) {
        $keysValues = array();
        foreach($keys as $indexnum => $key) {
            $keyValues[$key] = $values[$indexnum];
        }
       
        return $keysValues;
    }
}

if (!defined('FILE_USE_INCLUDE_PATH')) define('FILE_USE_INCLUDE_PATH', 1);
if (!defined('FILE_APPEND')) define('FILE_APPEND', 8);
if (!defined('LOCK_EX')) define('LOCK_EX', 2);

if (!function_exists('file_put_contents')) {
    function file_put_contents($f, $data, $flags = 0) {
        if (is_bool($flags)) $append = $flags;
        else $append= ( ($flags & FILE_APPEND) == FILE_APPEND );
   
        $fh= fopen($f,$append?'a':'w');
        if (!$fh) return false;
       
        $ok= fwrite($fh,$data);
        fclose($fh);
       
        return $ok;
    }
}

global $wsgWikiSenseMessages;
$wsgWikiSenseMessages= NULL;

function wsfWikiSenseMessage($key) {
    global $wsgWikiSenseMessages, $wsgUserLang;
   
    if ($wsgUserLang && $wsgWikiSenseMessages && $wsgWikiSenseMessages->language != $wsgUserLang) {
        $wsgWikiSenseMessages= NULL;
    }
   
    if (!$wsgWikiSenseMessages) {
        if (!class_exists('WikiSenseLocalizer')) {
            require_once('WikiSenseLocalizer.php');
        }
       
        $wsgWikiSenseMessages= WikiSenseLocalizer::load( 'WikiSense', $wsgUserLang ? $wsgUserLang : 'en' );
    }
   
    $args= func_get_args();
    return call_user_func_array ( array(&$wsgWikiSenseMessages, 'msg'), $args );
}

function wsfGetDBId( $db ) {
        if (is_object($db)) {
                $name = $db->getProperty('mServer') . '+' . $db->getProperty('mDBname');
        }
        else {
                $name = preg_replace('!^.*://(.*?@)?(.*?)/(.*)$!', '\2+\3', $db);
        }

        return $name;
}

function wsfReadStatusInfo($file) {
        if (!$file) return false;
        if (!file_exists($file)) return false;

        #print "CHECK STATUS: $file; ";
        $fh = fopen($file, 'r');
        if (!$fh) return false;

        $st = array();
        while (true) {
                $s = fgets($fh);
                if ($s==='' || $s===NULL || $s===false) break;
               
                $s = trim($s);
                if ($s==='') continue;
                if ($s[0] === '#') continue;
               
                $ss = explode(';', $s, 2);

                $st['status'] = strtoupper(trim($ss[0]));
                $st['message'] = isset($ss[1]) ? trim($ss[1]) : '';
                break;
        }

        fclose($fh);
        if (!$st) return false;
        return $st;
}

function wsfGetReplag( $db = NULL, $statusfile = NULL ) {
    global $wsgReplagDB, $wsgReplagFile, $wsgReplagPattern;

    if (is_string($db) && (int)$db!=0) $db = (int)$db;
   
    if (is_int($db) && $wsgReplagDB) {
        if (is_array($wsgReplagDB)) {
                $db = $wsgReplagDB[$db];

                if (is_array($db)) {
                        $db = $db['db'];
                        if (!$statusfile) $statusfile = $db['status-file'];
                        #TODO: abort, etc
                }
        }
        else $db = $wsgReplagDB;
    }

    if (!$db) $db = $wsgReplagDB;

    if ($db) {
        if (is_array( $db )) {
                $lag = array();
                foreach ($db as $n => $a) {
                        if (is_array($a)) {
                                $url = $a['db'];
                                $abort = @$a['abort'];
                                if (!$statusfile) $statusfile = $a['status-file'];
                        } else {
                                $url = $a;
                                $abort = false;
                                if (!$statusfile) $statusfile = false;
                        }

                        if ($abort) { //explicitely broken
                                $lag[$n] = array('status' => 'ABORT', 'message' => $abort, 'lag' => false);
                        }
                        else {
                                $lag[$n] = wsfGetReplag($url, $statusfile);
                        }
                }
               
                return $lag;
        }

        $cachekey = 'replag-' . wsfGetDBId($db);
       
        $s = read_from_disk_cache($cachekey, 1);
        if ($s) {
                #print "[$cachekey] = $s; ";
                $lag = unserialize($s);
                if ($lag) {
                        $lag['cached'] = $cachekey;
                        return $lag;
                }
                else {
                        trigger_error("bad serialized data: '$s'", E_USER_WARNING);
                }
        }

        $lag = array(
                'status' => 'OK',
                'message' => '',
                'lag' => false,
        );

        if ($statusfile) {
                $st = wsfReadStatusInfo($statusfile);
                if ($st) {
                        $lag['statusfile'] = $statusfile;
                        $lag['status'] = $st['status'];
                        $lag['message'] = $st['message'];
                }
        }

        if ($lag['status']=='ERROR' || $lag['status']=='DOWN') {
                write_to_disk_cache($cachekey, serialize($lag));
                return $lag;
        }

        $t = NULL;

        if (is_object($db)) $dbo = $db;
        else $dbo = openConnection($db);

        if ($dbo) {
            $sql= 'SELECT time_to_sec(timediff(now(),rc_timestamp)) FROM recentchanges ORDER BY rc_timestamp DESC LIMIT 1;';
                $retry = 5;

            while (true) { //retry loop (ugly hack)
                $res= $dbo->query($sql, 'wsfGetReplag');
               
                $row = $dbo->fetchRow($res);
                #if (!is_object($db)) $dbo->close();

                if ($row) {
                        $t= (int)$row[0];
                        if ($t === -3020399) { //XXX: mysql date/time overflow
                                $retry -= 1;
                                if ($retry == 0) break; //give up

                                sleep(1);
                                continue; //try again (ugly hack).
                        }

                        break;
                }
                else break;
            }
        }
    }

    if ($t === NULL && $wsgReplagFile) {
       
        $t= file_get_contents($wsgReplagFile);
        if ($t) {
                $t= trim($t);
                if ($wsgReplagPattern) $t= eval($wsgReplagPattern);
        }
    }

    wsfSetReplagStatus($t, $lag);
   
    write_to_disk_cache($cachekey, serialize($lag));
    return $lag;
}

function wsfSetReplagStatus($t, &$lag) {
        $lag['lag'] = $t;

        if ($t === NULL || $t===false) { $lag['status'] = 'FAILED'; $lag['message'] = 'failed'; }
        else if ($t===-3020399) { $lag['status'] = 'FAILED'; $lag['message'] = 'bad lag value: '.$t; }
        else if ($t<-30) { $lag['status'] = 'FAILED'; $lag['message'] = 'bad lag value: '.$t; }
}

function wsfGetReplagHTML( $lagInfo = NULL, $label = NULL ) {
    global $wsgWikiClusterNames;

    if ($label === NULL) $label = wsfWikiSenseMessage('estimated_database_lag');

    $html = escapeHtml($label) . ': ';

    if ($lagInfo===NULL) $lagInfo= wsfGetReplag();
    else {
        if (is_string($lagInfo) && (int)$lagInfo!=0) $lagInfo = (int)$lagInfo;
        if (is_int($lagInfo)) $lagInfo = wsfGetReplag($lagInfo);
    }

    if ($lagInfo===NULL || $lagInfo===false) return '';
   
    if (is_array( $lagInfo ) && !isset( $lagInfo['lag'] )) {
        $s= '';
        $show= false;
        foreach ($lagInfo as $n => $v) {
                if (is_array($v) && !isset($v['lag'])) {
                        $db = $v['db'];
                        $label = $v['label'];
                } else {
                        if (is_int($n)) {
                                if (isset($wsgWikiClusterNames[$n])) $label = $wsgWikiClusterNames[$n];
                                else $label = "s$n";
                        }

                        $db = $v;
                }

                $lag = wsfGetReplagHTML($db, $label);
                if ($lag) {
                        if ($s!=='') $s.=' | ';
                        $s.= $lag;
                }
        }

        if ($s) return $html . $s;
        else return false;
       
    }
    else {
        if (is_array($lagInfo)) {
                //noop...
        } else {
                $t = $lagInfo;
                $lagInfo = array( 'lag' => $t, 'status' => 'OK', 'message' => '' );
                wsfSetReplagStatus($t, $lagInfo);
        }
    }
   
    extract($lagInfo); //$status, $message, $lag
    $time = '';
    $style = '';

    if ($status == 'OK' && $lag < 30) return false;

    if ($status=='ERRO' || $status=='DOWN' || $status=='ABORT' || $status=='FAILED')
        return '<span class="error">'.$html.' '.escapeHtml($message).'</span>';

    if ($lag==='0' || $lag===0 || $lag < 30) {
        $style= 'color:green;';
        $time = 'up to date';
    }
    else if ($lag===3020399) {
        $style= 'font-weight:bold; color:yellow; background-color:red;';
        $time = 'more than a month';
    }
    else {
        $s= $lag;
        $has = 0;
       
        $d= floor($s / (60*60*24));
        if ($d && $has < 2) {
                $s-= $d * 60*60*24;
                if ($time) $time.= ', ';
                $time.= "$d days";
                $has+= 1;
        }
       
        $h= floor($s / (60*60));
        if ($h && $has < 2) {
                $s-= $h * 60*60;
                if ($time) $time.= ', ';
                $time.= "$h hours";
                $has+= 1;
        }
       
        $m= floor($s / (60));
        if ($m && $has < 2) {
                $s-= $m * 60;
                if ($time) $time.= ', ';
                $time.= "$m minutes";
                $has+= 1;
        }
       
        if ($s && $has < 2) {
                if ($time) $time.= ', ';
                $time.= "$s seconds";
                $has+= 1;
        }
       
        if ($lag > 60 * 60 * 12) $style= 'font-weight:bold; color:yellow; background-color:red;';
        else if ($lag > 60 * 60) $style= 'color:black; background-color:red;';
        else if ($lag > 605) $style= 'color:black; background-color:orange;';
        else if ($lag > 601) $style= 'color:black; background-color:yellow;';
        else $style= 'color:green;';
    }
   
    $html = "<span style='$style'>$html $time</span>";
   
    return $html;
}

/*
function preg_replace_recursive($begin, $end, $sep, $mod, $replace, $s) {
    $s= preg_replace("$sep$begin((?>.*?)(?R))*.*?$end$sep$mod",'',$s);
    return $s;
}
*/


function preg_strip($exp, $s) {
    while (true) {
        $l= strlen($s);
        #print "--> $s \n";
        $s= preg_replace($exp,'',$s);
        if ($l==strlen($s)) break;
    }
   
    return $s;
}

function wsfMicroTime()
{
   list($usec, $sec) = explode(" ", microtime());
   return ((float)$usec + (float)$sec);
}

function wsfHash($i, $n, $c) {
    global $wsgLogLevel;
    if ($wsgLogLevel<LL_INFO) return;

    if ( (($i+1) % 50) === 0 ) print " (".($c/1024)."K)\n";
    print ".";
    flush();
}

function wsfCopy($src, $tgt, $chunksize = 0, $callback = NULL) {
    if (is_resource($src)) $sf= $src;
    else {
        $sf= fopen($src,'r'.($chunksize?'b':''));
        if (!$sf) return false;
    }
   
    if (is_resource($tgt)) $tf= $tgt;
    else {
        $tf= fopen($tgt,'w'.($chunksize?'b':''));
        if (!$tf) return false;
    }
   
    $c= 0;
    $i= 0;
    while (true) {
        if (!$chunksize) $data= fgets($sf);
        else {
           $data= '';
           $exp= $chunksize;
           
           #print "|";
           while ($exp>0) {
              $d= fread($sf, $exp);
              if ($d===false || $d===NULL || $d==='') break; #TODO: handle error ($data==false)
             
              $exp-= strlen($d);
              $data.= $d;
              #print "($exp)";
           }
        }
       
        if ($data===false || $data===NULL || $data==='') break; #TODO: handle error ($data==false)
       
        if (!$n= fwrite($tf,$data)) break; #FIXME: abort?! delete partial?
       
        fflush($tf);
       
        $c+= $n;
        $i+= 1;
       
        if ($callback) {
            if (is_string($callback)) $callback($i, $n, $c);
            else $callback->progress($i, $n, $c);
        }
    }
   
    if ($sf !== $src) fclose($sf);
    if ($tf !== $tgt) fclose($tf);
   
    return $c;
}

function wsfThrottle($name, $rate, $interval = 1) {
    global $wsgThrottleDir;
    $f= "$wsgThrottleDir/$name.throttle";
   
    if (file_exists($f)) {
        $fh= fopen($f, 'r+');
        flock ( $fh, LOCK_EX );
       
        $t= fgets($fh);
    }
    else {
        $fh= fopen($f, 'w');
        flock ( $fh, LOCK_EX );
       
        $t= false;
    }
   
    if ($t && preg_match('/(\d+)\s+(\d+)/', $t, $m)) {
        $t= (int)$m[1];
        $n= (int)$m[2];
       
        $d= time() - $t;
        if ($d < $interval) {
            if ($n>=$rate) {
                #print "seeping for ".($interval - $d)." sec\n";
                sleep($interval - $d);
           
                $t= time();
                $n= 1;
            }
            else {
                $n+= 1;
            }
        }
        else {
            #print "resetting throttle\n";
       
            $t= time();
            $n= 1;
        }
       
    }
    else {
        $t= time();
        $n= 1;
    }
   
    #print "setting counter to $n\n";
   
    fseek($fh, 0);
    ftruncate($fh, 0);
    fseek($fh, 0);
   
    fwrite($fh, "$t $n");
   
    flock ( $fh, LOCK_UN );
    fclose( $fh );
}

function levensteinDistance($a, $b, $normalize=false, $limit=0) {
    $d= array();
    $ca= is_array($a) ? sizeof($a) : mb_strlen($a);
    $cb= is_array($b) ? sizeof($b) : mb_strlen($b);
   
    if ($a === $b) return 0;
    if ($a==='' || $a===NULL || (is_array($a) && sizeof($a)===0)) return $cb;
    if ($b==='' || $b===NULL || (is_array($b) && sizeof($b)===0)) return $ca;
   
    $v= abs($ca-$cb);
    if ($normalize) $v= $v / (float)max($ca,$cb);
    if ($limit && $v > $limit) return $v;
   
    for ($i= 0; $i<=$ca; $i+= 1) $d[$i]= array( 0 => $i );
    for ($j= 0; $j<=$cb; $j+= 1) $d[0][$j]= $j;
   
    for ($i= 1; $i<=$ca; $i+= 1) {
   
        for ($j= 1; $j<=$cb; $j+= 1) {
            $ach= is_array($a) ? $a[$i-1] : mb_substr($a,$i-1,1);
            $bch= is_array($a) ? $a[$i-1] : mb_substr($b,$j-1,1);
           
            $cost= $ach == $bch ? 0 : 1;
           
            $d[$i][$j]= min(
                              $d[$i-1][$j  ] + 1, //deletion
                              $d[$i  ][$j-1] + 1, //insertion
                              $d[$i-1][$j-1] + $cost //substitution
                           );

            /*                           
            $v= $d[$i][$cb];
            if ($normalize) $v= $v / (float)max($ca,$cb);
           
            if ($limit && $v > $limit) return $v;
            */

            #FIXME: abort if hopeles... how?!
        }
    }
   
    if ($normalize) {
        $f= (float)$d[$ca][$cb] / (float)max($ca,$cb);
        return $f;
    }
    else return $d[$ca][$cb];
}

function isProcessAlive($pid) {
    unset($dummy);
    #sucks: exec("ps p ".(int)$pid, $dummy, $code);
    #if ($code) return false;
    #else return true;

    #maybe: posix_kill($pid, 0);

    return file_exists('/proc/'.$pid); //NOTE: UNIX ONLY!
}

global $wsgLogDBConnection, $wsgScriptLogId, $wsgScriptLogStart, $wsgScriptLogComment, $wsgScriptEndLogged, $wsgScriptEndLogging;

$wsgLogDBConnection= NULL;
$wsgScriptLogId= NULL;
$wsgScriptLogStart= NULL;
$wsgScriptLogComment= NULL;
$wsgScriptEndLogged = false;
$wsgScriptEndLogging = false;

function & wsfGetLogDB() {
  global $wsgLogDB, $wsgLogTable, $wsgLogDBConnection;
 
  if ( is_null($wsgLogDBConnection) ) {
      if (!$wsgLogDB) {
          $wsgLogDBConnection= false;
      }
      else {
          $wsgLogDBConnection=& openConnection($wsgLogDB);
         
          if (!$wsgLogDBConnection) {
            wsfLog('failed to connect to log database ',LL_ERROR);
            $wsgLogDBConnection= false;
          }
      }
  }
 
  return $wsgLogDBConnection;
}


function wsfScriptLogInsert( $fields = NULL ) {
    global $wsgLogTable;
   
    $db=& wsfGetLogDB();
    if (!$db) return false;
   
    if (!$fields) $fields= array();
    if (!isset($fields['timestamp'])) $fields['timestamp']= wfTimestamp(TS_MW);
    if (!isset($fields['status']))    $fields['status']=    'run';
   
    if (defined('WS_CONSOLE')) {
        global $argv, $args;
       
        $client= @$_SERVER['USER'];
        if (!$client) $client= @$_ENV['USER'];
        if (!$client) $client= @$_SERVER['LOGNAME'];
        if (!$client && isset($_SERVER['HOME'])) $client= basename($_SERVER['HOME']);
        if (!$client) $client= '-/-';
       
        if (!isset($fields['script']))    $fields['script']=    $_SERVER['SCRIPT_NAME'];
        if (!isset($fields['params']))    $fields['params']=    wsfToString(array_slice($_SERVER['argv'],1));
        if (!isset($fields['client']))    $fields['client']=    $client;
    }
    else {
        if (!isset($fields['script']))    $fields['script']=    $_SERVER['PHP_SELF'];
        if (!isset($fields['params']))    $fields['params']=    wsfToString($_GET);
        if (!isset($fields['client']))    $fields['client']=    $_SERVER['REMOTE_ADDR'];
    }
   
    $names= '';
    $values= '';
    foreach ($fields as $k => $v) {
        if ($names!=='') $names.= ', ';
        if ($values!=='') $values.= ', ';
       
        $names.= "`$k`";
        if (is_null($v)) $values.= 'NULL';
        else if (is_int($v)) $values.= (int)$v;
        else if (is_float($v)) $values.= (float)$v;
        else if (is_bool($v)) $values.= $v ? '1' : '0';
        else $values.= $db->addQuotes($v);
    }
   
    $sql= "INSERT INTO $wsgLogTable ( $names ) VALUES ( $values )";
   
    $db->query($sql, 'wsfScriptLogInsert');
    $id= $db->insertId();
   
    if (!$id) {
        wsfLog('failed to insert log row (' . $db->lastError() . ')', LL_WARN);
    }

    return $id;
}

function wsfScriptLogUpdate( $id, $fields = NULL ) {
    global $wsgLogTable;
   
    if (!$fields) return;
   
    $db=& wsfGetLogDB();
    if (!$db) return false;
   
    $set= '';
    foreach ($fields as $k => $v) {
        if ($set!=='') $set.= ', ';
       
        $set.= "`$k` = ";
        if (is_null($v)) $set.= 'NULL';
        else if (is_int($v)) $set.= (int)$v;
        else if (is_float($v)) $set.= (float)$v;
        else if (is_bool($v)) $set.= $v ? '1' : '0';
        else $set.= $db->addQuotes($v);
    }
   
    $sql= "UPDATE $wsgLogTable SET $set WHERE id = ".(int)$id;
   
    $db->query($sql, 'wsfScriptLogUpdate');
}

function wsfLogScriptStart() {
    global $wsgLogDB, $wsgScriptLogId, $wsgScriptLogStart;
   
    if (!$wsgLogDB) return;
   
    if ($wsgScriptLogId) {
        wsfLog('script start already logged - skipping ',LL_WARN);
        return false;
    }
   
    $data= array(
        'status' => 'started',
    );
   
    $wsgScriptLogStart= time();
    $wsgScriptLogId= wsfScriptLogInsert($data);
   
    if (!$wsgScriptLogId) {
        wsfLog('failed to log script start!',LL_WARN);
        return false;
    }
   
    return $wsgScriptLogId;
}

function wsfLogScriptComment( $comment ) {
    global $wsgScriptLogComment;
   
    if ($wsgScriptLogComment) $wsgScriptLogComment.= '; '.$comment;
    else $wsgScriptLogComment= $comment;
}

function wsfLogScriptEnd( $status='done', $comment = NULL ) {
    global $wsgLogDB, $wsgScriptLogId, $wsgScriptLogStart, $wsgScriptLogComment, $wsgScriptEndLogged, $wsgScriptEndLogging;

    if ($wsgScriptEndLogged && $status=="done") return; //end already logged
   
    if ($comment) wsfLogScriptComment( $comment );
    if (function_exists("getmypid")) wsfLogScriptComment( "PID: " . getmypid() );
    if (function_exists("memory_get_usage")) wsfLogScriptComment( "MEM USE: " . memory_get_usage(true) );
    if (function_exists("memory_get_peak_usage")) wsfLogScriptComment( "MEM PEEK: " . memory_get_peak_usage(true) );

    if (!$wsgLogDB) return;
   
    if ($wsgScriptEndLogging) return; //avoid recursion. NOTE: we may lose info here!
    $wsgScriptEndLogging = true;

    try {
        if (!$wsgScriptLogId) {
                wsfLog('script start not logged! logging end anyway.',LL_WARN);
                return false;
        }
       
        $data= array(
                'status' => $status,
                'time' => $wsgScriptLogStart ? (time() - $wsgScriptLogStart) : NULL,
                'comment' => $wsgScriptLogComment,
        );
   
        if ($wsgScriptLogId) wsfScriptLogUpdate($wsgScriptLogId, $data);
        else wsfScriptLogInsert($data);
       
        global $wsgLogDBConnection;
        if ($wsgLogDBConnection) $wsgLogDBConnection->close();
        $wsgLogDBConnection= false;
       
        $wsgScriptEndLogged = true;
    }
    catch (Exception $ex) {
        $wsgScriptEndLogging = false;
        throw $ex;
    }

    $wsgScriptEndLogging = false;
}

function wsfGetWikiPassword($domain, $user) {
    global $wsgWikiPasswordFile;
   
    if (!$wsgWikiPasswordFile) return NULL;

    $auth= file($wsgWikiPasswordFile);
   
    if ($auth) {
        foreach ($auth as $a)  {
            if (!preg_match('/^(.*?):(.*?):(.*)$/',$a,$m)) continue;

            if ($m[1]==$domain && $m[2]==$user) {
                return $m[3];
            }
        }
    }
   
    return NULL;
}

function wsfGetWikiUsers($domain) {
    global $wsgWikiPasswordFile;
   
    if (!$wsgWikiPasswordFile) return NULL;

    $auth= file($wsgWikiPasswordFile);
   
    $users= array();
    if ($auth) {
        foreach ($auth as $a)  {
            if (!preg_match('/^(.*?):(.*?):(.*)$/',$a,$m)) continue;

            if ($m[1]==$domain) $users[]= $m[2];           
        }
    }
   
    return $users;
}

function urlencodeTitle($title) {
    return str_replace(
        array( '%3A', '%2F', '+' ),
        array( ':', '/', '_' ),
        urlencode( $title )
    );
}

function httpError($code, $name, $message) {
    while (ob_get_level()) ob_end_clean();
   
    $msg= "$code $name: " . escapeHtml($message);
   
    if (headers_sent()) {
        print("<p class='error'>$code $name: " . escapeHtml($message)."</p>");
       
        trigger_error("headers already sent!", E_USER_ERROR);
       
        die("$msg<br/>(headers already sent!)");
       
        return;
    }

    header("Status: $code $name", true, $code);
    header("Content-Type: text/html; charset=utf-8", true, $code);
   
    print "<html>
      <head>
          <title>$code $name</title>
      </head>
     
      <body>
          <h1>$code $name</h1>
          <p>"
.escapeHtml($message)."</p>
      </body>
    </html>"
;
   
    exit();
}

define('WS_FLOCK_LAZYNESS', 3);
define('WS_FLOCK_TIMEOUT', 60*4);

function wsfAquireFileLock($file) {
        //FIXME: this function is one big race condition :(

        $lock = "$file.lock";
        $t= time();
        while (file_exists($lock)) {
                $pid = file_get_contents($lock);
               
                if (!$pid || !($pid = (int)trim($pid))) {
                        wsfLog("encountered broken lock file $lock.", LL_INFO);
                        break; #NOTE: not quite safe, but somethign went wrong anyway.
                }

                if ( !isProcessAlive( $pid ) ) {
                        wsfLog("found lock file $lock owned by DEAD process $pid.", LL_DEBUG);
                        break;
                }

                $usecs = WS_FLOCK_LAZYNESS * 1000000;
                $usecs += mt_rand(1, 1000000); //avoid collisions

                wsfLog("waiting on $lock for process $pid: usleep( $usecs ).", LL_TRACE);
                usleep( $usecs );
               
                $d= time() - $t;
                if ( $d > WS_FLOCK_TIMEOUT ) { #simply die if the other process fails to terminate
                        trigger_error("timout while waiting on lock file $lock for process $pid",E_USER_ERROR);
                        throw new Exception("oops"); //can't happen
                }
        }

        #WARNING: not quite safe, RACE CONDITION!
        $ok = file_put_contents($lock, getmypid());
        if ($ok) wsfLog("created lock file $lock.", LL_DEBUG);
        else wsfLog("failed to create lock file $lock.", LL_WARN);

        return $ok;
}

function wsfReleaseFileLock($file) {
        $lock = "$file.lock";

        if (!file_exists($lock)) {
                wsfLog("missing lock file $lock.", LL_INFO);
                return false;
        }

        $pid = file_get_contents($lock);

        if (!$pid || !($pid = (int)trim($pid))) {
                wsfLog("encountered broken lock file $lock.", LL_INFO);
                return false;
        }

        $mypid = getmypid();

        if ($pid != $mypid) {
                wsfLog("lock file $lock owned by someone else! expected $mypid, found $pid.", LL_WARN);
                return false;
        }


        $ok = unlink($lock);
        if ($ok) wsfLog("removed lock file $lock.", LL_DEBUG);
        else wsfLog("failed to remove lock file $lock.", LL_WARN);

        return $ok;
}

function fetch_via_disk_cache($u, $maxage_minutes, $slurper = NULL) {
        $f = 'cache-' . urlencode($u);
        if ($slurper) $f .= '%#%' . urlencode($slurper);
        $f .= '.tmp';
        $f = $GLOBALS['wsgPersistentStateDir'] . '/' . $f;

        $oldabort = ignore_user_abort( true );
        wsfAquireFileLock($f);

        $txt = NULL;
        $mintime = time() - $maxage_minutes * 60;
        if (file_exists($f)) {
                $modtime = filemtime($f);

                #print " - mintime: $mintime; modtime: $modtime; - ";
                if ($modtime > $mintime) {
                        wsfLog("using data from cache file $f.", LL_DEBUG);
                        $txt = file_get_contents($f);
                }
        }

        if ($txt===NULL || $txt===false) {
                wsfLog("fetching fresh data from $u.", LL_DEBUG);
                if ($slurper) $txt = $slurper($u);
                else $txt = file_get_contents($u);

                if ($txt!==false) {
                        file_put_contents($f, $txt);
                        wsfLog("updated cache file $f.", LL_DEBUG);
                }
        }

        wsfReleaseFileLock($f);
        ignore_user_abort( $oldabort );
        return $txt;
}

function read_from_disk_cache($name, $maxage_minutes) {
        $f = $GLOBALS['wsgPersistentStateDir'] . '/' . urlencode($name) . ".tmp";

        $oldabort = ignore_user_abort( true );
        wsfAquireFileLock($f);

        $txt = NULL;
        $mintime = time() - $maxage_minutes * 60;
        if (file_exists($f)) {
                $modtime = filemtime($f);

                if ($modtime > $mintime) {
                        wsfLog("getting data from cache file $f.", LL_DEBUG);
                        $txt = file_get_contents($f);
                }
        }

        wsfReleaseFileLock($f);
        ignore_user_abort( $oldabort );
        return $txt;
}

function write_to_disk_cache($name, $data) {
        $f = $GLOBALS['wsgPersistentStateDir'] . '/' . urlencode($name) . ".tmp";

        $oldabort = ignore_user_abort( true );
        wsfAquireFileLock($f);

        $ok = file_put_contents($f, $data);

        wsfReleaseFileLock($f);
        ignore_user_abort( $oldabort );
        return $ok;
}

if (defined('WS_TEST_WIKISENSE')) {
        print "getting lock\n";
        wsfAquireFileLock('/tmptmp/locktest');
        print "got lock\n";

        file_get_contents("php://stdin");

        print "releasing lock\n";
        wsfReleaseFileLock('/tmptmp/locktest');
        print "released lock\n";
}

?>