Exemplo n.º 1
0
 /**
  * @args {"description": "Delay: Clean offline client and helpdesk every minute"}
  */
 public function perform()
 {
     $accounts = Account::findAll([]);
     foreach ($accounts as $account) {
         $accountId = $account->_id;
         $setting = HelpDeskSetting::findOne(['accountId' => $accountId]);
         if (!empty($setting)) {
             $maxWaitTime = $setting->maxWaitTime;
             // Close timeout conversation
             $chatConversations = ChatConversation::findAll(['accountId' => $accountId, 'status' => ChatConversation::STATUS_OPEN, 'lastChatTime' => ['$lt' => TimeUtil::msTime(time() - $maxWaitTime * 60)]]);
             foreach ($chatConversations as $chatConversation) {
                 HelpDesk::disconnect($chatConversation->_id, ['type' => 'brake']);
             }
             // Delete timeout pending client
             $pendingClients = PendingClient::findAll(['accountId' => $accountId, 'lastPingTime' => ['$lt' => TimeUtil::msTime(time() - PendingClient::PING_THRESHOLD)]]);
             foreach ($pendingClients as $pendingClient) {
                 $pendingClient->delete();
             }
             // Clean offline helpdesk
             $cache = Yii::$app->cache;
             $onlineHelpDesks = $cache->get(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId);
             if (!empty($onlineHelpDesks)) {
                 foreach ($onlineHelpDesks as $deskId => $lastPingTime) {
                     if ($lastPingTime < TimeUtil::msTime(time() - HelpDesk::PING_THRESHOLD)) {
                         HelpDesk::leave($deskId, $accountId, ['type' => 'droping']);
                     }
                 }
             }
         }
     }
 }
Exemplo n.º 2
0
 public function getCreatorDetail()
 {
     if (!empty($this->origin) && $this->origin !== IssueUser::HELPDESK) {
         return $this->hasOne(IssueUser::className(), ['_id' => 'creator']);
     }
     return $this->hasOne(HelpDesk::className(), ['_id' => 'creator']);
 }
Exemplo n.º 3
0
 /**
  * Set helpdesk notification type
  */
 public function actionSetNotificationType()
 {
     $helpdesks = HelpDesk::findAll([]);
     if (!empty($helpdesks)) {
         foreach ($helpdesks as $helpdesk) {
             if (!isset($helpdesk->notificationType)) {
                 $helpdesk->notificationType = HelpDesk::NOTIFICATION_TYPE_DESKTOP_MARK;
                 if ($helpdesk->save()) {
                     echo "Success to set notification type to helpdesk " . $helpdesk->_id . "\n";
                 } else {
                     echo "Fail to set notification type to helpdesk " . $helpdesk->_id . " successfully" . "\n";
                 }
             }
         }
     }
 }
Exemplo n.º 4
0
 /**
  * This method is used to push message.
  *  If mobile help-desk client is on backend, push message.
  */
 public function pushMessage($deskId, $eventType, $extra, $message = null)
 {
     $cache = \Yii::$app->cache;
     $deskStrId = $deskId . '';
     //Update the helpdesk unread message count
     $count = $cache->get(ConversationController::UNREAD_COUNT_PREFIX . $deskStrId);
     empty($count) && ($count = 0);
     $desk = HelpDesk::findByPk($deskId);
     if (empty($desk->deviceToken) || empty($desk->environment)) {
         LogUtil::info(['push' => 'missing device token', 'desk' => $deskId, 'eventType' => $eventType, 'extra' => $extra]);
     } else {
         if ($eventType == ConversationController::EVENT_CHAT_MESSAGE && $this->isPushMessage($deskId, $desk->accountId)) {
             $cache->set(ConversationController::UNREAD_COUNT_PREFIX . $deskStrId, ++$count);
         } else {
             //no code here, push state like 'sessionConnected', no need to add $count
         }
         $extra['type'] = $eventType;
         $target = [$desk->environment => [$desk->deviceToken]];
         \Yii::$app->tuisongbao->pushMessage($target, $count, $extra, $message);
     }
 }
