Ticket #6592: 6592-core.01.patch
| File 6592-core.01.patch, 44.2 KB (added by , 10 years ago) |
|---|
-
src/bp-core/bp-core-classes.php
diff --git a/src/bp-core/bp-core-classes.php b/src/bp-core/bp-core-classes.php index d2801ea..1566c85 100644
a b require dirname( __FILE__ ) . '/classes/class-bp-media-extractor.php'; 25 25 require dirname( __FILE__ ) . '/classes/class-bp-attachment.php'; 26 26 require dirname( __FILE__ ) . '/classes/class-bp-attachment-avatar.php'; 27 27 require dirname( __FILE__ ) . '/classes/class-bp-attachment-cover-image.php'; 28 require dirname( __FILE__ ) . '/classes/class-bp-email-recipient.php'; 29 require dirname( __FILE__ ) . '/classes/class-bp-email.php'; 30 require dirname( __FILE__ ) . '/classes/class-bp-email-delivery.php'; 31 require dirname( __FILE__ ) . '/classes/class-bp-phpmailer.php'; -
src/bp-core/bp-core-filters.php
diff --git a/src/bp-core/bp-core-filters.php b/src/bp-core/bp-core-filters.php index 0d4b342..6c96811 100644
a b add_filter( 'bp_core_render_message_content', 'wpautop' ); 53 53 add_filter( 'bp_core_render_message_content', 'shortcode_unautop' ); 54 54 add_filter( 'bp_core_render_message_content', 'wp_kses_data', 5 ); 55 55 56 // Emails. 57 add_filter( 'bp_email_set_content_html', 'wp_filter_post_kses', 6 ); 58 add_filter( 'bp_email_set_content_html', 'stripslashes', 8 ); 59 add_filter( 'bp_email_set_content_plaintext', 'wp_strip_all_tags', 6 ); 60 add_filter( 'bp_email_set_subject', 'sanitize_text_field', 6 ); 61 62 56 63 /** 57 64 * Template Compatibility. 58 65 * … … function _bp_core_inject_bp_widget_css_class( $params ) { 1038 1045 return $params; 1039 1046 } 1040 1047 add_filter( 'dynamic_sidebar_params', '_bp_core_inject_bp_widget_css_class' ); 1048 1049 /** 1050 * Add custom headers to outgoing emails. 1051 * 1052 * @since 2.5.0 1053 * 1054 * @param array $headers 1055 * @param string $property Name of property. Unused. 1056 * @param string $transform Return value transformation. Unused. 1057 * @param BP_Email $email Email object reference. 1058 * @return array 1059 */ 1060 function bp_email_set_default_headers( $headers, $property, $transform, $email ) { 1061 $headers['X-BuddyPress'] = bp_get_version(); 1062 $headers['X-BuddyPress-Type'] = $email->get( 'type' ); 1063 1064 return $headers; 1065 } 1066 add_filter( 'bp_email_get_headers', 'bp_email_set_default_headers', 6, 4 ); 1067 1068 /** 1069 * Add default email tokens. 1070 * 1071 * @since 2.5.0 1072 * 1073 * @param array $tokens Email tokens. 1074 * @param string $property_name Unused. 1075 * @param string $transform Unused. 1076 * @param BP_Email $email Email being sent. 1077 * @return array 1078 */ 1079 function bp_email_set_default_tokens( $tokens, $property_name, $transform, $email ) { 1080 $tokens['site.admin-email'] = bp_get_option( 'admin_email' ); 1081 $tokens['site.url'] = home_url(); 1082 1083 // These options are escaped with esc_html on the way into the database in sanitize_option(). 1084 $tokens['site.description'] = wp_specialchars_decode( bp_get_option( 'blogdescription' ), ENT_QUOTES ); 1085 $tokens['site.name'] = wp_specialchars_decode( bp_get_option( 'blogname' ), ENT_QUOTES ); 1086 1087 // Default values for tokens set conditionally below. 1088 $tokens['unsubscribe'] = ''; 1089 1090 1091 // Who is the email going to? 1092 $recipient = $email->get( 'to' ); 1093 if ( $recipient ) { 1094 1095 $user = array_shift( $recipient )->get_user( 'search-email' ); 1096 if ( $user ) { 1097 1098 // Unsubscribe link. 1099 $tokens['unsubscribe'] = esc_url( sprintf( 1100 '%s%s/notifications/', 1101 bp_core_get_user_domain( $user->ID ), 1102 function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings' 1103 ) ); 1104 } 1105 } 1106 1107 return $tokens; 1108 } 1109 add_filter( 'bp_email_get_tokens', 'bp_email_set_default_tokens', 6, 4 ); -
new file src/bp-core/classes/class-bp-email-delivery.php
diff --git a/src/bp-core/classes/class-bp-email-delivery.php b/src/bp-core/classes/class-bp-email-delivery.php new file mode 100644 index 0000000..f96cd01
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * Email delivery implementation base class. 14 * 15 * When implementing support for an email delivery service into BuddyPress, 16 * you are required to create a class that implements this interface. 17 * 18 * @since 2.5.0 19 */ 20 interface BP_Email_Delivery { 21 22 /** 23 * Send email(s). 24 * 25 * @since 2.5.0 26 * 27 * @param BP_Email $email Email to send. 28 * @return bool|WP_Error Returns true if email send, else a descriptive WP_Error. 29 */ 30 public function bp_email( BP_Email $email ); 31 } -
new file src/bp-core/classes/class-bp-email-recipient.php
diff --git a/src/bp-core/classes/class-bp-email-recipient.php b/src/bp-core/classes/class-bp-email-recipient.php new file mode 100644 index 0000000..54e2b88
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * Represents a recipient that an email will be sent to. 14 * 15 * @since 2.5.0 16 */ 17 class BP_Email_Recipient { 18 19 /** 20 * Recipient's email address. 21 * 22 * @since 2.5.0 23 * 24 * @var string 25 */ 26 protected $address = ''; 27 28 /** 29 * Recipient's name. 30 * 31 * @since 2.5.0 32 * 33 * @var string 34 */ 35 protected $name = ''; 36 37 /** 38 * Optional. A `WP_User` object relating to this recipient. 39 * 40 * @since 2.5.0 41 * 42 * @var WP_User 43 */ 44 protected $user_object = null; 45 46 /** 47 * Constructor. 48 * 49 * @since 2.5.0 50 * 51 * @param string|array|int|WP_User $email_or_user Either a email address, user ID, WP_User object, 52 * or an array containg the address and name. 53 * @param string $name Optional. If $email_or_user is a string, this is the recipient's name. 54 */ 55 public function __construct( $email_or_user, $name = '' ) { 56 $name = sanitize_text_field( $name ); 57 58 // User ID, WP_User object. 59 if ( is_int( $email_or_user ) || is_object( $email_or_user ) ) { 60 $this->user_object = is_object( $email_or_user ) ? $email_or_user : get_user_by( 'ID', $email_or_user ); 61 62 if ( $this->user_object ) { 63 // This is escaped with esc_html in bp_core_get_user_displayname() 64 $name = wp_specialchars_decode( bp_core_get_user_displayname( $this->user_object->ID ), ENT_QUOTES ); 65 66 $this->address = $this->user_object->user_email; 67 $this->name = sanitize_text_field( $name ); 68 } 69 70 // Array, address, and name. 71 } else { 72 if ( ! is_array( $email_or_user ) ) { 73 $email_or_user = array( $email_or_user => $name ); 74 } 75 76 // Handle numeric arrays. 77 if ( is_int( key( $email_or_user ) ) ) { 78 $address = current( $email_or_user ); 79 } else { 80 $address = key( $email_or_user ); 81 $name = current( $email_or_user ); 82 } 83 84 if ( is_email( $address ) ) { 85 $this->address = sanitize_email( $address ); 86 } 87 88 $this->name = $name; 89 } 90 91 /** 92 * Fires inside __construct() method for BP_Email_Recipient class. 93 * 94 * @since 2.5.0 95 * 96 * @param string|array|int|WP_User $email_or_user Either a email address, user ID, WP_User object, 97 * or an array containg the address and name. 98 * @param string $name If $email_or_user is a string, this is the recipient's name. 99 * @param BP_Email_Recipient $this Current instance of the email type class. 100 */ 101 do_action( 'bp_email_recipient', $email_or_user, $name, $this ); 102 } 103 104 /** 105 * Get recipient's address. 106 * 107 * @since 2.5.0 108 * 109 * @return string 110 */ 111 public function get_address() { 112 113 /** 114 * Filters the recipient's address before it's returned. 115 * 116 * @since 2.5.0 117 * 118 * @param string $address Recipient's address. 119 * @param BP_Email $recipient $this Current instance of the email recipient class. 120 */ 121 return apply_filters( 'bp_email_recipient_get_address', $this->address, $this ); 122 } 123 124 /** 125 * Get recipient's name. 126 * 127 * @since 2.5.0 128 * 129 * @return string 130 */ 131 public function get_name() { 132 133 /** 134 * Filters the recipient's name before it's returned. 135 * 136 * @since 2.5.0 137 * 138 * @param string $name Recipient's name. 139 * @param BP_Email $recipient $this Current instance of the email recipient class. 140 */ 141 return apply_filters( 'bp_email_recipient_get_name', $this->name, $this ); 142 } 143 144 /** 145 * Get WP_User object for this recipient. 146 * 147 * @since 2.5.0 148 * 149 * @param string $transform Optional. How to transform the return value. 150 * Accepts 'raw' (default) or 'search-email'. 151 * @return WP_User|null WP_User object, or null if not set. 152 */ 153 public function get_user( $transform = 'raw' ) { 154 155 // If transform "search-email", find the WP_User if not already set. 156 if ( $transform === 'search-email' && ! $this->user_object && $this->address ) { 157 $this->user_object = get_user_by( 'email', $this->address ); 158 } 159 160 /** 161 * Filters the WP_User object for this recipient before it's returned. 162 * 163 * @since 2.5.0 164 * 165 * @param WP_User $name WP_User object for this recipient, or null if not set. 166 * @param string $transform Optional. How the return value was transformed. 167 * Accepts 'raw' (default) or 'search-email'. 168 * @param BP_Email $recipient $this Current instance of the email recipient class. 169 */ 170 return apply_filters( 'bp_email_recipient_get_name', $this->user_object, $transform, $this ); 171 } 172 } -
new file src/bp-core/classes/class-bp-email.php
diff --git a/src/bp-core/classes/class-bp-email.php b/src/bp-core/classes/class-bp-email.php new file mode 100644 index 0000000..be8f2af
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * Represents an email that will be sent to member(s). 14 * 15 * @since 2.5.0 16 */ 17 class BP_Email { 18 /** 19 * Addressee details (BCC). 20 * 21 * @since 2.5.0 22 * 23 * @var BP_Email_Recipient[] BCC recipients. 24 */ 25 protected $bcc = array(); 26 27 /** 28 * Addressee details (CC). 29 * 30 * @since 2.5.0 31 * 32 * @var BP_Email_Recipient[] CC recipients. 33 */ 34 protected $cc = array(); 35 36 /** 37 * Email content (HTML). 38 * 39 * @since 2.5.0 40 * 41 * @var string 42 */ 43 protected $content_html = ''; 44 45 /** 46 * Email content (plain text). 47 * 48 * @since 2.5.0 49 * 50 * @var string 51 */ 52 protected $content_plaintext = ''; 53 54 /** 55 * The content type to send the email in ("html" or "plaintext"). 56 * 57 * @since 2.5.0 58 * 59 * @var string 60 */ 61 protected $content_type = 'html'; 62 63 /** 64 * Sender details. 65 * 66 * @since 2.5.0 67 * 68 * @var BP_Email_Recipient Sender details. 69 */ 70 protected $from = null; 71 72 /** 73 * Email headers. 74 * 75 * @since 2.5.0 76 * 77 * @var string[] Associative pairing of email header name/value. 78 */ 79 protected $headers = array(); 80 81 /** 82 * The Post object (the source of the email's content and subject). 83 * 84 * @since 2.5.0 85 * 86 * @var WP_Post 87 */ 88 protected $post_object = null; 89 90 /** 91 * Reply To details. 92 * 93 * @since 2.5.0 94 * 95 * @var BP_Email_Recipient "Reply to" details. 96 */ 97 protected $reply_to = null; 98 99 /** 100 * Email subject. 101 * 102 * @since 2.5.0 103 * 104 * @var string 105 */ 106 protected $subject = ''; 107 108 /** 109 * Email template (the HTML wrapper around the email content). 110 * 111 * @since 2.5.0 112 * 113 * @var string 114 */ 115 protected $template = '{{{content}}}'; 116 117 /** 118 * Addressee details (to). 119 * 120 * @since 2.5.0 121 * 122 * @var BP_Email_Recipient[] Email recipients. 123 * } 124 */ 125 protected $to = array(); 126 127 /** 128 * Unique identifier for this particular type of email. 129 * 130 * @since 2.5.0 131 * 132 * @var string 133 */ 134 protected $type = ''; 135 136 /** 137 * Token names and replacement values for this email. 138 * 139 * @since 2.5.0 140 * 141 * @var string[] Associative pairing of token name (key) and replacement value (value). 142 */ 143 protected $tokens = array(); 144 145 /** 146 * Constructor. 147 * 148 * Set the email type and default "from" and "reply to" name and address. 149 * 150 * @since 2.5.0 151 * 152 * @param string $email_type Unique identifier for a particular type of email. 153 */ 154 public function __construct( $email_type ) { 155 $this->type = $email_type; 156 157 // SERVER_NAME isn't always set (e.g CLI). 158 if ( ! empty( $_SERVER['SERVER_NAME'] ) ) { 159 $domain = strtolower( $_SERVER['SERVER_NAME'] ); 160 if ( substr( $domain, 0, 4 ) === 'www.' ) { 161 $domain = substr( $domain, 4 ); 162 } 163 164 } elseif ( function_exists( 'gethostname' ) && gethostname() !== false ) { 165 $domain = gethostname(); 166 167 } elseif ( php_uname( 'n' ) !== false ) { 168 $domain = php_uname( 'n' ); 169 170 } else { 171 $domain = 'localhost.localdomain'; 172 } 173 174 // This was escaped with esc_html on the way into the database in sanitize_option(). 175 $site_name = wp_specialchars_decode( bp_get_option( 'blogname' ), ENT_QUOTES ); 176 177 $this->from( "wordpress@$domain", $site_name ); 178 $this->reply_to( bp_get_option( 'admin_email' ), $site_name ); 179 180 /** 181 * Fires inside __construct() method for BP_Email class. 182 * 183 * @since 2.5.0 184 * 185 * @param string $email_type Unique identifier for this type of email. 186 * @param BP_Email $this Current instance of the email type class. 187 */ 188 do_action( 'bp_email', $email_type, $this ); 189 } 190 191 192 /* 193 * Psuedo setters/getters. 194 */ 195 196 /** 197 * Getter function to expose object properties. 198 * 199 * Unlike most other methods in this class, this one is not chainable. 200 * 201 * @since 2.5.0 202 * 203 * @param string $property_name Property to access. 204 * @param string $transform Optional. How to transform the return value. 205 * Accepts 'raw' (default) or 'replace-tokens'. 206 * @return mixed Returns null if property does not exist, otherwise the value. 207 */ 208 public function get( $property_name, $transform = 'raw' ) { 209 // "content" is replaced by HTML or plain text depending on $content_type. 210 if ( $property_name === 'content' ) { 211 $property_name = 'content_' . $this->get( 'content_type' ); 212 213 if ( ! in_array( $property_name, array( 'content_html', 'content_plaintext', ), true ) ) { 214 $property_name = 'content_html'; 215 } 216 } 217 218 if ( ! property_exists( $this, $property_name ) ) { 219 return null; 220 } 221 222 223 /** 224 * Filters the value of the specified email property. 225 * 226 * This is a dynamic filter dependent on the specified key. 227 * 228 * @since 2.5.0 229 * 230 * @param mixed $property_value Property value. 231 * @param string $property_name 232 * @param string $transform How to transform the return value. 233 * Accepts 'raw' (default) or 'replace-tokens'. 234 * @param BP_Email $this Current instance of the email type class. 235 */ 236 $retval = apply_filters( "bp_email_get_{$property_name}", $this->$property_name, $property_name, $transform, $this ); 237 238 switch ( $transform ) { 239 // Special-case to fill the $template with the email $content. 240 case 'add-content': 241 $retval = str_replace( '{{{content}}}', nl2br( $this->get( 'content', 'replace-tokens' ) ), $retval ); 242 // Fall through. 243 244 case 'replace-tokens': 245 $retval = self::replace_tokens( $retval, $this->get( 'tokens', 'raw' ) ); 246 // Fall through. 247 248 case 'raw': 249 default: 250 // Do nothing. 251 } 252 253 /** 254 * Filters the value of the specified email $property. 255 * 256 * @since 2.5.0 257 * 258 * @param string $retval Property value. 259 * @param string $property_name 260 * @param string $transform How to transform the return value. 261 * Accepts 'raw' (default) or 'replace-tokens'. 262 * @param BP_Email $this Current instance of the email type class. 263 */ 264 return apply_filters( 'bp_email_get_property', $retval, $property_name, $transform, $this ); 265 } 266 267 /** 268 * Set email headers. 269 * 270 * Does NOT let you override to/from, etc. Use the methods provided to set those. 271 * 272 * @since 2.5.0 273 * 274 * @param string[] $headers Key/value pairs of header name/values (strings). 275 * @return BP_Email 276 */ 277 public function headers( array $headers ) { 278 $new_headers = array(); 279 280 foreach ( $headers as $name => $content ) { 281 $content = str_replace( ':', '', $content ); 282 $name = str_replace( ':', '', $name ); 283 284 $new_headers[ sanitize_key( $name ) ] = sanitize_text_field( $content ); 285 } 286 287 /** 288 * Filters the new value of the email's "headers" property. 289 * 290 * @since 2.5.0 291 * 292 * @param string[] $new_headers Key/value pairs of new header name/values (strings). 293 * @param BP_Email $this Current instance of the email type class. 294 */ 295 $this->headers = apply_filters( 'bp_email_set_headers', $new_headers, $this ); 296 297 return $this; 298 } 299 300 /** 301 * Set the email's "bcc" address. 302 * 303 * To set a single address, the first parameter is the address and the second the name. 304 * You can also pass a user ID or a WP_User object. 305 * 306 * To set multiple addresses, for each array item, the key is the email address and 307 * the value is the name. 308 * 309 * @since 2.5.0 310 * 311 * @param string|array|int|WP_User $bcc_address Either a email address, user ID, WP_User object, 312 * or an array containg the address and name. 313 * @param string $name Optional. If $bcc_address is a string, this is the recipient's name. 314 * @return BP_Email 315 */ 316 public function bcc( $bcc_address, $name = '' ) { 317 $bcc = array( new BP_Email_Recipient( $bcc_address, $name ) ); 318 319 /** 320 * Filters the new value of the email's "BCC" property. 321 * 322 * @since 2.5.0 323 * 324 * @param BP_Email_Recipient[] $bcc BCC recipients. 325 * @param string|array|int|WP_User $bcc_address Either a email address, user ID, WP_User object, 326 * or an array containg the address and name. 327 * @param string $name Optional. If $bcc_address is a string, this is the recipient's name. 328 * @param BP_Email $this Current instance of the email type class. 329 */ 330 $this->bcc = apply_filters( 'bp_email_set_bcc', $bcc, $bcc_address, $name, $this ); 331 332 return $this; 333 } 334 335 /** 336 * Set the email's "cc" address. 337 * 338 * To set a single address, the first parameter is the address and the second the name. 339 * You can also pass a user ID or a WP_User object. 340 * 341 * To set multiple addresses, for each array item, the key is the email address and 342 * the value is the name. 343 * 344 * @since 2.5.0 345 * 346 * @param string|array|int|WP_User $cc_address Either a email address, user ID, WP_User object, 347 * or an array containg the address and name. 348 * @param string $name Optional. If $cc_address is a string, this is the recipient's name. 349 * @return BP_Email 350 */ 351 public function cc( $cc_address, $name = '' ) { 352 $cc = array( new BP_Email_Recipient( $cc_address, $name ) ); 353 354 /** 355 * Filters the new value of the email's "CC" property. 356 * 357 * @since 2.5.0 358 * 359 * @param BP_Email_Recipient[] $cc CC recipients. 360 * @param string|array|int|WP_User $cc_address Either a email address, user ID, WP_User object, 361 * or an array containg the address and name. 362 * @param string $name Optional. If $cc_address is a string, this is the recipient's name. 363 * @param BP_Email $this Current instance of the email type class. 364 */ 365 $this->cc = apply_filters( 'bp_email_set_cc', $cc, $cc_address, $name, $this ); 366 367 return $this; 368 } 369 370 /** 371 * Set the email content (HTML). 372 * 373 * @since 2.5.0 374 * 375 * @param string $content HTML email content. 376 * @return BP_Email 377 */ 378 public function content_html( $content ) { 379 380 /** 381 * Filters the new value of the email's "content" property (HTML). 382 * 383 * @since 2.5.0 384 * 385 * @param string $content HTML email content. 386 * @param BP_Email $this Current instance of the email type class. 387 */ 388 $this->content_html = apply_filters( 'bp_email_set_content_html', $content, $this ); 389 390 return $this; 391 } 392 393 /** 394 * Set the email content (plain text). 395 * 396 * @since 2.5.0 397 * 398 * @param string $content Plain text email content. 399 * @return BP_Email 400 */ 401 public function content_plaintext( $content ) { 402 403 /** 404 * Filters the new value of the email's "content" property (plain text). 405 * 406 * @since 2.5.0 407 * 408 * @param string $content Plain text email content. 409 * @param BP_Email $this Current instance of the email type class. 410 */ 411 $this->content_plaintext = apply_filters( 'bp_email_set_content_plaintext', $content, $this ); 412 413 return $this; 414 } 415 416 /** 417 * Set the content type (HTML or plain text) to send the email in. 418 * 419 * @since 2.5.0 420 * 421 * @param string $content_type Email content type ("html" or "plaintext"). 422 * @return BP_Email 423 */ 424 public function content_type( $content_type ) { 425 if ( ! in_array( $content_type, array( 'html', 'plaintext', ), true ) ) { 426 $class = get_class_vars( get_class() ); 427 $content_type = $class['content_type']; 428 } 429 430 /** 431 * Filters the new value of the email's "content type" property. 432 * 433 * The content type (HTML or plain text) to send the email in. 434 * 435 * @since 2.5.0 436 * 437 * @param string $content_type Email content type ("html" or "plaintext"). 438 * @param BP_Email $this Current instance of the email type class. 439 */ 440 $this->content_type = apply_filters( 'bp_email_set_content_type', $content_type, $this ); 441 442 return $this; 443 } 444 445 /** 446 * Set the email's "from" address and name. 447 * 448 * @since 2.5.0 449 * 450 * @param string|array|int|WP_User $email_address Either a email address, user ID, WP_User object, 451 * or an array containg the address and name. 452 * @param string $name Optional. If $email_address is a string, this is the recipient's name. 453 * @return BP_Email 454 */ 455 public function from( $email_address, $name = '' ) { 456 $from = new BP_Email_Recipient( $email_address, $name ); 457 458 /** 459 * Filters the new value of the email's "from" property. 460 * 461 * @since 2.5.0 462 * 463 * @param BP_Email_Recipient $from Sender details. 464 * @param string|array|int|WP_User $email_address Either a email address, user ID, WP_User object, 465 * or an array containg the address and name. 466 * @param string $name Optional. If $email_address is a string, this is the recipient's name. 467 * @param BP_Email $this Current instance of the email type class. 468 */ 469 $this->from = apply_filters( 'bp_email_set_from', $from, $email_address, $name, $this ); 470 471 return $this; 472 } 473 474 /** 475 * Set the Post object containing the email content template. 476 * 477 * Also sets the email's subject, content, and template from the Post, for convenience. 478 * 479 * @since 2.5.0 480 * 481 * @param WP_Post $post 482 * @return BP_Email 483 */ 484 public function post_object( WP_Post $post ) { 485 486 /** 487 * Filters the new value of the email's "post object" property. 488 * 489 * @since 2.5.0 490 * 491 * @param WP_Post $post A Post. 492 * @param BP_Email $this Current instance of the email type class. 493 */ 494 $this->post_object = apply_filters( 'bp_email_set_post_object', $post, $this ); 495 496 if ( is_a( $this->post_object, 'WP_Post' ) ) { 497 $this->subject( $this->post_object->post_title ) 498 ->content_html( $this->post_object->post_content ) 499 ->content_plaintext( $this->post_object->post_excerpt ); 500 501 ob_start(); 502 503 // Load the template. 504 bp_locate_template( bp_email_get_template( $this->post_object ), true, false ); 505 $this->template( ob_get_contents() ); 506 507 ob_end_clean(); 508 } 509 510 return $this; 511 } 512 513 /** 514 * Set the email's "reply to" address and name. 515 * 516 * @since 2.5.0 517 * 518 * @param string|array|int|WP_User $email_address Either a email address, user ID, WP_User object, 519 * or an array containg the address and name. 520 * @param string $name Optional. If $email_address is a string, this is the recipient's name. 521 * @return BP_Email 522 */ 523 public function reply_to( $email_address, $name = '' ) { 524 $reply_to = new BP_Email_Recipient( $email_address, $name ); 525 526 /** 527 * Filters the new value of the email's "reply to" property. 528 * 529 * @since 2.5.0 530 * 531 * @param BP_Email_Recipient $reply_to "Reply to" recipient. 532 * @param string|array|int|WP_User $email_address Either a email address, user ID, WP_User object, 533 * or an array containg the address and name. 534 * @param string $name Optional. If $email_address is a string, this is the recipient's name. 535 * @param BP_Email $this Current instance of the email type class. 536 */ 537 $this->reply_to = apply_filters( 'bp_email_set_reply_to', $reply_to, $email_address, $name, $this ); 538 539 return $this; 540 } 541 542 /** 543 * Set the email subject. 544 * 545 * @since 2.5.0 546 * 547 * @param string $subject Email subject. 548 * @return BP_Email 549 */ 550 public function subject( $subject ) { 551 552 /** 553 * Filters the new value of the subject email property. 554 * 555 * @since 2.5.0 556 * 557 * @param string $subject Email subject. 558 * @param BP_Email $this Current instance of the email type class. 559 */ 560 $this->subject = apply_filters( 'bp_email_set_subject', $subject, $this ); 561 562 return $this; 563 } 564 565 /** 566 * Set the email template (the HTML wrapper around the email content). 567 * 568 * This needs to include the string "{{{content}}}" to have the post content added 569 * when the email template is rendered. 570 * 571 * @since 2.5.0 572 * 573 * @param string $template Email template. Assumed to be HTML. 574 * @return BP_Email 575 */ 576 public function template( $template ) { 577 578 /** 579 * Filters the new value of the template email property. 580 * 581 * @since 2.5.0 582 * 583 * @param string $template Email template. Assumed to be HTML. 584 * @param BP_Email $this Current instance of the email type class. 585 */ 586 $this->template = apply_filters( 'bp_email_set_template', $template, $this ); 587 588 return $this; 589 } 590 591 /** 592 * Set the email's "to" address. 593 * 594 * IMPORTANT NOTE: the assumption with all emails sent by (and belonging to) BuddyPress itself 595 * is that there will only be a single `$to_address`. This is to simplify token and templating 596 * logic (for example, if multiple recipients, the "unsubscribe" link in the emails will all 597 * only link to the first recipient). 598 * 599 * To set a single address, the first parameter is the address and the second the name. 600 * You can also pass a user ID or a WP_User object. 601 * 602 * To set multiple addresses, for each array item, the key is the email address and 603 * the value is the name. 604 * 605 * @since 2.5.0 606 * 607 * @param string|array|int|WP_User $to_address Either a email address, user ID, WP_User object, 608 * or an array containg the address and name. 609 * @param string $name Optional. If $to_address is a string, this is the recipient's name. 610 * @return BP_Email 611 */ 612 public function to( $to_address, $name = '' ) { 613 $to = array( new BP_Email_Recipient( $to_address, $name ) ); 614 615 /** 616 * Filters the new value of the email's "to" property. 617 * 618 * @since 2.5.0 619 * 620 * @param BP_Email_Recipient[] "To" recipients. 621 * @param string $to_address "To" address. 622 * @param string $name "To" name. 623 * @param BP_Email $this Current instance of the email type class. 624 */ 625 $this->to = apply_filters( 'bp_email_set_to', $to, $to_address, $name, $this ); 626 627 return $this; 628 } 629 630 /** 631 * Set token names and replacement values for this email. 632 * 633 * In templates, tokens are inserted with a Handlebars-like syntax, e.g. `{{token_name}}`. 634 * { and } are reserved characters. There's no need to specify these brackets in your token names. 635 * 636 * @since 2.5.0 637 * 638 * @param string[] $tokens Associative array, contains key/value pairs of token name/value. 639 * Values are a string or a callable function. 640 * @return BP_Email 641 */ 642 public function tokens( array $tokens ) { 643 $formatted_tokens = array(); 644 645 foreach ( $tokens as $name => $value ) { 646 $name = str_replace( array( '{', '}' ), '', sanitize_text_field( $name ) ); 647 $formatted_tokens[ $name ] = $value; 648 } 649 650 /** 651 * Filters the new value of the email's "tokens" property. 652 * 653 * @since 2.5.0 654 * 655 * @param string[] $formatted_tokens Associative pairing of token names (key) and replacement values (value). 656 * @param string[] $tokens Associative pairing of unformatted token names (key) and replacement values (value). 657 * @param BP_Email $this Current instance of the email type class. 658 */ 659 $this->tokens = apply_filters( 'bp_email_set_tokens', $formatted_tokens, $tokens, $this ); 660 661 return $this; 662 } 663 664 665 /* 666 * Sanitisation and validation logic. 667 */ 668 669 /** 670 * Check that we'd be able to send this email. 671 * 672 * Unlike most other methods in this class, this one is not chainable. 673 * 674 * @since 2.5.0 675 * 676 * @return bool|WP_Error Returns true if validation succesful, else a descriptive WP_Error. 677 */ 678 public function validate() { 679 $retval = true; 680 681 // BCC, CC, and token properties are optional. 682 if ( 683 ! $this->get( 'from' ) || 684 ! $this->get( 'to' ) || 685 ! $this->get( 'subject' ) || 686 ! $this->get( 'content' ) || 687 ! $this->get( 'template' ) 688 ) { 689 $retval = new WP_Error( 'missing_parameter', __CLASS__, $this ); 690 } 691 692 /** 693 * Filters whether the email passes basic validation checks. 694 * 695 * @since 2.5.0 696 * 697 * @param bool|WP_Error $retval Returns true if validation succesful, else a descriptive WP_Error. 698 * @param BP_Email $this Current instance of the email type class. 699 */ 700 return apply_filters( 'bp_email_validate', $retval, $this ); 701 } 702 703 704 /* 705 * Utility functions. 706 * 707 * Unlike other methods in this class, utility functions are not chainable. 708 */ 709 710 /** 711 * Replace all tokens in the input with appropriate values. 712 * 713 * Unlike most other methods in this class, this one is not chainable. 714 * 715 * @since 2.5.0 716 * 717 * @param string $text 718 * @param array $tokens Token names and replacement values for the $text. 719 * @return string 720 */ 721 public static function replace_tokens( $text, $tokens ) { 722 $unescaped = array(); 723 $escaped = array(); 724 725 foreach ( $tokens as $token => $value ) { 726 if ( is_callable( $value ) ) { 727 $value = call_user_func( $value ); 728 } 729 730 // Some tokens are objects or arrays for backwards compatibilty. See bp_core_deprecated_email_filters(). 731 if ( ! is_scalar( $value ) ) { 732 continue; 733 } 734 735 $unescaped[ '{{{' . $token . '}}}' ] = $value; 736 $escaped[ '{{' . $token . '}}' ] = esc_html( $value ); 737 } 738 739 $text = strtr( $text, $unescaped ); // Do first. 740 $text = strtr( $text, $escaped ); 741 742 /** 743 * Filters text that has had tokens replaced. 744 * 745 * @since 2.5.0 746 * 747 * @param string $text 748 * @param array $tokens Token names and replacement values for the $text. 749 */ 750 return apply_filters( 'bp_email_replace_tokens', $text, $tokens ); 751 } 752 } -
new file src/bp-core/classes/class-bp-phpmailer.php
diff --git a/src/bp-core/classes/class-bp-phpmailer.php b/src/bp-core/classes/class-bp-phpmailer.php new file mode 100644 index 0000000..1610895
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * Email delivery implementation using PHPMailer. 14 * 15 * @since 2.5.0 16 */ 17 class BP_PHPMailer implements BP_Email_Delivery { 18 19 /** 20 * Constructor. 21 * 22 * @since 2.5.0 23 */ 24 public function __construct() { 25 global $phpmailer; 26 27 // We'll try to use the PHPMailer object that might have been created by WordPress. 28 if ( ! ( $phpmailer instanceof PHPMailer ) ) { 29 require_once ABSPATH . WPINC . '/class-phpmailer.php'; 30 require_once ABSPATH . WPINC . '/class-smtp.php'; 31 $phpmailer = new PHPMailer( true ); 32 } 33 } 34 35 /** 36 * Send email(s). 37 * 38 * @since 2.5.0 39 * 40 * @param BP_Email $email Email to send. 41 * @return bool|WP_Error Returns true if email send, else a descriptive WP_Error. 42 */ 43 public function bp_email( BP_Email $email ) { 44 global $phpmailer; 45 46 /* 47 * Resets. 48 */ 49 50 $phpmailer->clearAllRecipients(); 51 $phpmailer->clearAttachments(); 52 $phpmailer->clearCustomHeaders(); 53 $phpmailer->clearReplyTos(); 54 $phpmailer->Sender = ''; 55 56 57 /* 58 * Set up. 59 */ 60 61 $phpmailer->IsMail(); 62 $phpmailer->CharSet = bp_get_option( 'blog_charset' ); 63 $phpmailer->Hostname = self::get_hostname(); 64 65 66 /* 67 * Content. 68 */ 69 70 $phpmailer->Subject = $email->get( 'subject', 'replace-tokens' ); 71 $content_plaintext = PHPMailer::normalizeBreaks( $email->get( 'content_plaintext', 'replace-tokens' ) ); 72 73 if ( $email->get( 'content_type' ) === 'html' ) { 74 $phpmailer->msgHTML( $email->get( 'template', 'add-content' ), '', 'wp_strip_all_tags' ); 75 $phpmailer->AltBody = $content_plaintext; 76 77 } else { 78 $phpmailer->IsHTML( false ); 79 $phpmailer->Body = $content_plaintext; 80 } 81 82 $recipient = $email->get( 'from' ); 83 try { 84 $phpmailer->SetFrom( $recipient->get_address(), $recipient->get_name() ); 85 } catch ( phpmailerException $e ) { 86 } 87 88 $recipient = $email->get( 'reply_to' ); 89 try { 90 $phpmailer->addReplyTo( $recipient->get_address(), $recipient->get_name() ); 91 } catch ( phpmailerException $e ) { 92 } 93 94 $recipients = $email->get( 'to' ); 95 foreach ( $recipients as $recipient ) { 96 try { 97 $phpmailer->AddAddress( $recipient->get_address(), $recipient->get_name() ); 98 } catch ( phpmailerException $e ) { 99 } 100 } 101 102 $recipients = $email->get( 'cc' ); 103 foreach ( $recipients as $recipient ) { 104 try { 105 $phpmailer->AddCc( $recipient->get_address(), $recipient->get_name() ); 106 } catch ( phpmailerException $e ) { 107 } 108 } 109 110 $recipients = $email->get( 'bcc' ); 111 foreach ( $recipients as $recipient ) { 112 try { 113 $phpmailer->AddBcc( $recipient->get_address(), $recipient->get_name() ); 114 } catch ( phpmailerException $e ) { 115 } 116 } 117 118 $headers = $email->get( 'headers' ); 119 foreach ( $headers as $name => $content ) { 120 $phpmailer->AddCustomHeader( $name, $content ); 121 } 122 123 124 /** 125 * Fires after PHPMailer is initialised. 126 * 127 * @since 2.5.0 128 * 129 * @param PHPMailer $phpmailer The PHPMailer instance. 130 */ 131 do_action( 'bp_phpmailer_init', $phpmailer ); 132 133 try { 134 return $phpmailer->Send(); 135 } catch ( phpmailerException $e ) { 136 return new WP_Error( $e->getCode(), $e->getMessage(), $email ); 137 } 138 } 139 140 141 /* 142 * Utility/helper functions. 143 */ 144 145 /** 146 * Get an appropriate hostname for the email. Varies depending on site configuration. 147 * 148 * @since 2.5.0 149 * 150 * @return string 151 */ 152 static public function get_hostname() { 153 if ( is_multisite() ) { 154 return get_current_site()->domain; // From fix_phpmailer_messageid() 155 } 156 157 return preg_replace( '#^https?://#i', '', bp_get_option( 'home' ) ); 158 } 159 } -
tests/phpunit/includes/install.php
diff --git a/tests/phpunit/includes/install.php b/tests/phpunit/includes/install.php index fd9495d..a003132 100644
a b $multisite = ! empty( $argv[3] ); 13 13 14 14 require_once $config_file_path; 15 15 require_once $tests_dir_path . '/includes/functions.php'; 16 require_once $tests_dir_path . '/includes/mock-mailer.php'; 16 17 17 18 function _load_buddypress() { 18 19 require dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/bp-loader.php'; … … foreach ( $wpdb->get_col( "SHOW TABLES LIKE '" . $wpdb->prefix . "bp%'" ) as $bp 48 49 $wpdb->query( "DROP TABLE {$bp_table}" ); 49 50 } 50 51 52 function _bp_mock_mailer( $class ) { 53 return 'BP_UnitTest_Mailer'; 54 } 55 tests_add_filter( 'bp_send_email_delivery_class', '_bp_mock_mailer' ); 56 51 57 // Install BuddyPress 52 58 bp_version_updater(); -
tests/phpunit/includes/loader.php
diff --git a/tests/phpunit/includes/loader.php b/tests/phpunit/includes/loader.php index 6f9b6d2..e6a4838 100644
a b system( WP_PHP_BINARY . ' ' . escapeshellarg( dirname( __FILE__ ) . '/install.ph 7 7 8 8 // Bootstrap BP 9 9 require dirname( __FILE__ ) . '/../../../src/bp-loader.php'; 10 11 require_once( dirname( __FILE__ ) . '/mock-mailer.php' ); 12 function _bp_mock_mailer( $class ) { 13 return 'BP_UnitTest_Mailer'; 14 } 15 tests_add_filter( 'bp_send_email_delivery_class', '_bp_mock_mailer' ); -
new file tests/phpunit/includes/mock-mailer.php
diff --git a/tests/phpunit/includes/mock-mailer.php b/tests/phpunit/includes/mock-mailer.php new file mode 100644 index 0000000..0e0d99e
- + 1 <?php 2 3 /** 4 * Mock email delivery implementation. 5 * 6 * @since 2.5.0 7 */ 8 class BP_UnitTest_Mailer implements BP_Email_Delivery { 9 10 /** 11 * Send email(s). 12 * 13 * @param BP_Email $email Email to send. 14 * @return bool False if some error occurred. 15 * @since 2.5.0 16 */ 17 public function bp_email( BP_Email $email ) { 18 return true; 19 } 20 } -
new file tests/phpunit/testcases/core/class-bp-email-recipient.php
diff --git a/tests/phpunit/testcases/core/class-bp-email-recipient.php b/tests/phpunit/testcases/core/class-bp-email-recipient.php new file mode 100644 index 0000000..052e677
- + 1 <?php 2 /** 3 * @group core 4 * @group BP_Email_Recipient 5 */ 6 class BP_Email_Recipient_Tests extends BP_UnitTestCase { 7 protected $u1; 8 9 public function setUp() { 10 parent::setUp(); 11 12 $this->u1 = $this->factory->user->create( array( 13 'display_name' => 'Unit Test', 14 'user_email' => 'test@example.com', 15 ) ); 16 } 17 18 public function test_return_with_address_and_name() { 19 $email = 'test@example.com'; 20 $name = 'Unit Test'; 21 $recipient = new BP_Email_Recipient( $email, $name ); 22 23 $this->assertSame( $email, $recipient->get_address() ); 24 $this->assertSame( $name, $recipient->get_name() ); 25 } 26 27 public function test_return_with_array() { 28 $email = 'test@example.com'; 29 $name = 'Unit Test'; 30 $recipient = new BP_Email_Recipient( array( $email => $name ) ); 31 32 $this->assertSame( $email, $recipient->get_address() ); 33 $this->assertSame( $name, $recipient->get_name() ); 34 } 35 36 public function test_return_with_user_id() { 37 $recipient = new BP_Email_Recipient( $this->u1 ); 38 39 $this->assertSame( 'test@example.com', $recipient->get_address() ); 40 $this->assertSame( 'Unit Test', $recipient->get_name() ); 41 } 42 43 public function test_return_with_wp_user_object() { 44 $recipient = new BP_Email_Recipient( get_user_by( 'id', $this->u1 ) ); 45 46 $this->assertSame( 'test@example.com', $recipient->get_address() ); 47 $this->assertSame( 'Unit Test', $recipient->get_name() ); 48 } 49 50 public function test_return_with_address_and_optional_name() { 51 $email = 'test@example.com'; 52 $recipient = new BP_Email_Recipient( $email ); 53 54 $this->assertSame( $email, $recipient->get_address() ); 55 $this->assertEmpty( $recipient->get_name() ); 56 } 57 58 public function test_return_with_array_and_optional_name() { 59 $email = 'test@example.com'; 60 $recipient = new BP_Email_Recipient( array( $email ) ); 61 62 $this->assertSame( $email, $recipient->get_address() ); 63 $this->assertEmpty( $recipient->get_name() ); 64 } 65 66 public function test_should_return_empty_string_if_user_id_id_invalid() { 67 $recipient = new BP_Email_Recipient( time() ); 68 69 $this->assertEmpty( $recipient->get_address() ); 70 $this->assertEmpty( $recipient->get_name() ); 71 } 72 73 public function test_get_wp_user_object_from_email_address() { 74 $recipient = new BP_Email_Recipient( 'test@example.com' ); 75 $recipient = $recipient->get_user( 'search-email' ); 76 77 $this->assertSame( $this->u1, $recipient->ID ); 78 $this->assertSame( 'test@example.com', $recipient->user_email ); 79 } 80 } -
new file tests/phpunit/testcases/core/class-bp-email.php
diff --git a/tests/phpunit/testcases/core/class-bp-email.php b/tests/phpunit/testcases/core/class-bp-email.php new file mode 100644 index 0000000..3eed772
- + 1 <?php 2 /** 3 * @group core 4 * @group BP_Email 5 */ 6 class BP_Tests_Email extends BP_UnitTestCase { 7 public function setUp() { 8 parent::setUp(); 9 remove_filter( 'bp_email_get_headers', 'bp_email_set_default_headers', 6, 4 ); 10 remove_filter( 'bp_email_get_tokens', 'bp_email_set_default_tokens', 6, 4 ); 11 } 12 13 public function tearDown() { 14 add_filter( 'bp_email_get_tokens', 'bp_email_set_default_tokens', 6, 4 ); 15 add_filter( 'bp_email_get_headers', 'bp_email_set_default_headers', 6, 4 ); 16 parent::tearDown(); 17 } 18 19 public function test_valid_subject() { 20 $message = 'test'; 21 $email = new BP_Email( 'fake_type' ); 22 23 $email->subject( $message ); 24 $this->assertSame( $message, $email->get( 'subject' ) ); 25 } 26 27 public function test_valid_html_content() { 28 $message = '<b>test</b>'; 29 $email = new BP_Email( 'fake_type' ); 30 31 $email->content_html( $message ); 32 $email->content_type( 'html' ); 33 34 $this->assertSame( $message, $email->get( 'content' ) ); 35 } 36 37 public function test_valid_plaintext_content() { 38 $message = 'test'; 39 $email = new BP_Email( 'fake_type' ); 40 41 $email->content_plaintext( $message ); 42 $email->content_type( 'plaintext' ); 43 44 $this->assertSame( $message, $email->get( 'content' ) ); 45 } 46 47 public function test_valid_template() { 48 $message = 'test'; 49 $email = new BP_Email( 'fake_type' ); 50 51 $email->template( $message ); 52 $this->assertSame( $message, $email->get( 'template' ) ); 53 } 54 55 public function test_tokens() { 56 $original = array( 'test1' => 'hello', 'test2' => 'world' ); 57 58 $email = new BP_Email( 'fake_type' ); 59 $email->tokens( $original ); 60 61 $this->assertSame( 62 array( 'test1', 'test2' ), 63 array_keys( $email->get( 'tokens' ) ) 64 ); 65 66 $this->assertSame( 67 array( 'hello', 'world' ), 68 array_values( $email->get( 'tokens' ) ) 69 ); 70 } 71 72 public function test_headers() { 73 $email = new BP_Email( 'fake_type' ); 74 75 $headers = array( 'custom_header' => 'custom_value' ); 76 $email->headers( $headers ); 77 $this->assertSame( $headers, $email->get( 'headers' ) ); 78 } 79 80 public function test_validation() { 81 $email = new BP_Email( 'fake_type' ); 82 $email->from( 'test1@example.com' )->to( 'test2@example.com' )->subject( 'testing' ); 83 $email->content_html( 'testing' ); 84 85 $this->assertTrue( $email->validate() ); 86 } 87 88 public function test_invalid_characters_are_stripped_from_tokens() { 89 $email = new BP_Email( 'fake_type' ); 90 $email->tokens( array( 'te{st}1' => 'hello world' ) ); 91 92 $this->assertSame( 93 array( 'test1' ), 94 array_keys( $email->get( 'tokens' ) ) 95 ); 96 } 97 98 public function test_token_are_escaped() { 99 $token = '<blink>'; 100 $email = new BP_Email( 'fake_type' ); 101 $email->content_html( '{{test}}' )->tokens( array( 'test' => $token ) ); 102 103 $this->assertSame( 104 esc_html( $token ), 105 $email->get( 'content', 'replace-tokens' ) 106 ); 107 } 108 109 public function test_token_are_not_escaped() { 110 $token = '<blink>'; 111 $email = new BP_Email( 'fake_type' ); 112 $email->content_html( '{{{test}}}' )->tokens( array( 'test' => $token ) ); 113 114 $this->assertSame( 115 $token, 116 $email->get( 'content', 'replace-tokens' ) 117 ); 118 } 119 120 public function test_invalid_headers() { 121 $email = new BP_Email( 'fake_type' ); 122 123 $headers = array( 'custom:header' => 'custom:value' ); 124 $email->headers( $headers ); 125 $this->assertNotSame( $headers, $email->get( 'headers' ) ); 126 $this->assertSame( array( 'customheader' => 'customvalue' ), $email->get( 'headers' ) ); 127 } 128 129 public function test_validation_with_missing_required_data() { 130 $email = new BP_Email( 'fake_type' ); 131 $email->from( 'test1@example.com' )->to( 'test2@example.com' )->subject( 'testing' ); // Content 132 $result = $email->validate(); 133 134 $this->assertTrue( is_wp_error( $result ) ); 135 $this->assertSame( 'missing_parameter', $result->get_error_code() ); 136 } 137 138 public function test_validation_with_missing_template() { 139 $email = new BP_Email( 'fake_type' ); 140 $email->from( 'test1@example.com' )->to( 'test2@example.com' )->subject( 'testing' ); 141 $email->content_html( 'testing' )->template( '' ); 142 $result = $email->validate(); 143 144 // Template has a default value, but it can't be blank. 145 $this->assertTrue( is_wp_error( $result ) ); 146 $this->assertSame( 'missing_parameter', $result->get_error_code() ); 147 } 148 149 public function test_invalid_tags_should_be_removed_from_html_content() { 150 $message = '<b>hello world</b><iframe src="https://example.com"></iframe><b>hello world</b>'; 151 $email = new BP_Email( 'fake_type' ); 152 153 $email->content_html( $message ); 154 $email->content_type( 'html' ); 155 156 $this->assertSame( '<b>hello world</b><b>hello world</b>', $email->get( 'content' ) ); 157 } 158 }