2 # MantisBT - A PHP based bugtracking system
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.
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.
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/>.
21 * @subpackage EmailAPI
22 * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
23 * @copyright Copyright (C) 2002 - 2010 MantisBT Team - mantisbt-dev@lists.sourceforge.net
24 * @link http://www.mantisbt.org
26 * @uses access_api.php
27 * @uses authentication_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
38 * @uses helper_api.php
39 * @uses history_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
47 * @uses user_pref_api.php
48 * @uses utility_api.php
51 require_api( 'access_api.php' );
52 require_api( 'authentication_api.php' );
53 require_api( 'bug_api.php' );
54 require_api( 'bugnote_api.php' );
55 require_api( 'category_api.php' );
56 require_api( 'config_api.php' );
57 require_api( 'constant_inc.php' );
58 require_api( 'current_user_api.php' );
59 require_api( 'custom_field_api.php' );
60 require_api( 'database_api.php' );
61 require_api( 'email_queue_api.php' );
62 require_api( 'event_api.php' );
63 require_api( 'helper_api.php' );
64 require_api( 'history_api.php' );
65 require_api( 'lang_api.php' );
66 require_api( 'logging_api.php' );
67 require_api( 'project_api.php' );
68 require_api( 'relationship_api.php' );
69 require_api( 'sponsorship_api.php' );
70 require_api( 'string_api.php' );
71 require_api( 'user_api.php' );
72 require_api( 'user_pref_api.php' );
73 require_api( 'utility_api.php' );
76 * reusable object of class SMTP
82 * Use a simple perl regex for valid email addresses. This is not a complete regex,
83 * as it does not cover quoted addresses or domain literals, but it is simple and
84 * covers the vast majority of all email addresses without being overly complex.
87 function email_regex_simple() {
88 return "/([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
89 "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)/i"; # @domain
93 * check to see that the format is valid and that the mx record exists
94 * @param string $p_email
97 function email_is_valid( $p_email ) {
98 # if we don't validate then just accept
99 if( OFF
== config_get( 'validate_email' ) ) {
103 if ( ON
== config_get( 'use_ldap_email' ) ) {
107 if( is_blank( $p_email ) && ON
== config_get( 'allow_blank_email' ) ) {
111 # Use a regular expression to check to see if the email is in valid format
112 # x-xx.xxx@yyy.zzz.abc etc.
113 if( preg_match( email_regex_simple(), $p_email, $t_check ) ) {
114 $t_local = $t_check[1];
115 $t_domain = $t_check[2];
117 # see if we're limited to one domain
118 $t_limit_email_domain = config_get( 'limit_email_domain' );
119 if( $t_limit_email_domain !== OFF
) {
120 if( 0 != strcasecmp( $t_limit_email_domain, $t_domain ) ) {
125 if( ON
== config_get( 'check_mx_record' ) ) {
128 # Check for valid mx records
129 if( getmxrr( $t_domain, $temp ) ) {
132 $host = $t_domain . '.';
134 # for no mx record... try dns check
135 if( checkdnsrr( $host, 'ANY' ) ) {
140 # Email format was valid but did't check for valid mx records
145 # Everything failed. The email is invalid
150 * Check if the email address is valid
151 * trigger an ERROR if it isn't
152 * @param string $p_email
155 function email_ensure_valid( $p_email ) {
156 if( !email_is_valid( $p_email ) ) {
157 trigger_error( ERROR_EMAIL_INVALID
, ERROR
);
162 * Check if the email address is disposable
163 * @param string $p_email
166 function email_is_disposable( $p_email ) {
167 if( !class_exists( 'DisposableEmailChecker' ) ) {
168 require_lib( 'disposable' . DIRECTORY_SEPARATOR
. 'disposable.php' );
171 return DisposableEmailChecker
::is_disposable_email( $p_email );
175 * Check if the email address is disposable
176 * trigger an ERROR if it isn't
177 * @param string $p_email
180 function email_ensure_not_disposable( $p_email ) {
181 if( email_is_disposable( $p_email ) ) {
182 trigger_error( ERROR_EMAIL_DISPOSABLE
, ERROR
);
188 * Get the value associated with the specific action and flag.
189 * For example, you can get the value associated with notifying "admin"
190 * on action "new", i.e. notify administrators on new bugs which can be
192 * @param string $action
193 * @param string $flag
196 function email_notify_flag( $action, $flag ) {
197 $t_notify_flags = config_get( 'notify_flags' );
198 $t_default_notify_flags = config_get( 'default_notify_flags' );
199 if( isset( $t_notify_flags[$action][$flag] ) ) {
200 return $t_notify_flags[$action][$flag];
202 else if( isset( $t_default_notify_flags[$flag] ) ) {
203 return $t_default_notify_flags[$flag];
210 * @todo yarick123: email_collect_recipients(...) will be completely rewritten to provide additional information such as language, user access,..
211 * @todo yarick123:sort recipients list by language to reduce switches between different languages
212 * @param int $p_bug_id
213 * @param string $p_notify_type
214 * @param array $p_extra_user_ids_to_email
217 function email_collect_recipients( $p_bug_id, $p_notify_type, $p_extra_user_ids_to_email = array() ) {
218 $c_bug_id = db_prepare_int( $p_bug_id );
220 $t_recipients = array();
222 # add explicitly specified users
223 if ( ON
== email_notify_flag( $p_notify_type, 'explicit' ) ) {
224 foreach ( $p_extra_user_ids_to_email as $t_user_id ) {
225 $t_recipients[$t_user_id] = true;
226 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add explicitly specified user = @U%d', $p_bug_id, $t_user_id ) );
231 if( ON
== email_notify_flag( $p_notify_type, 'reporter' ) ) {
232 $t_reporter_id = bug_get_field( $p_bug_id, 'reporter_id' );
233 $t_recipients[$t_reporter_id] = true;
234 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add Reporter = @U%d', $p_bug_id, $t_reporter_id ) );
238 if( ON
== email_notify_flag( $p_notify_type, 'handler' ) ) {
239 $t_handler_id = bug_get_field( $p_bug_id, 'handler_id' );
241 if( $t_handler_id > 0 ) {
242 $t_recipients[$t_handler_id] = true;
243 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add Handler = @U%d', $p_bug_id, $t_handler_id ) );
247 $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
249 # add users monitoring the bug
250 $t_bug_monitor_table = db_get_table( 'bug_monitor' );
251 if( ON
== email_notify_flag( $p_notify_type, 'monitor' ) ) {
252 $query = "SELECT DISTINCT user_id
253 FROM $t_bug_monitor_table
254 WHERE bug_id=" . db_param();
255 $result = db_query_bound( $query, Array( $c_bug_id ) );
257 $count = db_num_rows( $result );
258 for( $i = 0;$i < $count;$i++
) {
259 $t_user_id = db_result( $result, $i );
260 $t_recipients[$t_user_id] = true;
261 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add Monitor = @U%d', $p_bug_id, $t_user_id ) );
265 # add users who contributed bugnotes
266 $t_bugnote_id = bugnote_get_latest_id( $p_bug_id );
267 $t_bugnote_view = bugnote_get_field( $t_bugnote_id, 'view_state' );
268 $t_bugnote_date = bugnote_get_field( $t_bugnote_id, 'last_modified' );
269 $t_bug = bug_get( $p_bug_id );
270 $t_bug_date = $t_bug->last_updated
;
272 $t_bugnote_table = db_get_table( 'bugnote' );
273 if( ON
== email_notify_flag( $p_notify_type, 'bugnotes' ) ) {
274 $query = "SELECT DISTINCT reporter_id
275 FROM $t_bugnote_table
276 WHERE bug_id = " . db_param();
277 $result = db_query_bound( $query, Array( $c_bug_id ) );
279 $count = db_num_rows( $result );
280 for( $i = 0;$i < $count;$i++
) {
281 $t_user_id = db_result( $result, $i );
282 $t_recipients[$t_user_id] = true;
283 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add Note Author = @U%d', $p_bug_id, $t_user_id ) );
287 # add project users who meet the thresholds
288 $t_bug_is_private = bug_get_field( $p_bug_id, 'view_state' ) == VS_PRIVATE
;
289 $t_threshold_min = email_notify_flag( $p_notify_type, 'threshold_min' );
290 $t_threshold_max = email_notify_flag( $p_notify_type, 'threshold_max' );
291 $t_threshold_users = project_get_all_user_rows( $t_project_id, $t_threshold_min );
292 foreach( $t_threshold_users as $t_user ) {
293 if( $t_user['access_level'] <= $t_threshold_max ) {
294 if( !$t_bug_is_private ||
access_compare_level( $t_user['access_level'], config_get( 'private_bug_threshold' ) ) ) {
295 $t_recipients[$t_user['id']] = true;
296 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, add Project User = @U%d', $p_bug_id, $t_user['id'] ) );
301 # add users as specified by plugins
302 $t_recipients_include_data = event_signal( 'EVENT_NOTIFY_USER_INCLUDE', array( $p_bug_id, $p_notify_type ) );
303 foreach( $t_recipients_include_data as $t_plugin => $t_recipients_include_data2 ) {
304 foreach( $t_recipients_include_data2 as $t_callback => $t_recipients_included ) {
305 # only handle if we get an array from the callback
306 if ( is_array( $t_recipients_included ) ) {
307 foreach( $t_recipients_included as $t_user_id ) {
308 $t_recipients[ $t_user_id ] = true;
309 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, %s plugin added user @U%d', $p_bug_id, $t_plugin, $t_user_id ) );
315 # FIXME: the value of $p_notify_type could at this stage be either a status
316 # or a built-in actions such as 'owner and 'sponsor'. We have absolutely no
317 # idea whether 'new' is indicating a new bug has been filed, or if the
318 # status of an existing bug has been changed to 'new'. Therefore it is best
319 # to just assume built-in actions have precedence over status changes.
320 switch( $p_notify_type) {
322 case 'feedback': # This isn't really a built-in action (delete me!)
327 $t_pref_field = 'email_on_' . $p_notify_type;
330 # The email_on_assigned notification type is now effectively
331 # email_on_change_of_handler.
332 $t_pref_field = 'email_on_assigned';
339 case 'priority': # This is never used, but exists in the database!
340 # FIXME: these notification actions are not actually implemented
341 # in the database and therefore aren't adjustable on a per-user
342 # basis! The exception is 'monitor' that makes no sense being a
343 # customisable per-user preference.
344 $t_pref_field = false;
347 # Anything not built-in is probably going to be a status
348 $t_pref_field = 'email_on_status';
352 # @@@ we could optimize by modifiying user_cache() to take an array
353 # of user ids so we could pull them all in. We'll see if it's necessary
354 $t_final_recipients = array();
356 $t_user_ids = array_keys( $t_recipients );
357 user_cache_array_rows( $t_user_ids );
358 user_pref_cache_array_rows( $t_user_ids );
359 user_pref_cache_array_rows( $t_user_ids, $t_bug->project_id
);
361 # Check whether users should receive the emails
362 # and put email address to $t_recipients[user_id]
363 foreach( $t_recipients as $t_id => $t_ignore ) {
364 # Possibly eliminate the current user
365 if(( auth_get_current_user_id() == $t_id ) && ( OFF
== config_get( 'email_receive_own' ) ) ) {
366 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (own)', $p_bug_id, $t_id ) );
370 # Eliminate users who don't exist anymore or who are disabled
371 if( !user_exists( $t_id ) ||
!user_is_enabled( $t_id ) ) {
372 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (disabled)', $p_bug_id, $t_id ) );
376 # Exclude users who have this notification type turned off
377 if( $t_pref_field ) {
378 $t_notify = user_pref_get_pref( $t_id, $t_pref_field );
379 if( OFF
== $t_notify ) {
380 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (pref %s off)', $p_bug_id, $t_id, $t_pref_field ) );
383 # Users can define the severity of an issue before they are emailed for
384 # each type of notification
385 $t_min_sev_pref_field = $t_pref_field . '_min_severity';
386 $t_min_sev_notify = user_pref_get_pref( $t_id, $t_min_sev_pref_field );
387 $t_bug_severity = bug_get_field( $p_bug_id, 'severity' );
389 if( $t_bug_severity < $t_min_sev_notify ) {
390 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (pref threshold)', $p_bug_id, $t_id ) );
396 # check that user can see bugnotes if the last update included a bugnote
397 if( $t_bug_date == $t_bugnote_date ) {
398 if( !access_has_bugnote_level( VIEWER
, $t_bugnote_id, $t_id ) ) {
399 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (access level)', $p_bug_id, $t_id ) );
404 # check to exclude users as specified by plugins
405 $t_recipient_exclude_data = event_signal( 'EVENT_NOTIFY_USER_EXCLUDE', array( $p_bug_id, $p_notify_type, $t_id ) );
407 foreach( $t_recipient_exclude_data as $t_plugin => $t_recipient_exclude_data2 ) {
408 foreach( $t_recipient_exclude_data2 as $t_callback => $t_recipient_excluded ) {
409 # exclude if any plugin returns true (excludes the user)
410 if ( $t_recipient_excluded ) {
412 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, %s plugin dropped user @U%d', $p_bug_id, $t_plugin, $t_id ) );
417 # user was excluded by a plugin
422 # Finally, let's get their emails, if they've set one
423 $t_email = user_get_email( $t_id );
424 if( is_blank( $t_email ) ) {
425 log_event( LOG_EMAIL_RECIPIENT
, sprintf( 'Issue = #%d, drop @U%d (no email)', $p_bug_id, $t_id ) );
427 # @@@ we could check the emails for validity again but I think
428 # it would be too slow
429 $t_final_recipients[$t_id] = $t_email;
433 return $t_final_recipients;
437 * Send password to user
438 * @param int $p_user_id
439 * @param string $p_password
440 * @param string $p_confirm_hash
441 * @param string $p_admin_name
444 function email_signup( $p_user_id, $p_password, $p_confirm_hash, $p_admin_name = '' ) {
445 if(( OFF
== config_get( 'send_reset_password' ) ) ||
( OFF
== config_get( 'enable_email_notification' ) ) ) {
449 # @@@ thraxisp - removed to address #6084 - user won't have any settings yet,
450 # use same language as display for the email
451 # lang_push( user_pref_get_language( $p_user_id ) );
452 # retrieve the username and email
453 $t_username = user_get_field( $p_user_id, 'username' );
454 $t_email = user_get_email( $p_user_id );
456 # Build Welcome Message
457 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' );
459 //if( $p_admin_created && $p_admin_name) {
460 if( $p_admin_name ) {
461 $intro_text = sprintf( lang_get( 'new_account_greeting_admincreated' ), $p_admin_name, $t_username );
463 $intro_text = sprintf( lang_get( 'new_account_greeting' ), $t_username );
466 $t_message = $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' );
468 # Send signup email regardless of mail notification pref
469 # or else users won't be able to sign up
470 if( !is_blank( $t_email ) ) {
471 email_store( $t_email, $t_subject, $t_message );
472 log_event( LOG_EMAIL
, sprintf( 'Signup Email = %s, Hash = %s, User = @U%d', $t_email, $p_confirm_hash, $p_user_id ) );
474 if( OFF
== config_get( 'email_send_using_cronjob' ) ) {
479 # lang_pop(); # see above
483 * Send confirm_hash url to user forgets the password
484 * @param int $p_user_id
485 * @param string $p_confirm_hash
488 function email_send_confirm_hash_url( $p_user_id, $p_confirm_hash ) {
489 if(( OFF
== config_get( 'send_reset_password' ) ) ||
( OFF
== config_get( 'enable_email_notification' ) ) ) {
493 lang_push( user_pref_get_language( $p_user_id ) );
495 # retrieve the username and email
496 $t_username = user_get_field( $p_user_id, 'username' );
497 $t_email = user_get_email( $p_user_id );
499 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'lost_password_subject' );
501 $t_message = lang_get( 'reset_request_msg' ) . " \n\n" . string_get_confirm_hash_url( $p_user_id, $p_confirm_hash ) . " \n\n" . lang_get( 'new_account_username' ) . ' ' . $t_username . " \n" . lang_get( 'new_account_IP' ) . ' ' . $_SERVER["REMOTE_ADDR"] . " \n\n" . lang_get( 'new_account_do_not_reply' );
503 # Send password reset regardless of mail notification prefs
504 # or else users won't be able to receive their reset pws
505 if( !is_blank( $t_email ) ) {
506 email_store( $t_email, $t_subject, $t_message );
507 log_event( LOG_EMAIL
, sprintf( 'Password reset for email = %s', $t_email ) );
509 if( OFF
== config_get( 'email_send_using_cronjob' ) ) {
518 * notify the selected group a new user has signup
519 * @param string $p_username
520 * @param string $p_email
523 function email_notify_new_account( $p_username, $p_email ) {
526 $t_threshold_min = config_get( 'notify_new_user_created_threshold_min' );
527 $t_threshold_users = project_get_all_user_rows( ALL_PROJECTS
, $t_threshold_min );
529 foreach( $t_threshold_users as $t_user ) {
530 lang_push( user_pref_get_language( $t_user['id'] ) );
532 $t_recipient_email = user_get_email( $t_user['id'] );
533 $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' );
535 $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" . $g_path . "\n\n" . lang_get( 'new_account_do_not_reply' );
537 if( !is_blank( $t_recipient_email ) ) {
538 email_store( $t_recipient_email, $t_subject, $t_message );
539 log_event( LOG_EMAIL
, sprintf( 'New Account Notify for email = \'%s\'', $t_recipient_email ) );
541 if( OFF
== config_get( 'email_send_using_cronjob' ) ) {
552 * send a generic email
553 * $p_notify_type: use check who she get notified of such event.
554 * $p_message_id: message id to be translated and included at the top of the email message.
555 * Return false if it were problems sending email * @param string
556 * @param int $p_bug_id
557 * @param string $p_notify_type
558 * @param int $p_message_id
559 * @param array $p_header_optional_params = null
560 * @param array $p_extra_user_ids_to_email
563 function email_generic( $p_bug_id, $p_notify_type, $p_message_id = null, $p_header_optional_params = null, $p_extra_user_ids_to_email = array() ) {
566 if( ON
=== config_get( 'enable_email_notification' ) ) {
567 ignore_user_abort( true );
569 bugnote_get_all_bugnotes( $p_bug_id );
571 # @todo yarick123: email_collect_recipients(...) will be completely rewritten to provide additional information such as language, user access,..
572 # @todo yarick123:sort recipients list by language to reduce switches between different languages
573 $t_recipients = email_collect_recipients( $p_bug_id, $p_notify_type, $p_extra_user_ids_to_email );
575 $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
577 if( is_array( $t_recipients ) ) {
578 # send email to every recipient
579 foreach( $t_recipients as $t_user_id => $t_user_email ) {
580 log_event( LOG_EMAIL
, sprintf( "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 ) );
582 # load (push) user language here as build_visible_bug_data assumes current language
583 lang_push( user_pref_get_language( $t_user_id, $t_project_id ) );
585 $t_visible_bug_data = email_build_visible_bug_data( $t_user_id, $p_bug_id, $p_message_id );
586 $t_ok = email_bug_info_to_one_user( $t_visible_bug_data, $p_message_id, $t_project_id, $t_user_id, $p_header_optional_params ) && $t_ok;
592 # Only trigger the draining of the email queue if cronjob is disabled and email notifications are enabled.
593 if( OFF
== config_get( 'email_send_using_cronjob' ) ) {
602 * Send notices that a user is now monitoring the bug. Typically this will only be sent when the added
603 * user is not the logged in user. This is assuming that receive own notifications is OFF (default).
604 * @param int $p_bug_id
605 * @param int $p_user_id
608 function email_monitor_added( $p_bug_id, $p_user_id ) {
609 log_event( LOG_EMAIL
, sprintf( 'Issue #%d monitored by user @U%d', $p_bug_id, $p_user_id ) );
612 $t_opt[] = bug_format_id( $p_bug_id );
613 $t_opt[] = user_get_name( $p_user_id );
615 email_generic( $p_bug_id, 'monitor', 'email_notification_title_for_action_monitor', $t_opt, array( $p_user_id ) );
619 * send notices when a relationship is ADDED
620 * @param int $p_bug_id
621 * @param int $p_related_bug_id
622 * @param int $p_rel_type
625 function email_relationship_added( $p_bug_id, $p_related_bug_id, $p_rel_type ) {
626 log_event( LOG_EMAIL
, sprintf( 'Relationship added: Issue #%d, related issue %d, relationship type %s.', $p_bug_id, $p_related_bug_id, $p_rel_type ) );
629 $t_opt[] = bug_format_id( $p_related_bug_id );
630 global $g_relationships;
631 if( !isset( $g_relationships[$p_rel_type] ) ) {
632 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND
, ERROR
);
634 email_generic( $p_bug_id, 'relation', $g_relationships[$p_rel_type]['#notify_added'], $t_opt );
638 * send notices when a relationship is DELETED
639 * @param int $p_bug_id
640 * @param int $p_related_bug_id
641 * @param int $p_rel_type
644 function email_relationship_deleted( $p_bug_id, $p_related_bug_id, $p_rel_type ) {
645 log_event( LOG_EMAIL
, sprintf( 'Relationship deleted: Issue #%d, related issue %d, relationship type %s.', $p_bug_id, $p_related_bug_id, $p_rel_type ) );
648 $t_opt[] = bug_format_id( $p_related_bug_id );
649 global $g_relationships;
650 if( !isset( $g_relationships[$p_rel_type] ) ) {
651 trigger_error( ERROR_RELATIONSHIP_NOT_FOUND
, ERROR
);
653 email_generic( $p_bug_id, 'relation', $g_relationships[$p_rel_type]['#notify_deleted'], $t_opt );
657 * send notices to all the handlers of the parent bugs when a child bug is RESOLVED
658 * @param int $p_bug_id
661 function email_relationship_child_resolved( $p_bug_id ) {
662 email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_resolved' );
666 * send notices to all the handlers of the parent bugs when a child bug is CLOSED
667 * @param int $p_bug_id
670 function email_relationship_child_closed( $p_bug_id ) {
671 email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_closed' );
675 * send notices to all the handlers of the parent bugs still open when a child bug is resolved/closed
677 * @param int $p_bug_id
678 * @param int $p_message_id
681 function email_relationship_child_resolved_closed( $p_bug_id, $p_message_id ) {
682 # retrieve all the relationships in which the bug is the destination bug
683 $t_relationship = relationship_get_all_dest( $p_bug_id );
684 $t_relationship_count = count( $t_relationship );
685 if( $t_relationship_count == 0 ) {
686 # no parent bug found
690 for( $i = 0;$i < $t_relationship_count;$i++
) {
691 if( $t_relationship[$i]->type
== BUG_DEPENDANT
) {
692 $t_src_bug_id = $t_relationship[$i]->src_bug_id
;
693 $t_status = bug_get_field( $t_src_bug_id, 'status' );
694 if( $t_status < config_get( 'bug_resolved_status_threshold' ) ) {
696 # sent the notification just for parent bugs not resolved/closed
698 $t_opt[] = bug_format_id( $p_bug_id );
699 email_generic( $t_src_bug_id, 'handler', $p_message_id, $t_opt );
706 * send notices when a bug is sponsored
707 * @param int $p_bug_id
710 function email_sponsorship_added( $p_bug_id ) {
711 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_added' );
715 * send notices when a sponsorship is modified
716 * @param int $p_bug_id
719 function email_sponsorship_updated( $p_bug_id ) {
720 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_updated' );
724 * send notices when a sponsorship is deleted
725 * @param int $p_bug_id
728 function email_sponsorship_deleted( $p_bug_id ) {
729 email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_deleted' );
733 * send notices when a new bug is added
734 * @param int $p_bug_id
737 function email_new_bug( $p_bug_id ) {
738 email_generic( $p_bug_id, 'new', 'email_notification_title_for_action_bug_submitted' );
742 * send notices when a new bugnote
743 * @param int $p_bug_id
746 function email_bugnote_add( $p_bug_id ) {
747 email_generic( $p_bug_id, 'bugnote', 'email_notification_title_for_action_bugnote_submitted' );
751 * send notices when a bug is RESOLVED
752 * @param int $p_bug_id
755 function email_resolved( $p_bug_id ) {
756 email_generic( $p_bug_id, 'resolved', 'email_notification_title_for_status_bug_resolved' );
760 * send notices when a bug is CLOSED
761 * @param int $p_bug_id
764 function email_close( $p_bug_id ) {
765 email_generic( $p_bug_id, 'closed', 'email_notification_title_for_status_bug_closed' );
769 * send notices when a bug is REOPENED
770 * @param int $p_bug_id
773 function email_reopen( $p_bug_id ) {
774 email_generic( $p_bug_id, 'reopened', 'email_notification_title_for_action_bug_reopened' );
778 * send notices when a bug is ASSIGNED
779 * @param int $p_bug_id
782 function email_assign( $p_bug_id ) {
783 email_generic( $p_bug_id, 'owner', 'email_notification_title_for_action_bug_assigned' );
787 * send notices when a bug is DELETED
788 * @param int $p_bug_id
791 function email_bug_deleted( $p_bug_id ) {
792 email_generic( $p_bug_id, 'deleted', 'email_notification_title_for_action_bug_deleted' );
796 * Store email in queue for sending
798 * @param string $p_recipient
799 * @param string $p_subject
800 * @param string $p_message
801 * @param array $p_headers
804 function email_store( $p_recipient, $p_subject, $p_message, $p_headers = null ) {
805 $t_recipient = trim( $p_recipient );
806 $t_subject = string_email( trim( $p_subject ) );
807 $t_message = string_email_links( trim( $p_message ) );
809 # short-circuit if no recipient is defined, or email disabled
810 # note that this may cause signup messages not to be sent
812 if( is_blank( $p_recipient ) ||
( OFF
== config_get( 'enable_email_notification' ) ) ) {
816 $t_email_data = new EmailData
;
818 $t_email_data->email
= $t_recipient;
819 $t_email_data->subject
= $t_subject;
820 $t_email_data->body
= $t_message;
821 $t_email_data->metadata
= array();
822 $t_email_data->metadata
['headers'] = $p_headers === null ?
array() : $p_headers;
823 $t_email_data->metadata
['priority'] = config_get( 'mail_priority' );
825 # Urgent = 1, Not Urgent = 5, Disable = 0
826 $t_email_data->metadata
['charset'] = 'utf-8';
829 $t_server = isset( $_SERVER ) ?
$_SERVER : $HTTP_SERVER_VARS;
830 if( isset( $t_server['SERVER_NAME'] ) ) {
831 $t_hostname = $t_server['SERVER_NAME'];
833 $t_address = explode( '@', config_get( 'from_email' ) );
834 if( isset( $t_address[1] ) ) {
835 $t_hostname = $t_address[1];
838 $t_email_data->metadata
['hostname'] = $t_hostname;
840 $t_email_id = email_queue_add( $t_email_data );
846 * This function sends all the emails that are stored in the queue. If a failure occurs, then the
847 * function exists. This function will be called after storing emails in case of synchronous
848 * emails, or will be called from a cronjob in case of asynchronous emails.
849 * @todo In case of synchronous email sending, we may get a race condition where two requests send the same email.
850 * @param bool $p_delete_on_failure indicates whether to remove email from queue on failure (default false)
853 function email_send_all($p_delete_on_failure = false) {
854 $t_ids = email_queue_get_ids();
856 $t_emails_recipients_failed = array();
857 $t_start = microtime(true);
858 foreach( $t_ids as $t_id ) {
859 $t_email_data = email_queue_get( $t_id );
861 # check if email was not found. This can happen if another request picks up the email first and sends it.
862 if( $t_email_data === false ) {
866 # if unable to place the email in the email server queue, then the connection to the server is down,
867 # and hence no point to continue trying with the rest of the emails.
868 if( !email_send( $t_email_data ) ) {
869 if ($p_delete_on_failure) {
870 email_queue_delete( $t_email_data->email_id
);
872 if( microtime(true) - $t_start > 5 ) {
882 * This function sends an email message based on the supplied email data.
884 * @param EmailData $p_email_data
887 function email_send( $p_email_data ) {
890 $t_email_data = $p_email_data;
892 $t_recipient = trim( $t_email_data->email
);
893 $t_subject = string_email( trim( $t_email_data->subject
) );
894 $t_message = string_email_links( trim( $t_email_data->body
) );
896 $t_debug_email = config_get( 'debug_email' );
897 $t_mailer_method = config_get( 'phpMailer_method' );
899 if( is_null( $g_phpMailer ) ) {
900 if ( $t_mailer_method == PHPMAILER_METHOD_SMTP
)
901 register_shutdown_function( 'email_smtp_close' );
902 if( !class_exists( 'PHPMailer' ) ) {
903 require_lib( 'phpmailer' . DIRECTORY_SEPARATOR
. 'class.phpmailer.php' );
905 $mail = new PHPMailer(true);
907 $mail = $g_phpMailer;
910 if( isset( $t_email_data->metadata
['hostname'] ) ) {
911 $mail->Hostname
= $t_email_data->metadata
['hostname'];
914 # @@@ should this be the current language (for the recipient) or the default one (for the user running the command) (thraxisp)
915 $t_lang = config_get( 'default_language' );
916 if( 'auto' == $t_lang ) {
917 $t_lang = config_get( 'fallback_language' );
919 $mail->SetLanguage( lang_get( 'phpmailer_language', $t_lang ) );
921 # Select the method to send mail
922 switch( config_get( 'phpMailer_method' ) ) {
923 case PHPMAILER_METHOD_MAIL
:
927 case PHPMAILER_METHOD_SENDMAIL
:
931 case PHPMAILER_METHOD_SMTP
:
934 // SMTP collection is always kept alive
935 $mail->SMTPKeepAlive
= true;
937 if ( !is_blank( config_get( 'smtp_username' ) ) ) {
938 # Use SMTP Authentication
939 $mail->SMTPAuth
= true;
940 $mail->Username
= config_get( 'smtp_username' );
941 $mail->Password
= config_get( 'smtp_password' );
944 if ( !is_blank( config_get( 'smtp_connection_mode' ) ) ) {
945 $mail->SMTPSecure
= config_get( 'smtp_connection_mode' );
948 $mail->Port
= config_get( 'smtp_port' );
953 $mail->IsHTML( false ); # set email format to plain text
954 $mail->WordWrap
= 80; # set word wrap to 50 characters
955 $mail->Priority
= $t_email_data->metadata
['priority']; # Urgent = 1, Not Urgent = 5, Disable = 0
956 $mail->CharSet
= $t_email_data->metadata
['charset'];
957 $mail->Host
= config_get( 'smtp_host' );
958 $mail->From
= config_get( 'from_email' );
959 $mail->Sender
= config_get( 'return_path_email' );
960 $mail->FromName
= config_get( 'from_name' );
962 if( OFF
!== $t_debug_email ) {
963 $t_message = 'To: ' . $t_recipient . "\n\n" . $t_message;
965 $mail->AddAddress( $t_debug_email, '' );
966 } catch ( phpmailerException
$e ) {
968 $mail->ClearAllRecipients();
969 $mail->ClearAttachments();
970 $mail->ClearReplyTos();
971 $mail->ClearCustomHeaders();
976 $mail->AddAddress( $t_recipient, '' );
977 } catch ( phpmailerException
$e ) {
979 $mail->ClearAllRecipients();
980 $mail->ClearAttachments();
981 $mail->ClearReplyTos();
982 $mail->ClearCustomHeaders();
987 $mail->Subject
= $t_subject;
988 $mail->Body
= make_lf_crlf( "\n" . $t_message );
990 if( isset( $t_email_data->metadata
['headers'] ) && is_array( $t_email_data->metadata
['headers'] ) ) {
991 foreach( $t_email_data->metadata
['headers'] as $t_key => $t_value ) {
994 /* Note: hostname can never be blank here as we set metadata['hostname']
995 in email_store() where mail gets queued. */
996 if ( !strchr( $t_value, '@' ) && !is_blank( $mail->Hostname
) ) {
997 $t_value = $t_value . '@' . $mail->Hostname
;
999 $mail->set( 'MessageID', "<$t_value>" );
1002 $mail->AddCustomHeader( "$t_key: <{$t_value}@{$mail->Hostname}>" );
1005 $mail->AddCustomHeader( "$t_key: $t_value" );
1013 if ( !$mail->Send() ) {
1018 if ( $t_email_data->email_id
> 0 ) {
1019 email_queue_delete( $t_email_data->email_id
);
1023 catch ( phpmailerException
$e )
1028 $mail->ClearAllRecipients();
1029 $mail->ClearAttachments();
1030 $mail->ClearReplyTos();
1031 $mail->ClearCustomHeaders();
1037 * closes opened kept alive SMTP connection (if it was opened)
1042 function email_smtp_close() {
1043 global $g_phpMailer;
1045 if( !is_null( $g_phpMailer ) ) {
1046 if( $g_phpMailer->smtp
->Connected() ) {
1047 $g_phpMailer->smtp
->Quit();
1048 $g_phpMailer->smtp
->Close();
1050 $g_phpMailer = null;
1055 * formats the subject correctly
1056 * we include the project name, bug id, and summary.
1058 * @param int $p_bug_id
1061 function email_build_subject( $p_bug_id ) {
1062 # grab the project name
1063 $p_project_name = project_get_field( bug_get_field( $p_bug_id, 'project_id' ), 'name' );
1065 # grab the subject (summary)
1066 $p_subject = bug_get_field( $p_bug_id, 'summary' );
1068 # padd the bug id with zeros
1069 $p_bug_id = bug_format_id( $p_bug_id );
1071 return '[' . $p_project_name . ' ' . $p_bug_id . ']: ' . $p_subject;
1075 * clean up LF to CRLF
1077 * @param string $p_string
1080 function make_lf_crlf( $p_string ) {
1081 $t_string = str_replace( "\n", "\r\n", $p_string );
1082 return str_replace( "\r\r\n", "\r\n", $t_string );
1086 * Appends an email domain to the specified email address if email is
1087 * not empty, it doesn't already have a domain part and if a
1088 * limit_email_domain is configured.
1090 * Check limit_email_domain option and append the domain name if it is set
1091 * @todo limit_email_domain called after we look for @ in domain name?
1092 * @param string $p_email The email address to append the domain to.
1093 * @returns The email address with the appended domain (if applicable).
1095 function email_append_domain( $p_email ) {
1096 # If email is empty or already contains a domain, then return as is.
1097 if ( is_blank( $p_email ) ||
strchr( $p_email, '@' ) ) {
1101 # If limit email domain is set, then append it.
1102 $t_limit_email_domain = config_get( 'limit_email_domain' );
1103 if ( $t_limit_email_domain === OFF
) {
1107 return "$p_email@$t_limit_email_domain";
1111 * Send a bug reminder to each of the given user, or to each user if the first parameter is an array
1112 * return an array of usernames to which the reminder was successfully sent
1114 * @todo I'm not sure this shouldn't return an array of user ids... more work for the caller but cleaner from an API point of view.
1115 * @param array $p_recipients
1116 * @param int $p_bug_id
1117 * @param string $p_message
1120 function email_bug_reminder( $p_recipients, $p_bug_id, $p_message ) {
1121 if( !is_array( $p_recipients ) ) {
1122 $p_recipients = array(
1127 $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
1128 $t_sender_id = auth_get_current_user_id();
1129 $t_sender = user_get_name( $t_sender_id );
1131 $t_subject = email_build_subject( $p_bug_id );
1132 $t_date = date( config_get( 'normal_date_format' ) );
1135 foreach( $p_recipients as $t_recipient ) {
1136 lang_push( user_pref_get_language( $t_recipient, $t_project_id ) );
1138 $t_email = user_get_email( $t_recipient );
1139 $result[] = user_get_name( $t_recipient );
1141 if( access_has_project_level( config_get( 'show_user_email_threshold' ), $t_project_id, $t_recipient ) ) {
1142 $t_sender_email = ' <' . current_user_get_field( 'email' ) . '>';
1144 $t_sender_email = '';
1146 $t_header = "\n" . lang_get( 'on_date' ) . " $t_date, $t_sender $t_sender_email " . lang_get( 'sent_you_this_reminder_about' ) . ": \n\n";
1147 $t_contents = $t_header . string_get_bug_view_url_with_fqdn( $p_bug_id, $t_recipient ) . " \n\n$p_message";
1149 if( ON
== config_get( 'enable_email_notification' ) ) {
1150 email_store( $t_email, $t_subject, $t_contents );
1156 if( OFF
== config_get( 'email_send_using_cronjob' ) ) {
1164 * Send bug info to given user
1165 * return true on success
1166 * @param array $p_visible_bug_data
1167 * @param string $p_message_id
1168 * @param int $p_project_id
1169 * @param int $p_user_id
1170 * @param array $p_header_optional_params
1173 function email_bug_info_to_one_user( $p_visible_bug_data, $p_message_id, $p_project_id, $p_user_id, $p_header_optional_params = null ) {
1174 $t_user_email = user_get_email( $p_user_id );
1176 # check whether email should be sent
1177 # @@@ can be email field empty? if yes - then it should be handled here
1178 if( ON
!== config_get( 'enable_email_notification' ) ||
is_blank( $t_user_email ) ) {
1183 $t_subject = '[' . $p_visible_bug_data['email_project'] . ' ' . bug_format_id( $p_visible_bug_data['email_bug'] ) . ']: ' . $p_visible_bug_data['email_summary'];
1187 $t_message = lang_get_defaulted( $p_message_id, null );
1189 if( is_array( $p_header_optional_params ) ) {
1190 $t_message = vsprintf( $t_message, $p_header_optional_params );
1193 if(( $t_message !== null ) && ( !is_blank( $t_message ) ) ) {
1194 $t_message .= " \n";
1197 $t_message .= email_format_bug_message( $p_visible_bug_data );
1200 $t_bug_id = $p_visible_bug_data['email_bug'];
1201 $t_message_md5 = md5( $t_bug_id . $p_visible_bug_data['email_date_submitted'] );
1202 $t_mail_headers = array(
1203 'keywords' => $p_visible_bug_data['set_category'],
1205 if( $p_message_id == 'email_notification_title_for_action_bug_submitted' ) {
1206 $t_mail_headers['Message-ID'] = $t_message_md5;
1208 $t_mail_headers['In-Reply-To'] = $t_message_md5;
1212 $t_ok = email_store( $t_user_email, $t_subject, $t_message, $t_mail_headers );
1218 * Build the bug info part of the message
1219 * @param array $p_visible_bug_data
1222 function email_format_bug_message( $p_visible_bug_data ) {
1223 $t_normal_date_format = config_get( 'normal_date_format' );
1224 $t_complete_date_format = config_get( 'complete_date_format' );
1226 $t_email_separator1 = config_get( 'email_separator1' );
1227 $t_email_separator2 = config_get( 'email_separator2' );
1228 $t_email_padding_length = config_get( 'email_padding_length' );
1230 $t_status = $p_visible_bug_data['email_status'];
1232 $p_visible_bug_data['email_date_submitted'] = date( $t_complete_date_format, $p_visible_bug_data['email_date_submitted'] );
1233 $p_visible_bug_data['email_last_modified'] = date( $t_complete_date_format, $p_visible_bug_data['email_last_modified'] );
1235 $p_visible_bug_data['email_status'] = get_enum_element( 'status', $t_status );
1236 $p_visible_bug_data['email_severity'] = get_enum_element( 'severity', $p_visible_bug_data['email_severity'] );
1237 $p_visible_bug_data['email_priority'] = get_enum_element( 'priority', $p_visible_bug_data['email_priority'] );
1238 $p_visible_bug_data['email_reproducibility'] = get_enum_element( 'reproducibility', $p_visible_bug_data['email_reproducibility'] );
1240 $t_message = $t_email_separator1 . " \n";
1242 if( isset( $p_visible_bug_data['email_bug_view_url'] ) ) {
1243 $t_message .= $p_visible_bug_data['email_bug_view_url'] . " \n";
1244 $t_message .= $t_email_separator1 . " \n";
1247 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reporter' );
1248 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_handler' );
1249 $t_message .= $t_email_separator1 . " \n";
1250 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_project' );
1251 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_bug' );
1252 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_category' );
1253 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reproducibility' );
1254 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_severity' );
1255 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_priority' );
1256 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_status' );
1257 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_target_version' );
1259 # custom fields formatting
1260 foreach( $p_visible_bug_data['custom_fields'] as $t_custom_field_name => $t_custom_field_data ) {
1261 $t_message .= utf8_str_pad( lang_get_defaulted( $t_custom_field_name, null ) . ': ', $t_email_padding_length, ' ', STR_PAD_RIGHT
);
1262 $t_message .= string_custom_field_value_for_email( $t_custom_field_data['value'], $t_custom_field_data['type'] );
1263 $t_message .= " \n";
1266 # end foreach custom field
1268 if( config_get( 'bug_resolved_status_threshold' ) <= $t_status ) {
1269 $p_visible_bug_data['email_resolution'] = get_enum_element( 'resolution', $p_visible_bug_data['email_resolution'] );
1270 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_resolution' );
1271 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_fixed_in_version' );
1273 $t_message .= $t_email_separator1 . " \n";
1275 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_date_submitted' );
1276 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_last_modified' );
1277 $t_message .= $t_email_separator1 . " \n";
1279 $t_message .= email_format_attribute( $p_visible_bug_data, 'email_summary' );
1281 $t_message .= lang_get( 'email_description' ) . ": \n" . $p_visible_bug_data['email_description'] . "\n";
1283 if ( !is_blank( $p_visible_bug_data['email_steps_to_reproduce'] ) ) {
1284 $t_message .= "\n" . lang_get( 'email_steps_to_reproduce' ) . ": \n" . $p_visible_bug_data['email_steps_to_reproduce'] . "\n";
1287 if ( !is_blank( $p_visible_bug_data['email_additional_information'] ) ) {
1288 $t_message .= "\n" . lang_get( 'email_additional_information' ) . ": \n" . $p_visible_bug_data['email_additional_information'] . "\n";
1291 if( isset( $p_visible_bug_data['relations'] ) ) {
1292 if( $p_visible_bug_data['relations'] != '' ) {
1293 $t_message .= $t_email_separator1 . "\n" . str_pad( lang_get( 'bug_relationships' ), 20 ) . str_pad( lang_get( 'id' ), 8 ) . lang_get( 'summary' ) . "\n" . $t_email_separator2 . "\n" . $p_visible_bug_data['relations'];
1298 if( isset( $p_visible_bug_data['sponsorship_total'] ) && ( $p_visible_bug_data['sponsorship_total'] > 0 ) ) {
1299 $t_message .= $t_email_separator1 . " \n";
1300 $t_message .= sprintf( lang_get( 'total_sponsorship_amount' ), sponsorship_format_amount( $p_visible_bug_data['sponsorship_total'] ) ) . "\n" . "\n";
1302 if( isset( $p_visible_bug_data['sponsorships'] ) ) {
1303 foreach( $p_visible_bug_data['sponsorships'] as $t_sponsorship ) {
1304 $t_date_added = date( config_get( 'normal_date_format' ), $t_sponsorship->date_submitted
);
1306 $t_message .= $t_date_added . ': ';
1307 $t_message .= user_get_name( $t_sponsorship->user_id
);
1308 $t_message .= ' (' . sponsorship_format_amount( $t_sponsorship->amount
) . ')' . " \n";
1313 $t_message .= $t_email_separator1 . " \n\n";
1316 foreach( $p_visible_bug_data['bugnotes'] as $t_bugnote ) {
1317 $t_last_modified = date( $t_normal_date_format, $t_bugnote->last_modified
);
1319 $t_formatted_bugnote_id = bugnote_format_id( $t_bugnote->id
);
1320 $t_bugnote_link = string_process_bugnote_link( config_get( 'bugnote_link_tag' ) . $t_bugnote->id
, false, false, true );
1322 if( $t_bugnote->time_tracking
> 0 ) {
1323 $t_time_tracking = ' ' . lang_get( 'time_tracking' ) . ' ' . db_minutes_to_hhmm( $t_bugnote->time_tracking
) . "\n";
1325 $t_time_tracking = '';
1328 if( user_exists( $t_bugnote->reporter_id
) ) {
1329 $t_access_level = access_get_project_level( $p_visible_bug_data['email_project_id'] , $t_bugnote->reporter_id
);
1330 $t_access_level_string = ' (' . get_enum_element( 'access_levels', $t_access_level ) . ') - ';
1332 $t_access_level_string = '';
1335 $t_string = ' (' . $t_formatted_bugnote_id . ') ' . user_get_name( $t_bugnote->reporter_id
) . $t_access_level_string . $t_last_modified . "\n" . $t_time_tracking . ' ' . $t_bugnote_link;
1337 $t_message .= $t_email_separator2 . " \n";
1338 $t_message .= $t_string . " \n";
1339 $t_message .= $t_email_separator2 . " \n";
1340 $t_message .= $t_bugnote->note
. " \n\n";
1344 if( array_key_exists( 'history', $p_visible_bug_data ) ) {
1345 $t_message .= lang_get( 'bug_history' ) . " \n";
1346 $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";
1348 $t_message .= $t_email_separator1 . " \n";
1350 foreach( $p_visible_bug_data['history'] as $t_raw_history_item ) {
1351 $t_localized_item = history_localize_item( $t_raw_history_item['field'], $t_raw_history_item['type'], $t_raw_history_item['old_value'], $t_raw_history_item['new_value'], false );
1353 $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";
1355 $t_message .= $t_email_separator1 . " \n\n";
1362 * if $p_visible_bug_data contains specified attribute the function
1363 * returns concatenated translated attribute name and original
1364 * attribute value. Else return empty string.
1365 * @param array $p_visible_bug_data
1366 * @param string $p_attribute_id
1369 function email_format_attribute( $p_visible_bug_data, $attribute_id ) {
1370 if( array_key_exists( $attribute_id, $p_visible_bug_data ) ) {
1371 return utf8_str_pad( lang_get( $attribute_id ) . ': ', config_get( 'email_padding_length' ), ' ', STR_PAD_RIGHT
) . $p_visible_bug_data[$attribute_id] . "\n";
1377 * Build the bug raw data visible for specified user to be translated and sent by email to the user
1378 * (Filter the bug data according to user access level)
1379 * return array with bug data. See usage in email_format_bug_message(...)
1380 * @param int $p_user_id
1381 * @param int $p_bug_id
1382 * @param string $p_message_id
1385 function email_build_visible_bug_data( $p_user_id, $p_bug_id, $p_message_id ) {
1386 $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
1387 $t_user_access_level = user_get_access_level( $p_user_id, $t_project_id );
1388 $t_user_bugnote_order = user_pref_get_pref( $p_user_id, 'bugnote_order' );
1389 $t_user_bugnote_limit = user_pref_get_pref( $p_user_id, 'email_bugnote_limit' );
1391 $row = bug_get_extended_row( $p_bug_id );
1392 $t_bug_data = array();
1394 $t_bug_data['email_bug'] = $p_bug_id;
1396 if( $p_message_id !== 'email_notification_title_for_action_bug_deleted' ) {
1397 $t_bug_data['email_bug_view_url'] = string_get_bug_view_url_with_fqdn( $p_bug_id );
1400 if( access_compare_level( $t_user_access_level, config_get( 'view_handler_threshold' ) ) ) {
1401 if( 0 != $row['handler_id'] ) {
1402 $t_bug_data['email_handler'] = user_get_name( $row['handler_id'] );
1404 $t_bug_data['email_handler'] = '';
1408 $t_bug_data['email_reporter'] = user_get_name( $row['reporter_id'] );
1409 $t_bug_data['email_project_id'] = $row['project_id'];
1410 $t_bug_data['email_project'] = project_get_field( $row['project_id'], 'name' );
1412 $t_category_name = category_full_name( $row['category_id'], false );
1413 $t_bug_data['email_category'] = $t_category_name;
1415 $t_bug_data['email_date_submitted'] = $row['date_submitted'];
1416 $t_bug_data['email_last_modified'] = $row['last_updated'];
1418 $t_bug_data['email_status'] = $row['status'];
1419 $t_bug_data['email_severity'] = $row['severity'];
1420 $t_bug_data['email_priority'] = $row['priority'];
1421 $t_bug_data['email_reproducibility'] = $row['reproducibility'];
1423 $t_bug_data['email_resolution'] = $row['resolution'];
1424 $t_bug_data['email_fixed_in_version'] = $row['fixed_in_version'];
1426 if( !is_blank( $row['target_version'] ) && access_compare_level( $t_user_access_level, config_get( 'roadmap_view_threshold' ) ) ) {
1427 $t_bug_data['email_target_version'] = $row['target_version'];
1430 $t_bug_data['email_summary'] = $row['summary'];
1431 $t_bug_data['email_description'] = $row['description'];
1432 $t_bug_data['email_additional_information'] = $row['additional_information'];
1433 $t_bug_data['email_steps_to_reproduce'] = $row['steps_to_reproduce'];
1435 $t_bug_data['set_category'] = '[' . $t_bug_data['email_project'] . '] ' . $t_category_name;
1437 $t_bug_data['custom_fields'] = custom_field_get_linked_fields( $p_bug_id, $t_user_access_level );
1438 $t_bug_data['bugnotes'] = bugnote_get_all_visible_bugnotes( $p_bug_id, $t_user_bugnote_order, $t_user_bugnote_limit, $p_user_id );
1441 if(( ON
== config_get( 'history_default_visible' ) ) && access_compare_level( $t_user_access_level, config_get( 'view_history_threshold' ) ) ) {
1442 $t_bug_data['history'] = history_get_raw_events_array( $p_bug_id, $p_user_id );
1445 # Sponsorship Information
1446 if(( config_get( 'enable_sponsorship' ) == ON
) && ( access_has_bug_level( config_get( 'view_sponsorship_total_threshold' ), $p_bug_id, $p_user_id ) ) ) {
1447 $t_sponsorship_ids = sponsorship_get_all_ids( $p_bug_id );
1448 $t_bug_data['sponsorship_total'] = sponsorship_get_amount( $t_sponsorship_ids );
1450 if( access_has_bug_level( config_get( 'view_sponsorship_details_threshold' ), $p_bug_id, $p_user_id ) ) {
1451 $t_bug_data['sponsorships'] = array();
1452 foreach( $t_sponsorship_ids as $id ) {
1453 $t_bug_data['sponsorships'][] = sponsorship_get( $id );
1458 $t_bug_data['relations'] = relationship_get_summary_text( $p_bug_id );