Exemplo n.º 5
0
 public function actionRemoveTag()
 {
     $result = ['status' => 'ok'];
     $params = $this->getParams();
     $accountId = $this->getAccountId();
     if (empty($params['tagName'])) {
         throw new BadRequestHttpException("Tag name can not be empty");
     }
     if (empty($params['helpdeskIds'])) {
         throw new BadRequestHttpException("Helpdesk id can not be empty");
     }
     $tagName = $params['tagName'];
     $helpdeskIds = $params['helpdeskIds'];
     if (!empty($helpdeskIds)) {
         $helpdeskMongoIds = [];
         foreach ($helpdeskIds as $helpdeskId) {
             $helpdeskMongoIds[] = new \MongoId($helpdeskId);
         }
         HelpDesk::removeTag($tagName, $helpdeskMongoIds);
     }
     return $result;
 }
Exemplo n.º 6
0
 public function getUser()
 {
     $userId = $this->getUserId();
     return HelpDesk::findOne(['_id' => $userId]);
 }
 /**
  * Used for tuisongbao web hook, realtime engine will call the service when user subscribe or unsubscribe a channel
  * @return string json status
  */
 public function actionUserState()
 {
     $body = $this->getParams();
     $headers = Yii::$app->request->getHeaders();
     $signature = hash_hmac('sha256', json_encode($body), TUISONGBAO_SECRET);
     LogUtil::info(['body' => $body, 'signature' => $signature], 'webhook');
     // Check whether it is called by the tuisongbao web hook
     if ($signature === $headers['X-Engine-Signature']) {
         //TODO: First event may not be the correct one
         $time = $body['timestamp'];
         $event = $body['events'][0];
         $userId = $event['userId'];
         $eventName = $event['name'];
         //Tip: Only handle the user_removed event on global channel here
         if (strpos($event['channel'], ChatConversation::CHANNEL_GLOBAL) !== false && ChatConversation::MONGOID_LENGTH === strlen($userId)) {
             $helpdesk = HelpDesk::findByPk(new \MongoId($userId));
             if (!empty($helpdesk)) {
                 if ($eventName == ChatConversation::EVENT_USER_ADDED || $eventName == ChatConversation::EVENT_USER_REMOVED) {
                     $updateTimePrefix = 'wm-update-time';
                     $cache = Yii::$app->cache;
                     $lastTime = $cache->get($updateTimePrefix . $userId);
                     // failed events will be send again, but timestamp will not change
                     // if a event is failed and other event has success, skip that failed event
                     // Event_1. user_add failed; Event_2, user_remove successed; Event_1. user_add successed
                     // skip Event_1. user_add successed
                     if (empty($lastTime) || $lastTime < $time) {
                         $cache->set($updateTimePrefix . $userId, $time);
                         HelpDesk::cacheOnlineDesks($helpdesk->_id . '', $helpdesk->accountId . '', $eventName);
                     }
                 }
             }
         }
     }
 }
Exemplo n.º 8
0
 /**
  * Reset password
  */
 public function actionResetPassword()
 {
     $code = $this->getParams('code');
     $newPassword = $this->getParams('password');
     $result = Validation::validateCode($code);
     if ($result == Validation::LINK_INVALID) {
         throw new BadRequestHttpException(Yii::t('common', 'link_invalid'));
     } else {
         if ($result == Validation::LINK_EXPIRED) {
             throw new BadRequestHttpException(Yii::t('common', 'link_expired'));
         }
     }
     $userId = $result;
     $user = HelpDesk::findByPk($userId);
     if (empty($user)) {
         throw new BadRequestHttpException(Yii::t('commmon', 'incorrect_userid'));
     }
     // update the user password
     $user->password = HelpDesk::encryptPassword($newPassword, $user->salt);
     if (!$user->save()) {
         throw new ServerErrorHttpException("Save user failed!");
     }
     return ['status' => 'ok'];
 }
