description; } /** * Define any i18n strings needed for this connector. Defaults to noop. * * @since 1.0 * * @return array */ public function connector_i18n() { return array(); } /** * A map to handle migrating existing settings to this connector. Should * return an array of arrays containing the following values: * * - original_key: The key for the setting in the existing add-on. * * - new_key: The new key to map the value to in our system. * * - sub_key: The optional sub-key to search for in a multidimensional array of * options. Arrays can be navigated using '/', e.g. 'key/subkey/subsubkey'. * * - transform: An optional callback to apply to the value before saving. * * @since 1.0 * * @return array */ public function migration_map() { return array(); } /** * Constructor. * * @since 1.0 * * @param $php_mailer * @param $data_store * @param $logger * @param $events * @param $header_parser * @param $recipient_parser * @param $debug_logger * * @return void */ public function __construct( $php_mailer, $data_store, $logger, $events, $header_parser, $recipient_parser, $debug_logger ) { $this->php_mailer = $php_mailer; $this->data_store = $data_store; $this->logger = $logger; $this->events = $events; $this->header_parser = $header_parser; $this->recipient_parser = $recipient_parser; $this->debug_logger = $debug_logger; } public function handle_suppressed_email( $email, $source ) { $atts = $this->get_atts(); $this->set_email_log_data( $atts['subject'], $atts['message'], $email, $atts['from'], $atts['headers'], $atts['attachments'], $source, array() ); $this->events->update( array( 'status' => 'suppressed' ), $this->email ); $this->logger->log( $this->email, 'failed', 'Recipient email address ' . $email . ' is suppressed.' ); $this->debug_logger->log_error( $this->wrap_debug_with_details( __FUNCTION__, $this->email, 'Recipient email address ' . $email . ' is suppressed.' ) ); } /** * Initialize the connector and map attributes as necessary. * * @since 1.0 * * @param $to * @param $subject * @param $message * @param $headers * @param $attachments * * @return void */ public function init( $to, $subject, $message, $headers = '', $attachments = array(), $source = '' ) { $service_name = $this->name === 'phpmail' ? 'wp_mail' : $this->name; $this->email = $this->events->create( $service_name, 'pending', '', '', '', '', array(), ); // Set to blank values to avoid warnings. $from = ''; $from_name = ''; /** * Filters the wp_mail() arguments. * * @since 2.2.0 * * @param array $args A compacted array of wp_mail() arguments, including the "to" email, * subject, message, headers, and attachments values. */ $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments', 'from', 'from_name', 'source' ) ); $atts['to'] = $this->recipient_parser->parse( $atts['to'] ); $parsed_headers = $this->get_parsed_headers( $atts['headers'] ); if ( isset( $parsed_headers['from'] ) ) { $from_data = $this->get_email_from_header( 'From', $parsed_headers['from'] ); $atts['from'] = $from_data->recipients()[0]->email(); $atts['from_name'] = $from_data->recipients()[0]->name(); } else { $atts['from'] = ''; $atts['from_name'] = ''; } $this->atts = $atts; do_action( 'gravitysmtp_after_connector_init', $this->email, $this ); } /** * Get all of the attributes for this connector. * * @since 1.5.0 * * @return array */ public function get_atts() { return $this->atts; } protected function set_email_log_data( $subject, $message, $to, $from, $headers, $attachments, $source, $params = array() ) { $params = $this->strip_sensitive_log_data( $params ); $this->events->update( array( 'subject' => $subject, 'message' => $message, 'extra' => serialize( array( 'to' => $to, 'from' => $from, 'headers' => array(), 'attachments' => $attachments, 'source' => $source, 'params' => $params, ) ) ), $this->email ); } private function strip_sensitive_log_data( $params ) { unset( $params['body'] ); unset( $params['headers'] ); return $params; } /** * Get an attribute, passing it through necessary filters. * * @since 1.0 * * @param $att_name * @param $default * * @return mixed|null */ public function get_att( $att_name, $default = '' ) { $value = isset( $this->atts[ $att_name ] ) ? $this->atts[ $att_name ] : $default; $wp_mail_filters = array( 'from', 'from_name', 'content_type', 'charset' ); if ( in_array( $att_name, $wp_mail_filters ) ) { $value = apply_filters( 'wp_mail_' . $att_name, $value ); } return apply_filters( 'gravitysmtp_email_attribute_' . $att_name, $value ); } /** * Set an attribute. * * @since 1.5.0 * * @param $att_name * @param $value * * @return void */ public function set_att( $att_name, $value ) { $this->atts[ $att_name ] = $value; } /** * Get the From email. * * @since 1.0 * * @param bool $return_array Wether to return an array containing the individual parts of the from address ( 'email', 'name' and 'from') or just the From string. * * @return string | array */ protected function get_from( $return_array = false ) { $force_from_email = $this->get_setting( self::SETTING_FORCE_FROM_EMAIL, false ); $force_from_name = $this->get_setting( self::SETTING_FORCE_FROM_NAME, false ); if ( empty( $force_from_email ) && empty( $force_from_name ) ) { $from = $this->get_att( 'from', '' ); if ( empty( $from ) ) { $from = $this->get_setting( self::SETTING_FROM_EMAIL, '' ); } $from_name = $this->get_att( 'from_name', '' ); if ( empty( $from_name ) ) { $from_name = $this->get_setting( self::SETTING_FROM_NAME, '' ); } } else { $from = ! empty( $force_from_email ) ? $this->get_setting( self::SETTING_FROM_EMAIL, '' ) : $this->get_att( 'from', '' ); $from_name = ! empty( $force_from_name ) ? $this->get_setting( self::SETTING_FROM_NAME, '' ) : $this->get_att( 'from_name', '' ); } // From was not passed; use admin email if ( empty( $from ) ) { $from = get_option( 'admin_email' ); } // RFC 5322: quote display names that contain specials so parsers // don't misinterpret them (e.g. parentheses treated as comments). if ( ! empty( $from_name ) && preg_match( '/[()<>\[\]:;@\\\\",.]/', $from_name ) ) { $from_name_quoted = '"' . str_replace( array( '\\', '"' ), array( '\\\\', '\\"' ), $from_name ) . '"'; } else { $from_name_quoted = $from_name; } $from_str = ! empty( $from_name ) ? $from_name_quoted . ' <' . $from . '>' : $from; if ( $return_array ) { $return = array( 'email' => $from, 'from' => $from_str, ); if ( ! empty( $from_name ) ) { $return['name'] = $from_name; } return $return; } return $from_str; } /** * Get the Reply-To email. * * @since 1.0 * * @param bool $return_as_array Wether to return an array containing the individual parts of the reply-to address or just the Reply-To string. * * @return string | array */ public function get_reply_to( $return_as_array = false ) { $force_reply_to_setting = $this->get_setting( self::SETTING_FORCE_REPLY_TO_EMAIL, false ); $default_reply_to_setting = $this->get_setting( self::SETTING_REPLY_TO_EMAIL, '' ); $parsed_headers = $this->get_parsed_headers( $this->atts['headers'] ); $forcing_reply_to = $force_reply_to_setting && ! empty( $default_reply_to_setting ); $defaulting_reply_to = ( ! isset( $parsed_headers['reply-to'] ) || empty( $parsed_headers['reply-to'] ) ) && ! empty( $default_reply_to_setting ); // If we're forcing the reply-to email or the reply-to header is empty, use the default reply-to email. if ( $forcing_reply_to || $defaulting_reply_to ) { return $return_as_array ? array( array( 'email' => $default_reply_to_setting ) ) : $default_reply_to_setting; } if ( ! isset( $parsed_headers['reply-to'] ) || empty( $parsed_headers['reply-to'] ) ) { return $return_as_array ? array() : ''; } $email_data = $this->get_email_from_header( 'Reply-To', $parsed_headers['reply-to'] ); return $return_as_array ? $email_data->as_array() : $email_data->as_string(); } /** * Get the sensitive fields array for this connector * * @since 1.9.0 * * @return array */ public function get_sensitive_fields() { return $this->sensitive_fields; } public function get_request_params() { return array(); } /** * Get the default From settings fields. * * @since 1.0 * * @return array[] Returns an array of settings fields. */ protected function get_from_settings_fields() { return array( array( 'component' => 'Input', 'props' => array( 'labelAttributes' => array( 'label' => esc_html__( 'Default From Email', 'gravitysmtp' ), 'size' => 'text-sm', 'weight' => 'medium', ), 'name' => self::SETTING_FROM_EMAIL, 'spacing' => 6, 'size' => 'size-l', 'value' => $this->get_setting( self::SETTING_FROM_EMAIL, '' ), ), ), array( 'component' => 'Toggle', 'props' => array( 'initialChecked' => (bool) $this->get_setting( self::SETTING_FORCE_FROM_EMAIL, false ), 'labelAttributes' => array( 'label' => esc_html__( 'Force From Email', 'gravitysmtp' ), ), 'helpTextAttributes' => array( 'content' => esc_html__( 'If Force Email is enabled, the Default From Email address will override other plugin settings for all outgoing emails.', 'gravitysmtp' ), 'size' => 'text-xs', 'spacing' => array( 2, 0, 0, 0 ), 'weight' => 'regular', ), 'helpTextWidth' => 'full', 'labelPosition' => 'left', 'name' => self::SETTING_FORCE_FROM_EMAIL, 'size' => 'size-m', 'spacing' => 6, 'width' => 'full', ), ), array( 'component' => 'Input', 'props' => array( 'labelAttributes' => array( 'label' => esc_html__( 'Default From Name', 'gravitysmtp' ), 'size' => 'text-sm', 'weight' => 'medium', ), 'name' => self::SETTING_FROM_NAME, 'size' => 'size-l', 'spacing' => 6, 'value' => $this->get_setting( self::SETTING_FROM_NAME, '' ), ), ), array( 'component' => 'Toggle', 'props' => array( 'initialChecked' => (bool) $this->get_setting( self::SETTING_FORCE_FROM_NAME, false ), 'labelAttributes' => array( 'label' => esc_html__( 'Force From Name', 'gravitysmtp' ), ), 'helpTextAttributes' => array( 'content' => esc_html__( 'If Force Name is enabled, the Default From Name will override other plugin settings for all outgoing emails.', 'gravitysmtp' ), 'size' => 'text-xs', 'spacing' => array( 2, 0, 0, 0 ), 'weight' => 'regular', ), 'helpTextWidth' => 'full', 'labelPosition' => 'left', 'name' => self::SETTING_FORCE_FROM_NAME, 'size' => 'size-m', 'spacing' => 6, 'width' => 'full', ), ), ); } /** * Get the default Reply-To settings fields. * * @since 1.0 * * @return array[] Returns an array of settings fields. */ protected function get_reply_to_settings_fields() { return array( array( 'component' => 'Input', 'props' => array( 'labelAttributes' => array( 'label' => esc_html__( 'Default Reply-To Email', 'gravitysmtp' ), 'size' => 'text-sm', 'weight' => 'medium', ), 'name' => self::SETTING_REPLY_TO_EMAIL, 'spacing' => 6, 'size' => 'size-l', 'value' => $this->get_setting( self::SETTING_REPLY_TO_EMAIL, '' ), ), ), array( 'component' => 'Toggle', 'props' => array( 'initialChecked' => (bool) $this->get_setting( self::SETTING_FORCE_REPLY_TO_EMAIL, false ), 'labelAttributes' => array( 'label' => esc_html__( 'Force Reply-To Email', 'gravitysmtp' ), ), 'helpTextAttributes' => array( 'content' => esc_html__( 'If Force Reply-To Email is enabled, the Default Reply-To Email address will override other plugin settings for all outgoing emails.', 'gravitysmtp' ), 'size' => 'text-xs', 'spacing' => array( 2, 0, 0, 0 ), 'weight' => 'regular', ), 'helpTextWidth' => 'full', 'labelPosition' => 'left', 'name' => self::SETTING_FORCE_REPLY_TO_EMAIL, 'size' => 'size-m', 'spacing' => 6, 'width' => 'full', ), ), ); } /** * Retrieve any additional message headers that may have been added, whether through filters or * custom code. * * @since 1.0 * * @return array */ public function get_filtered_message_headers() { $headers = $this->get_parsed_headers( $this->get_att( 'headers', array() ) ); foreach( $this->header_parser->standard_headers as $header ) { unset( $headers[ $header ] ); } return array_unique( $headers ); } /** * Get parsed/normalized headers for use in PHP Mailer. * * @since 1.0 * * @param $headers * * @return array */ protected function get_parsed_headers( $headers ) { $parsed_headers = $this->header_parser->parse( $headers ); if ( ! isset( $parsed_headers['content-type'] ) ) { $parsed_headers['content-type'] = 'text/plain'; } return $parsed_headers; } protected function get_header_from_string( $string ) { return $this->header_parser->get_header_from_string( $string ); } protected function get_formatted_cc( $values ) { return $this->header_parser->get_formatted_cc( $values ); } /** * Get the email address info from the Header strings. * * @sicne 1.0 * * @param $header_name * @param $header_string * * @return array|array[] */ protected function get_email_from_header( $header_name, $header_string ) { return $this->header_parser->get_email_from_header( $header_name, $header_string ); } /** * Helper method for retrieving plugin settings (i.e., settings for the plugin globally * and not specific to this connector). * * @since 1.0 * * @param $setting_name * @param $default * * @return mixed|null */ protected function get_plugin_setting( $setting_name, $default = null ) { return $this->data_store->get_plugin_setting( $setting_name, $default ); } /** * Helper method to retrieve a saved setting specifically for this connector. * * @since 1.0 * * @param $setting_name * @param $default * * @return mixed */ protected function get_setting( $setting_name, $default = null ) { if ( $setting_name === self::SETTING_IS_BACKUP || $setting_name === self::SETTING_IS_PRIMARY ) { $const_setting = $this->check_for_connector_status_flag( $setting_name ); if ( ! empty( $const_setting ) ) { return $const_setting === $this->name; } } return $this->data_store->get_setting( $this->name, $setting_name, $default ); } protected function setting_should_be_obfuscated( $setting_name ) { if ( ! in_array( $setting_name, $this->sensitive_fields ) ) { return false; } return true; } protected function get_locked_settings() { $return = array(); $defined_constants = array_filter( get_defined_constants(), function( $constant ) { return strpos( $constant, 'GRAVITYSMTP_' ) !== false; }, ARRAY_FILTER_USE_KEY ); foreach( $defined_constants as $constant => $constant_value ) { $setting_name = strtolower( str_replace( 'GRAVITYSMTP_', '', $constant ) ); $return[] = $setting_name; } return $return; } private function check_for_connector_status_flag( $setting_name ) { $const_check = $setting_name === self::SETTING_IS_PRIMARY ? 'GRAVITYSMTP_INTEGRATION_PRIMARY' : 'GRAVITYSMTP_INTEGRATION_BACKUP'; if ( defined( $const_check ) ) { return constant( $const_check ); } return false; } /** * Get the mapped data for this connector to use in a Config. * * @since 1.0 * * @return array */ public function get_data() { $fields = $this->settings_fields(); $data = $this->get_merged_data(); if ( ! empty( $fields['fields'] ) ) { foreach( $fields['fields'] as $idx => $field_data ) { if ( ! array_key_exists( 'value', $field_data['props'] ) ) { continue; } $name = $field_data['props']['name']; if ( ! $this->setting_should_be_obfuscated( $name ) ) { continue; } if ( empty( $fields['fields'][ $idx ]['props']['value'] ) ) { continue; } $fields['fields'][$idx]['props']['value'] = self::OBFUSCATED_STRING; } } foreach( $data as $key => $value ) { if ( ! $this->setting_should_be_obfuscated( $key ) ) { continue; } if ( empty( $value ) ) { continue; } $data[ $key ] = self::OBFUSCATED_STRING; } return array( 'fields' => $fields, 'data' => $data, 'i18n' => $this->connector_i18n(), 'name' => $this->name, 'logo' => $this->logo, 'full_logo' => $this->full_logo, 'title' => $this->title, 'description' => $this->get_description(), ); } /** * Get the unique data for this connector, merged with the default/common data for all * connectors in the system. * * @since 1.0 * * @return array */ protected function get_merged_data() { // @todo - we might want to refactor this to use the Cache class in the future. $configured_key = sprintf( 'gsmtp_connector_configured_%s', $this->name ); $cached = get_transient( $configured_key ); if ( $cached === false ) { $is_configured = $this->is_configured(); $configured = ( ! is_wp_error( $is_configured ) && $is_configured !== false ); set_transient( $configured_key, array( 'configured' => $configured ), DAY_IN_SECONDS ); } else { $configured = $cached['configured']; } $defaults = array( self::SETTING_ACTIVATED => $this->get_setting( self::SETTING_ACTIVATED, true ), self::SETTING_CONFIGURED => $configured, self::SETTING_ENABLED => $this->get_setting( self::SETTING_ENABLED, false ), self::SETTING_IS_PRIMARY => $this->get_setting( self::SETTING_IS_PRIMARY, false ), self::SETTING_IS_BACKUP => $this->get_setting( self::SETTING_IS_BACKUP, false ), ); return array_merge( $this->connector_data(), $defaults ); } /** * Whether this connector has been configured by the user. Defaults to checking for stored * settings values, but can be overridden for other logic. * * @since 1.0 * * @return bool */ public function is_configured() { $configured = $this->get_setting( self::SETTING_CONFIGURED, null ); if ( ! is_null( $configured ) ) { return $configured; } return false; } /** * Whether test mode setting is enabled for the plugin. * * @since 1.0 * * @return bool */ protected function is_test_mode() { $test_mode = $this->get_plugin_setting( 'test_mode', 'false' ); if ( empty( $test_mode ) ) { $test_mode = false; } else { $test_mode = $test_mode !== 'false'; } return $test_mode; } protected function wrap_debug_with_details( $function, $email, $message ) { return sprintf( '%s(): [EMAIL ID %s] - %s', $function, $email, $message ); } }