| [ 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 * Bug API 19 * 20 * @package CoreAPI 21 * @subpackage BugAPI 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 bugnote_api.php 29 * @uses bug_revision_api.php 30 * @uses category_api.php 31 * @uses config_api.php 32 * @uses constant_inc.php 33 * @uses custom_field_api.php 34 * @uses database_api.php 35 * @uses date_api.php 36 * @uses email_api.php 37 * @uses error_api.php 38 * @uses event_api.php 39 * @uses file_api.php 40 * @uses helper_api.php 41 * @uses history_api.php 42 * @uses lang_api.php 43 * @uses relationship_api.php 44 * @uses sponsorship_api.php 45 * @uses tag_api.php 46 * @uses twitter_api.php 47 * @uses user_api.php 48 * @uses utility_api.php 49 */ 50 51 require_api( 'access_api.php' ); 52 require_api( 'authentication_api.php' ); 53 require_api( 'bugnote_api.php' ); 54 require_api( 'bug_revision_api.php' ); 55 require_api( 'category_api.php' ); 56 require_api( 'config_api.php' ); 57 require_api( 'constant_inc.php' ); 58 require_api( 'custom_field_api.php' ); 59 require_api( 'database_api.php' ); 60 require_api( 'date_api.php' ); 61 require_api( 'email_api.php' ); 62 require_api( 'error_api.php' ); 63 require_api( 'event_api.php' ); 64 require_api( 'file_api.php' ); 65 require_api( 'helper_api.php' ); 66 require_api( 'history_api.php' ); 67 require_api( 'lang_api.php' ); 68 require_api( 'relationship_api.php' ); 69 require_api( 'sponsorship_api.php' ); 70 require_api( 'tag_api.php' ); 71 require_api( 'twitter_api.php' ); 72 require_api( 'user_api.php' ); 73 require_api( 'utility_api.php' ); 74 75 /** 76 * Bug Data Structure Definition 77 * @package MantisBT 78 * @subpackage classes 79 */ 80 class BugData { 81 protected $id; 82 protected $project_id = null; 83 protected $reporter_id = 0; 84 protected $handler_id = 0; 85 protected $duplicate_id = 0; 86 protected $priority = NORMAL; 87 protected $severity = MINOR; 88 protected $reproducibility = 10; 89 protected $status = NEW_; 90 protected $resolution = OPEN; 91 protected $projection = 10; 92 protected $category_id = 1; 93 protected $date_submitted = ''; 94 protected $last_updated = ''; 95 protected $eta = 10; 96 protected $os = ''; 97 protected $os_build = ''; 98 protected $platform = ''; 99 protected $version = ''; 100 protected $fixed_in_version = ''; 101 protected $target_version = ''; 102 protected $build = ''; 103 protected $view_state = VS_PUBLIC; 104 protected $summary = ''; 105 protected $sponsorship_total = 0; 106 protected $sticky = 0; 107 protected $due_date = 0; 108 109 # omitted: 110 # var $bug_text_id 111 protected $profile_id = 0; 112 113 # extended info 114 protected $description = ''; 115 protected $steps_to_reproduce = ''; 116 protected $additional_information = ''; 117 118 # internal helper objects 119 private $_stats = null; 120 121 public $attachment_count = null; 122 public $bugnotes_count = null; 123 124 private $loading = false; 125 126 /** 127 * return number of file attachment's linked to current bug 128 * @return int 129 */ 130 public function get_attachment_count() { 131 if ( $this->attachment_count === null ) { 132 $this->attachment_count = file_bug_attachment_count( $this->id ); 133 return $this->attachment_count; 134 } else { 135 return $this->attachment_count; 136 } 137 } 138 139 /** 140 * return number of bugnotes's linked to current bug 141 * @return int 142 */ 143 public function get_bugnotes_count() { 144 if ( $this->bugnotes_count === null ) { 145 $this->bugnotes_count = self::bug_get_bugnote_count(); 146 return $this->bugnotes_count; 147 } else { 148 return $this->bugnotes_count; 149 } 150 } 151 152 /** 153 * @private 154 */ 155 public function __set($name, $value) { 156 switch ($name) { 157 // integer types 158 case 'id': 159 case 'project_id': 160 case 'reporter_id': 161 case 'handler_id': 162 case 'duplicate_id': 163 case 'priority': 164 case 'severity': 165 case 'reproducibility': 166 case 'status': 167 case 'resolution': 168 case 'projection': 169 case 'category_id': 170 $value = (int)$value; 171 break; 172 case 'target_version': 173 if ( !$this->loading ) { 174 # Only set target_version if user has access to do so 175 if( !access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) { 176 trigger_error( ERROR_ACCESS_DENIED, ERROR ); 177 } 178 } 179 break; 180 case 'due_date': 181 if ( !is_numeric( $value ) ) { 182 $value = strtotime($value); 183 } 184 break; 185 } 186 $this->$name = $value; 187 } 188 189 /** 190 * @private 191 */ 192 public function __get($name) { 193 if( $this->is_extended_field($name) ) 194 $this->fetch_extended_info(); 195 return $this->{$name}; 196 } 197 198 /** 199 * @private 200 */ 201 public function __isset($name) { 202 return isset( $this->{$name} ); 203 } 204 205 /** 206 * fast-load database row into bugobject 207 * @param array $p_row 208 */ 209 public function loadrow( $p_row ) { 210 $this->loading = true; 211 212 foreach( $p_row as $var => $val ) { 213 $this->__set( $var, $p_row[$var] ); 214 } 215 $this->loading = false; 216 } 217 218 /** 219 * Retrieves extended information for bug (e.g. bug description) 220 * @return null 221 */ 222 private function fetch_extended_info() { 223 if ( $this->description == '' ) { 224 $t_text = bug_text_cache_row($this->id); 225 226 $this->description = $t_text['description']; 227 $this->steps_to_reproduce = $t_text['steps_to_reproduce']; 228 $this->additional_information = $t_text['additional_information']; 229 } 230 } 231 232 /** 233 * Returns if the field is an extended field which needs fetch_extended_info() 234 * @return boolean 235 */ 236 private function is_extended_field( $p_field_name ) { 237 switch( $p_field_name ) { 238 case 'description': 239 case 'steps_to_reproduce': 240 case 'additional_information': 241 return true; 242 default: 243 return false; 244 } 245 } 246 247 /** 248 * Returns the number of bugnotes for the given bug_id 249 * @return int number of bugnotes 250 * @access private 251 * @uses database_api.php 252 */ 253 private function bug_get_bugnote_count() { 254 if( !access_has_project_level( config_get( 'private_bugnote_threshold' ), $this->project_id ) ) { 255 $t_restriction = 'AND view_state=' . VS_PUBLIC; 256 } else { 257 $t_restriction = ''; 258 } 259 260 $t_bugnote_table = db_get_table( 'bugnote' ); 261 $query = "SELECT COUNT(*) 262 FROM $t_bugnote_table 263 WHERE bug_id =" . db_param() . " $t_restriction"; 264 $result = db_query_bound( $query, Array( $this->bug_id ) ); 265 266 return db_result( $result ); 267 } 268 269 /** 270 * validate current bug object for database insert/update 271 * triggers error on failure 272 * @param bool $p_update_extended 273 */ 274 function validate( $p_update_extended = true) { 275 # Summary cannot be blank 276 if( is_blank( $this->summary ) ) { 277 error_parameters( lang_get( 'summary' ) ); 278 trigger_error( ERROR_EMPTY_FIELD, ERROR ); 279 } 280 281 if( $p_update_extended ) { 282 # Description field cannot be empty 283 if( is_blank( $this->description ) ) { 284 error_parameters( lang_get( 'description' ) ); 285 trigger_error( ERROR_EMPTY_FIELD, ERROR ); 286 } 287 } 288 289 # Make sure a category is set 290 if( 0 == $this->category_id && !config_get( 'allow_no_category' ) ) { 291 error_parameters( lang_get( 'category' ) ); 292 trigger_error( ERROR_EMPTY_FIELD, ERROR ); 293 } 294 295 if( !is_blank( $this->duplicate_id ) && ( $this->duplicate_id != 0 ) && ( $this->id == $this->duplicate_id ) ) { 296 trigger_error( ERROR_BUG_DUPLICATE_SELF, ERROR ); 297 # never returns 298 } 299 } 300 301 /** 302 * Insert a new bug into the database 303 * @return int integer representing the bug id that was created 304 * @access public 305 * @uses database_api.php 306 * @uses lang_api.php 307 */ 308 function create() { 309 self::validate( true ); 310 311 # check due_date format 312 if( is_blank( $this->due_date ) ) { 313 $this->due_date = date_get_null(); 314 } 315 # check date submitted and last modified 316 if( is_blank( $this->date_submitted ) ) { 317 $this->date_submitted = db_now(); 318 } 319 if( is_blank( $this->last_updated ) ) { 320 $this->last_updated = db_now(); 321 } 322 323 $t_bug_table = db_get_table( 'bug' ); 324 $t_bug_text_table = db_get_table( 'bug_text' ); 325 $t_category_table = db_get_table( 'category' ); 326 327 # Insert text information 328 $query = "INSERT INTO $t_bug_text_table 329 ( description, steps_to_reproduce, additional_information ) 330 VALUES 331 ( " . db_param() . ',' . db_param() . ',' . db_param() . ')'; 332 db_query_bound( $query, Array( $this->description, $this->steps_to_reproduce, $this->additional_information ) ); 333 334 # Get the id of the text information we just inserted 335 # NOTE: this is guarranteed to be the correct one. 336 # The value LAST_INSERT_ID is stored on a per connection basis. 337 338 $t_text_id = db_insert_id( $t_bug_text_table ); 339 340 # check to see if we want to assign this right off 341 $t_starting_status = config_get( 'bug_submit_status' ); 342 $t_original_status = $this->status; 343 344 # if not assigned, check if it should auto-assigned. 345 if( 0 == $this->handler_id ) { 346 # if a default user is associated with the category and we know at this point 347 # that that the bug was not assigned to somebody, then assign it automatically. 348 $query = "SELECT user_id 349 FROM $t_category_table 350 WHERE id=" . db_param(); 351 $result = db_query_bound( $query, array( $this->category_id ) ); 352 353 if( db_num_rows( $result ) > 0 ) { 354 $this->handler_id = db_result( $result ); 355 } 356 } 357 358 # Check if bug was pre-assigned or auto-assigned. 359 if( ( $this->handler_id != 0 ) && ( $this->status == $t_starting_status ) && ( ON == config_get( 'auto_set_status_to_assigned' ) ) ) { 360 $t_status = config_get( 'bug_assigned_status' ); 361 } else { 362 $t_status = $this->status; 363 } 364 365 # Insert the rest of the data 366 $query = "INSERT INTO $t_bug_table 367 ( project_id,reporter_id, handler_id,duplicate_id, 368 priority,severity, reproducibility,status, 369 resolution,projection, category_id,date_submitted, 370 last_updated,eta, bug_text_id, 371 os, os_build,platform, version,build, 372 profile_id, summary, view_state, sponsorship_total, sticky, fixed_in_version, 373 target_version, due_date 374 ) 375 VALUES 376 ( " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 377 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 378 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 379 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 380 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 381 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ", 382 " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')'; 383 384 db_query_bound( $query, Array( $this->project_id, $this->reporter_id, $this->handler_id, $this->duplicate_id, $this->priority, $this->severity, $this->reproducibility, $t_status, $this->resolution, $this->projection, $this->category_id, $this->date_submitted, $this->last_updated, $this->eta, $t_text_id, $this->os, $this->os_build, $this->platform, $this->version, $this->build, $this->profile_id, $this->summary, $this->view_state, $this->sponsorship_total, $this->sticky, $this->fixed_in_version, $this->target_version, $this->due_date ) ); 385 386 $this->id = db_insert_id( $t_bug_table ); 387 388 # log new bug 389 history_log_event_special( $this->id, NEW_BUG ); 390 391 # log changes, if any (compare happens in history_log_event_direct) 392 history_log_event_direct( $this->id, 'status', $t_original_status, $t_status ); 393 history_log_event_direct( $this->id, 'handler_id', 0, $this->handler_id ); 394 395 return $this->id; 396 } 397 398 /** 399 * Update a bug from the given data structure 400 * If the third parameter is true, also update the longer strings table 401 * @param bool p_update_extended 402 * @param bool p_bypass_email Default false, set to true to avoid generating emails (if sending elsewhere) 403 * @return bool (always true) 404 * @access public 405 */ 406 function update( $p_update_extended = false, $p_bypass_mail = false ) { 407 self::validate( $p_update_extended ); 408 409 $c_bug_id = $this->id; 410 411 if( is_blank( $this->due_date ) ) { 412 $this->due_date = date_get_null(); 413 } 414 415 $t_old_data = bug_get( $this->id, true ); 416 417 $t_bug_table = db_get_table( 'bug' ); 418 419 # Update all fields 420 # Ignore date_submitted and last_updated since they are pulled out 421 # as unix timestamps which could confuse the history log and they 422 # shouldn't get updated like this anyway. If you really need to change 423 # them use bug_set_field() 424 $query = "UPDATE $t_bug_table 425 SET project_id=" . db_param() . ', reporter_id=' . db_param() . ", 426 handler_id=" . db_param() . ', duplicate_id=' . db_param() . ", 427 priority=" . db_param() . ', severity=' . db_param() . ", 428 reproducibility=" . db_param() . ', status=' . db_param() . ", 429 resolution=" . db_param() . ', projection=' . db_param() . ", 430 category_id=" . db_param() . ', eta=' . db_param() . ", 431 os=" . db_param() . ', os_build=' . db_param() . ", 432 platform=" . db_param() . ', version=' . db_param() . ", 433 build=" . db_param() . ', fixed_in_version=' . db_param() . ','; 434 435 $t_fields = Array( 436 $this->project_id, $this->reporter_id, 437 $this->handler_id, $this->duplicate_id, 438 $this->priority, $this->severity, 439 $this->reproducibility, $this->status, 440 $this->resolution, $this->projection, 441 $this->category_id, $this->eta, 442 $this->os, $this->os_build, 443 $this->platform, $this->version, 444 $this->build, $this->fixed_in_version, 445 ); 446 $t_roadmap_updated = false; 447 if( access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) { 448 $query .= " 449 target_version=" . db_param() . ","; 450 $t_fields[] = $this->target_version; 451 $t_roadmap_updated = true; 452 } 453 454 $query .= " 455 view_state=" . db_param() . ", 456 summary=" . db_param() . ", 457 sponsorship_total=" . db_param() . ", 458 sticky=" . db_param() . ", 459 due_date=" . db_param() . " 460 WHERE id=" . db_param(); 461 $t_fields[] = $this->view_state; 462 $t_fields[] = $this->summary; 463 $t_fields[] = $this->sponsorship_total; 464 $t_fields[] = (bool)$this->sticky; 465 $t_fields[] = $this->due_date; 466 $t_fields[] = $this->id; 467 468 db_query_bound( $query, $t_fields ); 469 470 bug_clear_cache( $this->id ); 471 472 # log changes 473 history_log_event_direct( $c_bug_id, 'project_id', $t_old_data->project_id, $this->project_id ); 474 history_log_event_direct( $c_bug_id, 'reporter_id', $t_old_data->reporter_id, $this->reporter_id ); 475 history_log_event_direct( $c_bug_id, 'handler_id', $t_old_data->handler_id, $this->handler_id ); 476 history_log_event_direct( $c_bug_id, 'priority', $t_old_data->priority, $this->priority ); 477 history_log_event_direct( $c_bug_id, 'severity', $t_old_data->severity, $this->severity ); 478 history_log_event_direct( $c_bug_id, 'reproducibility', $t_old_data->reproducibility, $this->reproducibility ); 479 history_log_event_direct( $c_bug_id, 'status', $t_old_data->status, $this->status ); 480 history_log_event_direct( $c_bug_id, 'resolution', $t_old_data->resolution, $this->resolution ); 481 history_log_event_direct( $c_bug_id, 'projection', $t_old_data->projection, $this->projection ); 482 history_log_event_direct( $c_bug_id, 'category', category_full_name( $t_old_data->category_id, false ), category_full_name( $this->category_id, false ) ); 483 history_log_event_direct( $c_bug_id, 'eta', $t_old_data->eta, $this->eta ); 484 history_log_event_direct( $c_bug_id, 'os', $t_old_data->os, $this->os ); 485 history_log_event_direct( $c_bug_id, 'os_build', $t_old_data->os_build, $this->os_build ); 486 history_log_event_direct( $c_bug_id, 'platform', $t_old_data->platform, $this->platform ); 487 history_log_event_direct( $c_bug_id, 'version', $t_old_data->version, $this->version ); 488 history_log_event_direct( $c_bug_id, 'build', $t_old_data->build, $this->build ); 489 history_log_event_direct( $c_bug_id, 'fixed_in_version', $t_old_data->fixed_in_version, $this->fixed_in_version ); 490 if( $t_roadmap_updated ) { 491 history_log_event_direct( $c_bug_id, 'target_version', $t_old_data->target_version, $this->target_version ); 492 } 493 history_log_event_direct( $c_bug_id, 'view_state', $t_old_data->view_state, $this->view_state ); 494 history_log_event_direct( $c_bug_id, 'summary', $t_old_data->summary, $this->summary ); 495 history_log_event_direct( $c_bug_id, 'sponsorship_total', $t_old_data->sponsorship_total, $this->sponsorship_total ); 496 history_log_event_direct( $c_bug_id, 'sticky', $t_old_data->sticky, $this->sticky ); 497 498 history_log_event_direct( $c_bug_id, 'due_date', ( $t_old_data->due_date != date_get_null() ) ? $t_old_data->due_date : null, ( $this->due_date != date_get_null() ) ? $this->due_date : null ); 499 500 # Update extended info if requested 501 if( $p_update_extended ) { 502 $t_bug_text_table = db_get_table( 'bug_text' ); 503 504 $t_bug_text_id = bug_get_field( $c_bug_id, 'bug_text_id' ); 505 506 $query = "UPDATE $t_bug_text_table 507 SET description=" . db_param() . ", 508 steps_to_reproduce=" . db_param() . ", 509 additional_information=" . db_param() . " 510 WHERE id=" . db_param(); 511 db_query_bound( $query, Array( $this->description, $this->steps_to_reproduce, $this->additional_information, $t_bug_text_id ) ); 512 513 bug_text_clear_cache( $c_bug_id ); 514 515 $t_current_user = auth_get_current_user_id(); 516 517 if( $t_old_data->description != $this->description ) { 518 if ( bug_revision_count( $c_bug_id, REV_DESCRIPTION ) < 1 ) { 519 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_DESCRIPTION, $t_old_data->description, 0, $t_old_data->last_updated ); 520 } 521 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_DESCRIPTION, $this->description ); 522 history_log_event_special( $c_bug_id, DESCRIPTION_UPDATED, $t_revision_id ); 523 } 524 525 if( $t_old_data->steps_to_reproduce != $this->steps_to_reproduce ) { 526 if ( bug_revision_count( $c_bug_id, REV_STEPS_TO_REPRODUCE ) < 1 ) { 527 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_STEPS_TO_REPRODUCE, $t_old_data->steps_to_reproduce, 0, $t_old_data->last_updated ); 528 } 529 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_STEPS_TO_REPRODUCE, $this->steps_to_reproduce ); 530 history_log_event_special( $c_bug_id, STEP_TO_REPRODUCE_UPDATED, $t_revision_id ); 531 } 532 533 if( $t_old_data->additional_information != $this->additional_information ) { 534 if ( bug_revision_count( $c_bug_id, REV_ADDITIONAL_INFO ) < 1 ) { 535 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_ADDITIONAL_INFO, $t_old_data->additional_information, 0, $t_old_data->last_updated ); 536 } 537 $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_ADDITIONAL_INFO, $this->additional_information ); 538 history_log_event_special( $c_bug_id, ADDITIONAL_INFO_UPDATED, $t_revision_id ); 539 } 540 } 541 542 # Update the last update date 543 bug_update_date( $c_bug_id ); 544 545 # allow bypass if user is sending mail separately 546 if( false == $p_bypass_mail ) { 547 # bug assigned 548 if( $t_old_data->handler_id != $this->handler_id ) { 549 email_generic( $c_bug_id, 'owner', 'email_notification_title_for_action_bug_assigned' ); 550 return true; 551 } 552 553 # status changed 554 if( $t_old_data->status != $this->status ) { 555 $t_status = MantisEnum::getLabel( config_get( 'status_enum_string' ), $this->status ); 556 $t_status = str_replace( ' ', '_', $t_status ); 557 email_generic( $c_bug_id, $t_status, 'email_notification_title_for_status_bug_' . $t_status ); 558 return true; 559 } 560 561 # @todo handle priority change if it requires special handling 562 # generic update notification 563 email_generic( $c_bug_id, 'updated', 'email_notification_title_for_action_bug_updated' ); 564 } 565 566 return true; 567 } 568 } 569 570 $g_cache_bug = array(); 571 $g_cache_bug_text = array(); 572 573 /** 574 * Cache a database result-set containing full contents of bug_table row. 575 * @param array p_bug_database_result database row containing all columns from mantis_bug_table 576 * @param array p_stats (optional) array representing bugnote stats 577 * @return array returns an array representing the bug row if bug exists 578 * @access public 579 */ 580 function bug_cache_database_result( $p_bug_database_result, $p_stats = null ) { 581 global $g_cache_bug; 582 583 if( !is_array( $p_bug_database_result ) || isset( $g_cache_bug[(int) $p_bug_database_result['id']] ) ) { 584 return $g_cache_bug[(int) $p_bug_database_result['id']]; 585 } 586 587 return bug_add_to_cache( $p_bug_database_result, $p_stats ); 588 } 589 590 /** 591 * Cache a bug row if necessary and return the cached copy 592 * @param array p_bug_id id of bug to cache from mantis_bug_table 593 * @param array p_trigger_errors set to true to trigger an error if the bug does not exist. 594 * @return bool|array returns an array representing the bug row if bug exists or false if bug does not exist 595 * @access public 596 * @uses database_api.php 597 */ 598 function bug_cache_row( $p_bug_id, $p_trigger_errors = true ) { 599 global $g_cache_bug; 600 601 if( isset( $g_cache_bug[$p_bug_id] ) ) { 602 return $g_cache_bug[$p_bug_id]; 603 } 604 605 $c_bug_id = (int) $p_bug_id; 606 $t_bug_table = db_get_table( 'bug' ); 607 608 $query = "SELECT * 609 FROM $t_bug_table 610 WHERE id=" . db_param(); 611 $result = db_query_bound( $query, Array( $c_bug_id ) ); 612 613 if( 0 == db_num_rows( $result ) ) { 614 $g_cache_bug[$c_bug_id] = false; 615 616 if( $p_trigger_errors ) { 617 error_parameters( $p_bug_id ); 618 trigger_error( ERROR_BUG_NOT_FOUND, ERROR ); 619 } else { 620 return false; 621 } 622 } 623 624 $row = db_fetch_array( $result ); 625 626 return bug_add_to_cache( $row ); 627 } 628 629 /** 630 * Cache a set of bugs 631 * @param array p_bug_id_array integer array representing bug ids to cache 632 * @return null 633 * @access public 634 * @uses database_api.php 635 */ 636 function bug_cache_array_rows( $p_bug_id_array ) { 637 global $g_cache_bug; 638 $c_bug_id_array = array(); 639 640 foreach( $p_bug_id_array as $t_bug_id ) { 641 if( !isset( $g_cache_bug[(int) $t_bug_id] ) ) { 642 $c_bug_id_array[] = (int) $t_bug_id; 643 } 644 } 645 646 if( empty( $c_bug_id_array ) ) { 647 return; 648 } 649 650 $t_bug_table = db_get_table( 'bug' ); 651 652 $query = "SELECT * 653 FROM $t_bug_table 654 WHERE id IN (" . implode( ',', $c_bug_id_array ) . ')'; 655 $result = db_query_bound( $query ); 656 657 while( $row = db_fetch_array( $result ) ) { 658 bug_add_to_cache( $row ); 659 } 660 return; 661 } 662 663 /** 664 * Inject a bug into the bug cache 665 * @param array p_bug_row bug row to cache 666 * @param array p_stats bugnote stats to cache 667 * @return null 668 * @access private 669 */ 670 function bug_add_to_cache( $p_bug_row, $p_stats = null ) { 671 global $g_cache_bug; 672 673 $g_cache_bug[(int) $p_bug_row['id']] = $p_bug_row; 674 675 if( !is_null( $p_stats ) ) { 676 $g_cache_bug[(int) $p_bug_row['id']]['_stats'] = $p_stats; 677 } 678 679 return $g_cache_bug[(int) $p_bug_row['id']]; 680 } 681 682 /** 683 * Clear a bug from the cache or all bugs if no bug id specified. 684 * @param int bug id to clear (optional) 685 * @return null 686 * @access public 687 */ 688 function bug_clear_cache( $p_bug_id = null ) { 689 global $g_cache_bug; 690 691 if( null === $p_bug_id ) { 692 $g_cache_bug = array(); 693 } else { 694 unset( $g_cache_bug[(int) $p_bug_id] ); 695 } 696 697 return true; 698 } 699 700 /** 701 * Cache a bug text row if necessary and return the cached copy 702 * @param int p_bug_id integer bug id to retrieve text for 703 * @param bool p_trigger_errors If the second parameter is true (default), trigger an error if bug text not found. 704 * @return bool|array returns false if not bug text found or array of bug text 705 * @access public 706 * @uses database_api.php 707 */ 708 function bug_text_cache_row( $p_bug_id, $p_trigger_errors = true ) { 709 global $g_cache_bug_text; 710 711 $c_bug_id = (int) $p_bug_id; 712 $t_bug_table = db_get_table( 'bug' ); 713 $t_bug_text_table = db_get_table( 'bug_text' ); 714 715 if( isset( $g_cache_bug_text[$c_bug_id] ) ) { 716 return $g_cache_bug_text[$c_bug_id]; 717 } 718 719 $query = "SELECT bt.* 720 FROM $t_bug_text_table bt, $t_bug_table b 721 WHERE b.id=" . db_param() . " AND 722 b.bug_text_id = bt.id"; 723 $result = db_query_bound( $query, Array( $c_bug_id ) ); 724 725 if( 0 == db_num_rows( $result ) ) { 726 $g_cache_bug_text[$c_bug_id] = false; 727 728 if( $p_trigger_errors ) { 729 error_parameters( $p_bug_id ); 730 trigger_error( ERROR_BUG_NOT_FOUND, ERROR ); 731 } else { 732 return false; 733 } 734 } 735 736 $row = db_fetch_array( $result ); 737 738 $g_cache_bug_text[$c_bug_id] = $row; 739 740 return $row; 741 } 742 743 /** 744 * Clear a bug's bug text from the cache or all bug text if no bug id specified. 745 * @param int bug id to clear (optional) 746 * @return null 747 * @access public 748 */ 749 function bug_text_clear_cache( $p_bug_id = null ) { 750 global $g_cache_bug_text; 751 752 if( null === $p_bug_id ) { 753 $g_cache_bug_text = array(); 754 } else { 755 unset( $g_cache_bug_text[(int) $p_bug_id] ); 756 } 757 758 return true; 759 } 760 761 /** 762 * Check if a bug exists 763 * @param int p_bug_id integer representing bug id 764 * @return bool true if bug exists, false otherwise 765 * @access public 766 */ 767 function bug_exists( $p_bug_id ) { 768 if( false == bug_cache_row( $p_bug_id, false ) ) { 769 return false; 770 } else { 771 return true; 772 } 773 } 774 775 /** 776 * Check if a bug exists. If it doesn't then trigger an error 777 * @param int p_bug_id integer representing bug id 778 * @return null 779 * @access public 780 */ 781 function bug_ensure_exists( $p_bug_id ) { 782 if( !bug_exists( $p_bug_id ) ) { 783 error_parameters( $p_bug_id ); 784 trigger_error( ERROR_BUG_NOT_FOUND, ERROR ); 785 } 786 } 787 788 /** 789 * check if the given user is the reporter of the bug 790 * @param int p_bug_id integer representing bug id 791 * @param int p_user_id integer reprenting a user id 792 * @return bool return true if the user is the reporter, false otherwise 793 * @access public 794 */ 795 function bug_is_user_reporter( $p_bug_id, $p_user_id ) { 796 if( bug_get_field( $p_bug_id, 'reporter_id' ) == $p_user_id ) { 797 return true; 798 } else { 799 return false; 800 } 801 } 802 803 /** 804 * check if the given user is the handler of the bug 805 * @param int p_bug_id integer representing bug id 806 * @param int p_user_id integer reprenting a user id 807 * @return bool return true if the user is the handler, false otherwise 808 * @access public 809 */ 810 function bug_is_user_handler( $p_bug_id, $p_user_id ) { 811 if( bug_get_field( $p_bug_id, 'handler_id' ) == $p_user_id ) { 812 return true; 813 } else { 814 return false; 815 } 816 } 817 818 /** 819 * Check if the bug is readonly and shouldn't be modified 820 * For a bug to be readonly the status has to be >= bug_readonly_status_threshold and 821 * current user access level < update_readonly_bug_threshold. 822 * @param int p_bug_id integer representing bug id 823 * @return bool 824 * @access public 825 * @uses access_api.php 826 * @uses config_api.php 827 */ 828 function bug_is_readonly( $p_bug_id ) { 829 $t_status = bug_get_field( $p_bug_id, 'status' ); 830 if( $t_status < config_get( 'bug_readonly_status_threshold' ) ) { 831 return false; 832 } 833 834 if( access_has_bug_level( config_get( 'update_readonly_bug_threshold' ), $p_bug_id ) ) { 835 return false; 836 } 837 838 return true; 839 } 840 841 /** 842 * Check if a given bug is resolved 843 * @param int p_bug_id integer representing bug id 844 * @return bool true if bug is resolved, false otherwise 845 * @access public 846 * @uses config_api.php 847 */ 848 function bug_is_resolved( $p_bug_id ) { 849 $t_status = bug_get_field( $p_bug_id, 'status' ); 850 return( $t_status >= config_get( 'bug_resolved_status_threshold' ) ); 851 } 852 853 /** 854 * Check if a given bug is overdue 855 * @param int p_bug_id integer representing bug id 856 * @return bool true if bug is overdue, false otherwise 857 * @access public 858 * @uses database_api.php 859 */ 860 function bug_is_overdue( $p_bug_id ) { 861 $t_due_date = bug_get_field( $p_bug_id, 'due_date' ); 862 if( !date_is_null( $t_due_date ) ) { 863 $t_now = db_now(); 864 if( $t_now > $t_due_date ) { 865 if( !bug_is_resolved( $p_bug_id ) ) { 866 return true; 867 } 868 } 869 } 870 return false; 871 } 872 873 /** 874 * Validate workflow state to see if bug can be moved to requested state 875 * @param int p_bug_status current bug status 876 * @param int p_wanted_status new bug status 877 * @return bool 878 * @access public 879 * @uses config_api.php 880 * @uses utility_api.php 881 */ 882 function bug_check_workflow( $p_bug_status, $p_wanted_status ) { 883 $t_status_enum_workflow = config_get( 'status_enum_workflow' ); 884 885 if( count( $t_status_enum_workflow ) < 1 ) { 886 887 # workflow not defined, use default enum 888 return true; 889 } 890 891 if ( $p_bug_status == $p_wanted_status ) { 892 # no change in state, allow the transition 893 return true; 894 } 895 896 # workflow defined - find allowed states 897 $t_allowed_states = $t_status_enum_workflow[$p_bug_status]; 898 899 return MantisEnum::hasValue( $t_allowed_states, $p_wanted_status ); 900 } 901 902 /** 903 * Copy a bug from one project to another. Also make copies of issue notes, attachments, history, 904 * email notifications etc. 905 * @todo Not managed FTP file upload 906 * @param array p_bug_id integer representing bug id 907 * @param int p_target_project_id 908 * @param bool p_copy_custom_fields 909 * @param bool p_copy_relationships 910 * @return int representing the new bugid 911 * @access public 912 */ 913 function bug_copy( $p_bug_id, $p_target_project_id = null, $p_copy_custom_fields = false, $p_copy_relationships = false, $p_copy_history = false, $p_copy_attachments = false, $p_copy_bugnotes = false, $p_copy_monitoring_users = false ) { 914 global $g_db; 915 916 $t_mantis_custom_field_string_table = db_get_table( 'custom_field_string' ); 917 $t_mantis_bug_file_table = db_get_table( 'bug_file' ); 918 $t_mantis_bugnote_table = db_get_table( 'bugnote' ); 919 $t_mantis_bugnote_text_table = db_get_table( 'bugnote_text' ); 920 $t_mantis_bug_history_table = db_get_table( 'bug_history' ); 921 $t_mantis_db = $g_db; 922 923 $t_bug_id = db_prepare_int( $p_bug_id ); 924 $t_target_project_id = db_prepare_int( $p_target_project_id ); 925 926 $t_bug_data = bug_get( $t_bug_id, true ); 927 928 # retrieve the project id associated with the bug 929 if(( $p_target_project_id == null ) || is_blank( $p_target_project_id ) ) { 930 $t_target_project_id = $t_bug_data->project_id; 931 } 932 933 $t_bug_data->project_id = $t_target_project_id; 934 935 $t_new_bug_id = $t_bug_data->create(); 936 937 # MASC ATTENTION: IF THE SOURCE BUG HAS TO HANDLER THE bug_create FUNCTION CAN TRY TO AUTO-ASSIGN THE BUG 938 # WE FORCE HERE TO DUPLICATE THE SAME HANDLER OF THE SOURCE BUG 939 # @todo VB: Shouldn't we check if the handler in the source project is also a handler in the destination project? 940 bug_set_field( $t_new_bug_id, 'handler_id', $t_bug_data->handler_id ); 941 942 bug_set_field( $t_new_bug_id, 'duplicate_id', $t_bug_data->duplicate_id ); 943 bug_set_field( $t_new_bug_id, 'status', $t_bug_data->status ); 944 bug_set_field( $t_new_bug_id, 'resolution', $t_bug_data->resolution ); 945 bug_set_field( $t_new_bug_id, 'projection', $t_bug_data->projection ); 946 bug_set_field( $t_new_bug_id, 'date_submitted', $t_bug_data->date_submitted ); 947 bug_set_field( $t_new_bug_id, 'last_updated', $t_bug_data->last_updated ); 948 bug_set_field( $t_new_bug_id, 'eta', $t_bug_data->eta ); 949 bug_set_field( $t_new_bug_id, 'fixed_in_version', $t_bug_data->fixed_in_version ); 950 bug_set_field( $t_new_bug_id, 'target_version', $t_bug_data->target_version ); 951 bug_set_field( $t_new_bug_id, 'sponsorship_total', 0 ); 952 bug_set_field( $t_new_bug_id, 'sticky', 0 ); 953 bug_set_field( $t_new_bug_id, 'due_date', $t_bug_data->due_date ); 954 955 # COPY CUSTOM FIELDS 956 if( $p_copy_custom_fields ) { 957 $query = "SELECT field_id, bug_id, value 958 FROM $t_mantis_custom_field_string_table 959 WHERE bug_id=" . db_param(); 960 $result = db_query_bound( $query, Array( $t_bug_id ) ); 961 $t_count = db_num_rows( $result ); 962 963 for( $i = 0;$i < $t_count;$i++ ) { 964 $t_bug_custom = db_fetch_array( $result ); 965 966 $c_field_id = db_prepare_int( $t_bug_custom['field_id'] ); 967 $c_new_bug_id = db_prepare_int( $t_new_bug_id ); 968 $c_value = $t_bug_custom['value']; 969 970 $query = "INSERT INTO $t_mantis_custom_field_string_table 971 ( field_id, bug_id, value ) 972 VALUES (" . db_param() . ', ' . db_param() . ', ' . db_param() . ')'; 973 db_query_bound( $query, Array( $c_field_id, $c_new_bug_id, $c_value ) ); 974 } 975 } 976 977 # Copy Relationships 978 if( $p_copy_relationships ) { 979 relationship_copy_all( $t_bug_id, $t_new_bug_id ); 980 } 981 982 # Copy bugnotes 983 if( $p_copy_bugnotes ) { 984 $query = "SELECT * 985 FROM $t_mantis_bugnote_table 986 WHERE bug_id=" . db_param(); 987 $result = db_query_bound( $query, Array( $t_bug_id ) ); 988 $t_count = db_num_rows( $result ); 989 990 for( $i = 0;$i < $t_count;$i++ ) { 991 $t_bug_note = db_fetch_array( $result ); 992 $t_bugnote_text_id = $t_bug_note['bugnote_text_id']; 993 994 $query2 = "SELECT * 995 FROM $t_mantis_bugnote_text_table 996 WHERE id=" . db_param(); 997 $result2 = db_query_bound( $query2, Array( $t_bugnote_text_id ) ); 998 $t_count2 = db_num_rows( $result2 ); 999 1000 $t_bugnote_text_insert_id = -1; 1001 if( $t_count2 > 0 ) { 1002 $t_bugnote_text = db_fetch_array( $result2 ); 1003 1004 $query2 = "INSERT INTO $t_mantis_bugnote_text_table 1005 ( note ) 1006 VALUES ( " . db_param() . ' )'; 1007 db_query_bound( $query2, Array( $t_bugnote_text['note'] ) ); 1008 $t_bugnote_text_insert_id = db_insert_id( $t_mantis_bugnote_text_table ); 1009 } 1010 1011 $query2 = "INSERT INTO $t_mantis_bugnote_table 1012 ( bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified ) 1013 VALUES ( " . db_param() . ", 1014 " . db_param() . ", 1015 " . db_param() . ", 1016 " . db_param() . ", 1017 " . db_param() . ", 1018 " . db_param() . ')'; 1019 db_query_bound( $query2, Array( $t_new_bug_id, $t_bug_note['reporter_id'], $t_bugnote_text_insert_id, $t_bug_note['view_state'], $t_bug_note['date_submitted'], $t_bug_note['last_modified'] ) ); 1020 } 1021 } 1022 1023 # Copy attachments 1024 if( $p_copy_attachments ) { 1025 $query = 'SELECT * FROM ' . $t_mantis_bug_file_table . ' WHERE bug_id = ' . db_param(); 1026 $result = db_query_bound( $query, Array( $t_bug_id ) ); 1027 $t_count = db_num_rows( $result ); 1028 1029 $t_bug_file = array(); 1030 for( $i = 0;$i < $t_count;$i++ ) { 1031 $t_bug_file = db_fetch_array( $result ); 1032 1033 # prepare the new diskfile name and then copy the file 1034 $t_file_path = dirname( $t_bug_file['folder'] ); 1035 $t_new_diskfile_name = $t_file_path . file_generate_unique_name( 'bug-' . $t_bug_file['filename'], $t_file_path ); 1036 $t_new_file_name = file_get_display_name( $t_bug_file['filename'] ); 1037 if(( config_get( 'file_upload_method' ) == DISK ) ) { 1038 copy( $t_bug_file['diskfile'], $t_new_diskfile_name ); 1039 chmod( $t_new_diskfile_name, config_get( 'attachments_file_permissions' ) ); 1040 } 1041 1042 $query = "INSERT INTO $t_mantis_bug_file_table 1043 ( bug_id, title, description, diskfile, filename, folder, filesize, file_type, date_added, content ) 1044 VALUES ( " . db_param() . ", 1045 " . db_param() . ", 1046 " . db_param() . ", 1047 " . db_param() . ", 1048 " . db_param() . ", 1049 " . db_param() . ", 1050 " . db_param() . ", 1051 " . db_param() . ", 1052 " . db_param() . ", 1053 " . db_param() . ");"; 1054 db_query_bound( $query, Array( $t_new_bug_id, $t_bug_file['title'], $t_bug_file['description'], $t_new_diskfile_name, $t_new_file_name, $t_bug_file['folder'], $t_bug_file['filesize'], $t_bug_file['file_type'], $t_bug_file['date_added'], $t_bug_file['content'] ) ); 1055 } 1056 } 1057 1058 # Copy users monitoring bug 1059 if( $p_copy_monitoring_users ) { 1060 bug_monitor_copy( $t_bug_id, $t_new_bug_id ); 1061 } 1062 1063 # COPY HISTORY 1064 history_delete( $t_new_bug_id ); # should history only be deleted inside the if statement below? 1065 if( $p_copy_history ) { 1066 $query = "SELECT * 1067 FROM $t_mantis_bug_history_table 1068 WHERE bug_id = " . db_param(); 1069 $result = db_query_bound( $query, Array( $t_bug_id ) ); 1070 $t_count = db_num_rows( $result ); 1071 1072 for( $i = 0;$i < $t_count;$i++ ) { 1073 $t_bug_history = db_fetch_array( $result ); 1074 $query = "INSERT INTO $t_mantis_bug_history_table 1075 ( user_id, bug_id, date_modified, field_name, old_value, new_value, type ) 1076 VALUES ( " . db_param() . ", 1077 " . db_param() . ", 1078 " . db_param() . ", 1079 " . db_param() . ", 1080 " . db_param() . ", 1081 " . db_param() . ", 1082 " . db_param() . " );"; 1083 db_query_bound( $query, Array( $t_bug_history['user_id'], $t_new_bug_id, $t_bug_history['date_modified'], $t_bug_history['field_name'], $t_bug_history['old_value'], $t_bug_history['new_value'], $t_bug_history['type'] ) ); 1084 } 1085 } 1086 1087 return $t_new_bug_id; 1088 } 1089 1090 /** 1091 * Moves an issue from a project to another. 1092 * 1093 * @todo Validate with sub-project / category inheritance scenarios. 1094 * @param int p_bug_id The bug to be moved. 1095 * @param int p_target_project_id The target project to move the bug to. 1096 * @access public 1097 */ 1098 function bug_move( $p_bug_id, $p_target_project_id ) { 1099 // Attempt to move disk based attachments to new project file directory. 1100 file_move_bug_attachments( $p_bug_id, $p_target_project_id ); 1101 1102 // Move the issue to the new project. 1103 bug_set_field( $p_bug_id, 'project_id', $p_target_project_id ); 1104 1105 // Check if the category for the issue is global or not. 1106 $t_category_id = bug_get_field( $p_bug_id, 'category_id' ); 1107 $t_category_project_id = category_get_field( $t_category_id, 'project_id' ); 1108 1109 // If not global, then attempt mapping it to the new project. 1110 if ( $t_category_project_id != ALL_PROJECTS ) { 1111 // Map by name 1112 $t_category_name = category_get_field( $t_category_id, 'name' ); 1113 $t_target_project_category_id = category_get_id_by_name( $t_category_name, $p_target_project_id, /* triggerErrors */ false ); 1114 if ( $t_target_project_category_id === false ) { 1115 // Use default category after moves, since there is no match by name. 1116 $t_target_project_category_id = config_get( 'default_category_for_moves' ); 1117 } 1118 1119 bug_set_field( $p_bug_id, 'category_id', $t_target_project_category_id ); 1120 } 1121 } 1122 1123 /** 1124 * allows bug deletion : 1125 * delete the bug, bugtext, bugnote, and bugtexts selected 1126 * @param array p_bug_id integer representing bug id 1127 * @return bool (always true) 1128 * @access public 1129 */ 1130 function bug_delete( $p_bug_id ) { 1131 $c_bug_id = (int) $p_bug_id; 1132 $t_bug_table = db_get_table( 'bug' ); 1133 $t_bug_text_table = db_get_table( 'bug_text' ); 1134 1135 # call pre-deletion custom function 1136 helper_call_custom_function( 'issue_delete_validate', array( $p_bug_id ) ); 1137 1138 # log deletion of bug 1139 history_log_event_special( $p_bug_id, BUG_DELETED, bug_format_id( $p_bug_id ) ); 1140 1141 email_bug_deleted( $p_bug_id ); 1142 1143 # call post-deletion custom function. We call this here to allow the custom function to access the details of the bug before 1144 # they are deleted from the database given it's id. The other option would be to move this to the end of the function and 1145 # provide it with bug data rather than an id, but this will break backward compatibility. 1146 helper_call_custom_function( 'issue_delete_notify', array( $p_bug_id ) ); 1147 1148 # Unmonitor bug for all users 1149 bug_unmonitor( $p_bug_id, null ); 1150 1151 # Delete custom fields 1152 custom_field_delete_all_values( $p_bug_id ); 1153 1154 # Delete bugnotes 1155 bugnote_delete_all( $p_bug_id ); 1156 1157 # Delete all sponsorships 1158 sponsorship_delete( sponsorship_get_all_ids( $p_bug_id ) ); 1159 1160 # MASC RELATIONSHIP 1161 # we delete relationships even if the feature is currently off. 1162 relationship_delete_all( $p_bug_id ); 1163 1164 # MASC RELATIONSHIP 1165 # Delete files 1166 file_delete_attachments( $p_bug_id ); 1167 1168 # Detach tags 1169 tag_bug_detach_all( $p_bug_id, false ); 1170 1171 # Delete the bug history 1172 history_delete( $p_bug_id ); 1173 1174 # Delete bug info revisions 1175 bug_revision_delete( $p_bug_id ); 1176 1177 # Delete the bugnote text 1178 $t_bug_text_id = bug_get_field( $p_bug_id, 'bug_text_id' ); 1179 1180 $query = "DELETE FROM $t_bug_text_table 1181 WHERE id=" . db_param(); 1182 db_query_bound( $query, Array( $t_bug_text_id ) ); 1183 1184 # Delete the bug entry 1185 $query = "DELETE FROM $t_bug_table 1186 WHERE id=" . db_param(); 1187 db_query_bound( $query, Array( $c_bug_id ) ); 1188 1189 bug_clear_cache( $p_bug_id ); 1190 bug_text_clear_cache( $p_bug_id ); 1191 1192 # db_query errors on failure so: 1193 return true; 1194 } 1195 1196 /** 1197 * Delete all bugs associated with a project 1198 * @param array p_project_id integer representing a projectid 1199 * @return bool always true 1200 * @access public 1201 * @uses database_api.php 1202 */ 1203 function bug_delete_all( $p_project_id ) { 1204 $c_project_id = (int) $p_project_id; 1205 1206 $t_bug_table = db_get_table( 'bug' ); 1207 1208 $query = "SELECT id 1209 FROM $t_bug_table 1210 WHERE project_id=" . db_param(); 1211 $result = db_query_bound( $query, array( $c_project_id ) ); 1212 1213 $bug_count = db_num_rows( $result ); 1214 1215 for( $i = 0;$i < $bug_count;$i++ ) { 1216 $row = db_fetch_array( $result ); 1217 1218 bug_delete( $row['id'] ); 1219 } 1220 1221 # @todo should we check the return value of each bug_delete() and 1222 # return false if any of them return false? Presumable bug_delete() 1223 # will eventually trigger an error on failure so it won't matter... 1224 1225 return true; 1226 } 1227 1228 /** 1229 * Returns the extended record of the specified bug, this includes 1230 * the bug text fields 1231 * @todo include reporter name and handler name, the problem is that 1232 * handler can be 0, in this case no corresponding name will be 1233 * found. Use equivalent of (+) in Oracle. 1234 * @param int p_bug_id integer representing bug id 1235 * @return array 1236 * @access public 1237 */ 1238 function bug_get_extended_row( $p_bug_id ) { 1239 $t_base = bug_cache_row( $p_bug_id ); 1240 $t_text = bug_text_cache_row( $p_bug_id ); 1241 1242 # merge $t_text first so that the 'id' key has the bug id not the bug text id 1243 return array_merge( $t_text, $t_base ); 1244 } 1245 1246 /** 1247 * Returns the record of the specified bug 1248 * @param int p_bug_id integer representing bug id 1249 * @return array 1250 * @access public 1251 */ 1252 function bug_get_row( $p_bug_id ) { 1253 return bug_cache_row( $p_bug_id ); 1254 } 1255 1256 /** 1257 * Returns an object representing the specified bug 1258 * @param int p_bug_id integer representing bug id 1259 * @param bool p_get_extended included extended information (including bug_text) 1260 * @return object BugData Object 1261 * @access public 1262 */ 1263 function bug_get( $p_bug_id, $p_get_extended = false ) { 1264 if( $p_get_extended ) { 1265 $row = bug_get_extended_row( $p_bug_id ); 1266 } else { 1267 $row = bug_get_row( $p_bug_id ); 1268 } 1269 1270 $t_bug_data = new BugData; 1271 $t_bug_data->loadrow( $row ); 1272 return $t_bug_data; 1273 } 1274 1275 function bug_row_to_object( $p_row ) { 1276 $t_bug_data = new BugData; 1277 $t_bug_data->loadrow( $p_row ); 1278 return $t_bug_data; 1279 } 1280 1281 /** 1282 * return the specified field of the given bug 1283 * if the field does not exist, display a warning and return '' 1284 * @param int p_bug_id integer representing bug id 1285 * @param string p_fieldname field name 1286 * @return string 1287 * @access public 1288 */ 1289 function bug_get_field( $p_bug_id, $p_field_name ) { 1290 $row = bug_get_row( $p_bug_id ); 1291 1292 if( isset( $row[$p_field_name] ) ) { 1293 return $row[$p_field_name]; 1294 } else { 1295 error_parameters( $p_field_name ); 1296 trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); 1297 return ''; 1298 } 1299 } 1300 1301 /** 1302 * return the specified text field of the given bug 1303 * if the field does not exist, display a warning and return '' 1304 * @param int p_bug_id integer representing bug id 1305 * @param string p_fieldname field name 1306 * @return string 1307 * @access public 1308 */ 1309 function bug_get_text_field( $p_bug_id, $p_field_name ) { 1310 $row = bug_text_cache_row( $p_bug_id ); 1311 1312 if( isset( $row[$p_field_name] ) ) { 1313 return $row[$p_field_name]; 1314 } else { 1315 error_parameters( $p_field_name ); 1316 trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); 1317 return ''; 1318 } 1319 } 1320 1321 /** 1322 * return the bug summary 1323 * this is a wrapper for the custom function 1324 * @param int p_bug_id integer representing bug id 1325 * @param int p_context representing SUMMARY_CAPTION, SUMMARY_FIELD 1326 * @return string 1327 * @access public 1328 * @uses helper_api.php 1329 */ 1330 function bug_format_summary( $p_bug_id, $p_context ) { 1331 return helper_call_custom_function( 'format_issue_summary', array( $p_bug_id, $p_context ) ); 1332 } 1333 1334 /** 1335 * return the timestamp for the most recent time at which a bugnote 1336 * associated with the bug was modified 1337 * @param int p_bug_id integer representing bug id 1338 * @return bool|int false or timestamp in integer format representing newest bugnote timestamp 1339 * @access public 1340 * @uses database_api.php 1341 */ 1342 function bug_get_newest_bugnote_timestamp( $p_bug_id ) { 1343 $c_bug_id = db_prepare_int( $p_bug_id ); 1344 $t_bugnote_table = db_get_table( 'bugnote' ); 1345 1346 $query = "SELECT last_modified 1347 FROM $t_bugnote_table 1348 WHERE bug_id=" . db_param() . " 1349 ORDER BY last_modified DESC"; 1350 $result = db_query_bound( $query, Array( $c_bug_id ), 1 ); 1351 $row = db_result( $result ); 1352 1353 if( false === $row ) { 1354 return false; 1355 } else { 1356 return $row; 1357 } 1358 } 1359 1360 /** 1361 * return the timestamp for the most recent time at which a bugnote 1362 * associated with the bug was modified and the total bugnote 1363 * count in one db query 1364 * @param int p_bug_id integer representing bug id 1365 * @return object consisting of bugnote stats 1366 * @access public 1367 * @uses database_api.php 1368 */ 1369 function bug_get_bugnote_stats( $p_bug_id ) { 1370 global $g_cache_bug; 1371 $c_bug_id = db_prepare_int( $p_bug_id ); 1372 1373 if( !is_null( $g_cache_bug[$c_bug_id]['_stats'] ) ) { 1374 if( $g_cache_bug[$c_bug_id]['_stats'] === false ) { 1375 return false; 1376 } else { 1377 $t_stats = $g_cache_bug[$c_bug_id]['_stats']; 1378 } 1379 return $t_stats; 1380 } 1381 1382 $t_bugnote_table = db_get_table( 'bugnote' ); 1383 1384 $query = "SELECT last_modified 1385 FROM $t_bugnote_table 1386 WHERE bug_id=" . db_param() . " 1387 ORDER BY last_modified DESC"; 1388 $result = db_query_bound( $query, Array( $c_bug_id ) ); 1389 $row = db_fetch_array( $result ); 1390 1391 if( false === $row ) { 1392 return false; 1393 } 1394 1395 $t_stats['last_modified'] = $row['last_modified']; 1396 $t_stats['count'] = db_num_rows( $result ); 1397 1398 return $t_stats; 1399 } 1400 1401 /** 1402 * Get array of attachments associated with the specified bug id. The array will be 1403 * sorted in terms of date added (ASC). The array will include the following fields: 1404 * id, title, diskfile, filename, filesize, file_type, date_added, user_id. 1405 * @param int p_bug_id integer representing bug id 1406 * @return array array of results or null 1407 * @access public 1408 * @uses database_api.php 1409 * @uses file_api.php 1410 */ 1411 function bug_get_attachments( $p_bug_id ) { 1412 $c_bug_id = db_prepare_int( $p_bug_id ); 1413 1414 $t_bug_file_table = db_get_table( 'bug_file' ); 1415 1416 $query = "SELECT id, title, diskfile, filename, filesize, file_type, date_added, user_id 1417 FROM $t_bug_file_table 1418 WHERE bug_id=" . db_param() . " 1419 ORDER BY date_added"; 1420 $db_result = db_query_bound( $query, Array( $c_bug_id ) ); 1421 $num_files = db_num_rows( $db_result ); 1422 1423 $t_result = array(); 1424 1425 for( $i = 0;$i < $num_files;$i++ ) { 1426 $t_result[] = db_fetch_array( $db_result ); 1427 } 1428 1429 return $t_result; 1430 } 1431 1432 # =================================== 1433 # Data Modification 1434 # =================================== 1435 /** 1436 * Set the value of a bug field 1437 * @param int p_bug_id integer representing bug id 1438 * @param string p_field_name pre-defined field name 1439 * @param any p_value value to set 1440 * @return bool (always true) 1441 * @access public 1442 * @uses database_api.php 1443 * @uses history_api.php 1444 */ 1445 function bug_set_field( $p_bug_id, $p_field_name, $p_value ) { 1446 $c_bug_id = db_prepare_int( $p_bug_id ); 1447 $c_value = null; 1448 1449 switch( $p_field_name ) { 1450 1451 # bool 1452 case 'sticky': 1453 $c_value = $p_value; 1454 break; 1455 1456 # int 1457 case 'project_id': 1458 case 'reporter_id': 1459 case 'handler_id': 1460 case 'duplicate_id': 1461 case 'priority': 1462 case 'severity': 1463 case 'reproducibility': 1464 case 'status': 1465 case 'resolution': 1466 case 'projection': 1467 case 'category_id': 1468 case 'eta': 1469 case 'view_state': 1470 case 'profile_id': 1471 case 'sponsorship_total': 1472 $c_value = (int) $p_value; 1473 break; 1474 1475 # string 1476 case 'os': 1477 case 'os_build': 1478 case 'platform': 1479 case 'version': 1480 case 'fixed_in_version': 1481 case 'target_version': 1482 case 'build': 1483 case 'summary': 1484 $c_value = $p_value; 1485 break; 1486 1487 # dates 1488 case 'last_updated': 1489 case 'date_submitted': 1490 case 'due_date': 1491 if ( !is_numeric( $p_value ) ) { 1492 trigger_error( ERROR_GENERIC, ERROR ); 1493 } 1494 $c_value = $p_value; 1495 break; 1496 1497 default: 1498 trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); 1499 break; 1500 } 1501 1502 $t_current_value = bug_get_field( $p_bug_id, $p_field_name ); 1503 1504 # return if status is already set 1505 if( $c_value == $t_current_value ) { 1506 return true; 1507 } 1508 $t_bug_table = db_get_table( 'bug' ); 1509 1510 # Update fields 1511 $query = "UPDATE $t_bug_table 1512 SET $p_field_name=" . db_param() . " 1513 WHERE id=" . db_param(); 1514 db_query_bound( $query, Array( $c_value, $c_bug_id ) ); 1515 1516 # updated the last_updated date 1517 bug_update_date( $p_bug_id ); 1518 1519 # log changes except for duplicate_id which is obsolete and should be removed in 1520 # MantisBT 1.3. 1521 switch( $p_field_name ) { 1522 case 'duplicate_id': 1523 break; 1524 1525 case 'category_id': 1526 history_log_event_direct( $p_bug_id, 'category', category_full_name( $t_current_value, false ), category_full_name( $c_value, false ) ); 1527 break; 1528 1529 default: 1530 history_log_event_direct( $p_bug_id, $p_field_name, $t_current_value, $c_value ); 1531 } 1532 1533 bug_clear_cache( $p_bug_id ); 1534 1535 return true; 1536 } 1537 1538 /** 1539 * assign the bug to the given user 1540 * @param array p_bug_id_array integer array representing bug ids to cache 1541 * @return null 1542 * @access public 1543 * @uses database_api.php 1544 */ 1545 function bug_assign( $p_bug_id, $p_user_id, $p_bugnote_text = '', $p_bugnote_private = false ) { 1546 $c_bug_id = db_prepare_int( $p_bug_id ); 1547 $c_user_id = db_prepare_int( $p_user_id ); 1548 1549 if(( $c_user_id != NO_USER ) && !access_has_bug_level( config_get( 'handle_bug_threshold' ), $p_bug_id, $p_user_id ) ) { 1550 trigger_error( ERROR_USER_DOES_NOT_HAVE_REQ_ACCESS ); 1551 } 1552 1553 # extract current information into history variables 1554 $h_status = bug_get_field( $p_bug_id, 'status' ); 1555 $h_handler_id = bug_get_field( $p_bug_id, 'handler_id' ); 1556 1557 if(( ON == config_get( 'auto_set_status_to_assigned' ) ) && ( NO_USER != $p_user_id ) ) { 1558 $t_ass_val = config_get( 'bug_assigned_status' ); 1559 } else { 1560 $t_ass_val = $h_status; 1561 } 1562 1563 $t_bug_table = db_get_table( 'bug' ); 1564 1565 if(( $t_ass_val != $h_status ) || ( $p_user_id != $h_handler_id ) ) { 1566 1567 # get user id 1568 $query = "UPDATE $t_bug_table 1569 SET handler_id=" . db_param() . ", status=" . db_param() . " 1570 WHERE id=" . db_param(); 1571 db_query_bound( $query, Array( $c_user_id, $t_ass_val, $c_bug_id ) ); 1572 1573 # log changes 1574 history_log_event_direct( $c_bug_id, 'status', $h_status, $t_ass_val ); 1575 history_log_event_direct( $c_bug_id, 'handler_id', $h_handler_id, $p_user_id ); 1576 1577 # Add bugnote if supplied ignore false return 1578 bugnote_add( $p_bug_id, $p_bugnote_text, 0, $p_bugnote_private, 0, '', NULL, FALSE ); 1579 1580 # updated the last_updated date 1581 bug_update_date( $p_bug_id ); 1582 1583 bug_clear_cache( $p_bug_id ); 1584 1585 # send assigned to email 1586 email_assign( $p_bug_id ); 1587 } 1588 1589 return true; 1590 } 1591 1592 /** 1593 * close the given bug 1594 * @param int p_bug_id 1595 * @param string p_bugnote_text 1596 * @param bool p_bugnote_private 1597 * @param string p_time_tracking 1598 * @return bool (always true) 1599 * @access public 1600 */ 1601 function bug_close( $p_bug_id, $p_bugnote_text = '', $p_bugnote_private = false, $p_time_tracking = '0:00' ) { 1602 $p_bugnote_text = trim( $p_bugnote_text ); 1603 1604 # Add bugnote if supplied ignore a false return 1605 # Moved bugnote_add before bug_set_field calls in case time_tracking_no_note is off. 1606 # Error condition stopped execution but status had already been changed 1607 bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking, $p_bugnote_private, 0, '', NULL, FALSE ); 1608 1609 bug_set_field( $p_bug_id, 'status', config_get( 'bug_closed_status_threshold' ) ); 1610 1611 email_close( $p_bug_id ); 1612 email_relationship_child_closed( $p_bug_id ); 1613 1614 return true; 1615 } 1616 1617 /** 1618 * resolve the given bug 1619 * @return bool (alawys true) 1620 * @access public 1621 */ 1622 function bug_resolve( $p_bug_id, $p_resolution, $p_fixed_in_version = '', $p_bugnote_text = '', $p_duplicate_id = null, $p_handler_id = null, $p_bugnote_private = false, $p_time_tracking = '0:00' ) { 1623 $c_resolution = (int) $p_resolution; 1624 $p_bugnote_text = trim( $p_bugnote_text ); 1625 1626 # Add bugnote if supplied 1627 # Moved bugnote_add before bug_set_field calls in case time_tracking_no_note is off. 1628 # Error condition stopped execution but status had already been changed 1629 bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking, $p_bugnote_private, 0, '', NULL, FALSE ); 1630 1631 $t_duplicate = !is_blank( $p_duplicate_id ) && ( $p_duplicate_id != 0 ); 1632 if( $t_duplicate ) { 1633 if( $p_bug_id == $p_duplicate_id ) { 1634 trigger_error( ERROR_BUG_DUPLICATE_SELF, ERROR ); 1635 1636 # never returns 1637 } 1638 1639 # the related bug exists... 1640 bug_ensure_exists( $p_duplicate_id ); 1641 1642 # check if there is other relationship between the bugs... 1643 $t_id_relationship = relationship_same_type_exists( $p_bug_id, $p_duplicate_id, BUG_DUPLICATE ); 1644 1645 if( $t_id_relationship > 0 ) { 1646 # Update the relationship 1647 relationship_update( $t_id_relationship, $p_bug_id, $p_duplicate_id, BUG_DUPLICATE ); 1648 1649 # Add log line to the history (both bugs) 1650 history_log_event_special( $p_bug_id, BUG_REPLACE_RELATIONSHIP, BUG_DUPLICATE, $p_duplicate_id ); 1651 history_log_event_special( $p_duplicate_id, BUG_REPLACE_RELATIONSHIP, BUG_HAS_DUPLICATE, $p_bug_id ); 1652 } else if ( $t_id_relationship != -1 ) { 1653 # Add the new relationship 1654 relationship_add( $p_bug_id, $p_duplicate_id, BUG_DUPLICATE ); 1655 1656 # Add log line to the history (both bugs) 1657 history_log_event_special( $p_bug_id, BUG_ADD_RELATIONSHIP, BUG_DUPLICATE, $p_duplicate_id ); 1658 history_log_event_special( $p_duplicate_id, BUG_ADD_RELATIONSHIP, BUG_HAS_DUPLICATE, $p_bug_id ); 1659 } # else relationship is -1 - same type exists, do nothing 1660 1661 # Copy list of users monitoring the duplicate bug to the original bug 1662 $t_old_reporter_id = bug_get_field( $p_bug_id, 'reporter_id' ); 1663 $t_old_handler_id = bug_get_field( $p_bug_id, 'handler_id' ); 1664 if ( user_exists( $t_old_reporter_id ) ) { 1665 bug_monitor( $p_duplicate_id, $t_old_reporter_id ); 1666 } 1667 if ( user_exists ( $t_old_handler_id ) ) { 1668 bug_monitor( $p_duplicate_id, $t_old_handler_id ); 1669 } 1670 bug_monitor_copy( $p_bug_id, $p_duplicate_id ); 1671 1672 bug_set_field( $p_bug_id, 'duplicate_id', (int) $p_duplicate_id ); 1673 } 1674 1675 bug_set_field( $p_bug_id, 'status', config_get( 'bug_resolved_status_threshold' ) ); 1676 bug_set_field( $p_bug_id, 'fixed_in_version', $p_fixed_in_version ); 1677 bug_set_field( $p_bug_id, 'resolution', $c_resolution ); 1678 1679 # only set handler if specified explicitly or if bug was not assigned to a handler 1680 if( null == $p_handler_id ) { 1681 if( bug_get_field( $p_bug_id, 'handler_id' ) == 0 ) { 1682 $p_handler_id = auth_get_current_user_id(); 1683 bug_set_field( $p_bug_id, 'handler_id', $p_handler_id ); 1684 } 1685 } else { 1686 bug_set_field( $p_bug_id, 'handler_id', $p_handler_id ); 1687 } 1688 1689 email_resolved( $p_bug_id ); 1690 email_relationship_child_resolved( $p_bug_id ); 1691 1692 if( $c_resolution >= config_get( 'bug_resolution_fixed_threshold' ) && 1693 $c_resolution < config_get( 'bug_resolution_not_fixed_threshold' ) ) { 1694 twitter_issue_resolved( $p_bug_id ); 1695 } 1696 1697 return true; 1698 } 1699 1700 /** 1701 * reopen the given bug 1702 * @param int p_bug_id 1703 * @param string p_bugnote_text 1704 * @param string p_time_tracking 1705 * @param bool p_bugnote_private 1706 * @return bool (always true) 1707 * @access public 1708 * @uses database_api.php 1709 * @uses email_api.php 1710 * @uses bugnote_api.php 1711 * @uses config_api.php 1712 */ 1713 function bug_reopen( $p_bug_id, $p_bugnote_text = '', $p_time_tracking = '0:00', $p_bugnote_private = false ) { 1714 $p_bugnote_text = trim( $p_bugnote_text ); 1715 1716 # Add bugnote if supplied 1717 # Moved bugnote_add before bug_set_field calls in case time_tracking_no_note is off. 1718 # Error condition stopped execution but status had already been changed 1719 bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking, $p_bugnote_private, 0, '', NULL, FALSE ); 1720 1721 bug_set_field( $p_bug_id, 'status', config_get( 'bug_reopen_status' ) ); 1722 bug_set_field( $p_bug_id, 'resolution', config_get( 'bug_reopen_resolution' ) ); 1723 1724 email_reopen( $p_bug_id ); 1725 1726 return true; 1727 } 1728 1729 /** 1730 * updates the last_updated field 1731 * @param int p_bug_id integer representing bug ids 1732 * @return bool (always true) 1733 * @access public 1734 * @uses database_api.php 1735 */ 1736 function bug_update_date( $p_bug_id ) { 1737 $c_bug_id = (int) $p_bug_id; 1738 1739 $t_bug_table = db_get_table( 'bug' ); 1740 1741 $query = "UPDATE $t_bug_table 1742 SET last_updated= " . db_param() . " 1743 WHERE id=" . db_param(); 1744 db_query_bound( $query, Array( db_now(), $c_bug_id ) ); 1745 1746 bug_clear_cache( $c_bug_id ); 1747 1748 return true; 1749 } 1750 1751 /** 1752 * enable monitoring of this bug for the user 1753 * @param int p_bug_id integer representing bug ids 1754 * @param int p_user_id integer representing user ids 1755 * @return true if successful, false if unsuccessful 1756 * @access public 1757 * @uses database_api.php 1758 * @uses history_api.php 1759 * @uses user_api.php 1760 */ 1761 function bug_monitor( $p_bug_id, $p_user_id ) { 1762 $c_bug_id = (int) $p_bug_id; 1763 $c_user_id = (int) $p_user_id; 1764 1765 # Make sure we aren't already monitoring this bug 1766 if( user_is_monitoring_bug( $c_user_id, $c_bug_id ) ) { 1767 return true; 1768 } 1769 1770 # Don't let the anonymous user monitor bugs 1771 if ( user_is_anonymous( $c_user_id ) ) { 1772 return false; 1773 } 1774 1775 $t_bug_monitor_table = db_get_table( 'bug_monitor' ); 1776 1777 # Insert monitoring record 1778 $query = 'INSERT INTO ' . $t_bug_monitor_table . '( user_id, bug_id ) VALUES (' . db_param() . ',' . db_param() . ')'; 1779 db_query_bound( $query, Array( $c_user_id, $c_bug_id ) ); 1780 1781 # log new monitoring action 1782 history_log_event_special( $c_bug_id, BUG_MONITOR, $c_user_id ); 1783 1784 # updated the last_updated date 1785 bug_update_date( $p_bug_id ); 1786 1787 email_monitor_added( $p_bug_id, $p_user_id ); 1788 1789 return true; 1790 } 1791 1792 /** 1793 * Returns the list of users monitoring the specified bug 1794 * 1795 * @param int $p_bug_id 1796 */ 1797 function bug_get_monitors( $p_bug_id ) { 1798 1799 if ( ! access_has_bug_level( config_get( 'show_monitor_list_threshold' ), $p_bug_id ) ) { 1800 return Array(); 1801 } 1802 1803 $c_bug_id = db_prepare_int( $p_bug_id ); 1804 $t_bug_monitor_table = db_get_table( 'bug_monitor' ); 1805 $t_user_table = db_get_table( 'user' ); 1806 1807 # get the bugnote data 1808 $query = "SELECT user_id, enabled 1809 FROM $t_bug_monitor_table m, $t_user_table u 1810 WHERE m.bug_id=" . db_param() . " AND m.user_id = u.id 1811 ORDER BY u.realname, u.username"; 1812 $result = db_query_bound($query, Array( $c_bug_id ) ); 1813 $num_users = db_num_rows($result); 1814 1815 $t_users = array(); 1816 for ( $i = 0; $i < $num_users; $i++ ) { 1817 $row = db_fetch_array( $result ); 1818 $t_users[$i] = $row['user_id']; 1819 } 1820 1821 user_cache_array_rows( $t_users ); 1822 1823 return $t_users; 1824 } 1825 1826 /** 1827 * Copy list of users monitoring a bug to the monitor list of a second bug 1828 * @param int p_source_bug_id integer representing the bug ID of the source bug 1829 * @param int p_dest_bug_id integer representing the bug ID of the destination bug 1830 * @return bool (always true) 1831 * @access public 1832 * @uses database_api.php 1833 * @uses history_api.php 1834 * @uses user_api.php 1835 */ 1836 function bug_monitor_copy( $p_source_bug_id, $p_dest_bug_id ) { 1837 $c_source_bug_id = (int)$p_source_bug_id; 1838 $c_dest_bug_id = (int)$p_dest_bug_id; 1839 1840 $t_bug_monitor_table = db_get_table( 'bug_monitor' ); 1841 1842 $query = 'SELECT user_id 1843 FROM ' . $t_bug_monitor_table . ' 1844 WHERE bug_id = ' . db_param(); 1845 $result = db_query_bound( $query, Array( $c_source_bug_id ) ); 1846 $t_count = db_num_rows( $result ); 1847 1848 for( $i = 0; $i < $t_count; $i++ ) { 1849 $t_bug_monitor = db_fetch_array( $result ); 1850 if ( user_exists( $t_bug_monitor['user_id'] ) && 1851 !user_is_monitoring_bug( $t_bug_monitor['user_id'], $c_dest_bug_id ) ) { 1852 $query = 'INSERT INTO ' . $t_bug_monitor_table . ' ( user_id, bug_id ) 1853 VALUES ( ' . db_param() . ', ' . db_param() . ' )'; 1854 db_query_bound( $query, Array( $t_bug_monitor['user_id'], $c_dest_bug_id ) ); 1855 history_log_event_special( $c_dest_bug_id, BUG_MONITOR, $t_bug_monitor['user_id'] ); 1856 } 1857 } 1858 } 1859 1860 /** 1861 * disable monitoring of this bug for the user 1862 * if $p_user_id = null, then bug is unmonitored for all users. 1863 * @param int p_bug_id integer representing bug ids 1864 * @param int p_user_id integer representing user ids 1865 * @return bool (always true) 1866 * @access public 1867 * @uses database_api.php 1868 * @uses history_api.php 1869 */ 1870 function bug_unmonitor( $p_bug_id, $p_user_id ) { 1871 $c_bug_id = (int) $p_bug_id; 1872 $c_user_id = (int) $p_user_id; 1873 1874 $t_bug_monitor_table = db_get_table( 'bug_monitor' ); 1875 1876 # Delete monitoring record 1877 $query = 'DELETE FROM ' . $t_bug_monitor_table . ' WHERE bug_id = ' . db_param(); 1878 $db_query_params[] = $c_bug_id; 1879 1880 if( $p_user_id !== null ) { 1881 $query .= " AND user_id = " . db_param(); 1882 $db_query_params[] = $c_user_id; 1883 } 1884 1885 db_query_bound( $query, $db_query_params ); 1886 1887 # log new un-monitor action 1888 history_log_event_special( $c_bug_id, BUG_UNMONITOR, $c_user_id ); 1889 1890 # updated the last_updated date 1891 bug_update_date( $p_bug_id ); 1892 1893 return true; 1894 } 1895 1896 /** 1897 * Pads the bug id with the appropriate number of zeros. 1898 * @param int p_bug_id 1899 * @return string 1900 * @access public 1901 * @uses config_api.php 1902 */ 1903 function bug_format_id( $p_bug_id ) { 1904 $t_padding = config_get( 'display_bug_padding' ); 1905 $t_string = utf8_str_pad( $p_bug_id, $t_padding, '0', STR_PAD_LEFT ); 1906 1907 return event_signal( 'EVENT_DISPLAY_BUG_ID', $t_string, array( $p_bug_id ) ); 1908 }
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 |