mchat_settings = $mchat_settings; $this->user = $user; $this->lang = $lang; $this->auth = $auth; $this->db = $db; $this->dispatcher = $dispatcher; $this->textformatter_parser = $textformatter_parser; } /** * Checks whether or not the given message row is a notification * * @param array $row The message row * @return int the notification type, or 0 if the $row is not a notification */ public function is_notification($row) { // If post_id is 0 it's not a notification if (isset($row['post_id']) && $row['post_id']) { // If forum_id is 0 it's a login notification if (isset($row['forum_id']) && !$row['forum_id']) { // post_id is either LOGIN_VISIBLE or LOGIN_HIDDEN return $row['post_id']; } return self::POST; } return 0; } /** * Checks the post rows for notifications and converts their language keys * * @param array $rows The rows to modify * @return array */ public function process($rows) { // All language keys of valid notifications. We need to check for them here because // notifications in < 2.0.0-RC6 are plain text and don't need to be processed. $notification_lang = [ 'MCHAT_NEW_POST', 'MCHAT_NEW_QUOTE', 'MCHAT_NEW_EDIT', 'MCHAT_NEW_REPLY', 'MCHAT_NEW_LOGIN', ]; /** * Event that allows to modify rows and language keys before checking for notifications * * @event dmzx.mchat.process_notifications_before * @var array rows Message rows about to be checked for notifications * @var array notification_lang Unprocessed language keys of valid/known notifications * @since 2.1.4-RC1 */ $vars = [ 'rows', 'notification_lang', ]; extract($this->dispatcher->trigger_event('dmzx.mchat.process_notifications_before', compact($vars))); $notification_langs = array_merge( // Raw notification messages in phpBB < 3.2 array_combine($notification_lang, $notification_lang), // XML notification messages in phpBB >= 3.2 array_combine(array_map([$this->textformatter_parser, 'parse'], $notification_lang), $notification_lang) ); $notifications = []; $post_ids = []; foreach ($rows as $i => $row) { $type = $this->is_notification($row); if ($type && isset($notification_langs[$row['message']])) { $notifications[$i] = $type; if ($type == self::POST) { $post_ids[$i] = $row['post_id']; } } } $notification_post_data = $this->mchat_get_post_data($post_ids); foreach ($notifications as $i => $type) { $lang_key = $notification_langs[$rows[$i]['message']]; $post_data = $type == self::POST ? $notification_post_data[$post_ids[$i]] : null; $rows[$i] = $this->process_notification($rows[$i], $type, $lang_key, $post_data); } /** * Event that allows to modify rows after processing their notifications * * @event dmzx.mchat.process_notifications_after * @var array rows Message rows about to be checked for notifications * @var array notification_lang Unprocessed language keys of valid/known notifications * @var array notification_langs Processed language keys of valid/known notifications * @var array notification_post_data Post data of notifications found in the rows array * @since 2.1.4-RC1 */ $vars = [ 'rows', 'notification_lang', 'notification_langs', 'notification_post_data', ]; extract($this->dispatcher->trigger_event('dmzx.mchat.process_notifications_after', compact($vars))); return $rows; } /** * Fetches post subjects and their forum names. If a post_id can't be found the value for the post_id is set to null. * * @param array $post_ids * @return array */ protected function mchat_get_post_data($post_ids) { if (!$post_ids) { return []; } $sql_array = [ 'SELECT' => 'p.post_id, p.post_subject, f.forum_id, f.forum_name', 'FROM' => [POSTS_TABLE => 'p', FORUMS_TABLE => 'f'], 'WHERE' => 'p.forum_id = f.forum_id AND ' . $this->db->sql_in_set('p.post_id', $post_ids), ]; $sql = $this->db->sql_build_query('SELECT', $sql_array); $result = $this->db->sql_query($sql); $rows = $this->db->sql_fetchrowset($result); $this->db->sql_freeresult($result); $existing_post_ids = array_column($rows, 'post_id'); $existing_posts = array_combine($existing_post_ids, $rows); // Map IDs of missing posts to null $missing_posts = array_fill_keys(array_diff($post_ids, $existing_post_ids), null); return $existing_posts + $missing_posts; } /** * Converts the message field of the post row so that it can be passed to generate_text_for_display() * * @param array $row * @param int $type * @param string $lang_key * @param array $post_data * @return array */ protected function process_notification($row, $type, $lang_key, $post_data = null) { $lang_args = []; $replacements = []; $post_subject_placeholder = '%POST_SUBJECT%'; $forum_name_placeholder = '%FORUM_NAME%'; if ($type == self::POST) { if ($post_data) { $viewtopic_url = append_sid($this->mchat_settings->url('viewtopic', true), [ 'p' => $row['post_id'], '#' => 'p' . $row['post_id'], ]); // We prefer $post_data because it was fetched from the forums table just now. // $row might contain outdated data if a post was moved to a new forum. $forum_id = isset($post_data['forum_id']) ? $post_data['forum_id'] : $row['forum_id']; $viewforum_url = append_sid($this->mchat_settings->url('viewforum', true), [ 'f' => $forum_id, ]); $lang_args[] = '[url=' . $viewtopic_url . ']' . $post_subject_placeholder . '[/url]'; $lang_args[] = '[url=' . $viewforum_url . ']' . $forum_name_placeholder . '[/url]'; $replacements = [ $post_subject_placeholder => $post_data['post_subject'], $forum_name_placeholder => $post_data['forum_name'], ]; } else { $lang_key .= '_DELETED'; } } else if ($type == self::LOGIN_HIDDEN) { $row['username'] = '' . $row['username'] . ''; } $row['message'] = $this->lang->lang_array($lang_key, $lang_args); // Quick'n'dirty check if BBCodes are in the message if (strpos($row['message'], '[') !== false) { generate_text_for_storage($row['message'], $row['bbcode_uid'], $row['bbcode_bitfield'], $row['bbcode_options'], true, true, true, true, true, true, true, 'mchat'); } $row['message'] = strtr($row['message'], $replacements); return $row; } /** * Inserts a message with posting information into the database * * @param string $mode One of post|quote|edit|reply * @param int $forum_id * @param int $post_id */ public function insert_post($mode, $forum_id, $post_id) { $this->insert($mode, $forum_id, $post_id); } /** * Inserts a message with login information into the database * * @param bool $is_hidden */ public function insert_login($is_hidden) { $this->insert('login', 0, $is_hidden ? self::LOGIN_HIDDEN : self::LOGIN_VISIBLE); } /** * Inserts a message with posting or login information into the database * * @param string $mode One of post|quote|edit|reply|login * @param int $forum_id * @param int $post_id Can be 0 if mode is login. */ protected function insert($mode, $forum_id, $post_id) { $mode_config = [ 'post' => 'mchat_posts_topic', 'quote' => 'mchat_posts_quote', 'edit' => 'mchat_posts_edit', 'reply' => 'mchat_posts_reply', 'login' => 'mchat_posts_login', ]; $is_mode_enabled = !empty($mode_config[$mode]) && $this->mchat_settings->cfg($mode_config[$mode]) && (!$this->mchat_settings->cfg('mchat_posts_auth_check') || $this->can_use_mchat()); $sql_array = [ 'forum_id' => (int) $forum_id, 'post_id' => (int) $post_id, 'user_id' => (int) $this->user->data['user_id'], 'user_ip' => $this->user->ip, 'message' => $this->textformatter_parser->parse('MCHAT_NEW_' . strtoupper($mode)), 'message_time' => time(), ]; /** * Event that allows to modify data of a posting or login notification before it is inserted in the database * * @event dmzx.mchat.insert_posting_before * @var string mode The posting mode, one of post|quote|edit|reply|login * @var int forum_id The ID of the forum where the post was made, or 0 if mode is login. * @var int post_id The ID of the post that was made. If mode is login this value is * one of the constants LOGIN_HIDDEN|LOGIN_VISIBLE * @var array is_mode_enabled Whether or not the posting should be added to the database. * @var array sql_array An array containing the data that is about to be inserted into the messages table. * @since 2.0.0-RC6 * @changed 2.1.0-RC1 Removed is_hidden_login */ $vars = [ 'mode', 'forum_id', 'post_id', 'is_mode_enabled', 'sql_array', ]; extract($this->dispatcher->trigger_event('dmzx.mchat.insert_posting_before', compact($vars))); if ($is_mode_enabled) { $sql = 'INSERT INTO ' . $this->mchat_settings->get_table_mchat() . ' ' . $this->db->sql_build_array('INSERT', $sql_array); $this->db->sql_query($sql); } } /** * The user might have just logged in successfully in which case the permissions haven't been updated yet. * Let's do that here so that notifications are recorded correctly. * * @return bool */ protected function can_use_mchat() { if ($this->auth->acl_get('u_mchat_use')) { return true; } $auth = new auth(); $auth->acl($this->user->data); return $auth->acl_get('u_mchat_use'); } /** * Generates an SQL WHERE condition to include or exlude notifacation * messages based on the current user's settings and permissions * * @param string $mode One of user|exclude. user mode uses the current user's settings to decide which notifications * to exclude. exclude mode always excludes all notifications. * @return string */ public function get_sql_where($mode = 'user') { // Exclude all post notifications if ($mode == 'exclude' || !$this->mchat_settings->cfg('mchat_posts')) { return 'm.post_id = 0'; } // If the current user doesn't have permission to see hidden users, exclude their login posts if (!$this->auth->acl_get('u_viewonline')) { return implode(' OR ', [ 'm.post_id <> ' . self::LOGIN_HIDDEN, // Exclude all notifications that were created by hidden users ... 'm.user_id = ' . (int) $this->user->data['user_id'], // ... but include all login notifications of the current user 'm.forum_id <> 0', // ... and include all post notifications ]); } return ''; } /** * Delete post notification messages, for example when disapproving posts * * @param array $post_ids */ public function delete_post_notifications($post_ids) { if ($post_ids) { $sql = 'DELETE FROM ' . $this->mchat_settings->get_table_mchat() . ' WHERE forum_id <> 0 AND ' . $this->db->sql_in_set('post_id', $post_ids); $this->db->sql_query($sql); } } /** * Change the user to which a post notification belongs * * @param int $post_id * @param int $user_id */ public function update_post_notification_user($post_id, $user_id) { $sql = 'UPDATE ' . $this->mchat_settings->get_table_mchat() . ' SET user_id = ' . (int) $user_id . ' WHERE forum_id <> 0 AND post_id = ' . (int) $post_id; $this->db->sql_query($sql); } }