Fix for compatibility with short_open_tag = Off
[lhc/web/wiklou.git] / includes / GlobalFunctions.php
index 1b99cee..c9cbe1b 100644 (file)
@@ -1,27 +1,38 @@
-<?
+<?php
 # Global functions used everywhere
 
 $wgNumberOfArticles = -1; # Unset
 $wgTotalViews = -1;
 $wgTotalEdits = -1;
 
-global $IP;
-include_once( "$IP/DatabaseFunctions.php" );
-include_once( "$IP/UpdateClasses.php" );
-include_once( "$IP/LogPage.php" );
-
-# PHP 4.1+ has array_key_exists, PHP 4.0.6 has key_exists instead, and earlier
-# versions of PHP have neither. So we roll our own. Note that this
-# function will return false even for keys that exist but whose associated 
-# value is NULL.
-#
-if ( phpversion() == "4.0.6" ) {
-       function array_key_exists( $k, $a ) {
-               return key_exists( $k, $a );
+include_once( "DatabaseFunctions.php" );
+include_once( "UpdateClasses.php" );
+include_once( "LogPage.php" );
+
+/*
+ * Compatibility functions
+ */
+
+# PHP <4.3.x is not actively supported; 4.1.x and 4.2.x might or might not work.
+# <4.1.x will not work, as we use a number of features introduced in 4.1.0
+# such as the new autoglobals.
+
+if( !function_exists('iconv') ) {
+       # iconv support is not in the default configuration and so may not be present.
+       # Assume will only ever use utf-8 and iso-8859-1.
+       # This will *not* work in all circumstances.
+       function iconv( $from, $to, $string ) {
+               if(strcasecmp( $from, $to ) == 0) return $string;
+               if(strcasecmp( $from, "utf-8" ) == 0) return utf8_decode( $string );
+               if(strcasecmp( $to, "utf-8" ) == 0) return utf8_encode( $string );
+               return $string;
        }
-} else if (phpversion() < "4.1") {
-       function array_key_exists( $k, $a ) {
-               return isset($a[$k]);
+}
+
+if( !function_exists('file_get_contents') ) {
+       # Exists in PHP 4.3.0+
+       function file_get_contents( $filename ) {
+               return implode( "", file( $filename ) );
        }
 }
 
@@ -32,7 +43,8 @@ function wfSeedRandom()
        global $wgRandomSeeded;
 
        if ( ! $wgRandomSeeded ) {
-               mt_srand( (double)microtime() * 1000000 );
+               $seed = hexdec(substr(md5(microtime()),-8)) & 0x7fffffff;
+               mt_srand( $seed );
                $wgRandomSeeded = true;
        }
 }
@@ -52,8 +64,10 @@ function wfLocalUrl( $a, $q = "" )
                }       
        } else if ( "" == $q ) {
                $a = str_replace( "$1", $a, $wgArticlePath );
-       } else {
+       } else if ($wgScript != '' ) {
                $a = "{$wgScript}?title={$a}&{$q}";     
+       } else { //XXX ugly hack for toplevel wikis
+               $a = "/{$a}&{$q}";      
        }
        return $a;
 }
@@ -63,11 +77,22 @@ function wfLocalUrlE( $a, $q = "" )
        return wfEscapeHTML( wfLocalUrl( $a, $q ) );
 }
 
+function wfFullUrl( $a, $q = "" ) {
+       global $wgServer;
+       return $wgServer . wfLocalUrl( $a, $q );
+}
+
+function wfFullUrlE( $a, $q = "" ) {
+       return wfEscapeHTML( wfFullUrl( $a, $q ) );
+}
+
 function wfImageUrl( $img )
 {
        global $wgUploadPath;
 
        $nt = Title::newFromText( $img );
+       if( !$nt ) return "";
+
        $name = $nt->getDBkey();
        $hash = md5( $name );
 
@@ -76,6 +101,47 @@ function wfImageUrl( $img )
        return wfUrlencode( $url );
 }
 
