1<?php 2# MantisBT - A PHP based bugtracking system 3 4# MantisBT is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 2 of the License, or 7# (at your option) any later version. 8# 9# MantisBT is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with MantisBT. If not, see <http://www.gnu.org/licenses/>. 16 17/** 18 * Email API 19 * 20 * @package CoreAPI 21 * @subpackage EmailAPI 22 * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org 23 * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net 24 * @link http://www.mantisbt.org 25 * 26 * @uses access_api.php 27 * @uses authentication_api.php 28 * @uses bug_api.php 29 * @uses bugnote_api.php 30 * @uses category_api.php 31 * @uses config_api.php 32 * @uses constant_inc.php 33 * @uses current_user_api.php 34 * @uses custom_field_api.php 35 * @uses database_api.php 36 * @uses email_queue_api.php 37 * @uses event_api.php 38 * @uses helper_api.php 39 * @uses history_api.php 40 * @uses lang_api.php 41 * @uses logging_api.php 42 * @uses project_api.php 43 * @uses relationship_api.php 44 * @uses sponsorship_api.php 45 * @uses string_api.php 46 * @uses user_api.php 47 * @uses user_pref_api.php 48 * @uses utility_api.php 49 * 50 * @uses PHPMailerAutoload.php PHPMailer library 51 */ 52 53require_api( 'access_api.php' ); 54require_api( 'authentication_api.php' ); 55require_api( 'bug_api.php' ); 56require_api( 'bugnote_api.php' ); 57require_api( 'category_api.php' ); 58require_api( 'config_api.php' ); 59require_api( 'constant_inc.php' ); 60require_api( 'current_user_api.php' ); 61require_api( 'custom_field_api.php' ); 62require_api( 'database_api.php' ); 63require_api( 'email_queue_api.php' ); 64require_api( 'event_api.php' ); 65require_api( 'helper_api.php' ); 66require_api( 'history_api.php' ); 67require_api( 'lang_api.php' ); 68require_api( 'logging_api.php' ); 69require_api( 'project_api.php' ); 70require_api( 'relationship_api.php' ); 71require_api( 'sponsorship_api.php' ); 72require_api( 'string_api.php' ); 73require_api( 'user_api.php' ); 74require_api( 'user_pref_api.php' ); 75require_api( 'utility_api.php' ); 76 77use PHPMailer\PHPMailer\PHPMailer; 78use PHPMailer\PHPMailer\Exception as phpmailerException; 79use Mantis\Exceptions\ClientException; 80 81/** @global PHPMailer $g_phpMailer reusable PHPMailer object */ 82$g_phpMailer = null; 83 84/** 85 * Indicates how generated emails will be processed by the shutdown function 86 * at the end of the current request's execution; this is a binary flag: 87 * - EMAIL_SHUTDOWN_SKIP Initial state: do nothing (no generated emails) 88 * - EMAIL_SHUTDOWN_GENERATED Emails will be sent, unless $g_email_send_using_cronjob is ON 89 * - EMAIL_SHUTDOWN_FORCE All queued emails will be sent regardless of cronjob settings 90 * @see email_shutdown_function() 91 * @global $g_email_shutdown_processing 92 */ 93$g_email_shutdown_processing = EMAIL_SHUTDOWN_SKIP; 94 95/** 96 * Regex for valid email addresses 97 * @see string_insert_hrefs() 98 * This pattern is consistent with email addresses validation logic 99 * @see $g_validate_email 100 * Uses the standard HTML5 pattern defined in 101 * {@link http://www.w3.org/TR/html5/forms.html#valid-e-mail-address} 102 * Note: the original regex from the spec has been modified to 103 * - escape the '/' in the first character class definition 104 * - remove the '^' and '$' anchors to allow matching anywhere in a string 105 * - add a limit of 64 chars on local part to avoid timeouts on very long texts with false matches. 106 * 107 * @return string 108 */ 109function email_regex_simple() { 110 return "/[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]{1,64}@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/"; 111} 112 113/** 114 * check to see that the format is valid and that the mx record exists 115 * @param string $p_email An email address. 116 * @return boolean 117 */ 118function email_is_valid( $p_email ) { 119 $t_validate_email = config_get_global( 'validate_email' ); 120 121 # if we don't validate then just accept 122 # If blank email is allowed or current user is admin, then accept blank emails which are useful for 123 # accounts that should never receive email notifications (e.g. anonymous account) 124 if( OFF == $t_validate_email || 125 ON == config_get_global( 'use_ldap_email' ) || 126 ( is_blank( $p_email ) && ( ON == config_get( 'allow_blank_email' ) || current_user_is_administrator() ) ) 127 ) { 128 return true; 129 } 130 131 # E-mail validation method 132 # Note: PHPMailer offers alternative validation methods. 133 # It was decided in PR 172 (https://github.com/mantisbt/mantisbt/pull/172) 134 # to just default to HTML5 without over-complicating things for end users 135 # by offering a potentially confusing choice between the different methods. 136 # Refer to PHPMailer documentation for ValidateAddress method for details. 137 # @link https://github.com/PHPMailer/PHPMailer/blob/v5.2.9/class.phpmailer.php#L863 138 $t_method = 'html5'; 139 140 # check email address is a valid format 141 log_event( LOG_EMAIL_VERBOSE, "Validating address '$p_email' with method '$t_method'" ); 142 if( PHPMailer::validateAddress( $p_email, $t_method ) ) { 143 $t_domain = substr( $p_email, strpos( $p_email, '@' ) + 1 ); 144 145 # see if we're limited to a set of known domains 146 $t_limit_email_domains = config_get( 'limit_email_domains' ); 147 if( !empty( $t_limit_email_domains ) ) { 148 foreach( $t_limit_email_domains as $t_email_domain ) { 149 if( 0 == strcasecmp( $t_email_domain, $t_domain ) ) { 150 return true; # no need to check mx record details (below) if we've explicitly allowed the domain 151 } 152 } 153 log_event( LOG_EMAIL, "failed - not in limited domains list '$t_limit_email_domains'" ); 154 return false; 155 } 156 157 if( ON == config_get( 'check_mx_record' ) ) { 158 $t_mx = array(); 159 160 # Check for valid mx records 161 if( getmxrr( $t_domain, $t_mx ) ) { 162 return true; 163 } else { 164 $t_host = $t_domain . '.'; 165 166 # for no mx record... try dns check 167 if( checkdnsrr( $t_host, 'ANY' ) ) { 168 return true; 169 } 170 log_event( LOG_EMAIL, "failed - mx/dns record check" ); 171 } 172 } else { 173 # Email format was valid but didn't check for valid mx records 174 return true; 175 } 176 } else { 177 log_event( LOG_EMAIL, "failed - invalid address" ); 178 } 179 180 # Everything failed. The email is invalid 181 return false; 182} 183 184/** 185 * Check if the email address is valid trigger an ERROR if it isn't 186 * @param string $p_email An email address. 187 * @throws ClientException 188 * @return void 189 */ 190function email_ensure_valid( $p_email ) { 191 if( !email_is_valid( $p_email ) ) { 192 throw new ClientException( 193 sprintf( "Email '%s' is invalid.", $p_email ), 194 ERROR_EMAIL_INVALID ); 195 } 196} 197 198/** 199 * Check if the email address is disposable 200 * @param string $p_email An email address. 201 * @return boolean 202 */ 203function email_is_disposable( $p_email ) { 204 return \VBoctor\Email\DisposableEmailChecker::is_disposable_email( $p_email ); 205} 206 207/** 208 * Check if the email address is disposable 209 * trigger an ERROR if it isn't 210 * @param string $p_email An email address. 211 * @throws ClientException 212 * @return void 213 */ 214function email_ensure_not_disposable( $p_email ) { 215 if( email_is_disposable( $p_email ) ) { 216 throw new ClientException( 217 sprintf( "Email '%s' is disposable.", $p_email ), 218 ERROR_EMAIL_DISPOSABLE 219 ); 220 } 221} 222 223/** 224 * Get the value associated with the specific action and flag. 225 * For example, you can get the value associated with notifying "admin" 226 * on action "new", i.e. notify administrators on new bugs which can be 227 * ON or OFF. 228 * @param string $p_action Action. 229 * @param string $p_flag Flag. 230 * @return integer 1 - enabled, 0 - disabled. 231 */ 232function email_notify_flag( $p_action, $p_flag ) { 233 # If flag is specified for the specific event, use that. 234 $t_notify_flags = config_get( 'notify_flags' ); 235 if( isset( $t_notify_flags[$p_action][$p_flag] ) ) { 236 return $t_notify_flags[$p_action][$p_flag]; 237 } 238 239 # If not, then use the default if specified in database or global. 240 # Note that web UI may not support or specify all flags (e.g. explicit), 241 # hence, if config is retrieved from database it may not have the flag. 242 $t_default_notify_flags = config_get( 'default_notify_flags' ); 243 if( isset( $t_default_notify_flags[$p_flag] ) ) { 244 return $t_default_notify_flags[$p_flag]; 245 } 246 247 # If the flag is not specified so far, then force using global config which 248 # should have all flags specified. 249 $t_global_default_notify_flags = config_get_global( 'default_notify_flags' ); 250 if( isset( $t_global_default_notify_flags[$p_flag] ) ) { 251 return $t_global_default_notify_flags[$p_flag]; 252 } 253 254 return OFF; 255} 256 257/** 258 * Collect valid email recipients for email notification 259 * @todo yarick123: email_collect_recipients(...) will be completely rewritten to provide additional information such as language, user access,.. 260 * @todo yarick123:sort recipients list by language to reduce switches between different languages 261 * @param integer $p_bug_id A bug identifier. 262 * @param string $p_notify_type Notification type. 263 * @param array $p_extra_user_ids_to_email Array of additional email addresses to notify. 264 * @param integer $p_bugnote_id The bugnote id in case of bugnote, otherwise null. 265 * @return array 266 */ 267function email_collect_recipients( $p_bug_id, $p_notify_type, array $p_extra_user_ids_to_email = array(), $p_bugnote_id = null ) { 268 $t_recipients = array(); 269 270 # add explicitly specified users 271 $t_explicit_enabled = ( ON == email_notify_flag( $p_notify_type, 'explicit' ) ); 272 foreach ( $p_extra_user_ids_to_email as $t_user_id ) { 273 if ( $t_explicit_enabled ) { 274 $t_recipients[$t_user_id] = true; 275 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (explicitly specified)', $p_bug_id, $t_user_id ); 276 } else { 277 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, skip @U%d (explicit disabled)', $p_bug_id, $t_user_id ); 278 } 279 } 280 281 # add Reporter 282 $t_reporter_id = bug_get_field( $p_bug_id, 'reporter_id' ); 283 if( ON == email_notify_flag( $p_notify_type, 'reporter' ) ) { 284 $t_recipients[$t_reporter_id] = true; 285 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (reporter)', $p_bug_id, $t_reporter_id ); 286 } else { 287 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, skip @U%d (reporter disabled)', $p_bug_id, $t_reporter_id ); 288 } 289 290 # add Handler 291 $t_handler_id = bug_get_field( $p_bug_id, 'handler_id' ); 292 if( $t_handler_id > 0 ) { 293 if( ON == email_notify_flag( $p_notify_type, 'handler' ) ) { 294 $t_recipients[$t_handler_id] = true; 295 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (handler)', $p_bug_id, $t_handler_id ); 296 } else { 297 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, skip @U%d (handler disabled)', $p_bug_id, $t_handler_id ); 298 } 299 } 300 301 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 302 303 # add users monitoring the bug 304 $t_monitoring_enabled = ON == email_notify_flag( $p_notify_type, 'monitor' ); 305 db_param_push(); 306 $t_query = 'SELECT DISTINCT user_id FROM {bug_monitor} WHERE bug_id=' . db_param(); 307 $t_result = db_query( $t_query, array( $p_bug_id ) ); 308 309 while( $t_row = db_fetch_array( $t_result ) ) { 310 $t_user_id = $t_row['user_id']; 311 if ( $t_monitoring_enabled ) { 312 $t_recipients[$t_user_id] = true; 313 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (monitoring)', $p_bug_id, $t_user_id ); 314 } else { 315 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, skip @U%d (monitoring disabled)', $p_bug_id, $t_user_id ); 316 } 317 } 318 319 # add Category Owner 320 if( ON == email_notify_flag( $p_notify_type, 'category' ) ) { 321 $t_category_id = bug_get_field( $p_bug_id, 'category_id' ); 322 323 if( $t_category_id > 0 ) { 324 $t_category_assigned_to = category_get_field( $t_category_id, 'user_id' ); 325 326 if( $t_category_assigned_to > 0 ) { 327 $t_recipients[$t_category_assigned_to] = true; 328 log_event( LOG_EMAIL_RECIPIENT, sprintf( 'Issue = #%d, add Category Owner = @U%d', $p_bug_id, $t_category_assigned_to ) ); 329 } 330 } 331 } 332 333 # add users who contributed bugnotes 334 $t_notes_enabled = ( ON == email_notify_flag( $p_notify_type, 'bugnotes' ) ); 335 db_param_push(); 336 $t_query = 'SELECT DISTINCT reporter_id FROM {bugnote} WHERE bug_id = ' . db_param(); 337 $t_result = db_query( $t_query, array( $p_bug_id ) ); 338 while( $t_row = db_fetch_array( $t_result ) ) { 339 $t_user_id = $t_row['reporter_id']; 340 if ( $t_notes_enabled ) { 341 $t_recipients[$t_user_id] = true; 342 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (note author)', $p_bug_id, $t_user_id ); 343 } else { 344 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, skip @U%d (note author disabled)', $p_bug_id, $t_user_id ); 345 } 346 } 347 348 # add project users who meet the thresholds 349 $t_bug_is_private = bug_get_field( $p_bug_id, 'view_state' ) == VS_PRIVATE; 350 $t_threshold_min = email_notify_flag( $p_notify_type, 'threshold_min' ); 351 $t_threshold_max = email_notify_flag( $p_notify_type, 'threshold_max' ); 352 $t_threshold_users = project_get_all_user_rows( $t_project_id, $t_threshold_min ); 353 foreach( $t_threshold_users as $t_user ) { 354 if( $t_user['access_level'] <= $t_threshold_max ) { 355 if( !$t_bug_is_private || access_compare_level( $t_user['access_level'], config_get( 'private_bug_threshold' ) ) ) { 356 $t_recipients[$t_user['id']] = true; 357 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (based on access level)', $p_bug_id, $t_user['id'] ); 358 } 359 } 360 } 361 362 # add users as specified by plugins 363 $t_recipients_include_data = event_signal( 'EVENT_NOTIFY_USER_INCLUDE', array( $p_bug_id, $p_notify_type ) ); 364 foreach( $t_recipients_include_data as $t_plugin => $t_recipients_include_data2 ) { 365 foreach( $t_recipients_include_data2 as $t_callback => $t_recipients_included ) { 366 # only handle if we get an array from the callback 367 if( is_array( $t_recipients_included ) ) { 368 foreach( $t_recipients_included as $t_user_id ) { 369 $t_recipients[$t_user_id] = true; 370 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, add @U%d (by %s plugin)', $p_bug_id, $t_user_id, $t_plugin ); 371 } 372 } 373 } 374 } 375 376 # FIXME: the value of $p_notify_type could at this stage be either a status 377 # or a built-in actions such as 'owner and 'sponsor'. We have absolutely no 378 # idea whether 'new' is indicating a new bug has been filed, or if the 379 # status of an existing bug has been changed to 'new'. Therefore it is best 380 # to just assume built-in actions have precedence over status changes. 381 switch( $p_notify_type ) { 382 case 'new': 383 case 'feedback': # This isn't really a built-in action (delete me!) 384 case 'reopened': 385 case 'resolved': 386 case 'closed': 387 case 'bugnote': 388 $t_pref_field = 'email_on_' . $p_notify_type; 389 if( !$p_bugnote_id ) { 390 $p_bugnote_id = bugnote_get_latest_id( $p_bug_id ); 391 } 392 break; 393 case 'owner': 394 # The email_on_assigned notification type is now effectively 395 # email_on_change_of_handler. 396 $t_pref_field = 'email_on_assigned'; 397 break; 398 case 'deleted': 399 case 'updated': 400 case 'sponsor': 401 case 'relation': 402 case 'monitor': 403 case 'priority': # This is never used, but exists in the database! 404 # Issue #19459 these notification actions are not actually implemented 405 # in the database and therefore aren't adjustable on a per-user 406 # basis! The exception is 'monitor' that makes no sense being a 407 # customisable per-user preference. 408 default: 409 # Anything not built-in is probably going to be a status 410 $t_pref_field = 'email_on_status'; 411 break; 412 } 413 414 # @@@ we could optimize by modifiying user_cache() to take an array 415 # of user ids so we could pull them all in. We'll see if it's necessary 416 $t_final_recipients = array(); 417 418 $t_bug = bug_get( $p_bug_id ); 419 $t_user_ids = array_keys( $t_recipients ); 420 user_cache_array_rows( $t_user_ids ); 421 user_pref_cache_array_rows( $t_user_ids ); 422 user_pref_cache_array_rows( $t_user_ids, $t_bug->project_id ); 423 424 # Check whether users should receive the emails 425 # and put email address to $t_recipients[user_id] 426 foreach( $t_recipients as $t_id => $t_ignore ) { 427 # Possibly eliminate the current user 428 if( ( auth_get_current_user_id() == $t_id ) && ( OFF == config_get( 'email_receive_own' ) ) ) { 429 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (own action)', $p_bug_id, $t_id ); 430 continue; 431 } 432 433 # Eliminate users who don't exist anymore or who are disabled 434 if( !user_exists( $t_id ) || !user_is_enabled( $t_id ) ) { 435 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (user disabled)', $p_bug_id, $t_id ); 436 continue; 437 } 438 439 # Exclude users who have this notification type turned off 440 if( $t_pref_field ) { 441 $t_notify = user_pref_get_pref( $t_id, $t_pref_field ); 442 if( OFF == $t_notify ) { 443 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (pref %s off)', $p_bug_id, $t_id, $t_pref_field ); 444 continue; 445 } else { 446 # Users can define the severity of an issue before they are emailed for 447 # each type of notification 448 $t_min_sev_pref_field = $t_pref_field . '_min_severity'; 449 $t_min_sev_notify = user_pref_get_pref( $t_id, $t_min_sev_pref_field ); 450 $t_bug_severity = bug_get_field( $p_bug_id, 'severity' ); 451 452 if( $t_bug_severity < $t_min_sev_notify ) { 453 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (pref threshold)', $p_bug_id, $t_id ); 454 continue; 455 } 456 } 457 } 458 459 # exclude users who don't have at least viewer access to the bug, 460 # or who can't see bugnotes if the last update included a bugnote 461 $t_view_bug_threshold = config_get( 'view_bug_threshold', null, $t_id, $t_bug->project_id ); 462 if( !access_has_bug_level( $t_view_bug_threshold, $p_bug_id, $t_id ) 463 || ( $p_bugnote_id 464 && !access_has_bugnote_level( $t_view_bug_threshold, $p_bugnote_id, $t_id ) 465 ) 466 ) { 467 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (access level)', $p_bug_id, $t_id ); 468 continue; 469 } 470 471 # check to exclude users as specified by plugins 472 $t_recipient_exclude_data = event_signal( 'EVENT_NOTIFY_USER_EXCLUDE', array( $p_bug_id, $p_notify_type, $t_id ) ); 473 $t_exclude = false; 474 foreach( $t_recipient_exclude_data as $t_plugin => $t_recipient_exclude_data2 ) { 475 foreach( $t_recipient_exclude_data2 as $t_callback => $t_recipient_excluded ) { 476 # exclude if any plugin returns true (excludes the user) 477 if( $t_recipient_excluded ) { 478 $t_exclude = true; 479 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (by %s plugin)', $p_bug_id, $t_id, $t_plugin ); 480 } 481 } 482 } 483 484 # user was excluded by a plugin 485 if( $t_exclude ) { 486 continue; 487 } 488 489 # Finally, let's get their emails, if they've set one 490 $t_email = user_get_email( $t_id ); 491 if( is_blank( $t_email ) ) { 492 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, drop @U%d (no email address)', $p_bug_id, $t_id ); 493 } else { 494 # @@@ we could check the emails for validity again but I think 495 # it would be too slow 496 $t_final_recipients[$t_id] = $t_email; 497 } 498 } 499 500 return $t_final_recipients; 501} 502 503/** 504 * Send password to user 505 * @param integer $p_user_id A valid user identifier. 506 * @param string $p_confirm_hash Confirmation hash. 507 * @param string $p_admin_name Administrator name. 508 * @return void 509 */ 510function email_signup( $p_user_id, $p_confirm_hash, $p_admin_name = '' ) { 511 if( ( OFF == config_get( 'send_reset_password' ) ) || ( OFF == config_get( 'enable_email_notification' ) ) ) { 512 return; 513 } 514 515 # @@@ thraxisp - removed to address #6084 - user won't have any settings yet, 516 # use same language as display for the email 517 # lang_push( user_pref_get_language( $p_user_id ) ); 518 # retrieve the username and email 519 $t_username = user_get_username( $p_user_id ); 520 $t_email = user_get_email( $p_user_id ); 521 522 # Build Welcome Message 523 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' ); 524 525 if( !empty( $p_admin_name ) ) { 526 $t_intro_text = sprintf( lang_get( 'new_account_greeting_admincreated' ), $p_admin_name, $t_username ); 527 } else { 528 $t_intro_text = sprintf( lang_get( 'new_account_greeting' ), $t_username ); 529 } 530 531 $t_message = $t_intro_text . "\n\n" . string_get_confirm_hash_url( $p_user_id, $p_confirm_hash ) . "\n\n" . lang_get( 'new_account_message' ) . "\n\n" . lang_get( 'new_account_do_not_reply' ); 532 533 # Send signup email regardless of mail notification pref 534 # or else users won't be able to sign up 535 if( !is_blank( $t_email ) ) { 536 email_store( $t_email, $t_subject, $t_message, null, true ); 537 log_event( LOG_EMAIL, 'Signup Email = %s, Hash = %s, User = @U%d', $t_email, $p_confirm_hash, $p_user_id ); 538 } 539 540 # lang_pop(); # see above 541} 542 543/** 544 * Send confirm_hash URL to let user reset their password. 545 * 546 * @param integer $p_user_id A valid user identifier. 547 * @param string $p_confirm_hash Confirmation hash. 548 * @param bool $p_reset_by_admin True if password was reset by admin, 549 * False (default) for user request (lost password) 550 * 551 * @return void 552 */ 553function email_send_confirm_hash_url( $p_user_id, $p_confirm_hash, $p_reset_by_admin = false ) { 554 if( OFF == config_get( 'send_reset_password' ) ) { 555 log_event( LOG_EMAIL_VERBOSE, 'Password reset email notifications disabled.' ); 556 return; 557 } 558 if( OFF == config_get( 'enable_email_notification' ) ) { 559 log_event( LOG_EMAIL_VERBOSE, 'email notifications disabled.' ); 560 return; 561 } 562 if( !user_is_enabled( $p_user_id ) ) { 563 log_event( LOG_EMAIL, 'Password reset for user @U%d not sent, user is disabled', $p_user_id ); 564 return; 565 } 566 lang_push( user_pref_get_language( $p_user_id ) ); 567 568 # retrieve the username and email 569 $t_username = user_get_username( $p_user_id ); 570 $t_email = user_get_email( $p_user_id ); 571 572 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'lost_password_subject' ); 573 574 if( $p_reset_by_admin ) { 575 $t_message = lang_get( 'reset_request_admin_msg' ); 576 } else { 577 $t_message = lang_get( 'reset_request_msg' ); 578 } 579 $t_message .= "\n\n" 580 . string_get_confirm_hash_url( $p_user_id, $p_confirm_hash ) . "\n\n" 581 . lang_get( 'new_account_username' ) . ' ' . $t_username . "\n" 582 . lang_get( 'new_account_IP' ) . ' ' . $_SERVER['REMOTE_ADDR'] . "\n\n" 583 . lang_get( 'new_account_do_not_reply' ); 584 585 # Send password reset regardless of mail notification preferences 586 # or else users won't be able to receive their reset passwords 587 if( !is_blank( $t_email ) ) { 588 email_store( $t_email, $t_subject, $t_message, null, true ); 589 log_event( LOG_EMAIL, 'Password reset for user @U%d sent to %s', $p_user_id, $t_email ); 590 } else { 591 log_event( LOG_EMAIL, 'Password reset for user @U%d not sent, email is empty', $p_user_id ); 592 } 593 594 lang_pop(); 595} 596 597/** 598 * notify the selected group a new user has signup 599 * @param string $p_username Username of new user. 600 * @param string $p_email Email address of new user. 601 * @return void 602 */ 603function email_notify_new_account( $p_username, $p_email ) { 604 log_event( LOG_EMAIL, 'New account for user %s', $p_username ); 605 606 $t_threshold_min = config_get( 'notify_new_user_created_threshold_min' ); 607 $t_threshold_users = project_get_all_user_rows( ALL_PROJECTS, $t_threshold_min ); 608 $t_user_ids = array_keys( $t_threshold_users ); 609 user_cache_array_rows( $t_user_ids ); 610 user_pref_cache_array_rows( $t_user_ids ); 611 612 foreach( $t_threshold_users as $t_user ) { 613 lang_push( user_pref_get_language( $t_user['id'] ) ); 614 615 $t_recipient_email = user_get_email( $t_user['id'] ); 616 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' ); 617 618 $t_message = lang_get( 'new_account_signup_msg' ) . "\n\n" . lang_get( 'new_account_username' ) . ' ' . $p_username . "\n" . lang_get( 'new_account_email' ) . ' ' . $p_email . "\n" . lang_get( 'new_account_IP' ) . ' ' . $_SERVER['REMOTE_ADDR'] . "\n" . config_get_global( 'path' ) . "\n\n" . lang_get( 'new_account_do_not_reply' ); 619 620 if( !is_blank( $t_recipient_email ) ) { 621 email_store( $t_recipient_email, $t_subject, $t_message ); 622 log_event( LOG_EMAIL, 'New Account Notify for email = \'%s\'', $t_recipient_email ); 623 } 624 625 lang_pop(); 626 } 627} 628 629 630/** 631 * send a generic email 632 * $p_notify_type: use check who she get notified of such event. 633 * $p_message_id: message id to be translated and included at the top of the email message. 634 * Return false if it were problems sending email 635 * @param integer $p_bug_id A bug identifier. 636 * @param string $p_notify_type Notification type. 637 * @param integer $p_message_id Message identifier. 638 * @param array $p_header_optional_params Optional Parameters (default null). 639 * @param array $p_extra_user_ids_to_email Array of additional users to email. 640 * @return void 641 */ 642function email_generic( $p_bug_id, $p_notify_type, $p_message_id = null, array $p_header_optional_params = null, array $p_extra_user_ids_to_email = array() ) { 643 # @todo yarick123: email_collect_recipients(...) will be completely rewritten to provide additional information such as language, user access,.. 644 # @todo yarick123:sort recipients list by language to reduce switches between different languages 645 $t_recipients = email_collect_recipients( $p_bug_id, $p_notify_type, $p_extra_user_ids_to_email ); 646 email_generic_to_recipients( $p_bug_id, $p_notify_type, $t_recipients, $p_message_id, $p_header_optional_params ); 647} 648 649/** 650 * Sends a generic email to the specific set of recipients. 651 * 652 * @param integer $p_bug_id A bug identifier 653 * @param string $p_notify_type Notification type 654 * @param array $p_recipients Array of recipients (key: user id, value: email address) 655 * @param integer $p_message_id Message identifier 656 * @param array $p_header_optional_params Optional Parameters (default null) 657 * @return void 658 */ 659function email_generic_to_recipients( $p_bug_id, $p_notify_type, array $p_recipients, $p_message_id = null, array $p_header_optional_params = null ) { 660 if( empty( $p_recipients ) ) { 661 return; 662 } 663 664 if( OFF == config_get( 'enable_email_notification' ) ) { 665 return; 666 } 667 668 ignore_user_abort( true ); 669 670 bugnote_get_all_bugnotes( $p_bug_id ); 671 672 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 673 674 if( is_array( $p_recipients ) ) { 675 # send email to every recipient 676 foreach( $p_recipients as $t_user_id => $t_user_email ) { 677 log_event( LOG_EMAIL_VERBOSE, 'Issue = #%d, Type = %s, Msg = \'%s\', User = @U%d, Email = \'%s\'.', $p_bug_id, $p_notify_type, $p_message_id, $t_user_id, $t_user_email ); 678 679 # load (push) user language here as build_visible_bug_data assumes current language 680 lang_push( user_pref_get_language( $t_user_id, $t_project_id ) ); 681 682 $t_visible_bug_data = email_build_visible_bug_data( $t_user_id, $p_bug_id, $p_message_id ); 683 email_bug_info_to_one_user( $t_visible_bug_data, $p_message_id, $t_user_id, $p_header_optional_params ); 684 685 lang_pop(); 686 } 687 } 688} 689 690/** 691 * Send notices that a user is now monitoring the bug. Typically this will only be sent when the added 692 * user is not the logged in user. This is assuming that receive own notifications is OFF (default). 693 * @param integer $p_bug_id A valid bug identifier. 694 * @param integer $p_user_id A valid user identifier. 695 * @return void 696 */ 697function email_monitor_added( $p_bug_id, $p_user_id ) { 698 log_event( LOG_EMAIL, 'Issue #%d monitored by user @U%d', $p_bug_id, $p_user_id ); 699 700 $t_opt = array(); 701 $t_opt[] = bug_format_id( $p_bug_id ); 702 $t_opt[] = user_get_name( $p_user_id ); 703 704 email_generic( $p_bug_id, 'monitor', 'email_notification_title_for_action_monitor', $t_opt, array( $p_user_id ) ); 705} 706 707/** 708 * send notices when a relationship is ADDED 709 * @param integer $p_bug_id A bug identifier. 710 * @param integer $p_related_bug_id Related bug identifier. 711 * @param integer $p_rel_type Relationship type. 712 * @param bool $p_email_for_source Should an email be triggered for source issue? 713 * @return void 714 */ 715function email_relationship_added( $p_bug_id, $p_related_bug_id, $p_rel_type, $p_email_for_source ) { 716 global $g_relationships; 717 718 if( !isset( $g_relationships[$p_rel_type] ) ) { 719 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR ); 720 } 721 722 $t_rev_rel_type = relationship_get_complementary_type( $p_rel_type ); 723 if( !isset( $g_relationships[$t_rev_rel_type] ) ) { 724 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR ); 725 } 726 727 log_event( 728 LOG_EMAIL, 729 'Issue #%d relationship added to issue #%d (relationship type %s)', 730 $p_bug_id, 731 $p_related_bug_id, 732 $g_relationships[$p_rel_type]['#description'] ); 733 734 # Source issue email notification 735 if( $p_email_for_source ) { 736 $t_recipients = email_collect_recipients( $p_bug_id, 'relation' ); 737 738 # Recipient has to have access to both bugs to get the notification. 739 $t_recipients = email_filter_recipients_for_bug( $p_bug_id, $t_recipients ); 740 $t_recipients = email_filter_recipients_for_bug( $p_related_bug_id, $t_recipients ); 741 742 $t_opt = array(); 743 $t_opt[] = bug_format_id( $p_related_bug_id ); 744 745 email_generic_to_recipients( 746 $p_bug_id, 'relation', $t_recipients, $g_relationships[$p_rel_type]['#notify_added'], $t_opt ); 747 } 748 749 # Destination issue email notification 750 $t_recipients = email_collect_recipients( $p_related_bug_id, 'relation' ); 751 752 # Recipient has to have access to both bugs to get the notification. 753 $t_recipients = email_filter_recipients_for_bug( $p_bug_id, $t_recipients ); 754 $t_recipients = email_filter_recipients_for_bug( $p_related_bug_id, $t_recipients ); 755 756 $t_opt = array(); 757 $t_opt[] = bug_format_id( $p_bug_id ); 758 email_generic_to_recipients( 759 $p_related_bug_id, 'relation', $t_recipients, $g_relationships[$t_rev_rel_type]['#notify_added'], $t_opt ); 760} 761 762/** 763 * Filter recipients to remove ones that don't have access to the specified bug. 764 * 765 * @param integer $p_bug_id The bug id 766 * @param array $p_recipients The recipients array (key: id, value: email) 767 * @return array The filtered list of recipients in same format 768 * @access private 769 */ 770function email_filter_recipients_for_bug( $p_bug_id, array $p_recipients ) { 771 $t_view_bug_threshold = config_get( 'view_bug_threshold' ); 772 773 $t_authorized_recipients = array(); 774 775 foreach( $p_recipients as $t_recipient_id => $t_recipient_email ) { 776 if( access_has_bug_level( $t_view_bug_threshold, $p_bug_id, $t_recipient_id ) ) { 777 $t_authorized_recipients[$t_recipient_id] = $t_recipient_email; 778 } 779 } 780 781 return $t_authorized_recipients; 782} 783 784/** 785 * send notices when a relationship is DELETED 786 * @param integer $p_bug_id A bug identifier. 787 * @param integer $p_related_bug_id Related bug identifier. 788 * @param integer $p_rel_type Relationship type. 789 * @param integer $p_skip_email_for_issue_id Skip email for specified issue, otherwise 0. 790 * @return void 791 */ 792function email_relationship_deleted( $p_bug_id, $p_related_bug_id, $p_rel_type, $p_skip_email_for_issue_id = 0 ) { 793 global $g_relationships; 794 if( !isset( $g_relationships[$p_rel_type] ) ) { 795 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR ); 796 } 797 798 $t_rev_rel_type = relationship_get_complementary_type( $p_rel_type ); 799 if( !isset( $g_relationships[$t_rev_rel_type] ) ) { 800 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR ); 801 } 802 803 log_event( 804 LOG_EMAIL, 805 'Issue #%d relationship to issue #%d (relationship type %s) deleted.', 806 $p_bug_id, 807 $p_related_bug_id, 808 $g_relationships[$p_rel_type]['#description'] ); 809 810 if( $p_bug_id != $p_skip_email_for_issue_id ) { 811 $t_recipients = email_collect_recipients( $p_bug_id, 'relation' ); 812 813 # Recipient has to have access to both bugs to get the notification. 814 $t_recipients = email_filter_recipients_for_bug( $p_bug_id, $t_recipients ); 815 $t_recipients = email_filter_recipients_for_bug( $p_related_bug_id, $t_recipients ); 816 817 $t_opt = array(); 818 $t_opt[] = bug_format_id( $p_related_bug_id ); 819 email_generic_to_recipients( 820 $p_bug_id, 821 'relation', 822 $t_recipients, 823 $g_relationships[$p_rel_type]['#notify_deleted'], 824 $t_opt ); 825 } 826 827 if( $p_bug_id != $p_related_bug_id && bug_exists( $p_related_bug_id) ) { 828 $t_recipients = email_collect_recipients( $p_related_bug_id, 'relation' ); 829 830 # Recipient has to have access to both bugs to get the notification. 831 $t_recipients = email_filter_recipients_for_bug( $p_bug_id, $t_recipients ); 832 $t_recipients = email_filter_recipients_for_bug( $p_related_bug_id, $t_recipients ); 833 834 $t_opt = array(); 835 $t_opt[] = bug_format_id( $p_bug_id ); 836 email_generic_to_recipients( 837 $p_related_bug_id, 838 'relation', 839 $t_recipients, 840 $g_relationships[$t_rev_rel_type]['#notify_deleted'], 841 $t_opt ); 842 } 843} 844 845/** 846 * Email related issues when a bug is deleted. This should be deleted before the bug is deleted. 847 * 848 * @param integer $p_bug_id The id of the bug to be deleted. 849 * @return void 850 */ 851function email_relationship_bug_deleted( $p_bug_id ) { 852 $t_ignore = false; 853 $t_relationships = relationship_get_all( $p_bug_id, $t_ignore ); 854 if( empty( $t_relationships ) ) { 855 return; 856 } 857 858 log_event( LOG_EMAIL, sprintf( 'Issue #%d has been deleted, sending notifications to related issues', $p_bug_id ) ); 859 860 foreach( $t_relationships as $t_relationship ) { 861 $t_related_bug_id = $p_bug_id == $t_relationship->src_bug_id ? 862 $t_relationship->dest_bug_id : $t_relationship->src_bug_id; 863 864 $t_opt = array(); 865 $t_opt[] = bug_format_id( $p_bug_id ); 866 email_generic( $t_related_bug_id, 'handler', 'email_notification_title_for_action_related_issue_deleted', $t_opt ); 867 } 868} 869 870/** 871 * send notices to all the handlers of the parent bugs when a child bug is RESOLVED 872 * @param integer $p_bug_id A bug identifier. 873 * @return void 874 */ 875function email_relationship_child_resolved( $p_bug_id ) { 876 email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_resolved' ); 877} 878 879/** 880 * send notices to all the handlers of the parent bugs when a child bug is CLOSED 881 * @param integer $p_bug_id A bug identifier. 882 * @return void 883 */ 884function email_relationship_child_closed( $p_bug_id ) { 885 email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_closed' ); 886} 887 888/** 889 * send notices to all the handlers of the parent bugs still open when a child bug is resolved/closed 890 * 891 * @param integer $p_bug_id A bug identifier. 892 * @param integer $p_message_id A message identifier. 893 * @return void 894 */ 895function email_relationship_child_resolved_closed( $p_bug_id, $p_message_id ) { 896 # retrieve all the relationships in which the bug is the destination bug 897 $t_relationship = relationship_get_all_dest( $p_bug_id ); 898 $t_relationship_count = count( $t_relationship ); 899 if( $t_relationship_count == 0 ) { 900 # no parent bug found 901 return; 902 } 903 904 if( $p_message_id == 'email_notification_title_for_action_relationship_child_closed' ) { 905 log_event( LOG_EMAIL, sprintf( 'Issue #%d child issue closed', $p_bug_id ) ); 906 } else { 907 log_event( LOG_EMAIL, sprintf( 'Issue #%d child issue resolved', $p_bug_id ) ); 908 } 909 910 for( $i = 0;$i < $t_relationship_count;$i++ ) { 911 if( $t_relationship[$i]->type == BUG_DEPENDANT ) { 912 $t_src_bug_id = $t_relationship[$i]->src_bug_id; 913 $t_status = bug_get_field( $t_src_bug_id, 'status' ); 914 if( $t_status < config_get( 'bug_resolved_status_threshold' ) ) { 915 916 # sent the notification just for parent bugs not resolved/closed 917 $t_opt = array(); 918 $t_opt[] = bug_format_id( $p_bug_id ); 919 email_generic( $t_src_bug_id, 'handler', $p_message_id, $t_opt ); 920 } 921 } 922 } 923} 924 925/** 926 * send notices when a bug is sponsored 927 * @param int $p_bug_id 928 * @return void 929 */ 930function email_sponsorship_added( $p_bug_id ) { 931 log_event( LOG_EMAIL, sprintf( 'Issue #%d sponsorship added', $p_bug_id ) ); 932 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_added' ); 933} 934 935/** 936 * send notices when a sponsorship is modified 937 * @param int $p_bug_id 938 * @return void 939 */ 940function email_sponsorship_updated( $p_bug_id ) { 941 log_event( LOG_EMAIL, sprintf( 'Issue #%d sponsorship updated', $p_bug_id ) ); 942 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_updated' ); 943} 944 945/** 946 * send notices when a sponsorship is deleted 947 * @param int $p_bug_id 948 * @return void 949 */ 950function email_sponsorship_deleted( $p_bug_id ) { 951 log_event( LOG_EMAIL, sprintf( 'Issue #%d sponsorship removed', $p_bug_id ) ); 952 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_deleted' ); 953} 954 955/** 956 * send notices when a new bug is added 957 * @param int $p_bug_id 958 * @return void 959 */ 960function email_bug_added( $p_bug_id ) { 961 log_event( LOG_EMAIL, sprintf( 'Issue #%d reported', $p_bug_id ) ); 962 email_generic( $p_bug_id, 'new', 'email_notification_title_for_action_bug_submitted' ); 963} 964 965/** 966 * Send notifications for bug update. 967 * @param int $p_bug_id The bug id. 968 * @return void 969 */ 970function email_bug_updated( $p_bug_id ) { 971 log_event( LOG_EMAIL, sprintf( 'Issue #%d updated', $p_bug_id ) ); 972 email_generic( $p_bug_id, 'updated', 'email_notification_title_for_action_bug_updated' ); 973} 974 975/** 976 * send notices when a new bugnote 977 * @param int $p_bugnote_id The bugnote id. 978 * @param array $p_files The array of file information (keys: name, size) 979 * @param array $p_exclude_user_ids The id of users to exclude. 980 * @return void 981 */ 982function email_bugnote_add( $p_bugnote_id, $p_files = array(), $p_exclude_user_ids = array() ) { 983 if( OFF == config_get( 'enable_email_notification' ) ) { 984 log_event( LOG_EMAIL_VERBOSE, 'email notifications disabled.' ); 985 return; 986 } 987 988 ignore_user_abort( true ); 989 990 $t_bugnote = bugnote_get( $p_bugnote_id ); 991 992 log_event( LOG_EMAIL, sprintf( 'Note ~%d added to issue #%d', $p_bugnote_id, $t_bugnote->bug_id ) ); 993 994 $t_project_id = bug_get_field( $t_bugnote->bug_id, 'project_id' ); 995 $t_separator = config_get( 'email_separator2' ); 996 $t_time_tracking_access_threshold = config_get( 'time_tracking_view_threshold' ); 997 $t_view_attachments_threshold = config_get( 'view_attachments_threshold' ); 998 $t_message_id = 'email_notification_title_for_action_bugnote_submitted'; 999 1000 $t_subject = email_build_subject( $t_bugnote->bug_id ); 1001 1002 $t_recipients = email_collect_recipients( $t_bugnote->bug_id, 'bugnote', /* extra_user_ids */ array(), $p_bugnote_id ); 1003 $t_recipients_verbose = array(); 1004 1005 # send email to every recipient 1006 foreach( $t_recipients as $t_user_id => $t_user_email ) { 1007 if( in_array( $t_user_id, $p_exclude_user_ids ) ) { 1008 log_event( LOG_EMAIL_RECIPIENT, 'Issue = #%d, Note = ~%d, Type = %s, Msg = \'%s\', User = @U%d excluded, Email = \'%s\'.', 1009 $t_bugnote->bug_id, $p_bugnote_id, 'bugnote', 'email_notification_title_for_action_bugnote_submitted', $t_user_id, $t_user_email ); 1010 continue; 1011 } 1012 1013 # Load this here per user to allow overriding this per user, or even per user per project 1014 if( config_get( 'email_notifications_verbose', /* default */ null, $t_user_id, $t_project_id ) == ON ) { 1015 $t_recipients_verbose[$t_user_id] = $t_user_email; 1016 continue; 1017 } 1018 1019 log_event( LOG_EMAIL_VERBOSE, 'Issue = #%d, Note = ~%d, Type = %s, Msg = \'%s\', User = @U%d, Email = \'%s\'.', 1020 $t_bugnote->bug_id, $p_bugnote_id, 'bugnote', $t_message_id, $t_user_id, $t_user_email ); 1021 1022 # load (push) user language 1023 lang_push( user_pref_get_language( $t_user_id, $t_project_id ) ); 1024 1025 $t_message = lang_get( 'email_notification_title_for_action_bugnote_submitted' ) . "\n\n"; 1026 1027 $t_show_time_tracking = access_has_bug_level( $t_time_tracking_access_threshold, $t_bugnote->bug_id, $t_user_id ); 1028 $t_formatted_note = email_format_bugnote( $t_bugnote, $t_project_id, $t_show_time_tracking, $t_separator ); 1029 $t_message .= trim( $t_formatted_note ) . "\n"; 1030 $t_message .= $t_separator . "\n"; 1031 1032 # Files attached 1033 if( count( $p_files ) > 0 && 1034 access_has_bug_level( $t_view_attachments_threshold, $t_bugnote->bug_id, $t_user_id ) ) { 1035 $t_message .= lang_get( 'bugnote_attached_files' ) . "\n"; 1036 1037 foreach( $p_files as $t_file ) { 1038 $t_message .= '- ' . $t_file['name'] . ' (' . number_format( $t_file['size'] ) . 1039 ' ' . lang_get( 'bytes' ) . ")\n"; 1040 } 1041 1042 $t_message .= $t_separator . "\n"; 1043 } 1044 1045 $t_contents = $t_message . "\n"; 1046 1047 email_store( $t_user_email, $t_subject, $t_contents ); 1048 1049 log_event( LOG_EMAIL_VERBOSE, 'queued bugnote email for note ~' . $p_bugnote_id . 1050 ' issue #' . $t_bugnote->bug_id . ' by U' . $t_user_id ); 1051 1052 lang_pop(); 1053 } 1054 1055 # Send emails out for users that select verbose notifications 1056 email_generic_to_recipients( 1057 $t_bugnote->bug_id, 1058 'bugnote', 1059 $t_recipients_verbose, 1060 $t_message_id ); 1061} 1062 1063/** 1064 * send notices when a bug is RESOLVED 1065 * @param int $p_bug_id 1066 * @return void 1067 */ 1068function email_resolved( $p_bug_id ) { 1069 log_event( LOG_EMAIL, sprintf( 'Issue #%d resolved', $p_bug_id ) ); 1070 email_generic( $p_bug_id, 'resolved', 'email_notification_title_for_status_bug_resolved' ); 1071} 1072 1073/** 1074 * send notices when a bug is CLOSED 1075 * @param int $p_bug_id 1076 * @return void 1077 */ 1078function email_close( $p_bug_id ) { 1079 log_event( LOG_EMAIL, sprintf( 'Issue #%d closed', $p_bug_id ) ); 1080 email_generic( $p_bug_id, 'closed', 'email_notification_title_for_status_bug_closed' ); 1081} 1082 1083/** 1084 * send notices when a bug is REOPENED 1085 * @param int $p_bug_id 1086 * @return void 1087 */ 1088function email_bug_reopened( $p_bug_id ) { 1089 log_event( LOG_EMAIL, sprintf( 'Issue #%d reopened', $p_bug_id ) ); 1090 email_generic( $p_bug_id, 'reopened', 'email_notification_title_for_action_bug_reopened' ); 1091} 1092 1093/** 1094 * Send notices when a bug handler is changed. 1095 * @param int $p_bug_id 1096 * @param int $p_prev_handler_id 1097 * @param int $p_new_handler_id 1098 * @return void 1099 */ 1100function email_owner_changed($p_bug_id, $p_prev_handler_id, $p_new_handler_id ) { 1101 if ( $p_prev_handler_id == 0 && $p_new_handler_id != 0 ) { 1102 log_event( LOG_EMAIL, sprintf( 'Issue #%d assigned to user @U%d.', $p_bug_id, $p_new_handler_id ) ); 1103 } else if ( $p_prev_handler_id != 0 && $p_new_handler_id == 0 ) { 1104 log_event( LOG_EMAIL, sprintf( 'Issue #%d is no longer assigned to @U%d.', $p_bug_id, $p_prev_handler_id ) ); 1105 } else { 1106 log_event( 1107 LOG_EMAIL, 1108 sprintf( 1109 'Issue #%d is assigned to @U%d instead of @U%d.', 1110 $p_bug_id, 1111 $p_new_handler_id, 1112 $p_prev_handler_id ) 1113 ); 1114 } 1115 1116 $t_message_id = $p_new_handler_id == NO_USER ? 1117 'email_notification_title_for_action_bug_unassigned' : 1118 'email_notification_title_for_action_bug_assigned'; 1119 1120 $t_extra_user_ids_to_email = array(); 1121 if ( $p_prev_handler_id !== NO_USER && $p_prev_handler_id != $p_new_handler_id ) { 1122 if ( email_notify_flag( 'owner', 'handler' ) == ON ) { 1123 $t_extra_user_ids_to_email[] = $p_prev_handler_id; 1124 } 1125 } 1126 1127 email_generic( $p_bug_id, 'owner', $t_message_id, /* headers */ null, $t_extra_user_ids_to_email ); 1128} 1129 1130/** 1131 * Send notifications when bug status is changed. 1132 * @param int $p_bug_id The bug id 1133 * @param string $p_new_status_label The new status label. 1134 * @return void 1135 */ 1136function email_bug_status_changed( $p_bug_id, $p_new_status_label ) { 1137 log_event( LOG_EMAIL, sprintf( 'Issue #%d status changed', $p_bug_id ) ); 1138 email_generic( $p_bug_id, $p_new_status_label, 'email_notification_title_for_status_bug_' . $p_new_status_label ); 1139} 1140 1141/** 1142 * send notices when a bug is DELETED 1143 * @param int $p_bug_id 1144 * @return void 1145 */ 1146function email_bug_deleted( $p_bug_id ) { 1147 log_event( LOG_EMAIL, sprintf( 'Issue #%d deleted', $p_bug_id ) ); 1148 email_generic( $p_bug_id, 'deleted', 'email_notification_title_for_action_bug_deleted' ); 1149} 1150 1151/** 1152 * Store email in queue for sending 1153 * 1154 * @param string $p_recipient Email recipient address. 1155 * @param string $p_subject Subject of email message. 1156 * @param string $p_message Body text of email message. 1157 * @param array $p_headers Array of additional headers to send with the email. 1158 * @param boolean $p_force True to force sending of emails in shutdown function, 1159 * even when using cronjob 1160 * @return integer|null 1161 */ 1162function email_store( $p_recipient, $p_subject, $p_message, array $p_headers = null, $p_force = false ) { 1163 global $g_email_shutdown_processing; 1164 1165 $t_recipient = trim( $p_recipient ); 1166 $t_subject = string_email( trim( $p_subject ) ); 1167 $t_message = string_email_links( trim( $p_message ) ); 1168 1169 # short-circuit if no recipient is defined, or email disabled 1170 # note that this may cause signup messages not to be sent 1171 if( is_blank( $p_recipient ) || ( OFF == config_get( 'enable_email_notification' ) ) ) { 1172 return null; 1173 } 1174 1175 $t_email_data = new EmailData; 1176 1177 $t_email_data->email = $t_recipient; 1178 $t_email_data->subject = $t_subject; 1179 $t_email_data->body = $t_message; 1180 $t_email_data->metadata = array(); 1181 $t_email_data->metadata['headers'] = $p_headers === null ? array() : $p_headers; 1182 1183 # Urgent = 1, Not Urgent = 5, Disable = 0 1184 $t_email_data->metadata['charset'] = 'utf-8'; 1185 1186 $t_hostname = ''; 1187 if( isset( $_SERVER['SERVER_NAME'] ) ) { 1188 $t_hostname = $_SERVER['SERVER_NAME']; 1189 } else { 1190 $t_address = explode( '@', config_get( 'from_email' ) ); 1191 if( isset( $t_address[1] ) ) { 1192 $t_hostname = $t_address[1]; 1193 } 1194 } 1195 $t_email_data->metadata['hostname'] = $t_hostname; 1196 1197 $t_email_id = email_queue_add( $t_email_data ); 1198 1199 # Set the email processing flag for the shutdown function 1200 $g_email_shutdown_processing |= EMAIL_SHUTDOWN_GENERATED; 1201 if( $p_force ) { 1202 $g_email_shutdown_processing |= EMAIL_SHUTDOWN_FORCE; 1203 } 1204 1205 return $t_email_id; 1206} 1207 1208/** 1209 * This function sends all the emails that are stored in the queue. 1210 * It will be called 1211 * - immediately after queueing messages in case of synchronous emails 1212 * - from a cronjob in case of asynchronous emails 1213 * If a failure occurs, then the function exits. 1214 * @todo In case of synchronous email sending, we may get a race condition where two requests send the same email. 1215 * @param boolean $p_delete_on_failure Indicates whether to remove email from queue on failure (default false). 1216 * @return void 1217 */ 1218function email_send_all( $p_delete_on_failure = false ) { 1219 $t_ids = email_queue_get_ids(); 1220 1221 log_event( LOG_EMAIL_VERBOSE, 'Processing e-mail queue (' . count( $t_ids ) . ' messages)' ); 1222 1223 foreach( $t_ids as $t_id ) { 1224 $t_email_data = email_queue_get( $t_id ); 1225 $t_start = microtime( true ); 1226 1227 # check if email was not found. This can happen if another request picks up the email first and sends it. 1228 if( $t_email_data === false ) { 1229 $t_email_sent = true; 1230 log_event( LOG_EMAIL_VERBOSE, 'Message $t_id has already been sent' ); 1231 } else { 1232 log_event( LOG_EMAIL_VERBOSE, 'Sending message ' . $t_id ); 1233 $t_email_sent = email_send( $t_email_data ); 1234 } 1235 1236 if( !$t_email_sent ) { 1237 # Delete emails that were submitted more than N days ago 1238 $t_submitted = (int)$t_email_data->submitted; 1239 $t_delete_after_in_days = (int)config_get_global( 'email_retry_in_days' ); 1240 $t_retry_cutoff = time() - ( $t_delete_after_in_days * 24 * 60 * 60 ); 1241 if( $p_delete_on_failure || $t_submitted < $t_retry_cutoff ) { 1242 $t_reason = $p_delete_on_failure ? 'delete on failure' : 'retry expired'; 1243 email_queue_delete( $t_email_data->email_id, $t_reason ); 1244 } 1245 1246 # If unable to place the email in the email server queue and more 1247 # than 5 seconds have elapsed, then we assume that the server 1248 # connection is down, hence no point to continue trying with the 1249 # rest of the emails. 1250 if( microtime( true ) - $t_start > 5 ) { 1251 log_event( LOG_EMAIL, 'Server not responding for 5 seconds, aborting' ); 1252 break; 1253 } 1254 } 1255 } 1256} 1257 1258/** 1259 * This function sends an email message based on the supplied email data. 1260 * 1261 * @param EmailData $p_email_data Email Data object representing the email to send. 1262 * @return boolean 1263 */ 1264function email_send( EmailData $p_email_data ) { 1265 global $g_phpMailer; 1266 1267 $t_email_data = $p_email_data; 1268 1269 $t_recipient = trim( $t_email_data->email ); 1270 $t_subject = string_email( trim( $t_email_data->subject ) ); 1271 $t_message = string_email_links( trim( $t_email_data->body ) ); 1272 1273 $t_debug_email = config_get_global( 'debug_email' ); 1274 $t_mailer_method = config_get( 'phpMailer_method' ); 1275 1276 $t_log_msg = 'ERROR: Message could not be sent - '; 1277 1278 if( is_null( $g_phpMailer ) ) { 1279 if( $t_mailer_method == PHPMAILER_METHOD_SMTP ) { 1280 register_shutdown_function( 'email_smtp_close' ); 1281 } 1282 $t_mail = new PHPMailer( true ); 1283 1284 // Set e-mail addresses validation pattern. The 'html5' setting is 1285 // consistent with the regex defined in email_regex_simple(). 1286 PHPMailer::$validator = 'html5'; 1287 1288 } else { 1289 $t_mail = $g_phpMailer; 1290 } 1291 1292 if( isset( $t_email_data->metadata['hostname'] ) ) { 1293 $t_mail->Hostname = $t_email_data->metadata['hostname']; 1294 } 1295 1296 # @@@ should this be the current language (for the recipient) or the default one (for the user running the command) (thraxisp) 1297 $t_lang = config_get_global( 'default_language' ); 1298 if( 'auto' == $t_lang ) { 1299 $t_lang = config_get_global( 'fallback_language' ); 1300 } 1301 $t_mail->setLanguage( lang_get( 'phpmailer_language', $t_lang ) ); 1302 1303 # Select the method to send mail 1304 switch( config_get( 'phpMailer_method' ) ) { 1305 case PHPMAILER_METHOD_MAIL: 1306 $t_mail->isMail(); 1307 break; 1308 1309 case PHPMAILER_METHOD_SENDMAIL: 1310 $t_mail->isSendmail(); 1311 break; 1312 1313 case PHPMAILER_METHOD_SMTP: 1314 $t_mail->isSMTP(); 1315 1316 # SMTP collection is always kept alive 1317 $t_mail->SMTPKeepAlive = true; 1318 1319 if( !is_blank( config_get( 'smtp_username' ) ) ) { 1320 # Use SMTP Authentication 1321 $t_mail->SMTPAuth = true; 1322 $t_mail->Username = config_get( 'smtp_username' ); 1323 $t_mail->Password = config_get( 'smtp_password' ); 1324 } 1325 1326 if( is_blank( config_get( 'smtp_connection_mode' ) ) ) { 1327 $t_mail->SMTPAutoTLS = false; 1328 } 1329 else { 1330 $t_mail->SMTPSecure = config_get( 'smtp_connection_mode' ); 1331 } 1332 1333 $t_mail->Port = config_get( 'smtp_port' ); 1334 1335 break; 1336 } 1337 1338 # S/MIME signature 1339 if( ON == config_get_global( 'email_smime_enable' ) ) { 1340 $t_mail->sign( 1341 config_get_global( 'email_smime_cert_file' ), 1342 config_get_global( 'email_smime_key_file' ), 1343 config_get_global( 'email_smime_key_password' ), 1344 config_get_global( 'email_smime_extracerts_file' ) 1345 ); 1346 } 1347 1348 #apply DKIM settings 1349 if( config_get_global( 'email_dkim_enable' ) ) { 1350 $t_mail->DKIM_domain = config_get_global( 'email_dkim_domain' ); 1351 $t_mail->DKIM_private = config_get_global( 'email_dkim_private_key_file_path' ); 1352 $t_mail->DKIM_private_string = config_get_global( 'email_dkim_private_key_string' ); 1353 $t_mail->DKIM_selector = config_get_global( 'email_dkim_selector' ); 1354 $t_mail->DKIM_passphrase = config_get_global( 'email_dkim_passphrase' ); 1355 $t_mail->DKIM_identity = config_get_global( 'email_dkim_identity' ); 1356 } 1357 1358 $t_mail->isHTML( false ); # set email format to plain text 1359 $t_mail->WordWrap = 80; # set word wrap to 80 characters 1360 $t_mail->CharSet = $t_email_data->metadata['charset']; 1361 $t_mail->Host = config_get( 'smtp_host' ); 1362 $t_mail->From = config_get( 'from_email' ); 1363 $t_mail->Sender = config_get( 'return_path_email' ); 1364 $t_mail->FromName = config_get( 'from_name' ); 1365 $t_mail->AddCustomHeader( 'Auto-Submitted:auto-generated' ); 1366 $t_mail->AddCustomHeader( 'X-Auto-Response-Suppress: All' ); 1367 1368 $t_mail->Encoding = 'quoted-printable'; 1369 1370 if( isset( $t_email_data->metadata['priority'] ) ) { 1371 $t_mail->Priority = $t_email_data->metadata['priority']; # Urgent = 1, Not Urgent = 5, Disable = 0 1372 } 1373 1374 if( !empty( $t_debug_email ) ) { 1375 $t_message = 'To: ' . $t_recipient . "\n\n" . $t_message; 1376 $t_recipient = $t_debug_email; 1377 log_event(LOG_EMAIL_VERBOSE, "Using debug email '$t_debug_email'"); 1378 } 1379 1380 try { 1381 $t_mail->addAddress( $t_recipient, '' ); 1382 } 1383 catch ( phpmailerException $e ) { 1384 log_event( LOG_EMAIL, $t_log_msg . $t_mail->ErrorInfo ); 1385 $t_success = false; 1386 $t_mail->clearAllRecipients(); 1387 $t_mail->clearAttachments(); 1388 $t_mail->clearReplyTos(); 1389 $t_mail->clearCustomHeaders(); 1390 return $t_success; 1391 } 1392 1393 $t_mail->Subject = $t_subject; 1394 $t_mail->Body = make_lf_crlf( $t_message ); 1395 1396 if( isset( $t_email_data->metadata['headers'] ) && is_array( $t_email_data->metadata['headers'] ) ) { 1397 foreach( $t_email_data->metadata['headers'] as $t_key => $t_value ) { 1398 switch( $t_key ) { 1399 case 'Message-ID': 1400 # Note: hostname can never be blank here as we set metadata['hostname'] 1401 # in email_store() where mail gets queued. 1402 if( !strchr( $t_value, '@' ) && !is_blank( $t_mail->Hostname ) ) { 1403 $t_value = $t_value . '@' . $t_mail->Hostname; 1404 } 1405 $t_mail->set( 'MessageID', '<' . $t_value . '>' ); 1406 break; 1407 case 'In-Reply-To': 1408 $t_mail->addCustomHeader( $t_key . ': <' . $t_value . '@' . $t_mail->Hostname . '>' ); 1409 break; 1410 default: 1411 $t_mail->addCustomHeader( $t_key . ': ' . $t_value ); 1412 break; 1413 } 1414 } 1415 } 1416 1417 try { 1418 $t_success = $t_mail->send(); 1419 if( $t_success ) { 1420 $t_success = true; 1421 1422 if( $t_email_data->email_id > 0 ) { 1423 email_queue_delete( $t_email_data->email_id ); 1424 } 1425 } else { 1426 # We should never get here, as an exception is thrown after failures 1427 log_event( LOG_EMAIL, $t_log_msg . $t_mail->ErrorInfo ); 1428 $t_success = false; 1429 } 1430 } 1431 catch ( phpmailerException $e ) { 1432 log_event( LOG_EMAIL, $t_log_msg . $t_mail->ErrorInfo ); 1433 $t_success = false; 1434 } 1435 1436 $t_mail->clearAllRecipients(); 1437 $t_mail->clearAttachments(); 1438 $t_mail->clearReplyTos(); 1439 $t_mail->clearCustomHeaders(); 1440 1441 return $t_success; 1442} 1443 1444/** 1445 * closes opened kept alive SMTP connection (if it was opened) 1446 * 1447 * @return void 1448 */ 1449function email_smtp_close() { 1450 global $g_phpMailer; 1451 1452 if( !is_null( $g_phpMailer ) ) { 1453 $t_smtp = $g_phpMailer->getSMTPInstance(); 1454 if( $t_smtp->connected() ) { 1455 $t_smtp->quit(); 1456 $t_smtp->close(); 1457 } 1458 $g_phpMailer = null; 1459 } 1460} 1461 1462/** 1463 * formats the subject correctly 1464 * we include the project name, bug id, and summary. 1465 * 1466 * @param integer $p_bug_id A bug identifier. 1467 * @return string 1468 */ 1469function email_build_subject( $p_bug_id ) { 1470 # grab the project name 1471 $p_project_name = project_get_field( bug_get_field( $p_bug_id, 'project_id' ), 'name' ); 1472 1473 # grab the subject (summary) 1474 $p_subject = bug_get_field( $p_bug_id, 'summary' ); 1475 1476 # pad the bug id with zeros 1477 $t_bug_id = bug_format_id( $p_bug_id ); 1478 1479 # build standard subject string 1480 $t_email_subject = '[' . $p_project_name . ' ' . $t_bug_id . ']: ' . $p_subject; 1481 1482 # update subject as defined by plugins 1483 $t_email_subject = event_signal( 'EVENT_DISPLAY_EMAIL_BUILD_SUBJECT', $t_email_subject, array( 'bug_id' => $p_bug_id ) ); 1484 1485 return $t_email_subject; 1486} 1487 1488/** 1489 * clean up LF to CRLF 1490 * 1491 * @param string $p_string String to convert linefeeds on. 1492 * @return string 1493 */ 1494function make_lf_crlf( $p_string ) { 1495 $t_string = str_replace( "\n", "\r\n", $p_string ); 1496 return str_replace( "\r\r\n", "\r\n", $t_string ); 1497} 1498 1499/** 1500 * Send a bug reminder to the given user(s), or to each user if the first parameter is an array 1501 * 1502 * @param integer|array $p_recipients User id or list of user ids array to send reminder to. 1503 * @param integer $p_bug_id Issue for which the reminder is sent. 1504 * @param string $p_message Optional message to add to the e-mail. 1505 * @return array List of users ids to whom the reminder e-mail was actually sent 1506 */ 1507function email_bug_reminder( $p_recipients, $p_bug_id, $p_message ) { 1508 if( OFF == config_get( 'enable_email_notification' ) ) { 1509 return array(); 1510 } 1511 1512 if( !is_array( $p_recipients ) ) { 1513 $p_recipients = array( 1514 $p_recipients, 1515 ); 1516 } 1517 1518 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 1519 $t_sender_id = auth_get_current_user_id(); 1520 $t_sender = user_get_name( $t_sender_id ); 1521 1522 $t_subject = email_build_subject( $p_bug_id ); 1523 $t_date = date( config_get( 'normal_date_format' ) ); 1524 1525 $t_result = array(); 1526 foreach( $p_recipients as $t_recipient ) { 1527 lang_push( user_pref_get_language( $t_recipient, $t_project_id ) ); 1528 1529 $t_email = user_get_email( $t_recipient ); 1530 1531 if( access_has_project_level( config_get( 'show_user_email_threshold' ), $t_project_id, $t_recipient ) ) { 1532 $t_sender_email = ' <' . user_get_email( $t_sender_id ) . '>'; 1533 } else { 1534 $t_sender_email = ''; 1535 } 1536 $t_header = "\n" . lang_get( 'on_date' ) . ' ' . $t_date . ', ' . $t_sender . ' ' . $t_sender_email . lang_get( 'sent_you_this_reminder_about' ) . ': ' . "\n\n"; 1537 $t_contents = $t_header . string_get_bug_view_url_with_fqdn( $p_bug_id ) . " \n\n" . $p_message; 1538 1539 $t_id = email_store( $t_email, $t_subject, $t_contents ); 1540 if( $t_id !== null ) { 1541 $t_result[] = $t_recipient; 1542 } 1543 log_event( LOG_EMAIL_VERBOSE, 'queued reminder email ' . $t_id . ' for U' . $t_recipient ); 1544 1545 lang_pop(); 1546 } 1547 1548 return $t_result; 1549} 1550 1551/** 1552 * Send a notification to user or set of users that were mentioned in an issue 1553 * or an issue note. 1554 * 1555 * @param integer $p_bug_id Issue for which the reminder is sent. 1556 * @param array $p_mention_user_ids User id or list of user ids array. 1557 * @param string $p_message Optional message to add to the e-mail. 1558 * @param array $p_removed_mention_user_ids The users that were removed due to lack of access. 1559 * @return array List of users ids to whom the mentioned e-mail were actually sent 1560 */ 1561function email_user_mention( $p_bug_id, $p_mention_user_ids, $p_message, $p_removed_mention_user_ids = array() ) { 1562 if( OFF == config_get( 'enable_email_notification' ) ) { 1563 log_event( LOG_EMAIL_VERBOSE, 'email notifications disabled.' ); 1564 return array(); 1565 } 1566 1567 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 1568 $t_sender_id = auth_get_current_user_id(); 1569 $t_sender = user_get_name( $t_sender_id ); 1570 1571 $t_subject = email_build_subject( $p_bug_id ); 1572 $t_date = date( config_get( 'normal_date_format' ) ); 1573 $t_user_id = auth_get_current_user_id(); 1574 $t_users_processed = array(); 1575 1576 foreach( $p_removed_mention_user_ids as $t_removed_mention_user_id ) { 1577 log_event( LOG_EMAIL_VERBOSE, 'skipped mention email for U' . $t_removed_mention_user_id . ' (no access to issue or note).' ); 1578 } 1579 1580 $t_result = array(); 1581 foreach( $p_mention_user_ids as $t_mention_user_id ) { 1582 # Don't trigger mention emails for self mentions 1583 if( $t_mention_user_id == $t_user_id ) { 1584 log_event( LOG_EMAIL_VERBOSE, 'skipped mention email for U' . $t_mention_user_id . ' (self-mention).' ); 1585 continue; 1586 } 1587 1588 # Don't process a user more than once 1589 if( isset( $t_users_processed[$t_mention_user_id] ) ) { 1590 continue; 1591 } 1592 1593 $t_users_processed[$t_mention_user_id] = true; 1594 1595 # Don't email mention notifications to disabled users. 1596 if( !user_is_enabled( $t_mention_user_id ) ) { 1597 continue; 1598 } 1599 1600 lang_push( user_pref_get_language( $t_mention_user_id, $t_project_id ) ); 1601 1602 $t_email = user_get_email( $t_mention_user_id ); 1603 1604 if( access_has_project_level( config_get( 'show_user_email_threshold' ), $t_project_id, $t_mention_user_id ) ) { 1605 $t_sender_email = ' <' . user_get_email( $t_sender_id ) . '> '; 1606 } else { 1607 $t_sender_email = ''; 1608 } 1609 1610 $t_complete_subject = sprintf( lang_get( 'mentioned_in' ), $t_subject ); 1611 $t_header = "\n" . lang_get( 'on_date' ) . ' ' . $t_date . ', ' . $t_sender . ' ' . $t_sender_email . lang_get( 'mentioned_you' ) . "\n\n"; 1612 $t_contents = $t_header . string_get_bug_view_url_with_fqdn( $p_bug_id ) . " \n\n" . $p_message; 1613 1614 $t_id = email_store( $t_email, $t_complete_subject, $t_contents ); 1615 if( $t_id !== null ) { 1616 $t_result[] = $t_mention_user_id; 1617 } 1618 1619 log_event( LOG_EMAIL_VERBOSE, 'queued mention email ' . $t_id . ' for U' . $t_mention_user_id ); 1620 1621 lang_pop(); 1622 } 1623 1624 return $t_result; 1625} 1626 1627/** 1628 * Send bug info to given user 1629 * return true on success 1630 * @param array $p_visible_bug_data Array of bug data information. 1631 * @param string $p_message_id A message identifier. 1632 * @param integer $p_user_id A valid user identifier. 1633 * @param array $p_header_optional_params Array of additional email headers. 1634 * @return void 1635 */ 1636function email_bug_info_to_one_user( array $p_visible_bug_data, $p_message_id, $p_user_id, array $p_header_optional_params = null ) { 1637 $t_user_email = user_get_email( $p_user_id ); 1638 1639 # check whether email should be sent 1640 # @@@ can be email field empty? if yes - then it should be handled here 1641 if( ON !== config_get( 'enable_email_notification' ) || is_blank( $t_user_email ) ) { 1642 return; 1643 } 1644 1645 # build subject 1646 $t_subject = email_build_subject( $p_visible_bug_data['email_bug'] ); 1647 1648 # build message 1649 $t_message = lang_get_defaulted( $p_message_id, null ); 1650 1651 if( is_array( $p_header_optional_params ) ) { 1652 $t_message = vsprintf( $t_message, $p_header_optional_params ); 1653 } 1654 1655 if( ( $t_message !== null ) && ( !is_blank( $t_message ) ) ) { 1656 $t_message .= " \n"; 1657 } 1658 1659 $t_message .= email_format_bug_message( $p_visible_bug_data ); 1660 1661 # build headers 1662 $t_bug_id = $p_visible_bug_data['email_bug']; 1663 $t_message_md5 = md5( $t_bug_id . $p_visible_bug_data['email_date_submitted'] ); 1664 $t_mail_headers = array( 1665 'keywords' => $p_visible_bug_data['set_category'], 1666 ); 1667 if( $p_message_id == 'email_notification_title_for_action_bug_submitted' ) { 1668 $t_mail_headers['Message-ID'] = $t_message_md5; 1669 } else { 1670 $t_mail_headers['In-Reply-To'] = $t_message_md5; 1671 } 1672 1673 # send mail 1674 email_store( $t_user_email, $t_subject, $t_message, $t_mail_headers ); 1675 1676 return; 1677} 1678 1679/** 1680 * Generates a formatted note to be used in email notifications. 1681 * 1682 * @param BugnoteData $p_bugnote The bugnote object. 1683 * @param integer $p_project_id The project id 1684 * @param boolean $p_show_time_tracking true: show time tracking, false otherwise. 1685 * @param string $p_horizontal_separator The horizontal line separator to use. 1686 * @param string $p_date_format The date format to use. 1687 * @return string The formatted note. 1688 */ 1689function email_format_bugnote( $p_bugnote, $p_project_id, $p_show_time_tracking, $p_horizontal_separator, $p_date_format = null ) { 1690 $t_date_format = ( $p_date_format === null ) ? config_get( 'normal_date_format' ) : $p_date_format; 1691 1692 $t_last_modified = date( $t_date_format, $p_bugnote->last_modified ); 1693 1694 $t_formatted_bugnote_id = bugnote_format_id( $p_bugnote->id ); 1695 $t_bugnote_link = string_process_bugnote_link( config_get( 'bugnote_link_tag' ) . $p_bugnote->id, false, false, true ); 1696 1697 if( $p_show_time_tracking && $p_bugnote->time_tracking > 0 ) { 1698 $t_time_tracking = ' ' . lang_get( 'time_tracking' ) . ' ' . db_minutes_to_hhmm( $p_bugnote->time_tracking ) . "\n"; 1699 } else { 1700 $t_time_tracking = ''; 1701 } 1702 1703 if( user_exists( $p_bugnote->reporter_id ) ) { 1704 $t_access_level = access_get_project_level( $p_project_id, $p_bugnote->reporter_id ); 1705 $t_access_level_string = ' (' . access_level_get_string( $t_access_level ) . ')'; 1706 } else { 1707 $t_access_level_string = ''; 1708 } 1709 1710 $t_private = ( $p_bugnote->view_state == VS_PUBLIC ) ? '' : ' (' . lang_get( 'private' ) . ')'; 1711 1712 $t_string = ' (' . $t_formatted_bugnote_id . ') ' . user_get_name( $p_bugnote->reporter_id ) . 1713 $t_access_level_string . ' - ' . $t_last_modified . $t_private . "\n" . 1714 $t_time_tracking . ' ' . $t_bugnote_link; 1715 1716 $t_message = $p_horizontal_separator . " \n"; 1717 $t_message .= $t_string . " \n"; 1718 $t_message .= $p_horizontal_separator . " \n"; 1719 $t_message .= $p_bugnote->note . " \n"; 1720 1721 return $t_message; 1722} 1723 1724/** 1725 * Build the bug info part of the message 1726 * @param array $p_visible_bug_data Bug data array to format. 1727 * @return string 1728 */ 1729function email_format_bug_message( array $p_visible_bug_data ) { 1730 $t_normal_date_format = config_get( 'normal_date_format' ); 1731 $t_complete_date_format = config_get( 'complete_date_format' ); 1732 1733 $t_email_separator1 = config_get( 'email_separator1' ); 1734 $t_email_separator2 = config_get( 'email_separator2' ); 1735 $t_email_padding_length = config_get( 'email_padding_length' ); 1736 1737 $p_visible_bug_data['email_date_submitted'] = date( $t_complete_date_format, $p_visible_bug_data['email_date_submitted'] ); 1738 $p_visible_bug_data['email_last_modified'] = date( $t_complete_date_format, $p_visible_bug_data['email_last_modified'] ); 1739 1740 $t_message = $t_email_separator1 . " \n"; 1741 1742 if( isset( $p_visible_bug_data['email_bug_view_url'] ) ) { 1743 $t_message .= $p_visible_bug_data['email_bug_view_url'] . " \n"; 1744 $t_message .= $t_email_separator1 . " \n"; 1745 } 1746 1747 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reporter' ); 1748 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_handler' ); 1749 $t_message .= $t_email_separator1 . " \n"; 1750 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_project' ); 1751 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_bug' ); 1752 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_category' ); 1753 1754 if( isset( $p_visible_bug_data['email_tag'] ) ) { 1755 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_tag' ); 1756 } 1757 1758 if ( isset( $p_visible_bug_data[ 'email_reproducibility' ] ) ) { 1759 $p_visible_bug_data['email_reproducibility'] = get_enum_element( 'reproducibility', $p_visible_bug_data['email_reproducibility'] ); 1760 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reproducibility' ); 1761 } 1762 1763 if ( isset( $p_visible_bug_data[ 'email_severity' ] ) ) { 1764 $p_visible_bug_data['email_severity'] = get_enum_element( 'severity', $p_visible_bug_data['email_severity'] ); 1765 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_severity' ); 1766 } 1767 1768 if ( isset( $p_visible_bug_data[ 'email_priority' ] ) ) { 1769 $p_visible_bug_data['email_priority'] = get_enum_element( 'priority', $p_visible_bug_data['email_priority'] ); 1770 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_priority' ); 1771 } 1772 1773 if ( isset( $p_visible_bug_data[ 'email_status' ] ) ) { 1774 $t_status = $p_visible_bug_data['email_status']; 1775 $p_visible_bug_data['email_status'] = get_enum_element( 'status', $t_status ); 1776 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_status' ); 1777 } 1778 1779 if ( isset( $p_visible_bug_data[ 'email_target_version' ] ) ) { 1780 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_target_version' ); 1781 } 1782 1783 # custom fields formatting 1784 foreach( $p_visible_bug_data['custom_fields'] as $t_custom_field_name => $t_custom_field_data ) { 1785 $t_message .= utf8_str_pad( lang_get_defaulted( $t_custom_field_name, null ) . ': ', $t_email_padding_length, ' ', STR_PAD_RIGHT ); 1786 $t_message .= string_custom_field_value_for_email( $t_custom_field_data['value'], $t_custom_field_data['type'] ); 1787 $t_message .= " \n"; 1788 } 1789 1790 # end foreach custom field 1791 1792 if( isset( $t_status ) && config_get( 'bug_resolved_status_threshold' ) <= $t_status ) { 1793 1794 if ( isset( $p_visible_bug_data[ 'email_resolution' ] ) ) { 1795 $p_visible_bug_data['email_resolution'] = get_enum_element( 'resolution', $p_visible_bug_data['email_resolution'] ); 1796 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_resolution' ); 1797 } 1798 1799 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_fixed_in_version' ); 1800 } 1801 $t_message .= $t_email_separator1 . " \n"; 1802 1803 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_date_submitted' ); 1804 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_last_modified' ); 1805 1806 if( isset( $p_visible_bug_data['email_due_date'] ) ) { 1807 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_due_date' ); 1808 } 1809 1810 $t_message .= $t_email_separator1 . " \n"; 1811 1812 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_summary' ); 1813 1814 $t_message .= lang_get( 'email_description' ) . ": \n" . $p_visible_bug_data['email_description'] . "\n"; 1815 1816 if( isset( $p_visible_bug_data[ 'email_steps_to_reproduce' ] ) && !is_blank( $p_visible_bug_data['email_steps_to_reproduce'] ) ) { 1817 $t_message .= "\n" . lang_get( 'email_steps_to_reproduce' ) . ": \n" . $p_visible_bug_data['email_steps_to_reproduce'] . "\n"; 1818 } 1819 1820 if( isset( $p_visible_bug_data[ 'email_additional_information' ] ) && !is_blank( $p_visible_bug_data['email_additional_information'] ) ) { 1821 $t_message .= "\n" . lang_get( 'email_additional_information' ) . ": \n" . $p_visible_bug_data['email_additional_information'] . "\n"; 1822 } 1823 1824 if( isset( $p_visible_bug_data['relations'] ) ) { 1825 if( $p_visible_bug_data['relations'] != '' ) { 1826 $t_message .= $t_email_separator1 . "\n" . utf8_str_pad( lang_get( 'bug_relationships' ), 20 ) . utf8_str_pad( lang_get( 'id' ), 8 ) . lang_get( 'summary' ) . "\n" . $t_email_separator2 . "\n" . $p_visible_bug_data['relations']; 1827 } 1828 } 1829 1830 # Sponsorship 1831 if( isset( $p_visible_bug_data['sponsorship_total'] ) && ( $p_visible_bug_data['sponsorship_total'] > 0 ) ) { 1832 $t_message .= $t_email_separator1 . " \n"; 1833 $t_message .= sprintf( lang_get( 'total_sponsorship_amount' ), sponsorship_format_amount( $p_visible_bug_data['sponsorship_total'] ) ) . "\n\n"; 1834 1835 if( isset( $p_visible_bug_data['sponsorships'] ) ) { 1836 foreach( $p_visible_bug_data['sponsorships'] as $t_sponsorship ) { 1837 $t_date_added = date( config_get( 'normal_date_format' ), $t_sponsorship->date_submitted ); 1838 1839 $t_message .= $t_date_added . ': '; 1840 $t_message .= user_get_name( $t_sponsorship->user_id ); 1841 $t_message .= ' (' . sponsorship_format_amount( $t_sponsorship->amount ) . ')' . " \n"; 1842 } 1843 } 1844 } 1845 1846 $t_message .= $t_email_separator1 . " \n\n"; 1847 1848 # format bugnotes 1849 foreach( $p_visible_bug_data['bugnotes'] as $t_bugnote ) { 1850 # Show time tracking is always true, since data has already been filtered out when creating the bug visible data. 1851 $t_message .= email_format_bugnote( $t_bugnote, $p_visible_bug_data['email_project_id'], 1852 /* show_time_tracking */ true, $t_email_separator2, $t_normal_date_format ) . "\n"; 1853 } 1854 1855 # format history 1856 if( array_key_exists( 'history', $p_visible_bug_data ) ) { 1857 $t_message .= lang_get( 'bug_history' ) . " \n"; 1858 $t_message .= utf8_str_pad( lang_get( 'date_modified' ), 17 ) . utf8_str_pad( lang_get( 'username' ), 15 ) . utf8_str_pad( lang_get( 'field' ), 25 ) . utf8_str_pad( lang_get( 'change' ), 20 ) . " \n"; 1859 1860 $t_message .= $t_email_separator1 . " \n"; 1861 1862 foreach( $p_visible_bug_data['history'] as $t_raw_history_item ) { 1863 $t_localized_item = history_localize_item( 1864 $t_raw_history_item['bug_id'], 1865 $t_raw_history_item['field'], 1866 $t_raw_history_item['type'], 1867 $t_raw_history_item['old_value'], 1868 $t_raw_history_item['new_value'], 1869 false 1870 ); 1871 1872 $t_message .= utf8_str_pad( date( $t_normal_date_format, $t_raw_history_item['date'] ), 17 ) . utf8_str_pad( $t_raw_history_item['username'], 15 ) . utf8_str_pad( $t_localized_item['note'], 25 ) . utf8_str_pad( $t_localized_item['change'], 20 ) . "\n"; 1873 } 1874 $t_message .= $t_email_separator1 . " \n\n"; 1875 } 1876 1877 return $t_message; 1878} 1879 1880/** 1881 * if $p_visible_bug_data contains specified attribute the function 1882 * returns concatenated translated attribute name and original 1883 * attribute value. Else return empty string. 1884 * @param array $p_visible_bug_data Visible Bug Data array. 1885 * @param string $p_attribute_id Attribute ID. 1886 * @return string 1887 */ 1888function email_format_attribute( array $p_visible_bug_data, $p_attribute_id ) { 1889 if( array_key_exists( $p_attribute_id, $p_visible_bug_data ) ) { 1890 return utf8_str_pad( lang_get( $p_attribute_id ) . ': ', config_get( 'email_padding_length' ), ' ', STR_PAD_RIGHT ) . $p_visible_bug_data[$p_attribute_id] . "\n"; 1891 } 1892 return ''; 1893} 1894 1895/** 1896 * Build the bug raw data visible for specified user to be translated and sent by email to the user 1897 * (Filter the bug data according to user access level) 1898 * return array with bug data. See usage in email_format_bug_message(...) 1899 * @param integer $p_user_id A user identifier. 1900 * @param integer $p_bug_id A bug identifier. 1901 * @param string $p_message_id A message identifier. 1902 * @return array 1903 */ 1904function email_build_visible_bug_data( $p_user_id, $p_bug_id, $p_message_id ) { 1905 # Override current user with user to construct bug data for. 1906 # This is to make sure that APIs that check against current user (e.g. relationship) work correctly. 1907 $t_current_user_id = current_user_set( $p_user_id ); 1908 1909 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 1910 $t_user_access_level = user_get_access_level( $p_user_id, $t_project_id ); 1911 $t_user_bugnote_order = user_pref_get_pref( $p_user_id, 'bugnote_order' ); 1912 $t_user_bugnote_limit = user_pref_get_pref( $p_user_id, 'email_bugnote_limit' ); 1913 1914 $t_row = bug_get_extended_row( $p_bug_id ); 1915 $t_bug_data = array(); 1916 1917 $t_bug_view_fields = config_get( 'bug_view_page_fields', null, $p_user_id, $t_row['project_id'] ); 1918 1919 $t_bug_data['email_bug'] = $p_bug_id; 1920 1921 if( $p_message_id !== 'email_notification_title_for_action_bug_deleted' ) { 1922 $t_bug_data['email_bug_view_url'] = string_get_bug_view_url_with_fqdn( $p_bug_id ); 1923 } 1924 1925 if( access_compare_level( $t_user_access_level, config_get( 'view_handler_threshold' ) ) ) { 1926 if( 0 != $t_row['handler_id'] ) { 1927 $t_bug_data['email_handler'] = user_get_name( $t_row['handler_id'] ); 1928 } else { 1929 $t_bug_data['email_handler'] = ''; 1930 } 1931 } 1932 1933 $t_bug_data['email_reporter'] = user_get_name( $t_row['reporter_id'] ); 1934 $t_bug_data['email_project_id'] = $t_row['project_id']; 1935 $t_bug_data['email_project'] = project_get_field( $t_row['project_id'], 'name' ); 1936 1937 $t_category_name = category_full_name( $t_row['category_id'], false ); 1938 $t_bug_data['email_category'] = $t_category_name; 1939 1940 $t_tag_rows = tag_bug_get_attached( $p_bug_id ); 1941 if( in_array( 'tags', $t_bug_view_fields ) && !empty( $t_tag_rows ) && access_compare_level( $t_user_access_level, config_get( 'tag_view_threshold' ) ) ) { 1942 $t_bug_data['email_tag'] = ''; 1943 1944 foreach( $t_tag_rows as $t_tag ) { 1945 $t_bug_data['email_tag'] .= $t_tag['name'] . ', '; 1946 } 1947 1948 $t_bug_data['email_tag'] = trim( $t_bug_data['email_tag'], ', ' ); 1949 } 1950 1951 $t_bug_data['email_date_submitted'] = $t_row['date_submitted']; 1952 $t_bug_data['email_last_modified'] = $t_row['last_updated']; 1953 1954 if( !date_is_null( $t_row['due_date'] ) && access_compare_level( $t_user_access_level, config_get( 'due_date_view_threshold' ) ) ) { 1955 $t_bug_data['email_due_date'] = date( config_get( 'short_date_format' ), $t_row['due_date'] ); 1956 } 1957 1958 if ( in_array( 'status', $t_bug_view_fields ) ) { 1959 $t_bug_data['email_status'] = $t_row['status']; 1960 } 1961 1962 if ( in_array( 'severity', $t_bug_view_fields ) ) { 1963 $t_bug_data['email_severity'] = $t_row['severity']; 1964 } 1965 1966 if ( in_array( 'priority', $t_bug_view_fields ) ) { 1967 $t_bug_data['email_priority'] = $t_row['priority']; 1968 } 1969 1970 if ( in_array( 'reproducibility', $t_bug_view_fields ) ) { 1971 $t_bug_data['email_reproducibility'] = $t_row['reproducibility']; 1972 } 1973 1974 if ( in_array( 'resolution', $t_bug_view_fields ) ) { 1975 $t_bug_data['email_resolution'] = $t_row['resolution']; 1976 } 1977 1978 $t_bug_data['email_fixed_in_version'] = $t_row['fixed_in_version']; 1979 1980 if( in_array( 'target_version', $t_bug_view_fields ) && !is_blank( $t_row['target_version'] ) && access_compare_level( $t_user_access_level, config_get( 'roadmap_view_threshold' ) ) ) { 1981 $t_bug_data['email_target_version'] = $t_row['target_version']; 1982 } 1983 1984 $t_bug_data['email_summary'] = $t_row['summary']; 1985 $t_bug_data['email_description'] = $t_row['description']; 1986 1987 if( in_array( 'additional_info', $t_bug_view_fields ) ) { 1988 $t_bug_data['email_additional_information'] = $t_row['additional_information']; 1989 } 1990 1991 if ( in_array( 'steps_to_reproduce', $t_bug_view_fields ) ) { 1992 $t_bug_data['email_steps_to_reproduce'] = $t_row['steps_to_reproduce']; 1993 } 1994 1995 $t_bug_data['set_category'] = '[' . $t_bug_data['email_project'] . '] ' . $t_category_name; 1996 1997 $t_bug_data['custom_fields'] = custom_field_get_linked_fields( $p_bug_id, $t_user_access_level ); 1998 $t_bug_data['bugnotes'] = bugnote_get_all_visible_bugnotes( $p_bug_id, $t_user_bugnote_order, $t_user_bugnote_limit, $p_user_id ); 1999 2000 # put history data 2001 if( ( ON == config_get( 'history_default_visible' ) ) && access_compare_level( $t_user_access_level, config_get( 'view_history_threshold' ) ) ) { 2002 $t_bug_data['history'] = history_get_raw_events_array( $p_bug_id, $p_user_id ); 2003 } 2004 2005 # Sponsorship Information 2006 if( ( config_get( 'enable_sponsorship' ) == ON ) && ( access_has_bug_level( config_get( 'view_sponsorship_total_threshold' ), $p_bug_id, $p_user_id ) ) ) { 2007 $t_sponsorship_ids = sponsorship_get_all_ids( $p_bug_id ); 2008 $t_bug_data['sponsorship_total'] = sponsorship_get_amount( $t_sponsorship_ids ); 2009 2010 if( access_has_bug_level( config_get( 'view_sponsorship_details_threshold' ), $p_bug_id, $p_user_id ) ) { 2011 $t_bug_data['sponsorships'] = array(); 2012 foreach( $t_sponsorship_ids as $t_id ) { 2013 $t_bug_data['sponsorships'][] = sponsorship_get( $t_id ); 2014 } 2015 } 2016 } 2017 2018 $t_bug_data['relations'] = email_relationship_get_summary_text( $p_bug_id ); 2019 2020 current_user_set( $t_current_user_id ); 2021 2022 return $t_bug_data; 2023} 2024 2025/** 2026 * return formatted string with all the details on the requested relationship 2027 * @param integer $p_bug_id A bug identifier. 2028 * @param BugRelationshipData $p_relationship A bug relationship object. 2029 * @return string 2030 */ 2031function email_relationship_get_details( $p_bug_id, BugRelationshipData $p_relationship ) { 2032 $t_summary_wrap_at = mb_strlen( config_get( 'email_separator2' ) ) - 28; 2033 2034 if( $p_bug_id == $p_relationship->src_bug_id ) { 2035 # root bug is in the source side, related bug in the destination side 2036 $t_related_project_id = $p_relationship->dest_bug_id; 2037 $t_related_bug_id = $p_relationship->dest_bug_id; 2038 $t_relationship_descr = relationship_get_description_src_side( $p_relationship->type ); 2039 } else { 2040 # root bug is in the dest side, related bug in the source side 2041 $t_related_project_id = $p_relationship->src_bug_id; 2042 $t_related_bug_id = $p_relationship->src_bug_id; 2043 $t_relationship_descr = relationship_get_description_dest_side( $p_relationship->type ); 2044 } 2045 2046 # related bug not existing... 2047 if( !bug_exists( $t_related_bug_id ) ) { 2048 return ''; 2049 } 2050 2051 # user can access to the related bug at least as a viewer 2052 if( !access_has_bug_level( config_get( 'view_bug_threshold', null, null, $t_related_project_id ), $t_related_bug_id ) ) { 2053 return ''; 2054 } 2055 2056 # get the information from the related bug and prepare the link 2057 $t_bug = bug_get( $t_related_bug_id, false ); 2058 2059 $t_relationship_info_text = utf8_str_pad( $t_relationship_descr, 20 ); 2060 $t_relationship_info_text .= utf8_str_pad( bug_format_id( $t_related_bug_id ), 8 ); 2061 2062 # add summary 2063 if( mb_strlen( $t_bug->summary ) <= $t_summary_wrap_at ) { 2064 $t_relationship_info_text .= string_email_links( $t_bug->summary ); 2065 } else { 2066 $t_relationship_info_text .= mb_substr( string_email_links( $t_bug->summary ), 0, $t_summary_wrap_at - 3 ) . '...'; 2067 } 2068 2069 $t_relationship_info_text .= "\n"; 2070 2071 return $t_relationship_info_text; 2072} 2073 2074/** 2075 * Get ALL the RELATIONSHIPS OF A SPECIFIC BUG in text format (used by email_api.php 2076 * @param integer $p_bug_id A bug identifier. 2077 * @return string 2078 */ 2079function email_relationship_get_summary_text( $p_bug_id ) { 2080 # A variable that will be set by the following call to indicate if relationships belong 2081 # to multiple projects. 2082 $t_show_project = false; 2083 2084 $t_relationship_all = relationship_get_all( $p_bug_id, $t_show_project ); 2085 $t_relationship_all_count = count( $t_relationship_all ); 2086 2087 # prepare the relationships table 2088 $t_summary = ''; 2089 for( $i = 0; $i < $t_relationship_all_count; $i++ ) { 2090 $t_summary .= email_relationship_get_details( $p_bug_id, $t_relationship_all[$i] ); 2091 } 2092 2093 return $t_summary; 2094} 2095 2096/** 2097 * The email sending shutdown function 2098 * Will send any queued emails, except when $g_email_send_using_cronjob = ON. 2099 * If $g_email_shutdown_processing EMAIL_SHUTDOWN_FORCE flag is set, emails 2100 * will be sent regardless of cronjob setting. 2101 * @return void 2102 */ 2103function email_shutdown_function() { 2104 global $g_email_shutdown_processing; 2105 2106 # Nothing to do if 2107 # - no emails have been generated in the current request 2108 # - system is configured to use cron job (unless processing is forced) 2109 if( $g_email_shutdown_processing == EMAIL_SHUTDOWN_SKIP 2110 || ( !( $g_email_shutdown_processing & EMAIL_SHUTDOWN_FORCE ) 2111 && config_get( 'email_send_using_cronjob' ) 2112 ) 2113 ) { 2114 return; 2115 } 2116 2117 $t_msg ='Shutdown function called for ' . $_SERVER['SCRIPT_NAME']; 2118 if( $g_email_shutdown_processing & EMAIL_SHUTDOWN_FORCE ) { 2119 $t_msg .= ' (email processing forced)'; 2120 } 2121 2122 log_event( LOG_EMAIL_VERBOSE, $t_msg ); 2123 2124 if( $g_email_shutdown_processing ) { 2125 email_send_all(); 2126 } 2127} 2128 2129/** 2130 * Get the list of supported email actions. 2131 * 2132 * @return array List of actions 2133 */ 2134function email_get_actions() { 2135 $t_actions = array( 'updated', 'owner', 'reopened', 'deleted', 'bugnote', 'relation', 'monitor' ); 2136 2137 if( config_get( 'enable_sponsorship' ) == ON ) { 2138 $t_actions[] = 'sponsor'; 2139 } 2140 2141 $t_statuses = MantisEnum::getAssocArrayIndexedByValues( config_get( 'status_enum_string' ) ); 2142 ksort( $t_statuses ); 2143 reset( $t_statuses ); 2144 2145 foreach( $t_statuses as $t_label ) { 2146 $t_actions[] = $t_label; 2147 } 2148 2149 return $t_actions; 2150} 2151