From 03ffd84e763e6fc388e0c4e5c54b87120338ca4a Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Mon, 17 Sep 2012 12:59:39 +0000 Subject: [PATCH] Create Thread class and tests for it --- .../server_side/webim/classes/ThreadTest.php | 1226 +++++++++++++++++ src/messenger/webim/libs/classes/thread.php | 727 ++++++++++ 2 files changed, 1953 insertions(+) create mode 100644 src/messenger/tests/server_side/webim/classes/ThreadTest.php create mode 100644 src/messenger/webim/libs/classes/thread.php 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 @@ +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 @@ + '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