/** * Runs the cron. * * Triggers {@link \Mibew\EventDispatcher\Events::CRON_RUN} event. * * @param Request $request Incoming request. * @return string Rendered page content. */ public function runAction(Request $request) { $cron_key = $request->query->get('cron_key', ''); // Check cron security key if ($cron_key != Settings::get('cron_key')) { // Return an empty response return ''; } // Do the job. $worker = new CronWorker($this->getCache()); $success = $worker->run(); // Determine use or not quiet mode $quiet = $request->query->has('q'); if (!$quiet) { if ($success) { // Everything is fine. return 'All cron jobs done.'; } // Prepare error message for system's error log. $error_message = "Cron job failed. Here are the errors:\n"; foreach ($worker->getErrors() as $error) { $error_message .= ' ' . $error . "\n"; } trigger_error($error_message, E_USER_WARNING); // Let the client know about the problem. return 'Cron job failed.'; } }
/** * Runs the cron. * * Triggers {@link \Mibew\EventDispatcher\Events::CRON_RUN} event. * * @param Request $request Incoming request. * @return string Rendered page content. */ public function runAction(Request $request) { $cron_key = $request->query->get('cron_key', ''); // Check cron security key if ($cron_key != Settings::get('cron_key')) { // Return an empty response return ''; } // Determine use or not quiet mode $quiet = $request->query->has('q'); set_time_limit(0); // Remove stale cached items $this->getCache()->purge(); // Run cron jobs of the core calculate_thread_statistics(); calculate_operator_statistics(); calculate_page_statistics(); // Trigger cron event $dispatcher = EventDispatcher::getInstance(); $dispatcher->triggerEvent(Events::CRON_RUN); // Update time of last cron run Settings::set('_last_cron_run', time()); if (!$quiet) { // TODO: May be localize it return 'All cron jobs done.'; } }
/** * Renders operator's home page. * * @param Request $request Incoming request * @return string Rendered page content. */ public function dashboardAction(Request $request) { $operator = $this->getOperator(); $is_online = is_operator_online($operator['operatorid']); $page = array('version' => MIBEW_VERSION, 'localeLinks' => get_locale_links(), 'needUpdate' => version_compare(Settings::get('dbversion'), MIBEW_VERSION, '<'), 'profilePage' => $this->generateUrl('operator_edit', array('operator_id' => $operator['operatorid'])), 'isOnline' => $is_online, 'warnOffline' => true, 'title' => getlocal('Home'), 'menuid' => 'main'); $page = array_merge($page, prepare_menu($operator)); return $this->render('index', $page); }
function get_user_addr($addr) { if (Settings::get('geolink') && preg_match("/(\\d+\\.\\d+\\.\\d+\\.\\d+)/", $addr, $matches)) { $user_ip = $matches[1]; return get_popup(str_replace("{ip}", $user_ip, Settings::get('geolink')), '', htmlspecialchars($addr), "GeoLocation", "ip{$user_ip}", Settings::get('geolinkparams')); } return htmlspecialchars($addr); }
/** * Generates JavaScript code that starts client side application. * * @param Request $request Incoming request. * @param array $operator Current operator. * @return string JavaScript code that starts "users" client side * application. */ protected function startJsApplication(Request $request, $operator) { // Load dialogs style options $chat_style = new ChatStyle(ChatStyle::getCurrentStyle()); $chat_style_config = $style_config = $chat_style->getConfigurations(); // Load page style options $page_style_config = $style_config = $this->getStyle()->getConfigurations(); return sprintf('jQuery(document).ready(function() {Mibew.Application.start(%s);});', json_encode(array('server' => array('url' => $this->generateUrl('users_update'), 'requestsFrequency' => Settings::get('updatefrequency_operator')), 'agent' => array('id' => $operator['operatorid']), 'page' => array('mibewBasePath' => $request->getBasePath(), 'mibewBaseUrl' => $request->getBaseUrl(), 'showOnlineOperators' => Settings::get('showonlineoperators') == '1', 'showVisitors' => Settings::get('enabletracking') == '1', 'showPopup' => Settings::get('enablepopupnotification') == '1', 'threadTag' => $page_style_config['users']['thread_tag'], 'visitorTag' => $page_style_config['users']['visitor_tag'], 'agentLink' => $request->getBaseUrl() . '/operator/chat', 'geoLink' => Settings::get('geolink'), 'trackedLink' => $request->getBaseUrl() . '/operator/history/user-track', 'banLink' => $request->getBaseUrl() . '/operator/ban', 'inviteLink' => $request->getBaseUrl() . '/operator/invite', 'chatWindowParams' => $chat_style_config['chat']['window'], 'geoWindowParams' => Settings::get('geolinkparams'), 'trackedUserWindowParams' => $page_style_config['tracked']['user_window'], 'trackedVisitorWindowParams' => $page_style_config['tracked']['visitor_window'], 'banWindowParams' => $page_style_config['ban']['window'], 'inviteWindowParams' => $chat_style_config['chat']['window'])))); }
/** * Builds list of the styles tabs. * * @param Request $request Current request. * @return array Tabs list. The keys of the array are tabs titles and the * values are tabs URLs. */ protected function buildTabs(Request $request) { $tabs = array(); $type = $request->attributes->get('type'); $tabs[getlocal("Operator pages themes preview")] = $type != self::TYPE_PAGE ? $this->generateUrl('style_preview', array('type' => self::TYPE_PAGE)) : ''; $tabs[getlocal("Chat themes preview")] = $type != self::TYPE_CHAT ? $this->generateUrl('style_preview', array('type' => self::TYPE_CHAT)) : ''; if (Settings::get('enabletracking')) { $tabs[getlocal("Invitation themes preview")] = $type != self::TYPE_INVITATION ? $this->generateUrl('style_preview', array('type' => self::TYPE_INVITATION)) : ''; } return $tabs; }
/** * Processes submitting of the form which is generated in * {@link \Mibew\Controller\Settings\FeaturesController::showFormAction()} * method. * * @param Request $request Incoming request. * @return string Rendered page content. */ public function submitFormAction(Request $request) { csrf_check_token($request); // Update options in the database. $options = $this->getOptionsList(); foreach ($options as $opt) { $value = $request->request->get($opt) == 'on' ? '1' : '0'; Settings::set($opt, $value); } // Redirect the current operator to the same page using GET method. $redirect_to = $this->generateUrl('settings_features', array('stored' => true)); return $this->redirect($redirect_to); }
/** * Processes submitting of the form which is generated in * {@link \Mibew\Controller\TranslationImportController::showFormAction()} * method. * * @param Request $request Incoming request. * @return string Rendered page content. */ public function submitFormAction(Request $request) { csrf_check_token($request); $errors = array(); $target = $request->request->get('target'); if (!preg_match("/^[\\w-]{2,5}\$/", $target)) { $target = get_current_locale(); } $override = (bool) $request->request->get('override', false); // Validate uploaded file $file = $request->files->get('translation_file'); if ($file) { // Process uploaded file. $orig_filename = $file->getClientOriginalName(); $file_size = $file->getSize(); if ($file_size == 0 || $file_size > Settings::get('max_uploaded_file_size')) { $errors[] = failed_uploading_file($orig_filename, "Uploaded file size exceeded"); } elseif ($file->getClientOriginalExtension() != 'po') { $errors[] = failed_uploading_file($orig_filename, "Invalid file type"); } } else { $errors[] = getlocal("No file selected"); } // Try to process uploaded file if (count($errors) == 0) { try { // Try to import new messages. import_messages($target, $file->getRealPath(), $override); // Remove cached client side translations. $this->getCache()->getItem('translation/js/' . $target)->clear(); // The file is not needed any more. Remove it. unlink($file->getRealPath()); } catch (\Exception $e) { $errors[] = $e->getMessage(); } } if (count($errors) != 0) { $request->attributes->set('errors', $errors); // The form should be rebuild. Invoke appropriate action. return $this->showFormAction($request); } // Redirect the operator to the same page using GET method. $redirect_to = $this->generateUrl('translation_import', array('stored' => true)); return $this->redirect($redirect_to); }
/** * Performs all periodical actions. * * @return boolean True if all periodical actions are done and false * otherwise. */ public function run() { try { set_time_limit(0); // Remove stale cached items $this->cache->purge(); // Run cron jobs of the core calculate_thread_statistics(); calculate_operator_statistics(); calculate_page_statistics(); // Trigger cron event $dispatcher = EventDispatcher::getInstance(); $dispatcher->triggerEvent(Events::CRON_RUN); // Update time of last cron run Settings::set('_last_cron_run', time()); } catch (\Exception $e) { $this->log[] = $e->getMessage(); return false; } return true; }
/** * Generates HTML markup for Mibew widget. * * @return \Canteen\HTML5\Fragment */ protected function getWidgetCode() { $widget_data = array(); // Get actual invitation style instance $style_name = $this->getOption('invitation_style') ? $this->getOption('invitation_style') : InvitationStyle::getCurrentStyle(); $style = new InvitationStyle($style_name); // URL of file with additional CSS rules for invitation popup $widget_data['inviteStyle'] = $this->generateAssetUrl($style->getFilesPath() . '/invite.css'); // Time between requests to the server in milliseconds $widget_data['requestTimeout'] = Settings::get('updatefrequency_tracking') * 1000; // URL for requests $widget_data['requestURL'] = $this->generateUrl('widget_gateway'); // Locale for invitation $widget_data['locale'] = $this->getOption('locale'); // Name of the cookie to track user. It is used if third-party cookie // blocked $widget_data['visitorCookieName'] = VISITOR_COOKIE_NAME; $markup = HTML5\html('fragment'); $markup->addChild(HTML5\html('div#mibew-invitation')); $markup->addChild(HTML5\html('script')->setAttributes(array('type' => 'text/javascript', 'src' => $this->generateAssetUrl('js/compiled/widget.js')))); $markup->addChild(HTML5\html('script')->setAttribute('type', 'text/javascript')->addChild('Mibew.Widget.init(' . json_encode($widget_data) . ')')); return $markup; }
/** * Check if group is away * * @param array $group Associative group array. Should contain 'ilastseenaway' * key. * @return bool */ function group_is_away($group) { return $group['ilastseenaway'] !== null && $group['ilastseenaway'] < Settings::get('online_timeout'); }
/** * Processes submitting of the form which is generated in * {@link \Mibew\Controller\Settings\CommonController::showFormAction()} * method. * * @param Request $request Incoming request. * @return string Rendered page content. * @throws BadRequestException If one or more parameters of the request have * wrong format. */ public function submitFormAction(Request $request) { csrf_check_token($request); $errors = array(); $params = array(); $params['email'] = $request->request->get('email'); $params['title'] = $request->request->get('title'); $params['logo'] = $request->request->get('logo'); $params['hosturl'] = $request->request->get('hosturl'); $params['usernamepattern'] = $request->request->get('usernamepattern'); $params['chattitle'] = $request->request->get('chattitle'); $params['geolink'] = $request->request->get('geolink'); $params['geolinkparams'] = $request->request->get('geolinkparams'); $params['cron_key'] = $request->request->get('cronkey'); $send_key = $request->request->get('sendmessagekey'); if (!preg_match("/^c?enter\$/", $send_key)) { throw new BadRequestException('Wrong format of "sendmessagekey" field.'); } $params['sendmessagekey'] = $send_key; $params['left_messages_locale'] = $request->request->get('leftmessageslocale'); if (!in_array($params['left_messages_locale'], get_available_locales())) { $params['left_messages_locale'] = get_home_locale(); } if ($params['email'] && !MailUtils::isValidAddress($params['email'])) { $errors[] = getlocal('Enter a valid email address'); } if ($params['geolinkparams']) { foreach (explode(',', $params['geolinkparams']) as $one_param) { $wrong_param = !preg_match("/^\\s*(toolbar|scrollbars|location|status|menubar|width|height|resizable)=\\d{1,4}\$/", $one_param); if ($wrong_param) { $errors[] = "Wrong link parameter: \"{$one_param}\", " . "should be one of 'toolbar, scrollbars, location, " . "status, menubar, width, height or resizable'"; } } } if (preg_match("/^[0-9A-Za-z]*\$/", $params['cron_key']) == 0) { $errors[] = getlocal('Use only Latin letters(upper and lower case) and numbers in cron key.'); } // Load styles configs $chat_style = $request->request->get('chat_style', ChatStyle::getDefaultStyle()); $chat_style_list = ChatStyle::getAvailableStyles(); if (!in_array($chat_style, $chat_style_list)) { $chat_style = $chat_style_list[0]; } $page_style = $request->request->get('page_style', PageStyle::getDefaultStyle()); $page_style_list = PageStyle::getAvailableStyles(); if (!in_array($page_style, $page_style_list)) { $page_style = $page_style_list[0]; } if (Settings::get('enabletracking')) { $invitation_style = $request->request->get('invitation_style', InvitationStyle::getDefaultStyle()); $invitation_style_list = InvitationStyle::getAvailableStyles(); if (!in_array($invitation_style, $invitation_style_list)) { $invitation_style = $invitation_style_list[0]; } } if (count($errors) != 0) { $request->attributes->set('errors', $errors); // The form should be rebuild. Invoke appropriate action. return $this->showFormAction($request); } // Update system settings foreach ($params as $key => $value) { Settings::set($key, $value); } // Update styles params ChatStyle::setDefaultStyle($chat_style); PageStyle::setDefaultStyle($page_style); if (Settings::get('enabletracking')) { InvitationStyle::setDefaultStyle($invitation_style); } // Redirect the user to the same page using GET method $redirect_to = $this->generateUrl('settings_common', array('stored' => true)); return $this->redirect($redirect_to); }
/** * Releases the lock */ public function release() { Settings::set($this->getInternalName(), '0'); }
/** * Prepare values to render page menu. * * @param array $operator An array with operators data. * @param boolean $has_right Restricts access to menu items. If it equals to * FALSE only "Home", "Visitors", and "Chat history" items will be displayed. * Otherwise items set depends on operator's permissions and system settings. * Default value is TRUE. * @return array */ function prepare_menu($operator, $has_right = true) { $result = array(); $result['showMenu'] = true; $result['operator'] = get_operator_name($operator); if ($has_right) { $result['showban'] = Settings::get('enableban') == "1"; $result['showstat'] = Settings::get('enablestatistics') == "1"; $result['showadmin'] = is_capable(CAN_ADMINISTRATE, $operator); $result['currentopid'] = $operator['operatorid']; } return $result; }
/** * Generates a page with Mibew button code form. * * @param Request $request Incoming request * @return Response Rendered content of the page. */ public function generateAction(Request $request) { $operator = $this->getOperator(); $page = array('errors' => array()); $image_locales_map = $this->getImageLocalesMap(MIBEW_FS_ROOT . '/locales'); $image = $request->query->get('i', 'mibew'); if (!isset($image_locales_map[$image])) { $page['errors'][] = 'Unknown image: ' . $image; $avail = array_keys($image_locales_map); $image = $avail[0]; } $image_locales = $image_locales_map[$image]; $style_list = ChatStyle::getAvailableStyles(); $style_list[''] = getlocal('-from general settings-'); $style = $request->query->get('style', ''); if ($style && !in_array($style, $style_list)) { $style = ''; } $invitation_style_list = InvitationStyle::getAvailableStyles(); $invitation_style_list[''] = getlocal('-from general settings-'); $invitation_style = $request->query->get('invitationstyle', ''); if ($invitation_style && !in_array($invitation_style, $invitation_style_list)) { $invitation_style = ''; } $locales_list = get_available_locales(); $group_id = $request->query->getInt('group'); if ($group_id && !group_by_id($group_id)) { $page['errors'][] = getlocal("No such group"); $group_id = false; } $show_host = $request->query->get('hostname') == 'on'; $force_secure = $request->query->get('secure') == 'on'; $mod_security = $request->query->get('modsecurity') == 'on'; $force_windows = $request->query->get('forcewindows') == 'on'; $code_type = $request->query->get('codetype', 'button'); if (!in_array($code_type, array('button', 'operator_code', 'text_link'))) { throw new BadRequestException('Wrong value of "codetype" param.'); } $lang = $request->query->get('lang', ''); if (!preg_match("/^[\\w-]{2,5}\$/", $lang)) { $lang = ''; } $operator_code = $code_type == 'operator_code'; $generate_button = $code_type == 'button'; $button_generator_options = array('chat_style' => $style, 'group_id' => $group_id, 'show_host' => $show_host, 'force_secure' => $force_secure, 'mod_security' => $mod_security, 'prefer_iframe' => !$force_windows); if ($operator_code) { $button_generator = new OperatorCodeFieldGenerator($this->getRouter(), $this->getAssetManager()->getUrlGenerator(), $button_generator_options); } elseif ($generate_button) { // Make sure locale exists if (!$lang || !in_array($lang, $image_locales)) { $lang = in_array(get_current_locale(), $image_locales) ? get_current_locale() : $image_locales[0]; } $button_generator = new ImageButtonGenerator($this->getRouter(), $this->getAssetManager()->getUrlGenerator(), $button_generator_options); // Set generator-specific options $button_generator->setOption('image', $image); } else { // Make sure locale exists if (!$lang || !in_array($lang, $locales_list)) { $lang = in_array(get_current_locale(), $locales_list) ? get_current_locale() : $locales_list[0]; } $button_generator = new TextButtonGenerator($this->getRouter(), $this->getAssetManager()->getUrlGenerator(), $button_generator_options); // Set generator-specific options $button_generator->setOption('caption', getlocal('Click to chat')); } // Set verified locale code to a button generator $button_generator->setOption('locale', $lang); $page['buttonCode'] = $button_generator->generate(); $page['availableImages'] = array_keys($image_locales_map); $page['availableLocales'] = $generate_button ? $image_locales : $locales_list; $page['availableChatStyles'] = $style_list; $page['availableInvitationStyles'] = $invitation_style_list; $page['groups'] = $this->getGroupsList(); $page['availableCodeTypes'] = array('button' => getlocal('button'), 'operator_code' => getlocal('operator code field'), 'text_link' => getlocal('text link')); $page['formgroup'] = $group_id; $page['formstyle'] = $style; $page['forminvitationstyle'] = $invitation_style; $page['formimage'] = $image; $page['formlang'] = $lang; $page['formhostname'] = $show_host; $page['formsecure'] = $force_secure; $page['formmodsecurity'] = $mod_security; $page['formcodetype'] = $code_type; $page['formforcewindows'] = $force_windows; $page['enabletracking'] = Settings::get('enabletracking'); $page['operator_code'] = $operator_code; $page['generateButton'] = $generate_button; $page['title'] = getlocal("Button HTML code generation"); $page['menuid'] = "getcode"; $page = array_merge($page, prepare_menu($operator)); return $this->render('button_code', $page); }
/** * Return updated operators list. API function * * @param array $args Associative array of arguments. It must contains the * following keys: * - 'agentId': Id of the agent related to users window * * @return array Array of results. It contains the following keys: * - 'operators': array of online operators */ protected function apiUpdateOperators($args) { // Check access and get operators info $operator = $this->checkOperator($args['agentId']); // Return empty array if show operators option disabled if (Settings::get('showonlineoperators') != '1') { return array('operators' => array()); } // Check if curent operator is in isolation $list_options = in_isolation($operator) ? array('isolated_operator_id' => $operator['operatorid']) : array(); // Get operators list $operators = get_operators_list($list_options); // Create resulting list of operators $result_list = array(); foreach ($operators as $item) { if (!operator_is_online($item)) { continue; } $result_list[] = array('id' => (int) $item['operatorid'], 'name' => htmlspecialchars($item['vclocalename']), 'away' => (bool) operator_is_away($item)); } // Send operators list to the client side return array('operators' => $result_list); }
/** * 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); }
/** * Retrives an instance of Update Checker attached to the worker. * * If there was no attached checker it creates a new one. * * @return UpdateChecker */ protected function getUpdateChecker() { if (is_null($this->updateChecker)) { $this->updateChecker = new UpdateChecker(); $id = Settings::get('_instance_id'); if ($id) { $this->updateChecker->setInstanceId($id); } } return $this->updateChecker; }
/** * Processes submitting of the form which is generated in * {@link \Mibew\Controller\Settings\PerformanceController::showFormAction()} * method. * * @param Request $request Incoming request. * @return string Rendered page content. */ public function submitFormAction(Request $request) { csrf_check_token($request); $errors = array(); $params = array(); $params['online_timeout'] = $request->request->get('onlinetimeout'); if (!is_numeric($params['online_timeout'])) { $errors[] = wrong_field("Operator online time threshold"); } $params['connection_timeout'] = $request->request->get('connectiontimeout'); if (!is_numeric($params['connection_timeout'])) { $errors[] = wrong_field("Connection timeout for messaging window"); } $params['updatefrequency_operator'] = $request->request->get('frequencyoperator'); if (!is_numeric($params['updatefrequency_operator'])) { $errors[] = wrong_field("Operator's console refresh time"); } $params['updatefrequency_chat'] = $request->request->get('frequencychat'); if (!is_numeric($params['updatefrequency_chat'])) { $errors[] = wrong_field("Chat refresh time"); } $params['max_connections_from_one_host'] = $request->request->get('onehostconnections'); if (!is_numeric($params['max_connections_from_one_host'])) { $errors[] = getlocal("\"Max number of threads\" field should be a number"); } $params['thread_lifetime'] = $request->request->get('threadlifetime'); if (!is_numeric($params['thread_lifetime'])) { $errors[] = getlocal("\"Thread lifetime\" field should be a number"); } if (Settings::get('enabletracking')) { $params['updatefrequency_tracking'] = $request->request->get('frequencytracking'); if (!is_numeric($params['updatefrequency_tracking'])) { $errors[] = wrong_field("Tracking refresh time"); } $params['visitors_limit'] = $request->request->get('visitorslimit'); if (!is_numeric($params['visitors_limit'])) { $errors[] = wrong_field("Limit for tracked visitors list"); } $params['invitation_lifetime'] = $request->request->get('invitationlifetime'); if (!is_numeric($params['invitation_lifetime'])) { $errors[] = wrong_field("Invitation lifetime"); } $params['tracking_lifetime'] = $request->request->get('trackinglifetime'); if (!is_numeric($params['tracking_lifetime'])) { $errors[] = wrong_field("Track lifetime"); } } $params['max_uploaded_file_size'] = $request->request->get('maxuploadedfilesize'); if (!is_numeric($params['max_uploaded_file_size'])) { $errors[] = wrong_field("Maximum size of uploaded files"); } if (count($errors) != 0) { $request->attributes->set('errors', $errors); // The form should be rebuild. Invoke appropriate action. return $this->showFormAction($request); } // Update settings in the database foreach ($params as $key => $value) { Settings::set($key, $value); } // Redirect the current operator to the same page using get method. $redirect_to = $this->generateUrl('settings_performance', array('stored' => true)); return $this->redirect($redirect_to); }
/** * Prepare data to dispaly invitation * * @param Thread $thread Thread object related with invitation * @return array Array of invitation data */ function setup_invitation_view(Thread $thread) { $data = prepare_chat_app_data(); // Set refresh frequency $data['frequency'] = Settings::get('updatefrequency_chat'); // Create some empty arrays $data['invitation'] = array(); $data['invitation']['thread'] = array('id' => $thread->id, 'token' => $thread->lastToken, 'agentId' => $thread->agentId, 'userId' => $thread->userId); $data['invitation']['user'] = array('name' => htmlspecialchars($thread->userName), 'canChangeName' => false, 'isAgent' => false); $data['startFrom'] = 'invitation'; return $data; }
/** * 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; }
/** * Processes submitting of the form which is generated in * {@link \Mibew\Controller\Operator\AvatarController::showFormAction()} * method. * * @param Request $request Incoming request. * @return string Rendered page content. * @throws NotFoundException If the operator with specified ID is not found * in the system. */ public function submitFormAction(Request $request) { csrf_check_token($request); $operator = $this->getOperator(); $op_id = $request->attributes->getInt('operator_id'); $errors = array(); // Try to load the target operator. $op = operator_by_id($op_id); if (!$op) { throw new NotFoundException('The operator is not found'); } $avatar = $op['vcavatar']; $file = $request->files->get('avatarFile'); if ($file) { // Process uploaded file. $valid_types = array("gif", "jpg", "png", "tif", "jpeg"); $ext = $file->getClientOriginalExtension(); $orig_filename = $file->getClientOriginalName(); $new_file_name = intval($op_id) . ".{$ext}"; $file_size = $file->getSize(); if ($file_size == 0 || $file_size > Settings::get('max_uploaded_file_size')) { $errors[] = failed_uploading_file($orig_filename, "Uploaded file size exceeded"); } elseif (!in_array($ext, $valid_types)) { $errors[] = failed_uploading_file($orig_filename, "Invalid file type"); } else { // Remove avatar if it already exists $avatar_local_dir = MIBEW_FS_ROOT . '/files/avatar/'; $full_file_path = $avatar_local_dir . $new_file_name; if (file_exists($full_file_path)) { unlink($full_file_path); } // Move uploaded file to avatar directory try { $file->move($avatar_local_dir, $new_file_name); $avatar = 'files/avatar/' . $new_file_name; } catch (Exception $e) { $errors[] = failed_uploading_file($orig_filename, "Error moving file"); } } } else { $errors[] = getlocal("No file selected"); } if (count($errors) != 0) { $request->attributes->set('errors', $errors); // The form should be rebuild. Invoke appropriate action. return $this->showFormAction($request); } // Update path to avatar in the database update_operator_avatar($op['operatorid'], $avatar); // Operator's data are cached in the authentication manager thus we need // to update them manually. if ($avatar && $operator['operatorid'] == $op_id) { $operator['vcavatar'] = $avatar; $this->getAuthenticationManager()->setOperator($operator); } // Redirect the operator to the same page using GET method. $redirect_to = $this->generateUrl('operator_avatar', array('operator_id' => $op_id)); return $this->redirect($redirect_to); }
/** * Generates a page with user tracking information. * * @param Request $request * @return string Rendered page content */ public function userTrackAction(Request $request) { if (Settings::get('enabletracking') == '0') { throw new BadRequestException('Tracking is disabled.'); } if ($request->query->has('thread')) { $thread_id = $request->query->get('thread'); if (!preg_match("/^\\d{1,8}\$/", $thread_id)) { throw new BadRequestException('Wrong thread ID.'); } $visitor = track_get_visitor_by_thread_id($thread_id); if (!$visitor) { throw new BadRequestException('Wrong thread.'); } } else { $visitor_id = $request->query->get('visitor'); if (!preg_match("/^\\d{1,8}\$/", $visitor_id)) { throw new BadRequestException('Wrong visitor ID.'); } $visitor = track_get_visitor_by_id($visitor_id); if (!$visitor) { throw new BadRequestException('Wrong visitor.'); } } $path = track_get_path($visitor); $page['entry'] = htmlspecialchars($visitor['entry']); $page['history'] = array(); ksort($path); foreach ($path as $k => $v) { $page['history'][] = array('date' => date_to_text($k), 'link' => htmlspecialchars($v)); } $page['title'] = getlocal('Tracked path of visitor'); $page['show_small_login'] = false; return $this->render('tracked', $page); }
/** * Process submitted leave message form. * * Send message to operator email and create special meil thread. * @param array $args Associative array of arguments. It must contains the * following keys: * - 'threadId': for this function this param equals to null; * - 'token': for this function this param equals to null; * - 'name': string, user name; * - 'email': string, user email; * - 'message': string, user message; * - 'info': string, some info about user; * - 'referrer': string, page user came from; * - 'captcha': string, captcha value; * - 'groupId': selected group id. * * @throws \Mibew\RequestProcessor\ThreadProcessorException Can throw an * exception if captcha or email is wrong. */ protected function apiProcessLeaveMessage($args) { // Check captcha if (Settings::get('enablecaptcha') == '1' && can_show_captcha()) { $captcha = $args['captcha']; $original = isset($_SESSION[SESSION_PREFIX . 'mibew_captcha']) ? $_SESSION[SESSION_PREFIX . 'mibew_captcha'] : ''; unset($_SESSION[SESSION_PREFIX . 'mibew_captcha']); if (empty($original) || empty($captcha) || $captcha != $original) { throw new ThreadProcessorException( getlocal('The letters you typed don\'t match the letters that were shown in the picture.'), ThreadProcessorException::ERROR_WRONG_CAPTCHA ); } } // Get form fields $email = $args['email']; $name = $args['name']; $message = $args['message']; $info = $args['info']; $referrer = $args['referrer']; if (!MailUtils::isValidAddress($email)) { throw new ThreadProcessorException( wrong_field("Your email"), ThreadProcessorException::ERROR_WRONG_EMAIL ); } // Verify group id $group_id = ''; if (Settings::get('enablegroups') == '1') { if (preg_match("/^\d{1,8}$/", $args['groupId']) != 0) { $group = group_by_id($args['groupId']); if ($group) { $group_id = $args['groupId']; } } } // Create thread for left message $remote_host = get_remote_host(); $user_browser = $_SERVER['HTTP_USER_AGENT']; $visitor = visitor_from_request(); // Get message locale $message_locale = Settings::get('left_messages_locale'); if (!locale_is_available($message_locale)) { $message_locale = get_home_locale(); } // Create thread $thread = new Thread(); $thread->groupId = $group_id; $thread->userName = $name; $thread->remote = $remote_host; $thread->referer = $referrer; $thread->locale = get_current_locale(); $thread->userId = $visitor['id']; $thread->userAgent = $user_browser; $thread->state = Thread::STATE_LEFT; $thread->closed = time(); $thread->save(); // Send some messages if ($referrer) { $thread->postMessage( Thread::KIND_FOR_AGENT, getlocal('Vistor came from page {0}', array($referrer), get_current_locale(), true) ); } if ($email) { $thread->postMessage( Thread::KIND_FOR_AGENT, getlocal('E-Mail: {0}', array($email), get_current_locale(), true) ); } if ($info) { $thread->postMessage( Thread::KIND_FOR_AGENT, getlocal('Info: {0}', array($info), get_current_locale(), true) ); } $thread->postMessage(Thread::KIND_USER, $message, array('name' => $name)); // Get email for message $inbox_mail = get_group_email($group_id); if (empty($inbox_mail)) { $inbox_mail = Settings::get('email'); } // Send email if ($inbox_mail) { // Prepare message to send by email $mail_template = MailTemplate::loadByName('leave_message', $message_locale); if (!$mail_template) { trigger_error( 'Cannot send e-mail because "leave_message" mail template cannot be loaded.', E_USER_WARNING ); return; } $subject = $mail_template->buildSubject(array($args['name'])); $body = $mail_template->buildBody(array( $args['name'], $email, $message, ($info ? $info . "\n" : ""), )); // Send $this->getMailerFactory()->getMailer()->send( MailUtils::buildMessage($inbox_mail, $email, $subject, $body) ); } }
/** * 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); }
/** * Generates JavaScript code that starts client side application. * * @param Request $request Incoming request. * @param array $options Client side application options. At the moment the * method accepts the following options: * - "company": array, a set of company info. See {@link setup_logo()} * for details. * - "mibewHost": string, a URL which is used as a Mibew host. See * {@link setup_logo()} for details. * - "page.title": string, a value which will be used as a page title. * - "startFrom": string, indicates what module should be invoked first. * - "chatOptions": array, (optional) list of chat module options. * - "surveyOptions": array, (optional) list of pre-chat survey module * options. * - "leaveMessageOptions": array, (optional) list of leave message module * options. * - "invitationOptions": array, (optional) list of invitation module * options. * @return string JavaScript code that starts "users" client side * application. * @todo The way options passed here should be reviewed. The method must get * finite number of well-structured arguments. */ protected function startJsApplication(Request $request, $options) { $app_settings = array('server' => array('url' => $this->generateUrl('chat_thread_update'), 'requestsFrequency' => Settings::get('updatefrequency_chat')), 'page' => array('style' => $this->getStyle()->getName(), 'mibewBasePath' => $request->getBasePath(), 'mibewBaseUrl' => $request->getBaseUrl(), 'stylePath' => $request->getBasePath() . '/' . $this->getStyle()->getFilesPath(), 'company' => isset($options['company']) ? $options['company'] : '', 'mibewHost' => isset($options['mibewHost']) ? $options['mibewHost'] : '', 'title' => isset($options['page.title']) ? $options['page.title'] : ''), 'startFrom' => $options['startFrom']); // Add module specific options $module_options_list = array('chatOptions', 'surveyOptions', 'leaveMessageOptions', 'invitationOptions'); foreach ($module_options_list as $key) { if (isset($options[$key])) { $app_settings[$key] = $options[$key]; } } return sprintf('jQuery(document).ready(function() {Mibew.Application.start(%s);});', json_encode($app_settings)); }
/** * Builds list of the statistics tabs. * * @param Request $request Current request. * @return array Tabs list. The keys of the array are tabs titles and the * values are tabs URLs. */ protected function buildTabs(Request $request) { $tabs = array(); $args = $request->query->all(); $type = $request->attributes->get('type'); $tabs[getlocal('Usage statistics for each date')] = $type != self::TYPE_BY_DATE ? $this->generateUrl('statistics', $args + array('type' => self::TYPE_BY_DATE)) : ''; $tabs[getlocal('Threads by operator')] = $type != self::TYPE_BY_OPERATOR ? $this->generateUrl('statistics', $args + array('type' => self::TYPE_BY_OPERATOR)) : ''; if (Settings::get('enabletracking')) { $tabs[getlocal('Chat threads by page')] = $type != self::TYPE_BY_PAGE ? $this->generateUrl('statistics', $args + array('type' => self::TYPE_BY_PAGE)) : ''; } return $tabs; }
/** * Ping the thread. * * Updates ping time for conversation members and sends messages about * connection problems. * * @param boolean $is_user Indicates user or operator pings thread. Boolean * true for user and boolean false otherwise. * @param boolean $is_typing Indicates if user or operator is typing a * message. */ public function ping($is_user, $is_typing) { // Indicates if revision ID of the thread should be updated on save. // Update revision leads to rerender thread in threads list at client // side. Do it on every ping is too costly. $update_revision = false; // Last ping time of other side $last_ping_other_side = 0; // Update last ping time if ($is_user) { $last_ping_other_side = $this->lastPingAgent; $this->lastPingUser = time(); $this->userTyping = $is_typing ? "1" : "0"; } else { $last_ping_other_side = $this->lastPingUser; $this->lastPingAgent = time(); $this->agentTyping = $is_typing ? "1" : "0"; } // Update thread state for the first user ping if ($this->state == self::STATE_LOADING && $is_user) { $this->state = self::STATE_QUEUE; $this->save(); return; } // Check if other side of the conversation have connection problems if ($last_ping_other_side > 0 && abs(time() - $last_ping_other_side) > Settings::get('connection_timeout')) { // Connection problems detected if ($is_user) { // _Other_ side is operator // Update operator's last ping time $this->lastPingAgent = 0; // Check if user chatting at the moment if ($this->state == self::STATE_CHATTING) { // Send message to user $message_to_post = getlocal('Your operator has connection issues. We have moved you to a priorty position in the queue. Sorry for keeping you waiting.', null, $this->locale, true); $this->postMessage(self::KIND_CONN, $message_to_post, array('created' => $last_ping_other_side + Settings::get('connection_timeout'))); // And update thread $this->state = self::STATE_WAITING; $this->nextAgent = 0; // Significant fields of the thread (state and nextAgent) // are changed. Update revision ID on save. $update_revision = true; } } else { // _Other_ side is user // Update user's last ping time $this->lastPingUser = 0; // And send a message to operator. if ($this->state == self::STATE_CHATTING) { $message_to_post = getlocal('Visitor closed chat window', null, $this->locale, true); $this->postMessage(self::KIND_FOR_AGENT, $message_to_post, array('created' => $last_ping_other_side + Settings::get('connection_timeout'))); } } } $this->save($update_revision); }
/** * Sets style which is used in the system by default * * @param string $style_name Name of a style */ public static function setDefaultStyle($style_name) { Settings::set('invitation_style', $style_name); }
/** * Start chat thread for user * * @param int $group_id Id of group related to thread * @param array $requested_operator Array of requested operator info * @param string $visitor_id Id of the visitor * @param string $visitor_name Name of the visitor * @param string $referrer Page user came from * @param string $info User info * * @return Thread thread object */ function chat_start_for_user($group_id, $requested_operator, $visitor_id, $visitor_name, $referrer, $info) { // Get user info $remote_host = get_remote_host(); $user_browser = $_SERVER['HTTP_USER_AGENT']; // Check connection limit if (Thread::connectionLimitReached($remote_host)) { die("number of connections from your IP is exceeded, try again later"); } // Check if visitor was invited to chat $is_invited = false; if (Settings::get('enabletracking')) { $invitation_state = invitation_state($_SESSION[SESSION_PREFIX . 'visitorid']); if ($invitation_state['invited']) { $is_invited = true; } } // Get info about requested operator $requested_operator_online = false; if ($requested_operator) { $requested_operator_online = is_operator_online($requested_operator['operatorid']); } // Get thread object if ($is_invited) { // Get thread from invitation $thread = invitation_accept($_SESSION[SESSION_PREFIX . 'visitorid']); if (!$thread) { die("Cannot start thread"); } } else { // Create thread $thread = new Thread(); $thread->state = Thread::STATE_LOADING; $thread->agentId = 0; if ($requested_operator && $requested_operator_online) { $thread->nextAgent = $requested_operator['operatorid']; } } // Update thread fields $thread->groupId = $group_id; $thread->userName = $visitor_name; $thread->remote = $remote_host; $thread->referer = $referrer; $thread->locale = get_current_locale(); $thread->userId = $visitor_id; $thread->userAgent = $user_browser; $thread->save(); $_SESSION[SESSION_PREFIX . 'threadid'] = $thread->id; // 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; // Bind thread to the visitor if (Settings::get('enabletracking')) { track_visitor_bind_thread($visitor_id, $thread); } // Send several messages if ($is_invited) { $operator = operator_by_id($thread->agentId); $operator_name = get_operator_name($operator); $thread->postMessage(Thread::KIND_FOR_AGENT, getlocal('Visitor accepted invitation from operator {0}', array($operator_name), get_current_locale(), true)); } else { if ($referrer) { $thread->postMessage(Thread::KIND_FOR_AGENT, getlocal('Vistor came from page {0}', array($referrer), get_current_locale(), true)); } if ($requested_operator && !$requested_operator_online) { $thread->postMessage(Thread::KIND_INFO, getlocal('Thank you for contacting us. We are sorry, but requested operator <strong>{0}</strong> is offline. Another operator will be with you shortly.', array(get_operator_name($requested_operator)), get_current_locale(), true)); } else { $thread->postMessage(Thread::KIND_INFO, getlocal('Thank you for contacting us. An operator will be with you shortly.', null, get_current_locale(), true)); } } // TODO: May be move sending this message somewhere else? if ($info) { $thread->postMessage(Thread::KIND_FOR_AGENT, getlocal('Info: {0}', array($info), get_current_locale(), true)); } // Let plugins know that user is ready to chat. $dispatcher = EventDispatcher::getInstance(); $event_args = array('thread' => $thread); $dispatcher->triggerEvent(Events::THREAD_USER_IS_READY, $event_args); return $thread; }