/** * Webhook parser class method for SendGrid. */ public function webhook_parser() { if (empty($_SERVER['HTTP_USER_AGENT']) || !empty($_SERVER['HTTP_USER_AGENT']) && 0 !== strpos($_SERVER['HTTP_USER_AGENT'], 'SendGrid')) { return; } if (empty($_POST) || empty($_POST['headers'])) { return; } bp_rbe_log('- SendGrid webhook received -'); // Format email headers to fit RBE spec. $temp = explode("\n", $_POST['headers']); $headers = array(); foreach ($temp as $line) { $colun = strpos($line, ':'); if (false === $colun) { continue; } $key = substr($line, 0, $colun); $headers[$key] = stripslashes(trim(substr($line, $colun + 1))); } $data = array('headers' => $headers, 'to_email' => BP_Reply_By_Email_Parser::get_header($headers, 'To'), 'from_email' => BP_Reply_By_Email_Parser::get_header($headers, 'From'), 'content' => $_POST['text'], 'subject' => $_POST['subject']); $parser = BP_Reply_By_Email_Parser::init($data, 1); if (is_wp_error($parser)) { do_action('bp_rbe_no_match', $parser, $data, 1, false); } bp_rbe_log('- Webhook parsing completed -'); die; }
/** * Webhook parser class method for Mandrill. */ public function webhook_parser() { if (empty($_SERVER['CONTENT_TYPE']) || !empty($_SERVER['CONTENT_TYPE']) && 'application/json' !== $_SERVER['CONTENT_TYPE']) { return; } if (!empty($_SERVER['HTTP_USER_AGENT']) && 'Postmark' !== $_SERVER['HTTP_USER_AGENT']) { return; } bp_rbe_log('- Postmark webhook received -'); $response = file_get_contents('php://input'); if (empty($response)) { bp_rbe_log('- Postmark webhook response failed -'); } $response = json_decode($response); // Format email headers to fit RBE spec. $headers = array(); foreach ($response->Headers as $header) { $headers[$header->Name] = $header->Value; } // Postmark separates parsed email headers; add them back for RBE parsing. $headers['From'] = $response->From; $headers['To'] = $response->To; $data = array('headers' => $headers, 'to_email' => $response->To, 'from_email' => $response->From, 'content' => $response->TextBody, 'subject' => $response->Subject); $parser = BP_Reply_By_Email_Parser::init($data, 1); if (is_wp_error($parser)) { do_action('bp_rbe_no_match', $parser, $data, 1, false); } bp_rbe_log('- Webhook parsing completed -'); die; }
/** * Remove our scheduled function from WP and stop the IMAP loop. */ function bp_rbe_deactivate() { // stop IMAP connection if active if (bp_rbe_is_connected()) { bp_rbe_stop_imap(); // give plugin a chance to stop IMAP connection as it could be sleeping sleep(10); bp_rbe_log('Daisy, Daisy, give me your answer, do...'); } // remove remnants from any previous failed attempts to stop the inbox bp_rbe_cleanup(); bp_rbe_log('Plugin deactivated!'); }
/** * Webhook parser class method for SparkPost. */ public function webhook_parser() { if (empty($_SERVER['CONTENT_TYPE']) || !empty($_SERVER['CONTENT_TYPE']) && 'application/json' !== $_SERVER['CONTENT_TYPE']) { return; } if (false === isset($_SERVER['HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN'])) { return; } bp_rbe_log('- SparkPost webhook received -'); // SparkPost auth token verification. if (defined('BP_RBE_SPARKPOST_WEBHOOK_TOKEN') && !empty($_SERVER['HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN'])) { // Make sure auth token matches; if it doesn't, bail! if (constant('BP_RBE_SPARKPOST_WEBHOOK_TOKEN') !== $_SERVER['HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN']) { bp_rbe_log('SparkPost token verification failed.'); die; } } $response = file_get_contents('php://input'); if (empty($response)) { bp_rbe_log('- SporkPost webhook response failed -'); } $response = json_decode($response); $i = 1; foreach ($response as $item) { // Format email headers to fit RBE spec. $headers = array(); foreach ($item->msys->relay_message->content->headers as $header) { $headers = array_merge($headers, (array) $header); } $data = array('headers' => $headers, 'to_email' => $item->msys->relay_message->rcpt_to, 'from_email' => $item->msys->relay_message->friendly_from, 'content' => $item->msys->relay_message->content->text, 'subject' => $item->msys->relay_message->content->subject); $parser = BP_Reply_By_Email_Parser::init($data, $i); if (is_wp_error($parser)) { do_action('bp_rbe_no_match', $parser, $data, $i, false); } ++$i; } bp_rbe_log('- Webhook parsing completed -'); die; }
/** * Validate and sanitize our options before saving to the DB. * * Callback from {@link register_setting()}. * * @param array $input The submitted values from the form * @return array $output The validated values to be inserted into the DB */ public function validate($input) { $messages = false; $output = array(); $output['mode'] = wp_filter_nohtml_kses($input['mode']); // switching from IMAP to inbound email mode if ('inbound' == $output['mode'] && 'imap' == bp_rbe_get_setting('mode')) { // stop RBE if still connected via IMAP if (bp_rbe_is_connected()) { bp_rbe_stop_imap(); } bp_rbe_log('- Operating mode switched to inbound -'); } // check if key is alphanumeric if (ctype_alnum($input['key'])) { $output['key'] = $input['key']; } /** INBOUND-related ***************************************************/ $inbound_provider = wp_filter_nohtml_kses($input['inbound-provider']); if (!empty($inbound_provider)) { $output['inbound-provider'] = $inbound_provider; } $inbound_domain = isset($input['inbound-domain']) ? wp_filter_nohtml_kses($input['inbound-domain']) : ''; if (!empty($inbound_domain)) { $output['inbound-domain'] = $inbound_domain; } /** IMAP-related ******************************************************/ $username = wp_filter_nohtml_kses($input['username']); $password = wp_filter_nohtml_kses($input['password']); if ($email = is_email($input['email'])) { $output['email'] = $email; if ($input['gmail'] == 1) { $output['username'] = $email; } } if (!empty($username)) { $output['username'] = $username; if (is_email($username)) { $output['email'] = $username; } } if (!empty($password)) { $output['password'] = $password; } // check if port is numeric if (is_numeric($input['port'])) { $output['port'] = $input['port']; } // check if address tag is one character if (strlen($input['tag']) == 1) { $output['tag'] = $input['tag']; } // override certain settings if "gmail" setting is true if (!empty($input['gmail']) && $input['gmail'] == 1) { $output['servername'] = 'imap.gmail.com'; $output['port'] = 993; $output['tag'] = '+'; $output['gmail'] = 1; $output['email'] = $username; // use alternate server settings as defined by the user } else { $output['servername'] = wp_filter_nohtml_kses($input['servername']); $output['port'] = absint($input['port']); $output['tag'] = wp_filter_nohtml_kses($input['tag']); $output['gmail'] = 0; } if (is_numeric($input['keepalive']) && $input['keepalive'] < 30) { $output['keepalive'] = $input['keepalive']; } // keepalive for safe mode will never exceed the execution time if (ini_get('safe_mode')) { $output['keepalive'] = $input['keepalive'] = bp_rbe_get_execution_time('minutes'); } // automatically reconnect after keep-alive limit if (!empty($input['keepaliveauto'])) { $output['keepaliveauto'] = 1; } else { $output['keepaliveauto'] = 0; } // do a quick imap check if we have valid credentials to check if ($output['mode'] == 'imap' && !empty($output['servername']) && !empty($output['port']) && !empty($output['username']) && !empty($output['password'])) { if (function_exists('imap_open')) { $imap = BP_Reply_By_Email_Connect::init(array('host' => $output['servername'], 'port' => $output['port'], 'username' => $output['username'], 'password' => $output['password'])); // if connection failed, add an error if ($imap === false) { $errors = imap_errors(); $messages['connect_error'] = sprintf(__('Error: Unable to connect to inbox - %s', 'bp-rbe'), $errors[0]); $output['connect'] = 0; // connection was successful, now close our temporary connection } else { // this tells bp_rbe_is_required_completed() that we're good to go! $output['connect'] = 1; imap_close($imap); } } } // encode the password if (!empty($password)) { $output['password'] = bp_rbe_encode(array('string' => $password, 'key' => wp_salt())); } /**********************************************************************/ /* error time! */ if (strlen($input['tag']) > 1 && !$output['tag']) { $messages['tag_error'] = __('Error: <strong>Address tag</strong> must only be one character.', 'bp-rbe'); } if (!empty($input['port']) && !is_numeric($input['port']) && !$output['port']) { $messages['port_error'] = __('Error: <strong>Port</strong> must be numeric.', 'bp-rbe'); } if (!empty($input['key']) && !$output['key']) { $messages['key_error'] = __('Error: <strong>Key</strong> must only contain letters and / or numbers.', 'bp-rbe'); } if (!empty($input['keepalive']) && !is_numeric($input['keepalive']) && !$output['keepalive']) { $messages['keepalive_error'] = __('Error: <strong>Keep Alive Connection</strong> value must be less than 30.', 'bp-rbe'); } if (is_array($messages)) { $output['messages'] = $messages; } // For sites using an external object cache, make sure they get the new value. // This is all sorts of ugly! :( // I think this is a WP core bug with the WP Settings API... wp_cache_delete('alloptions', 'options'); return $output; }
/** * Post by email routine for legacy forums. * * Validates the parsed data and posts the various BuddyPress content. * * @since 1.0-RC3 * * @param bool $retval True by default. * @param array $data { * An array of arguments. * * @type array $headers Email headers. * @type string $content The email body content. * @type string $subject The email subject line. * @type int $user_id The user ID who sent the email. * @type bool $is_html Whether the email content is HTML or not. * @type int $i The email message number. * } * @param array $params Parsed paramaters from the email address querystring. * See {@link BP_Reply_By_Email_Parser::get_parameters()}. * @return array|object Array of the parsed item on success. WP_Error object * on failure. */ function bp_rbe_legacy_forums_post($retval, $data, $params) { // Forum reply if (!empty($params['t'])) { if (bp_is_active('groups') && bp_is_active('forums')) { bp_rbe_log('Message #' . $data['i'] . ': this is a forum reply'); // get all group member data for the user in one swoop! $group_member_data = bp_rbe_get_group_member_info($data['user_id'], $params['g']); // user is not a member of the group anymore if (empty($group_member_data)) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'user_not_group_member' ); return new WP_Error('user_not_group_member'); } // user is banned from group if ((int) $group_member_data->is_banned == 1) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'user_banned_from_group' ); return new WP_Error('user_banned_from_group'); } // Don't allow reply flooding // Only available in 1.6+ if (function_exists('bp_forums_reply_exists') && bp_forums_reply_exists(esc_sql($data['content']), $params['t'], $data['user_id'])) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'forum_reply_exists' ); return new WP_Error('forum_reply_exists'); } /* okay, we should be good to post now! */ $forum_post_id = bp_rbe_groups_new_group_forum_post(array('post_text' => $data['content'], 'topic_id' => $params['t'], 'user_id' => $data['user_id'], 'group_id' => $params['g'])); if (!$forum_post_id) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'forum_reply_fail' ); return new WP_Error('forum_reply_fail'); } bp_rbe_log('Message #' . $data['i'] . ': forum reply successfully posted!'); // could potentially add attachments // @todo do_action('bp_rbe_new_forum_post', false, $forum_post_id, $data['user_id'], $params['g'], $data['headers']); return array('legacy_forums_post_id' => $forum_post_id); } // New forum topic } elseif (!empty($params['g'])) { if (bp_is_active('groups') && bp_is_active('forums')) { bp_rbe_log('Message #' . $data['i'] . ': this is a new forum topic'); bp_rbe_log('Message #' . $data['i'] . ': body contents - ' . $data['content']); bp_rbe_log('Subject - ' . $subject); if (empty($data['content']) || empty($data['subject'])) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'new_forum_topic_empty' ); return new WP_Error('new_forum_topic_empty'); } // get all group member data for the user in one swoop! $group_member_data = bp_rbe_get_group_member_info($data['user_id'], $params['g']); // user is not a member of the group anymore if (empty($group_member_data)) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'user_not_group_member' ); return new WP_Error('user_not_group_member'); } // user is banned from group if ((int) $group_member_data->is_banned == 1) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'user_banned_from_group' ); return new WP_Error('user_banned_from_group'); } /* okay, we should be good to post now! */ $topic = bp_rbe_groups_new_group_forum_topic(array('topic_title' => $data['subject'], 'topic_text' => $data['content'], 'user_id' => $data['user_id'], 'group_id' => $params['g'])); if (!$topic) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'new_topic_fail' ); return new WP_Error('new_topic_fail'); } bp_rbe_log('Message #' . $data['i'] . ': forum topic successfully posted!'); // could potentially add attachments do_action('bp_rbe_new_forum_topic', false, $topic, $data['user_id'], $params['g'], $data['headers']); return array('legacy_forums_topic_id' => $topic); } } }
/** * Failsafe for RBE. * * RBE occasionally hangs during the inbox loop. This function tries * to snap RBE out of it by checking the last few lines of the RBE * debug log. * * If all lines match our failed cronjob message, then reset RBE so * RBE can run fresh on the next scheduled run. * * @uses bp_rbe_tail() Grabs the last N lines from the RBE debug log * @uses bp_rbe_cleanup() Cleans up the DB entries that RBE uses * @since 1.0-RC1 */ function bp_rbe_failsafe() { // get the last N lines from the RBE debug log $last_entries = bp_rbe_tail(constant('BP_RBE_DEBUG_LOG_PATH'), constant('BP_RBE_TAIL_LINES')); if (empty($last_entries)) { return; } // count the number of tines our 'cronjob wants to connect' message occurs $counter = 0; // see if each line contains our cronjob fail string foreach ($last_entries as $entry) { if (strpos($entry, '--- Cronjob wants to connect - however according to our DB indicator, we already have an active IMAP connection! ---') !== false) { ++$counter; } } // if all lines match the cronjob fail string, reset RBE! if ($counter == constant('BP_RBE_TAIL_LINES')) { bp_rbe_log('--- Uh-oh! Looks like RBE is stuck! - FORCE RBE cleanup ---'); // cleanup RBE! bp_rbe_cleanup(); // use this hook to perhaps send an email to the admin? do_action('bp_rbe_failsafe_complete'); } }
/** * Post by email handler. * * Validate data and post on success. * * @param bool $retval True by default. * @param array $data { * An array of arguments. * * @type array $headers Email headers. * @type string $content The email body content. * @type string $subject The email subject line. * @type int $user_id The user ID who sent the email. * @type bool $is_html Whether the email content is HTML or not. * @type int $i The email message number. * } * @param array $params Parsed paramaters from the email address querystring. * See {@link BP_Reply_By_Email_Parser::get_parameters()}. * @return array|object Array of the parsed item on success. WP_Error object * on failure. */ public function post($retval, $data, $params) { global $bp; $comment_id = !empty($params[$this->secondary_item_id_param]) ? $params[$this->secondary_item_id_param] : false; $i = $data['i']; // this means that the current email is a BP Doc reply // let's proceed! if (!empty($comment_id)) { // it's important to let RBE know what's happening during the process // for debugging purposes // // use bp_rbe_log() to log anything you want // in this case, we're letting RBE know that we're in the process of // rendering a comment reply bp_rbe_log('Message #' . $i . ': this is a BP Doc comment reply'); // get parent comment data $comment = get_comment($comment_id); // parent comment doesn't exist or was deleted if (empty($comment)) { // when a condition for posting isn't met, return a WP_Error object. // next, log it under the internal_rbe_log()_method // and optionally, prep a failure message under the failure_message_to_sender() method //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_parent_comment_deleted' ); return new WP_Error('bp_doc_parent_comment_deleted', '', $data); } // parent comment status checks switch ($comment->comment_approved) { case 'spam': //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_parent_comment_spam' ); return new WP_Error('bp_doc_parent_comment_spam', '', $data); break; case 'trash': //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_parent_comment_deleted' ); return new WP_Error('bp_doc_parent_comment_deleted', '', $data); break; case '0': //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_parent_comment_unapproved' ); return new WP_Error('bp_doc_parent_comment_unapproved', '', $data); break; } // get doc settings $doc_settings = get_post_meta($comment->comment_post_ID, 'bp_docs_settings', true); // set temporary variable $bp->rbe = $bp->rbe->temp = new stdClass(); // get group ID // $bp->rbe->temp->group_id gets passed to BP_Reply_By_Email::set_group_id() $group_id = $bp->rbe->temp->group_id = $params[$this->item_id_param]; // get user ID $user_id = $data['user_id']; // check to see if the user can post comments for the group doc in question // // bp_docs_user_can( 'post_comments', $user_id, $group_id ) doesn't work the way I want it to // using the doc's comment settings as a guideline // check the comment settings for the doc switch ($doc_settings['post_comments']) { // this means that the comment settings for the doc recently switched to 'no-one' case 'no-one': //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_comment_change_to_noone' ); return new WP_Error('bp_doc_comment_change_to_noone', '', $data); break; // if the doc only allows group admins and mods to comment, return false for regular group members // if the doc only allows group admins and mods to comment, return false for regular group members case 'admins-mods': // get the email address of the replier $user_email = BP_Reply_By_Email_Parser::get_header($data['headers'], 'From'); // get an array of group admin / mod email addresses // note: email addresses are set as key, not value $admin_mod_emails = $this->get_admin_mod_user_emails($group_id); // if the replier's email address does not match a group admin or mod, stop now! if (!isset($admin_mod_emails[$user_email])) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_user_not_admin_mod' ); return new WP_Error('bp_doc_user_not_admin_mod', '', $data); } break; // if the doc allows any group member to comment, check if member is still part of // the group and not banned // if the doc allows any group member to comment, check if member is still part of // the group and not banned case 'group-members': if (!groups_is_user_member($user_id, $group_id)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_user_not_member' ); return new WP_Error('bp_doc_user_not_member', '', $data); } if (groups_is_user_banned($user_id, $group_id)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_user_banned' ); return new WP_Error('bp_doc_user_banned', '', $data); } break; } /* okay! we should be good to post now! */ // get the userdata $userdata = get_userdata($user_id); // we're using wp_insert_comment() instead of wp_new_comment() // why? because wp_insert_comment() bypasses all the WP comment hooks, which is good for us! $new_comment_id = wp_insert_comment(array('user_id' => $user_id, 'comment_post_ID' => $comment->comment_post_ID, 'comment_content' => $data['content'], 'comment_parent' => $comment_id, 'comment_author' => $userdata->user_nicename, 'comment_author_url' => '', 'comment_author_email' => $userdata->user_email, 'comment_author_IP' => '', 'comment_agent' => '', 'comment_type' => '')); // comment successfully posted! if (!empty($new_comment_id)) { // more internal logging bp_rbe_log('Message #' . $i . ': BP Doc comment reply successfully posted!'); /* now let's record the activity item for this comment */ // override BP Docs' default comment activity action add_filter('bp_docs_comment_activity_action', array($this, 'comment_activity_action')); // now post the activity item with BP Docs' special class method if (class_exists('BP_Docs_BP_Integration')) { // BP Docs v1.1.x support $activity_id = BP_Docs_BP_Integration::post_comment_activity($new_comment_id); } else { // BP Docs v1.2.x support $activity_id = BP_Docs_Component::post_comment_activity($new_comment_id); } // special hook for RBE activity items // if you're adding an activity entry in this method, remember to add this hook after posting // your activity item in this method! do_action('bp_rbe_new_activity', array('activity_id' => $activity_id, 'type' => $this->activity_type, 'user_id' => $user_id, 'item_id' => $group_id, 'secondary_item_id' => $comment_id, 'content' => $data['content'])); // remove the filter after posting remove_filter('bp_docs_comment_activity_action', array($this, 'comment_activity_action')); return array('bp_doc_comment_id' => $new_comment_id); } else { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bp_doc_new_comment_fail' ); return new WP_Error('bp_doc_new_comment_fail', '', $data); } } }
/** * Post new topic by email handler. * * For bbPress, the logic in this method is the same as {@link bbp_new_topic_handler()}. * It's duplicated because bbPress doesn't utilize hooks for verifying topics. * * @todo No fancy support for topic tags, subscriptions yet. Will probably need shortcodes. * * @param array $data { * An array of arguments. * * @type array $headers Email headers. * @type string $content The email body content. * @type string $subject The email subject line. * @type int $user_id The user ID who sent the email. * @type bool $is_html Whether the email content is HTML or not. * @type int $i The email message number. * } * @param array $params Parsed paramaters from the email address querystring. * See {@link BP_Reply_By_Email_Parser::get_parameters()}. * @return array|object Array of the parsed item on success. WP_Error object * on failure. */ private function post_new_topic($data, $params) { //private function post_new_topic( $connection, $i, $headers, $params, $body, $topic_author ) { /** SETUP DATA ***************************************************/ $i = $data['i']; $topic_author = $data['user_id']; $forum_id = $params[$this->forum_id_param]; /* current email is a bbPress new topic, let's proceed! */ // let RBE know that we're in the process of rendering a bbP new topic // BuddyPress group new topic if (!empty($params[$this->item_id_param])) { bp_rbe_log('Message #' . $i . ': this is a bbPress group forum new topic'); // bbPress } else { bp_rbe_log('Message #' . $i . ': this is a bbPress new topic'); } // other variables $anonymous_data = 0; /** GROUP PERMISSIONS ********************************************/ // posting from a BP group if (!empty($params[$this->item_id_param])) { global $bp; // set group ID and cache it in global for later use // $bp->rbe->temp->group_id gets passed to the set_group_id() method later on $group_id = $bp->rbe->temp->group_id = $params[$this->item_id_param]; // get all group member data for the user in one swoop! $group_member_data = bp_rbe_get_group_member_info($topic_author, $group_id); // user is not a member of the group anymore if (empty($group_member_data)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'user_not_group_member' ); return new WP_Error('user_not_group_member', '', $data); } // user is banned from group if ((int) $group_member_data->is_banned == 1) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'user_banned_from_group' ); return new WP_Error('user_banned_from_group', '', $data); } // override groups_get_current_group() with our cached group ID add_filter('groups_get_current_group', array($this, 'set_group_id')); // temporarily add some GES filters here add_filter('bp_ass_activity_notification_subject', 'wp_specialchars_decode'); add_filter('bp_ass_activity_notification_content', 'wp_specialchars_decode'); } /** TOPIC / FORUM PERMISSIONS ************************************/ // Allow member to pass default cap checks. // The reason why we keep the user_can() checks below is b/c bbPress // plugins may disable cap access for a specific user if they have hooked into // the 'bbp_map_meta_caps' filter. add_filter('bbp_map_meta_caps', array($this, 'map_forum_meta_caps'), 5, 4); // User cannot create topics if (!user_can($topic_author, 'publish_topics')) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_topic_permissions' ); return new WP_Error('bbp_topic_permissions', '', $data); } // Forum is a category if (bbp_is_forum_category($forum_id)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_edit_topic_forum_category' ); //bbp_add_error( 'bbp_edit_topic_forum_category', __( '<strong>ERROR</strong>: This forum is a category. No topics can be created in this forum.', 'bbpress' ) ); return new WP_Error('bbp_edit_topic_forum_category', '', $data); // Forum is not a category } else { // Forum is closed and user cannot access if (bbp_is_forum_closed($forum_id) && !user_can($topic_author, 'edit_forum')) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_edit_topic_forum_closed' ); //bbp_add_error( 'bbp_edit_topic_forum_closed', __( '<strong>ERROR</strong>: This forum has been closed to new topics.', 'bbpress' ) ); return new WP_Error('bbp_edit_topic_forum_closed', '', $data); } // Forum is private and user cannot access if (bbp_is_forum_private($forum_id)) { if (!user_can($topic_author, 'read_private_forums')) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_edit_topic_forum_private' ); //bbp_add_error( 'bbp_edit_topic_forum_private', __( '<strong>ERROR</strong>: This forum is private and you do not have the capability to read or create new topics in it.', 'bbpress' ) ); return new WP_Error('bbp_edit_topic_forum_private', '', $data); } } // Forum is hidden and user cannot access if (bbp_is_forum_hidden($forum_id)) { if (!user_can($topic_author, 'read_hidden_forums')) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_edit_topic_forum_hidden' ); //bbp_add_error( 'bbp_edit_topic_forum_hidden', __( '<strong>ERROR</strong>: This forum is hidden and you do not have the capability to read or create new topics in it.', 'bbpress' ) ); return new WP_Error('bbp_edit_topic_forum_hidden', '', $data); } } } /** UNFILTERED HTML **********************************************/ // Remove wp_filter_kses filters from title and content for capable users if (user_can($topic_author, 'unfiltered_html')) { remove_filter('bbp_new_topic_pre_title', 'wp_filter_kses'); remove_filter('bbp_new_topic_pre_content', 'wp_filter_kses'); } /** TOPIC DATA ***************************************************/ $topic_content = $data['content']; $topic_title = $data['subject']; bp_rbe_log('Message #' . $i . ': body contents - ' . $topic_content); bp_rbe_log('Subject - ' . $topic_title); if (empty($topic_content) || empty($topic_title)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_new_forum_topic_empty' ); return new WP_Error('bbp_new_forum_topic_empty', '', $data); } // Filter and sanitize $topic_title = apply_filters('bbp_new_topic_pre_title', $topic_title); $topic_content = apply_filters('bbp_new_topic_pre_content', $topic_content); /** Topic Tags ****************************************************/ /* TODO if ( bbp_allow_topic_tags() ) { // Escape tag input $terms = esc_attr( strip_tags( $_POST['bbp_topic_tags'] ) ); // Explode by comma if ( strstr( $terms, ',' ) ) { $terms = explode( ',', $terms ); } // Add topic tag ID as main key $terms = array( bbp_get_topic_tag_tax_id() => $terms ); } */ /** TOPIC MODERATION *********************************************/ // Post Flooding if (!bbp_check_for_flood($anonymous_data, $topic_author)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_topic_flood' ); //bbp_add_error( 'bbp_reply_flood', __( '<strong>ERROR</strong>: Slow down; you move too fast.', 'bbpress' ) ); return new WP_Error('bbp_topic_flood', '', $data); } // Topic Duplicate if (!bbp_check_for_duplicate(array('post_type' => bbp_get_topic_post_type(), 'post_author' => $topic_author, 'post_content' => $topic_content, 'anonymous_data' => $anonymous_data))) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_topic_duplicate' ); return new WP_Error('bbp_topic_duplicate', '', $data); } // Topic Blacklist if (!bbp_check_for_blacklist($anonymous_data, $topic_author, $topic_title, $topic_content)) { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_topic_blacklist' ); return new WP_Error('bbp_topic_blacklist', '', $data); } // Topic Status // Maybe put into moderation if (!bbp_check_for_moderation($anonymous_data, $topic_author, $topic_title, $topic_content)) { $topic_status = bbp_get_pending_status_id(); // Default } else { $topic_status = bbp_get_public_status_id(); } /** POSTING TIME! ************************************************/ // bbP hook before save do_action('bbp_new_topic_pre_extras', $forum_id); // Setup reply data $topic_data = apply_filters('bbp_new_topic_pre_insert', array('post_author' => $topic_author, 'post_title' => $topic_title, 'post_content' => $topic_content, 'post_status' => $topic_status, 'post_parent' => $forum_id, 'post_type' => bbp_get_topic_post_type(), 'comment_status' => 'closed')); // Insert topic $topic_id = wp_insert_post($topic_data); // Topic posted! if (!is_wp_error($topic_id)) { // more internal logging bp_rbe_log('Message #' . $i . ': bbPress topic successfully posted!'); // Problem posting } else { //do_action( 'bp_rbe_imap_no_match', $connection, $i, $headers, 'bbp_topic_error' ); return new WP_Error('bbp_topic_error', '', $data); } /** AFTER POSTING ************************************************/ // stuff that needs to happen after a bbP topic is posted occurs here... bbP // should preferably do the following at the 'bbp_new_reply' hook, until then // do what bbP does inline. // Trash Check //////////////////////////////////////////////////// // If the forum is trash, or the topic_status is switched to // trash, trash it properly if (get_post_field('post_status', $forum_id) == bbp_get_trash_status_id() || $topic_data['post_status'] == bbp_get_trash_status_id()) { // Trash the reply wp_trash_post($topic_id); } // Spam Check ///////////////////////////////////////////////////// // If reply or topic are spam, officially spam this reply if ($topic_data['post_status'] == bbp_get_spam_status_id()) { add_post_meta($topic_id, '_bbp_spam_meta_status', bbp_get_public_status_id()); } // Reply By Email ///////////////////////////////////////////////// // Add a RBE marker to the post's meta // Could potentially show that post was made via email on the frontend add_post_meta($topic_id, 'bp_rbe', 1); /** POST HOOKS ***************************************************/ // RBE Custom Hooks /////////////////////////////////////////////// // change activity action add_filter('bbp_before_record_activity_parse_args', array($this, 'change_activity_action')); // add RBE's special activity hook add_action('bp_activity_after_save', array($this, 'activity_rbe_hook')); // bbPress Topic Hooks //////////////////////////////////////////// do_action('bbp_new_topic', $topic_id, $forum_id, $anonymous_data, $topic_author); do_action('bbp_new_topic_post_extras', $topic_id); return array('bbp_topic_id' => $topic_id); }
/** * Parses and returns the email body content. * * @param string $body The email body content. * @param bool $html Whether the email body is HTML or not. Defaults to false. * @param bool $reply Whether the current item is a reply. Defaults to true. * @param int $i The current email message number. * @return string|bool */ public static function get_body($body = '', $html = false, $reply = true, $i = 1) { if ($html) { $body = apply_filters('bp_rbe_parse_html_email', $body); } // Check to see if we're parsing a reply if ($reply) { // Find our pointer $pointer = strpos($body, bp_rbe_get_marker()); // If our pointer isn't found, return false if ($pointer === false) { return false; } // Return email body up to our pointer only $body = apply_filters('bp_rbe_parse_email_body_reply', trim(substr($body, 0, $pointer)), $body); // this means we're posting something new (eg. new forum topic) // do something special for this case } else { $body = apply_filters('bp_rbe_parse_email_body_new', $body); } if (empty($body)) { bp_rbe_log('Message #' . $i . ': empty body'); return false; } return apply_filters('bp_rbe_parse_email_body', trim($body)); }
/** * Webhook parser class method for Mandrill. */ public function webhook_parser() { if (empty($_SERVER['HTTP_X_MANDRILL_SIGNATURE'])) { return; } if (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Mandrill-Webhook/') === false) { return; } if (empty($_POST) || empty($_POST['mandrill_events'])) { return; } bp_rbe_log('- Mandrill webhook received -'); // mandrill signature verification if (defined('BP_RBE_MANDRILL_WEBHOOK_URL') && defined('BP_RBE_MANDRILL_WEBHOOK_KEY')) { $signed_data = constant('BP_RBE_MANDRILL_WEBHOOK_URL'); $signed_data .= 'mandrill_events'; $signed_data .= stripslashes($_POST['mandrill_events']); $webhook_key = constant('BP_RBE_MANDRILL_WEBHOOK_KEY'); $signature = base64_encode(hash_hmac('sha1', $signed_data, $webhook_key, true)); // check if generated signature matches Mandrill's if ($signature !== $_SERVER['HTTP_X_MANDRILL_SIGNATURE']) { bp_rbe_log('Mandrill signature verification failed.'); die; } } // get parsed content $response = json_decode(stripslashes($_POST['mandrill_events'])); // log JSON errors if present if (json_last_error() != JSON_ERROR_NONE) { switch (json_last_error()) { case JSON_ERROR_DEPTH: bp_rbe_log('json error: - Maximum stack depth exceeded'); break; case JSON_ERROR_STATE_MISMATCH: bp_rbe_log('json error: - Underflow or the modes mismatch'); break; case JSON_ERROR_CTRL_CHAR: bp_rbe_log('json error: - Unexpected control character found'); break; case JSON_ERROR_SYNTAX: bp_rbe_log('json error: - Syntax error, malformed JSON'); break; case JSON_ERROR_UTF8: bp_rbe_log('json error: - Malformed UTF-8 characters, possibly incorrectly encoded'); break; default: bp_rbe_log('json error: - Unknown error'); break; } die; // ready to start the parsing! } else { $i = 1; foreach ($response as $item) { $data = array('headers' => $item->msg->headers, 'to_email' => $item->msg->email, 'from_email' => $item->msg->from_email, 'content' => $item->msg->text, 'subject' => $item->msg->subject); $parser = BP_Reply_By_Email_Parser::init($data, $i); if (is_wp_error($parser)) { do_action('bp_rbe_no_match', $parser, $data, $i, false); } ++$i; } bp_rbe_log('- Webhook parsing completed -'); die; } }
/** * Fetch all headers for an email and parses them into an array. * * @uses imap_fetchheader() Grabs full, raw unmodified email header * @param resource $imap The current IMAP connection * @param int $i The current email message number * @return mixed Array of email headers. False if no headers. */ protected function get_mail_headers($imap, $i) { // Grab full, raw email header $header = imap_fetchheader($imap, $i); // No header? Return false if (empty($header)) { bp_rbe_log('Message #' . $i . ': error - no IMAP header'); return false; } // Do a regex match $pattern = apply_filters('bp_rbe_header_regex', '/([^: ]+): (.+?(?:\\r\\n\\s(?:.+?))*)\\r\\n/m'); preg_match_all($pattern, $header, $matches); // Parse headers into an array with descriptive key $headers = array_combine($matches[1], $matches[2]); // No headers? Return false if (empty($headers)) { bp_rbe_log('Message #' . $i . ': error - no headers found'); return false; } return $headers; }
/** * Post by email routine. * * Validates the parsed data and posts the various BuddyPress content. * * @since 1.0-RC3 * * @param bool $retval True by default. * @param array $data { * An array of arguments. * * @type array $headers Email headers. * @type string $content The email body content. * @type string $subject The email subject line. * @type int $user_id The user ID who sent the email. * @type bool $is_html Whether the email content is HTML or not. * @type int $i The email message number. * } * @param array $params Parsed paramaters from the email address querystring. * See {@link BP_Reply_By_Email_Parser::get_parameters()}. * @return array|object Array of the parsed item on success. WP_Error object * on failure. */ public function post($retval, $data, $params) { global $bp, $wpdb; // Activity reply if (!empty($params['a'])) { bp_rbe_log('Message #' . $data['i'] . ': this is an activity reply, checking if parent activities still exist'); // Check to see if the root activity ID and the parent activity ID exist before posting $activity_count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$bp->activity->table_name} WHERE id IN ( %d, %d )", $params['a'], $params['p'])); // If $a = $p, this means that we're replying to a top-level activity update // So check if activity count is 1 if ($params['a'] == $params['p'] && $activity_count != 1) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'root_activity_deleted' ); return new WP_Error('root_activity_deleted'); // If we're here, this means we're replying to an activity comment // If count != 2, this means either the super admin or activity author has deleted one of the update(s) } elseif ($params['a'] != $params['p'] && $activity_count != 2) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'root_or_parent_activity_deleted' ); return new WP_Error('root_or_parent_activity_deleted'); } /* Let's start posting! */ // Add our filter to override the activity action in bp_activity_new_comment() bp_rbe_activity_comment_action_filter($data['user_id']); $comment_id = bp_activity_new_comment(array('content' => $data['content'], 'user_id' => $data['user_id'], 'activity_id' => $params['a'], 'parent_id' => $params['p'])); if (!$comment_id) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'activity_comment_fail' ); return new WP_Error('activity_comment_fail'); } // special hook for RBE activity items // might want to do something like add some activity meta do_action('bp_rbe_new_activity', array('activity_id' => $comment_id, 'type' => 'activity_comment', 'user_id' => $data['user_id'], 'item_id' => $params['a'], 'secondary_item_id' => $params['p'], 'content' => $data['content'])); bp_rbe_log('Message #' . $data['i'] . ': activity comment successfully posted!'); // remove the filter after posting remove_filter('bp_activity_comment_action', 'bp_rbe_activity_comment_action'); // return array of item on success return array('activity_comment_id' => $comment_id); // Private message reply } elseif (!empty($params['m'])) { if (bp_is_active($bp->messages->id)) { bp_rbe_log('Message #' . $data['i'] . ': this is a private message reply'); // see if the PM thread still exists if (messages_is_valid_thread($params['m'])) { // see if the user is in the PM conversation $has_access = messages_check_thread_access($params['m'], $data['user_id']) || is_super_admin($data['user_id']); if (!$has_access) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'private_message_not_in_thread' ); return new WP_Error('private_message_not_in_thread'); } // post the PM! $message_id = messages_new_message(array('thread_id' => $params['m'], 'sender_id' => $data['user_id'], 'content' => $data['content'])); if (!$message_id) { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'private_message_fail' ); return new WP_Error('private_message_fail'); } // special hook for RBE parsed PMs do_action('bp_rbe_new_pm_reply', array('thread_id' => $params['m'], 'sender_id' => $data['user_id'], 'content' => $data['content'])); bp_rbe_log('Message #' . $data['i'] . ': PM reply successfully posted!'); // return array of item on success return array('message_id' => $message_id); // the PM thread doesn't exist anymore } else { //do_action( 'bp_rbe_imap_no_match', $this->connection, $i, $headers, 'private_message_thread_deleted' ); return new WP_Error('private_message_thread_deleted'); } } } }