| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729 |
- <?php
- class imMessage extends model
- {
- /**
- * @var imModel
- */
- public $im;
- /**
- * @var settingModel
- */
- public $setting;
- /**
- * Get message by id.
- * @param int $id
- * @param bool $format
- * @access public
- * @return object
- */
- public function getById($id, $format = true)
- {
- $message = $this->dbh->query("SELECT * FROM " . TABLE_IM_MESSAGE . " WHERE id = $id")->fetch();
- return $format ? $this->format($message) : $message;
- }
- /**
- * Get message list.
- *
- * @param string $cgid
- * @param array $idList
- * @param object $pager
- * @param string $startDate
- * @param string $type
- * @param bool $format
- * @param bool $masterOnly
- * @param int $userID
- * @access public
- * @return array
- */
- public function getList($cgid = '', $idList = array(), $pager = null, $startDate = '', $type = '', $format = true, $masterOnly = false, $userID = null)
- {
- if($masterOnly)
- {
- $tables = array((object)array('tableName' => TABLE_IM_MESSAGE, 'messages' => $idList));
- }
- else
- {
- if(!empty($idList)) $tables = $this->getTableByMessages($idList);
- if(!empty($startDate)) $tables = $this->getTablesByDateRange($startDate);
- if(empty($idList) && empty($startDate)) $tables = $this->getAllTables();
- if(empty($tables)) return array();
- }
- $queries = array();
- foreach($tables as $table)
- {
- $queries[] = $this->dao->select('*')->from($table->tableName)->where('1=1')
- ->beginIF(!empty($cgid))->andWhere('cgid')->eq($cgid)->fi()
- ->beginIF(!empty($idList))->andWhere('id')->in($table->messages)->fi()
- ->beginIF(!empty($startDate))->andWhere('date')->ge($startDate)->fi()
- ->beginIF(!empty($type) && strpos($type, '!') === 0)->andWhere('type')->ne(substr($type, 1))->fi()
- ->beginIF(!empty($type) && strpos($type, '!') !== 0)->andWhere('type')->eq($type)->fi()
- ->beginIF($userID != null)->andWhere('user')->eq($userID)->fi()
- ->get();
- }
- $query = join(' UNION ALL ', $queries);
- $sql = $this->dao->select('*')->from(TABLE_IM_MESSAGE);
- $sql->sqlobj->sql = 'SELECT * FROM (' . $query . ') as t';
- /* Assemble order by and pager stuff. */
- $messages = $sql
- ->orderBy('id_desc')
- ->beginIF($pager != null)->page($pager)->fi()
- ->fetchAll();
- $messages = $this->decodeMessages($messages);
- return $format ? $this->format($messages) : $messages;
- }
- /**
- * Get message list by indexes.
- *
- * @param string $cgid
- * @param array $indexList
- * @param object $pager
- * @param string $startDate
- * @param string $type
- * @param bool $format
- * @param bool $masterOnly
- * @param int $userID
- * @access public
- * @return array
- */
- public function getListByIndexes($cgid = '', $indexList = array(), $pager = null, $startDate = '', $type = '', $format = true, $masterOnly = false, $userID = null)
- {
- if($cgid == '') return array();
- if($masterOnly)
- {
- $tables = array((object)array('tableName' => TABLE_IM_MESSAGE, 'messages' => $indexList));
- }
- else
- {
- if(!empty($indexList)) $tables = $this->getChatTablesByChatIndexes($cgid, $indexList);
- if(!empty($startDate)) $tables = $this->getChatTablesByDateRange($cgid, $startDate);
- if(empty($indexList) && empty($startDate)) $tables = $this->getChatAllTables($cgid);
- if(empty($tables)) return array();
- }
- $queries = array();
- foreach($tables as $table)
- {
- $queries[] = $this->dao->select('*')->from($table->tableName)->where('1=1')
- ->andWhere('cgid')->eq($cgid)
- ->beginIF(!empty($indexList))->andWhere('`index`')->in($table->messages)->fi()
- ->beginIF(!empty($startDate))->andWhere('date')->ge($startDate)->fi()
- ->beginIF(!empty($type) && strpos($type, '!') === 0)->andWhere('type')->ne(substr($type, 1))->fi()
- ->beginIF(!empty($type) && strpos($type, '!') !== 0)->andWhere('type')->eq($type)->fi()
- ->beginIF($userID != null)->andWhere('user')->eq($userID)->fi()
- ->get();
- }
- $query = join(' UNION ALL ', $queries);
- $sql = $this->dao->select('*')->from(TABLE_IM_MESSAGE);
- $sql->sqlobj->sql = 'SELECT * FROM (' . $query . ') as t';
- /* Assemble order by and pager stuff. */
- $messages = $sql
- ->orderBy('id_desc')
- ->beginIF($pager != null)->page($pager)->fi()
- ->fetchAll();
- $messages = $this->decodeMessages($messages);
- return $format ? $this->format($messages) : $messages;
- }
- /**
- * Get messages ranged from id for user.
- *
- * @param string $cgid
- * @param int $fromID
- * @param bool $reverse
- * @param int $limit
- * @param int $userID
- * @param bool $returnID return id list only.
- * @access public
- * @return array
- */
- public function getListAroundIDForUser($cgid, $fromID, $reverse = false, $limit = 50, $userID = 0, $returnID = false)
- {
- if($fromID == 0 && $reverse) $fromID = PHP_INT_MAX;
- $chats = empty($cgid) ? $this->loadModel('im')->chatGetGidListByUserID($userID, true) : array($cgid);
- $tables = $this->getTablesByChats($chats);
- if(!empty($tables)) $tables = $this->dao->select('tableName')->from(TABLE_IM_MESSAGE_INDEX)->where(1)
- ->beginIF($reverse)->andWhere('start')->le($fromID)->fi()
- ->beginIF(!$reverse)->andWhere('end')->ge($fromID)->fi()
- ->andWhere('tableName')->in($tables)
- ->fetchAll();
- $tables[] = (object)array('tableName' => TABLE_IM_MESSAGE);
- $queries = array();
- foreach($tables as $table)
- {
- $queries[] = $this->dao->select($returnID ? 'id' : '*')->from($table->tableName)->where(1)
- ->beginIF($reverse)->andWhere('id')->le($fromID)->fi()
- ->beginIF(!$reverse)->andWhere('id')->ge($fromID)->fi()
- ->andWhere('cgid')->in($chats)
- ->get();
- }
- $query = join(' UNION ALL ', $queries);
- $sql = $this->dao->select('*')->from(TABLE_IM_MESSAGE);
- $sql->sqlobj->sql = 'SELECT * FROM (' . $query . ') as t';
- $messages = $sql
- ->beginIF($reverse)->orderBy('id_desc')->fi()
- ->beginIF(!$reverse)->orderBy('id')->fi()
- ->limit($limit)
- ->fetchAll();
- $messages = $this->decodeMessages($messages);
- return $returnID ? array_map(function($obj){return (int)$obj->id;}, $messages) : $this->format($messages);
- }
- /**
- * Get chat messages by indexes.
- *
- * @param string $cgid
- * @param array $indexList
- * @param bool $reverse
- * @param bool $returnID return id list only.
- * @access public
- * @return array
- */
- public function getListAroundIDByIndexes($cgid, $indexList, $reverse = false, $returnID = false)
- {
- $tables = $this->getChatTablesByChatIndexes($cgid, $indexList);
- $queries = array();
- foreach($tables as $table)
- {
- $queries[] = $this->dao->select($returnID ? 'id' : '*')->from($table->tableName)
- ->where('cgid')->eq($cgid)
- ->beginIF(!empty($indexList))->andWhere('`index`')->in($table->messages)->fi()
- ->get();
- }
- $query = join(' UNION ALL ', $queries);
- $sql = $this->dao->select('*')->from(TABLE_IM_MESSAGE);
- $sql->sqlobj->sql = 'SELECT * FROM (' . $query . ') as t';
- /* Assemble order by and pager stuff. */
- $messages = $sql
- ->beginIF($reverse)->orderBy('id_desc')->fi()
- ->beginIF(!$reverse)->orderBy('id')->fi()
- ->fetchAll();
- $messages = $this->decodeMessages($messages);
- return $returnID ? array_map(function($obj){return (int)$obj->id;}, $messages) : $this->format($messages);
- }
- /**
- * Get last messages of chats.
- *
- * @param array $cgids
- * @access public
- * @return array
- */
- public function getLast($cgids)
- {
- $chatMessages = $this->dao->select('gid,lastMessage')->from(TABLE_IM_CHAT)->where('gid')->in($cgids)->fetchPairs();
- return $chatMessages ? $this->getList('', array_values($chatMessages)) : array();
- }
- /**
- * Get voice message read by message id.
- * @param int $messageID
- * @access public
- * @return string
- */
- public function getVoiceRead($messageID)
- {
- $this->dao->select('read')
- ->from(TABLE_IM_MESSAGE)
- ->where('id')->eq($messageID)
- ->fetch();
- }
- /**
- * read voice message.
- * @param int $messageID
- * @param int $userID
- * @access public
- * @return int
- */
- public function readVoice($messageID, $userID)
- {
- return $this->dbh->exec('UPDATE ' . TABLE_IM_MESSAGE . " SET `read` = JSON_ARRAY_APPEND(IFNULL(`read`, '[]'), '$', $userID) WHERE `id` = $messageID AND `contentType` = 'voice'");
- }
- /**
- * Get read status on messages.
- *
- * @param array $messageIds message ids to get read status
- * @param bool $detailed return detailed read status or not
- * @param int $currentUser user id to get read status for
- * @access public
- * @return array array(messageId => array(userId => readStatus)
- */
- public function getReadStatus($messageIds, $detailed = false, $currentUser = 0)
- {
- $systemGroupChat = $this->dao->select('gid')->from(TABLE_IM_CHAT)->where('type')->eq('system')->fetch('gid');
- $messageChats = $this->dao->select('id,cgid')->from(TABLE_IM_MESSAGE)->where('id')->in($messageIds)->groupBy('id')->fetchPairs();
- $systemMessages = array();
- $chatMessages = array();
- foreach($messageChats as $messageId => $cgid)
- {
- if($cgid == $systemGroupChat)
- {
- $systemMessages[] = $messageId;
- continue;
- }
- if(!isset($chatMessages[$cgid])) $chatMessages[$cgid] = array();
- $chatMessages[$cgid][] = $messageId;
- }
- $chatReads = $this->dao->select('cgid,user,lastReadMessage')
- ->from(TABLE_IM_CHATUSER)
- ->where('cgid')
- ->in(array_keys($chatMessages))
- ->andWhere("(quit is null or quit = null)")
- ->fetchGroup('cgid');
- $readStatuses = array();
- foreach($chatMessages as $cgid => $messages)
- {
- if(!isset($chatReads[$cgid])) continue;
- foreach($chatReads[$cgid] as $chatRead)
- {
- foreach($messages as $messageId)
- {
- if(!isset($readStatuses[$messageId])) $readStatuses[$messageId] = array();
- if($chatRead->user != $currentUser) $readStatuses[$messageId][$chatRead->user] = $messageId <= $chatRead->lastReadMessage ? 1 : 0;
- }
- }
- }
- if(!empty($systemMessages))
- {
- $accountIdPairs = $this->dao->select('account,id')->from(TABLE_USER)->fetchPairs('account');
- $lastReadIds = $this->dao->select('`owner`,`value`')->from(TABLE_CONFIG)->where('module')->eq('chat')->andWhere('section')->eq('system')->andWhere('`key`')->eq('lastreadid')->fetchPairs('owner');
- foreach($systemMessages as $messageId)
- {
- $readStatuses[$messageId] = array();
- foreach($accountIdPairs as $account => $userId)
- {
- if ($userId != $currentUser) $readStatuses[$messageId][$userId] = $messageId <= (isset($lastReadIds[$account]) ? $lastReadIds[$account] : 0) ? 1 : 0;
- }
- }
- }
- if($detailed) return $readStatuses;
- foreach($readStatuses as $messageId => $readStatus)
- {
- $readCount = array_reduce($readStatus, function($carry, $item) { return $carry + $item; }, 0);
- $readStatuses[$messageId] = (object)array('read' => $readCount, 'total' => count($readStatus));
- }
- return $readStatuses;
- }
- /**
- * Format messages.
- *
- * @param mixed $messages object | array
- * @access public
- * @return object | array
- */
- public function format($messages)
- {
- $isObject = false;
- if(is_object($messages))
- {
- $isObject = true;
- $messages = array($messages);
- }
- $messageList = array();
- foreach($messages as $message)
- {
- $message->id = (int)$message->id;
- $message->index = (int)$message->index;
- $message->user = (int)$message->user;
- $message->date = strtotime($message->date);
- $message->deleted = isset($message->deleted) ? (bool)$message->deleted : false;
- /* Clear content for retracted messages */
- if($message->deleted) $message->content = '';
- $messageList[$message->gid] = $message;
- }
- if($isObject) return reset($messageList);
- return $messageList;
- }
- /**
- * Insert message into message table, ensuring unique index, retrying up to 3 times.
- *
- * @param object $messageData
- * @param int $retries
- * @access private
- * @return int|false message index or false if failed.
- */
- private function insertMessageEnsuringUniqueIndex($messageData, $retries = 0)
- {
- try
- {
- $stmt = $this->dao->insert(TABLE_IM_MESSAGE)->data($messageData)->get();
- $this->dao->dbh->exec($stmt);
- return $messageData->index;
- }
- catch(PDOException $e)
- {
- /* Handle constraint violation, which is likely caused by index collision, increase index and retry. */
- if(++$retries > 3) return false;
- $maxIndex = $this->dao->select('MAX(`index`)')->from(TABLE_IM_MESSAGE)
- ->where('cgid')->eq($messageData->cgid)
- ->fetch('MAX(`index`)');
- $messageData->index = $maxIndex + 1;
- $retries++;
- return $this->insertMessageEnsuringUniqueIndex($messageData, $retries);
- }
- }
- /**
- * Create messages.
- *
- * @param array $messageList
- * @param int $userID
- * @access public
- * @return array
- */
- public function create($messageList = array(), $userID = 0)
- {
- $idList = array();
- $now = helper::now();
- foreach($messageList as $message)
- {
- $message = (object) $message;
- $msg = $this->dao->select('*')->from(TABLE_IM_MESSAGE)->where('gid')->eq($message->gid)->fetch();
- if($msg)
- {
- if($msg->contentType == 'image' || $msg->contentType == 'file')
- {
- $message = $this->encodeMessage($message);
- $this->dao->update(TABLE_IM_MESSAGE)->set('content')->eq($message->content)->where('id')->eq($msg->id)->exec();
- }
- $idList[] = $msg->id;
- }
- elseif(!$msg)
- {
- if(!(isset($message->user) && $message->user)) $message->user = $userID;
- if(!(isset($message->date) && $message->date)) $message->date = $now;
- $initialMsgIndex = $this->dao->select('`lastMessageIndex` + 1 as msgIndex')->from(TABLE_IM_CHAT)->where('gid')->eq($message->cgid)->fetch('msgIndex');
- if(empty($initialMsgIndex)) $initialMsgIndex = 1;
- $message->index = $initialMsgIndex;
- $message->index = $this->insertMessageEnsuringUniqueIndex($this->encodeMessage($message));
- if($message->index === false) continue;
- /* Get last insert id from dbh directly, as dao->lastInsertID() is not usable for records inserted by dbh. */
- $message->id = $this->dao->dbh->lastInsertId();
- if(empty($message->id)) continue;
- $chatUpdateData = "lastActiveTime = '$now', lastMessage = $message->id, lastMessageIndex = $message->index";
- $this->dao->update(TABLE_IM_CHAT)
- ->set($chatUpdateData)
- ->where('gid')->eq($message->cgid)
- ->exec();
- $idList[] = $message->id;
- }
- }
- if(empty($idList)) return array();
- return $this->getList('', $idList);
- }
- /**
- * @param object $inviteMessageContent
- * @param int $userID
- * @return stdclass
- */
- public function createConferenceMessage($inviteMessageContent, $userID)
- {
- $this->loadModel('im');
- $sender = $this->im->bot->createDefaultBotSender($this->lang->im->conference->botName);
- $inviteMessage = new stdclass();
- $inviteMessage->gid = imModel::createGID();
- $inviteMessage->cgid = "$userID&xuanbot";
- $inviteMessage->user = "xuanbot";
- $inviteMessage->content = json_encode($inviteMessageContent);
- $inviteMessage->type = 'normal';
- $inviteMessage->contentType = 'object';
- $inviteMessage->data = new stdclass();
- $inviteMessage->data->sender = $sender;
- $inviteMessage->data = json_encode($inviteMessage->data);
- $chatMessages = $this->create(array($inviteMessage), $userID);
- $chatOutput = new stdclass();
- $chatOutput->result = 'success';
- $chatOutput->method = 'messagesend';
- $chatOutput->users = array($userID);
- $chatOutput->data = $chatMessages;
- return $chatOutput;
- }
- /**
- * create xuanbot message.
- *
- * @param int $userID
- * @param string $content
- * @param string $contentType
- * @param object $sender
- * @return stdclass
- */
- public function createXuanBotMessage($userID, $content, $contentType, $sender = null)
- {
- $message = new stdclass();
- $message->gid = imModel::createGID();
- $message->cgid = "$userID&xuanbot";
- $message->user = "xuanbot";
- $message->type = 'normal';
- $message->content = $content;
- $message->contentType = $contentType;
- if ($sender)
- {
- $message->data = new stdclass();
- $message->data->sender = $sender;
- $message->data = json_encode($message->data);
- }
- $chatMessages = $this->create(array($message), $userID);
- $chatOutput = new stdclass();
- $chatOutput->result = 'success';
- $chatOutput->method = 'messagesend';
- $chatOutput->users = array($userID);
- $chatOutput->data = $chatMessages;
- return $chatOutput;
- }
- /**
- * set message with new content.
- * @param $message
- * @return void
- */
- public function setMessage($message)
- {
- $msg = $this->encodeMessage($message);
- $this->dao->update(TABLE_IM_MESSAGE)->set('content')->eq($msg->content)->where('id')->eq($message->id)->exec();
- }
- /**
- * Get message list by cgid.
- *
- * @param string|array $cgid
- * @param object $pager
- * @param string $startDate
- * @access public
- * @return array
- */
- public function getListByCgid($cgid, $pager = null, $startDate = '')
- {
- if(empty($startDate)) $tables = $this->getAllTables();
- if(!empty($startDate)) $tables = $this->getTablesByDateRange($startDate);
- if(empty($tables)) return array();
- $queries = array();
- foreach($tables as $table)
- {
- $queries[] = $this->dao->select('*')->from($table->tableName)->where('1=1')
- ->beginIF(!is_array($cgid))->andWhere('cgid')->eq($cgid)->fi()
- ->beginIF(is_array($cgid))->andWhere('cgid')->in($cgid)->fi()
- ->beginIF(!empty($startDate))->andWhere('date')->ge($startDate)->fi()
- ->get();
- }
- $query = join(' UNION ALL ', $queries);
- $sql = $this->dao->select('*')->from(TABLE_IM_MESSAGE);
- $sql->sqlobj->sql = 'SELECT * FROM (' . $query . ') as t';
- /* Assemble order by and pager stuff. */
- $messages = $sql
- ->orderBy('id_desc')
- ->beginIF($pager != null)->page($pager)->fi()
- ->fetchAll();
- return $this->format($this->decodeMessages($messages));
- }
- /**
- * Fetch chat and message information if chat's last read is greater than user's last known.
- *
- * @param int $lastKnownMessage
- * @param int $userID
- * @access public
- * @return array of chat => lastReadMessage, empty if all last reads are not greater than last known.
- */
- public function getMissedByLastKnown($lastKnownMessage, $userID = 0)
- {
- $missedMessageChats = $this->dao->select('cgid,lastReadMessage')->from(TABLE_IM_CHATUSER)
- ->where('user')->eq($userID)
- ->andWhere('lastReadMessage')->gt($lastKnownMessage)
- ->fetchPairs();
- $messageIDs = array();
- foreach($missedMessageChats as $cgid => $lastReadID)
- {
- $chatMessageIDs = $this->getListAroundIDForUser($cgid, $lastReadID, true, 5, $userID, true);
- $messageIDs = array_merge($messageIDs, $chatMessageIDs);
- }
- $unreadIDs = $this->dao->select('message')->from(TABLE_IM_MESSAGESTATUS)
- ->where('user')->eq($userID)
- ->andWhere('status')->eq('waiting')
- ->fetchAll();
- $missedIDs = array_diff($messageIDs, array_keys($unreadIDs));
- if(empty($missedIDs)) return array();
- return $this->getList('', $missedIDs);
- }
- /**
- * Get offline messages.
- *
- * @param bool $full
- * @param int $userID
- * @access public
- * @return array
- */
- public function getOfflineList($full = false, $userID = 0)
- {
- $messageIDs = $this->dao->select('message')->from(TABLE_IM_MESSAGESTATUS)
- ->where('user')->eq($userID)
- ->andWhere('status')->eq('waiting')
- ->fetchPairs('message');
- if(empty($messageIDs)) return array();
- if(!$full)
- {
- $firstRecordID = $this->dao->select('MIN(id)')->from(TABLE_IM_MESSAGE)->fetch('MIN(id)');
- $messageIDs = array_filter(
- $messageIDs,
- function($id) use ($firstRecordID)
- {
- return $id >= $firstRecordID;
- }
- );
- }
- if(empty($messageIDs)) return array();
- $messages = $this->getList('', $messageIDs, null, '', '!notify', false);
- $messageIDs = array();
- foreach($messages as $message) array_push($messageIDs, $message->id);
- $this->dao->delete()->from(TABLE_IM_MESSAGESTATUS)
- ->where('user')->eq($userID)
- ->andWhere('message')->in($messageIDs)
- ->exec();
- return $this->format($messages);
- }
- /**
- * Get history list.
- *
- * @param object $user
- * @param string $device
- * @access public
- * @return array
- */
- public function getHistoryList($user, $device = 'desktop')
- {
- $gids = $this->loadModel('im')->chat->getGidListByUserID($user->id);
- $startDate = $this->loadModel('setting')->getItem("owner={$user->account}&module=common§ion=lastLogin&key={$device}");
- if(!empty($startDate) && !empty($gids)) return $this->getListByCgid($gids, null, $startDate);
- }
- /**
- * Create a output of broadcast.
- *
- * @param string $type
- * @param object $chat
- * @param array $onlineUsers
- * @param int $userID
- * @param array $members
- * @param bool $setReminders if true, send members and userID as property "reminders" in $message->data.
- * @access public
- * @return object
- */
- public function createBroadcast($type, $chat, $onlineUsers, $userID, $members = array(), $setReminders = false)
- {
- $adminUsers = array();
- $message = new stdclass();
- $message->gid = imModel::createGID();
- $message->cgid = $chat->gid;
- $message->type = 'broadcast';
- $message->contentType = 'text';
- $message->content = $this->getBroadcastContent($type, $chat, $userID, $members);
- $message->date = helper::now();
- $message->user = $userID;
- if($setReminders)
- {
- $membersData = array_merge($members, array($userID));
- $message->data = json_encode(array('reminders' => $membersData));
- }
- /* If quit a chat, only send broadcast to the admins or the created user of chat. */
- if($type == 'leaveChat')
- {
- if($chat->admins)
- {
- if(is_array($chat->admins))
- {
- $adminUsers = array_map(function($value){
- return trim($value);
- }, $chat->admins);
- }
- if(is_string($chat->admins))
- {
- $adminUsers = explode(',', trim($chat->admins, ','));
- }
- }
- if(!$adminUsers)
- {
- $user = $this->loadModel('user')->getByAccount($chat->createdBy);
- if($user) $adminUsers = array($user->id);
- }
- $users = $this->loadModel('im')->user->getList($status = 'online', $adminUsers);
- $onlineUsers = array_keys($users);
- }
- /* Save broadcast to im_message. */
- $messages = $this->create(array($message), $userID);
- $offlineUsers = $this->loadModel('im')->user->getList($status = 'offline', $chat->members);
- $this->saveOfflineList($messages, array_keys($offlineUsers));
- $output = new stdclass();
- $output->method = 'messagesend';
- if(dao::isError())
- {
- $output->result = 'fail';
- $output->message = 'Send message failed.';
- }
- else
- {
- $output->result = 'success';
- $output->users = $onlineUsers;
- $output->data = $messages;
- }
- return $output;
- }
- /**
- * Get content of broadcast.
- *
- * @param string $type
- * @param object $chat
- * @param int $userID
- * @param array $members
- * @access public
- * @return string
- */
- public function getBroadcastContent($type, $chat, $userID, $members)
- {
- $user = $this->loadModel('im')->userGetByID($userID);
- $userName = empty($user->realname) ? $user->account : $user->realname;
- $userMention = "[@$userName](@#$user->id)";
- if(stripos($type, 'changeChatOwnership') === 0)
- {
- $nameInMarkdown = preg_replace('/([#\\`*_{}\[\]\(\)\+\-\.!])/i', '\\\\$1', $chat->name);
- return sprintf($this->lang->im->broadcast->$type, $nameInMarkdown, $chat->gid, $userMention);
- }
- if($type == 'chatMerged')
- {
- return sprintf($this->lang->im->broadcast->$type, $chat->name, $chat->intoName);
- }
- if($type == 'mergeChat')
- {
- return sprintf($this->lang->im->broadcast->$type, $chat->name);
- }
- if($type == 'renamePrivate')
- {
- $nameInMarkdown = preg_replace('/([#\\`*_{}\[\]\(\)\+\-\.!])/i', '\\\\$1', $chat->name);
- return sprintf($this->lang->im->broadcast->$type, $nameInMarkdown, $chat->gid);
- }
- if($type == 'createChat' or $type == 'renameChat')
- {
- $nameInMarkdown = preg_replace('/([#\\`*_{}\[\]\(\)\+\-\.!])/i', '\\\\$1', $chat->name);
- return sprintf($this->lang->im->broadcast->$type, $userMention, $nameInMarkdown, $chat->gid);
- }
- if(in_array($type, array('inviteUser', 'createConferenceInvitation', 'mergeChatWithMembers')))
- {
- $memberMentions = array();
- $membersList = $this->im->userGetList('', $members, true, true);
- foreach($membersList as $member)
- {
- $memberName = empty($member->realname) ? $member->account : $member->realname;
- $memberMentions[] = "[@$memberName](@#$member->id)";
- }
- $memberMentions = implode($this->lang->im->connector, $memberMentions);
- if($type == 'mergeChatWithMembers') return sprintf($this->lang->im->broadcast->mergeChatWithMembers, $chat->name, $memberMentions);
- return sprintf($this->lang->im->broadcast->$type, $userMention, $memberMentions);
- }
- if($type == 'archiveChat' or $type == 'unarchiveChat')
- {
- $nameInMarkdown = preg_replace('/([#\\`*_{}\[\]\(\)\+\-\.!])/i', '\\\\$1', $chat->name);
- return sprintf($this->lang->im->broadcast->$type, $userMention, $nameInMarkdown);
- }
- return sprintf($this->lang->im->broadcast->$type, $userMention);
- }
- /**
- * Retract one message.
- *
- * @param string $gid
- * @param boolean $byAdmin
- * @param int $deletedBy
- * @access public
- * @return array
- */
- public function retract($gid = '', $byAdmin = false, $deletedBy = 0)
- {
- $message = $this->dao->select('id, gid, cgid, `index`, user, date, data, deleted, type, contentType')->from(TABLE_IM_MESSAGE)->where('gid')->eq($gid)->fetch();
- $archiveDate = $this->dao->select('archiveDate')->from(TABLE_IM_CHAT)->where('gid')->eq($message->cgid)->fetch('archiveDate');
- if(!empty($archiveDate) && $archiveDate != null) return array();
- $messageLife = (strtotime(helper::now()) - strtotime($message->date)) / 60;
- if($messageLife <= $this->config->im->retract->validTime && $message->user == $deletedBy)
- {
- $message->deleted = 1;
- $this->dao->update(TABLE_IM_MESSAGE)->set('deleted')->eq($message->deleted)->where('gid')->eq($gid)->exec();
- }
- elseif($byAdmin)
- {
- $messageData = empty($message->data) ? new stdClass() : json_decode($message->data);
- $bySelf = $message->user == $deletedBy;
- if(!$bySelf)
- {
- $messageData->deletedBy = $deletedBy;
- $message->data = $messageData;
- }
- $message->deleted = 1;
- if($bySelf) $this->dao->update(TABLE_IM_MESSAGE)->set('deleted')->eq($message->deleted)->where('gid')->eq($gid)->exec();
- else $this->dao->update(TABLE_IM_MESSAGE)->set('deleted')->eq($message->deleted)->set('data')->eq(json_encode($messageData))->where('gid')->eq($gid)->exec();
- }
- return $this->format(array($message));
- }
- /**
- * Save offline messages.
- *
- * @param array $messages
- * @param array $users
- * @access public
- * @return bool
- */
- public function saveOfflineList($messages = array(), $users = array())
- {
- /* Prevent deleted users from being stored in TABLE_MESSAGESTATUS. */
- $deletedUsers = $this->dao->select('id')->from(TABLE_USER)->where('deleted')->eq('1')->fetchPairs();
- $users = array_values(array_diff($users, $deletedUsers));
- foreach($messages as $message)
- {
- $this->saveStatus($users, $message->id, 'waiting');
- }
- return !dao::isError();
- }
- /**
- * Save message status.
- *
- * @param array $users
- * @param int $message
- * @param string $status
- * @access public
- * @return bool
- */
- public function saveStatus($users, $message, $status = 'waiting')
- {
- if(empty($users) || empty($message)) return false;
- if($this->config->db->driver == 'mysql') // Optimized for mysql.
- {
- $queryData = array_map(
- function($user) use ($message, $status)
- {
- return "($user,$message,'$status')";
- },
- $users
- );
- $query = "INSERT INTO " . TABLE_IM_MESSAGESTATUS . "(`user`,`message`,`status`) VALUES " . join(',', $queryData) . "ON DUPLICATE KEY UPDATE `status`=`status`";
- $this->dao->query($query); $this->dao->setCache(trim(TABLE_IM_MESSAGESTATUS, "`"));
- }
- else
- {
- foreach($users as $user)
- {
- $data = new stdclass();
- $data->user = $user;
- $data->message = $message;
- $data->status = $status;
- $this->dao->replace(TABLE_IM_MESSAGESTATUS)->data($data)->exec();
- }
- }
- return !dao::isError();
- }
- /**
- * Get notify.
- * @access public
- * @return array
- */
- public function getNotifyList()
- {
- $onlineUsers = $this->loadModel('im')->user->getList('online');
- if(empty($onlineUsers)) return array();
- $onlineUsers = array_keys($onlineUsers);
- $messageUserPairsData = $this->dao->select('message,user')->from(TABLE_IM_MESSAGESTATUS)
- ->where('status')->eq('waiting')
- ->andWhere('user')->in($onlineUsers)
- ->fetchAll();
- if(empty($messageUserPairsData)) return array();
- $messageUserPairs = array();
- foreach($messageUserPairsData as $data)
- {
- if(isset($messageUserPairs[$data->message]))
- {
- $messageUserPairs[$data->message][] = $data->user;
- continue;
- }
- $messageUserPairs[$data->message] = array($data->user);
- }
- $notifyMessages = $this->getList('', array_keys($messageUserPairs), null, '', 'notify', false);
- if(empty($notifyMessages)) return array();
- $messageIDs = array();
- foreach($notifyMessages as $message) $messageIDs[] = $message->id;
- $messageUserPairs = array_intersect_key($messageUserPairs, array_flip($messageIDs));
- $notifications = $this->formatNotify($notifyMessages);
- $data = array();
- $messages = array();
- foreach($notifications as $message)
- {
- foreach($messageUserPairs[$message->id] as $userID)
- {
- $messages[$userID][] = $message->id;
- $data[$userID][] = $message;
- }
- }
- foreach($messages as $userID => $message)
- {
- $this->dao->delete()->from(TABLE_IM_MESSAGESTATUS)
- ->where('message')->in($message)
- ->andWhere('user')->eq($userID)
- ->exec();
- }
- return $data;
- }
- /**
- * Get offline notify.
- * @param $userID
- * @return array
- */
- public function getNotifyByUserID($userID)
- {
- $messageIDs = $this->dao->select('message')->from(TABLE_IM_MESSAGESTATUS)
- ->where('user')->eq($userID)
- ->andWhere('status')->eq('waiting')
- ->fetchPairs('message');
- if(empty($messageIDs)) return array();
- $messages = $this->getList('', $messageIDs, null, '', 'notify', false);
- if(empty($messages)) return array();
- $notifications = $this->formatNotify($messages);
- $messages = array();
- foreach($notifications as $message) $messages[] = $message->id;
- $this->dao->delete()->from(TABLE_IM_MESSAGESTATUS)
- ->where('message')->in($messages)
- ->andWhere('user')->eq($userID)
- ->exec();
- return $notifications;
- }
- /**
- * Foramt messages for notify.
- * @param object $messages
- * @access public
- * @return array
- */
- public function formatNotify($messages)
- {
- $notifications = array();
- foreach($messages as $message)
- {
- $data = new stdClass();
- $messageData = json_decode($message->data);
- $data->id = $message->id;
- $data->gid = $message->gid;
- $data->cgid = $message->cgid;
- $data->type = $message->type;
- $data->content = $message->deleted ? '' : $message->content;
- $data->date = strtotime($message->date);
- $data->contentType = $message->contentType;
- $data->title = $messageData->title;
- $data->subtitle = $messageData->subtitle;
- $data->url = $messageData->url;
- $data->actions = $messageData->actions;
- $data->sender = $messageData->sender;
- $data->users = $messageData->target;
- if($data->cgid != 'notification' && !empty($message->index)) $data->index = $message->index;
- $notifications[] = $data;
- }
- return $notifications;
- }
- /**
- * Create a bot welcome message.
- *
- * @param int $userID
- * @param bool $needUpdate
- * @access public
- * @return void
- */
- public function createXuanbotWelcomeNotify($userID, $needUpdate = false)
- {
- $sender = $this->loadModel('im')->bot->createDefaultBotSender();
- if($needUpdate) $this->createNotify(array($userID), $this->lang->im->bot->upgradeWelcome->title, '', $this->lang->im->bot->upgradeWelcome->content, 'text', $this->lang->im->bot->upgradeWelcome->link, array(), $sender);
- $this->createNotify(array($userID), $this->lang->im->bot->welcome->title, '', $this->lang->im->bot->welcome->content, 'text', $this->lang->im->bot->welcome->link, array(), $sender);
- }
- /**
- * Notify that detached conference is enabled.
- *
- * @access public
- * @return void
- */
- public function createDetachedConferenceEnableNotify()
- {
- $sender = $this->loadModel('im')->bot->createDefaultBotSender();
- $allUsers = $this->im->userGetList();
- $allUsers = array_keys($allUsers);
- $this->createNotify($allUsers, $this->lang->im->detachedConferenceUpgradeMessage->title, '', $this->lang->im->detachedConferenceUpgradeMessage->content, 'text', $this->lang->im->bot->upgradeWelcome->link, array(), $sender);
- }
- /**
- * Insert message for notify.
- * @param string $target
- * @param string $title
- * @param string $subtitle
- * @param string $content
- * @param string $contentType
- * @param string $url
- * @param array $actions
- * @param int $sender
- * @access public
- * @return bool
- */
- public function createNotify($target = '', $title = '', $subtitle = '', $content = '', $contentType = 'text', $url = '', $actions = array(), $sender = 0)
- {
- /* Check if target is a chat gid or a list of users. */
- if(is_array($target))
- {
- $cgid = 'notification';
- }
- else
- {
- $cgid = $target;
- $target = $this->dao->select('user')->from(TABLE_IM_CHATUSER)
- ->where('cgid')->eq($target)
- ->andWhere("(quit is null or quit = null)")
- ->fetchPairs('user');
- }
- $users = $this->loadModel('im')->user->getList('', $target);
- $info = array();
- $info['title'] = $title;
- $info['subtitle'] = $subtitle;
- $info['url'] = $url;
- $info['actions'] = $actions;
- $info['sender'] = $sender;
- $info['target'] = array_keys($users);
- $notify = new stdClass();
- $notify->gid = imModel::createGID();
- $notify->cgid = $cgid;
- $notify->user = 0;
- $notify->date = helper::now();
- $notify->type = 'notify';
- $notify->content = $content;
- $notify->contentType = $contentType;
- $notify->data = json_encode($info);
- $msgIndex = $this->dao->select('`lastMessageIndex` + 1 as msgIndex')->from(TABLE_IM_CHAT)->where('gid')->eq($cgid)->fetch('msgIndex');
- if(empty($msgIndex)) $msgIndex = 1;
- $notify->index = $msgIndex;
- $msgIndex = $this->insertMessageEnsuringUniqueIndex($this->encodeMessage($notify));
- if($msgIndex === false) return false;
- /* Get last insert id from dbh directly, as dao->lastInsertID() is not usable for records inserted by dbh. */
- $message = $this->dao->dbh->lastInsertId();
- if(empty($message)) return false;
- $this->dao->update(TABLE_IM_CHAT)
- ->set('lastActiveTime')->eq(helper::now())
- ->set('lastMessage')->eq($message)
- ->set('lastMessageIndex')->eq($msgIndex)
- ->where('gid')->eq($cgid)->exec();
- $this->saveStatus($info['target'], $message, 'waiting');
- return !dao::isError();
- }
- /**
- * Add offline messages according to the gid of messages that failed to be sent.
- * @param array $sendfail
- * @access public
- * @return bool
- */
- public function sendFailures($sendfail = array())
- {
- foreach($sendfail as $userID => $gid)
- {
- if(empty($gid)) continue;
- $idList = $this->dao->select('id')->from(TABLE_IM_MESSAGE)->where('gid')->in($gid)->fetchPairs();
- $messages = $this->getList('', $idList);
- $this->saveOfflineList($messages, array($userID));
- }
- return !dao::isError();
- }
- /**
- * Get message count for block.
- *
- * @access public
- * @return object
- */
- public function getCountForBlock()
- {
- $masterTableTotal = $this->dao->select('COUNT(*) AS masterTableTotal')->from(TABLE_IM_MESSAGE)->where('deleted')->eq('0')->fetch('masterTableTotal');
- $partitionsTotal = $this->dao->select('SUM(`count`) AS partitionsTotal')->from(TABLE_IM_CHAT_MESSAGE_INDEX)->fetch('partitionsTotal');
- $dayCount = $this->dao->select("count(1) AS dayCount")->from(TABLE_IM_MESSAGE)->where('date')->gt(date('Y-m-d H:i', strtotime('-1 day')))->andWhere('deleted')->eq('0')->fetch('dayCount');
- $hourCount = $this->dao->select("count(1) AS hourCount")->from(TABLE_IM_MESSAGE)->where('date')->gt(date('Y-m-d H:i', strtotime('-1 hour')))->andWhere('deleted')->eq('0')->fetch('hourCount');
- $count = new stdClass();
- $count->total = $masterTableTotal + ($partitionsTotal || 0);
- $count->day = $dayCount;
- $count->hour = $hourCount;
- if(empty($count->total))
- {
- $count->total = 0;
- $count->day = 0;
- $count->hour = 0;
- }
- return $count;
- }
- /**
- * Get all message tables.
- *
- * @access public
- * @return array
- */
- public function getAllTables()
- {
- $tables = $this->dao->select('tableName')->from(TABLE_IM_MESSAGE_INDEX)->fetchAll();
- foreach($tables as $key => $table) $tables[$key]->messages = '';
- $master = new stdclass;
- $master->tableName = TABLE_IM_MESSAGE;
- $master->messages = '';
- $tables[] = $master;
- return $tables;
- }
- /**
- * Get all message tables with cgid.
- *
- * @param string $cgid
- * @access public
- * @return array
- */
- public function getChatAllTables($cgid = '')
- {
- if($cgid == '') return array();
- $tables = $this->dao->select('DISTINCT tableName')->from(TABLE_IM_CHAT_MESSAGE_INDEX)->fetchAll();
- foreach($tables as $key => $table) $tables[$key]->messages = '';
- $master = new stdclass;
- $master->tableName = TABLE_IM_MESSAGE;
- $master->messages = '';
- $tables[] = $master;
- return $tables;
- }
- /**
- * Get message table names by message IDs.
- *
- * @param array $messageIDs
- * @access public
- * @return array
- */
- public function getTableByMessages($messageIDs)
- {
- $tables = array();
- $indices = $this->dao->select('tableName,start,end')->from(TABLE_IM_MESSAGE_INDEX)->fetchAll('tableName');
- $processedIDs = array();
- foreach($indices as $index)
- {
- $min = $index->start;
- $max = $index->end;
- $ids = array_filter(
- $messageIDs,
- function($id) use ($min, $max)
- {
- return $id >= $min && $id <= $max;
- }
- );
- if(!empty($ids))
- {
- $result = new stdclass();
- $result->tableName = $index->tableName;
- $result->messages = $ids;
- $tables[$index->tableName] = $result;
- $processedIDs = array_merge($processedIDs, $ids);
- }
- }
- $unindexed = array_diff($messageIDs, $processedIDs);
- if(!empty($unindexed))
- {
- $result = new stdclass();
- $result->tableName = TABLE_IM_MESSAGE;
- $result->messages = $unindexed;
- $tables[TABLE_IM_MESSAGE] = $result;
- }
- return $tables;
- }
- /**
- * Get chat message table names by message indexes.
- *
- * @param string $cgid
- * @param array $indexes
- * @access public
- * @return array
- */
- public function getChatTablesByChatIndexes($cgid, $indexes)
- {
- if($cgid == '') return array();
- $indexIds = $indexes;
- $tables = array();
- $indices = $this->dao->select('tableName,startIndex,endIndex')->from(TABLE_IM_CHAT_MESSAGE_INDEX)->where('gid')->eq($cgid)->fetchAll('tableName');
- $processedIDs = array();
- foreach($indices as $index)
- {
- $min = $index->startIndex;
- $max = $index->endIndex;
- $ids = array_filter(
- $indexes,
- function($id) use ($min, $max)
- {
- return $id >= $min && $id <= $max;
- }
- );
- $indexes = array_diff($indexes, $ids);
- if(!empty($ids))
- {
- $result = new stdclass();
- $result->tableName = $index->tableName;
- $result->messages = $ids;
- $tables[$index->tableName] = $result;
- $processedIDs = array_merge($processedIDs, $ids);
- }
- }
- $unindexed = array_diff($indexIds, $processedIDs);
- if(!empty($unindexed))
- {
- $result = new stdclass();
- $result->tableName = TABLE_IM_MESSAGE;
- $result->messages = $unindexed;
- $tables[TABLE_IM_MESSAGE] = $result;
- }
- return $tables;
- }
- /**
- * Get tables by start (and / or) end date).
- *
- * @param string $startDate
- * @param string $endDate
- * @access public
- * @return array
- */
- public function getTablesByDateRange($startDate = '', $endDate = '')
- {
- $tables = $this->dao->select('tableName,startDate,endDate')->from(TABLE_IM_MESSAGE_INDEX)
- ->where('1=1')
- ->beginIF(!empty($startDate))->andWhere('endDate')->ge($startDate)->fi()
- ->beginIF(!empty($endDate))->andWhere('startDate')->le($endDate)->fi()
- ->fetchAll('tableName');
- if(empty($tables)) $appendMaster = true;
- /* If endDate is even later than the max endDate we have in the index, append the master table. */
- elseif(!empty($endDate))
- {
- $maxEndDate = max(array_map(
- function($t)
- {
- return $t->endDate;
- },
- $tables
- ));
- if($maxEndDate < $endDate) $appendMaster = true;
- }
- if(isset($appendMaster))
- {
- $master = new stdclass();
- $master->tableName = TABLE_IM_MESSAGE;
- $master->startDate = isset($maxEndDate) ? $maxEndDate : null;
- $master->endDate = '9999-12-31 23:59:59';
- $tables[] = $master;
- }
- return $tables;
- }
- /**
- * Get chat tables by start (and / or) end date).
- *
- * @param string $cgid
- * @param string $startDate
- * @param string $endDate
- * @access public
- * @return array
- */
- public function getChatTablesByDateRange($cgid = '', $startDate = '', $endDate = '')
- {
- if($cgid == '') return array();
- $tables = $this->dao->select('tableName,startDate,endDate')->from(TABLE_IM_CHAT_MESSAGE_INDEX)
- ->where('gid')->eq($cgid)
- ->beginIF(!empty($startDate))->andWhere('endDate')->ge($startDate)->fi()
- ->beginIF(!empty($endDate))->andWhere('startDate')->le($endDate)->fi()
- ->fetchAll('tableName');
- if(empty($tables)) $appendMaster = true;
- /* If endDate is even later than the max endDate we have in the index, append the master table. */
- elseif(!empty($endDate))
- {
- $maxEndDate = max(array_map(
- function($t)
- {
- return $t->endDate;
- },
- $tables
- ));
- if($maxEndDate < $endDate) $appendMaster = true;
- }
- if(isset($appendMaster))
- {
- $master = new stdclass();
- $master->tableName = TABLE_IM_MESSAGE;
- $master->startDate = isset($maxEndDate) ? $maxEndDate : null;
- $master->endDate = '9999-12-31 23:59:59';
- $tables[] = $master;
- }
- return $tables;
- }
- /**
- * Get tables by gids of chats.
- *
- * @param array|string $cgids
- * @access public
- * @return array
- */
- public function getTablesByChats($cgids)
- {
- return $this->dao->select('DISTINCT tableName')->from(TABLE_IM_CHAT_MESSAGE_INDEX)
- ->where('gid')->in($cgids)
- ->fetchPairs();
- }
- /**
- * Mark an ongoing partition opreation.
- *
- * @param bool $ongoing
- * @access public
- * @return bool true if successfully marked, otherwise there already is another ongoing opreation.
- */
- public function markOngoingPartition($ongoing)
- {
- $this->loadModel('setting');
- $lastStatus = $this->setting->getItem("owner=system&module=common§ion=partition&key=ongoing");
- if($ongoing != $lastStatus)
- {
- $this->setting->setItem('system.common.partition.ongoing', $ongoing);
- return true;
- }
- return false;
- }
- /**
- * Check if message count in the master table exceeds twice the partition size.
- *
- * @access public
- * @return bool true if partition is needed.
- */
- public function needPartition()
- {
- $currentID = $this->dao->select('id')->from(TABLE_IM_MESSAGE)->orderBy('id_desc')->limit(1)->fetch('id');
- $lastIndexID = $this->dao->select('end')->from(TABLE_IM_MESSAGE_INDEX)->orderBy('end_desc')->limit(1)->fetch('end');
- return $currentID > ($lastIndexID + 2 * $this->config->im->partition->messagePerTable);
- }
- /**
- * Backup master message table.
- *
- * @param int $fromID
- * @param int $toID
- * @access public
- * @return int count of message affected.
- */
- public function backupMasterTable($fromID, $toID = 0)
- {
- $insertStmt = $this->dao->insert(TABLE_IM_MESSAGE_BACKUP)->get();
- $selectStmt = $this->dao->select('*')->from(TABLE_IM_MESSAGE)
- ->where('id')->ge($fromID)
- ->beginIF(!empty($toID))->andWhere('id')->le($toID)->fi()
- ->get();
- $stmt = substr($insertStmt, 0, -4) . $selectStmt;
- return $this->dao->exec($stmt);
- }
- /**
- * Delete a large amount of rows from im_message table
- * by selecting the rest into a new table and replace current table with the new one.
- *
- * @param int $end
- * @param int $start
- * @access public
- * @return bool
- */
- public function deleteFromMasterTable($end, $start = 0)
- {
- $tmpTable = str_replace(array('`', '-'), '', sprintf("%s_%s", TABLE_IM_MESSAGE, 'tmp_' . helper::today()));
- $oldTable = str_replace(array('`', '-'), '', sprintf("%s_%s", TABLE_IM_MESSAGE, 'old_' . helper::today()));
- $zdb = $this->app->loadClass('zdb');
- $fields = $zdb->getTableFields(TABLE_IM_MESSAGE);
- foreach($fields as $k => $f) if($f->default === "'\\''") $fields[$k]->default = ''; // fix wrong values from zdb of zentao
- $zdb->createTable($tmpTable, $fields);
- $insertStmt = $this->dao->insert($tmpTable)->get();
- $selectStmt = $this->dao->select('*')->from(TABLE_IM_MESSAGE)
- ->where('id')->gt($end)
- ->beginIF($start)->orWhere('id')->lt($start)->fi()
- ->get();
- $stmt = substr($insertStmt, 0, -4) . $selectStmt;
- $this->dao->exec($stmt);
- $renameQuery = 'RENAME TABLE ' . TABLE_IM_MESSAGE . ' TO ' . $oldTable . '; ';
- $renameQuery .= 'RENAME TABLE ' . $tmpTable . ' TO ' . TABLE_IM_MESSAGE . '; ';
- $this->dao->exec($renameQuery);
- $dropQuery = "DROP TABLE $oldTable;";
- $this->dao->exec($dropQuery);
- return !dao::isError();
- }
- /**
- * Partition master message table into smaller tables.
- *
- * @access public
- * @return bool
- */
- public function partitionTable()
- {
- /* Check if creation of a new partition is needed. */
- if(!$this->needPartition()) return false;
- /* Set memory limit to avoid OOMs. */
- ini_set('memory_limit', -1);
- /* Start a transaction. */
- $this->dao->begin();
- /* Create a new message table. */
- $zdb = $this->app->loadClass('zdb');
- $fields = $zdb->getTableFields(TABLE_IM_MESSAGE);
- $fields['id']->extra = ''; // disable auto_increment.
- $currentTable = $this->dao->select('id,end')->from(TABLE_IM_MESSAGE_INDEX)->orderBy('id_desc')->limit(1)->fetch();
- $newTable = str_replace('`', '', sprintf("%s_%s", TABLE_IM_MESSAGE, ++$currentTable->id));
- foreach($fields as $k => $f) if($f->default === "'\\''") $fields[$k]->default = ''; // fix wrong values from zdb of zentao
- $zdb->createTable($newTable, $fields);
- /* Select messages and insert into the new table. */
- $insertStmt = $this->dao->insert($newTable)->get();
- $selectStmt = $this->dao->select('*')->from(TABLE_IM_MESSAGE)
- ->where('id')->gt($currentTable->end)
- ->andWhere('id')->le($currentTable->end + $this->config->im->partition->messagePerTable)
- ->get();
- $stmt = substr($insertStmt, 0, -4) . $selectStmt;
- $this->dao->exec($stmt);
- /* Index the new table. */
- $this->reindex($newTable);
- /* Backup the master table. */
- $this->backupMasterTable($currentTable->end + 1, $currentTable->end + $this->config->im->partition->messagePerTable);
- /* Delete messages from master table on success. */
- $this->deleteFromMasterTable($currentTable->end + $this->config->im->partition->messagePerTable);
- /* End the transaction. */
- $this->dao->commit();
- return !dao::isError();
- }
- /**
- * Index partitions.
- *
- * @param string $table table to index
- * @access public
- * @return void
- */
- public function reindex($table = '')
- {
- $MAXID = 'MAX(id)';
- $MINID = 'MIN(id)';
- $MAXINDEX = 'MAX(`index`)';
- $MININDEX = 'MIN(`index`)';
- $IDDATE = 'id,date';
- $messageMeta = new stdclass();
- $firstRecord = $this->dao->select($IDDATE)->from($table)->orderBy('id')->limit(1)->fetch();
- $lastRecord = $this->dao->select($IDDATE)->from($table)->orderBy('id_desc')->limit(1)->fetch();
- $chats = $this->dao->select('DISTINCT(cgid)')->from($table)->fetchPairs();
- $messageMeta->tableName = $table;
- $messageMeta->start = $firstRecord->id;
- $messageMeta->end = $lastRecord->id;
- $messageMeta->startDate = $firstRecord->date;
- $messageMeta->endDate = $lastRecord->date;
- $messageMeta->chats = ',' . join(',', $chats) . ',';
- $this->dao->insert(TABLE_IM_MESSAGE_INDEX)->data($messageMeta)->exec();
- $chatsInfo = $this->dao->select('cgid,MAX(id),MIN(id),MAX(`index`),MIN(`index`),count(*)')->from($table)->groupBy('cgid')->fetchAll();
- $messages = array();
- foreach($chatsInfo as $info)
- {
- $messages[] = $info->{$MAXID};
- $messages[] = $info->{$MINID};
- }
- $messages = array_unique($messages);
- $messageDates = $this->dao->select($IDDATE)->from($table)->where('id')->in($messages)->fetchPairs('id');
- $values = array();
- foreach($chatsInfo as $info)
- {
- $meta = array();
- $meta[] = $table;
- $meta[] = $info->cgid;
- $meta[] = $info->{$MINID};
- $meta[] = $info->{$MAXID};
- $meta[] = $info->{$MININDEX};
- $meta[] = $info->{$MAXINDEX};
- $meta[] = $messageDates[$info->{$MINID}];
- $meta[] = $messageDates[$info->{$MAXID}];
- $meta[] = $info->{'count(*)'};
- $data = "('" . join("','", $meta) . "')";
- $values[] = $data;
- }
- $insertStmt = $this->dao->insert(TABLE_IM_CHAT_MESSAGE_INDEX)->get();
- $insertStmt = substr($insertStmt, 0, -4) . '(`tableName`,`gid`,`start`,`end`,`startIndex`,`endIndex`,`startDate`,`endDate`,`count`) VALUES ' . join(',', $values);
- $this->dao->exec($insertStmt);
- }
- /**
- * codec string with rot47.
- *
- * @access public
- * @param string $str string to be rot47
- * @return string
- */
- public function codecWithRot47($str)
- {
- return strtr($str, '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~', 'PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO');
- }
- /**
- * decode text with rot47 & base64.
- *
- * @access public
- * @param string $text text to be rot47
- * @return string
- */
- public function decodeText($text)
- {
- $rot47Encoded = $this->codecWithRot47($text);
- return base64_decode($rot47Encoded);
- }
- /**
- * decode chat messages.
- *
- * @access public
- * @param array $messages messages to be decode
- * @return array
- */
- public function decodeMessages($messages)
- {
- if(isset($this->config->xuanxuan->messageEncrypt) && ($this->config->xuanxuan->messageEncrypt == 'on') && commonModel::isLicensedMethod('im', 'messageEncrypt'))
- {
- $config = $this->config;
- return array_map(function($msg) use ($config)
- {
- if(isset($msg->content) && isset($config->xuanxuan->lastUnEncryptMessageId) && intval($config->xuanxuan->lastUnEncryptMessageId) <= $msg->id)
- {
- $msg->content = $this->decodeText($msg->content);
- }
- return $msg;
- }, $messages);
- }
- return $messages;
- }
- /**
- * decode one chat message.
- *
- * @access public
- * @param object $message message to be decode
- * @return string
- */
- public function decodeMessage($message)
- {
- if(isset($this->config->xuanxuan->messageEncrypt) && ($this->config->xuanxuan->messageEncrypt == 'on') && commonModel::isLicensedMethod('im', 'messageEncrypt') && isset($this->config->xuanxuan->lastUnEncryptMessageId) && intval($this->config->xuanxuan->lastUnEncryptMessageId) <= $message->id)
- {
- $message->content = $this->decodeText($message->content);
- }
- return $message;
- }
- /**
- * encode one chat message.
- *
- * @access public
- * @param object $message message to be encode
- * @return object
- */
- public function encodeMessage($message)
- {
- if(isset($this->config->xuanxuan->messageEncrypt) && ($this->config->xuanxuan->messageEncrypt == 'on') && commonModel::isLicensedMethod('im', 'messageEncrypt'))
- {
- $message->content = $this->codecWithRot47(base64_encode($message->content));
- }
- return $message;
- }
- /**
- * get last message id
- *
- * @access public
- * @return int
- */
- public function getLastMessageId()
- {
- $lastMessageId = $this->dao->select('id')
- ->from(TABLE_IM_MESSAGE)
- ->orderBy('id desc')
- ->limit(1)
- ->fetch('id');
- if(!$lastMessageId)
- {
- $messagesCount = $this->dao->select('COUNT(*)')->from(TABLE_IM_MESSAGE)->fetch('COUNT(*)');
- if($messagesCount == 0)
- {
- $messagesIdxCount = $this->dao->select('COUNT(*)')->from(TABLE_IM_MESSAGE_INDEX)->fetch('COUNT(*)');
- if($messagesIdxCount > 0)
- {
- $lastMessageId = $this->dao->select('MAX(end)')->from(TABLE_IM_MESSAGE_INDEX)->fetch('MAX(end)');
- }
- else
- {
- $lastMessageId = 0;
- }
- }
- }
- return $lastMessageId;
- }
- }
|