+function wfImagePath( $img )
+{
+       global $wgUploadDirectory;
+
+       $nt = Title::newFromText( $img );
+       if( !$nt ) return "";
+
+       $name = $nt->getDBkey();
+       $hash = md5( $name );
+
+       $path = "{$wgUploadDirectory}/" . $hash{0} . "/" .
+         substr( $hash, 0, 2 ) . "/{$name}";
+       return $path;
+}
+
+function wfThumbUrl( $img )
+{
+       global $wgUploadPath;
+
+       $nt = Title::newFromText( $img );
+       if( !$nt ) return "";
+
+       $name = $nt->getDBkey();
+       $hash = md5( $name );
+
+       $url = "{$wgUploadPath}/thumb/" . $hash{0} . "/" .
+         substr( $hash, 0, 2 ) . "/{$name}";
+       return wfUrlencode( $url );
+}
+
+
+function wfImageThumbUrl( $name, $subdir="thumb" )
+{
+       global $wgUploadPath;
+
+       $hash = md5( $name );
+       $url = "{$wgUploadPath}/{$subdir}/" . $hash{0} . "/" .
+         substr( $hash, 0, 2 ) . "/{$name}";
+       return wfUrlencode($url);
+}
+
 function wfImageArchiveUrl( $name )
 {
        global $wgUploadPath;
@@ -83,7 +149,7 @@ function wfImageArchiveUrl( $name )
        $hash = md5( substr( $name, 15) );
        $url = "{$wgUploadPath}/archive/" . $hash{0} . "/" .
          substr( $hash, 0, 2 ) . "/{$name}";
-       return $url;
+       return wfUrlencode($url);
 }
 
 function wfUrlencode ( $s )