Exemplo n.º 9
0
 /**
  * Get the latest documents of the pending clients.
  * The clients should ping the server in the threshold or sourced from WeChat.
  * @param Integer $count the count of the elements to be dequeued
  * @return array the client.
  */
 public static function deQueue($deskId, $count = 1)
 {
     $helpdesk = HelpDesk::findOne($deskId);
     $condition = ['$or' => [['lastPingTime' => ['$gt' => TimeUtil::msTime(time() - self::PING_THRESHOLD)]], ['source' => ['$in' => [ChatConversation::TYPE_WECHAT, ChatConversation::TYPE_WEIBO, ChatConversation::TYPE_ALIPAY]]]], 'accountId' => $helpdesk->accountId];
     if (!empty($helpdesk->tags)) {
         $condition['tags'] = ['$in' => $helpdesk->tags];
         $pendingClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count)->all();
         if (count($pendingClients) < $count) {
             $condition['tags'] = ['$nin' => $helpdesk->tags];
             $excludeTagClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count - count($pendingClients))->all();
             $pendingClients = array_merge_recursive($pendingClients, $excludeTagClients);
         }
     } else {
         $pendingClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count)->all();
     }
     $clients = [];
     foreach ($pendingClients as $pendingClient) {
         $client = ['nick' => $pendingClient->nick, 'avatar' => $pendingClient->avatar, 'openId' => $pendingClient->openId, 'source' => $pendingClient->source, 'sourceChannel' => $pendingClient->sourceChannel];
         if ($pendingClient->accountInfo) {
             $client['accountInfo'] = $pendingClient->accountInfo;
         }
         $clients[] = $client;
         $pendingClient->delete();
     }
     return $clients;
 }
Exemplo n.º 10
0
 public function getAssigneeDetail()
 {
     return $this->hasOne(HelpDesk::className(), ['_id' => 'assignee']);
 }
Exemplo n.º 11
0
 public static function saveRecord($desk, $client, $status, $accountId)
 {
     $chatConversation = new ChatConversation();
     $channelName = self::getChannelName($desk->_id, $client['openId']);
     $chatConversation->conversation = $channelName;
     $chatConversation->status = $status;
     $chatConversation->desk = ['id' => $desk->_id, 'badge' => $desk->badge, 'email' => $desk->email, 'avatar' => $desk->avatar, 'name' => $desk->name];
     $chatConversation->client = $client;
     $chatConversation->accountId = $accountId;
     $chatConversation->lastChatTime = TimeUtil::msTime();
     if (!$chatConversation->save()) {
         LogUtil::error(['message' => 'save chat conversation failed', 'error' => $chatConversation->errors], 'helpdesk');
         throw new ServerErrorHttpException('save chatConversation failed');
     } else {
         HelpDesk::incClientCount($desk->_id);
     }
     return $chatConversation;
 }
