<?php

/* Q3 WebRCON : web-based rcon l33t interface. Yes.
 * http://FrenchFragFactory.net/ressources/webrcon/
 * Ozh - ozh@planetozh.com - ozh on #sarl @ Quakenet
 *
 * Version 1.1
 */


/*******************************************************************************************************
 * CUSTOMISE BELOW THIS LINE
 */


/* If, for some weird reason, the script doesn't work, or $_PHP['SELF'] has no value on your server,
 * give the following variable, as a value, the virtual path of this file.
 * Example : http://www.yoursite.com/yourclan/l33t/webrcon.php -> $this_script = "/yourclan/l33t/webrcon.php";
 * If you don't know about $_PHP['SELF'], upload a blank page with the following line, and check it out in your browser
 *                  <?php phpinfo(); ?>                                                                                                         */
$this_script = $_PHP['SELF']; 


/* Do you want the player datas (ip, nick, score) to be sortable ?
 * This is r0x0r javascript high lvl pv me, but may work only with MSIE .. :)
 * Set to 1 to use, set to 0 to display regular tables          */
$javascript = 1; 


/* Define your servers.
 *
 * "ip" and "port" : well, pretty obvious. Mandatory.
 * "pass" : the g_password needed for players to join the server, if any. Optional.
 * "rcon" : the rconpassword for admin commands. Mandatory.
 * "cfg" : optional custom config files for each servers.
 *   Can be a list (array of cfg), a single cfg, or nothing
 *   "cfg" => array ("league.cfg", "ctf.cfg", "tdm.cfg")
 *   "cfg" => "justone.cfg"
 *   "cfg" => ""                                                                       */
$servers = array (
    "w00t" => array (
        "ip" => "10.11.12.13",
        "port" => 27960 ,
        "pass" => "w00t",
        "rcon" => "l33t",
        "cfg" => array ("sarl.cfg", "ctf.cfg", "test.cfg")
    ),

    "server2" => array (
        "ip" => "110.110.110.101",
        "port" => 27960 ,
        "pass" => "kikoo",
        "rcon" => "faggot",
        "cfg" => "server.cfg"
    ),

    "server3" => array (
        "ip" => "201.202.203.204",
        "pass" => "ctb",
        "port" => 27961 ,
        "rcon" => "gnigni",
    ),

);


/* Define your default RCON Commands :
 * enclosed by quotes, separated by a comma, between parenthesis, with a trailing semicolon. Ok ? :) */
$rcon_buttons = array (
    "say tg n00b",
    "kick all",
    "kick allbot",
    "map_restart",
    "listip",
    "g_gravity 10",
    "g_gravity 800"
    ); 


/* Define your CSS style between the line <<<YOURCSS and YOURCSS; */
$yourcss = <<<YOURCSS
<style>
body    {
    BackGround : black;
    Left : 0px;
    Top : 0px;
    Margin-Left : 20px;
    Margin-Right : 20px;
    Margin-Top : 0px;
    Margin-Bottom : 0px;
    Font-Family : Verdana, Arial;
    font-size: 10pt;
    Color : #A0A0A0;
}

.titrepage{
    font-Family : Verdana, Arial;
    font-size: 15pt;
    margin: 15;
    color: #A7A2E2;
    Font-Weight : bold;
    Text-Align : center;
}

td,p    {Font-Family : Verdana, Arial;
    Margin : 10px;
    font-size: 9pt;
    Color : #B0B0C0;
}

#maintable, #maintable td {
    border:1px solid #252548;
}

#maintable td:hover {
    background: #101015;
}

#serverinfo, #serverinfo td {
    border:0px solid white;
}

#serverinfo td:hover, {
    color: white;
    background: #606090;
}

input, select {
    color : #303060;
    background: #A7A2E2;
    font: normal 11px Verdana, Arial;
    border-color : #A7A2E2;
    margin: 1px 1px 1px 1px ;
}

input:hover, select:hover {
    background: #909090;
}

.odd {
    background: #313337;
}

.even {

}

#status, #status th, #status td {
    whitespace: pre;
    text-align: left;
    font-weight: normal;
    font-size: 100%;
    border:0px solid white;
}

#playerdata tr.odd {
  background-color: #313337;
}

#playerdata td.sorted {
  background-color: #606090;
}

