| [ Index ] |
PHP Cross Reference of MantisBT |
[Summary view] [Print] [Text view]
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 );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Jul 28 15:48:31 2011 | Cross-referenced by PHPXref 0.7 |