Andrew Dolgov 10 years ago
commit
9b6414a3c4

+ 75 - 0
backend.php

@@ -0,0 +1,75 @@
+<?php
+	error_reporting(E_ERROR | E_WARNING | E_PARSE);
+
+	require_once "functions.php"; 
+	require_once "sessions.php";
+	require_once "sanity_check.php";
+	require_once "version.php"; 
+	require_once "config.php";
+
+	$link = db_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);	
+
+	$dt_add = get_script_dt_add();
+
+	no_cache_incantation();
+
+	header('Content-Type: text/html; charset=utf-8');
+
+	if (!$_SESSION["uid"]) {
+		print json_encode(array("error" => 6));
+		return;
+	}
+
+	if (!sanity_check($link)) { return; }
+
+	$op = $_REQUEST["op"];
+
+	switch ($op) {
+	case "send":
+		$message = db_escape_string($_REQUEST["message"]);
+
+		# TODO
+		$connection_id = 1;
+		$chan = db_escape_string($_REQUEST["chan"]);
+
+		push_message($link, $connection_id, $chan, $message);
+
+		break;
+	case "update":
+		$last_id = (int) db_escape_string($_REQUEST["last_id"]);
+		$active_buf = db_escape_string($_REQUEST["active"]);
+
+		$lines = get_new_lines($link, $last_id);
+		$conn = get_conn_info($link);
+		$nicklist = get_nick_list($link, $active_buf);
+
+		print json_encode(array($conn, $lines, $nicklist));
+
+		break;
+	case "init":
+		$result = db_query($link, "SELECT MAX(ttirc_messages.id) AS max_id
+			FROM ttirc_messages, ttirc_connections
+			WHERE connection_id = ttirc_connections.id AND owner_uid = " . $_SESSION["uid"]);
+
+		$rv = array();
+
+		if (db_num_rows($result) != 0) {
+			$rv["max_id"] = db_fetch_result($result, 0, "max_id");
+		} else {
+			$rv["max_id"] = 0;
+		}
+
+		print json_encode($rv);
+
+		break;
+	case "toggle-connection":
+		$connection_id = 1; #TODO
+		
+		$status = bool_to_sql_bool(db_escape_string($_REQUEST["status"]));
+
+		db_query($link, "UPDATE ttirc_connections SET enabled = NOT $status
+			WHERE id = '$connection_id'");
+
+		break;
+	}
+?>

+ 38 - 0
config.php-dist

@@ -0,0 +1,38 @@
+<?php
+
+	define('DB_TYPE', "pgsql"); // or mysql
+	define('DB_HOST', "localhost");
+	define('DB_USER', "");
+	define('DB_NAME', "");
+	define('DB_PASS', "");
+	define('DB_PORT', '5432'); // when neeeded, PG-only
+
+	define('DATABASE_BACKED_SESSIONS', true);
+	// Store session information in a database, recommended for multiuser 
+	// configurations. Doesn't seem to work for everyone, so enable with caution.
+	// tt-rss uses default PHP session storing mechanism if disabled.
+
+	define('SESSION_CHECK_ADDRESS', false);
+	// Bind session to client IP address (recommended)
+
+	define('SESSION_COOKIE_LIFETIME', 2592000);
+	// Default lifetime of a session cookie. In seconds, 
+	// 0 means delete on browser close
+
+	define('SESSION_COOKIE_LIFETIME_REMEMBER', 2592000);
+	// Session cookie lifetime if "remember me" is checked on login.
+
+	define('SESSION_EXPIRE_TIME', 2592000);
+	// Hard expiration limit for sessions. Should be
+	// >= SESSION_COOKIE_LIFETIME_REMEMBER
+
+	define('SINGLE_USER_MODE', false);
+	// Operate in single user mode, disables all functionality related to
+	// multiple users.
+
+	define('LOCK_DIRECTORY', '/var/tmp');
+	// Directory for lockfiles, must be writable to the user you run
+	// daemon process or cronjobs under.
+
+	define('CONFIG_VERSION', 1);
+?>

+ 213 - 0
connection.php

@@ -0,0 +1,213 @@
+<?php
+
+require_once "config.php";
+require_once "lib/yapircl/Yapircl.php";
+require_once "db.php";
+
+class Connection extends Yapircl {
+
+	var $link = false;
+	var $checkpoint = false;
+	var $last_sent_id = false;
+	var $connection_id = false;
+	var $encoding = false;
+
+	function Connection($link, $connection_id, $encoding, $last_sent_id) {
+		$this->Yapircl();
+
+		$this->link = $link;
+		$this->checkpoint = 0;
+		$this->connection_id = $connection_id;
+		$this->last_sent_id = $last_sent_id;
+		$this->encoding = $encoding;
+		$this->_resolution = 5000;
+	}
+
+	function check_messages() {
+		$ts = time();
+
+		if ($ts != $this->checkpoint) {
+			$this->checkpoint = $ts;
+
+			$result = db_query($this->link, "SELECT * FROM ttirc_messages
+				WHERE incoming = false AND
+					ts > NOW() - INTERVAL '1 year' AND
+					connection_id = '".($this->connection_id)."' AND 
+					id > ".($this->last_sent_id) . "  ORDER BY id");
+
+			$tmp_id = $this->last_sent_id;
+
+			while ($line = db_fetch_assoc($result)) {
+				if ($line["id"] > $tmp_id) {
+					$tmp_id = $line["id"];
+
+					$message = iconv("UTF-8", $this->encoding, $line["message"]);
+
+					$this->privmsg($line["destination"], $message);
+				}
+			}
+
+			if ($tmp_id != $this->last_sent_id) {
+				$this->last_sent_id = $tmp_id;
+
+				db_query($this->link, "UPDATE ttirc_connections SET last_sent_id = ".
+					($this->last_sent_id)." WHERE id = ".
+					($this->connection_id));
+			}
+
+		}
+	}
+
+	function run() {
+		$this->push_message('-IRC-', '---', 'Connection established.');
+		while ($this->connected()) {
+			$this->disconnect_if_disabled();
+			$this->check_messages();
+			$this->join_channels();
+			$this->idle();
+			usleep($this->_resolution);
+		}
+	}
+
+	function disconnect_if_disabled() {
+		$result = db_query($this->link, "SELECT enabled FROM ttirc_connections
+			WHERE id = " . $this->connection_id);
+
+		if (db_num_rows($result) != 1) {
+			$this->quit("Tiny Tiny IRC");
+		} else {
+			$enabled = sql_bool_to_bool(db_fetch_result($result, 0, "enabled"));
+			if (!$enabled) {
+				$this->quit("Tiny Tiny IRC");
+			}
+		}
+
+	}
+
+	function push_message($sender, $destination, $message) {
+
+		#FIXME convert sender/dest to unicode
+		$message = iconv($this->encoding, "UTF-8", $message);
+		$destination = iconv($this->encoding, "UTF-8", $destination);
+		$sender = iconv($this->encoding, "UTF-8", $sender);
+
+		$query = sprintf("INSERT INTO ttirc_messages (incoming,
+			connection_id, sender, 
+			destination, message) VALUES (true, %d, '%s', '%s', '%s')",
+			$this->connection_id,
+			db_escape_string($sender), db_escape_string($destination), 
+			db_escape_string($message));
+
+		db_query($this->link, $query, false);
+	}
+
+	function event_join() {
+		$this->update_nicklist($this->from);
+	}
+
+	function event_nick() {
+		$this->update_nicklist(false);
+	}
+
+	function event_part() {
+		$this->update_nicklist($this->from);
+	}
+
+	function event_kick() {
+		$this->update_nicklist($this->from);
+	}
+
+	function event_public_privmsg() {
+#		echo "<" . $this->nick . "> " . $this->full . "\n";
+		$this->push_message($this->nick, $this->from, $this->full);
+	}
+
+	function event_private_privmsg() {
+//		echo "[" . $this->nick . "(" . $this->user . 
+//			"@" . $this->host . ")] " . $this->full . "\n"; 
+
+		$this->push_message($this->nick, $this->from, $this->full);
+	}
+
+	function event_public_ctcp_action() {
+		$this->push_message($this->nick, $this->from, $this->full);
+	}
+
+	function handle_ctcp_version() {
+		$this->ctcp($this->nick, "VERSION " . $this->getVersion());
+		echo $this->mask . " requested CTCP VERSION from " . $this->from . "\n";
+	}
+
+	function handle_ctcp_ping() {
+		$this->ctcp($this->nick, $this->full);
+		echo $this->mask . " requested CTCP PING from " . $this->from . "\n";
+	}
+
+	function event_public_ctcp_version() {
+		$this->handle_ctcp_version(); 
+	}
+	
+	function event_private_ctcp_version() {
+		$this->handle_ctcp_version();
+	}
+
+	function event_public_ctcp_ping() {
+		$this->handle_ctcp_ping();
+	}
+
+	function event_private_ctcp_ping() {
+		$this->handle_ctcp_ping();
+	}
+
+	function event_rpl_endofnames() {
+		$nicklist = $this->channels[$this->_xline[3]];
+
+		$this->push_message('-IRC-', $this->_xline[3], 
+			__('You have joined the channel.'));
+
+		$this->update_nicklist($this->_xline[3]);
+
+	}
+
+	function to_utf($text) {
+		return iconv($this->encoding, "utf-8", $text);
+	}
+
+	function join_channels() {
+		$result = db_query($this->link, "SELECT destination FROM ttirc_destinations
+			WHERE auto_join = true AND connection_id = " . $this->connection_id);
+
+		while ($line = db_fetch_assoc($result)) {
+			if (!array_key_exists($line["destination"], $this->channels)) {
+				$this->join($line["destination"]);
+			}
+		}
+	}
+
+	function update_nicklist($destination) {
+
+		if ($destination) {
+			$nicklist = db_escape_string($this->to_utf(
+				json_encode($this->getNickList($destination))));
+			$destination = db_escape_string($destination);
+
+
+			db_query($this->link, "UPDATE ttirc_destinations SET nicklist = '$nicklist'
+				WHERE destination = '$destination' AND connection_id = " . 
+				$this->connection_id);
+		} else {
+			foreach (array_keys($this->channels) as $chan) {
+
+				$nicklist = db_escape_string($this->to_utf(
+					json_encode($this->getNickList($chan))));
+				$destination = db_escape_string($chan);
+
+				db_query($this->link, "UPDATE ttirc_destinations SET nicklist = '$nicklist'
+					WHERE destination = '$destination' AND connection_id = " . 
+					$this->connection_id);
+			}
+		}
+	}
+}
+
+

+ 146 - 0
db.php

@@ -0,0 +1,146 @@
+<?php
+
+require_once "config.php";
+
+function db_connect($host, $user, $pass, $db) {
+	if (DB_TYPE == "pgsql") {	
+			  
+		$string = "dbname=$db user=$user password=$pass";	
+		
+		if ($host) {
+			$string .= " host=$host";
+		}
+
+		if (defined('DB_PORT')) {
+			$string = "$string port=" . DB_PORT;
+		}
+
+		$link = pg_connect($string);
+
+		if (!$link) {
+			die("Connection failed: " . pg_last_error($link));
+		}
+
+		return $link;
+
+	} else if (DB_TYPE == "mysql") {
+		$link = mysql_connect($host, $user, $pass);
+		if ($link) {
+			$result = mysql_select_db($db, $link);			
+			if (!$result) {
+				die("Can't select DB: " . mysql_error($link));
+			}			
+			return $link;
+		} else {
+			die("Connection failed: " . mysql_error($link));
+		}
+	}
+}
+
+function db_escape_string($s) {
+	if (DB_TYPE == "pgsql") {	
+		return pg_escape_string($s);
+	} else {
+		return mysql_real_escape_string($s);
+	}
+}
+
+/* I hate MySQL :( */
+
+function db_escape_string_2($s, $link) {
+	if (DB_TYPE == "pgsql") {	
+		return pg_escape_string($s);
+	} else {
+		return mysql_real_escape_string($s, $link);
+	}
+}
+
+function db_query($link, $query, $die_on_error = true) {
+	if (DB_TYPE == "pgsql") {
+		$result = pg_query($link, $query);
+		if (!$result) {
+			$query = htmlspecialchars($query); // just in case
+			if ($die_on_error) {
+				die("Query <i>$query</i> failed [$result]: " . pg_last_error($link));			
+			}
+		}
+		return $result;
+	} else if (DB_TYPE == "mysql") {
+		$result = mysql_query($query, $link);
+		if (!$result) {
+			$query = htmlspecialchars($query);
+			if ($die_on_error) {
+				die("Query <i>$query</i> failed: " . mysql_error($link));
+			}
+		}
+		return $result;
+	}
+}
+
+function db_query_2($query) {
+	if (DB_TYPE == "pgsql") {
+		return pg_query($query);
+	} else if (DB_TYPE == "mysql") {
+		return mysql_query($link);
+	}
+}
+
+function db_fetch_assoc($result) {
+	if (DB_TYPE == "pgsql") {
+		return pg_fetch_assoc($result);
+	} else if (DB_TYPE == "mysql") {
+		return mysql_fetch_assoc($result);
+	}
+}
+
+
+function db_num_rows($result) {
+	if (DB_TYPE == "pgsql") {
+		return pg_num_rows($result);
+	} else if (DB_TYPE == "mysql") {
+		return mysql_num_rows($result);
+	}
+}
+
+function db_fetch_result($result, $row, $param) {
+	if (DB_TYPE == "pgsql") {
+		return pg_fetch_result($result, $row, $param);
+	} else if (DB_TYPE == "mysql") {
+		// I hate incoherent naming of PHP functions
+		return mysql_result($result, $row, $param);
+	}
+}
+
+function db_unescape_string($str) {
+	$tmp = str_replace("\\\"", "\"", $str);
+	$tmp = str_replace("\\'", "'", $tmp);
+	return $tmp;
+}
+
+function db_close($link) {
+	if (DB_TYPE == "pgsql") {
+
+		return pg_close($link);
+
+	} else if (DB_TYPE == "mysql") {
+		return mysql_close($link);
+	}
+}
+
+function db_affected_rows($link, $result) {
+	if (DB_TYPE == "pgsql") {
+		return pg_affected_rows($result);
+	} else if (DB_TYPE == "mysql") {
+		return mysql_affected_rows($link);
+	}
+}
+
+function db_last_error($link) {
+	if (DB_TYPE == "pgsql") {
+		return pg_last_error($link);
+	} else if (DB_TYPE == "mysql") {
+		return mysql_error($link);
+	}
+}
+
+?>

+ 230 - 0
functions.js

@@ -0,0 +1,230 @@
+var hotkeys_enabled = false;
+
+/* add method to remove element from array */
+
+Array.prototype.remove = function(s) {
+	for (var i=0; i < this.length; i++) {
+		if (s == this[i]) this.splice(i, 1);
+	}
+}
+
+function is_opera() {
+	return window.opera;
+}
+
+function exception_error(location, e, ext_info) {
+	var msg = format_exception_error(location, e);
+
+	if (!ext_info) ext_info = "N/A";
+
+	disableHotkeys();
+
+	try {
+
+		var ebc = $("xebContent");
+	
+		if (ebc) {
+	
+			Element.show("dialog_overlay");
+			Element.show("errorBoxShadow");
+	
+			if (ext_info) {
+				if (ext_info.responseText) {
+					ext_info = ext_info.responseText;
+				}
+			}
+	
+			ebc.innerHTML = 
+				"<div><b>Error message:</b></div>" +
+				"<pre>" + msg + "</pre>" +
+				"<div><b>Additional information:</b></div>" +
+				"<textarea readonly=\"1\">" + ext_info + "</textarea>";
+	
+		} else {
+			alert(msg);
+		}
+
+	} catch (e) {
+		alert(msg);
+
+	}
+
+}
+
+function format_exception_error(location, e) {
+	var msg;
+
+	if (e.fileName) {
+		var base_fname = e.fileName.substring(e.fileName.lastIndexOf("/") + 1);
+	
+		msg = "Exception: " + e.name + ", " + e.message + 
+			"\nFunction: " + location + "()" +
+			"\nLocation: " + base_fname + ":" + e.lineNumber;
+
+	} else if (e.description) {
+		msg = "Exception: " + e.description + "\nFunction: " + location + "()";
+	} else {
+		msg = "Exception: " + e + "\nFunction: " + location + "()";
+	}
+
+	debug("<b>EXCEPTION: " + msg + "</b>");
+
+	return msg;
+}
+
+
+function disableHotkeys() {
+	hotkeys_enabled = false;
+}
+
+function enableHotkeys() {
+	hotkeys_enabled = true;
+}
+
+function param_escape(arg) {
+	if (typeof encodeURIComponent != 'undefined')
+		return encodeURIComponent(arg);	
+	else
+		return escape(arg);
+}
+
+function param_unescape(arg) {
+	if (typeof decodeURIComponent != 'undefined')
+		return decodeURIComponent(arg);	
+	else
+		return unescape(arg);
+}
+
+var debug_last_class = "even";
+
+function toggle_debug() {
+	if (Element.visible('debug_output')) {
+		Element.hide('debug_output');
+	} else {
+		Element.show('debug_output');
+	}
+}
+
+function debug(msg) {
+
+	if (debug_last_class == "even") {
+		debug_last_class = "odd";
+	} else {
+		debug_last_class = "even";
+	}
+
+	var c = $('debug_output');
+	if (c && Element.visible(c)) {
+		while (c.lastChild != 'undefined' && c.childNodes.length > 100) {
+			c.removeChild(c.lastChild);
+		}
+	
+		var d = new Date();
+		var ts = leading_zero(d.getHours()) + ":" + leading_zero(d.getMinutes()) +
+			":" + leading_zero(d.getSeconds());
+		c.innerHTML = "<li class=\"" + debug_last_class + "\"><span class=\"debugTS\">[" + ts + "]</span> " + 
+			msg + "</li>" + c.innerHTML;
+	}
+}
+
+// originally stolen from http://www.11tmr.com/11tmr.nsf/d6plinks/MWHE-695L9Z
+// bugfixed just a little bit :-)
+function getURLParam(strParamName){
+  var strReturn = "";
+  var strHref = window.location.href;
+
+  if (strHref.indexOf("#") == strHref.length-1) {
+		strHref = strHref.substring(0, strHref.length-1);
+  }
+
+  if ( strHref.indexOf("?") > -1 ){
+    var strQueryString = strHref.substr(strHref.indexOf("?"));
+    var aQueryString = strQueryString.split("&");
+    for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
+      if (aQueryString[iParam].indexOf(strParamName + "=") > -1 ){
+        var aParam = aQueryString[iParam].split("=");
+        strReturn = aParam[1];
+        break;
+      }
+    }
+  }
+  return strReturn;
+} 
+
+function leading_zero(p) {
+	var s = String(p);
+	if (s.length == 1) s = "0" + s;
+	return s;
+}
+
+function closeErrorBox() {
+
+	if (Element.visible("errorBoxShadow")) {
+		Element.hide("dialog_overlay");
+		Element.hide("errorBoxShadow");
+
+		enableHotkeys();
+	}
+
+	return false;
+}
+
+
+function closeInfoBox(cleanup) {
+
+	try {
+		enableHotkeys();
+
+		if (Element.visible("infoBoxShadow")) {
+			Element.hide("dialog_overlay");
+			Element.hide("infoBoxShadow");
+
+			if (cleanup) $("infoBoxShadow").innerHTML = "&nbsp;";
+		}
+	} catch (e) {
+		exception_error("closeInfoBox", e);
+	}
+	
+	return false;
+}
+
+function fatal_error(code, msg, ext_info) {
+	try {	
+
+		if (!ext_info) ext_info = "N/A";
+
+		if (code == 6) {
+			window.location.href = "index.php";			
+		} else if (code == 5) {
+			window.location.href = "update.php";
+		} else {
+	
+			if (msg == "") msg = "Unknown error";
+
+			var ebc = $("xebContent");
+	
+			if (ebc) {
+	
+				Element.show("dialog_overlay");
+				Element.show("errorBoxShadow");
+				Element.hide("xebBtn");
+
+				if (ext_info) {
+					if (ext_info.responseText) {
+						ext_info = ext_info.responseText;
+					}
+				}
+	
+				ebc.innerHTML = 
+					"<div><b>Error message:</b></div>" +
+					"<pre>" + msg + "</pre>" +
+					"<div><b>Additional information:</b></div>" +
+					"<textarea readonly=\"1\">" + ext_info + "</textarea>";
+			}
+		}
+
+	} catch (e) {
+		exception_error("fatal_error", e);
+	}
+}
+

+ 583 - 0
functions.php

@@ -0,0 +1,583 @@
+<?php
+	require_once "config.php";
+	require_once "lib/gettext/gettext.inc";
+
+	if (DB_TYPE == "pgsql") {
+		define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
+	} else {
+		define('SUBSTRING_FOR_DATE', 'SUBSTRING');
+	}
+
+	function get_translations() {
+		$tr = array(
+					"auto"  => "Detect automatically",					
+					"en_US" => "English");
+
+		return $tr;
+	}
+
+	if (ENABLE_TRANSLATIONS == true) { // If translations are enabled.
+		require_once "lib/accept-to-gettext.php";
+		require_once "lib/gettext/gettext.inc";
+
+		function startup_gettext() {
+	
+			# Get locale from Accept-Language header
+			$lang = al2gt(array_keys(get_translations()), "text/html");
+
+			if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
+				$lang = _TRANSLATION_OVERRIDE_DEFAULT;
+			}
+
+			if ($_COOKIE["ttirc_lang"] && $_COOKIE["ttirc_lang"] != "auto") {				
+				$lang = $_COOKIE["ttirc_lang"];
+			}
+
+			/* In login action of mobile version */
+			if ($_POST["language"] && defined('MOBILE_VERSION')) {
+				$lang = $_POST["language"];
+				$_COOKIE["ttirc_lang"] = $lang;
+			}
+
+			if ($lang) {
+				if (defined('LC_MESSAGES')) {
+					_setlocale(LC_MESSAGES, $lang);
+				} else if (defined('LC_ALL')) {
+					_setlocale(LC_ALL, $lang);
+				} else {
+					die("can't setlocale(): please set ENABLE_TRANSLATIONS to false in config.php");
+				}
+
+				if (defined('MOBILE_VERSION')) {
+					_bindtextdomain("messages", "../locale");
+				} else {
+					_bindtextdomain("messages", "locale");
+				}
+
+				_textdomain("messages");
+				_bind_textdomain_codeset("messages", "UTF-8");
+			}
+		}
+
+		startup_gettext();
+
+	} else { // If translations are enabled.
+		function __($msg) {
+			return $msg;
+		}
+		function startup_gettext() {
+			// no-op
+			return true;
+		}
+	} // If translations are enabled.
+
+	function init_connection($link) {
+
+		if (!$link) {
+			if (DB_TYPE == "mysql") {
+			print mysql_error();
+		}
+		// PG seems to display its own errors just fine by default.		
+			die("Connection failed.");
+		}
+
+		if (DB_TYPE == "pgsql") {
+			pg_query($link, "set client_encoding = 'UTF-8'");
+			pg_set_client_encoding("UNICODE");
+			pg_query($link, "set datestyle = 'ISO, european'");
+		} else {
+			if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
+				db_query($link, "SET NAMES " . MYSQL_CHARSET);
+	//			db_query($link, "SET CHARACTER SET " . MYSQL_CHARSET);
+			}
+		}
+	}
+
+	// from http://developer.apple.com/internet/safari/faq.html
+	function no_cache_incantation() {
+		header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
+		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
+		header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
+		header("Cache-Control: post-check=0, pre-check=0", false);
+		header("Pragma: no-cache"); // HTTP/1.0
+	}
+
+	function login_sequence($link, $mobile = false) {
+		if (!SINGLE_USER_MODE) {
+
+			$login_action = $_POST["login_action"];
+
+			# try to authenticate user if called from login form			
+			if ($login_action == "do_login") {
+				$login = $_POST["login"];
+				$password = $_POST["password"];
+				$remember_me = $_POST["remember_me"];
+
+				if (authenticate_user($link, $login, $password)) {
+					$_POST["password"] = "";
+
+					$_SESSION["language"] = $_POST["language"];
+					$_SESSION["ref_schema_version"] = get_schema_version($link, true);
+					$_SESSION["bw_limit"] = !!$_POST["bw_limit"];
+
+					header("Location: " . $_SERVER["REQUEST_URI"]);
+					exit;
+
+					return;
+				} else {
+					$_SESSION["login_error_msg"] = __("Incorrect username or password");
+				}
+			}
+
+			if (!$_SESSION["uid"] || !validate_session($link)) {
+				render_login_form($link, $mobile);
+				//header("Location: login.php");
+				exit;
+			} else {
+				/* bump login timestamp */
+				db_query($link, "UPDATE ttirc_users SET last_login = NOW() WHERE id = " . 
+					$_SESSION["uid"]);
+
+				if ($_SESSION["language"] && SESSION_COOKIE_LIFETIME > 0) {
+					setcookie("ttirc_lang", $_SESSION["language"], 
+						time() + SESSION_COOKIE_LIFETIME);
+				}
+
+				/* bump counters stamp since we're getting reloaded anyway */
+
+				$_SESSION["get_all_counters_stamp"] = time();
+			}
+
+		} else {
+			return authenticate_user($link, "admin", null);
+		}
+	}
+
+	function render_login_form($link, $mobile = 0) {
+		switch ($mobile) {
+		case 0:
+			require_once "login_form.php";
+			break;
+		case 1:
+			require_once "mobile/login_form.php";
+			break;
+		case 2:
+			require_once "mobile/classic/login_form.php";
+		}
+	}
+
+	function print_select($id, $default, $values, $attributes = "") {
+		print "<select name=\"$id\" id=\"$id\" $attributes>";
+		foreach ($values as $v) {
+			if ($v == $default)
+				$sel = " selected";
+			 else
+			 	$sel = "";
+			
+			print "<option$sel>$v</option>";
+		}
+		print "</select>";
+	}
+
+	function print_select_hash($id, $default, $values, $attributes = "") {
+		print "<select name=\"$id\" id='$id' $attributes>";
+		foreach (array_keys($values) as $v) {
+			if ($v == $default)
+				$sel = 'selected="selected"';
+			 else
+			 	$sel = "";
+			
+			print "<option $sel value=\"$v\">".$values[$v]."</option>";
+		}
+
+		print "</select>";
+	}
+
+	function encrypt_password($pass, $login = '') {
+		if ($login) {
+			return "SHA1X:" . sha1("$login:$pass");
+		} else {
+			return "SHA1:" . sha1($pass);
+		}
+	} // function encrypt_password
+
+	function authenticate_user($link, $login, $password, $force_auth = false) {
+
+		if (!SINGLE_USER_MODE) {
+
+			$pwd_hash1 = encrypt_password($password);
+			$pwd_hash2 = encrypt_password($password, $login);
+			$login = db_escape_string($login);
+
+			if (defined('ALLOW_REMOTE_USER_AUTH') && ALLOW_REMOTE_USER_AUTH 
+					&& $_SERVER["REMOTE_USER"] && $login != "admin") {
+
+				$login = db_escape_string($_SERVER["REMOTE_USER"]);
+
+				$query = "SELECT id,login,access_level,pwd_hash
+	            FROM ttirc_users WHERE
+					login = '$login'";
+
+			} else {
+				$query = "SELECT id,login,access_level,pwd_hash
+	            FROM ttirc_users WHERE
+					login = '$login' AND (pwd_hash = '$pwd_hash1' OR
+						pwd_hash = '$pwd_hash2')";
+			}
+
+			$result = db_query($link, $query);
+	
+			if (db_num_rows($result) == 1) {
+				$_SESSION["uid"] = db_fetch_result($result, 0, "id");
+				$_SESSION["name"] = db_fetch_result($result, 0, "login");
+				$_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
+	
+				db_query($link, "UPDATE ttirc_users SET last_login = NOW() WHERE id = " . 
+					$_SESSION["uid"]);
+	
+				$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
+				$_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
+	
+				//initialize_user_prefs($link, $_SESSION["uid"]);
+	
+				return true;
+			}
+	
+			return false;
+
+		} else {
+
+			$_SESSION["uid"] = 1;
+			$_SESSION["name"] = "admin";
+
+			$_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
+	
+			//initialize_user_prefs($link, $_SESSION["uid"]);
+	
+			return true;
+		}
+	}
+
+	function get_schema_version($link, $nocache = false) {
+		if (!$_SESSION["schema_version"] || $nocache) {
+			$result = db_query($link, "SELECT schema_version FROM ttirc_version");
+			$version = db_fetch_result($result, 0, "schema_version");
+			$_SESSION["schema_version"] = $version;
+			return $version;
+		} else {
+			return $_SESSION["schema_version"];
+		}
+	}
+
+	function validate_session($link) {
+		if (SINGLE_USER_MODE) { 
+			return true;
+		}
+
+		if (SESSION_CHECK_ADDRESS && $_SESSION["uid"]) {
+			if ($_SESSION["ip_address"]) {
+				if ($_SESSION["ip_address"] != $_SERVER["REMOTE_ADDR"]) {
+					$_SESSION["login_error_msg"] = __("Session failed to validate (incorrect IP)");
+					return false;
+				}
+			}
+		}
+
+		if ($_SESSION["ref_schema_version"] != get_schema_version($link, true)) {
+			return false;
+		}
+
+		if ($_SESSION["uid"]) {
+
+			$result = db_query($link, 
+				"SELECT pwd_hash FROM ttirc_users WHERE id = '".$_SESSION["uid"]."'");
+
+			$pwd_hash = db_fetch_result($result, 0, "pwd_hash");
+
+			if ($pwd_hash != $_SESSION["pwd_hash"]) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	function get_script_dt_add() {
+		return time();
+	}
+
+	function theme_image($link, $filename) {
+		if ($link) {
+			$theme_path = get_user_theme_path($link);
+
+			if ($theme_path && is_file($theme_path.$filename)) {
+				return $theme_path.$filename;
+			} else {
+				return $filename;
+			}
+		} else {
+			return $filename;
+		}
+	}
+
+	function get_user_theme($link) {
+		return ''; // TODO
+	}
+
+	function get_user_theme_path($link) {
+		return false; // TODO
+	}
+
+	function get_all_themes() {
+		$themes = glob("themes/*");
+
+		asort($themes);
+
+		$rv = array();
+
+		foreach ($themes as $t) {
+			if (is_file("$t/theme.ini")) {
+				$ini = parse_ini_file("$t/theme.ini", true);
+				if ($ini['theme']['version'] && !$ini['theme']['disabled']) {
+					$entry = array();
+					$entry["path"] = $t;
+					$entry["base"] = basename($t);
+					$entry["name"] = $ini['theme']['name'];
+					$entry["version"] = $ini['theme']['version'];
+					$entry["author"] = $ini['theme']['author'];
+					$entry["options"] = $ini['theme']['options'];
+					array_push($rv, $entry);
+				}
+			}
+		}
+
+		return $rv;
+	}
+
+	function logout_user() {
+		session_destroy();
+		if (isset($_COOKIE[session_name()])) {
+		   setcookie(session_name(), '', time()-42000, '/');
+		}
+	}
+
+	function format_warning($msg, $id = "") {
+		global $link;
+		return "<div class=\"warning\" id=\"$id\"> 
+			<img src=\"".theme_image($link, "images/sign_excl.png")."\">$msg</div>";
+	}
+
+	function format_notice($msg) {
+		global $link;
+		return "<div class=\"notice\" id=\"$id\"> 
+			<img src=\"".theme_image($link, "images/sign_info.png")."\">$msg</div>";
+	}
+
+	function format_error($msg) {
+		global $link;
+		return "<div class=\"error\" id=\"$id\"> 
+			<img src=\"".theme_image($link, "images/sign_excl.png")."\">$msg</div>";
+	}
+
+	function print_notice($msg) {
+		return print format_notice($msg);
+	}
+
+	function print_warning($msg) {
+		return print format_warning($msg);
+	}
+
+	function print_error($msg) {
+		return print format_error($msg);
+	}
+
+
+	function T_sprintf() {
+		$args = func_get_args();
+		return vsprintf(__(array_shift($args)), $args);
+	}
+	
+	function _debug($msg) {
+		$ts = strftime("%H:%M:%S", time());
+		if (function_exists('posix_getpid')) {
+			$ts = "$ts/" . posix_getpid();
+		}
+		print "[$ts] $msg\n";
+	} // function _debug
+
+	function file_is_locked($filename) {
+		if (function_exists('flock')) {
+			error_reporting(0);
+			$fp = fopen(LOCK_DIRECTORY . "/$filename", "r");
+			error_reporting(DEFAULT_ERROR_LEVEL);
+			if ($fp) {
+				if (flock($fp, LOCK_EX | LOCK_NB)) {
+					flock($fp, LOCK_UN);
+					fclose($fp);
+					return false;
+				}
+				fclose($fp);
+				return true;
+			} else {
+				return false;
+			}
+		}
+		return true; // consider the file always locked and skip the test
+	}
+
+	function make_lockfile($filename) {
+		$fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
+
+		if (flock($fp, LOCK_EX | LOCK_NB)) {		
+			return $fp;
+		} else {
+			return false;
+		}
+	}
+
+	# TODO return actual nick, not hardcoded one
+	function get_nick($link, $connection_id) {
+		$result = db_query($link, "SELECT nick FROM ttirc_connections
+			WHERE id ='$connection_id'");
+
+		if (db_num_rows($result) == 1) {
+			return db_fetch_result($result, 0, "nick");
+		} else {
+			return "?UNKNOWN?";
+		}
+	}
+
+	function push_message($link, $connection_id, $destination, $message, 
+		$incoming = false) {
+
+		$incoming = bool_to_sql_bool($incoming);
+
+		if ($destination != "---") {
+			$my_nick = get_nick($link, $connection_id);
+		} else {
+			$my_nick = "-IRC-";
+		}
+
+		db_query($link, "INSERT INTO ttirc_messages 
+			(incoming, connection_id, destination, sender, message) VALUES
+			($incoming, $connection_id, '$destination', '$my_nick', '$message')");
+	}
+
+	function get_new_lines($link, $last_id) {
+
+		$result = db_query($link, "SELECT ttirc_messages.id,
+			sender, destination, message, ".SUBSTRING_FOR_DATE."(ts,12,8) AS ts
+			FROM ttirc_messages, ttirc_connections WHERE
+			connection_id = ttirc_connections.id AND
+			active = true AND
+			ts > NOW() - INTERVAL '1 hour' AND
+			ttirc_messages.id > '$last_id' AND 
+			owner_uid = ".$_SESSION["uid"]." ORDER BY ttirc_messages.id");
+
+		$lines = array();
+
+		while ($line = db_fetch_assoc($result)) {
+			$line["message"] = htmlspecialchars($line["message"]);
+			array_push($lines, $line);
+		}
+
+		return $lines;
+
+	}
+
+	function get_nick_list($link, $active_chan = false) {
+
+		if ($active_chan && $active_chan != "---") {
+			$active_chan_qpart = "destination = '$active_chan' AND";
+		} else {
+			$active_chan_qpart = "";
+		}
+
+		$result = db_query($link, "SELECT nicklist,destination
+			FROM ttirc_destinations, ttirc_connections 
+			WHERE connection_id = ttirc_connections.id AND 
+			active = true AND
+			$active_chan_qpart
+			owner_uid = ".$_SESSION["uid"]);
+
+		$rv = array();
+
+		while ($line = db_fetch_assoc($result)) {
+			$rv[$line["destination"]] = json_decode($line["nicklist"]);
+		}
+
+		return $rv;
+	}
+
+	function get_conn_info($link) {
+
+		$result = db_query($link, "SELECT * FROM ttirc_connections
+			WHERE owner_uid = ".$_SESSION["uid"]);
+	
+		$conn = array();
+
+		while ($line = db_fetch_assoc($result)) {
+			array_push($conn, $line);
+		}
+
+		return $conn;
+
+	}
+
+	function sql_bool_to_string($s) {
+		if ($s == "t" || $s == "1") {
+			return "true";
+		} else {
+			return "false";
+		}
+	}
+
+	function sql_bool_to_bool($s) {
+		if ($s == "t" || $s == "1") {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	function bool_to_sql_bool($s) {
+		if ($s) {
+			return "true";
+		} else {
+			return "false";
+		}
+	}
+
+	function sanity_check($link) {
+
+		error_reporting(0);
+
+		$error_code = 0;
+		$schema_version = get_schema_version($link);
+
+		if ($schema_version != SCHEMA_VERSION) {
+			$error_code = 5;
+		}
+
+		if (DB_TYPE == "mysql") {
+			$result = db_query($link, "SELECT true", false);
+			if (db_num_rows($result) != 1) {
+				$error_code = 10;
+			}
+		}
+
+		if (db_escape_string("testTEST") != "testTEST") {
+			$error_code = 12;
+		}
+
+		error_reporting (DEFAULT_ERROR_LEVEL);
+
+		if ($error_code != 0) {
+			print json_encode(array("error" => $error_code));
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+?>

+ 44 - 0
handle.php

@@ -0,0 +1,44 @@
+#!/usr/bin/php
+<?php
+	require_once "config.php";
+	require_once "functions.php";
+	require_once "db.php";
+	require_once "connection.php";
+
+	$link = db_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
+
+	init_connection($link);
+
+	$connection_id = db_escape_string($argv[1]);
+
+	$result = db_query($link, "SELECT * FROM ttirc_connections
+		WHERE id = $connection_id");
+
+	if (db_num_rows($result) == 1) {
+
+		$line = db_fetch_assoc($result);
+
+		_debug("[CHILD] connecting to server " . $line["server"]);
+	
+		$connection = new Connection($link, $connection_id, $line["encoding"], 
+			$line["last_sent_id"]);
+		$connection->setDebug(false);
+		$connection->setUser($line["ident"], $line['nick'], 
+			$line['realname'], '+i');
+		$connection->setServer($line["server"], $line["port"]);
+	
+		if ($connection->connect()) {
+
+			$result = db_query($link, "SELECT destination FROM ttirc_destinations
+				WHERE connection_id = $connection_id AND auto_join = true");
+
+			while ($line = db_fetch_assoc($result)) {
+				$connection->join($line["destination"]);
+			}
+
+			$connection->run();
+		}
+	}
+
+	db_close($link);
+?>

BIN
images/logo.png


+ 164 - 0
images/logo.svg

@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="155.00000pt"
+   height="25.000000pt"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="logo.svg"
+   inkscape:export-filename="/Users/fox/Desktop/logo.png"
+   inkscape:export-xdpi="101.71"
+   inkscape:export-ydpi="101.71"
+   version="1.1">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3674">
+      <stop
+         id="stop3676"
+         offset="0"
+         style="stop-color:#ffffff;stop-opacity:1;" />
+      <stop
+         id="stop3678"
+         offset="1"
+         style="stop-color:#b5f6c4;stop-opacity:1;" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 15.625 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="193.75 : 15.625 : 1"
+       inkscape:persp3d-origin="96.875 : 10.416667 : 1"
+       id="perspective2900" />
+    <linearGradient
+       id="linearGradient3112">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3114" />
+      <stop
+         style="stop-color:#b5cdf6;stop-opacity:1;"
+         offset="1"
+         id="stop3116" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2800">
+      <stop
+         style="stop-color:#000000;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop2802" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop2804" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2782">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop2784" />
+      <stop
+         style="stop-color:#f6d6b5;stop-opacity:1;"
+         offset="1"
+         id="stop2786" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3112"
+       id="linearGradient3118"
+       x1="50"
+       y1="12.985595"
+       x2="50"
+       y2="31.920942"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-0.474438,0.321428)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3674"
+       id="linearGradient3124"
+       gradientUnits="userSpaceOnUse"
+       x1="50"
+       y1="12.985595"
+       x2="50"
+       y2="31.920942"
+       gradientTransform="translate(139.6953,0.37974)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="5.6"
+     inkscape:cx="124.51161"
+     inkscape:cy="6.3105931"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     inkscape:window-width="1596"
+     inkscape:window-height="1153"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     showgrid="false"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <text
+       xml:space="preserve"
+       style="font-size:28px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:url(#linearGradient3118);fill-opacity:1;stroke:#88b0f0;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Verdana"
+       x="3.2755625"
+       y="24.07143"
+       id="text1306"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/fox/public_html/testbox/tt-rss-blue/images/ttrss_logo.png"
+       inkscape:export-xdpi="180.00000"
+       inkscape:export-ydpi="180.00000"><tspan
+         sodipodi:role="line"
+         id="tspan1308"
+         x="3.2755625"
+         y="24.07143"
+         style="fill:url(#linearGradient3118);fill-opacity:1;stroke:#88b0f0;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">tiny tiny</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:28px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient3124);fill-opacity:1;stroke:#00d210;stroke-width:0.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Verdana"
+       x="143.44531"
+       y="24.129744"
+       id="text3120"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/fox/public_html/testbox/tt-rss-blue/images/ttrss_logo.png"
+       inkscape:export-xdpi="180.00000"
+       inkscape:export-ydpi="180.00000"><tspan
+         sodipodi:role="line"
+         id="tspan3122"
+         x="143.44531"
+         y="24.129744"
+         style="fill:url(#linearGradient3124);fill-opacity:1;stroke:#00d210;stroke-width:0.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">irc</tspan></text>
+  </g>
+</svg>

BIN
images/logo_big.png


BIN
images/shadow.png


BIN
images/shadow_white.png


BIN
images/sign_excl.png


BIN
images/sign_info.png


BIN
images/sign_quest.png


+ 129 - 0
index.php

@@ -0,0 +1,129 @@
+<?php
+	error_reporting(E_ERROR | E_WARNING | E_PARSE);
+
+	require_once "functions.php"; 
+	require_once "sessions.php";
+	require_once "sanity_check.php";
+	require_once "version.php"; 
+	require_once "config.php";
+
+	$link = db_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);	
+
+	login_sequence($link);
+
+	$dt_add = get_script_dt_add();
+
+	no_cache_incantation();
+
+	header('Content-Type: text/html; charset=utf-8');
+	
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+	<title>Tiny Tiny IRC</title>
+
+	<link rel="stylesheet" type="text/css" href="tt-irc.css?<?php echo $dt_add ?>"/>
+
+	<?php	$user_theme = get_user_theme_path($link);
+		if ($user_theme) { ?>
+			<link rel="stylesheet" type="text/css" href="<?php echo $user_theme ?>/theme.css?<?php echo $dt_add ?>">
+	<?php } ?>
+
+	<link rel="shortcut icon" type="image/png" href="images/favicon.png"/>
+
+	<script type="text/javascript" src="lib/prototype.js"></script>
+	<script type="text/javascript" src="lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls"></script>
+	<script type="text/javascript" charset="utf-8" src="tt-irc.js?<?php echo $dt_add ?>"></script>
+	<script type="text/javascript" charset="utf-8" src="functions.js?<?php echo $dt_add ?>"></script>
+	
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+	<script type="text/javascript">
+		Event.observe(window, 'load', function() {
+			init();
+		});
+	</script>
+</head>
+<body class="main">
+
+<div id="overlay" style="display : block">
+	<div id="overlay_inner">
+		<?php echo __("Loading, please wait...") ?>
+
+		<div id="l_progress_o">
+			<div id="l_progress_i"></div>
+		</div>
+
+	<noscript>
+		<p><?php print_error(__("Your browser doesn't support Javascript, which is required
+		for this application to function properly. Please check your
+		browser settings.")) ?></p>
+	</noscript>
+	</div>
+</div> 
+
+<div id="dialog_overlay" style="display : none"> </div>
+
+<ul id="debug_output" style='display : none'><li>&nbsp;</li></ul>
+
+<div id="infoBoxShadow" style="display : none"><div id="infoBox">&nbsp;</div></div>
+
+<div id="errorBoxShadow" style="display : none">
+	<div id="errorBox">
+	<div id="xebTitle"><?php echo __('Fatal Exception') ?></div><div id="xebContent">&nbsp;</div>
+		<div id="xebBtn" align='center'>
+			<button onclick="closeErrorBox()"><?php echo __('Close this window') ?></button>
+		</div>
+	</div>
+</div>
+
+<div id="header">
+	<div class="topLinks" id="topLinks">
+
+	<span id="topLinksOnline">
+
+	<?php if (!SINGLE_USER_MODE) { ?>
+			<?php echo __('Hello,') ?> <b><?php echo $_SESSION["name"] ?></b> |
+	<?php } ?>
+	<a href="#" onclick="showPreferences()"><?php echo __('Preferences') ?></a>
+
+	<?php if (!SINGLE_USER_MODE) { ?>
+			| <a href="logout.php"><?php echo __('Logout') ?></a>
+	<?php } ?>
+
+	</div>
+
+	<img src="<?php echo theme_image($link, 'images/logo.png') ?>" alt="Tiny Tiny IRC"/>	
+</div>
+
+<div id="actions">
+	<button onclick="toggle_debug()">Debug</button> 
+	<button id="connect-btn" disabled='true' onclick="toggle_connect(this)">Connect</button>
+</div>
+
+<div id="tabs">
+	<div class="first">&nbsp;</div>
+	<div class="selected" onclick="change_tab(this)" id="tab----">Console</div>
+	<!-- <div>#test</div>
+	<div>#wtf</div> -->
+</div>
+
+<div id="log-outer">
+	<div id="log"><ul id="log-list"></ul></div>	
+</div>
+
+<div id="userlist">
+	<div id="userlist-inner"><ul id="userlist-list">
+	</ul></div>
+</div>
+
+<div id="input">
+	<input id="input-prompt" onchange="send(this)"></input>
+</div>
+
+<?php db_close($link); ?>
+
+</body>
+</html>

+ 184 - 0
lib/accept-to-gettext.php

@@ -0,0 +1,184 @@
+<?php
+/*
+ * accept-to-gettext.inc -- convert information in 'Accept-*' headers to
+ * gettext language identifiers.
+ * Copyright (c) 2003, Wouter Verhelst <[email protected]>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Usage:
+ *
+ *  $locale=al2gt(<array of supported languages/charsets in gettext syntax>,
+ *                <MIME type of document>);
+ *  setlocale('LC_ALL', $locale); // or 'LC_MESSAGES', or whatever...
+ *
+ * Example:
+ *
+ *  $langs=array('nl_BE.ISO-8859-15','nl_BE.UTF-8','en_US.UTF-8','en_GB.UTF-8');
+ *  $locale=al2gt($langs, 'text/html');
+ *  setlocale('LC_ALL', $locale);
+ *
+ * Note that this will send out header information (to be
+ * RFC2616-compliant), so it must be called before anything is sent to
+ * the user.
+ * 
+ * Assumptions made:
+ * * Charset encodings are written the same way as the Accept-Charset
+ *   HTTP header specifies them (RFC2616), except that they're parsed
+ *   case-insensitive.
+ * * Country codes and language codes are the same in both gettext and
+ *   the Accept-Language syntax (except for the case differences, which
+ *   are dealt with easily). If not, some input may be ignored.
+ * * The provided gettext-strings are fully qualified; i.e., no "en_US";
+ *   always "en_US.ISO-8859-15" or "en_US.UTF-8", or whichever has been
+ *   used. "en.ISO-8859-15" is OK, though.
+ * * The language is more important than the charset; i.e., if the
+ *   following is given:
+ * 
+ *   Accept-Language: nl-be, nl;q=0.8, en-us;q=0.5, en;q=0.3
+ *   Accept-Charset: ISO-8859-15, utf-8;q=0.5
+ *
+ *   And the supplied parameter contains (amongst others) nl_BE.UTF-8
+ *   and nl.ISO-8859-15, then nl_BE.UTF-8 will be picked.
+ * 
+ * $Log: accept-to-gettext.inc,v $
+ * Revision 1.1.1.1  2003/11/19 19:31:15  wouter
+ * * moved to new CVS repo after death of the old
+ * * Fixed code to apply a default to both Accept-Charset and
+ *   Accept-Language if none of those headers are supplied; patch from
+ *   Dominic Chambers <[email protected]>
+ *
+ * Revision 1.2  2003/08/14 10:23:59  wouter
+ * Removed little error in Content-Type header syntaxis.
+ *
+ */
+
+/* not really important, this one; perhaps I could've put it inline with
+ * the rest. */
+function find_match($curlscore,$curcscore,$curgtlang,$langval,$charval,
+                    $gtlang)
+{
+  if($curlscore < $langval) {
+    $curlscore=$langval;
+    $curcscore=$charval;
+    $curgtlang=$gtlang;
+  } else if ($curlscore == $langval) {
+    if($curcscore < $charval) {
+      $curcscore=$charval;
+      $curgtlang=$gtlang;
+    }
+  }
+  return array($curlscore, $curcscore, $curgtlang);
+}
+
+function al2gt($gettextlangs, $mime) {
+  /* default to "everything is acceptable", as RFC2616 specifies */
+  $acceptLang=(($_SERVER["HTTP_ACCEPT_LANGUAGE"] == '') ? '*' :
+  	$_SERVER["HTTP_ACCEPT_LANGUAGE"]);
+  $acceptChar=(($_SERVER["HTTP_ACCEPT_CHARSET"] == '') ? '*' :
+  	$_SERVER["HTTP_ACCEPT_CHARSET"]);
+  [email protected]_split("/,/",$acceptLang);
+  [email protected]_split("/,/",$acceptChar);
+  
+  /* Parse the contents of the Accept-Language header.*/
+  foreach($alparts as $part) {
+    $part=trim($part);
+    if(preg_match("/;/", $part)) {
+      [email protected]_split("/;/",$part);
+      [email protected]_split("/=/",$lang[1]);
+      $alscores[$lang[0]]=$score[1];
+    } else {
+      $alscores[$part]=1;
+    }
+  }
+
+  /* Do the same for the Accept-Charset header. */
+
+  /* RFC2616: ``If no "*" is present in an Accept-Charset field, then
+   * all character sets not explicitly mentioned get a quality value of
+   * 0, except for ISO-8859-1, which gets a quality value of 1 if not
+   * explicitly mentioned.''
+   * 
+   * Making it 2 for the time being, so that we
+   * can distinguish between "not specified" and "specified as 1" later
+   * on. */
+  $acscores["ISO-8859-1"]=2;
+
+  foreach($acparts as $part) {
+    $part=trim($part);
+    if(preg_match("/;/", $part)) {
+      [email protected]_split("/;/",$part);
+      [email protected]_split("/=/",$cs[1]);
+      $acscores[strtoupper($cs[0])]=$score[1];
+    } else {
+      $acscores[strtoupper($part)]=1;
+    }
+  }
+  if($acscores["ISO-8859-1"]==2) {
+    $acscores["ISO-8859-1"]=(isset($acscores["*"])?$acscores["*"]:1);
+  }
+
+  /* 
+   * Loop through the available languages/encodings, and pick the one
+   * with the highest score, excluding the ones with a charset the user
+   * did not include.
+   */
+  $curlscore=0;
+  $curcscore=0;
+  $curgtlang=NULL;
+  foreach($gettextlangs as $gtlang) {
+
+    $tmp1=preg_replace("/\_/","-",$gtlang);
+    [email protected]_split("/\./",$tmp1);
+    $allang=strtolower($tmp2[0]);
+    $gtcs=strtoupper($tmp2[1]);
+    [email protected]_split("/-/",$allang);
+
+    $testvals=array(
+         array($alscores[$allang], $acscores[$gtcs]),
+	 array($alscores[$noct[0]], $acscores[$gtcs]),
+	 array($alscores[$allang], $acscores["*"]),
+	 array($alscores[$noct[0]], $acscores["*"]),
+	 array($alscores["*"], $acscores[$gtcs]),
+	 array($alscores["*"], $acscores["*"]));
+
+    $found=FALSE;
+    foreach($testvals as $tval) {
+      if(!$found && isset($tval[0]) && isset($tval[1])) {
+        $arr=find_match($curlscore, $curcscore, $curgtlang, $tval[0],
+	          $tval[1], $gtlang);
+        $curlscore=$arr[0];
+        $curcscore=$arr[1];
+        $curgtlang=$arr[2];
+	$found=TRUE;
+      }
+    }
+  }
+
+  /* We must re-parse the gettext-string now, since we may have found it
+   * through a "*" qualifier.*/
+  
+  [email protected]_split("/\./",$curgtlang);
+  $tmp=strtolower($gtparts[0]);
+  $lang=preg_replace("/\_/", "-", $tmp);
+  $charset=$gtparts[1];
+
+  header("Content-Language: $lang");
+  header("Content-Type: $mime; charset=$charset");
+
+  return $curgtlang;
+}
+
+?>

+ 189 - 0
lib/gettext/README

@@ -0,0 +1,189 @@
+PHP-gettext 1.0
+
+Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan
+Licensed under GPLv2 (or any later version, see COPYING)
+
+[1] PHP is actually cyrillic, and translates roughly to 
+    "works-doesn't-work" (UTF-8: Ради-Не-Ради)
+
+
+Introduction
+
+    How many times did you look for a good translation tool, and
+    found out that gettext is best for the job? Many times.
+
+    How many times did you try to use gettext in PHP, but failed
+    miserably, because either your hosting provider didn't support
+    it, or the server didn't have adequate locale? Many times.
+
+    Well, this is a solution to your needs. It allows using gettext
+    tools for managing translations, yet it doesn't require gettext
+    library at all. It parses generated MO files directly, and thus
+    might be a bit slower than the (maybe provided) gettext library.
+
+    PHP-gettext is a simple reader for GNU gettext MO files. Those
+    are binary containers for translations, produced by GNU msgfmt.
+
+Why?
+
+    I got used to having gettext work even without gettext
+    library. It's there in my favourite language Python, so I was
+    surprised that I couldn't find it in PHP. I even Googled for it,
+    but to no avail.
+
+    So, I said, what the heck, I'm going to write it for this
+    disguisting language of PHP, because I'm often constrained to it.
+
+Features
+
+  o Support for simple translations
+    Just define a simple alias for translate() function (suggested
+    use of _() or gettext(); see provided example).
+
+  o Support for ngettext calls (plural forms, see a note under bugs)
+    You may also use plural forms. Translations in MO files need to
+    provide this, and they must also provide "plural-forms" header.
+    Please see 'info gettext' for more details.
+
+  o Support for reading straight files, or strings (!!!)
+    Since I can imagine many different backends for reading in the MO
+    file data, I used imaginary abstract class StreamReader to do all
+    the input (check streams.php). For your convenience, I've already
+    provided two classes for reading files: FileReader and
+    StringReader (CachedFileReader is a combination of the two: it 
+    loads entire file contents into a string, and then works on that). 
+    See example below for usage. You can for instance use StringReader 
+    when you read in data from a database, or you can create your own 
+    derivative of StreamReader for anything you like. 
+    
+
+Bugs
+
+    Plural-forms field in MO header (translation for empty string,
+    i.e. "") is treated according to PHP syntactic rules (it's
+    eval()ed). Since these should actually follow C syntax, there are
+    some problems.
+
+    For instance, I'm used to using this:
+      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
+         n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
+    but it fails with PHP (it sets $plural=2 instead of 0 for $n==1).
+
+    The fix is usually simple, but I'm lazy to go into the details of
+    PHP operator precedence, and maybe try to fix it. In here, I had
+    to put everything after the first ':' in parenthesis:
+      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
+         (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
+    That works, and I'm satisfied.
+
+    Besides this one, there are probably a bunch of other bugs, since
+    I hate PHP (did I mention it already? no? strange), and don't
+    know it very well. So, feel free to fix any of those and report
+    them back to me at <[email protected]>.
+
+Usage
+
+    Put files streams.php and gettext.php somewhere you can load them
+    from, and require 'em in where you want to use them.
+
+    Then, create one 'stream reader' (a class that provides functions
+    like read(), seekto(), currentpos() and length()) which will
+    provide data for the 'gettext_reader', with eg.
+      $streamer = new FileStream('data.mo');
+
+    Then, use that as a parameter to gettext_reader constructor:
+      $wohoo = new gettext_reader($streamer);
+
+    If you want to disable pre-loading of entire message catalog in 
+    memory (if, for example, you have a multi-thousand message catalog 
+    which you'll use only occasionally), use "false" for second 
+    parameter to gettext_reader constructor:
+      $wohoo = new gettext_reader($streamer, false);
+
+    From now on, you have all the benefits of gettext data at your
+    disposal, so may run: 
+      print $wohoo->translate("This is a test");
+      print $wohoo->ngettext("%d bird", "%d birds", $birds);
+
+    You might need to pass parameter "-k" to xgettext to make it
+    extract all the strings. In above example, try with 
+      xgettext -ktranslate -kngettext:1,2 file.php
+    what should create messages.po which contains two messages for
+    translation.
+
+    I suggest creating simple aliases for these functions (see
+    example/pigs.php for how do I do it, which means it's probably a
+    bad way).
+
+
+Usage with gettext.inc (standard gettext interfaces emulation)
+
+    Check example in examples/pig_dropin.php, basically you include 
+    gettext.inc and use all the standard gettext interfaces as 
+    documented on:
+
+       http://www.php.net/gettext
+
+    The only catch is that you can check return value of setlocale()
+    to see if your locale is system supported or not.
+
+
+Example
+
+    See in examples/ subdirectory. There are a couple of files.
+    pigs.php is an example, serbian.po is a translation to Serbian
+    language, and serbian.mo is generated with
+       msgfmt -o serbian.mo serbian.po
+    There is also simple "update" script that can be used to generate
+    POT file and to update the translation using msgmerge.
+
+Interesting TODO:
+
+  o Try to parse "plural-forms" header field, and to follow C syntax
+    rules. This won't be easy.
+
+Boring TODO:
+
+  o Learn PHP and fix bugs, slowness and other stuff resulting from
+    my lack of knowledge (but *maybe*, it's not my knowledge that is
+    bad, but PHP itself ;-).  
+
+    (This is mostly done thanks to Nico Kaiser.)
+
+  o Try to use hash tables in MO files: with pre-loading, would it 
+    be useful at all?
+
+Never-asked-questions:
+
+  o Why did you mark this as version 1.0 when this is the first code
+    release?
+
+    Well, it's quite simple. I consider that the first released thing
+    should be labeled "version 1" (first, right?). Zero is there to
+    indicate that there's zero improvement and/or change compared to 
+    "version 1".
+
+    I plan to use version numbers 1.0.* for small bugfixes, and to
+    release 1.1 as "first stable release of version 1".
+
+    This may trick someone that this is actually useful software, but
+    as with any other free software, I take NO RESPONSIBILITY for
+    creating such a masterpiece that will smoke crack, trash your
+    hard disk, and make lasers in your CD device dance to the tune of
+    Mozart's 40th Symphony (there is one like that, right?).
+
+  o Can I...?
+    
+    Yes, you can. This is free software (as in freedom, free speech),
+    and you might do whatever you wish with it, provided you do not
+    limit freedom of others (GPL).
+
+    I'm considering licensing this under LGPL, but I *do* want
+    *every* PHP-gettext user to contribute and respect ideas of free
+    software, so don't count on it happening anytime soon.
+
+    I'm sorry that I'm taking away your freedom of taking others'
+    freedom away, but I believe that's neglible as compared to what
+    freedoms you could take away. ;-)
+
+    Uhm, whatever.

+ 318 - 0
lib/gettext/gettext.inc

@@ -0,0 +1,318 @@
+<?php
+/*
+   Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
+   
+   Drop in replacement for native gettext.
+   
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+/*
+LC_CTYPE		0
+LC_NUMERIC	1
+LC_TIME			2
+LC_COLLATE	3
+LC_MONETARY	4
+LC_MESSAGES	5
+LC_ALL			6
+*/
+
+require('streams.php');
+require('gettext.php');
+
+
+// Variables
+
+global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
+$text_domains = array();
+$default_domain = 'messages';
+$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
+$EMULATEGETTEXT = 0;
+$CURRENTLOCALE = '';
+
+
+// Utility functions
+
+/**
+ * Utility function to get a StreamReader for the given text domain.
+ */
+function _get_reader($domain=null, $category=5, $enable_cache=true) {
+	global $text_domains, $default_domain, $LC_CATEGORIES;
+	if (!isset($domain)) $domain = $default_domain;
+	if (!isset($text_domains[$domain]->l10n)) {
+		// get the current locale
+		$locale = _setlocale(LC_MESSAGES, 0);
+		$p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './';
+		$path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo";
+		if (file_exists($path)) {
+			$input = new FileReader($path);
+		}
+		else {
+			$input = null;
+		}
+		$text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache);
+	}
+	return $text_domains[$domain]->l10n;
+}
+
+/**
+ * Returns whether we are using our emulated gettext API or PHP built-in one.
+ */
+function locale_emulation() {
+    global $EMULATEGETTEXT;
+    return $EMULATEGETTEXT;
+}
+
+/**
+ * Checks if the current locale is supported on this system.
+ */
+function _check_locale() {
+    global $EMULATEGETTEXT;
+    return !$EMULATEGETTEXT;
+}
+
+/**
+ * Get the codeset for the given domain.
+ */
+function _get_codeset($domain=null) {
+	global $text_domains, $default_domain, $LC_CATEGORIES;
+	if (!isset($domain)) $domain = $default_domain;
+	return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
+}
+
+/**
+ * Convert the given string to the encoding set by bind_textdomain_codeset.
+ */
+function _encode($text) {
+	$source_encoding = mb_detect_encoding($text);
+	$target_encoding = _get_codeset();
+	if ($source_encoding != $target_encoding) {
+		return mb_convert_encoding($text, $target_encoding, $source_encoding);
+	}
+	else {
+		return $text;
+	}
+}
+
+
+
+
+// Custom implementation of the standard gettext related functions
+
+/**
+ * Sets a requested locale, if needed emulates it.
+ */
+function _setlocale($category, $locale) {
+    global $CURRENTLOCALE, $EMULATEGETTEXT;
+    if ($locale === 0) { // use === to differentiate between string "0"
+        if ($CURRENTLOCALE != '') 
+            return $CURRENTLOCALE;
+        else 
+            // obey LANG variable, maybe extend to support all of LC_* vars
+            // even if we tried to read locale without setting it first
+            return _setlocale($category, $CURRENTLOCALE);
+    } else {
+        $ret = 0;
+        if (function_exists('setlocale')) // I don't know if this ever happens ;)
+           $ret = setlocale($category, $locale);
+        if (($ret and $locale == '') or ($ret == $locale)) {
+            $EMULATEGETTEXT = 0;
+            $CURRENTLOCALE = $ret;
+        } else {
+  	    if ($locale == '') // emulate variable support
+ 	        $CURRENTLOCALE = getenv('LANG');
+	    else
+	        $CURRENTLOCALE = $locale;
+            $EMULATEGETTEXT = 1;
+        }
+        return $CURRENTLOCALE;
+    }
+}
+
+/**
+ * Sets the path for a domain.
+ */
+function _bindtextdomain($domain, $path) {
+	global $text_domains;
+	// ensure $path ends with a slash
+	if ($path[strlen($path) - 1] != '/') $path .= '/';
+	elseif ($path[strlen($path) - 1] != '\\') $path .= '\\';
+	$text_domains[$domain]->path = $path;
+}
+
+/**
+ * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
+ */
+function _bind_textdomain_codeset($domain, $codeset) {
+	global $text_domains;
+	$text_domains[$domain]->codeset = $codeset;
+}
+
+/**
+ * Sets the default domain.
+ */
+function _textdomain($domain) {
+	global $default_domain;
+	$default_domain = $domain;
+}
+
+/**
+ * Lookup a message in the current domain.
+ */
+function _gettext($msgid) {
+	$l10n = _get_reader();
+	//return $l10n->translate($msgid);
+	return _encode($l10n->translate($msgid));
+}
+/**
+ * Alias for gettext.
+ */
+function __($msgid) {
+	return _gettext($msgid);
+}
+/**
+ * Plural version of gettext.
+ */
+function _ngettext($single, $plural, $number) {
+	$l10n = _get_reader();
+	//return $l10n->ngettext($single, $plural, $number);
+	return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+/**
+ * Override the current domain.
+ */
+function _dgettext($domain, $msgid) {
+	$l10n = _get_reader($domain);
+	//return $l10n->translate($msgid);
+	return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dgettext.
+ */
+function _dngettext($domain, $single, $plural, $number) {
+	$l10n = _get_reader($domain);
+	//return $l10n->ngettext($single, $plural, $number);
+	return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+/**
+ * Overrides the domain and category for a single lookup.
+ */
+function _dcgettext($domain, $msgid, $category) {
+	$l10n = _get_reader($domain, $category);
+	//return $l10n->translate($msgid);
+	return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dcgettext.
+ */
+function _dcngettext($domain, $single, $plural, $number, $category) {
+	$l10n = _get_reader($domain, $category);
+	//return $l10n->ngettext($single, $plural, $number);
+	return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+
+
+// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system.
+// Use the standard impl if the current locale is supported, use the custom impl otherwise.
+
+function T_setlocale($category, $locale) {
+    return _setlocale($category, $locale);
+}
+
+function T_bindtextdomain($domain, $path) {
+	if (_check_locale()) return bindtextdomain($domain, $path);
+	else return _bindtextdomain($domain, $path);
+}
+function T_bind_textdomain_codeset($domain, $codeset) {
+    // bind_textdomain_codeset is available only in PHP 4.2.0+
+	if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset);
+	else return _bind_textdomain_codeset($domain, $codeset);
+}
+function T_textdomain($domain) {
+	if (_check_locale()) return textdomain($domain);
+	else return _textdomain($domain);
+}
+function T_gettext($msgid) {
+	if (_check_locale()) return gettext($msgid);
+	else return _gettext($msgid);
+}
+function T_($msgid) {
+	if (_check_locale()) return _($msgid);
+	return __($msgid);
+}
+function T_ngettext($single, $plural, $number) {
+	if (_check_locale()) return ngettext($single, $plural, $number);
+	else return _ngettext($single, $plural, $number);
+}
+function T_dgettext($domain, $msgid) {
+	if (_check_locale()) return dgettext($domain, $msgid);
+	else return _dgettext($domain, $msgid);
+}
+function T_dngettext($domain, $single, $plural, $number) {
+	if (_check_locale()) return dngettext($domain, $single, $plural, $number);
+	else return _dngettext($domain, $single, $plural, $number);
+}
+function T_dcgettext($domain, $msgid, $category) {
+	if (_check_locale()) return dcgettext($domain, $msgid, $category);
+	else return _dcgettext($domain, $msgid, $category);
+}
+function T_dcngettext($domain, $single, $plural, $number, $category) {
+	if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category);
+	else return _dcngettext($domain, $single, $plural, $number, $category);
+}
+
+
+
+// Wrappers used as a drop in replacement for the standard gettext functions
+
+if (!function_exists('gettext')) {
+	function bindtextdomain($domain, $path) {
+		return _bindtextdomain($domain, $path);
+	}
+	function bind_textdomain_codeset($domain, $codeset) {
+		return _bind_textdomain_codeset($domain, $codeset);
+	}
+	function textdomain($domain) {
+		return _textdomain($domain);
+	}
+	function gettext($msgid) {
+		return _gettext($msgid);
+	}
+	function _($msgid) {
+		return __($msgid);
+	}
+	function ngettext($single, $plural, $number) {
+		return _ngettext($single, $plural, $number);
+	}
+	function dgettext($domain, $msgid) {
+		return _dgettext($domain, $msgid);
+	}
+	function dngettext($domain, $single, $plural, $number) {
+		return _dngettext($domain, $single, $plural, $number);
+	}
+	function dcgettext($domain, $msgid, $category) {
+		return _dcgettext($domain, $msgid, $category);
+	}
+	function dcngettext($domain, $single, $plural, $number, $category) {
+		return _dcngettext($domain, $single, $plural, $number, $category);
+	}
+}
+
+?>

+ 358 - 0
lib/gettext/gettext.php

@@ -0,0 +1,358 @@
+<?php
+/*
+   Copyright (c) 2003 Danilo Segan <[email protected]>.
+   Copyright (c) 2005 Nico Kaiser <[email protected]>
+   
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+ 
+/**
+ * Provides a simple gettext replacement that works independently from
+ * the system's gettext abilities.
+ * It can read MO files and use them for translating strings.
+ * The files are passed to gettext_reader as a Stream (see streams.php)
+ * 
+ * This version has the ability to cache all strings and translations to
+ * speed up the string lookup.
+ * While the cache is enabled by default, it can be switched off with the
+ * second parameter in the constructor (e.g. whenusing very large MO files
+ * that you don't want to keep in memory)
+ */
+class gettext_reader {
+  //public:
+   var $error = 0; // public variable that holds error code (0 if no error)
+   
+   //private:
+  var $BYTEORDER = 0;        // 0: low endian, 1: big endian
+  var $STREAM = NULL;
+  var $short_circuit = false;
+  var $enable_cache = false;
+  var $originals = NULL;      // offset of original table
+  var $translations = NULL;    // offset of translation table
+  var $pluralheader = NULL;    // cache header field for plural forms
+  var $total = 0;          // total string count
+  var $table_originals = NULL;  // table for original strings (offsets)
+  var $table_translations = NULL;  // table for translated strings (offsets)
+  var $cache_translations = NULL;  // original -> translation mapping
+
+
+  /* Methods */
+  
+    
+  /**
+   * Reads a 32bit Integer from the Stream
+   * 
+   * @access private
+   * @return Integer from the Stream
+   */
+  function readint() {
+      if ($this->BYTEORDER == 0) {
+        // low endian
+        return array_shift(unpack('V', $this->STREAM->read(4)));
+      } else {
+        // big endian
+        return array_shift(unpack('N', $this->STREAM->read(4)));
+      }
+    }
+
+  /**
+   * Reads an array of Integers from the Stream
+   * 
+   * @param int count How many elements should be read
+   * @return Array of Integers
+   */
+  function readintarray($count) {
+    if ($this->BYTEORDER == 0) {
+        // low endian
+        return unpack('V'.$count, $this->STREAM->read(4 * $count));
+      } else {
+        // big endian
+        return unpack('N'.$count, $this->STREAM->read(4 * $count));
+      }
+  }
+  
+  /**
+   * Constructor
+   * 
+   * @param object Reader the StreamReader object
+   * @param boolean enable_cache Enable or disable caching of strings (default on)
+   */
+  function gettext_reader($Reader, $enable_cache = true) {
+    // If there isn't a StreamReader, turn on short circuit mode.
+    if (! $Reader || isset($Reader->error) ) {
+      $this->short_circuit = true;
+      return;
+    }
+    
+    // Caching can be turned off
+    $this->enable_cache = $enable_cache;
+
+    // $MAGIC1 = (int)0x950412de; //bug in PHP 5
+    $MAGIC1 = (int) - 1794895138;
+    // $MAGIC2 = (int)0xde120495; //bug
+    $MAGIC2 = (int) - 569244523;
+
+    $this->STREAM = $Reader;
+    $magic = $this->readint();
+    if ($magic == $MAGIC1) {
+      $this->BYTEORDER = 0;
+    } elseif ($magic == $MAGIC2) {
+      $this->BYTEORDER = 1;
+    } else {
+      $this->error = 1; // not MO file
+      return false;
+    }
+    
+    // FIXME: Do we care about revision? We should.
+    $revision = $this->readint();
+    
+    $this->total = $this->readint();
+    $this->originals = $this->readint();
+    $this->translations = $this->readint();
+  }
+  
+  /**
+   * Loads the translation tables from the MO file into the cache
+   * If caching is enabled, also loads all strings into a cache
+   * to speed up translation lookups
+   * 
+   * @access private
+   */
+  function load_tables() {
+    if (is_array($this->cache_translations) &&
+      is_array($this->table_originals) &&
+      is_array($this->table_translations))
+      return;
+    
+    /* get original and translations tables */
+    $this->STREAM->seekto($this->originals);
+    $this->table_originals = $this->readintarray($this->total * 2);
+    $this->STREAM->seekto($this->translations);
+    $this->table_translations = $this->readintarray($this->total * 2);
+    
+    if ($this->enable_cache) {
+      $this->cache_translations = array ();
+      /* read all strings in the cache */
+      for ($i = 0; $i < $this->total; $i++) {
+        $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
+        $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
+        $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
+        $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
+        $this->cache_translations[$original] = $translation;
+      }
+    }
+  }
+  
+  /**
+   * Returns a string from the "originals" table
+   * 
+   * @access private
+   * @param int num Offset number of original string
+   * @return string Requested string if found, otherwise ''
+   */
+  function get_original_string($num) {
+    $length = $this->table_originals[$num * 2 + 1];
+    $offset = $this->table_originals[$num * 2 + 2];
+    if (! $length)
+      return '';
+    $this->STREAM->seekto($offset);
+    $data = $this->STREAM->read($length);
+    return (string)$data;
+  }
+  
+  /**
+   * Returns a string from the "translations" table
+   * 
+   * @access private
+   * @param int num Offset number of original string
+   * @return string Requested string if found, otherwise ''
+   */
+  function get_translation_string($num) {
+    $length = $this->table_translations[$num * 2 + 1];
+    $offset = $this->table_translations[$num * 2 + 2];
+    if (! $length)
+      return '';
+    $this->STREAM->seekto($offset);
+    $data = $this->STREAM->read($length);
+    return (string)$data;
+  }
+  
+  /**
+   * Binary search for string
+   * 
+   * @access private
+   * @param string string
+   * @param int start (internally used in recursive function)
+   * @param int end (internally used in recursive function)
+   * @return int string number (offset in originals table)
+   */
+  function find_string($string, $start = -1, $end = -1) {
+    if (($start == -1) or ($end == -1)) {
+      // find_string is called with only one parameter, set start end end
+      $start = 0;
+      $end = $this->total;
+    }
+    if (abs($start - $end) <= 1) {
+      // We're done, now we either found the string, or it doesn't exist
+      $txt = $this->get_original_string($start);
+      if ($string == $txt)
+        return $start;
+      else
+        return -1;
+    } else if ($start > $end) {
+      // start > end -> turn around and start over
+      return $this->find_string($string, $end, $start);
+    } else {
+      // Divide table in two parts
+      $half = (int)(($start + $end) / 2);
+      $cmp = strcmp($string, $this->get_original_string($half));
+      if ($cmp == 0)
+        // string is exactly in the middle => return it
+        return $half;
+      else if ($cmp < 0)
+        // The string is in the upper half
+        return $this->find_string($string, $start, $half);
+      else
+        // The string is in the lower half
+        return $this->find_string($string, $half, $end);
+    }
+  }
+  
+  /**
+   * Translates a string
+   * 
+   * @access public
+   * @param string string to be translated
+   * @return string translated string (or original, if not found)
+   */
+  function translate($string) {
+    if ($this->short_circuit)
+      return $string;
+    $this->load_tables();     
+    
+    if ($this->enable_cache) {
+      // Caching enabled, get translated string from cache
+      if (array_key_exists($string, $this->cache_translations))
+        return $this->cache_translations[$string];
+      else
+        return $string;
+    } else {
+      // Caching not enabled, try to find string
+      $num = $this->find_string($string);
+      if ($num == -1)
+        return $string;
+      else
+        return $this->get_translation_string($num);
+    }
+  }
+
+  /**
+   * Get possible plural forms from MO header
+   * 
+   * @access private
+   * @return string plural form header
+   */
+  function get_plural_forms() {
+    // lets assume message number 0 is header  
+    // this is true, right?
+    $this->load_tables();
+    
+    // cache header field for plural forms
+    if (! is_string($this->pluralheader)) {
+      if ($this->enable_cache) {
+        $header = $this->cache_translations[""];
+      } else {
+        $header = $this->get_translation_string(0);
+      }
+      if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
+        $expr = $regs[1];
+      else
+        $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
+      $this->pluralheader = $expr;
+    }
+    return $this->pluralheader;
+  }
+
+  /**
+   * Detects which plural form to take
+   * 
+   * @access private
+   * @param n count
+   * @return int array index of the right plural form
+   */
+  function select_string($n) {
+    $string = $this->get_plural_forms();
+    $string = str_replace('nplurals',"\$total",$string);
+    $string = str_replace("n",$n,$string);
+    $string = str_replace('plural',"\$plural",$string);
+    
+    $total = 0;
+    $plural = 0;
+
+    eval("$string");
+    if ($plural >= $total) $plural = $total - 1;
+    return $plural;
+  }
+
+  /**
+   * Plural version of gettext
+   * 
+   * @access public
+   * @param string single
+   * @param string plural
+   * @param string number
+   * @return translated plural form
+   */
+  function ngettext($single, $plural, $number) {
+    if ($this->short_circuit) {
+      if ($number != 1)
+        return $plural;
+      else
+        return $single;
+    }
+
+    // find out the appropriate form
+    $select = $this->select_string($number); 
+    
+    // this should contains all strings separated by NULLs
+    $key = $single.chr(0).$plural;
+    
+    
+    if ($this->enable_cache) {
+      if (! array_key_exists($key, $this->cache_translations)) {
+        return ($number != 1) ? $plural : $single;
+      } else {
+        $result = $this->cache_translations[$key];
+        $list = explode(chr(0), $result);
+        return $list[$select];
+      }
+    } else {
+      $num = $this->find_string($key);
+      if ($num == -1) {
+        return ($number != 1) ? $plural : $single;
+      } else {
+        $result = $this->get_translation_string($num);
+        $list = explode(chr(0), $result);
+        return $list[$select];
+      }
+    }
+  }
+
+}
+
+?>

+ 166 - 0
lib/gettext/streams.php

@@ -0,0 +1,166 @@
+<?php
+/*
+   Copyright (c) 2003, 2005 Danilo Segan <[email protected]>.
+
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+
+// Simple class to wrap file streams, string streams, etc.
+// seek is essential, and it should be byte stream
+class StreamReader {
+  // should return a string [FIXME: perhaps return array of bytes?]
+  function read($bytes) {
+    return false;
+  }
+  
+  // should return new position
+  function seekto($position) {
+    return false;
+  }
+  
+  // returns current position
+  function currentpos() {
+    return false;
+  }
+  
+  // returns length of entire stream (limit for seekto()s)
+  function length() {
+    return false;
+  }
+}
+
+class StringReader {
+  var $_pos;
+  var $_str;
+
+  function StringReader($str='') {
+    $this->_str = $str;
+    $this->_pos = 0;
+  }
+
+  function read($bytes) {
+    $data = substr($this->_str, $this->_pos, $bytes);
+    $this->_pos += $bytes;
+    if (strlen($this->_str)<$this->_pos)
+      $this->_pos = strlen($this->_str);
+
+    return $data;
+  }
+
+  function seekto($pos) {
+    $this->_pos = $pos;
+    if (strlen($this->_str)<$this->_pos)
+      $this->_pos = strlen($this->_str);
+    return $this->_pos;
+  }
+
+  function currentpos() {
+    return $this->_pos;
+  }
+
+  function length() {
+    return strlen($this->_str);
+  }
+
+}
+
+
+class FileReader {
+  var $_pos;
+  var $_fd;
+  var $_length;
+
+  function FileReader($filename) {
+    if (file_exists($filename)) {
+
+      $this->_length=filesize($filename);
+      $this->_pos = 0;
+      $this->_fd = fopen($filename,'rb');
+      if (!$this->_fd) {
+	$this->error = 3; // Cannot read file, probably permissions
+	return false;
+      }
+    } else {
+      $this->error = 2; // File doesn't exist
+      return false;
+    }
+  }
+
+  function read($bytes) {
+    if ($bytes) {
+      fseek($this->_fd, $this->_pos);
+
+      // PHP 5.1.1 does not read more than 8192 bytes in one fread()
+      // the discussions at PHP Bugs suggest it's the intended behaviour
+      while ($bytes > 0) {
+        $chunk  = fread($this->_fd, $bytes);
+        $data  .= $chunk;
+        $bytes -= strlen($chunk);
+      }
+      $this->_pos = ftell($this->_fd);
+      
+      return $data;
+    } else return '';
+  }
+
+  function seekto($pos) {
+    fseek($this->_fd, $pos);
+    $this->_pos = ftell($this->_fd);
+    return $this->_pos;
+  }
+
+  function currentpos() {
+    return $this->_pos;
+  }
+
+  function length() {
+    return $this->_length;
+  }
+
+  function close() {
+    fclose($this->_fd);
+  }
+
+}
+
+// Preloads entire file in memory first, then creates a StringReader 
+// over it (it assumes knowledge of StringReader internals)
+class CachedFileReader extends StringReader {
+  function CachedFileReader($filename) {
+    if (file_exists($filename)) {
+
+      $length=filesize($filename);
+      $fd = fopen($filename,'rb');
+
+      if (!$fd) {
+	$this->error = 3; // Cannot read file, probably permissions
+	return false;
+      }
+      $this->_str = fread($fd, $length);
+      fclose($fd);
+
+    } else {
+      $this->error = 2; // File doesn't exist
+      return false;
+    }
+  }
+}
+
+
+?>

+ 4170 - 0
lib/prototype.js

@@ -0,0 +1,4170 @@
+/*  Prototype JavaScript framework, version 1.6.0.1
+ *  (c) 2005-2007 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.1',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;