[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/ -> bug_update.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   * Update bug data then redirect to the appropriate viewing page
  19   *
  20   * @package MantisBT
  21   * @copyright Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
  22   * @copyright Copyright (C) 2002 - 2011  MantisBT Team - mantisbt-dev@lists.sourceforge.net
  23   * @link http://www.mantisbt.org
  24   *
  25   * @uses core.php
  26   * @uses access_api.php
  27   * @uses authentication_api.php
  28   * @uses bug_api.php
  29   * @uses bugnote_api.php
  30   * @uses config_api.php
  31   * @uses constant_inc.php
  32   * @uses custom_field_api.php
  33   * @uses email_api.php
  34   * @uses error_api.php
  35   * @uses event_api.php
  36   * @uses form_api.php
  37   * @uses gpc_api.php
  38   * @uses helper_api.php
  39   * @uses history_api.php
  40   * @uses lang_api.php
  41   * @uses print_api.php
  42   * @uses relationship_api.php
  43   * @uses twitter_api.php
  44   */
  45  
  46  /**
  47   * MantisBT Core API's
  48   */
  49  require_once ( 'core.php' );
  50  require_api( 'access_api.php' );
  51  require_api( 'authentication_api.php' );
  52  require_api( 'bug_api.php' );
  53  require_api( 'bugnote_api.php' );
  54  require_api( 'config_api.php' );
  55  require_api( 'constant_inc.php' );
  56  require_api( 'custom_field_api.php' );
  57  require_api( 'email_api.php' );
  58  require_api( 'error_api.php' );
  59  require_api( 'event_api.php' );
  60  require_api( 'form_api.php' );
  61  require_api( 'gpc_api.php' );
  62  require_api( 'helper_api.php' );
  63  require_api( 'history_api.php' );
  64  require_api( 'lang_api.php' );
  65  require_api( 'print_api.php' );
  66  require_api( 'relationship_api.php' );
  67  require_api( 'twitter_api.php' );
  68  
  69  form_security_validate( 'bug_update' );
  70  
  71  $f_bug_id = gpc_get_int( 'bug_id' );
  72  $t_existing_bug = bug_get( $f_bug_id, true );
  73  
  74  if ( helper_get_current_project() !== $t_existing_bug->project_id ) {
  75      $g_project_override = $t_existing_bug->project_id;
  76  }
  77  
  78  # Ensure that the user has permission to update bugs. This check also factors
  79  # in whether the user has permission to view private bugs. The
  80  # $g_limit_reporters option is also taken into consideration.
  81  access_ensure_bug_level( config_get( 'update_bug_threshold' ), $f_bug_id );
  82  
  83  # Check if the bug is in a read-only state and whether the current user has
  84  # permission to update read-only bugs.
  85  if ( bug_is_readonly( $f_bug_id ) ) {
  86      error_parameters( $f_bug_id );
  87      trigger_error( ERROR_BUG_READ_ONLY_ACTION_DENIED, ERROR );
  88  }
  89  
  90  $t_updated_bug = clone $t_existing_bug;
  91  
  92  $t_updated_bug->additional_information = gpc_get_string( 'additional_information', $t_existing_bug->additional_information );
  93  $t_updated_bug->build = gpc_get_string( 'build', $t_existing_bug->build );
  94  $t_updated_bug->category_id = gpc_get_int( 'category_id', $t_existing_bug->category_id );
  95  $t_updated_bug->description = gpc_get_string( 'description', $t_existing_bug->description );
  96  $t_due_date = gpc_get_string( 'due_date', null );
  97  if ( $t_due_date !== null) {
  98      if ( is_blank ( $t_due_date ) ) {
  99          $t_updated_bug->due_date = 1;
 100      } else {
 101          $t_updated_bug->due_date = strtotime( $t_due_date );
 102      }
 103  }
 104  $t_updated_bug->duplicate_id = gpc_get_int( 'duplicate_id', 0 );
 105  $t_updated_bug->eta = gpc_get_int( 'eta', $t_existing_bug->eta );
 106  $t_updated_bug->fixed_in_version = gpc_get_string( 'fixed_in_version', $t_existing_bug->fixed_in_version );
 107  $t_updated_bug->handler_id = gpc_get_int( 'handler_id', $t_existing_bug->handler_id );
 108  $t_updated_bug->os = gpc_get_string( 'os', $t_existing_bug->os );
 109  $t_updated_bug->os_build = gpc_get_string( 'os_build', $t_existing_bug->os_build );
 110  $t_updated_bug->platform = gpc_get_string( 'platform', $t_existing_bug->platform );
 111  $t_updated_bug->priority = gpc_get_int( 'priority', $t_existing_bug->priority );
 112  $t_updated_bug->projection = gpc_get_int( 'projection', $t_existing_bug->projection );
 113  $t_updated_bug->reporter_id = gpc_get_int( 'reporter_id', $t_existing_bug->reporter_id );
 114  $t_updated_bug->reproducibility = gpc_get_int( 'reproducibility', $t_existing_bug->reproducibility );
 115  $t_updated_bug->resolution = gpc_get_int( 'resolution', $t_existing_bug->resolution );
 116  $t_updated_bug->severity = gpc_get_int( 'severity', $t_existing_bug->severity );
 117  $t_updated_bug->status = gpc_get_int( 'status', $t_existing_bug->status );
 118  $t_updated_bug->steps_to_reproduce = gpc_get_string( 'steps_to_reproduce', $t_existing_bug->steps_to_reproduce );
 119  $t_updated_bug->summary = gpc_get_string( 'summary', $t_existing_bug->summary );
 120  $t_updated_bug->target_version = gpc_get_string( 'target_version', $t_existing_bug->target_version );
 121  $t_updated_bug->version = gpc_get_string( 'version', $t_existing_bug->version );
 122  $t_updated_bug->view_state = gpc_get_int( 'view_state', $t_existing_bug->view_state );
 123  
 124  $t_bug_note = new BugNoteData();
 125  $t_bug_note->note = gpc_get_string( 'bugnote_text', '' );
 126  $t_bug_note->view_state = gpc_get_bool( 'private', config_get( 'default_bugnote_view_status' ) == VS_PRIVATE ) ? VS_PRIVATE : VS_PUBLIC;
 127  $t_bug_note->time_tracking = gpc_get_string( 'time_tracking', '0:00' );
 128  
 129  # Determine whether the new status will reopen, resolve or close the issue.
 130  # Note that multiple resolved or closed states can exist and thus we need to
 131  # look at a range of statuses when performing this check.
 132  $t_resolved_status = config_get( 'bug_resolved_status_threshold' );
 133  $t_closed_status = config_get( 'bug_closed_status_threshold' );
 134  $t_resolve_issue = false;
 135  $t_close_issue = false;
 136  $t_reopen_issue = false;
 137  if ( $t_existing_bug->status < $t_resolved_status &&
 138       $t_updated_bug->status >= $t_resolved_status &&
 139       $t_updated_bug->status < $t_closed_status ) {
 140      $t_resolve_issue = true;
 141  } else if ( $t_existing_bug->status < $t_closed_status &&
 142              $t_updated_bug->status >= $t_closed_status ) {
 143      $t_close_issue = true;
 144  } else if ( $t_existing_bug->status >= $t_resolved_status &&
 145              $t_updated_bug->status <= config_get( 'bug_reopen_status' ) ) {
 146      $t_reopen_issue = true;
 147  }
 148  
 149  # If resolving or closing, ensure that all dependant issues have been resolved.
 150  if ( ( $t_resolve_issue || $t_close_issue ) &&
 151       !relationship_can_resolve_bug( $f_bug_id ) ) {
 152      trigger_error( ERROR_BUG_RESOLVE_DEPENDANTS_BLOCKING, ERROR );
 153  }
 154  
 155  # Validate any change to the status of the issue.
 156  if ( $t_existing_bug->status !== $t_updated_bug->status ) {
 157      access_ensure_bug_level( config_get( 'update_bug_status_threshold' ), $f_bug_id );
 158      if ( !bug_check_workflow( $t_existing_bug->status, $t_updated_bug->status ) ) {
 159          error_parameters( lang_get( 'status' ) );
 160          trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
 161      }
 162      if ( !access_has_bug_level( access_get_status_threshold( $t_updated_bug->status, $t_updated_bug->project_id ), $f_bug_id ) ) {
 163          # The reporter may be allowed to close or reopen the issue regardless.
 164          $t_can_bypass_status_access_thresholds = false;
 165          if ( $t_close_issue &&
 166               $t_existing_bug->status >= $t_resolved_status &&
 167               $t_existing_bug->reporter_id === auth_get_current_user_id() &&
 168               config_get( 'allow_reporter_close' ) ) {
 169              $t_can_bypass_status_access_thresholds = true;
 170          } else if ( $t_reopen_issue &&
 171                      $t_existing_bug->status < $t_closed_status &&
 172                      $t_existing_bug->reporter_id === auth_get_current_user_id() &&
 173                      config_get( 'allow_reporter_reopen' ) ) {
 174              $t_can_bypass_status_access_thresholds = true;
 175          }
 176          if ( !$t_can_bypass_status_access_thresholds ) {
 177              trigger_error( ERROR_ACCESS_DENIED, ERROR );
 178          }
 179      }
 180      if( $t_reopen_issue ) {
 181          # for everyone allowed to reopen an issue, set the reopen resolution
 182          $t_updated_bug->resolution = config_get( 'bug_reopen_resolution' );
 183      }
 184  }
 185  
 186  # Validate any change to the handler of an issue.
 187  $t_issue_is_sponsored = sponsorship_get_amount( sponsorship_get_all_ids( $f_bug_id ) ) > 0;
 188  if ( $t_existing_bug->handler_id !== $t_updated_bug->handler_id ) {
 189      access_ensure_bug_level( config_get( 'update_bug_assign_threshold' ), $f_bug_id );
 190      if ( $t_issue_is_sponsored && !access_has_bug_level( config_get( 'handle_sponsored_bugs_threshold' ), $f_bug_id ) ) {
 191          trigger_error( ERROR_SPONSORSHIP_HANDLER_ACCESS_LEVEL_TOO_LOW, ERROR );
 192      }
 193      if ( $t_updated_bug->handler_id !== NO_USER ) {
 194          if ( !access_has_bug_level( config_get( 'handle_bug_threshold' ), $f_bug_id, $t_updated_bug->handler_id ) ) {
 195              trigger_error( ERROR_HANDLER_ACCESS_TOO_LOW, ERROR );
 196          }
 197          if ( $t_issue_is_sponsored && !access_has_bug_level( config_get( 'assign_sponsored_bugs_threshold' ), $f_bug_id ) ) {
 198              trigger_error( ERROR_SPONSORSHIP_ASSIGNER_ACCESS_LEVEL_TOO_LOW, ERROR );
 199          }
 200      }
 201  }
 202  
 203  # Check whether the category has been undefined when it's compulsory.
 204  if ( $t_existing_bug->category_id !== $t_updated_bug->category_id ) {
 205      if ( $t_updated_bug->category_id === 0 &&
 206           !config_get( 'allow_no_category' ) ) {
 207          error_parameters( lang_get( 'category' ) );
 208          trigger_error( ERROR_EMPTY_FIELD, ERROR );
 209      }
 210  }
 211  
 212  # Don't allow resolutions denoting completion of issue to be used if the issue
 213  # has yet to be resolved or closed.
 214  if ( $t_existing_bug->resolution !== $t_updated_bug->resolution &&
 215       $t_updated_bug->resolution >= config_get( 'bug_resolution_fixed_threshold' ) &&
 216       $t_updated_bug->status < $t_resolved_status ) {
 217      error_parameters( lang_get( 'resolution' ) );
 218      trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
 219  }
 220  
 221  # Ensure that the user has permission to change the target version of the issue.
 222  if ( $t_existing_bug->target_version !== $t_updated_bug->target_version ) {
 223      access_ensure_bug_level( config_get( 'roadmap_update_threshold' ), $f_bug_id );
 224  }
 225  
 226  # Ensure that the user has permission to change the view status of the issue.
 227  if ( $t_existing_bug->view_state !== $t_updated_bug->view_state ) {
 228      access_ensure_bug_level( config_get( 'change_view_status_threshold' ), $f_bug_id );
 229  }
 230  
 231  # Determine the custom field "require check" to use for validating
 232  # whether fields can be undefined during this bug update.
 233  if ( $t_close_issue ) {
 234      $t_cf_require_check = 'require_closed';
 235  } else if ( $t_resolve_issue ) {
 236      $t_cf_require_check = 'require_resolved';
 237  } else {
 238      $t_cf_require_check = 'require_update';
 239  }
 240  
 241  $t_related_custom_field_ids = custom_field_get_linked_ids( $t_existing_bug->project_id );
 242  $t_custom_fields_to_set = array();
 243  foreach ( $t_related_custom_field_ids as $t_cf_id ) {
 244      $t_cf_def = custom_field_get_definition( $t_cf_id );
 245  
 246      if ( !gpc_isset_custom_field( $t_cf_id, $t_cf_def['type'] ) ) {
 247          if ( $t_cf_def[$t_cf_require_check] ) {
 248              # A value for the custom field was expected however
 249              # no value was given by the user.
 250              error_parameters( lang_get_defaulted( custom_field_get_field( $t_cf_id, 'name' ) ) );
 251              trigger_error( ERROR_EMPTY_FIELD, ERROR );
 252          } else {
 253              # The custom field isn't compulsory and the user did
 254              # not supply a value. Therefore we can just ignore this
 255              # custom field completely (ie. don't attempt to update
 256              # the field).
 257              continue;
 258          }
 259      }
 260  
 261      if( !custom_field_has_write_access( $t_cf_id, $f_bug_id ) ) {
 262          trigger_error( ERROR_ACCESS_DENIED, ERROR );
 263      }
 264  
 265      $t_new_custom_field_value = gpc_get_custom_field( "custom_field_$t_cf_id", $t_cf_def['type'], null );
 266      $t_old_custom_field_value = custom_field_get_value( $t_cf_id, $f_bug_id );
 267  
 268      # Validate the value of the field against current validation rules.
 269      # This may cause an error if validation rules have recently been
 270      # modified such that old values that were once OK are now considered
 271      # invalid.
 272      if ( !custom_field_validate( $t_cf_id, $t_new_custom_field_value ) ) {
 273          error_parameters( lang_get_defaulted( custom_field_get_field( $t_cf_id, 'name' ) ) );
 274          trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
 275      }
 276  
 277      # Remember the new custom field values so we can set them when updating
 278      # the bug (done after all data passed to this update page has been
 279      # validated).
 280      $t_custom_fields_to_set[] = array( 'id' => $t_cf_id, 'value' => $t_new_custom_field_value );
 281  }
 282  
 283  # Perform validation of the duplicate ID of the bug.
 284  if ( $t_updated_bug->duplicate_id !== 0 ) {
 285      if ( $t_updated_bug->duplicate_id === $f_bug_id ) {
 286          trigger_error( ERROR_BUG_DUPLICATE_SELF, ERROR );
 287      }
 288      bug_ensure_exists( $t_updated_bug->duplicate_id );
 289      if ( !access_has_bug_level( config_get( 'update_bug_threshold' ), $t_updated_bug->duplicate_id ) ) {
 290          trigger_error( ERROR_RELATIONSHIP_ACCESS_LEVEL_TO_DEST_BUG_TOO_LOW, ERROR );
 291      }
 292      if ( relationship_exists( $f_bug_id, $t_updated_bug->duplicate_id ) ) {
 293          trigger_error( ERROR_RELATIONSHIP_ALREADY_EXISTS, ERROR );
 294      }
 295  }
 296  
 297  # Validate the new bug note (if any is provided).
 298  if ( $t_bug_note->note ||
 299       ( config_get( 'time_tracking_enabled' ) &&
 300         helper_duration_to_minutes( $t_bug_note->time_tracking ) > 0 ) ) {
 301      access_ensure_bug_level( config_get( 'add_bugnote_threshold' ), $f_bug_id );
 302      if ( !$t_bug_note->note &&
 303           !config_get( 'time_tracking_without_note' ) ) {
 304          error_parameters( lang_get( 'bugnote' ) );
 305          trigger_error( ERROR_EMPTY_FIELD, ERROR );
 306      }
 307      if ( $t_bug_note->view_state !== config_get( 'default_bugnote_view_status' ) ) {
 308          access_ensure_bug_level( config_get( 'set_view_status_threshold' ), $f_bug_id );
 309      }
 310  }
 311  
 312  # Handle the reassign on feedback feature. Note that this feature generally
 313  # won't work very well with custom workflows as it makes a lot of assumptions
 314  # that may not be true. It assumes you don't have any statuses in the workflow
 315  # between 'bug_submit_status' and 'bug_feedback_status'. It assumes you only
 316  # have one feedback, assigned and submitted status.
 317  if ( $t_bug_note->note &&
 318       config_get( 'reassign_on_feedback' ) &&
 319       $t_existing_bug->status === config_get( 'bug_feedback_status' ) &&
 320       $t_updated_bug->status !== $t_existing_bug->status &&
 321       $t_updated_bug->handler_id !== auth_get_current_user_id() &&
 322       $t_updated_bug->reporter_id === auth_get_current_user_id() ) {
 323      if ( $t_updated_bug->handler_id !== NO_USER ) {
 324          $t_updated_bug->status = config_get( 'bug_assigned_status' );
 325      } else {
 326          $t_updated_bug->status = config_get( 'bug_submit_status' );
 327      }
 328  }
 329  
 330  # Handle automatic assignment of issues.
 331  if ( $t_existing_bug->handler_id === NO_USER &&
 332       $t_updated_bug->handler_id !== NO_USER &&
 333       $t_updated_bug->status < config_get( 'bug_assigned_status' ) &&
 334       config_get( 'auto_set_status_to_assigned' ) ) {
 335      $t_updated_bug->status = config_get( 'bug_assigned_status' );
 336  }
 337  
 338  # Allow a custom function to validate the proposed bug updates. Note that
 339  # custom functions are being deprecated in MantisBT. You should migrate to
 340  # the new plugin system instead.
 341  helper_call_custom_function( 'issue_update_validate', array( $f_bug_id, $t_updated_bug, $t_bug_note->note ) );
 342  
 343  # Allow plugins to validate/modify the update prior to it being committed.
 344  $t_updated_bug = event_signal( 'EVENT_UPDATE_BUG_DATA', $t_updated_bug, $t_existing_bug );
 345  
 346  # Commit the bug updates to the database.
 347  $t_text_field_update_required = ( $t_existing_bug->description !== $t_updated_bug->description ) ||
 348                                  ( $t_existing_bug->additional_information !== $t_updated_bug->additional_information ) ||
 349                                  ( $t_existing_bug->steps_to_reproduce !== $t_updated_bug->steps_to_reproduce );
 350  $t_updated_bug->update( $t_text_field_update_required, true );
 351  
 352  # Update custom field values.
 353  foreach ( $t_custom_fields_to_set as $t_custom_field_to_set ) {
 354      custom_field_set_value( $t_custom_field_to_set['id'], $f_bug_id, $t_custom_field_to_set['value'] );
 355  }
 356  
 357  # Add a bug note if there is one.
 358  if ( $t_bug_note->note || helper_duration_to_minutes( $t_bug_note->time_tracking ) > 0 ) {
 359      bugnote_add( $f_bug_id, $t_bug_note->note, $t_bug_note->time_tracking, $t_bug_note->view_state == VS_PRIVATE, 0, '', null, false );
 360  }
 361  
 362  # Add a duplicate relationship if requested.
 363  if ( $t_updated_bug->duplicate_id !== 0 ) {
 364      relationship_add( $f_bug_id, $t_updated_bug->duplicate_id, BUG_DUPLICATE );
 365      history_log_event_special( $f_bug_id, BUG_ADD_RELATIONSHIP, BUG_DUPLICATE, $t_updated_bug->duplicate_id );
 366      history_log_event_special( $t_updated_bug->duplicate_id, BUG_ADD_RELATIONSHIP, BUG_HAS_DUPLICATE, $f_bug_id );
 367      if ( user_exists( $t_existing_bug->reporter_id ) ) {
 368          bug_monitor( $f_bug_id, $t_existing_bug->reporter_id );
 369      }
 370      if ( user_exists ( $t_existing_bug->handler_id ) ) {
 371          bug_monitor( $f_bug_id, $t_existing_bug->handler_id );
 372      }
 373      bug_monitor_copy( $f_bug_id, $t_updated_bug->duplicate_id );
 374  }
 375  
 376  event_signal( 'EVENT_UPDATE_BUG', array( $t_existing_bug, $t_updated_bug ) );
 377  
 378  # Allow a custom function to respond to the modifications made to the bug. Note
 379  # that custom functions are being deprecated in MantisBT. You should migrate to
 380  # the new plugin system instead.
 381  helper_call_custom_function( 'issue_update_notify', array( $f_bug_id ) );
 382  
 383  # Send a notification of changes via email.
 384  if ( $t_resolve_issue ) {
 385      email_resolved( $f_bug_id );
 386      email_relationship_child_resolved( $f_bug_id );
 387  } else if ( $t_close_issue ) {
 388      email_close( $f_bug_id );
 389      email_relationship_child_closed( $f_bug_id );
 390  } else if ( $t_reopen_issue ) {
 391      email_reopen( $f_bug_id );
 392  } else if ( $t_existing_bug->handler_id === NO_USER &&
 393              $t_updated_bug->handler_id !== NO_USER ) {
 394      email_assign( $f_bug_id );
 395  } else if ( $t_existing_bug->status !== $t_updated_bug->status ) {
 396      $t_new_status_label = MantisEnum::getLabel( config_get( 'status_enum_string' ), $t_updated_bug->status );
 397      $t_new_status_label = str_replace( ' ', '_', $t_new_status_label );
 398      email_generic( $f_bug_id, $t_new_status_label, 'email_notification_title_for_status_bug_' . $t_new_status_label );
 399  } else {
 400      email_generic( $f_bug_id, 'updated', 'email_notification_title_for_action_bug_updated' );
 401  }
 402  
 403  # Twitter notification of bug update.
 404  if ( $t_resolve_issue &&
 405       $t_updated_bug->resolution >= config_get( 'bug_resolution_fixed_threshold' ) &&
 406       $t_updated_bug->resolution < config_get( 'bug_resolution_not_fixed_threshold' ) ) {
 407      twitter_issue_resolved( $f_bug_id );
 408  }
 409  
 410  form_security_purge( 'bug_update' );
 411  
 412  print_successful_redirect_to_bug( $f_bug_id );


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