Exemplo n.º 12
0
 public function perform()
 {
     $args = $this->args;
     $message = $args['message'];
     $type = $message['msgType'];
     $content = $message['content'];
     $WEConnectAccountInfo = $args['account'];
     $WEConnectUserInfo = $args['user'];
     $accountId = new \MongoId($args['accountId']);
     $accountInfoType = null;
     $source = null;
     switch ($WEConnectAccountInfo['channel']) {
         case 'WEIBO':
             $source = ChatConversation::TYPE_WEIBO;
             $accountInfoType = 'WEIBO';
             break;
         case 'ALIPAY':
             $source = ChatConversation::TYPE_ALIPAY;
             $accountInfoType = 'ALIPAY';
             break;
         case 'WEIXIN':
             $source = ChatConversation::TYPE_WECHAT;
             $accountInfoType = $WEConnectAccountInfo['accountType'];
             break;
         default:
             throw new BadRequestHttpException("Unsupported channel type");
             break;
     }
     $client = ['nick' => $WEConnectUserInfo['nickname'], 'avatar' => $WEConnectUserInfo['headerImgUrl'], 'openId' => $WEConnectUserInfo['id'], 'originId' => $WEConnectUserInfo['originId'], 'source' => $source, 'sourceChannel' => $WEConnectUserInfo['accountId'], 'accountId' => $accountId, 'accountInfo' => ['type' => $accountInfoType, 'name' => $WEConnectAccountInfo['name']]];
     ResqueUtil::log(['message' => 'get message from wechat', 'WEConnectAccountInfo' => $WEConnectAccountInfo, 'WEConnectUserInfo' => $WEConnectUserInfo, 'accountId' => $accountId]);
     if (empty($type) || empty($content)) {
         ResqueUtil::log(['message' => 'missing required fields', 'senario' => 'accepting messages from WeConnect', 'WEConnectAccountInfo' => $WEConnectAccountInfo, 'WEConnectUserInfo' => $WEConnectUserInfo]);
         HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_ERROR);
         return;
     }
     try {
         if ('EVENT' === $type) {
             switch ($content) {
                 case 'CONNECT':
                     $isInWorkingHour = HelpDeskSetting::isInWorkingHours($accountId);
                     if ($isInWorkingHour) {
                         if (empty($WEConnectAccountInfo)) {
                             ResqueUtil::log(['message' => 'Account parameters missing', 'senario' => 'Wechat message, event connect']);
                             HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_ERROR);
                             return;
                         }
                         //check if the wechat enduser has connected to a helpdesk
                         $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId);
                         if (!empty($conversation)) {
                             ResqueUtil::log(['message' => 'Have connect to helpdesk already', 'senario' => 'Wechat message, event connect', 'user' => $WEConnectUserInfo, 'accountId' => $accountId]);
                             HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_CUSTOM, ChatConversation::NO_DUPLICATE_CLIENT);
                             return;
                         }
                         return HelpDesk::connect($client, $accountId);
                     } else {
                         //send the disconnect event to WeConnect
                         Yii::$app->weConnect->sendCustomerServiceMessage($WEConnectUserInfo['id'], $WEConnectUserInfo['accountId'], ['msgType' => ChatConversation::WECHAT_MESSAGE_TYPE_EVENT, 'content' => 'DISCONNECT']);
                         HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_NONWORKING);
                     }
                     break;
                 case 'DISCONNECT':
                     //get the conversationId
                     $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId, ['type' => 'left']);
                     if (empty($conversation)) {
                         return ['status' => 'ok'];
                     }
                     //disconnect
                     return HelpDesk::disconnect($conversation->_id, ['type' => 'brake']);
                     break;
                 default:
                     throw new BadRequestHttpException("Unsupported event content type");
                     break;
             }
         } else {
             //get the conversation information
             $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId);
             if (empty($conversation)) {
                 $pendingClient = PendingClient::findOne(['openId' => $WEConnectUserInfo['id'], 'accountId' => $accountId]);
                 if (!empty($pendingClient)) {
                     HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_WAITTING);
                     return ['status' => 'ok'];
                 } else {
                     return HelpDesk::connect($client, $accountId);
                 }
             }
             $desk = $conversation->desk;
             $desk['id'] = (string) $desk['id'];
             $client = $conversation->client;
             $sentTime = TimeUtil::msTime();
             //trigger send message event
             $name = ChatConversation::EVENT_CHAT_MESSAGE;
             $data = ['conversationId' => (string) $conversation->_id, 'desk' => $desk, 'client' => $client, 'chatMessage' => ['content' => ['msgType' => $type, 'body' => $content], 'sentTime' => $sentTime], 'isReply' => false];
             //add chatMessage record
             $chatMessage = ChatMessage::saveRecord(['msgType' => $type, 'body' => $content], $sentTime, false, $conversation->_id, $accountId);
             //helpDesk web client message
             $channels = [ChatConversation::getChannelName($desk['id'], $client['openId'])];
             $data['messageId'] = (string) $chatMessage->_id;
             Yii::$app->tuisongbao->triggerEvent($name, $data, $channels);
             //helpDesk mobile client message
             $pushExtra = ['messageId' => (string) $chatMessage->_id, 'openId' => $client['openId'], 'conversationId' => (string) $conversation->_id, 'sentTime' => $chatMessage->sentTime];
             $pushMessage = empty($client['nick']) ? $content : $client['nick'] . ':' . $content;
             ChatConversation::pushMessage($desk['id'], ChatConversation::EVENT_CHAT_MESSAGE, $pushExtra, $pushMessage);
             return ['status' => 'ok'];
         }
     } catch (Exception $e) {
         LogUtil::error(['message' => $e->getMessage()], 'helpdesk');
         return HelpDesk::sendSystemReplyByType($client, $account, HelpDeskSetting::REPLY_ERROR);
     }
 }