@@ -120,40 +186,47 @@ function wfMungeToUtf8($string) {
 
 function wfDebug( $text, $logonly = false )
 {
-       global $wgOut, $wgDebugLogFile;
+       global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly;
 
-       if ( $logonly ) {
+       if ( $wgDebugComments && !$logonly ) {
                $wgOut->debug( $text );
        }
-       if ( "" != $wgDebugLogFile ) {
+       if ( "" != $wgDebugLogFile && !$wgProfileOnly ) {
                error_log( $text, 3, $wgDebugLogFile );
        }
 }
 
-if( !isset( $wgProfiling ) )
-       $wgProfiling = false;
-$wgProfileStack = array();
-$wgProfileWorkStack = array();
-
-if( $wgProfiling ) {
-       function wfProfileIn( $functionname )
-       {
-               global $wgProfileStack, $wgProfileWorkStack;
-               array_push( $wgProfileWorkStack, "$functionname " .
-                       count( $wgProfileWorkStack ) . " " . microtime() );
-       }
-
-       function wfProfileOut() {
-               global $wgProfileStack, $wgProfileWorkStack;
-               $bit = array_pop( $wgProfileWorkStack );
-               $bit .= " " . microtime();
-               array_push( $wgProfileStack, $bit );
+function logProfilingData()
+{
+       global $wgRequestTime, $wgDebugLogFile;
+       global $wgProfiling, $wgProfileStack, $wgProfileLimit, $wgUser;
+       list( $usec, $sec ) = explode( " ", microtime() );
+       $now = (float)$sec + (float)$usec;
+
+       list( $usec, $sec ) = explode( " ", $wgRequestTime );
+       $start = (float)$sec + (float)$usec;
+       $elapsed = $now - $start;
+       if ( "" != $wgDebugLogFile ) {
+               $prof = wfGetProfilingOutput( $start, $elapsed );
+               $forward = "";
+               if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
+                       $forward = " forwarded for " . $_SERVER['HTTP_X_FORWARDED_FOR'];
+               if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
+                       $forward .= " client IP " . $_SERVER['HTTP_CLIENT_IP'];
+               if( !empty( $_SERVER['HTTP_FROM'] ) )
+                       $forward .= " from " . $_SERVER['HTTP_FROM'];
+               if( $forward )
+                       $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
+               if($wgUser->getId() == 0)
+                       $forward .= " anon";
+               $log = sprintf( "%s\t%04.3f\t%s\n",
+                 gmdate( "YmdHis" ), $elapsed,
+                 urldecode( $_SERVER['REQUEST_URI'] . $forward ) );
+               error_log( $log . $prof, 3, $wgDebugLogFile );
        }
-} else {
-       function wfProfileIn( $functionname ) { }
-       function wfProfileOut( ) { }
 }
 
+
 function wfReadOnly()
 {
        global $wgReadOnlyFile;
@@ -163,21 +236,46 @@ function wfReadOnly()
 }
 
 $wgReplacementKeys = array( "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9" );
-function wfMsg( $key )
-{
-       global $wgLang, $wgReplacementKeys;
-       $ret = $wgLang->getMessage( $key );
-       
-       if( func_num_args() > 1 ) {
-               $reps = func_get_args();
-               array_shift( $reps );
-               $ret = str_replace( $wgReplacementKeys, $reps, $ret );
+
+# Get a message from anywhere
+function wfMsg( $key ) {
+       $args = func_get_args();
+       if ( count( $args ) ) {
+               array_shift( $args );
        }
+       return wfMsgReal( $key, $args, true );
+}
 
-       if ( "" == $ret ) {
-               user_error( "Couldn't find text for message \"{$key}\"." );
+# Get a message from the language file
+function wfMsgNoDB( $key ) {
+       $args = func_get_args();
+       if ( count( $args ) ) {
+               array_shift( $args );
        }
-       return $ret;
+       return wfMsgReal( $key, $args, false );
+}
+
+# Really get a message
+function wfMsgReal( $key, $args, $useDB ) {
+       global $wgReplacementKeys, $wgMessageCache, $wgLang;
+
+       $fname = "wfMsg";
+       wfProfileIn( $fname );
+       if ( $wgMessageCache ) {
+               $message = $wgMessageCache->get( $key, $useDB );
+       } elseif ( $wgLang ) {
+               $message = $wgLang->getMessage( $key );
+       } else {
+               wfDebug( "No language object when getting $key\n" );
+               $message = "&lt;$key&gt;";
+       }
+
+       # Replace arguments
+       if( count( $args ) ) {
+               $message = str_replace( $wgReplacementKeys, $args, $message );
+       }
+       wfProfileOut( $fname );
+       return $message;
 }
 
 function wfCleanFormFields( $fields )
@@ -244,23 +342,29 @@ function wfSpecialPage()
 {
        global $wgUser, $wgOut, $wgTitle, $wgLang;
 
+       /* FIXME: this list probably shouldn't be language-specific, per se */
        $validSP = $wgLang->getValidSpecialPages();
        $sysopSP = $wgLang->getSysopSpecialPages();
        $devSP = $wgLang->getDeveloperSpecialPages();
 
-       $wgOut->setArticleFlag( false );
+       $wgOut->setArticleRelated( false );
        $wgOut->setRobotpolicy( "noindex,follow" );
 
-       $t = $wgTitle->getDBkey();
+       $par = NULL;
+       list($t, $par) = split( "/", $wgTitle->getDBkey(), 2 );
+
        if ( array_key_exists( $t, $validSP ) ||
          ( $wgUser->isSysop() && array_key_exists( $t, $sysopSP ) ) ||
          ( $wgUser->isDeveloper() && array_key_exists( $t, $devSP ) ) ) {
+               if($par !== NULL)
+                       $wgTitle = Title::makeTitle( Namespace::getSpecial(), $t );
+
                $wgOut->setPageTitle( wfMsg( strtolower( $wgTitle->getText() ) ) );
 
                $inc = "Special" . $t . ".php";
                include_once( $inc );
                $call = "wfSpecial" . $t;
-               $call();
+               $call( $par );
        } else if ( array_key_exists( $t, $sysopSP ) ) {
                $wgOut->sysopRequired();
        } else if ( array_key_exists( $t, $devSP ) ) {
@@ -282,6 +386,27 @@ function wfGo( $s )
        $se->goResult();
 }
 
+# Just like exit() but makes a note of it.
+function wfAbruptExit(){
+       static $called = false;
+       if ( $called ){
+               exit();
+       }
+       $called = true;
+
+       if( function_exists( "debug_backtrace" ) ){ // PHP >= 4.3
+               $bt = debug_backtrace();
+               for($i = 0; $i < count($bt) ; $i++){
+                       $file = $bt[$i]["file"];
+                       $line = $bt[$i]["line"];
+                       wfDebug("WARNING: Abrupt exit in $file at line $line\n");
+               }
+       } else { 
+               wfDebug("WARNING: Abrupt exit\n");
+       }
+       exit();
+}
+
 function wfNumberOfArticles()
 {
        global $wgNumberOfArticles;
@@ -297,7 +422,7 @@ function wfNumberOfArticles()
 
        $sql = "SELECT ss_total_views, ss_total_edits, ss_good_articles " .
          "FROM site_stats WHERE ss_row_id=1";
-       $res = wfQuery( $sql, "wfLoadSiteStats" );
+       $res = wfQuery( $sql, DB_READ, "wfLoadSiteStats" );
 
        if ( 0 == wfNumRows( $res ) ) { return; }
        else {
@@ -347,13 +472,18 @@ function wfImageDir( $fname )
        return $dest;
 }
 
-function wfImageArchiveDir( $fname )
+function wfImageThumbDir( $fname , $subdir="thumb")
+{
+       return wfImageArchiveDir( $fname, $subdir );
+}
+
+function wfImageArchiveDir( $fname , $subdir="archive")
 {
        global $wgUploadDirectory;
 
        $hash = md5( $fname );
        $oldumask = umask(0);
-       $archive = "{$wgUploadDirectory}/archive";
+       $archive = "{$wgUploadDirectory}/{$subdir}";
        if ( ! is_dir( $archive ) ) { mkdir( $archive, 0777 ); }
        $archive .= "/" . $hash{0};
        if ( ! is_dir( $archive ) ) { mkdir( $archive, 0777 ); }
@@ -367,27 +497,42 @@ function wfImageArchiveDir( $fname )
 function wfRecordUpload( $name, $oldver, $size, $desc )
 {
        global $wgUser, $wgLang, $wgTitle, $wgOut, $wgDeferredUpdateList;
+       global $wgUseCopyrightUpload , $wpUploadCopyStatus , $wpUploadSource ;
+       
        $fname = "wfRecordUpload";
 
        $sql = "SELECT img_name,img_size,img_timestamp,img_description,img_user," .
          "img_user_text FROM image WHERE img_name='" . wfStrencode( $name ) . "'";
-       $res = wfQuery( $sql, $fname );
+       $res = wfQuery( $sql, DB_READ, $fname );
 
+       $now = wfTimestampNow();
+       $won = wfInvertTimestamp( $now );
+       $size = IntVal( $size );
+       
+       if ( $wgUseCopyrightUpload )
+         {
+           $textdesc = "== " . wfMsg ( "filedesc" ) . " ==\n" . $desc . "\n" .
+             "== " . wfMsg ( "filestatus" ) . " ==\n" . $wpUploadCopyStatus . "\n" .
+             "== " . wfMsg ( "filesource" ) . " ==\n" . $wpUploadSource ;
+         }
+       else $textdesc = $desc ;
+
+       $now = wfTimestampNow();
+       $won = wfInvertTimestamp( $now );
+       
        if ( 0 == wfNumRows( $res ) ) {
                $sql = "INSERT INTO image (img_name,img_size,img_timestamp," .
                  "img_description,img_user,img_user_text) VALUES ('" .
-                 wfStrencode( $name ) . "',{$size},'" . date( "YmdHis" ) . "','" .
+                 wfStrencode( $name ) . "',$size,'{$now}','" .
                  wfStrencode( $desc ) . "', '" . $wgUser->getID() .
                  "', '" . wfStrencode( $wgUser->getName() ) . "')";
-               wfQuery( $sql, $fname );
+               wfQuery( $sql, DB_WRITE, $fname );
 
                $sql = "SELECT cur_id,cur_text FROM cur WHERE cur_namespace=" .
                  Namespace::getImage() . " AND cur_title='" .
                  wfStrencode( $name ) . "'";
-               $res = wfQuery( $sql, $fname );
+               $res = wfQuery( $sql, DB_READ, $fname );
                if ( 0 == wfNumRows( $res ) ) {
-                       $now = wfTimestampNow();
-                       $won = wfInvertTimestamp( $now );
             $common =
                          Namespace::getImage() . ",'" .
                          wfStrencode( $name ) . "','" .
@@ -396,15 +541,15 @@ function wfRecordUpload( $name, $oldver, $size, $desc )
                          "',1";
                        $sql = "INSERT INTO cur (cur_namespace,cur_title," .
                          "cur_comment,cur_user,cur_user_text,cur_timestamp,cur_is_new," .
-                         "cur_text,inverse_timestamp) VALUES (" .
+                         "cur_text,inverse_timestamp,cur_touched) VALUES (" .
                          $common .
-                         ",'" . wfStrencode( $desc ) . "','{$won}')";
-                       wfQuery( $sql, $fname );
+                         ",'" . wfStrencode( $textdesc ) . "','{$won}','{$now}')";
+                       wfQuery( $sql, DB_WRITE, $fname );
                        $id = wfInsertId() or 0; # We should throw an error instead
                        $sql = "INSERT INTO recentchanges (rc_namespace,rc_title,
                                rc_comment,rc_user,rc_user_text,rc_timestamp,rc_new,
                                rc_cur_id,rc_cur_time) VALUES ({$common},{$id},'{$now}')";
-            wfQuery( $sql, $fname );
+            wfQuery( $sql, DB_WRITE, $fname );
                        $u = new SearchUpdate( $id, $name, $desc );
                        $u->doUpdate();
                }
@@ -419,22 +564,26 @@ function wfRecordUpload( $name, $oldver, $size, $desc )
                  wfStrencode( $s->img_description ) . "','" .
                  wfStrencode( $s->img_user ) . "','" .
                  wfStrencode( $s->img_user_text) . "')";
-               wfQuery( $sql, $fname );
+               wfQuery( $sql, DB_WRITE, $fname );
 
                $sql = "UPDATE image SET img_size={$size}," .
-                 "img_timestamp='" . date( "YmdHis" ) . "',img_user='" .
+                 "img_timestamp='" . wfTimestampNow() . "',img_user='" .
                  $wgUser->getID() . "',img_user_text='" .
                  wfStrencode( $wgUser->getName() ) . "', img_description='" .
                  wfStrencode( $desc ) . "' WHERE img_name='" .
                  wfStrencode( $name ) . "'";
-               wfQuery( $sql, $fname );
+               wfQuery( $sql, DB_WRITE, $fname );
+               
+               $sql = "UPDATE cur SET cur_touched='{$now}' WHERE cur_namespace=" .
+                 Namespace::getImage() . " AND cur_title='" .
+                 wfStrencode( $name ) . "'";
+               wfQuery( $sql, DB_WRITE, $fname );
        }
 
        $log = new LogPage( wfMsg( "uploadlogpage" ), wfMsg( "uploadlogpagetext" ) );
-       $da = str_replace( "$1", "[[:" . $wgLang->getNsText(
-         Namespace::getImage() ) . ":{$name}|{$name}]]",
-         wfMsg( "uploadedimage" ) );
-       $ta = str_replace( "$1", $name, wfMsg( "uploadedimage" ) );
+       $da = wfMsg( "uploadedimage", "[[:" . $wgLang->getNsText(
+         Namespace::getImage() ) . ":{$name}|{$name}]]" );
+       $ta = wfMsg( "uploadedimage", $name );
        $log->addEntry( $da, $desc, $ta );
 }
 
@@ -443,24 +592,19 @@ function wfRecordUpload( $name, $oldver, $size, $desc )
 
 function wfShowingResults( $offset, $limit )
 {
-       $top = str_replace( "$1", $limit, wfMsg( "showingresults" ) );
-       $top = str_replace( "$2", $offset+1, $top );
-       return $top;
+       return wfMsg( "showingresults", $limit, $offset+1 );
 }
 
 function wfShowingResultsNum( $offset, $limit, $num )
 {
-       $top = str_replace( "$1", $limit, wfMsg( "showingresultsnum" ) );
-       $top = str_replace( "$2", $offset+1, $top );
-       $top = str_replace( "$3", $num, $top );
-       return $top;
+       return wfMsg( "showingresultsnum", $limit, $offset+1, $num );
 }
 
-function wfViewPrevNext( $offset, $limit, $link, $query = "" )
+function wfViewPrevNext( $offset, $limit, $link, $query = "", $atend = false )
 {
        global $wgUser;
-       $prev = str_replace( "$1", $limit, wfMsg( "prevn" ) );
-       $next = str_replace( "$1", $limit, wfMsg( "nextn" ) );
+       $prev = wfMsg( "prevn", $limit );
+       $next = wfMsg( "nextn", $limit );
 
        $sk = $wgUser->getSkin();
        if ( 0 != $offset ) {
@@ -475,17 +619,18 @@ function wfViewPrevNext( $offset, $limit, $link, $query = "" )
        $q = "limit={$limit}&offset={$no}";
        if ( "" != $query ) { $q .= "&{$query}"; }
 
-       $nlink = "<a href=\"" . wfLocalUrlE( $link, $q ) . "\">{$next}</a>";
+       if ( $atend ) {
+               $nlink = $next;
+       } else {
+               $nlink = "<a href=\"" . wfLocalUrlE( $link, $q ) . "\">{$next}</a>";
+       }
        $nums = wfNumLink( $offset, 20, $link , $query ) . " | " .
          wfNumLink( $offset, 50, $link, $query ) . " | " .
          wfNumLink( $offset, 100, $link, $query ) . " | " .
          wfNumLink( $offset, 250, $link, $query ) . " | " .
          wfNumLink( $offset, 500, $link, $query );
 
-       $sl = str_replace( "$1", $plink, wfMsg( "viewprevnext" ) );
-       $sl = str_replace( "$2", $nlink, $sl );
-       $sl = str_replace( "$3", $nums, $sl );
-       return $sl;
+       return wfMsg( "viewprevnext", $plink, $nlink, $nums );
 }
 
 function wfNumLink( $offset, $limit, $link, $query = "" )
@@ -535,4 +680,159 @@ function wfCheckLimits( $deflimit = 50, $optionname = "rclimit" ) {
        return array( $limit, $offset );
 }
 
+# Escapes the given text so that it may be output using addWikiText() 
+# without any linking, formatting, etc. making its way through. This 
+# is achieved by substituting certain characters with HTML entities.
+# As required by the callers, <nowiki> is not used. It currently does
+# not filter out characters which have special meaning only at the
+# start of a line, such as "*".
+function wfEscapeWikiText( $text )
+{
+       $text = str_replace( 
+               array( '[',     '|',      "'",     'ISBN '    , '://'     , "\n=" ),
+               array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', '&#58;//' , "\n&#61;" ),
+               htmlspecialchars($text) );
+       return $text;
+}
+
+function wfQuotedPrintable( $string, $charset = "" ) 
+{
+       # Probably incomplete; see RFC 2045
+       if( empty( $charset ) ) {
+               global $wgInputEncoding;
+               $charset = $wgInputEncoding;
+       }
+       $charset = strtoupper( $charset );
+       $charset = str_replace( "ISO-8859", "ISO8859", $charset ); // ?
+
+       $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
+       $replace = $illegal . '\t ?_';
+       if( !preg_match( "/[$illegal]/", $string ) ) return $string;
+       $out = "=?$charset?Q?";
+       $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
+       $out .= "?=";
+       return $out;
+}
+
+# Changes the first character to an HTML entity
+function wfHtmlEscapeFirst( $text ) {
+       $ord = ord($text);
+       $newText = substr($text, 1);
+       return "&#$ord;$newText";
+}
+
+function wfSetVar( &$dest, $source )
+{
+       $temp = $dest;
+       $dest = $source;
+       return $temp;
+}
+
+function &wfSetRef( &$dest, $source )
+{
+       $temp =& $dest;
+       $dest =& $source;
+       return $temp;
+}
+
+# This function takes two arrays as input, and returns a CGI-style string, e.g.
+# "days=7&limit=100". Options in the first array override options in the second.
+# Options set to "" will not be output.
+function wfArrayToCGI( $array1, $array2 = NULL ) 
+{
+       if ( !is_null( $array2 ) ) {
+               $array1 = $array1 + $array2;
+       }
+
+       $cgi = "";
+       foreach ( $array1 as $key => $value ) {
+               if ( "" !== $value ) {
+                       if ( "" != $cgi ) {
+                               $cgi .= "&";
+                       }
+                       $cgi .= "{$key}={$value}";
+               }
+       }
+       return $cgi;
+}
+
+/* Purges a list of Squids defined in $wgSquidServers.
+$urlArr should contain the full URLs to purge as values 
+(example: $urlArr[] = 'http://my.host/something')
+XXX report broken Squids per mail or log */
+
+function wfPurgeSquidServers ($urlArr) {
+    global  $wgSquidServers;
+    $maxsocketspersquid = 8; //  socket cap per Squid
+    $urlspersocket = 400; // 400 seems to be a good tradeoff, opening a socket takes a while
+    $sockspersq =  ceil(count($urlArr) / $urlspersocket );
+    if ($sockspersq == 1) {
+       /* the most common case */
+        $urlspersocket = count($urlArr);
+    } else if ($sockspersq > $maxsocketspersquid ) {
+       $urlspersocket = ceil(count($urlArr) / $maxsocketspersquid);
+       $sockspersq = $maxsocketspersquid;
+    }
+    $totalsockets = count($wgSquidServers) * $sockspersq;
+    $sockets = Array();
+    
+    /* this sets up the sockets and tests the first socket for each server. */
+    for ($ss=0;$ss < count($wgSquidServers);$ss++) {
+        $failed = false;
+        $so = 0;
+        while ($so < $sockspersq && !$failed) {
+            if ($so == 0) {
+               /* first socket for this server, do the tests */
+                $socket = @fsockopen($wgSquidServers[$ss], 80, $error, $errstr, 3);
+                if (!$socket) {
+                    $failed = true;
+                    $totalsockets -= $sockspersq;
+                } else {
+                    @fputs($socket,"PURGE " . $urlArr[0] . " HTTP/1.0\r\n".
+                    "Connection: Keep-Alive\r\n\r\n");
+                   $res = @fread($socket,512);
+                   /* Squid only returns http headers with 200 or 404 status, 
+                   if there's more returned something's wrong */
+                   if (strlen($res) > 250) {
+                        fclose($socket);
+                        $failed = true;
+                        $totalsockets -= $sockspersq;
+                    } else {
+                        @stream_set_blocking($socket,false);
+                        $sockets[] = $socket;
+                    }
+                } 
+            } else {
+               /* open the remaining sockets for this server */
+                $sockets[] = @fsockopen($wgSquidServers[$ss], 80, $error, $errstr, 2);
+                @stream_set_blocking($sockets[$s],false);
+            }
+            $so++;
+        }
+    }
+    
+    if ($urlspersocket > 1) {
+        /* now do the heavy lifting. The fread() relies on Squid returning only the headers */
+        for ($r=0;$r < $urlspersocket;$r++) {
+            for ($s=0;$s < $totalsockets;$s++) {
+               if($r != 0) {
+                   $res = '';
+                   $esc = 0;
+                   while (strlen($res) < 100 && $esc < 20  ) {
+                       $res .= @fread($sockets[$s],512);
+                       $esc++;
+                   }
+               }
+                $urindex = $r + $urlspersocket * ($s - $sockspersq * floor($s / $sockspersq));
+                @fputs($sockets[$s],"PURGE " . $urlArr[$urindex] . " HTTP/1.0\r\n".
+                "Connection: Keep-Alive\r\n\r\n");
+            }
+        }
+    }
+
+    foreach ($sockets as $socket) {
+        @fclose($sockets);
+    }
+    return;
+}
 ?>