diff --git a/src/messenger/tests/server_side/webim/classes/ThreadTest.php b/src/messenger/tests/server_side/webim/classes/ThreadTest.php
new file mode 100644
index 00000000..4f90d4cd
--- /dev/null
+++ b/src/messenger/tests/server_side/webim/classes/ThreadTest.php
@@ -0,0 +1,1226 @@
+<?php
+
+require_once dirname(__FILE__) . '/../../../../webim/libs/classes/thread.php';
+require_once dirname(__FILE__) . '/../../../../webim/libs/classes/database.php';
+require_once dirname(__FILE__) . '/../../../../webim/libs/classes/settings.php';
+require_once dirname(__FILE__) . '/../../../../webim/libs/common/locale.php';
+require_once dirname(__FILE__) . '/../config.php';
+
+/**
+ * Test class for Thread.
+ * Generated by PHPUnit on 2012-09-07 at 19:04:21.
+ */
+class ThreadTest extends PHPUnit_Framework_TestCase {
+
+	/**
+	 * Temporary variable to store thread lifetime
+	 * @var int
+	 */
+	protected static $thread_lifetime;
+
+	/**
+	 * Get Messages count from database
+	 *
+	 * @param int $kind Message kind
+	 * @param string $message Message text
+	 * @param int $last_msg_id Message id to start search from
+	 * @return int Messages count
+	 */
+	protected function _helper_getMessagesCount($kind, $message, $last_msg_id) {
+		$db = Database::getInstance();
+		list($count) = $db->query(
+			"SELECT COUNT(*) FROM {chatmessage} " .
+				"WHERE ikind = :kind AND tmessage = :message AND messageid > :last_msg_id",
+			array(
+				':kind' => $kind,
+				':message' => $message,
+				':last_msg_id' => $last_msg_id
+			),
+			array('return_rows' => Database::RETURN_ONE_ROW, 'fetch_type' => Database::FETCH_NUM)
+		);
+		return $count;
+	}
+
+	/**
+	 * Get total message count from database
+	 *
+	 * @param int $last_msg_id Message id to start search from
+	 * @return int Messages count
+	 */
+	protected function _helper_getTotalMessagesCount($last_msg_id) {
+		$db = Database::getInstance();
+		list($count) = $db->query(
+			"SELECT COUNT(*) FROM {chatmessage} WHERE messageid > :last_msg_id",
+			array(':last_msg_id' => $last_msg_id),
+			array('return_rows' => Database::RETURN_ONE_ROW, 'fetch_type' => Database::FETCH_NUM)
+		);
+		return $count;
+	}
+
+	/**
+	 * Returns last message id
+	 *
+	 * @return int Id of the last message
+	 */
+	protected function _helper_getLastMessageId() {
+		$db = Database::getInstance();
+		list($last_msg_id) = $db->query(
+			"SELECT MAX(messageid) FROM {chatmessage}",
+			NULL,
+			array('return_rows' => Database::RETURN_ONE_ROW, 'fetch_type' => Database::FETCH_NUM)
+		);
+		if (! $last_msg_id) {
+			$last_msg_id = 0;
+		}
+		return $last_msg_id;
+	}
+
+	/**
+	 * Get threads count with specified thread id
+	 *
+	 * @param int $thread_id Id of the thread
+	 * @return int Count of threads
+	 */
+	protected function _helper_getThreadCount($thread_id) {
+		$db = Database::getInstance();
+		list($count) = $db->query(
+			"SELECT COUNT(*) FROM {chatthread} WHERE threadid = :id",
+			array(':id' => $thread_id),
+			array('return_rows' => Database::RETURN_ONE_ROW, 'fetch_type' => Database::FETCH_NUM)
+		);
+		return $count;
+	}
+
+	/**
+	 * Get thread info from database
+	 *
+	 * @param int $thread_id Id of the thread
+	 * @return array Thread info
+	 */
+	protected function _helper_getThreadInfo($thread_id) {
+		$db = Database::getInstance();
+		return $db->query(
+			"SELECT * FROM {chatthread} WHERE threadid = :id",
+			array(':id' => $thread_id),
+			array('return_rows' => Database::RETURN_ONE_ROW)
+		);
+	}
+
+	public static function setUpBeforeClass() {
+		// Initialize database object
+		global $db_host, $db_name, $db_user, $db_pass, $tables_prefix,
+			$db_encoding, $force_charset_in_connection, $use_persistent_connection;
+		Database::initialize(
+			$db_host,
+			$db_user,
+			$db_pass,
+			$use_persistent_connection,
+			$db_name,
+			$tables_prefix,
+			$force_charset_in_connection,
+			$db_encoding
+		);
+		$db = Database::getInstance();
+		// Clear tables
+		$db->query("TRUNCATE {chatthread}");
+		$db->query("TRUNCATE {chatmessage}");
+		self::$thread_lifetime = Settings::get('thread_lifetime');
+		Settings::set('thread_lifetime', 600);
+	}
+
+	public static function tearDownAfterClass() {
+		$db = Database::getInstance();
+		// Clear tables
+		$db->query("TRUNCATE {chatthread}");
+		$db->query("TRUNCATE {chatmessage}");
+		Settings::set('thread_lifetime', self::$thread_lifetime);
+		// Destroy Database object
+		Database::destroy();
+	}
+
+	public function testCreate() {
+		// Create thread
+		$thread = Thread::create();
+
+		// Check thread
+		$this->assertNotEmpty($thread);
+		$this->assertNotEmpty($thread->id);
+
+		// Check if thread in database
+		$this->assertEquals(1, $this->_helper_getThreadCount($thread->id));
+
+		$threadid = $thread->id;
+		unset($thread);
+		return $threadid;
+	}
+
+	/**
+	 * @depends testCreate
+	 */
+	public function testLoad($threadid) {
+		// Load thread
+		$thread = Thread::load($threadid);
+
+		// Check thread
+		$this->assertNotEmpty($thread);
+		$this->assertEquals($threadid, $thread->id);
+
+		return $thread;
+	}
+
+	/**
+	 * @depends testLoad
+	 */
+	public function testDelete(Thread $thread) {
+		$threadid = $thread->id;
+
+		// Check if thread in database
+		$this->assertEquals(1, $this->_helper_getThreadCount($threadid));
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+
+		// Check thread in database
+		$this->assertEquals(0, $this->_helper_getThreadCount($threadid));
+	}
+
+	public function test__isset() {
+		// Create new thread
+		$thread = Thread::create();
+
+		// Property exists and not empty
+		$this->assertNotEmpty($thread->id);
+
+		// assertTrue use instead of assertEmpty because of PHPUnit don't work correctly with __isset magic method
+
+		// Property exists in internal Thread::$propertyMap property but not set
+		$this->assertTrue(empty($thread->lastToken));
+		$this->assertFalse(isset($thread->lastToken));
+
+		// Property does not exists
+		$this->assertTrue(empty($thread->someFakeProp));
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function test__set() {
+		// Create thread
+		$thread = Thread::create();
+
+		// Try to set value for unexistent property
+		// Following code wait for trigger user notice, which converts by PHPUnit to an
+		// Exception
+		try {
+			$thread->someFakeField = 'some_test_value';
+			$this->fail("Exception must be thrown");
+		} catch(Exception $e) {}
+
+		// Try to set exist property
+		$thread->lastToken = 129;
+
+		return $thread;
+	}
+
+	/**
+	 * @depends test__set
+	 */
+	public function test__get(Thread $thread) {
+		// Check property value from test_set() method
+		$this->assertEquals(129, $thread->lastToken);
+
+		// Try to get value of unexistent property
+		// Following code wait for trigger user notice, which converts by PHPUnit to an
+		// Exception
+		try {
+			$some_value = $thread->someFakeField;
+			$this->fail("Exception must be thrown");
+		} catch(Exception $e) {}
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testSave() {
+		// Create new thread
+		$thread = Thread::create();
+
+		// Update thread values
+		$thread->lastRevision = $last_revision = 10;
+		$thread->state = $state = Thread::STATE_CHATTING;
+		$thread->lastToken = $last_token = 11;
+
+		$thread->nextAgent = $next_agent = 12;
+		$thread->groupId = $group_id = 13;
+
+		$thread->shownMessageId = $shown_message_id = 14;
+		$thread->messageCount = $message_count = 15;
+
+		$thread->created = $created = time() - 200;
+		$thread->modified = $modified = time() - 190;
+		$thread->chatStarted = $chat_started = time() - 180;
+
+		$thread->agentId = $agent_id = 16;
+		$thread->agentName = $agent_name = '17';
+		$thread->agentTyping = $agent_typing = 0;
+		$thread->lastPingAgent = $last_ping_agent = time() - 170;
+
+		$thread->locale = $locale = '18';
+
+		$thread->userId = $user_id = 19;
+		$thread->userName = $user_name = '20';
+		$thread->userTyping = $user_tiping = 1;
+		$thread->lastPingUser = $last_user_ping = time() - 160;
+
+		$thread->remote = $remote = '21';
+		$thread->referer = $referer = '22';
+		$thread->userAgent = $user_agent = '23';
+
+
+		// Save thread
+		$thread->save();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+
+		// Check values
+		$this->assertEquals($thread_info['lrevision'],$last_revision);
+		$this->assertEquals($thread_info['istate'], $state);
+		$this->assertEquals($thread_info['ltoken'], $last_token);
+
+		$this->assertEquals($thread_info['nextagent'], $next_agent);
+		$this->assertEquals($thread_info['groupid'], $group_id);
+
+		$this->assertEquals($thread_info['shownmessageid'], $shown_message_id);
+		$this->assertEquals($thread_info['messageCount'], $message_count);
+
+		$this->assertEquals($thread_info['dtmcreated'], $created);
+		$this->assertEquals($thread_info['dtmmodified'], $modified);
+		$this->assertEquals($thread_info['dtmchatstarted'], $chat_started);
+
+		$this->assertEquals($thread_info['agentId'], $agent_id);
+		$this->assertEquals($thread_info['agentName'], $agent_name);
+		$this->assertEquals($thread_info['agentTyping'], $agent_typing);
+		$this->assertEquals($thread_info['lastpingagent'], $last_ping_agent);
+
+		$this->assertEquals($thread_info['locale'], $locale);
+
+		$this->assertEquals($thread_info['userid'], $user_id);
+		$this->assertEquals($thread_info['userName'], $user_name);
+		$this->assertEquals($thread_info['userTyping'], $user_tiping);
+		$this->assertEquals($thread_info['lastpinguser'], $last_user_ping);
+
+		$this->assertEquals($thread_info['remote'], $remote);
+		$this->assertEquals($thread_info['referer'], $referer);
+		$this->assertEquals($thread_info['userAgent'], $user_agent);
+
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testReopen() {
+		global $home_locale;
+
+		$db = Database::getInstance();
+
+		// Create new thread
+		$thread = Thread::create();
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->locale = $home_locale;
+		$thread->lastPingAgent = time() - Settings::get('thread_lifetime') * 2;
+		$thread->lastPingUser = time() - Settings::get('thread_lifetime') * 2;
+		$thread->save();
+
+
+		// Last ping was more than life_time ago
+		$this->assertFalse(Thread::reopen($thread->id));
+
+
+		// Try reopen closed thread
+		$thread->state = Thread::STATE_CLOSED;
+		$thread->lastPingAgent = time();
+		$thread->lastPingUser = time();
+		$thread->save();
+		$this->assertFalse(Thread::reopen($thread->id));
+
+
+		// Try to reopen left thread
+		$thread->state = Thread::STATE_LEFT;
+		$thread->save();
+		$this->assertFalse(Thread::reopen($thread->id));
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Try to reopen thread with Thread::STATE_WAITING state
+		$thread->nextAgent = 2;
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+
+		$another_thread = Thread::reopen($thread->id);
+		$this->assertNotEmpty($another_thread);
+
+		// Load another thread info
+		$thread_info = $this->_helper_getThreadInfo($another_thread->id);
+		// Check next agent field
+		$this->assertEquals(0, $another_thread->nextAgent);
+		$this->assertEquals(0, $thread_info['nextagent']);
+
+		// Check sent messages
+		$message_count = $this->_helper_getTotalMessagesCount($last_msg_id);
+		$this->assertEquals(1, $message_count);
+
+		// Check message text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring_("chat.status.user.reopenedthread", $another_thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+		unset($another_thread);
+
+
+		// Try to reopen thread with Thread::STATE_QUEUE state
+		$thread->nextAgent = 2;
+		$thread->state = Thread::STATE_QUEUE;
+		$thread->save();
+
+		$another_thread = Thread::reopen($thread->id);
+		$this->assertNotEmpty($another_thread);
+
+		// Load another thread info
+		$thread_info = $this->_helper_getThreadInfo($another_thread->id);
+		// Check next agent field
+		$this->assertEquals(2, $another_thread->nextAgent);
+		$this->assertEquals(2, $thread_info['nextagent']);
+
+		unset($another_thread);
+
+		// Check equlity of treads returned by reopen method and returned by load method
+		$thread_id = $thread->id;
+		unset($thread);
+		$thread = Thread::load($thread_id);
+		$another_thread = Thread::reopen($thread_id);
+		$this->assertEquals($thread, $another_thread);
+		unset($another_thread);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testCloseOldThreads() {
+		$timeout = Settings::get('thread_lifetime');
+
+		// Create new thread
+		$thread = Thread::create();
+
+
+		// Try to close thread with state = Thread::STATE_CHATTING and time no timeout
+		// Update values
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->lastPingAgent = time();
+		$thread->lastPingUser = time();
+		// Save thread
+		$thread->save();
+		// And close all threads
+		Thread::closeOldThreads();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertNotEquals($thread_info['istate'], Thread::STATE_CLOSED);
+
+
+		// Try to close thread with timeout for user and no timeout for agent
+		// Update values
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->lastPingAgent = time();
+		$thread->lastPingUser = time() - $timeout * 2;
+		// Save thread
+		$thread->save();
+		// And close all threads
+		Thread::closeOldThreads();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertNotEquals($thread_info['istate'], Thread::STATE_CLOSED);
+
+
+		// Try to close thread with timeout for agent and no timeout for user
+		// Update values
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->lastPingAgent = time() - $timeout * 2;
+		$thread->lastPingUser = time();
+		// Save thread
+		$thread->save();
+		// And close all threads
+		Thread::closeOldThreads();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertNotEquals($thread_info['istate'], Thread::STATE_CLOSED);
+
+
+		// Try to close thread with timeout for agent and timeout for user
+		// Update values
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->lastPingAgent = time() - $timeout * 2;
+		$thread->lastPingUser = time() - $timeout * 2;
+		// Save thread
+		$thread->save();
+		// And close all threads
+		Thread::closeOldThreads();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals($thread_info['istate'], Thread::STATE_CLOSED);
+
+
+		// Try to close thread with timeout for user and not started for agent(was no agent ping yet)
+		// Update values
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->lastPingAgent = 0;
+		$thread->lastPingUser = time() - $timeout * 2;
+		// Save thread
+		$thread->save();
+		// And close all threads
+		Thread::closeOldThreads();
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals($thread_info['istate'], Thread::STATE_CLOSED);
+
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testPostMessage() {
+		$db = Database::getInstance();
+
+		// Create new thread
+		$thread = Thread::create();
+
+		// Set values
+		$message = array(
+			'threadid' => $thread->id,
+			'ikind' => Thread::KIND_INFO,
+			'agentId' => 12,
+			'tmessage' => 'New message text',
+			'dtmcreated' => time(),
+			'tname' => 'Sender name'
+		);
+
+		$message['messageid'] = $thread->postMessage(
+			$message['ikind'],
+			$message['tmessage'],
+			$message['tname'],
+			$message['agentId'],
+			$message['dtmcreated']
+		);
+		// Load message info from database
+		$msg_info = $db->query(
+			"SELECT * FROM {chatmessage} WHERE messageid = ?",
+			array($message['messageid']),
+			array('return_rows' => Database::RETURN_ONE_ROW)
+		);
+		// Check values
+		$this->assertEquals($message, $msg_info);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+
+		return $message['messageid'];
+	}
+
+	/**
+	 * @depends testPostMessage
+	 */
+	public function testGetMessages($msg_id) {
+		// Create new thread
+		$thread = Thread::create();
+
+		// Create messages
+		// The first
+		$first_message = array(
+			'ikind' => Thread::KIND_USER,
+			'tmessage' => 'The first message',
+			'created' => time(),
+			'tname' => 'System message only for agent'
+		);
+		// The second
+		$second_message = array(
+			'ikind' => Thread::KIND_AGENT,
+			'tmessage' => 'The second message',
+			'created' => time(),
+			'tname' => 'User'
+		);
+		// The third
+		$third_message = array(
+			'ikind' => Thread::KIND_FOR_AGENT,
+			'tmessage' => 'The third message',
+			'created' => time(),
+			'tname' => 'Agent'
+		);
+
+		// Send messages
+		// The first
+		$first_message['messageid'] = $thread->postMessage(
+			$first_message['ikind'],
+			$first_message['tmessage'],
+			$first_message['tname'],
+			12,
+			$first_message['created']
+		);
+		// The second
+		$second_message['messageid'] = $thread->postMessage(
+			$second_message['ikind'],
+			$second_message['tmessage'],
+			$second_message['tname'],
+			14,
+			$second_message['created']
+		);
+		// The third
+		$third_message['messageid'] = $thread->postMessage(
+			$third_message['ikind'],
+			$third_message['tmessage'],
+			$third_message['tname'],
+			16,
+			$third_message['created']
+		);
+
+		// Check messages for agent with ids starts from $msg_id
+		$last_id = $msg_id;
+		$this->assertEquals(
+			array($first_message, $second_message, $third_message),
+			$thread->getMessages(false, $last_id)
+		);
+		// Check last message id
+		$this->assertEquals($third_message['messageid'], $last_id);
+
+		// Check messages for user with ids starts from $msg_id
+		$last_id = $msg_id;
+		$this->assertEquals(
+			array($first_message, $second_message),
+			$thread->getMessages(true, $last_id)
+		);
+		// Check last message id
+		$this->assertEquals($second_message['messageid'], $last_id);
+
+		// Check messages for agent with ids starts from first message's id
+		$last_id = $first_message['messageid'];
+		$this->assertEquals(
+			array($second_message, $third_message),
+			$thread->getMessages(false, $last_id)
+		);
+		// Check last message id
+		$this->assertEquals($third_message['messageid'], $last_id);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testRenameUser() {
+		// Create new thread
+		$thread = Thread::create();
+		// Set user initial name
+		$thread->userName = 'User name';
+		$thread->locale = 'en';
+		$thread->save();
+
+		// Rename user
+		$new_user_name = 'New user name';
+		$thread->renameUser($new_user_name);
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check user name
+		$this->assertEquals($new_user_name, $thread_info['userName']);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testCheckForReassign() {
+		global $home_locale;
+
+		// Create new thread
+		$thread = Thread::create();
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->locale = $home_locale;
+		$thread->agentName = "First name";
+		$thread->agentId = 1;
+		$thread->nextAgent = 0;
+		$thread->save();
+
+		// Create operator
+		$operator = array(
+			'operatorid' => 2,
+			'vclocalename' => 'Second local name',
+			'vccommonname' => 'Second common name',
+			'vcavatar' => 'avatar'
+		);
+
+		// Chat in progress. No reassign expected
+		$thread->checkForReassign($operator);
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(1, $thread_info['agentId']);
+
+
+		// User waiting for another agent
+		$thread->nextAgent = 5;
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+		$thread->checkForReassign($operator);
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		$this->assertEquals(Thread::STATE_WAITING, $thread_info['istate']);
+		$this->assertEquals(Thread::STATE_WAITING, $thread->state);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+		// User waiting for another agent, but last agent checks for reassign
+		$thread->nextAgent = 5;
+		$thread->agentId = 2;
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+		$thread->checkForReassign($operator);
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check state
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+
+		// Check sent messages
+		$this->assertEquals(2, $this->_helper_getTotalMessagesCount($last_msg_id));
+
+		// Check messages text:
+		// Thread::KIND_EVENTS message
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_("chat.status.operator.returned", array($operator['vclocalename']), $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		// Thread::KIND_AVATAR message
+		$message_count = $this->_helper_getMessagesCount(Thread::KIND_AVATAR, 'avatar', $last_msg_id);
+		$this->assertEquals(1, $message_count);
+
+
+		// Update last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// User waiting for this agent.
+		// It differs from previous test only in sent message text
+		$thread->nextAgent = 2;
+		$thread->agentId = 1;
+		$thread->agentName = 'First agent';
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+		$thread->checkForReassign($operator);
+
+		// Check message text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_(
+				"chat.status.operator.changed",
+				array($operator['vclocalename'], 'First agent'),
+				$thread->locale
+			),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testClose() {
+		global $home_locale;
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+		// Create new thread
+		$thread = Thread::create();
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->userName = 'User';
+		$thread->agentName = 'Agent';
+		$thread->locale = $home_locale;
+		$thread->save();
+
+
+		// Check close thread by user
+		$thread->close(true);
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check state
+		$this->assertEquals(Thread::STATE_CLOSED, $thread_info['istate']);
+		$this->assertEquals(Thread::STATE_CLOSED, $thread->state);
+
+		// Check messages count
+		$this->assertEquals(0, $thread_info['messageCount']);
+
+		// Check sent messages
+		$this->assertEquals(1, $this->_helper_getTotalMessagesCount($last_msg_id));
+
+		// Check message text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_("chat.status.user.left", array($thread->userName), $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Check close thread by agent.
+		// It differs from previous test only in message text
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->save();
+
+		$thread->close(false);
+
+		// Check message text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_("chat.status.operator.left", array($thread->agentName), $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+		// Check close thread with user's and agent's messages
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->messageCount = 0;
+		$thread->save();
+
+		// Send messages
+		$thread->postMessage(Thread::KIND_USER, "Test message");
+		$thread->postMessage(Thread::KIND_USER, "Next test message");
+		$thread->postMessage(Thread::KIND_AGENT, "Test message");
+		$thread->postMessage(Thread::KIND_EVENTS, "Test message");
+
+		$thread->close(true);
+
+		// Load thread info from database
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+
+		// Check messages count
+		$this->assertEquals(2, $thread_info['messageCount']);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testPing() {
+		global $home_locale;
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+		// Create new thread
+		$thread = Thread::create();
+		$thread->state = Thread::STATE_LOADING;
+		$thread->lastPingAgent = 0;
+		$thread->lastPingUser = 0;
+		$thread->locale = $home_locale;
+		$thread->save();
+
+
+		// Check first user ping
+		$thread->ping(true, false);
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_QUEUE, $thread->state);
+		$this->assertEquals(Thread::STATE_QUEUE, $thread_info['istate']);
+
+
+		// Check not first ping user without any connection problems at the other side
+		// Update thread info
+		$thread->lastPingAgent = time();
+		$thread->lastPingUser = 0;
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->save();
+		// Ping the thread
+		$thread->ping(true, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check no message sent
+		$this->assertEquals($last_msg_id, $this->_helper_getLastMessageId());
+		// Check last ping time updated for user
+		$this->assertLessThan($thread->lastPingUser, 0);
+		$this->assertLessThan($thread_info['lastpinguser'], 0);
+		// Check thread state not changed
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+
+
+		// Check ping agent without any connection problems at the other side
+		// Update thread info
+		$thread->lastPingAgent = 0;
+		$thread->save();
+		// Ping the thread
+		$thread->ping(false, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check no message sent
+		$this->assertEquals($last_msg_id, $this->_helper_getLastMessageId());
+		// Check last ping time updated for user
+		$this->assertLessThan($thread->lastPingAgent, 0);
+		$this->assertLessThan($thread_info['lastpingagent'], 0);
+		// Check thread state not changed
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+
+
+		// Check ping user with connection problem at the other side and thread state equals to
+		// Thread::STATE_WAITING
+		// Update thread info
+		$thread->lastPingAgent = time() - Thread::CONNECTION_TIMEOUT * 2;
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+		// Ping the thread
+		$thread->ping(true, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check no message sent
+		$this->assertEquals($last_msg_id, $this->_helper_getLastMessageId());
+		// Check last ping agent
+		$this->assertEquals(0, $thread->lastPingAgent);
+		$this->assertEquals(0, $thread_info['lastpingagent']);
+
+
+		// Check ping user with connection problem at the other side and thread state equals to Thread::STATE_CHATTING.
+		// In this case message must be sent and state must be changed
+		// Update thread info
+		$thread->lastPingAgent = time() - Thread::CONNECTION_TIMEOUT * 2;
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->nextAgent = 1;
+		$thread->save();
+		// Ping the thread
+		$thread->ping(true, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check message sent
+		$this->assertEquals(1, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messsage text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_CONN,
+			getstring_("chat.status.operator.dead", $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		// Check last ping agent
+		$this->assertEquals(0, $thread->lastPingAgent);
+		$this->assertEquals(0, $thread_info['lastpingagent']);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_WAITING, $thread->state);
+		$this->assertEquals(Thread::STATE_WAITING, $thread_info['istate']);
+		// Check next agent
+		$this->assertEquals(0, $thread->nextAgent);
+		$this->assertEquals(0, $thread_info['nextagent']);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Check ping agent with connection problem at the other side
+		// Update thread info
+		$thread->lastPingUser = time() - Thread::CONNECTION_TIMEOUT * 2;
+		$thread->save();
+		// Ping the thread
+		$thread->ping(false, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check message sent
+		$this->assertEquals(1, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messsage text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_FOR_AGENT,
+			getstring_("chat.status.user.dead", $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		// Check last ping user
+		$this->assertEquals(0, $thread->lastPingUser);
+		$this->assertEquals(0, $thread_info['lastpinguser']);
+
+
+		// Check user typing
+		// Update thread info
+		$thread->userTyping = '0';
+		$thread->save();
+		// Ping the thread
+		$thread->ping(true, true);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check
+		$this->assertEquals('1', $thread->userTyping);
+		$this->assertEquals('1', $thread_info['userTyping']);
+
+		// Check user not typing
+		// Update thread info
+		$thread->userTyping = '1';
+		$thread->save();
+		// Ping the thread
+		$thread->ping(true, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check
+		$this->assertEquals('0', $thread->userTyping);
+		$this->assertEquals('0', $thread_info['userTyping']);
+
+
+		// Check agent typing
+		// Update thread info
+		$thread->agentTyping = '0';
+		$thread->save();
+		// Ping the thread
+		$thread->ping(false, true);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check
+		$this->assertEquals('1', $thread->agentTyping);
+		$this->assertEquals('1', $thread_info['agentTyping']);
+
+		// Check agent not typing
+		// Update thread info
+		$thread->agentTyping = '1';
+		$thread->save();
+		// Ping the thread
+		$thread->ping(false, false);
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check
+		$this->assertEquals('0', $thread->agentTyping);
+		$this->assertEquals('0', $thread_info['agentTyping']);
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+	public function testTake() {
+		global $home_locale;
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+		// Create operator
+		$operator = array(
+			'operatorid' => 2,
+			'vclocalename' => 'Local name',
+			'vccommonname' => 'Common name',
+			'vcavatar' => 'avatar'
+		);
+
+		// Create new thread
+		$thread = Thread::create();
+		$thread->state = Thread::STATE_CLOSED;
+		$thread->locale = $home_locale;
+		$thread->agentName = 'Agent';
+		$thread->userName = 'User';
+		$thread->agentId = 2;
+		$thread->save();
+
+
+		// Try to take closed thread
+		$this->assertFalse($thread->take($operator));
+
+
+		// Try to take left thread
+		$thread->state = Thread::STATE_LEFT;
+		$thread->save();
+		$this->assertFalse($thread->take($operator));
+
+
+		// Try to take thread by current operator during chat
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->save();
+		$this->assertTrue($thread->take($operator));
+		// No message must be sent
+		$this->assertEquals(0, $this->_helper_getTotalMessagesCount($last_msg_id));
+
+
+		// Try to take thread during chat
+		$thread->state = Thread::STATE_CHATTING;
+		$thread->agentId = 1;
+		$thread->nextAgent = 10;
+		$thread->save();
+		$this->assertTrue($thread->take($operator));
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		// Check agent id
+		$this->assertEquals(2, $thread->agentId);
+		$this->assertEquals(2, $thread_info['agentId']);
+		// Check agent name
+		$this->assertEquals($operator['vclocalename'], $thread->agentName);
+		$this->assertEquals($operator['vclocalename'], $thread_info['agentName']);
+		// Check next agent id
+		$this->assertEquals(0, $thread->nextAgent);
+		$this->assertEquals(0, $thread_info['nextagent']);
+		// Check chat started time
+		$this->assertFalse(empty($thread->chatStarted));
+		$this->assertFalse(empty($thread_info['dtmchatstarted']));
+		// Two messages must be sent
+		$this->assertEquals(2, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messages text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_(
+				"chat.status.operator.changed",
+				array($operator['vclocalename'], 'Agent'),
+				$thread->locale
+			),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_AVATAR,
+			$operator['vcavatar'],
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Try to take thread with state Thread::STATE_WAITING by current operator
+		$thread->state = Thread::STATE_WAITING;
+		$thread->save();
+		$this->assertTrue($thread->take($operator));
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		// Check agent id
+		$this->assertEquals(2, $thread->agentId);
+		$this->assertEquals(2, $thread_info['agentId']);
+		// Two messages must be sent
+		$this->assertEquals(2, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messages text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_("chat.status.operator.returned", array($operator['vclocalename']), $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_AVATAR,
+			$operator['vcavatar'],
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Try to take thread with state Thread::STATE_WAITING
+		$thread->state = Thread::STATE_WAITING;
+		$thread->agentId = 1;
+		$thread->agentName = 'Agent';
+		$thread->save();
+		$this->assertTrue($thread->take($operator));
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		// Two messages must be sent
+		$this->assertEquals(2, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messages text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			getstring2_(
+				"chat.status.operator.changed",
+				array($operator['vclocalename'], 'Agent'),
+				$thread->locale
+			),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_AVATAR,
+			$operator['vcavatar'],
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+
+		// Get last message id
+		$last_msg_id = $this->_helper_getLastMessageId();
+
+
+		// Try to take thread with state Thread::STATE_QUEUE
+		$thread->state = Thread::STATE_QUEUE;
+		$thread->save();
+		$this->assertTrue($thread->take($operator));
+		// Get thread info
+		$thread_info = $this->_helper_getThreadInfo($thread->id);
+		// Check thread state
+		$this->assertEquals(Thread::STATE_CHATTING, $thread->state);
+		$this->assertEquals(Thread::STATE_CHATTING, $thread_info['istate']);
+		// Two messages must be sent
+		$this->assertEquals(2, $this->_helper_getTotalMessagesCount($last_msg_id));
+		// Check messages text
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_EVENTS,
+			$message = getstring2_("chat.status.operator.joined", array($operator['vclocalename']), $thread->locale),
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+		$message_count = $this->_helper_getMessagesCount(
+			Thread::KIND_AVATAR,
+			$operator['vcavatar'],
+			$last_msg_id
+		);
+		$this->assertEquals(1, $message_count);
+
+
+		// Delete thread
+		$thread->delete();
+		unset($thread);
+	}
+
+}
+
+?>
diff --git a/src/messenger/webim/libs/classes/thread.php b/src/messenger/webim/libs/classes/thread.php
new file mode 100644
index 00000000..9add6ed9
--- /dev/null
+++ b/src/messenger/webim/libs/classes/thread.php
@@ -0,0 +1,727 @@
+<?php
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Represents a chat thread
+ *
+ * @todo Think about STATE_* and KIND_* constant systems and may be simplifies them.
+ */
+Class Thread {
+
+	/**
+	 * User in the users queue
+	 */
+	const STATE_QUEUE = 0;
+	/**
+	 * User waiting for operator
+	 */
+	const STATE_WAITING = 1;
+	/**
+	 * Conversation in progress
+	 */
+	const STATE_CHATTING = 2;
+	/**
+	 * Thread closed
+	 */
+	const STATE_CLOSED = 3;
+	/**
+	 * Thread just created
+	 */
+	const STATE_LOADING = 4;
+	/**
+	 * User left message without starting a conversation
+	 */
+	const STATE_LEFT = 5;
+
+	/**
+	 * Message sent by user
+	 */
+	const KIND_USER = 1;
+	/**
+	 * Message sent by operator
+	 */
+	const KIND_AGENT = 2;
+	/**
+	 * Hidden system message to operator
+	 */
+	const KIND_FOR_AGENT = 3;
+	/**
+	 * System messages for user and operator
+	 */
+	const KIND_INFO = 4;
+	/**
+	 * Message for user if operator have connection problems
+	 */
+	const KIND_CONN = 5;
+	/**
+	 * System message about some events (like rename).
+	 */
+	const KIND_EVENTS = 6;
+	/**
+	 * Message with operators avatar
+	 */
+	const KIND_AVATAR = 7;
+
+	/**
+	 * Messaging window connection timeout.
+	 */
+	const CONNECTION_TIMEOUT = 30;
+
+	/**
+	 * Contain mapping of thread object properties to fields in database.
+	 *
+	 * Keys are object properties and vlues are {chatthread} table fields. Properties are available via magic __get
+	 * and __set methods. Real values are stored in the Thread::$threadInfo array.
+	 *
+	 * Thread object have following properties:
+	 *  - 'id': id of the thread
+	 *  - 'lastRevision': last revision number
+	 *  - 'state': state of the thread. See Thread::STATE_*
+	 *  - 'lastToken': last chat token
+	 *  - 'nextAgent': id of the next agent(agent that change current agent in the chat)
+	 *  - 'groupId': id of the group related to the thread
+	 *  - 'shownMessageId': last id of shown message
+	 *  - 'messageCount': count of user's messages related to the thread
+	 *  - 'created': unix timestamp of the thread creation
+	 *  - 'modified': unix timestamp of the thread's last modification
+	 *  - 'chatStarted': unix timestamp of related to thread chat started
+	 *  - 'agentId': id of an operator who take part in the chat
+	 *  - 'agentName': name of an operator who take part in the chat
+	 *  - 'agentTyping': "1" if operator typing at last ping time and "0" otherwise
+	 *  - 'lastPingAgent': unix timestamp of last operator ping
+	 *  - 'locale': locale code of the chat related to thread
+	 *  - 'userId': id of an user who take part in the chat
+	 *  - 'userName': name of an user who take part in the chat
+	 *  - 'userTyping': "1" if user typing at last ping time and "0" otherwise
+	 *  - 'lastPingUser': unix timestamp of last user ping
+	 *  - 'remote': user's IP
+	 *  - 'referer': content of HTTP Referer header for user
+	 *  - 'userAgent': content of HTTP User-agent header for user
+	 *
+	 * @var array
+	 *
+	 * @see Thread::__get()
+	 * @see Thread::__set()
+	 * @see Thread::$threadInfo
+	 */
+	protected $propertyMap = array(
+		'id' => 'threadid',
+
+		'lastRevision' => 'lrevision',
+		'state' => 'istate',
+		'lastToken' => 'ltoken',
+
+		'nextAgent' => 'nextagent',
+		'groupId' => 'groupid',
+
+		'shownMessageId' => 'shownmessageid',
+		'messageCount' => 'messageCount',
+
+		'created' => 'dtmcreated',
+		'modified' => 'dtmmodified',
+		'chatStarted' => 'dtmchatstarted',
+
+		'agentId' => 'agentId',
+		'agentName' => 'agentName',
+		'agentTyping' => 'agentTyping',
+		'lastPingAgent' => 'lastpingagent',
+
+		'locale' => 'locale',
+
+		'userId' => 'userid',
+		'userName' => 'userName',
+		'userTyping' => 'userTyping',
+		'lastPingUser' => 'lastpinguser',
+
+		'remote' => 'remote',
+		'referer' => 'referer',
+		'userAgent' => 'userAgent'
+	);
+
+	/**
+	 * Contain loaded from database information about thread
+	 *
+	 * Do not use this property manually!
+	 * @var array
+	 */
+	protected $threadInfo;
+
+	/**
+	 * List of modified fields.
+	 *
+	 * Do not use this property manually!
+	 * @var array
+	 */
+	protected $updatedFields = array();
+
+	/**
+	 * Load thread from database or create a new one in the database if $id is empty.
+	 *
+	 * @param int $id ID of the thread to load
+	 */
+	protected function __construct($id = null) {
+		// Get database object
+		$db = Database::getInstance();
+
+		if (! empty($id)) {
+			// Load thread
+			$thread_info = $db->query(
+				"select * from {chatthread} where threadid = :threadid",
+				array(
+					':threadid' => $id
+				),
+				array('return_rows' => Database::RETURN_ONE_ROW)
+			);
+
+			if (! $thread_info) {
+				return;
+			}
+
+			$this->threadInfo = $thread_info;
+		} else {
+			// Create thread
+			$db->query("insert into {chatthread} (threadid) values (NULL)");
+			// Set initial values
+			// In this case Thread::$threadInfo array use because id of a thread should not be update
+			$this->threadInfo['threadid'] = $db->insertedId();
+		}
+	}
+
+	/**
+	 * Create new empty thread in database
+	 *
+	 * @return boolean|Thread Returns an object of the Thread class or boolean false on failure
+	 */
+	public static function create() {
+		// Create new empty thread
+		$thread = new self();
+		// Check if something went wrong
+		if (empty($thread->id)) {
+			return false;
+		}
+		return $thread;
+	}
+
+	/**
+	 * Load thread from database
+	 *
+	 * @param int $id ID of the thread to load
+	 * @return boolean|Thread Returns an object of the Thread class or boolean false on failure
+	 */
+	public static function load($id) {
+		// Check $id
+		if (empty($id)) {
+			return false;
+		}
+
+		// Load thread
+		$thread = new self($id);
+
+		// Check if something went wrong
+		if ($thread->id != $id) {
+			return false;
+		}
+		return $thread;
+	}
+
+	/**
+	 * Reopen thread and send message about it
+	 *
+	 * @return boolean|Thread Boolean FALSE on failure or thread object on success
+	 */
+	public static function reopen($id) {
+		// Load thread
+		$thread = self::load($id);
+		// Check if user and agent gone
+		if (Settings::get('thread_lifetime') != 0 &&
+				abs($thread->lastPingUser - time()) > Settings::get('thread_lifetime') &&
+				abs($thread->lastPingAgent - time()) > Settings::get('thread_lifetime')) {
+			unset($thread);
+			return false;
+		}
+
+		// Check if thread closed
+		if ($thread->state == self::STATE_CLOSED || $thread->state == self::STATE_LEFT) {
+			unset($thread);
+			return false;
+		}
+
+		// Reopen thread
+		if ($thread->state == self::STATE_WAITING) {
+			$thread->nextAgent = 0;
+			$thread->save();
+		}
+
+		// Send message
+		$thread->postMessage(self::KIND_EVENTS, getstring_("chat.status.user.reopenedthread", $thread->locale));
+		return $thread;
+	}
+
+	/**
+	 * Close all old threads that were not closed by some reasons
+	 */
+	public static function closeOldThreads() {
+		if (Settings::get('thread_lifetime') == 0) {
+			return;
+		}
+
+		$db = Database::getInstance();
+
+		$query = "update {chatthread} set lrevision = :next_revision, " .
+			"dtmmodified = :now, istate = :state_closed " .
+			"where istate <> :state_closed and istate <> :state_left " .
+			"and ((lastpingagent <> 0 and lastpinguser <> 0 and " .
+			"(ABS(:now - lastpinguser) > :thread_lifetime and " .
+			"ABS(:now - lastpingagent) > :thread_lifetime)) or " .
+			"(lastpingagent = 0 and lastpinguser <> 0 and " .
+			"ABS(:now - lastpinguser) > :thread_lifetime))";
+
+		$db->query(
+			$query,
+			array(
+				':next_revision' => self::nextRevision(),
+				':now' => time(),
+				':state_closed' => self::STATE_CLOSED,
+				':state_left' => self::STATE_LEFT,
+				':thread_lifetime' => Settings::get('thread_lifetime')
+			)
+		);
+	}
+
+	/**
+	 * Return next revision number (last revision number plus one)
+	 *
+	 * @return int revision number
+	 */
+	protected static function nextRevision() {
+		$db = Database::getInstance();
+		$db->query("update {chatrevision} set id=LAST_INSERT_ID(id+1)");
+		$val = $db->insertedId();
+		return $val;
+	}
+
+	/**
+	 * Implementation of the magic __get method
+	 *
+	 * Check if variable with name $name exists in the Thread::$propertyMap array.
+	 * If it does not exist triggers an error with E_USER_NOTICE level and returns false.
+	 *
+	 * @param string $name property name
+	 * @return mixed
+	 * @see Thread::$propertyMap
+	 */
+	public function __get($name) {
+		// Check property existance
+		if (! array_key_exists($name, $this->propertyMap)) {
+			trigger_error("Undefined property '{$name}'", E_USER_NOTICE);
+			return NULL;
+		}
+
+		$field_name = $this->propertyMap[$name];
+		return $this->threadInfo[$field_name];
+	}
+
+	/**
+	 * Implementation of the magic __set method
+	 *
+	 * Check if variable with name $name exists in the Thread::$propertyMap array before setting.
+	 * If it does not exist triggers an error with E_USER_NOTICE level and value will NOT set.
+	 *
+	 * @param string $name Property name
+	 * @param mixed $value Property value
+	 * @return mixed
+	 * @see Thread::$propertyMap
+	 */
+	public function __set($name, $value) {
+		if (empty($this->propertyMap[$name])) {
+			trigger_error("Undefined property '{$name}'", E_USER_NOTICE);
+			return;
+		}
+
+		$field_name = $this->propertyMap[$name];
+		$this->threadInfo[$field_name] = $value;
+
+		if (! in_array($field_name, $this->updatedFields)) {
+			$this->updatedFields[] = $field_name;
+		}
+	}
+
+	/**
+	 * Implementation of the magic __isset method
+	 *
+	 * Check if variable with $name exists.
+	 *
+	 * param string $name Variable name
+	 * return boolean True if variable exists and false otherwise
+	 */
+	public function __isset($name) {
+		if (!array_key_exists($name, $this->propertyMap)) {
+			return false;
+		}
+
+		$property_name = $this->propertyMap[$name];
+		return isset($this->threadInfo[$property_name]);
+	}
+
+	/**
+	 * Remove thread from database
+	 */
+	public function delete() {
+		$db = Database::getInstance();
+		$db->query(
+			"DELETE FROM {chatthread} WHERE threadid = :id LIMIT 1",
+			array(':id' => $this->id)
+		);
+	}
+
+	/**
+	 * Ping the thread.
+	 *
+	 * Updates ping time for conversation members and sends messages about connection problems.
+	 *
+	 * @param boolean $is_user Indicates user or operator pings thread. Boolean true for user and boolean false
+	 * otherwise.
+	 * @param boolean $is_typing Indicates if user or operator is typing a message.
+	 */
+	public function ping($is_user, $is_typing) {
+		// Last ping time of other side
+		$last_ping_other_side = 0;
+		// Update last ping time
+		if ($is_user) {
+			$last_ping_other_side = $this->lastPingAgent;
+			$this->lastPingUser = time();
+			$this->userTyping = $is_typing ? "1" : "0";
+		} else {
+			$last_ping_other_side = $this->lastPingUser;
+			$this->lastPingAgent = time();
+			$this->agentTyping = $is_typing ? "1" : "0";
+		}
+
+		// Update thread state for the first user ping
+		if ($this->state == self::STATE_LOADING && $is_user) {
+			$this->state = self::STATE_QUEUE;
+			$this->save();
+			return;
+		}
+
+		// Check if other side of the conversation have connection problems
+		if ($last_ping_other_side > 0 && abs(time() - $last_ping_other_side) > self::CONNECTION_TIMEOUT) {
+			// Connection problems detected
+			if ($is_user) {
+				// _Other_ side is operator
+				// Update operator's last ping time
+				$this->lastPingAgent = 0;
+
+				// Check if user chatting at the moment
+				if ($this->state == self::STATE_CHATTING) {
+					// Send message to user
+					$message_to_post = getstring_("chat.status.operator.dead", $this->locale);
+					$this->postMessage(
+						self::KIND_CONN,
+						$message_to_post,
+						null,
+						null,
+						$last_ping_other_side + self::CONNECTION_TIMEOUT
+					);
+
+					// And update thread
+					$this->state = self::STATE_WAITING;
+					$this->nextAgent = 0;
+				}
+			} else {
+				// _Other_ side is user
+				// Update user's last ping time
+				$this->lastPingUser = 0;
+
+				// And send a message to operator
+				$message_to_post = getstring_("chat.status.user.dead", $this->locale);
+				$this->postMessage(
+					self::KIND_FOR_AGENT,
+					$message_to_post,
+					null,
+					null,
+					$last_ping_other_side + self::CONNECTION_TIMEOUT
+				);
+			}
+		}
+
+		$this->save();
+	}
+
+	/**
+	 * Save the thread to the database
+	 */
+	public function save(){
+		$db = Database::getInstance();
+
+		$query = "update {chatthread} t " .
+			"set lrevision = ?, dtmmodified = ?";
+
+		$values = array();
+		$values[] = $this->nextRevision();
+		$values[] = time();
+
+		foreach ($this->updatedFields as $field_name) {
+			$query .= ", {$field_name} = ?" ;
+			$values[] = $this->threadInfo[$field_name];
+		}
+
+		$query .= " where threadid = ?";
+		$values[] = $this->id;
+		$db->query($query, $values);
+	}
+
+	/**
+	 * Check if thread is reassigned for another operator
+	 *
+	 * Updates thread info, send events messages and avatar message to user
+	 * @global string $home_locale
+	 * @param array $operator Operator for test
+	 */
+	public function checkForReassign($operator) {
+		global $home_locale;
+
+		$operator_name = ($this->locale == $home_locale) ? $operator['vclocalename'] : $operator['vccommonname'];
+
+		if ($this->state == self::STATE_WAITING &&
+			($this->nextAgent == $operator['operatorid'] || $this->agentId == $operator['operatorid'])) {
+
+			// Prepare message
+			if ($this->nextAgent == $operator['operatorid']) {
+				$message_to_post = getstring2_(
+					"chat.status.operator.changed",
+					array($operator_name, $this->agentName),
+					$this->locale
+				);
+			} else {
+				$message_to_post = getstring2_("chat.status.operator.returned", array($operator_name), $this->locale);
+			}
+
+			// Update thread info
+			$this->state = self::STATE_CHATTING;
+			$this->nextAgent = 0;
+			$this->agentId = $operator['operatorid'];
+			$this->agentName = $operator_name;
+			$this->save();
+
+			// Send messages
+			$this->postMessage(self::KIND_EVENTS, $message_to_post);
+			$this->postMessage(self::KIND_AVATAR, $operator['vcavatar'] ? $operator['vcavatar'] : "");
+		}
+	}
+
+	/**
+	 * Load messages from database corresponding to the thread those ID's more than $lastid
+	 *
+	 * @param boolean $is_user Boolean TRUE if messages loads for user and boolean FALSE if they loads for operator.
+	 * @param int $lastid ID of the last loaded message.
+	 * @return array Array of messages
+	 * @see Thread::postMessage()
+	 */
+	public function getMessages($is_user, &$last_id) {
+
+		$db = Database::getInstance();
+
+		// Load messages
+		$messages = $db->query(
+			"select messageid,ikind,dtmcreated as created,tname,tmessage from {chatmessage} " .
+			"where threadid = :threadid and messageid > :lastid " .
+			($is_user ? "and ikind <> " . self::KIND_FOR_AGENT : "") .
+			" order by messageid",
+			array(
+				':threadid' => $this->id,
+				':lastid' => $last_id
+			),
+			array('return_rows' => Database::RETURN_ALL_ROWS)
+		);
+
+		foreach ($messages as $msg) {
+			// Get last message ID
+			if ($msg['messageid'] > $last_id) {
+				$last_id = $msg['messageid'];
+			}
+		}
+
+		return $messages;
+	}
+
+	/**
+	 * Send the messsage
+	 *
+	 * @param int $kind Message kind. One of the Thread::KIND_*
+	 * @param string $message Message body
+	 * @param string|null $from Sender name
+	 * @param int|null $opid operator id. Use NULL for system messages
+	 * @param int|null $time unix timestamp of the send time. Use NULL for current time.
+	 * @return int Message ID
+	 *
+	 * @see Thread::KIND_USER
+	 * @see Thread::KIND_AGENT
+	 * @see Thread::KIND_FOR_AGENT
+	 * @see Thread::KIND_INFO
+	 * @see Thread::KIND_CONN
+	 * @see Thread::KIND_EVENTS
+	 * @see Thread::KIND_AVATAR
+	 * @see Thread::getMessages()
+	 */
+	public function postMessage($kind, $message, $from = null, $opid = null, $time = null) {
+		$db = Database::getInstance();
+
+		$query = "INSERT INTO {chatmessage} " .
+			"(threadid,ikind,tmessage,tname,agentId,dtmcreated) " .
+			"VALUES (:threadid,:kind,:message,:name,:agentid,:created)";
+
+		$values = array(
+			':threadid' => $this->id,
+			':kind' => $kind,
+			':message' => $message,
+			':name' => ($from ? $from : "null"),
+			':agentid' => ($opid ? $opid : 0),
+			':created' => ($time ? $time : time())
+		);
+
+		$db->query($query, $values);
+		return $db->insertedId();
+	}
+
+	/**
+	 * Close thread and send closing messages to the conversation members
+	 *
+	 * @param boolean $is_user Boolean TRUE if user initiate thread closing or boolean FALSE otherwise
+	 */
+	public function close($is_user) {
+		$db = Database::getInstance();
+
+		// Get messages count
+		list($message_count) = $db->query(
+			"SELECT COUNT(*) FROM {chatmessage} WHERE {chatmessage}.threadid = :threadid AND ikind = :kind_user",
+			array(
+				':threadid' => $this->id,
+				':kind_user' => Thread::KIND_USER
+			),
+			array(
+				'return_rows' => Database::RETURN_ONE_ROW,
+				'fetch_type' => Database::FETCH_NUM
+			)
+		);
+
+		// Close thread if it's not already closed
+		if ($this->state != self::STATE_CLOSED) {
+			$this->state = self::STATE_CLOSED;
+			$this->messageCount = $message_count;
+			$this->save();
+		}
+
+		// Send message about closing
+		$message = '';
+		if ($is_user) {
+			$message = getstring2_("chat.status.user.left", array($this->userName), $this->locale);
+		} else {
+			$message = getstring2_("chat.status.operator.left", array($this->agentName), $this->locale);
+		}
+		$this->postMessage(self::KIND_EVENTS, $message);
+	}
+
+	/**
+	 * Assign operator to thread
+	 *
+	 * @global string $home_locale
+	 * @param array $operator Operator who try to take thread
+	 * @return boolean Boolean TRUE on success or FALSE on failure
+	 */
+	public function take($operator) {
+		global $home_locale;
+
+		$take_thread = false;
+		$message = '';
+		$operator_name = ($this->locale == $home_locale) ? $operator['vclocalename'] : $operator['vccommonname'];
+
+		if ($this->state == self::STATE_QUEUE || $this->state == self::STATE_WAITING || $this->state == self::STATE_LOADING) {
+			// User waiting
+			$take_thread = true;
+			if ($this->state == self::STATE_WAITING) {
+				if ($operator['operatorid'] != $this->agentId) {
+					$message = getstring2_(
+						"chat.status.operator.changed",
+						array($operator_name, $this->agentName),
+						$this->locale
+					);
+				} else {
+					$message = getstring2_("chat.status.operator.returned", array($operator_name), $this->locale);
+				}
+			} else {
+				$message = getstring2_("chat.status.operator.joined", array($operator_name), $this->locale);
+			}
+		} elseif ($this->state == self::STATE_CHATTING) {
+			// User chatting
+			if ($operator['operatorid'] != $this->agentId) {
+				$take_thread = true;
+				$message = getstring2_(
+					"chat.status.operator.changed",
+					array($operator_name, $this->agentName),
+					$this->locale
+				);
+			}
+		} else {
+			// Thread closed
+			return false;
+		}
+
+		// Change operator and update chat info
+		if ($take_thread) {
+			$this->state = self::STATE_CHATTING;
+			$this->nextAgent = 0;
+			$this->agentId = $operator['operatorid'];
+			$this->agentName = $operator_name;
+			if (empty($this->chatStarted)) {
+				$this->chatStarted = time();
+			}
+			$this->save();
+		}
+
+		// Send message
+		if ($message) {
+			$this->postMessage(self::KIND_EVENTS, $message);
+			$this->postMessage(self::KIND_AVATAR, $operator['vcavatar'] ? $operator['vcavatar'] : "");
+		}
+		return true;
+	}
+
+	/**
+	 * Change user name in the conversation
+	 *
+	 * @param string $new_name New user name
+	 */
+	public function renameUser($new_name) {
+		// Rename only if a new name is realy new
+		if ($this->userName != $new_name) {
+			// Rename user
+			$this->userName = $new_name;
+			$this->save();
+
+			// Send message about renaming
+			$message = getstring2_(
+				"chat.status.user.changedname",
+				array($this->userName, $new_name),
+				$this->locale
+			);
+			$this->postMessage(self::KIND_EVENTS, $message);
+		}
+	}
+}
+
+?>
\ No newline at end of file