Exemplo n.º 13
0
 public function actionCheckAuth()
 {
     $secret = TUISONGBAO_SECRET;
     $socketId = $this->getParams('socketId');
     $channelName = $this->getParams('channelName');
     $authData = $this->getParams('authData');
     //helpdesk is saved as authData
     $parts = explode(':', $authData);
     //If it is the helpdesk
     $clientId = $parts[1];
     $userData = ['userId' => $clientId, 'userInfo' => []];
     if ('h' === $parts[0]) {
         $client = HelpDesk::findByPk(new \MongoId($clientId));
         $userData['userInfo'] = ['badge' => $client->badge, 'email' => $client->email];
     }
     $userDataJsonStr = json_encode($userData);
     $strToSign = $socketId . ':' . $channelName . ':' . $userDataJsonStr;
     LogUtil::info(['strToSign' => $strToSign, 'secret' => $secret], 'signature');
     $signature = hash_hmac('sha256', $strToSign, $secret);
     LogUtil::info(['signature' => $signature, 'channelData' => $userDataJsonStr], 'signature');
     $result = ['signature' => $signature, 'channelData' => $userDataJsonStr];
     header("Content-Type:application/json");
     echo json_encode($result);
 }
Exemplo n.º 14
0
 /**
  * Activate a new user
  *
  * <b>Request Type</b>: POST<br/><br/>
  * <b>Request Endpoint</b>:http://{server-domain}/site/update-info<br/><br/>
  * <b>Content-type</b>: application/json<br/><br/>
  * <b>Summary</b>: This api is used for a user to activate account
  * <br/><br/>
  *
  * <b>Request Params</b>:<br/>
  *     name: string, the user name, required<br/>
  *     password: string, the user password, required<br/>
  *     id: string, the user id, required<br/>
  *     avatar: string, the user avatar, required<br/>
  *     code: string, the user validation code, required<br/>
  *     <br/><br/>
  *
  * <b>Response Params:</b><br/>
  *     ack: integer, mark the create result, 0 means create successfully, 1 means create fail<br/>
  *     data: array, json array to describe user id<br/>
  *     <br/><br/>
  *
  * <b>Request Example:</b><br/>
  * <pre>
  * {
  *     "name" : "sarazhang",
  *     "password" : "45345345gdfgdf",
  *     "id" : "643hfjht567",
  *     "avatar" : "http://www.baidu.com/1.jpg",
  *     "code" : "543gfdg45745sd",
  *
  * }
  * </pre>
  * <br/><br/>
  *
  * <b>Response Example</b>:<br/>
  * <pre>
  * {
  *    'ack' : 1,
  *    'data': {"id": "5345gdfg45745"}
  * }
  * </pre>
  */
 public function actionUpdateInfo()
 {
     $data = $this->getParams();
     if (empty($data['password']) || empty($data['name']) || empty($data['id']) || $data['password'] === md5('')) {
         throw new BadRequestHttpException(Yii::t('common', 'parameters_missing'));
     }
     $code = empty($data['code']) ? '' : $data['code'];
     $type = empty($data['type']) ? '' : $data['type'];
     $result = Validation::validateCode($code, false);
     if ($result == Validation::LINK_INVALID) {
         throw new GoneHttpException(Yii::t('common', 'link_invalid'));
     } else {
         if ($result == Validation::LINK_EXPIRED) {
             throw new GoneHttpException(Yii::t('common', 'link_invalid'));
         }
     }
     $salt = StringUtil::rndString(6);
     $password = User::encryptPassword($data['password'], $salt);
     $name = $data['name'];
     $avatar = $data['avatar'];
     $id = $data['id'];
     if (!empty($type) && $type == self::ACCOUNT_INVITATION) {
         $user = User::findOne(['_id' => $id]);
         $accountId = $user->accountId;
         if (empty(User::getByName($accountId, $name))) {
             $user->isActivated = User::ACTIVATED;
             $user->salt = $salt;
             $user->language = Yii::$app->language;
             $user->password = $password;
             $user->name = $name;
             $user->avatar = $avatar;
             $flag = $user->save();
         } else {
             throw new InvalidParameterException(['name' => Yii::t('common', 'name_exist')]);
         }
     } else {
         if (!empty($type) && $type == self::HELPDESK_INVITATION) {
             $helpDesk = HelpDesk::findOne(['_id' => $id]);
             $accountId = $helpDesk->accountId;
             if (empty(HelpDesk::getByName($accountId, $name))) {
                 $helpDesk->isActivated = User::ACTIVATED;
                 $helpDesk->language = Yii::$app->language;
                 $helpDesk->salt = $salt;
                 $helpDesk->password = $password;
                 $helpDesk->name = $name;
                 $helpDesk->avatar = $avatar;
                 $flag = $helpDesk->save();
             } else {
                 throw new InvalidParameterException(['name' => Yii::t('common', 'name_exist')]);
             }
         }
     }
     if ($flag) {
         Validation::deleteAll(['code' => $code]);
         return ['id' => $id, 'type' => $type];
     }
     throw new ServerErrorHttpException('activate fail');
 }
