/** * Starts chat process. * * @param Request $request Incoming request. * @return string|\Symfony\Component\HttpFoundation\RedirectResponse Rendered * page content or a redirect response. */ public function startAction(Request $request) { $operator = $this->getOperator(); $thread_id = $request->attributes->getInt('thread_id'); // Check if the thread can be loaded. $thread = Thread::load($thread_id); if (!$thread || !isset($thread->lastToken)) { return $this->showErrors(array(getlocal('Wrong thread'))); } $view_only = $request->query->get('viewonly') == 'true'; $force_take = $request->query->get('force') == 'true'; $try_take_over = !$view_only && $thread->state == Thread::STATE_CHATTING && $operator['operatorid'] != $thread->agentId; if ($try_take_over) { if (!is_capable(CAN_TAKEOVER, $operator)) { return $this->showErrors(array(getlocal('Cannot take over'))); } if ($force_take == false) { $link = $this->generateUrl('chat_operator_start', array('thread_id' => $thread_id, 'force' => 'true')); $page = array('user' => $thread->userName, 'agent' => $thread->agentName, 'link' => $link, 'title' => getlocal('Change operator')); // Show confirmation page. return $this->render('confirm', $page); } } if (!$view_only) { if (!$thread->take($operator)) { return $this->showErrors(array(getlocal('Cannot take thread'))); } } elseif (!is_capable(CAN_VIEWTHREADS, $operator)) { return $this->showErrors(array(getlocal('Cannot view threads'))); } // Redrect the operator to initialized chat page $redirect_to = $this->generateUrl('chat_operator', array('thread_id' => intval($thread_id), 'token' => urlencode($thread->lastToken))); return $this->redirect($redirect_to); }
/** * Process chat thread redirection. * * @param Request $request Incoming request. * @return string|\Symfony\Component\HttpFoundation\RedirectResponse Rendered * page content or a redirect response. * @throws NotFoundException If the thread with specified ID and token is * not found. * @throws BadRequestException If one or more arguments have a wrong format. */ public function redirectAction(Request $request) { $thread_id = $request->attributes->get('thread_id'); $token = $request->attributes->get('token'); $thread = Thread::load($thread_id, $token); if (!$thread) { throw new NotFoundException('The thread is not found.'); } $page = array('errors' => array()); if ($request->query->has('nextGroup')) { // The thread was redirected to a group. $next_id = $request->query->get('nextGroup'); if (!preg_match("/^\\d{1,10}\$/", $next_id)) { throw new BadRequestException('Wrong value of "nextGroup" argument.'); } $next_group = group_by_id($next_id); if ($next_group) { $page['message'] = getlocal('The visitor has been placed in a priorty queue of the group {0}.', array(get_group_name($next_group))); if (!$this->redirectToGroup($thread, (int) $next_id)) { $page['errors'][] = getlocal('You are not chatting with the visitor.'); } } else { $page['errors'][] = 'Unknown group'; } } else { // The thread was redirected to an operator. $next_id = $request->query->get('nextAgent'); if (!preg_match("/^\\d{1,10}\$/", $next_id)) { throw new BadRequestException('Wrong value of "nextAgent" argument.'); } $next_operator = operator_by_id($next_id); if ($next_operator) { $page['message'] = getlocal('The visitor has been placed in the priorty queue of the operator {0}.', array(get_operator_name($next_operator))); if (!$this->redirectToOperator($thread, $next_id)) { $page['errors'][] = getlocal('You are not chatting with the visitor.'); } } else { $page['errors'][] = 'Unknown operator'; } } $page = array_merge_recursive($page, setup_logo()); if (count($page['errors']) > 0) { return $this->render('error', $page); } else { return $this->render('redirected', $page); } }
/** * Process submitting of the mail form. * * @param Request $request Incoming request. * @return string Rendered page content. * @throws NotFoundException If the thread with specified ID and token is * not found. */ public function submitFormAction(Request $request) { $errors = array(); $thread_id = $request->attributes->get('thread_id'); $token = $request->attributes->get('token'); // Try to load the thread $thread = Thread::load($thread_id, $token); if (!$thread) { throw new NotFoundException('The thread is not found.'); } $email = $request->request->get('email'); $group = $thread->groupId ? group_by_id($thread->groupId) : null; if (!$email) { $errors[] = no_field('Your email'); } elseif (!MailUtils::isValidAddress($email)) { $errors[] = wrong_field('Your email'); } if (count($errors) > 0) { $request->attributes->set('errors', $errors); // Render the mail form again return $this->showFormAction($request); } $history = ''; $last_id = -1; $messages = $thread->getMessages(true, $last_id); foreach ($messages as $msg) { $history .= message_to_text($msg); } // Load mail templates and substitute placeholders there. $mail_template = MailTemplate::loadByName('user_history', get_current_locale()); if ($mail_template) { $this->sendMail(MailUtils::buildMessage($email, MIBEW_MAILBOX, $mail_template->buildSubject(), $mail_template->buildBody(array($thread->userName, $history, Settings::get('title'), Settings::get('hosturl'))))); } else { trigger_error('Cannot send e-mail because "user_history" mail template cannot be loaded.', E_USER_WARNING); } $page = setup_logo($group); $page['email'] = $email; return $this->render('mailsent', $page); }
/** * Inviation was rejected by visitor * * Triggers {@link \Mibew\EventDispatcher\Events::INVITATION_REJECT} event. * * @param int $visitor_id ID of the visitor */ function invitation_reject($visitor_id) { $visitor = track_get_visitor_by_id($visitor_id); // Send message to operator $thread = Thread::load($visitor['threadid']); if ($thread) { $thread->postMessage(Thread::KIND_FOR_AGENT, getlocal('Visitor rejected invitation', null, get_current_locale(), true)); } $db = Database::getInstance(); $db->query("UPDATE {sitevisitor} v, {thread} t SET " . "v.threadid = NULL, " . "t.invitationstate = :invitation_rejected, " . "t.istate = :state_closed, " . "t.dtmclosed = :now " . "WHERE t.threadid = v.threadid " . "AND visitorid = :visitor_id", array(':invitation_rejected' => Thread::INVITATION_REJECTED, ':state_closed' => Thread::STATE_CLOSED, ':visitor_id' => $visitor_id, ':now' => time())); $args = array('invitation' => $thread); EventDispatcher::getInstance()->triggerEvent(Events::INVITATION_REJECT, $args); }
/** * Process chat in an invitation block. * * @param Request $request Incoming request. * @return string|\Symfony\Component\HttpFoundation\RedirectResponse Rendered * page content or a redirect response. */ public function invitationAction(Request $request) { // Check if an user tries to use invitation functionality when it's // disabled. if (!Settings::get('enabletracking')) { return $this->redirect($this->generateUrl('chat_user_start')); } // Check if we should force the user to use SSL. $ssl_redirect = $this->sslRedirect($request); if ($ssl_redirect !== false) { return $ssl_redirect; } // Check if user invited to chat. $invitation_state = invitation_state($_SESSION[SESSION_PREFIX . 'visitorid']); if (!$invitation_state['invited'] || !$invitation_state['threadid']) { return $this->redirect($this->generateUrl('chat_user_start')); } $thread = Thread::load($invitation_state['threadid']); // Store own thread ids to restrict access for other people if (!isset($_SESSION[SESSION_PREFIX . 'own_threads'])) { $_SESSION[SESSION_PREFIX . 'own_threads'] = array(); } $_SESSION[SESSION_PREFIX . 'own_threads'][] = $thread->id; // Prepare page $page = setup_invitation_view($thread); // Build js application options $page['invitationOptions'] = $page['invitation']; // Initialize client side application $this->getAssetManager()->attachJs('js/compiled/chat_app.js'); $this->getAssetManager()->attachJs( $this->startJsApplication($request, $page), AssetManagerInterface::INLINE, 1000 ); // Expand page return $this->render('chat', $page); }
/** * Loads thread by id and token and checks if thread loaded * * @param int $thread_id Id of the thread * @param int $last_token Last token of the thread * @return \Mibew\Thread * @throws \Mibew\RequestProcessor\ThreadProcessorException */ public static function getThread($thread_id, $last_token) { // Load thread $thread = Thread::load($thread_id, $last_token); // Check thread if (!$thread) { throw new ThreadProcessorException( 'Wrong thread', ThreadProcessorException::ERROR_WRONG_THREAD ); } // Return thread return $thread; }
/** * Builds a page with form for add/edit ban. * * @param Request $request Incoming request. * @return string Rendered page content. * @throws NotFoundException If the ban with specified ID is not found in * the system. * @throws BadRequestException If "thread" GET param is specified but has a * wrong format. */ public function showEditFormAction(Request $request) { $operator = $this->getOperator(); $page = array('banId' => '', 'saved' => false, 'thread' => '', 'threadid' => '', 'errors' => $request->attributes->get('errors', array())); if ($request->attributes->has('ban_id')) { $ban_id = $request->attributes->getInt('ban_id'); // Retrieve ban information from the database $ban = Ban::load($ban_id); if (!$ban) { throw new NotFoundException('The ban is not found.'); } $page['banId'] = $ban->id; $page['formaddress'] = $ban->address; $page['formdays'] = round(($ban->till - time()) / 86400); $page['formcomment'] = $ban->comment; } elseif ($request->query->has('thread')) { // Prepopulate form using thread data $thread_id = $request->query->has('thread'); if (!preg_match("/^\\d{1,10}\$/", $thread_id)) { throw new BadRequestException('Wrong value of "thread" argument.'); } $thread = Thread::load($thread_id); if ($thread) { $page['thread'] = htmlspecialchars($thread->userName); $page['threadid'] = $thread_id; $page['formaddress'] = $thread->remote; $page['formdays'] = 15; } } // Override form fields from the request if it is needed if ($request->isMethod('POST')) { $page['formaddress'] = $request->request->get('address'); $page['formdays'] = $request->request->get('days'); $page['formcomment'] = $request->request->get('comment'); $page['threadid'] = $request->request->get('threadid'); } $page['title'] = getlocal('Block address'); $page['formaction'] = $request->getBaseUrl() . $request->getPathInfo(); $page = array_merge($page, prepare_menu($operator, false)); return $this->render('ban', $page); }
/** * Returns content of the chat button. * * @param Request $request * @return string Rendered page content */ public function indexAction(Request $request) { $referer = $request->server->get('HTTP_REFERER', ''); // We need to display message about visited page only if the visitor // really change it. $new_page = empty($_SESSION[SESSION_PREFIX . 'last_visited_page']) || $_SESSION[SESSION_PREFIX . 'last_visited_page'] != $referer; // Display message about page change if ($referer && isset($_SESSION[SESSION_PREFIX . 'threadid']) && $new_page) { $thread = Thread::load($_SESSION[SESSION_PREFIX . 'threadid']); if ($thread && $thread->state != Thread::STATE_CLOSED) { $msg = getlocal("Visitor navigated to {0}", array($referer), $thread->locale, true); $thread->postMessage(Thread::KIND_FOR_AGENT, $msg); } } $_SESSION[SESSION_PREFIX . 'last_visited_page'] = $referer; $image = $request->query->get('i', ''); if (!preg_match("/^\\w+\$/", $image)) { $image = 'mibew'; } $lang = $request->query->get('lang', ''); if (!preg_match("/^[\\w-]{2,5}\$/", $lang)) { $lang = ''; } if (!$lang || !locale_is_available($lang)) { $lang = get_current_locale(); } $group_id = $request->query->get('group', ''); if (!preg_match("/^\\d{1,8}\$/", $group_id)) { $group_id = false; } if ($group_id) { if (Settings::get('enablegroups') == '1') { $group = group_by_id($group_id); if (!$group) { $group_id = false; } } else { $group_id = false; } } // Get image file content $image_postfix = has_online_operators($group_id) ? "on" : "off"; $file_name = "locales/{$lang}/button/{$image}_{$image_postfix}.png"; $content_type = 'image/png'; if (!is_readable($file_name)) { // Fall back to .gif image $file_name = "locales/{$lang}/button/{$image}_{$image_postfix}.gif"; $content_type = 'image/gif'; } $fh = fopen($file_name, 'rb'); if ($fh) { // Create response with image in body $file_size = filesize($file_name); $content = fread($fh, $file_size); fclose($fh); $response = new Response($content, 200); // Set correct content info $response->headers->set('Content-Type', $content_type); $response->headers->set('Content-Length', $file_size); } else { $response = new Response('Not found', 404); } // Disable caching $response->headers->addCacheControlDirective('no-cache', true); $response->headers->addCacheControlDirective('no-store', true); $response->headers->addCacheControlDirective('must-revalidate', true); $response->setExpires(new \DateTime('yesterday noon')); $response->headers->set('Pragma', 'no-cache'); return $response; }
/** * Generates a page with thread history (thread log). * * @param Request $request * @return string Rendered page content */ public function threadAction(Request $request) { $operator = $this->getOperator(); $page = array(); // Load thread info $thread = Thread::load($request->attributes->get('thread_id')); $group = group_by_id($thread->groupId); $thread_info = array('userName' => $thread->userName, 'userAddress' => get_user_addr($thread->remote), 'userAgentVersion' => get_user_agent_version($thread->userAgent), 'agentName' => $thread->agentName, 'chatTime' => $thread->modified - $thread->created, 'chatStarted' => $thread->created, 'groupName' => get_group_name($group)); $page['threadInfo'] = $thread_info; // Build messages list $last_id = -1; $messages = array_map('sanitize_message', $thread->getMessages(false, $last_id)); $page['title'] = getlocal("Chat log"); $page = array_merge($page, prepare_menu($operator, false)); $this->getAssetManager()->attachJs('js/compiled/thread_log_app.js'); $this->getAssetManager()->attachJs(sprintf('jQuery(document).ready(function(){Mibew.Application.start(%s);});', json_encode(array('messages' => $messages))), \Mibew\Asset\AssetManagerInterface::INLINE, 1000); return $this->render('history_thread', $page); }
/** * Save the thread to the database * * Triggers {@link \Mibew\EventDispatcher\Events::THREAD_UPDATE} and * {@link \Mibew\EventDispatcher\Events::THREAD_CREATE} events. * * @param boolean $update_revision Indicates if last modified time and last * revision should be updated. */ public function save($update_revision = true) { // Update modification time and revision number only if needed if ($update_revision) { $this->lastRevision = $this->nextRevision(); $this->modified = time(); } $db = Database::getInstance(); if (!$this->id) { $db->query('INSERT INTO {thread} (' . 'username, userid, agentname, agentid, ' . 'dtmcreated, dtmchatstarted, dtmmodified, dtmclosed, ' . 'lrevision, istate, invitationstate, ltoken, remote, ' . 'referer, nextagent, locale, lastpinguser, ' . 'lastpingagent, usertyping, agenttyping, ' . 'shownmessageid, useragent, messagecount, groupid' . ') VALUES (' . ':user_name, :user_id, :agent_name, :agent_id, ' . ':created, :chat_started, :modified, :closed, ' . ':revision, :state, :invitation_state, :token, :remote, ' . ':referer, :next_agent, :locale, :last_ping_user, ' . ':last_ping_agent, :user_typing, :agent_typing, ' . ':shown_message_id, :user_agent, :message_count, :group_id ' . ')', array(':user_name' => $this->userName, ':user_id' => $this->userId, ':agent_name' => $this->agentName, ':agent_id' => $this->agentId, ':created' => $this->created, ':chat_started' => $this->chatStarted, ':modified' => $this->modified, ':closed' => $this->closed, ':revision' => $this->lastRevision, ':state' => $this->state, ':invitation_state' => $this->invitationState, ':token' => $this->lastToken, ':remote' => $this->remote, ':referer' => $this->referer, ':next_agent' => $this->nextAgent, ':locale' => $this->locale, ':last_ping_user' => $this->lastPingUser, ':last_ping_agent' => $this->lastPingAgent, ':user_typing' => $this->userTyping, ':agent_typing' => $this->agentTyping, ':shown_message_id' => $this->shownMessageId, ':user_agent' => $this->userAgent, ':message_count' => $this->messageCount, ':group_id' => $this->groupId)); $this->id = $db->insertedId(); $args = array('thread' => $this); EventDispatcher::getInstance()->triggerEvent(Events::THREAD_CREATE, $args); } else { // Get the original state of the thread to trigger event later. $original_thread = Thread::load($this->id); $db->query('UPDATE {thread} SET ' . 'username = :user_name, userid = :user_id, ' . 'agentname = :agent_name, agentid = :agent_id, ' . 'dtmcreated = :created, dtmchatstarted = :chat_started, ' . 'dtmmodified = :modified, dtmclosed = :closed, ' . 'lrevision = :revision, istate = :state, ' . 'invitationstate = :invitation_state, ltoken = :token, ' . 'remote = :remote, referer = :referer, ' . 'nextagent = :next_agent, locale = :locale, ' . 'lastpinguser = :last_ping_user, ' . 'lastpingagent = :last_ping_agent, ' . 'usertyping = :user_typing, agenttyping = :agent_typing, ' . 'shownmessageid = :shown_message_id, ' . 'useragent = :user_agent, messagecount = :message_count, ' . 'groupid = :group_id ' . 'WHERE threadid = :thread_id', array(':thread_id' => $this->id, ':user_name' => $this->userName, ':user_id' => $this->userId, ':agent_name' => $this->agentName, ':agent_id' => $this->agentId, ':created' => $this->created, ':chat_started' => $this->chatStarted, ':modified' => $this->modified, ':closed' => $this->closed, ':revision' => $this->lastRevision, ':state' => $this->state, ':invitation_state' => $this->invitationState, ':token' => $this->lastToken, ':remote' => $this->remote, ':referer' => $this->referer, ':next_agent' => $this->nextAgent, ':locale' => $this->locale, ':last_ping_user' => $this->lastPingUser, ':last_ping_agent' => $this->lastPingAgent, ':user_typing' => $this->userTyping, ':agent_typing' => $this->agentTyping, ':shown_message_id' => $this->shownMessageId, ':user_agent' => $this->userAgent, ':message_count' => $this->messageCount, ':group_id' => $this->groupId)); $args = array('thread' => $this, 'original_thread' => $original_thread); EventDispatcher::getInstance()->triggerEvent(Events::THREAD_UPDATE, $args); } }
/** * Provides a gateway for widget requests. * * Triggers {@link \Mibew\EventDispatcher\Events::VISITOR_TRACK} event. * * @param Request $request * @return string Rendered page content */ public function indexAction(Request $request) { $operator = array(); $response_data = array('load' => array(), 'handlers' => array(), 'dependencies' => array(), 'data' => array()); $tracking_allowed = Settings::get('enabletracking') == '1' && (Settings::get('trackoperators') == '1' || !$this->getOperator()); if ($tracking_allowed) { $entry = $request->query->get('entry', ''); $referer = $request->server->get('HTTP_REFERER', ''); $user_id = $request->query->get('user_id', false); // Check if session was started if (isset($_SESSION[SESSION_PREFIX . 'visitorid']) && preg_match('/^[0-9]+$/', $_SESSION[SESSION_PREFIX . 'visitorid'])) { // Session was started. Just track the visitor. $visitor_id = track_visitor($_SESSION[SESSION_PREFIX . 'visitorid'], $entry, $referer); $visitor = track_get_visitor_by_id($visitor_id); } else { $visitor = track_get_visitor_by_user_id($user_id); if ($visitor !== false) { // Session is not started but the visitor exists in // database. Probably third-party cookies are disabled by // the browser. Use tracking by local cookie at target site. $visitor_id = track_visitor($visitor['visitorid'], $entry, $referer); } else { // Start tracking session $visitor_id = track_visitor_start($entry, $referer); $visitor = track_get_visitor_by_id($visitor_id); } } if ($visitor_id) { $_SESSION[SESSION_PREFIX . 'visitorid'] = $visitor_id; } if ($user_id === false) { // Update local cookie value at target site $response_data['handlers'][] = 'updateUserId'; $response_data['dependencies']['updateUserId'] = array(); $response_data['data']['user']['id'] = $visitor['userid']; } // Provide an ability for others to make something on visitor // tracking $event_arguments = array('visitor' => $visitor); EventDispatcher::getInstance()->triggerEvent(Events::VISITOR_TRACK, $event_arguments); // Get invitation state $invitation_state = invitation_state($visitor_id); // Check if invitation is closed if (!$invitation_state['invited'] && !empty($_SESSION[SESSION_PREFIX . 'invitation_threadid'])) { $response_data['handlers'][] = 'invitationClose'; $response_data['dependencies']['invitationClose'] = array(); unset($_SESSION[SESSION_PREFIX . 'invitation_threadid']); } // Check if the visitor is just invited to chat $is_invited = $invitation_state['invited'] && (empty($_SESSION[SESSION_PREFIX . 'invitation_threadid']) ? true : $_SESSION[SESSION_PREFIX . 'invitation_threadid'] != $invitation_state['threadid']); if ($is_invited) { // Load invitation thread $thread = Thread::load($invitation_state['threadid']); // Get operator info $operator = operator_by_id($thread->agentId); $locale = $request->query->get('locale', ''); $operator_name = $locale == get_home_locale() ? $operator['vclocalename'] : $operator['vccommonname']; $avatar_url = $operator['vcavatar'] ? $this->asset($operator['vcavatar'], AssetUrlGeneratorInterface::ABSOLUTE_URL) : false; // Show invitation dialog at widget side $response_data['handlers'][] = 'invitationCreate'; $response_data['dependencies']['invitationCreate'] = array(); $response_data['data']['invitation'] = array('operatorName' => htmlspecialchars($operator_name), 'avatarUrl' => htmlspecialchars($avatar_url), 'threadUrl' => $this->generateUrl('chat_user_invitation', array(), UrlGeneratorInterface::ABSOLUTE_URL), 'acceptCaption' => getlocal('Answer')); $_SESSION[SESSION_PREFIX . 'invitation_threadid'] = $thread->id; } // Check if the visitor rejects invitation if ($invitation_state['invited'] && $request->query->get('invitation_rejected')) { invitation_reject($visitor_id); } $event_arguments = array('visitor' => $visitor, 'request' => $request, 'response' => $response_data, 'route_url_generator' => $this->getRouter(), 'asset_url_generator' => $this->getAssetManager()->getUrlGenerator()); EventDispatcher::getInstance()->triggerEvent(Events::WIDGET_RESPONSE_ALTER, $event_arguments); $response_data = $event_arguments['response']; } // Builds JSONP response $response = new JsonResponse($response_data); $response->setCallback("Mibew.Objects.widget.onResponse"); // Add headers to overcome third-party cookies problem. $response->headers->set('P3P', 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); return $response; }