[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/core/ -> email_api.php (source)

   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 (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
  23   * @copyright Copyright (C) 2002 - 2011  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  
  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' );
  74  
  75  /**
  76   * reusable object of class SMTP
  77   */
  78   $g_phpMailer = null;
  79  
  80  /**
  81   *
  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.
  85   * @return string
  86   */
  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
  90  }
  91  
  92  /**
  93   * check to see that the format is valid and that the mx record exists
  94   * @param string $p_email
  95   * @return bool
  96   */
  97  function email_is_valid( $p_email ) {
  98      # if we don't validate then just accept
  99      if( OFF == config_get( 'validate_email' ) ) {
 100          return true;
 101      }
 102  
 103      if ( ON == config_get( 'use_ldap_email' ) ) {
 104          return true;
 105      }
 106  
 107      if( is_blank( $p_email ) && ON == config_get( 'allow_blank_email' ) ) {
 108          return true;
 109      }
 110  
 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];
 116  
 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 ) ) {
 121                  return false;
 122              }
 123          }
 124  
 125          if( ON == config_get( 'check_mx_record' ) ) {
 126              $temp = '';
 127  
 128              # Check for valid mx records
 129              if( getmxrr( $t_domain, $temp ) ) {
 130                  return true;
 131              } else {
 132                  $host = $t_domain . '.';
 133  
 134                  # for no mx record... try dns check
 135                  if( checkdnsrr( $host, 'ANY' ) ) {
 136                      return true;
 137                  }
 138              }
 139          } else {
 140              # Email format was valid but did't check for valid mx records
 141              return true;
 142          }
 143      }
 144  
 145      # Everything failed.  The email is invalid
 146      return false;
 147  }
 148  
 149  /**
 150   * Check if the email address is valid
 151   * trigger an ERROR if it isn't
 152   * @param string $p_email
 153   * @return null
 154   */
 155  function email_ensure_valid( $p_email ) {
 156      if( !email_is_valid( $p_email ) ) {
 157          trigger_error( ERROR_EMAIL_INVALID, ERROR );
 158      }
 159  }
 160  
 161  /**
 162   * Check if the email address is disposable
 163   * @param string $p_email
 164   * @return bool
 165   */
 166  function email_is_disposable( $p_email ) {
 167      if( !class_exists( 'DisposableEmailChecker' ) ) {
 168          require_lib( 'disposable' . DIRECTORY_SEPARATOR . 'disposable.php' );
 169      }
 170  
 171      return DisposableEmailChecker::is_disposable_email( $p_email );
 172  }
 173  
 174  /**
 175   * Check if the email address is disposable
 176   * trigger an ERROR if it isn't
 177   * @param string $p_email
 178   * @return null
 179   */
 180  function email_ensure_not_disposable( $p_email ) {
 181      if( email_is_disposable( $p_email ) ) {
 182          trigger_error( ERROR_EMAIL_DISPOSABLE, ERROR );
 183      }
 184  }
 185  
 186  /**
 187   * email_notify_flag
 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
 191   * ON or OFF.
 192   * @param string $action
 193   * @param string $flag
 194   * @return int
 195   */
 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];
 201      }
 202      else if( isset( $t_default_notify_flags[$flag] ) ) {
 203          return $t_default_notify_flags[$flag];
 204      }
 205  
 206      return OFF;
 207  }
 208  
 209  /**
 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
 215   * @return array
 216   */
 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 );
 219  
 220      $t_recipients = array();
 221  
 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 ) );
 227          }
 228      }
 229  
 230      # add Reporter
 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 ) );
 235      }
 236  
 237      # add Handler
 238      if( ON == email_notify_flag( $p_notify_type, 'handler' ) ) {
 239          $t_handler_id = bug_get_field( $p_bug_id, 'handler_id' );
 240  
 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 ) );
 244          }
 245      }
 246  
 247      $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
 248  
 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 ) );
 256  
 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 ) );
 262          }
 263      }
 264  
 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;
 271  
 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 ) );
 278  
 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 ) );
 284          }
 285      }
 286  
 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'] ) );
 297              }
 298          }
 299      }
 300  
 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 ) );
 310                  }
 311              }
 312          }
 313      }
 314  
 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) {
 321          case 'new':
 322          case 'feedback': # This isn't really a built-in action (delete me!)
 323          case 'reopened':
 324          case 'resolved':
 325          case 'closed':
 326          case 'bugnote':
 327              $t_pref_field = 'email_on_' . $p_notify_type;
 328              break;
 329          case 'owner':
 330              # The email_on_assigned notification type is now effectively
 331              # email_on_change_of_handler.
 332              $t_pref_field = 'email_on_assigned';
 333              break;
 334          case 'deleted':
 335          case 'updated':
 336          case 'sponsor':
 337          case 'relation':
 338          case 'monitor':
 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;
 345              break;
 346          default:
 347              # Anything not built-in is probably going to be a status
 348              $t_pref_field = 'email_on_status';
 349              break;
 350      }
 351  
 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();
 355  
 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 );
 360  
 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 ) );
 367              continue;
 368          }
 369  
 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 ) );
 373              continue;
 374          }
 375  
 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 ) );
 381                  continue;
 382              } else {
 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' );
 388  
 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 ) );
 391                      continue;
 392                  }
 393              }
 394          }
 395  
 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 ) );
 400                  continue;
 401              }
 402          }
 403  
 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 ) );
 406          $t_exclude = false;
 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 ) {
 411                      $t_exclude = true;
 412                      log_event( LOG_EMAIL_RECIPIENT, sprintf( 'Issue = #%d, %s plugin dropped user @U%d', $p_bug_id, $t_plugin, $t_id ) );
 413                  }
 414              }
 415          }
 416  
 417          # user was excluded by a plugin
 418          if ( $t_exclude ) {
 419              continue;
 420          }
 421  
 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 ) );
 426          } else {
 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;
 430          }
 431      }
 432  
 433      return $t_final_recipients;
 434  }
 435  
 436  /**
 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
 442   * @return null
 443   */
 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' ) ) ) {
 446          return;
 447      }
 448  
 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 );
 455  
 456      # Build Welcome Message
 457      $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' );
 458  
 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 );
 462      } else {
 463          $intro_text = sprintf( lang_get( 'new_account_greeting' ), $t_username );
 464      }
 465  
 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' );
 467  
 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 ) );
 473  
 474          if( OFF == config_get( 'email_send_using_cronjob' ) ) {
 475              email_send_all();
 476          }
 477      }
 478  
 479      #        lang_pop(); # see above
 480  }
 481  
 482  /**
 483   * Send confirm_hash url to user forgets the password
 484   * @param int $p_user_id
 485   * @param string $p_confirm_hash
 486   * @return null
 487   */
 488  function email_send_confirm_hash_url( $p_user_id, $p_confirm_hash ) {
 489      if ( OFF == config_get( 'send_reset_password' ) ||
 490          OFF == config_get( 'enable_email_notification' ) ) {
 491          return;
 492      }
 493  
 494      lang_push( user_pref_get_language( $p_user_id ) );
 495  
 496      # retrieve the username and email
 497      $t_username = user_get_field( $p_user_id, 'username' );
 498      $t_email = user_get_email( $p_user_id );
 499  
 500      $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'lost_password_subject' );
 501  
 502      $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  
 504      # Send password reset regardless of mail notification prefs
 505      # or else users won't be able to receive their reset pws
 506      if( !is_blank( $t_email ) ) {
 507          email_store( $t_email, $t_subject, $t_message );
 508          log_event( LOG_EMAIL, sprintf( 'Password reset for email = %s', $t_email ) );
 509  
 510          if( OFF == config_get( 'email_send_using_cronjob' ) ) {
 511              email_send_all();
 512          }
 513      }
 514  
 515      lang_pop();
 516  }
 517  
 518  /**
 519   * notify the selected group a new user has signup
 520   * @param string $p_username
 521   * @param string $p_email
 522   * @return null
 523   */
 524  function email_notify_new_account( $p_username, $p_email ) {
 525      global $g_path;
 526  
 527      $t_threshold_min = config_get( 'notify_new_user_created_threshold_min' );
 528      $t_threshold_users = project_get_all_user_rows( ALL_PROJECTS, $t_threshold_min );
 529  
 530      foreach( $t_threshold_users as $t_user ) {
 531          lang_push( user_pref_get_language( $t_user['id'] ) );
 532  
 533          $t_recipient_email = user_get_email( $t_user['id'] );
 534          $t_subject = '[' . config_get( 'window_title' ) . '] ' . lang_get( 'new_account_subject' );
 535  
 536          $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  
 538          if( !is_blank( $t_recipient_email ) ) {
 539              email_store( $t_recipient_email, $t_subject, $t_message );
 540              log_event( LOG_EMAIL, sprintf( 'New Account Notify for email = \'%s\'', $t_recipient_email ) );
 541  
 542              if( OFF == config_get( 'email_send_using_cronjob' ) ) {
 543                  email_send_all();
 544              }
 545          }
 546  
 547          lang_pop();
 548      }
 549  }
 550  
 551  
 552  /**
 553   * send a generic email
 554   * $p_notify_type: use check who she get notified of such event.
 555   * $p_message_id: message id to be translated and included at the top of the email message.
 556   * Return false if it were problems sending email * @param string
 557   * @param int $p_bug_id
 558   * @param string $p_notify_type
 559   * @param int $p_message_id
 560   * @param array $p_header_optional_params = null
 561   * @param array $p_extra_user_ids_to_email
 562   * @return bool
 563   */
 564  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() ) {
 565      $t_ok = true;
 566  
 567      if( ON === config_get( 'enable_email_notification' ) ) {
 568          ignore_user_abort( true );
 569  
 570          bugnote_get_all_bugnotes( $p_bug_id );
 571  
 572          # @todo yarick123: email_collect_recipients(...) will be completely rewritten to provide additional information such as language, user access,..
 573          # @todo yarick123:sort recipients list by language to reduce switches between different languages
 574          $t_recipients = email_collect_recipients( $p_bug_id, $p_notify_type, $p_extra_user_ids_to_email );
 575  
 576          $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
 577  
 578          if( is_array( $t_recipients ) ) {
 579              # send email to every recipient
 580              foreach( $t_recipients as $t_user_id => $t_user_email ) {
 581                  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  
 583                  # load (push) user language here as build_visible_bug_data assumes current language
 584                  lang_push( user_pref_get_language( $t_user_id, $t_project_id ) );
 585  
 586                  $t_visible_bug_data = email_build_visible_bug_data( $t_user_id, $p_bug_id, $p_message_id );
 587                  $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;
 588  
 589                  lang_pop();
 590              }
 591          }
 592  
 593          # Only trigger the draining of the email queue if cronjob is disabled and email notifications are enabled.
 594          if( OFF == config_get( 'email_send_using_cronjob' ) ) {
 595              email_send_all();
 596          }
 597      }
 598  
 599      return $t_ok;
 600  }
 601  
 602  /**
 603   * Send notices that a user is now monitoring the bug.  Typically this will only be sent when the added
 604   * user is not the logged in user.  This is assuming that receive own notifications is OFF (default).
 605   * @param int $p_bug_id
 606   * @param int $p_user_id
 607   * @return null
 608   */
 609  function email_monitor_added( $p_bug_id, $p_user_id ) {
 610      log_event( LOG_EMAIL, sprintf( 'Issue #%d monitored by user @U%d', $p_bug_id, $p_user_id ) );
 611  
 612      $t_opt = array();
 613      $t_opt[] = bug_format_id( $p_bug_id );
 614      $t_opt[] = user_get_name( $p_user_id );
 615  
 616      email_generic( $p_bug_id, 'monitor', 'email_notification_title_for_action_monitor', $t_opt, array( $p_user_id ) );
 617  }
 618  
 619  /**
 620   * send notices when a relationship is ADDED
 621   * @param int $p_bug_id
 622   * @param int $p_related_bug_id
 623   * @param int $p_rel_type
 624   * @return null
 625   */
 626  function email_relationship_added( $p_bug_id, $p_related_bug_id, $p_rel_type ) {
 627      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 ) );
 628  
 629      $t_opt = array();
 630      $t_opt[] = bug_format_id( $p_related_bug_id );
 631      global $g_relationships;
 632      if( !isset( $g_relationships[$p_rel_type] ) ) {
 633          trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR );
 634      }
 635      email_generic( $p_bug_id, 'relation', $g_relationships[$p_rel_type]['#notify_added'], $t_opt );
 636  }
 637  
 638  /**
 639   * send notices when a relationship is DELETED
 640   * @param int $p_bug_id
 641   * @param int $p_related_bug_id
 642   * @param int $p_rel_type
 643   * @return null
 644   */
 645  function email_relationship_deleted( $p_bug_id, $p_related_bug_id, $p_rel_type ) {
 646      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 ) );
 647  
 648      $t_opt = array();
 649      $t_opt[] = bug_format_id( $p_related_bug_id );
 650      global $g_relationships;
 651      if( !isset( $g_relationships[$p_rel_type] ) ) {
 652          trigger_error( ERROR_RELATIONSHIP_NOT_FOUND, ERROR );
 653      }
 654      email_generic( $p_bug_id, 'relation', $g_relationships[$p_rel_type]['#notify_deleted'], $t_opt );
 655  }
 656  
 657  /**
 658   * send notices to all the handlers of the parent bugs when a child bug is RESOLVED
 659   * @param int $p_bug_id
 660   * @return null
 661   */
 662  function email_relationship_child_resolved( $p_bug_id ) {
 663      email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_resolved' );
 664  }
 665  
 666  /**
 667   * send notices to all the handlers of the parent bugs when a child bug is CLOSED
 668   * @param int $p_bug_id
 669   * @return null
 670   */
 671  function email_relationship_child_closed( $p_bug_id ) {
 672      email_relationship_child_resolved_closed( $p_bug_id, 'email_notification_title_for_action_relationship_child_closed' );
 673  }
 674  
 675  /**
 676   * send notices to all the handlers of the parent bugs still open when a child bug is resolved/closed
 677   *
 678   * @param int $p_bug_id
 679   * @param int $p_message_id
 680   * @return null
 681   */
 682  function email_relationship_child_resolved_closed( $p_bug_id, $p_message_id ) {
 683      # retrieve all the relationships in which the bug is the destination bug
 684      $t_relationship = relationship_get_all_dest( $p_bug_id );
 685      $t_relationship_count = count( $t_relationship );
 686      if( $t_relationship_count == 0 ) {
 687          # no parent bug found
 688          return;
 689      }
 690  
 691      for( $i = 0;$i < $t_relationship_count;$i++ ) {
 692          if( $t_relationship[$i]->type == BUG_DEPENDANT ) {
 693              $t_src_bug_id = $t_relationship[$i]->src_bug_id;
 694              $t_status = bug_get_field( $t_src_bug_id, 'status' );
 695              if( $t_status < config_get( 'bug_resolved_status_threshold' ) ) {
 696  
 697                  # sent the notification just for parent bugs not resolved/closed
 698                  $t_opt = array();
 699                  $t_opt[] = bug_format_id( $p_bug_id );
 700                  email_generic( $t_src_bug_id, 'handler', $p_message_id, $t_opt );
 701              }
 702          }
 703      }
 704  }
 705  
 706  /**
 707   * send notices when a bug is sponsored
 708   * @param int $p_bug_id
 709   * @return null
 710   */
 711  function email_sponsorship_added( $p_bug_id ) {
 712      email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_added' );
 713  }
 714  
 715  /**
 716   * send notices when a sponsorship is modified
 717   * @param int $p_bug_id
 718   * @return null
 719   */
 720  function email_sponsorship_updated( $p_bug_id ) {
 721      email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_updated' );
 722  }
 723  
 724  /**
 725   * send notices when a sponsorship is deleted
 726   * @param int $p_bug_id
 727   * @return null
 728   */
 729  function email_sponsorship_deleted( $p_bug_id ) {
 730      email_generic( $p_bug_id, 'sponsor', 'email_notification_title_for_action_sponsorship_deleted' );
 731  }
 732  
 733  /**
 734   * send notices when a new bug is added
 735   * @param int $p_bug_id
 736   * @return null
 737   */
 738  function email_new_bug( $p_bug_id ) {
 739      email_generic( $p_bug_id, 'new', 'email_notification_title_for_action_bug_submitted' );
 740  }
 741  
 742  /**
 743   * send notices when a new bugnote
 744   * @param int $p_bug_id
 745   * @return null
 746   */
 747  function email_bugnote_add( $p_bug_id ) {
 748      email_generic( $p_bug_id, 'bugnote', 'email_notification_title_for_action_bugnote_submitted' );
 749  }
 750  
 751  /**
 752   * send notices when a bug is RESOLVED
 753   * @param int $p_bug_id
 754   * @return null
 755   */
 756  function email_resolved( $p_bug_id ) {
 757      email_generic( $p_bug_id, 'resolved', 'email_notification_title_for_status_bug_resolved' );
 758  }
 759  
 760  /**
 761   * send notices when a bug is CLOSED
 762   * @param int $p_bug_id
 763   * @return null
 764   */
 765   function email_close( $p_bug_id ) {
 766      email_generic( $p_bug_id, 'closed', 'email_notification_title_for_status_bug_closed' );
 767  }
 768  
 769  /**
 770   * send notices when a bug is REOPENED
 771   * @param int $p_bug_id
 772   * @return null
 773   */
 774  function email_reopen( $p_bug_id ) {
 775      email_generic( $p_bug_id, 'reopened', 'email_notification_title_for_action_bug_reopened' );
 776  }
 777  
 778  /**
 779   * send notices when a bug is ASSIGNED
 780   * @param int $p_bug_id
 781   * @return null
 782   */
 783  function email_assign( $p_bug_id ) {
 784      email_generic( $p_bug_id, 'owner', 'email_notification_title_for_action_bug_assigned' );
 785  }
 786  
 787  /**
 788   * send notices when a bug is DELETED
 789   * @param int $p_bug_id
 790   * @return null
 791   */
 792  function email_bug_deleted( $p_bug_id ) {
 793      email_generic( $p_bug_id, 'deleted', 'email_notification_title_for_action_bug_deleted' );
 794  }
 795  
 796  /**
 797   * Store email in queue for sending
 798   *
 799   * @param string $p_recipient
 800   * @param string $p_subject
 801   * @param string $p_message
 802   * @param array $p_headers
 803   * @return int
 804   */
 805  function email_store( $p_recipient, $p_subject, $p_message, $p_headers = null ) {
 806      $t_recipient = trim( $p_recipient );
 807      $t_subject = string_email( trim( $p_subject ) );
 808      $t_message = string_email_links( trim( $p_message ) );
 809  
 810      # short-circuit if no recipient is defined, or email disabled
 811      # note that this may cause signup messages not to be sent
 812  
 813      if( is_blank( $p_recipient ) || ( OFF == config_get( 'enable_email_notification' ) ) ) {
 814          return;
 815      }
 816  
 817      $t_email_data = new EmailData;
 818  
 819      $t_email_data->email = $t_recipient;
 820      $t_email_data->subject = $t_subject;
 821      $t_email_data->body = $t_message;
 822      $t_email_data->metadata = array();
 823      $t_email_data->metadata['headers'] = $p_headers === null ? array() : $p_headers;
 824      $t_email_data->metadata['priority'] = config_get( 'mail_priority' );
 825  
 826      # Urgent = 1, Not Urgent = 5, Disable = 0
 827      $t_email_data->metadata['charset'] = 'utf-8';
 828  
 829      $t_hostname = '';
 830      $t_server = isset( $_SERVER ) ? $_SERVER : $HTTP_SERVER_VARS;
 831      if( isset( $t_server['SERVER_NAME'] ) ) {
 832          $t_hostname = $t_server['SERVER_NAME'];
 833      } else {
 834          $t_address = explode( '@', config_get( 'from_email' ) );
 835          if( isset( $t_address[1] ) ) {
 836              $t_hostname = $t_address[1];
 837          }
 838      }
 839      $t_email_data->metadata['hostname'] = $t_hostname;
 840  
 841      $t_email_id = email_queue_add( $t_email_data );
 842  
 843      return $t_email_id;
 844  }
 845  
 846  /**
 847   * This function sends all the emails that are stored in the queue.  If a failure occurs, then the
 848   * function exists.  This function will be called after storing emails in case of synchronous
 849   * emails, or will be called from a cronjob in case of asynchronous emails.
 850   * @todo In case of synchronous email sending, we may get a race condition where two requests send the same email.
 851   * @param bool $p_delete_on_failure indicates whether to remove email from queue on failure (default false)
 852   * @return null
 853   */
 854  function email_send_all($p_delete_on_failure = false) {
 855      $t_ids = email_queue_get_ids();
 856  
 857      $t_emails_recipients_failed = array();
 858      $t_start = microtime(true);
 859      foreach( $t_ids as $t_id ) {
 860          $t_email_data = email_queue_get( $t_id );
 861  
 862          # check if email was not found.  This can happen if another request picks up the email first and sends it.
 863          if( $t_email_data === false ) {
 864              continue;
 865          }
 866  
 867          # if unable to place the email in the email server queue, then the connection to the server is down,
 868          # and hence no point to continue trying with the rest of the emails.
 869          if( !email_send( $t_email_data ) ) {
 870              if ($p_delete_on_failure) {
 871                  email_queue_delete( $t_email_data->email_id );
 872              }
 873              if( microtime(true) - $t_start > 5 ) {
 874                  break;
 875              } else {
 876                  continue;
 877              }
 878          }
 879      }
 880  }
 881  
 882  /**
 883   * This function sends an email message based on the supplied email data.
 884   *
 885   * @param EmailData $p_email_data
 886   * @return bool
 887   */
 888  function email_send( $p_email_data ) {
 889      global $g_phpMailer;
 890  
 891      $t_email_data = $p_email_data;
 892  
 893      $t_recipient = trim( $t_email_data->email );
 894      $t_subject = string_email( trim( $t_email_data->subject ) );
 895      $t_message = string_email_links( trim( $t_email_data->body ) );
 896  
 897      $t_debug_email = config_get( 'debug_email' );
 898      $t_mailer_method = config_get( 'phpMailer_method' );
 899  
 900      if( is_null( $g_phpMailer ) ) {
 901          if ( $t_mailer_method == PHPMAILER_METHOD_SMTP )
 902              register_shutdown_function( 'email_smtp_close' );
 903          if( !class_exists( 'PHPMailer' ) ) {
 904              require_lib( 'phpmailer' . DIRECTORY_SEPARATOR . 'class.phpmailer.php' );
 905          }
 906          $mail = new PHPMailer(true);
 907      } else {
 908          $mail = $g_phpMailer;
 909      }
 910  
 911      if( isset( $t_email_data->metadata['hostname'] ) ) {
 912          $mail->Hostname = $t_email_data->metadata['hostname'];
 913      }
 914  
 915      # @@@ should this be the current language (for the recipient) or the default one (for the user running the command) (thraxisp)
 916      $t_lang = config_get( 'default_language' );
 917      if( 'auto' == $t_lang ) {
 918          $t_lang = config_get( 'fallback_language' );
 919      }
 920      $mail->SetLanguage( lang_get( 'phpmailer_language', $t_lang ) );
 921  
 922      # Select the method to send mail
 923      switch( config_get( 'phpMailer_method' ) ) {
 924          case PHPMAILER_METHOD_MAIL:
 925              $mail->IsMail();
 926              break;
 927  
 928          case PHPMAILER_METHOD_SENDMAIL:
 929              $mail->IsSendmail();
 930              break;
 931  
 932          case PHPMAILER_METHOD_SMTP:
 933              $mail->IsSMTP();
 934  
 935              // SMTP collection is always kept alive
 936              $mail->SMTPKeepAlive = true;
 937  
 938              if ( !is_blank( config_get( 'smtp_username' ) ) ) {
 939                  # Use SMTP Authentication
 940                  $mail->SMTPAuth = true;
 941                  $mail->Username = config_get( 'smtp_username' );
 942                  $mail->Password = config_get( 'smtp_password' );
 943              }
 944  
 945              if ( !is_blank( config_get( 'smtp_connection_mode' ) ) ) {
 946                  $mail->SMTPSecure = config_get( 'smtp_connection_mode' );
 947              }
 948  
 949              $mail->Port = config_get( 'smtp_port' );
 950  
 951              break;
 952      }
 953  
 954      $mail->IsHTML( false );              # set email format to plain text
 955      $mail->WordWrap = 80;              # set word wrap to 50 characters
 956      $mail->Priority = $t_email_data->metadata['priority'];  # Urgent = 1, Not Urgent = 5, Disable = 0
 957      $mail->CharSet = $t_email_data->metadata['charset'];
 958      $mail->Host = config_get( 'smtp_host' );
 959      $mail->From = config_get( 'from_email' );
 960      $mail->Sender = config_get( 'return_path_email' );
 961      $mail->FromName = config_get( 'from_name' );
 962  
 963      if( OFF !== $t_debug_email ) {
 964          $t_message = 'To: ' . $t_recipient . "\n\n" . $t_message;
 965          try {
 966              $mail->AddAddress( $t_debug_email, '' );
 967          } catch ( phpmailerException $e ) {
 968              $t_success = false;
 969              $mail->ClearAllRecipients();
 970              $mail->ClearAttachments();
 971              $mail->ClearReplyTos();
 972              $mail->ClearCustomHeaders();
 973              return $t_success;
 974          }
 975      } else {
 976          try {
 977              $mail->AddAddress( $t_recipient, '' );
 978          } catch ( phpmailerException $e ) {
 979              $t_success = false;
 980              $mail->ClearAllRecipients();
 981              $mail->ClearAttachments();
 982              $mail->ClearReplyTos();
 983              $mail->ClearCustomHeaders();
 984              return $t_success;
 985          }
 986      }
 987  
 988      $mail->Subject = $t_subject;
 989      $mail->Body = make_lf_crlf( "\n" . $t_message );
 990  
 991      if( isset( $t_email_data->metadata['headers'] ) && is_array( $t_email_data->metadata['headers'] ) ) {
 992          foreach( $t_email_data->metadata['headers'] as $t_key => $t_value ) {
 993              switch( $t_key ) {
 994                  case 'Message-ID':
 995                      /* Note: hostname can never be blank here as we set metadata['hostname']
 996                         in email_store() where mail gets queued. */
 997                          if ( !strchr( $t_value, '@' ) && !is_blank( $mail->Hostname ) ) {
 998                              $t_value = $t_value . '@' . $mail->Hostname;
 999                          }
1000                      $mail->set( 'MessageID', "<$t_value>" );
1001                      break;
1002                  case 'In-Reply-To':
1003                      $mail->AddCustomHeader( "$t_key: <{$t_value}@{$mail->Hostname}>" );
1004                      break;
1005                  default:
1006                      $mail->AddCustomHeader( "$t_key: $t_value" );
1007                      break;
1008              }
1009          }
1010      }
1011  
1012      try
1013      {
1014          if ( !$mail->Send() ) {
1015              $t_success = false;
1016          } else {
1017              $t_success = true;
1018  
1019              if ( $t_email_data->email_id > 0 ) {
1020                  email_queue_delete( $t_email_data->email_id );
1021              }
1022          }
1023      }
1024      catch ( phpmailerException $e )
1025      {
1026          $t_success = false;
1027      }
1028  
1029      $mail->ClearAllRecipients();
1030      $mail->ClearAttachments();
1031      $mail->ClearReplyTos();
1032      $mail->ClearCustomHeaders();
1033  
1034      return $t_success;
1035  }
1036  
1037  /**
1038   * closes opened kept alive SMTP connection (if it was opened)
1039   *
1040   * @param string
1041   * @return null
1042   */
1043  function email_smtp_close() {
1044      global $g_phpMailer;
1045  
1046      if( !is_null( $g_phpMailer ) ) {
1047          if( $g_phpMailer->smtp->Connected() ) {
1048              $g_phpMailer->smtp->Quit();
1049              $g_phpMailer->smtp->Close();
1050          }
1051          $g_phpMailer = null;
1052      }
1053  }
1054  
1055  /**
1056   * formats the subject correctly
1057   * we include the project name, bug id, and summary.
1058   *
1059   * @param int $p_bug_id
1060   * @return null
1061   */
1062  function email_build_subject( $p_bug_id ) {
1063      # grab the project name
1064      $p_project_name = project_get_field( bug_get_field( $p_bug_id, 'project_id' ), 'name' );
1065  
1066      # grab the subject (summary)
1067      $p_subject = bug_get_field( $p_bug_id, 'summary' );
1068  
1069      # padd the bug id with zeros
1070      $p_bug_id = bug_format_id( $p_bug_id );
1071  
1072      return '[' . $p_project_name . ' ' . $p_bug_id . ']: ' . $p_subject;
1073  }
1074  
1075  /**
1076   * clean up LF to CRLF
1077   *
1078   * @param string $p_string
1079   * @return null
1080   */
1081  function make_lf_crlf( $p_string ) {
1082      $t_string = str_replace( "\n", "\r\n", $p_string );
1083      return str_replace( "\r\r\n", "\r\n", $t_string );
1084  }
1085  
1086  /**
1087   * Appends an email domain to the specified email address if email is
1088   * not empty, it doesn't already have a domain part and if a
1089   * limit_email_domain is configured.
1090   *
1091   * Check limit_email_domain option and append the domain name if it is set
1092   * @todo limit_email_domain called after we look for @ in domain name?
1093   * @param string $p_email The email address to append the domain to.
1094   * @returns The email address with the appended domain (if applicable).
1095   */
1096  function email_append_domain( $p_email ) {
1097      # If email is empty or already contains a domain, then return as is.
1098      if ( is_blank( $p_email ) || strchr( $p_email, '@' ) ) {
1099          return $p_email;
1100      }
1101  
1102      # If limit email domain is set, then append it.
1103      $t_limit_email_domain = config_get( 'limit_email_domain' );
1104      if ( $t_limit_email_domain === OFF ) {
1105          return $p_email;
1106      }
1107  
1108      return "$p_email@$t_limit_email_domain";
1109  }
1110  
1111  /**
1112   * Send a bug reminder to each of the given user, or to each user if the first parameter is an array
1113   * return an array of usernames to which the reminder was successfully sent
1114   *
1115   * @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.
1116   * @param array $p_recipients
1117   * @param int $p_bug_id
1118   * @param string $p_message
1119   * @return null
1120   */
1121  function email_bug_reminder( $p_recipients, $p_bug_id, $p_message ) {
1122      if( !is_array( $p_recipients ) ) {
1123          $p_recipients = array(
1124              $p_recipients,
1125          );
1126      }
1127  
1128      $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
1129      $t_sender_id = auth_get_current_user_id();
1130      $t_sender = user_get_name( $t_sender_id );
1131  
1132      $t_subject = email_build_subject( $p_bug_id );
1133      $t_date = date( config_get( 'normal_date_format' ) );
1134  
1135      $result = array();
1136      foreach( $p_recipients as $t_recipient ) {
1137          lang_push( user_pref_get_language( $t_recipient, $t_project_id ) );
1138  
1139          $t_email = user_get_email( $t_recipient );
1140          $result[] = user_get_name( $t_recipient );
1141  
1142          if( access_has_project_level( config_get( 'show_user_email_threshold' ), $t_project_id, $t_recipient ) ) {
1143              $t_sender_email = ' <' . current_user_get_field( 'email' ) . '>';
1144          } else {
1145              $t_sender_email = '';
1146          }
1147          $t_header = "\n" . lang_get( 'on_date' ) . " $t_date, $t_sender $t_sender_email " . lang_get( 'sent_you_this_reminder_about' ) . ": \n\n";
1148          $t_contents = $t_header . string_get_bug_view_url_with_fqdn( $p_bug_id, $t_recipient ) . " \n\n$p_message";
1149  
1150          if( ON == config_get( 'enable_email_notification' ) ) {
1151              email_store( $t_email, $t_subject, $t_contents );
1152          }
1153  
1154          lang_pop();
1155      }
1156  
1157      if( OFF == config_get( 'email_send_using_cronjob' ) ) {
1158          email_send_all();
1159      }
1160  
1161      return $result;
1162  }
1163  
1164  /**
1165   * Send bug info to given user
1166   * return true on success
1167   * @param array $p_visible_bug_data
1168   * @param string $p_message_id
1169   * @param int $p_project_id
1170   * @param int $p_user_id
1171   * @param array $p_header_optional_params
1172   * @return bool
1173   */
1174  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 ) {
1175      $t_user_email = user_get_email( $p_user_id );
1176  
1177      # check whether email should be sent
1178      # @@@ can be email field empty? if yes - then it should be handled here
1179      if( ON !== config_get( 'enable_email_notification' ) || is_blank( $t_user_email ) ) {
1180          return true;
1181      }
1182  
1183      # build subject
1184      $t_subject = '[' . $p_visible_bug_data['email_project'] . ' ' . bug_format_id( $p_visible_bug_data['email_bug'] ) . ']: ' . $p_visible_bug_data['email_summary'];
1185  
1186      # build message
1187  
1188      $t_message = lang_get_defaulted( $p_message_id, null );
1189  
1190      if( is_array( $p_header_optional_params ) ) {
1191          $t_message = vsprintf( $t_message, $p_header_optional_params );
1192      }
1193  
1194      if(( $t_message !== null ) && ( !is_blank( $t_message ) ) ) {
1195          $t_message .= " \n";
1196      }
1197  
1198      $t_message .= email_format_bug_message( $p_visible_bug_data );
1199  
1200      # build headers
1201      $t_bug_id = $p_visible_bug_data['email_bug'];
1202      $t_message_md5 = md5( $t_bug_id . $p_visible_bug_data['email_date_submitted'] );
1203      $t_mail_headers = array(
1204          'keywords' => $p_visible_bug_data['set_category'],
1205      );
1206      if( $p_message_id == 'email_notification_title_for_action_bug_submitted' ) {
1207          $t_mail_headers['Message-ID'] = $t_message_md5;
1208      } else {
1209          $t_mail_headers['In-Reply-To'] = $t_message_md5;
1210      }
1211  
1212      # send mail
1213      $t_ok = email_store( $t_user_email, $t_subject, $t_message, $t_mail_headers );
1214  
1215      return $t_ok;
1216  }
1217  
1218  /**
1219   * Build the bug info part of the message
1220   * @param array $p_visible_bug_data
1221   * @return string
1222   */
1223  function email_format_bug_message( $p_visible_bug_data ) {
1224      $t_normal_date_format = config_get( 'normal_date_format' );
1225      $t_complete_date_format = config_get( 'complete_date_format' );
1226  
1227      $t_email_separator1 = config_get( 'email_separator1' );
1228      $t_email_separator2 = config_get( 'email_separator2' );
1229      $t_email_padding_length = config_get( 'email_padding_length' );
1230  
1231      $t_status = $p_visible_bug_data['email_status'];
1232  
1233      $p_visible_bug_data['email_date_submitted'] = date( $t_complete_date_format, $p_visible_bug_data['email_date_submitted'] );
1234      $p_visible_bug_data['email_last_modified'] = date( $t_complete_date_format, $p_visible_bug_data['email_last_modified'] );
1235  
1236      $p_visible_bug_data['email_status'] = get_enum_element( 'status', $t_status );
1237      $p_visible_bug_data['email_severity'] = get_enum_element( 'severity', $p_visible_bug_data['email_severity'] );
1238      $p_visible_bug_data['email_priority'] = get_enum_element( 'priority', $p_visible_bug_data['email_priority'] );
1239      $p_visible_bug_data['email_reproducibility'] = get_enum_element( 'reproducibility', $p_visible_bug_data['email_reproducibility'] );
1240  
1241      $t_message = $t_email_separator1 . " \n";
1242  
1243      if( isset( $p_visible_bug_data['email_bug_view_url'] ) ) {
1244          $t_message .= $p_visible_bug_data['email_bug_view_url'] . " \n";
1245          $t_message .= $t_email_separator1 . " \n";
1246      }
1247  
1248      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reporter' );
1249      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_handler' );
1250      $t_message .= $t_email_separator1 . " \n";
1251      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_project' );
1252      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_bug' );
1253      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_category' );
1254      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_reproducibility' );
1255      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_severity' );
1256      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_priority' );
1257      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_status' );
1258      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_target_version' );
1259  
1260      # custom fields formatting
1261      foreach( $p_visible_bug_data['custom_fields'] as $t_custom_field_name => $t_custom_field_data ) {
1262          $t_message .= utf8_str_pad( lang_get_defaulted( $t_custom_field_name, null ) . ': ', $t_email_padding_length, ' ', STR_PAD_RIGHT );
1263          $t_message .= string_custom_field_value_for_email( $t_custom_field_data['value'], $t_custom_field_data['type'] );
1264          $t_message .= " \n";
1265      }
1266  
1267      # end foreach custom field
1268  
1269      if( config_get( 'bug_resolved_status_threshold' ) <= $t_status ) {
1270          $p_visible_bug_data['email_resolution'] = get_enum_element( 'resolution', $p_visible_bug_data['email_resolution'] );
1271          $t_message .= email_format_attribute( $p_visible_bug_data, 'email_resolution' );
1272          $t_message .= email_format_attribute( $p_visible_bug_data, 'email_fixed_in_version' );
1273      }
1274      $t_message .= $t_email_separator1 . " \n";
1275  
1276      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_date_submitted' );
1277      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_last_modified' );
1278      $t_message .= $t_email_separator1 . " \n";
1279  
1280      $t_message .= email_format_attribute( $p_visible_bug_data, 'email_summary' );
1281  
1282      $t_message .= lang_get( 'email_description' ) . ": \n" . $p_visible_bug_data['email_description'] . "\n";
1283  
1284      if ( !is_blank( $p_visible_bug_data['email_steps_to_reproduce'] ) ) {
1285          $t_message .= "\n" . lang_get( 'email_steps_to_reproduce' ) . ": \n" . $p_visible_bug_data['email_steps_to_reproduce'] . "\n";
1286      }
1287  
1288      if ( !is_blank( $p_visible_bug_data['email_additional_information'] ) ) {
1289          $t_message .= "\n" . lang_get( 'email_additional_information' ) . ": \n" . $p_visible_bug_data['email_additional_information'] . "\n";
1290      }
1291  
1292      if( isset( $p_visible_bug_data['relations'] ) ) {
1293          if( $p_visible_bug_data['relations'] != '' ) {
1294              $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'];
1295          }
1296      }
1297  
1298      # Sponsorship
1299      if( isset( $p_visible_bug_data['sponsorship_total'] ) && ( $p_visible_bug_data['sponsorship_total'] > 0 ) ) {
1300          $t_message .= $t_email_separator1 . " \n";
1301          $t_message .= sprintf( lang_get( 'total_sponsorship_amount' ), sponsorship_format_amount( $p_visible_bug_data['sponsorship_total'] ) ) . "\n" . "\n";
1302  
1303          if( isset( $p_visible_bug_data['sponsorships'] ) ) {
1304              foreach( $p_visible_bug_data['sponsorships'] as $t_sponsorship ) {
1305                  $t_date_added = date( config_get( 'normal_date_format' ), $t_sponsorship->date_submitted );
1306  
1307                  $t_message .= $t_date_added . ': ';
1308                  $t_message .= user_get_name( $t_sponsorship->user_id );
1309                  $t_message .= ' (' . sponsorship_format_amount( $t_sponsorship->amount ) . ')' . " \n";
1310              }
1311          }
1312      }
1313  
1314      $t_message .= $t_email_separator1 . " \n\n";
1315  
1316      # format bugnotes
1317      foreach( $p_visible_bug_data['bugnotes'] as $t_bugnote ) {
1318          $t_last_modified = date( $t_normal_date_format, $t_bugnote->last_modified );
1319  
1320          $t_formatted_bugnote_id = bugnote_format_id( $t_bugnote->id );
1321          $t_bugnote_link = string_process_bugnote_link( config_get( 'bugnote_link_tag' ) . $t_bugnote->id, false, false, true );
1322  
1323          if( $t_bugnote->time_tracking > 0 ) {
1324              $t_time_tracking = ' ' . lang_get( 'time_tracking' ) . ' ' . db_minutes_to_hhmm( $t_bugnote->time_tracking ) . "\n";
1325          } else {
1326              $t_time_tracking = '';
1327          }
1328  
1329          if( user_exists( $t_bugnote->reporter_id ) ) {
1330              $t_access_level = access_get_project_level( $p_visible_bug_data['email_project_id'] , $t_bugnote->reporter_id );
1331              $t_access_level_string = ' (' . get_enum_element( 'access_levels', $t_access_level ) . ') - ';
1332          } else {
1333              $t_access_level_string = '';
1334          }
1335  
1336          $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  
1338          $t_message .= $t_email_separator2 . " \n";
1339          $t_message .= $t_string . " \n";
1340          $t_message .= $t_email_separator2 . " \n";
1341          $t_message .= $t_bugnote->note . " \n\n";
1342      }
1343  
1344      # format history
1345      if( array_key_exists( 'history', $p_visible_bug_data ) ) {
1346          $t_message .= lang_get( 'bug_history' ) . " \n";
1347          $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  
1349          $t_message .= $t_email_separator1 . " \n";
1350  
1351          foreach( $p_visible_bug_data['history'] as $t_raw_history_item ) {
1352              $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  
1354              $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          }
1356          $t_message .= $t_email_separator1 . " \n\n";
1357      }
1358  
1359      return $t_message;
1360  }
1361  
1362  /**
1363   * if $p_visible_bug_data contains specified attribute the function
1364   * returns concatenated translated attribute name and original
1365   * attribute value. Else return empty string.
1366   * @param array $p_visible_bug_data
1367   * @param string $p_attribute_id
1368   * @return string
1369   */
1370  function email_format_attribute( $p_visible_bug_data, $attribute_id ) {
1371      if( array_key_exists( $attribute_id, $p_visible_bug_data ) ) {
1372          return utf8_str_pad( lang_get( $attribute_id ) . ': ', config_get( 'email_padding_length' ), ' ', STR_PAD_RIGHT ) . $p_visible_bug_data[$attribute_id] . "\n";
1373      }
1374      return '';
1375  }
1376  
1377  /**
1378   * Build the bug raw data visible for specified user to be translated and sent by email to the user
1379   * (Filter the bug data according to user access level)
1380   * return array with bug data. See usage in email_format_bug_message(...)
1381   * @param int $p_user_id
1382   * @param int $p_bug_id
1383   * @param string $p_message_id
1384   * @return array
1385   */
1386  function email_build_visible_bug_data( $p_user_id, $p_bug_id, $p_message_id ) {
1387      $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
1388      $t_user_access_level = user_get_access_level( $p_user_id, $t_project_id );
1389      $t_user_bugnote_order = user_pref_get_pref( $p_user_id, 'bugnote_order' );
1390      $t_user_bugnote_limit = user_pref_get_pref( $p_user_id, 'email_bugnote_limit' );
1391  
1392      $row = bug_get_extended_row( $p_bug_id );
1393      $t_bug_data = array();
1394  
1395      $t_bug_data['email_bug'] = $p_bug_id;
1396  
1397      if( $p_message_id !== 'email_notification_title_for_action_bug_deleted' ) {
1398          $t_bug_data['email_bug_view_url'] = string_get_bug_view_url_with_fqdn( $p_bug_id );
1399      }
1400  
1401      if( access_compare_level( $t_user_access_level, config_get( 'view_handler_threshold' ) ) ) {
1402          if( 0 != $row['handler_id'] ) {
1403              $t_bug_data['email_handler'] = user_get_name( $row['handler_id'] );
1404          } else {
1405              $t_bug_data['email_handler'] = '';
1406          }
1407      }
1408  
1409      $t_bug_data['email_reporter'] = user_get_name( $row['reporter_id'] );
1410      $t_bug_data['email_project_id'] = $row['project_id'];
1411      $t_bug_data['email_project'] = project_get_field( $row['project_id'], 'name' );
1412  
1413      $t_category_name = category_full_name( $row['category_id'], false );
1414      $t_bug_data['email_category'] = $t_category_name;
1415  
1416      $t_bug_data['email_date_submitted'] = $row['date_submitted'];
1417      $t_bug_data['email_last_modified'] = $row['last_updated'];
1418  
1419      $t_bug_data['email_status'] = $row['status'];
1420      $t_bug_data['email_severity'] = $row['severity'];
1421      $t_bug_data['email_priority'] = $row['priority'];
1422      $t_bug_data['email_reproducibility'] = $row['reproducibility'];
1423  
1424      $t_bug_data['email_resolution'] = $row['resolution'];
1425      $t_bug_data['email_fixed_in_version'] = $row['fixed_in_version'];
1426  
1427      if( !is_blank( $row['target_version'] ) && access_compare_level( $t_user_access_level, config_get( 'roadmap_view_threshold' ) ) ) {
1428          $t_bug_data['email_target_version'] = $row['target_version'];
1429      }
1430  
1431      $t_bug_data['email_summary'] = $row['summary'];
1432      $t_bug_data['email_description'] = $row['description'];
1433      $t_bug_data['email_additional_information'] = $row['additional_information'];
1434      $t_bug_data['email_steps_to_reproduce'] = $row['steps_to_reproduce'];
1435  
1436      $t_bug_data['set_category'] = '[' . $t_bug_data['email_project'] . '] ' . $t_category_name;
1437  
1438      $t_bug_data['custom_fields'] = custom_field_get_linked_fields( $p_bug_id, $t_user_access_level );
1439      $t_bug_data['bugnotes'] = bugnote_get_all_visible_bugnotes( $p_bug_id, $t_user_bugnote_order, $t_user_bugnote_limit, $p_user_id );
1440  
1441      # put history data
1442      if(( ON == config_get( 'history_default_visible' ) ) && access_compare_level( $t_user_access_level, config_get( 'view_history_threshold' ) ) ) {
1443          $t_bug_data['history'] = history_get_raw_events_array( $p_bug_id, $p_user_id );
1444      }
1445  
1446      # Sponsorship Information
1447      if(( config_get( 'enable_sponsorship' ) == ON ) && ( access_has_bug_level( config_get( 'view_sponsorship_total_threshold' ), $p_bug_id, $p_user_id ) ) ) {
1448          $t_sponsorship_ids = sponsorship_get_all_ids( $p_bug_id );
1449          $t_bug_data['sponsorship_total'] = sponsorship_get_amount( $t_sponsorship_ids );
1450  
1451          if( access_has_bug_level( config_get( 'view_sponsorship_details_threshold' ), $p_bug_id, $p_user_id ) ) {
1452              $t_bug_data['sponsorships'] = array();
1453              foreach( $t_sponsorship_ids as $id ) {
1454                  $t_bug_data['sponsorships'][] = sponsorship_get( $id );
1455              }
1456          }
1457      }
1458  
1459      $t_bug_data['relations'] = relationship_get_summary_text( $p_bug_id );
1460  
1461      return $t_bug_data;
1462  }


Generated: Thu Jul 28 15:48:31 2011 Cross-referenced by PHPXref 0.7