Exemplo n.º 15
0
 /**
  * HelpDesk offline
  * @param string $deskId
  * @param MongoId $accountId
  * @param  Object $extra
  * @return array
  */
 public static function leave($deskId, $accountId, $extra)
 {
     $cache = Yii::$app->cache;
     //remove it from conversation caches
     $conversations = $cache->get('conversations' . $accountId);
     unset($conversations[$deskId]);
     $cache->set('conversations' . $accountId, $conversations);
     //remove it from online caches
     self::cacheOnlineDesks($deskId, (string) $accountId, ChatConversation::EVENT_USER_REMOVED);
     //remove the helpdesk ping time
     $onlineHelpDesks = $cache->get(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId);
     unset($onlineHelpDesks[$deskId]);
     $cache->set(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId, $onlineHelpDesks);
     //get the conversation records in db according to the deskId
     $conversationInstances = ChatConversation::findOpenByDeskId(new \MongoId($deskId), $accountId);
     foreach ($conversationInstances as $conversationInstance) {
         $clientTemp = $conversationInstance->client;
         $desk = $conversationInstance->desk;
         //trigger deskLeft event
         $data = ['conversationId' => (string) $conversationInstance['_id'], 'desk' => $desk, 'client' => $clientTemp, 'extra' => $extra, 'sentTime' => TimeUtil::msTime()];
         $channels = [ChatConversation::getChannelName($deskId, $clientTemp['openId'])];
         Yii::$app->tuisongbao->triggerEvent(ChatConversation::EVENT_DESK_LEFT, $data, $channels);
         //set the status of the chatConversation to "closed"
         ChatConversation::closeById($conversationInstance['_id']);
         HelpDesk::sendSystemReplyByType($clientTemp, $accountId, isset($extra['type']) ? $extra['type'] : HelpDeskSetting::REPLY_CLOSE);
     }
     //flush the client count in db
     helpDesk::flushClientCount(new \MongoId($deskId));
     LogUtil::info(['event' => 'deskLeft', 'deskId' => $deskId], 'helpdesk');
     return ['status' => 'ok'];
 }