#playerdata th.sorted {
  background-color: #606090;
}

#playerdata tr.odd td.sorted {
  background-color: #303060;
}

#playerdata .player, #playerdata .ip {
    cursor:crosshair;
}

#playerdata tr:hover, #playerdata td:hover {
    color: white;
    background: #606090;
}

.status .player:hover {
    color: white;
}

.titre {
    font: bolder 12px Verdana, Arial;
    color: #9090D0;
}

a {
    font: bolder 12px Verdana, Arial;
    color: #9090D0;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}
</style>
YOURCSS;


/*
 * STOP EDITING. OK ?
 ******************************************************************************************************/
 
function droplist ($list = array()) {
    global $server, $this_script;
    print "<form name=\"lstsrv\" action=\"$this_script\" method=\"post\">\n";
    print "<select name=\"server\">\n";
    print "<option value=\"\">Serveurs :</option>\n";
    foreach ($list as $key => $val) {
        if ($server == $key) {$selected="selected";} else {$selected='';}
        print "<option value=\"$key\" $selected><b>$key</b> (". $val['ip'] . ':' . $val['port'] . ' ' . $val['pass'] . ")</option>\n";
    }
    if ($server) {
        $button="Refresh";
    } else {
        $button="Select";
    }
    print "</select><input type=\"submit\" value=\"$button\"></form>\n";
}


// This will take the server response and makes it in to readable key value pair
function process($data = array()) {
    $haystack = explode("\n", $data, 3);
    $stack = explode('\\', $haystack[1]);
    for ($i = 1;$i <= count($stack);$i = $i + 2) {
        $list[@$stack[$i]] = @$stack[$i + 1];
    }
    $list[] = @$haystack[2];
    foreach ($list as $key => $dum) {
        if (empty($dum) || !isset($dum)) {
            unset($list[$key]);
        }
    }
    return $list;
}


//This will iterate thru each key value pair and prints a table.
function outputData($list = array()) {
    global $ip,$port,$pass;
    echo "<table id=\"serverinfo\" border=\"0\" cellspacing=\"1\" cellpadding=\"0\">\n";
    // $serv = stripcolors ($list['sv_hostname']);
    $serv = $list['sv_hostname'];
    echo "<tr class=\"titre\"><td class=\"titre\">Serveur</td><td class=\"titre\" nowrap>$serv</td></tr>\n";
    echo "<tr class=\"titre\"><td class=\"titre\">ip:port</td><td class=\"titre\">$ip:$port</td></tr>\n";
    ksort($list);
    foreach ($list as $key => $val) {
        if ($key != "0") {
            if ($class=="even") {$class="odd";} else {$class="even";}
            echo "<tr class=\"$class\"><td class=\"$class\">$key</td><td class=\"$class\">$val</td><tr>\n";
        }
    }
    echo "</TABLE>\n";
}

function status ($ip, $port, $timeout = 15) {
    $errno = $errstr = null;
    $cmd = "˙˙˙˙getstatus\n";
    $f = fsockopen('udp://' . $ip, $port, $errno, $errstr, $timeout);
    if (!$f)
        die ("Unable to connect. Error $errno - $errstr\n");
    socket_set_timeout ($f, 1, 0);
    fwrite ($f, $cmd);
    $data = '';
    while ($d = fread ($f, 10000)) {
        $data .= $d;
    }
    fclose ($f);
    $data = stripcolors ($data);
    return $data;
}

function rcon ($ip, $port, $rcon_pass, $command) {
    $fp = fsockopen("udp://$ip",$port, $errno, $errstr, 2);
    socket_set_timeout($fp,2);

    if (!$fp)    {
        echo "$errstr ($errno)<br>\n";
    } else {
        $query = "\xFF\xFF\xFF\xFFrcon \"" . $rcon_pass . "\" " . $command;
        fwrite($fp,$query);
    }
    $data = '';
    while ($d = fread ($fp, 10000)) {
        $data .= $d;
    }
    fclose ($fp);
    $data = preg_replace ("/....print\n/", "", $data);
    $data = stripcolors ($data);
    return $data;
}

function stripcolors ($quoi) {
    $result = $quoi;
    $result = preg_replace ("/\^x....../i", "", $result); // remove OSP's colors (^Xrrggbb)
    $result = preg_replace ("/\^./", "", $result); // remove Q3 colors (^2) or OSP control (^N, ^B etc..)
    $result = preg_replace ("/</", "&lt;", $result); // convert < into &lt; for proper display
    return $result;
}

function printheadrow ($list = array()) {
    global $javascript;
    print "<tr>\n";
    $index=0;
    foreach ($list as $elem) {
        if ($javascript) {
            print "<th><a href=\"\" onclick=\"this.blur();return sortTable('playerdata', $index)\" title=\"Click to Sort by $elem\">$elem</a></th>";
        } else {
            print "<th>$elem</th>";
        }
        $index++;
    }
    print "</tr>\n";
}

function printrow ($list = array(), $class) {
    print "<tr class=\"$class\">\n";
    foreach ($list as $elem) {
        print "<td>$elem</td>";
    }
    print "</tr>\n";
}

function rconstatus ($ip, $port, $rcon_pass) {
    $result = rcon ($ip, $port, $rcon_pass, "status");
    $result = explode ("\n", $result);

    # ok, let's deal with the following :
    #
    # map: q3wcp9
    # num score ping name            lastmsg address               qport rate
    # --- ----- ---- --------------- ------- --------------------- ----- -----
    #   1    19   33 l33t^n1ck       33 62.212.106.216:27960   5294 25000

    print "<table id=\"status\">\n";
    print "<thead><tr><th colspan=\"8\">$result[0]</th></tr>\n";
    printheadrow (array("num", "score", "ping", "name", "lastmsg", "address", "qport", "rate"));
    print "</thead>\n";
    print "<tbody id=\"playerdata\">\n";
    array_shift($result); // 1st line : map q3wcp9
    array_shift($result); // 2nd line : col headers
    array_shift($result); // 3rd line : -- ------ ----
    array_pop($result);
    array_pop($result); // two empty lines at the end, go figure.
    foreach ($result as $line) {
        if ($class=="odd") {$class = "even";} else {$class = "odd";}
        $player = $line;
        preg_match_all("/^\s*(\d+)\s*(\d+)\s*(\d+)(.*?)(\d*)\s*(\S*)\s*(\d*)\s*(\d*)\s*$/", $player, $out);
        // weeeeeeeeee \o/
        $num = $out[1][0];
        $score = $out[2][0];
        $ping = $out[3][0];
        $name = trim($out[4][0]);
        $name = "<span class=\"player\" title=\"Click to kick $name\"  onClick=\"document.forms.rcon.rcon_cmd.value='clientkick $num';document.rcon.rcon_cmd.focus()\">$name</span>";
        $lastmsg = $out[5][0];
        $address = $out[6][0];
        if ($address != 'bot') {
            $addressip = preg_replace ("/:.*$/", "", $address);
            $address = "<span class=\"ip\" title=\"Click to ban IP $addressip\" onClick=\"document.forms.rcon.rcon_cmd.value='addip $addressip';document.rcon.rcon_cmd.focus()\">$address</span>";
        }
        $qport = $out[7][0];
        $rate = $out[8][0];
        printrow (array ($num, $score, $ping, $name, $lastmsg, $address, $qport, $rate), $class);
    }
    print "</tbody>\n</table>\n";
}

function theterriblejavascript () {
    // found here : http://www.brainjar.com/dhtml/tablesort/

    print <<<JAVASCRIPT
    
<script type="text/javascript">//<![CDATA[
function sortTable(id, col) {

  // Get the table section to sort.
  var tblEl = document.getElementById(id);

  // Set up an array of reverse sort flags, if not done already.
  if (tblEl.reverseSort == null)
    tblEl.reverseSort = new Array();

  // If this column was the last one sorted, reverse its sort direction.
  if (col == tblEl.lastColumn)
    tblEl.reverseSort[col] = !tblEl.reverseSort[col];

  // Remember this column as the last one sorted.
  tblEl.lastColumn = col;

  // Set the table display style to "none" - necessary for Netscape 6 
  // browsers.
  var oldDsply = tblEl.style.display;
  tblEl.style.display = "none";

  // Sort the rows based on the content of the specified column using a
  // selection sort.

  var tmpEl;
  var i, j;
  var minVal, minIdx;
  var testVal;
  var cmp;

  for (i = 0; i < tblEl.rows.length - 1; i++) {

    // Assume the current row has the minimum value.
    minIdx = i;
    minVal = getTextValue(tblEl.rows[i].cells[col]);

    // Search the rows that follow the current one for a smaller value.
    for (j = i + 1; j < tblEl.rows.length; j++) {
      testVal = getTextValue(tblEl.rows[j].cells[col]);
      cmp = compareValues(minVal, testVal);
      // Reverse order?
      if (tblEl.reverseSort[col])
        cmp = -cmp;
      // If this row has a smaller value than the current minimum, remember its
      // position and update the current minimum value.
      if (cmp > 0) {
        minIdx = j;
        minVal = testVal;
      }
    }

    // By now, we have the row with the smallest value. Remove it from the
    // table and insert it before the current row.
    if (minIdx > i) {
      tmpEl = tblEl.removeChild(tblEl.rows[minIdx]);
      tblEl.insertBefore(tmpEl, tblEl.rows[i]);
    }
  }

  // Restore the table's display style.
  tblEl.style.display = oldDsply;

  // Make it look pretty.
  makePretty(tblEl, col);
  
  return false;
}

//-----------------------------------------------------------------------------
// Functions to get and compare values during a sort.
//-----------------------------------------------------------------------------

// This code is necessary for browsers that don't reflect the DOM constants
// (like IE).
if (document.ELEMENT_NODE == null) {
  document.ELEMENT_NODE = 1;
  document.TEXT_NODE = 3;
}

function getTextValue(el) {

  var i;
  var s;

  // Find and concatenate the values of all text nodes contained within the
  // element.
  s = "";
  for (i = 0; i < el.childNodes.length; i++)
    if (el.childNodes[i].nodeType == document.TEXT_NODE)
      s += el.childNodes[i].nodeValue;
    else if (el.childNodes[i].nodeType == document.ELEMENT_NODE &&
             el.childNodes[i].tagName == "BR")
      s += " ";
    else
      // Use recursion to get text within sub-elements.
      s += getTextValue(el.childNodes[i]);

  return normalizeString(s);
}

function compareValues(v1, v2) {

  var f1, f2;

  // If the values are numeric, convert them to floats.

  f1 = parseFloat(v1);
  f2 = parseFloat(v2);
  if (!isNaN(f1) && !isNaN(f2)) {
    v1 = f1;
    v2 = f2;
  }

  // Compare the two values.
  if (v1 == v2)
    return 0;
  if (v1 > v2)
    return 1
  return -1;
}

// Regular expressions for normalizing white space.
var whtSpEnds = new RegExp("^\\s*|\\s*$", "g");
var whtSpMult = new RegExp("\\s\\s+", "g");

function normalizeString(s) {

  s = s.replace(whtSpMult, " ");  // Collapse any multiple whites space.
  s = s.replace(whtSpEnds, "");   // Remove leading or trailing white space.

  return s;
}

// Style class names.
var rowClsNm = "odd";
var colClsNm = "sorted";

// Regular expressions for setting class names.
var rowTest = new RegExp(rowClsNm, "gi");
var colTest = new RegExp(colClsNm, "gi");


function makePretty(tblEl, col) {

  var i, j;
  var rowEl, cellEl;

  // Set style classes on each row to alternate their appearance.
  for (i = 0; i < tblEl.rows.length; i++) {
   rowEl = tblEl.rows[i];
   rowEl.className = rowEl.className.replace(rowTest, "");
    if (i % 2 != 0)
      rowEl.className += " " + rowClsNm;
    rowEl.className = normalizeString(rowEl.className);
    // Set style classes on each column (other than the name column) to
    // highlight the one that was sorted.
    for (j = 0; j < tblEl.rows[i].cells.length; j++) {
      cellEl = rowEl.cells[j];
      cellEl.className = cellEl.className.replace(colTest, "");
      if (j == col)
        cellEl.className += " " + colClsNm;
      cellEl.className = normalizeString(cellEl.className);
    }
  }

  // Find the table header and highlight the column that was sorted.
  var el = tblEl.parentNode.tHead;
  rowEl = el.rows[el.rows.length - 1];
  // Set style classes for each column as above.
  for (i = 0; i < rowEl.cells.length; i++) {
    cellEl = rowEl.cells[i];
    cellEl.className = cellEl.className.replace(colTest, "");
    // Highlight the header of the sorted column.
    if (i == col)
      cellEl.className += " " + colClsNm;
      cellEl.className = normalizeString(cellEl.className);
  }
}

//]]></script>
JAVASCRIPT;
}

// let's c'est parti go

print "<html>\n";
print "<head>\n";
print "<title>Q3 WebRCON</title>\n";
print $yourcss;
if ($javascript) theterriblejavascript();
print "</head>\n<body>";
print "<p></p>";
print "<table align=\"center\" border=\"1\" cellpadding=\"5\" cellspacing=\"0\" id=\"maintable\">\n";
print "<tr><td colspan=\"2\">";
print "<span class=\"titrepage\">Quake3 WebRCON</span>\n";
print "</td><td>";
droplist($servers) ;
print "</td></tr>";

if ($server) {
    $ip = $servers[$server]['ip'];
    $port = $servers[$server]['port'];
    $pass = $servers[$server]['pass'];
    $rcon = $servers[$server]['rcon'];
    $cfg = $servers[$server]['cfg'];

    print "<tr><td valign=\"top\" nowrap rowspan=\"2\">\n";

    print "<span class=\"titre\">/exec cfg :</span><br>\n";
    print "<form name=\"chgcfg\" action=\"$this_script\" method=\"post\">\n";
    print "<input type=\"hidden\" name=server value=\"$server\">\n";
    print "<input type=\"hidden\" name=action value=\"chgcfg\">\n";
    print "<input type=\"hidden\" name=ozh value=\"$server\">\n";

    if ($action=='chgcfg') {
        $lastcmd = "exec $config";
        $lastresult = rcon ($ip, $port, $rcon, "exec $config");
    }

    if ($action=='rcon') {
        if (preg_match("/^kick /i", $rcon_cmd)) {
            $rcon_cmd=preg_replace("/^kick /", "kick \"", $rcon_cmd) . "\"";
        } else {
            //$rcon_cmd="non";
        }
        $lastcmd = $rcon_cmd;
        $lastresult = rcon ($ip, $port, $rcon, $rcon_cmd);
    }

    if (is_array($cfg)) {
        foreach ($cfg as $config) {
            print "<p><input type=\"submit\" name=\"config\" value=\"$config\"></p>\n";
        }
    } else {
        if ($cfg) {
            print "<p><input type=\"submit\" name=\"config\" value=\"$cfg\"></p>\n";
        } else {
            print "<p>No .cfg</p>";
        }
    }

    print "</form>\n";

    print "</td><td valign=\"top\" rowspan=\"2\">\n";

    $data = status($ip,$port,10);
    $list = process($data);
    outputData($list);

    print "</td><td valign=\"top\" nowrap>\n";

    //$result = rcon ($ip, $port, $rcon, "status");
    print "<span class=\"titre\">/rcon status :</span><br><br>\n";
    rconstatus ($ip, $port, $rcon);
    //print "<pre>$result</pre>";

    print "</td></tr><tr><td valign=\"bottom\">\n";

    if ($lastcmd) {
        print "<span class=\"titre\">/rcon $lastcmd :</span><br><br>\n";
        echo "<pre>$lastresult\n</pre>\n";
    }

    print "<form name=\"rcon\" action=\"$this_script\" method=\"post\">\n";
    print "<p><span class=\"titre\">/rcon <input type=\"text\" name=\"rcon_cmd\" size=\"25\"><input type=\"submit\" value=\"&laquo;\"></span></p>\n";
    print "<input type=\"hidden\" name=\"server\" value=\"$server\">\n";
    print "<input type=\"hidden\" name=\"action\" value=\"rcon\">\n";

    foreach ($rcon_buttons as $button) {
        print "<input type=\"button\" value=\"$button\" onClick=\"document.forms.rcon.rcon_cmd.value=this.value;document.forms.rcon.submit()\"> ";
    }
    print "</form>\n";
    print "\n</td></tr>\n";
}

print "</table>\n";

print "<script>document.rcon.rcon_cmd.focus()</script>";
print "<center><span class=\"titre\"><br><a href=\"http://frenchfragfactory.net/\">&copy; SARL FFS M8</a></span></center>";
print "</body></html>";

?>