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 * Bugnote API 19 * 20 * @package CoreAPI 21 * @subpackage BugnoteAPI 22 * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org 23 * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net 24 * @link http://www.mantisbt.org 25 * 26 * @uses access_api.php 27 * @uses antispam_api.php 28 * @uses authentication_api.php 29 * @uses bug_api.php 30 * @uses bug_revision_api.php 31 * @uses config_api.php 32 * @uses constant_inc.php 33 * @uses database_api.php 34 * @uses email_api.php 35 * @uses error_api.php 36 * @uses event_api.php 37 * @uses file_api.php 38 * @uses helper_api.php 39 * @uses history_api.php 40 * @uses lang_api.php 41 * @uses mention_api.php 42 * @uses user_api.php 43 * @uses utility_api.php 44 */ 45 46require_api( 'access_api.php' ); 47require_api( 'antispam_api.php' ); 48require_api( 'authentication_api.php' ); 49require_api( 'bug_api.php' ); 50require_api( 'bug_revision_api.php' ); 51require_api( 'config_api.php' ); 52require_api( 'constant_inc.php' ); 53require_api( 'database_api.php' ); 54require_api( 'email_api.php' ); 55require_api( 'error_api.php' ); 56require_api( 'event_api.php' ); 57require_api( 'file_api.php' ); 58require_api( 'helper_api.php' ); 59require_api( 'history_api.php' ); 60require_api( 'lang_api.php' ); 61require_api( 'mention_api.php' ); 62require_api( 'user_api.php' ); 63require_api( 'utility_api.php' ); 64 65use Mantis\Exceptions\ClientException; 66 67# Cache of bugnotes arrays related to a bug, indexed by bug_id. 68# Each item is an array of BugnoteData objects 69$g_cache_bugnotes_by_bug_id = array(); 70 71# Cache of BugnoteData objects, indexed by bugnote id 72$g_cache_bugnotes_by_id = array(); 73 74/** 75 * Bugnote Data Structure Definition 76 */ 77class BugnoteData { 78 /** 79 * Bugnote ID 80 */ 81 public $id; 82 83 /** 84 * Bug ID 85 */ 86 public $bug_id; 87 88 /** 89 * Reporter ID 90 */ 91 public $reporter_id; 92 93 /** 94 * Note text 95 */ 96 public $note; 97 98 /** 99 * View State 100 */ 101 public $view_state; 102 103 /** 104 * Date submitted 105 */ 106 public $date_submitted; 107 108 /** 109 * Last Modified 110 */ 111 public $last_modified; 112 113 /** 114 * Bugnote type 115 */ 116 public $note_type; 117 118 /** 119 * Used for storing list of recipients for a reminder truncated to 120 * field length. 121 */ 122 public $note_attr; 123 124 /** 125 * Time tracking information 126 */ 127 public $time_tracking; 128 129 /** 130 * Bugnote Text id 131 */ 132 public $bugnote_text_id; 133} 134 135/** 136 * Check if a bugnote with the given ID exists 137 * return true if the bugnote exists, false otherwise 138 * @param integer $p_bugnote_id A bugnote identifier. 139 * @return boolean 140 * @access public 141 */ 142function bugnote_exists( $p_bugnote_id ) { 143 $c_bugnote_id = (int)$p_bugnote_id; 144 145 global $g_cache_bugnotes_by_id; 146 if( isset( $g_cache_bugnotes_by_id[$c_bugnote_id] ) ) { 147 return true; 148 } 149 150 # Check for invalid id values 151 if( $c_bugnote_id <= 0 || $c_bugnote_id > DB_MAX_INT ) { 152 return false; 153 } 154 155 db_param_push(); 156 $t_query = 'SELECT b.*, t.note 157 FROM {bugnote} b 158 LEFT JOIN {bugnote_text} t ON b.bugnote_text_id = t.id 159 WHERE b.id = ' . db_param(); 160 $t_result = db_query( $t_query, array( $c_bugnote_id ) ); 161 $t_row = db_fetch_array( $t_result ); 162 163 if( $t_row === false ) { 164 return false; 165 } 166 167 $t_bugnote = bugnote_row_to_object( $t_row ); 168 bugnote_cache( $t_bugnote ); 169 return true; 170} 171 172/** 173 * Caches the provided bugnote object. 174 * 175 * @param BugnoteData $p_bugnote The bugnote object. 176 * @return void 177 */ 178function bugnote_cache( BugnoteData $p_bugnote ) { 179 global $g_cache_bugnotes_by_id; 180 181 $g_cache_bugnotes_by_id[(int)$p_bugnote->id] = $p_bugnote; 182} 183 184/** 185 * Check if a bugnote with the given ID exists 186 * return true if the bugnote exists, raise an error if not 187 * @param integer $p_bugnote_id A bugnote identifier. 188 * @access public 189 * @return void 190 */ 191function bugnote_ensure_exists( $p_bugnote_id ) { 192 if( !bugnote_exists( $p_bugnote_id ) ) { 193 throw new ClientException( 194 "Issue note #$p_bugnote_id not found", 195 ERROR_BUGNOTE_NOT_FOUND, 196 array( $p_bugnote_id ) ); 197 } 198} 199 200/** 201 * Check if the given user is the reporter of the bugnote 202 * return true if the user is the reporter, false otherwise 203 * @param integer $p_bugnote_id A bugnote identifier. 204 * @param integer $p_user_id A user identifier. 205 * @return boolean 206 * @access public 207 */ 208function bugnote_is_user_reporter( $p_bugnote_id, $p_user_id ) { 209 if( bugnote_get_field( $p_bugnote_id, 'reporter_id' ) == $p_user_id ) { 210 return true; 211 } else { 212 return false; 213 } 214} 215 216/** 217 * Add a bugnote to a bug 218 * return the ID of the new bugnote 219 * @param integer $p_bug_id A bug identifier. 220 * @param string $p_bugnote_text The bugnote text to add. 221 * @param string $p_time_tracking Time tracking value - hh:mm string. 222 * @param boolean $p_private Whether bugnote is private. 223 * @param integer $p_type The bugnote type. 224 * @param string $p_attr Bugnote Attribute. 225 * @param integer $p_user_id A user identifier. 226 * @param boolean $p_send_email Whether to generate email. 227 * @param integer $p_date_submitted Date submitted (defaults to now()). 228 * @param integer $p_last_modified Last modification date (defaults to now()). 229 * @param boolean $p_skip_bug_update Skip bug last modification update (useful when importing bugs/bugnotes). 230 * @param boolean $p_log_history Log changes to bugnote history (defaults to true). 231 * @param boolean $p_trigger_event Trigger extensibility event. 232 * @return boolean|integer false or indicating bugnote id added 233 * @access public 234 */ 235function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_private = false, $p_type = BUGNOTE, $p_attr = '', $p_user_id = null, $p_send_email = true, $p_date_submitted = 0, $p_last_modified = 0, $p_skip_bug_update = false, $p_log_history = true, $p_trigger_event = true ) { 236 $c_bug_id = (int)$p_bug_id; 237 $c_time_tracking = helper_duration_to_minutes( $p_time_tracking ); 238 $c_type = (int)$p_type; 239 $c_date_submitted = $p_date_submitted <= 0 ? db_now() : (int)$p_date_submitted; 240 $c_last_modified = $p_last_modified <= 0 ? db_now() : (int)$p_last_modified; 241 242 antispam_check(); 243 244 if( REMINDER !== $p_type ) { 245 # Check if this is a time-tracking note 246 $t_time_tracking_enabled = config_get( 'time_tracking_enabled' ); 247 if( ON == $t_time_tracking_enabled && $c_time_tracking > 0 ) { 248 $t_time_tracking_without_note = config_get( 'time_tracking_without_note' ); 249 if( is_blank( $p_bugnote_text ) && OFF == $t_time_tracking_without_note ) { 250 throw new ClientException( 251 'Time tracking not allowed with empty note', 252 ERROR_EMPTY_FIELD, 253 array( lang_get( 'bugnote' ) ) ); 254 } 255 256 $c_type = TIME_TRACKING; 257 } 258 } 259 260 # Event integration 261 $t_bugnote_text = event_signal( 'EVENT_BUGNOTE_DATA', $p_bugnote_text, $c_bug_id ); 262 263 # MySQL 4-bytes UTF-8 chars workaround #21101 264 $t_bugnote_text = db_mysql_fix_utf8( $t_bugnote_text ); 265 266 # insert bugnote text 267 db_param_push(); 268 $t_query = 'INSERT INTO {bugnote_text} ( note ) VALUES ( ' . db_param() . ' )'; 269 db_query( $t_query, array( $t_bugnote_text ) ); 270 271 # retrieve bugnote text id number 272 $t_bugnote_text_id = db_insert_id( db_get_table( 'bugnote_text' ) ); 273 274 # get user information 275 if( $p_user_id === null ) { 276 $p_user_id = auth_get_current_user_id(); 277 } 278 279 # Check for private bugnotes. 280 if( $p_private && access_has_bug_level( config_get( 'set_view_status_threshold' ), $p_bug_id, $p_user_id ) ) { 281 $t_view_state = VS_PRIVATE; 282 } else { 283 $t_view_state = VS_PUBLIC; 284 } 285 286 # insert bugnote info 287 db_param_push(); 288 $t_query = 'INSERT INTO {bugnote} 289 (bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified, note_type, note_attr, time_tracking) 290 VALUES (' 291 . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' 292 . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' 293 . db_param() . ' )'; 294 $t_params = array( 295 $c_bug_id, $p_user_id, $t_bugnote_text_id, $t_view_state, 296 $c_date_submitted, $c_last_modified, $c_type, $p_attr, 297 $c_time_tracking ); 298 db_query( $t_query, $t_params ); 299 300 # get bugnote id 301 $t_bugnote_id = db_insert_id( db_get_table( 'bugnote' ) ); 302 303 # update bug last updated 304 if( !$p_skip_bug_update ) { 305 bug_update_date( $p_bug_id ); 306 } 307 308 # log new bug 309 if( $p_log_history ) { 310 history_log_event_special( $p_bug_id, BUGNOTE_ADDED, bugnote_format_id( $t_bugnote_id ) ); 311 } 312 313 # Event integration 314 if( $p_trigger_event ) { 315 event_signal( 'EVENT_BUGNOTE_ADD', array( $p_bug_id, $t_bugnote_id, 'files' => array() ) ); 316 } 317 318 # only send email if the text is not blank, otherwise, it is just recording of time without a comment. 319 if( true == $p_send_email && !is_blank( $t_bugnote_text ) ) { 320 email_bugnote_add( $t_bugnote_id ); 321 } 322 323 return $t_bugnote_id; 324} 325 326/** 327 * Process mentions in bugnote, typically after its added. 328 * 329 * @param int $p_bug_id The bug id 330 * @param int $p_bugnote_id The bugnote id 331 * @param string $p_bugnote_text The bugnote text 332 * @return array User ids that received mentioned emails. 333 * @access public 334 */ 335function bugnote_process_mentions( $p_bug_id, $p_bugnote_id, $p_bugnote_text ) { 336 # Process the mentions that have access to the issue note 337 $t_mentioned_user_ids = mention_get_users( $p_bugnote_text ); 338 $t_filtered_mentioned_user_ids = access_has_bugnote_level_filter( 339 config_get( 'view_bug_threshold' ), 340 $p_bugnote_id, 341 $t_mentioned_user_ids ); 342 343 $t_removed_mentions_user_ids = array_diff( $t_mentioned_user_ids, $t_filtered_mentioned_user_ids ); 344 345 return mention_process_user_mentions( 346 $p_bug_id, 347 $t_filtered_mentioned_user_ids, 348 $p_bugnote_text, 349 $t_removed_mentions_user_ids ); 350} 351 352/** 353 * Delete a bugnote 354 * @param integer $p_bugnote_id A bug note identifier. 355 * @return boolean 356 * @access public 357 */ 358function bugnote_delete( $p_bugnote_id ) { 359 $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); 360 $t_bugnote_text_id = bugnote_get_field( $p_bugnote_id, 'bugnote_text_id' ); 361 362 # Remove the bugnote 363 db_param_push(); 364 $t_query = 'DELETE FROM {bugnote} WHERE id=' . db_param(); 365 db_query( $t_query, array( $p_bugnote_id ) ); 366 367 # Remove the bugnote text 368 db_param_push(); 369 $t_query = 'DELETE FROM {bugnote_text} WHERE id=' . db_param(); 370 db_query( $t_query, array( $t_bugnote_text_id ) ); 371 372 # log deletion of bug 373 history_log_event_special( $t_bug_id, BUGNOTE_DELETED, bugnote_format_id( $p_bugnote_id ) ); 374 375 # Delete attachments linked to bugnote in the db (i.e. bugnote_id is set) 376 file_delete_bugnote_attachments( $t_bug_id, $p_bugnote_id ); 377 378 # Event integration 379 event_signal( 'EVENT_BUGNOTE_DELETED', array( $t_bug_id, $p_bugnote_id ) ); 380 381 return true; 382} 383 384/** 385 * delete all bugnotes associated with the given bug 386 * @param integer $p_bug_id A bug identifier. 387 * @return void 388 * @access public 389 */ 390function bugnote_delete_all( $p_bug_id ) { 391 # Delete the bugnote text items 392 db_param_push(); 393 $t_query = 'SELECT bugnote_text_id FROM {bugnote} WHERE bug_id=' . db_param(); 394 $t_result = db_query( $t_query, array( (int)$p_bug_id ) ); 395 while( $t_row = db_fetch_array( $t_result ) ) { 396 $t_bugnote_text_id = $t_row['bugnote_text_id']; 397 398 # Delete the corresponding bugnote texts 399 db_param_push(); 400 $t_query = 'DELETE FROM {bugnote_text} WHERE id=' . db_param(); 401 db_query( $t_query, array( $t_bugnote_text_id ) ); 402 } 403 404 # Delete the corresponding bugnotes 405 db_param_push(); 406 $t_query = 'DELETE FROM {bugnote} WHERE bug_id=' . db_param(); 407 db_query( $t_query, array( (int)$p_bug_id ) ); 408} 409 410/** 411 * Get the text associated with the bugnote 412 * @param integer $p_bugnote_id A bugnote identifier. 413 * @return string bugnote text 414 * @access public 415 */ 416function bugnote_get_text( $p_bugnote_id ) { 417 $t_bugnote_text_id = bugnote_get_field( $p_bugnote_id, 'bugnote_text_id' ); 418 419 # grab the bugnote text 420 db_param_push(); 421 $t_query = 'SELECT note FROM {bugnote_text} WHERE id=' . db_param(); 422 $t_result = db_query( $t_query, array( $t_bugnote_text_id ) ); 423 424 return db_result( $t_result ); 425} 426 427/** 428 * Get a field for the given bugnote 429 * @param integer $p_bugnote_id A bugnote identifier. 430 * @param string $p_field_name Field name to retrieve. 431 * @return string field value 432 * @access public 433 */ 434function bugnote_get_field( $p_bugnote_id, $p_field_name ) { 435 $t_bugnote = bugnote_get( $p_bugnote_id ); 436 return $t_bugnote->$p_field_name; 437} 438 439/** 440 * Get latest bugnote id 441 * @param integer $p_bug_id A bug identifier. 442 * @return int latest bugnote id 443 * @access public 444 */ 445function bugnote_get_latest_id( $p_bug_id ) { 446 db_param_push(); 447 $t_query = 'SELECT id FROM {bugnote} WHERE bug_id=' . db_param() . ' ORDER by last_modified DESC'; 448 $t_result = db_query( $t_query, array( (int)$p_bug_id ), 1 ); 449 450 return (int)db_result( $t_result ); 451} 452 453/** 454 * Build the bugnotes array for the given bug_id filtered by specified $p_user_access_level. 455 * Bugnotes are sorted by date_submitted according to 'bugnote_order' configuration setting. 456 * Return BugnoteData class object with raw values from the tables except the field 457 * last_modified - it is UNIX_TIMESTAMP. 458 * @param integer $p_bug_id A bug identifier. 459 * @param integer $p_user_bugnote_order Sort order. 460 * @param integer $p_user_bugnote_limit Number of bugnotes to display to user. 461 * @param integer $p_user_id A user identifier. 462 * @return array array of bugnotes 463 * @access public 464 */ 465function bugnote_get_all_visible_bugnotes( $p_bug_id, $p_user_bugnote_order, $p_user_bugnote_limit, $p_user_id = null ) { 466 if( $p_user_id === null ) { 467 $t_user_id = auth_get_current_user_id(); 468 } else { 469 $t_user_id = $p_user_id; 470 } 471 472 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 473 $t_user_access_level = user_get_access_level( $t_user_id, $t_project_id ); 474 475 $t_all_bugnotes = bugnote_get_all_bugnotes( $p_bug_id ); 476 477 $t_private_bugnote_visible = access_compare_level( $t_user_access_level, config_get( 'private_bugnote_threshold' ) ); 478 $t_time_tracking_visible = access_compare_level( $t_user_access_level, config_get( 'time_tracking_view_threshold' ) ); 479 480 $t_bugnotes = array(); 481 $t_bugnote_count = count( $t_all_bugnotes ); 482 $t_bugnote_limit = $p_user_bugnote_limit > 0 ? $p_user_bugnote_limit : $t_bugnote_count; 483 $t_bugnotes_found = 0; 484 485 # build a list of the latest bugnotes that the user can see 486 for( $i = 0; ( $i < $t_bugnote_count ) && ( $t_bugnotes_found < $t_bugnote_limit ); $i++ ) { 487 $t_bugnote = array_pop( $t_all_bugnotes ); 488 489 if( $t_private_bugnote_visible || $t_bugnote->reporter_id == $t_user_id || ( VS_PUBLIC == $t_bugnote->view_state ) ) { 490 # If the access level specified is not enough to see time tracking information 491 # then reset it to 0. 492 if( !$t_time_tracking_visible ) { 493 $t_bugnote->time_tracking = 0; 494 } 495 496 $t_bugnotes[$t_bugnotes_found++] = $t_bugnote; 497 } 498 } 499 500 # reverse the list for users with ascending view preferences 501 if( 'ASC' == $p_user_bugnote_order ) { 502 $t_bugnotes = array_reverse( $t_bugnotes ); 503 } 504 505 return $t_bugnotes; 506} 507 508/** 509 * Build a string that captures all the notes visible to the logged in user along with their 510 * metadata. The string will contain information about each note including reporter, timestamp, 511 * time tracking, view state. This will result in multi-line string with "\n" as the line 512 * separator. 513 * 514 * @param integer $p_bug_id A bug identifier. 515 * @param integer $p_user_bugnote_order Sort order. 516 * @param integer $p_user_bugnote_limit Number of bugnotes to display to user. 517 * @param integer $p_user_id A user identifier. 518 * @return string The string containing all visible notes. 519 * @access public 520 */ 521function bugnote_get_all_visible_as_string( $p_bug_id, $p_user_bugnote_order, $p_user_bugnote_limit, $p_user_id = null ) { 522 $t_notes = bugnote_get_all_visible_bugnotes( $p_bug_id, $p_user_bugnote_order, $p_user_bugnote_limit, $p_user_id ); 523 $t_date_format = config_get( 'normal_date_format' ); 524 $t_show_time_tracking = access_has_bug_level( config_get( 'time_tracking_view_threshold' ), $p_bug_id ); 525 526 $t_output = ''; 527 528 foreach( $t_notes as $t_note ) { 529 $t_note_string = '@' . user_get_name( $t_note->reporter_id ); 530 if ( $t_note->view_state != VS_PUBLIC ) { 531 $t_note_string .= ' (' . lang_get( 'private' ) . ')'; 532 } 533 534 $t_note_string .= ' ' . date( $t_date_format, $t_note->date_submitted ); 535 536 if ( $t_show_time_tracking && $t_note->note_type == TIME_TRACKING ) { 537 $t_time_tracking_hhmm = db_minutes_to_hhmm( $t_note->time_tracking ); 538 $t_note_string .= ' ' . lang_get( 'time_tracking_time_spent' ) . ' ' . $t_time_tracking_hhmm; 539 } 540 541 $t_note_string .= "\n" . $t_note->note . "\n"; 542 543 if ( !empty( $t_output ) ) { 544 # Use a marker that doesn't confuse markdown parser. 545 # `---` or `===` would mark previous line as a header. 546 $t_output .= "=-=\n"; 547 } 548 549 $t_output .= $t_note_string; 550 } 551 552 return $t_output; 553} 554 555/** 556 * Converts a bugnote database row to a bugnote object. 557 * 558 * @param array $p_row The bugnote row (including bugnote_text note) 559 * @return BugnoteData The bugnote object. 560 * @access private 561 */ 562function bugnote_row_to_object( array $p_row ) { 563 $t_bugnote = new BugnoteData; 564 565 $t_bugnote->id = $p_row['id']; 566 $t_bugnote->bug_id = (int)$p_row['bug_id']; 567 $t_bugnote->bugnote_text_id = (int)$p_row['bugnote_text_id']; 568 $t_bugnote->note = $p_row['note']; 569 $t_bugnote->view_state = (int)$p_row['view_state']; 570 $t_bugnote->reporter_id = (int)$p_row['reporter_id']; 571 $t_bugnote->date_submitted = (int)$p_row['date_submitted']; 572 $t_bugnote->last_modified = (int)$p_row['last_modified']; 573 $t_bugnote->note_type = (int)$p_row['note_type']; 574 $t_bugnote->note_attr = $p_row['note_attr']; 575 $t_bugnote->time_tracking = (int)$p_row['time_tracking']; 576 577 # Handle old bugnotes before setting type to time tracking 578 if ( $t_bugnote->time_tracking != 0 ) { 579 $t_bugnote->note_type = TIME_TRACKING; 580 } 581 582 return $t_bugnote; 583} 584 585/** 586 * Build the bugnotes array for the given bug_id. 587 * Return BugnoteData class object with raw values from the tables except the field 588 * last_modified - it is UNIX_TIMESTAMP. 589 * The data is not filtered by VIEW_STATE !! 590 * @param integer $p_bug_id A bug identifier. 591 * @return array array of bugnotes 592 * @access public 593 */ 594function bugnote_get_all_bugnotes( $p_bug_id ) { 595 global $g_cache_bugnotes_by_bug_id; 596 597 # the cache should be aware of the sorting order 598 if( !isset( $g_cache_bugnotes_by_bug_id[(int)$p_bug_id] ) ) { 599 # Now sorting by submit date and id (#11742). The date_submitted 600 # column is currently not indexed, but that does not seem to affect 601 # performance in a measurable way 602 db_param_push(); 603 $t_query = 'SELECT b.*, t.note 604 FROM {bugnote} b 605 LEFT JOIN {bugnote_text} t ON b.bugnote_text_id = t.id 606 WHERE b.bug_id=' . db_param() . ' 607 ORDER BY b.date_submitted ASC, b.id ASC'; 608 $t_bugnotes = array(); 609 610 # BUILD bugnotes array 611 $t_result = db_query( $t_query, array( $p_bug_id ) ); 612 613 while( $t_row = db_fetch_array( $t_result ) ) { 614 $t_bugnote = bugnote_row_to_object( $t_row ); 615 $t_bugnotes[] = $t_bugnote; 616 bugnote_cache( $t_bugnote ); 617 } 618 619 $g_cache_bugnotes_by_bug_id[(int)$p_bug_id] = $t_bugnotes; 620 } 621 622 return $g_cache_bugnotes_by_bug_id[(int)$p_bug_id]; 623} 624 625/** 626 * Gets the bugnote object given its id. 627 * 628 * @param int $p_bugnote_id The bugnote id. 629 * @return BugnoteData The bugnote object. 630 */ 631function bugnote_get( $p_bugnote_id ) { 632 # If bugnote exists but not in cache, it will be added to cache. 633 # If bugnote doesn't exist, this will trigger an error. 634 bugnote_ensure_exists( $p_bugnote_id ); 635 636 global $g_cache_bugnotes_by_id; 637 638 # Return the object from the cache, fetched above. 639 if( isset( $g_cache_bugnotes_by_id[(int)$p_bugnote_id] ) ) { 640 return $g_cache_bugnotes_by_id[(int)$p_bugnote_id]; 641 } 642 643 # if we reached here something is wrong, trigger an error. 644 trigger_error( ERROR_BUGNOTE_NOT_FOUND, ERROR ); 645} 646 647/** 648 * Update the time_tracking field of the bugnote 649 * @param integer $p_bugnote_id A bugnote identifier. 650 * @param string $p_time_tracking Timetracking string (hh:mm format). 651 * @return void 652 * @access public 653 */ 654function bugnote_set_time_tracking( $p_bugnote_id, $p_time_tracking ) { 655 $c_bugnote_time_tracking = helper_duration_to_minutes( $p_time_tracking ); 656 657 db_param_push(); 658 $t_query = 'UPDATE {bugnote} SET time_tracking = ' . db_param() . ' WHERE id=' . db_param(); 659 db_query( $t_query, array( $c_bugnote_time_tracking, $p_bugnote_id ) ); 660} 661 662/** 663 * Update the last_modified field of the bugnote 664 * @param integer $p_bugnote_id A bugnote identifier. 665 * @return void 666 * @access public 667 */ 668function bugnote_date_update( $p_bugnote_id ) { 669 db_param_push(); 670 $t_query = 'UPDATE {bugnote} SET last_modified=' . db_param() . ' WHERE id=' . db_param(); 671 db_query( $t_query, array( db_now(), $p_bugnote_id ) ); 672} 673 674/** 675 * Set the bugnote text 676 * @param integer $p_bugnote_id A bugnote identifier. 677 * @param string $p_bugnote_text The bugnote text to set. 678 * @return boolean 679 * @access public 680 */ 681function bugnote_set_text( $p_bugnote_id, $p_bugnote_text ) { 682 $t_old_text = bugnote_get_text( $p_bugnote_id ); 683 684 if( $t_old_text == $p_bugnote_text ) { 685 return true; 686 } 687 # MySQL 4-bytes UTF-8 chars workaround #21101 688 $p_bugnote_text = db_mysql_fix_utf8( $p_bugnote_text ); 689 690 691 $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); 692 $t_bugnote_text_id = bugnote_get_field( $p_bugnote_id, 'bugnote_text_id' ); 693 694 # insert an 'original' revision if needed 695 if( bug_revision_count( $t_bug_id, REV_BUGNOTE, $p_bugnote_id ) < 1 ) { 696 $t_user_id = bugnote_get_field( $p_bugnote_id, 'reporter_id' ); 697 $t_timestamp = bugnote_get_field( $p_bugnote_id, 'last_modified' ); 698 bug_revision_add( $t_bug_id, $t_user_id, REV_BUGNOTE, $t_old_text, $p_bugnote_id, $t_timestamp ); 699 } 700 701 db_param_push(); 702 $t_query = 'UPDATE {bugnote_text} SET note=' . db_param() . ' WHERE id=' . db_param(); 703 db_query( $t_query, array( $p_bugnote_text, $t_bugnote_text_id ) ); 704 705 # updated the last_updated date 706 bugnote_date_update( $p_bugnote_id ); 707 bug_update_date( $t_bug_id ); 708 709 # insert a new revision 710 $t_user_id = auth_get_current_user_id(); 711 $t_revision_id = bug_revision_add( $t_bug_id, $t_user_id, REV_BUGNOTE, $p_bugnote_text, $p_bugnote_id ); 712 713 # log new bugnote 714 history_log_event_special( $t_bug_id, BUGNOTE_UPDATED, bugnote_format_id( $p_bugnote_id ), $t_revision_id ); 715 716 return true; 717} 718 719/** 720 * Set the view state of the bugnote 721 * @param integer $p_bugnote_id A bugnote identifier. 722 * @param boolean $p_private Whether bugnote should be set to private status. 723 * @return boolean 724 * @access public 725 */ 726function bugnote_set_view_state( $p_bugnote_id, $p_private ) { 727 $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); 728 729 if( $p_private ) { 730 $t_view_state = VS_PRIVATE; 731 } else { 732 $t_view_state = VS_PUBLIC; 733 } 734 735 db_param_push(); 736 $t_query = 'UPDATE {bugnote} SET view_state=' . db_param() . ' WHERE id=' . db_param(); 737 db_query( $t_query, array( $t_view_state, $p_bugnote_id ) ); 738 739 history_log_event_special( $t_bug_id, BUGNOTE_STATE_CHANGED, $t_view_state, bugnote_format_id( $p_bugnote_id ) ); 740 741 return true; 742} 743 744/** 745 * Pad the bugnote id with the appropriate number of zeros for printing 746 * @param integer $p_bugnote_id A bugnote identifier. 747 * @return string 748 * @access public 749 */ 750function bugnote_format_id( $p_bugnote_id ) { 751 $t_padding = config_get( 'display_bugnote_padding' ); 752 753 return utf8_str_pad( $p_bugnote_id, $t_padding, '0', STR_PAD_LEFT ); 754} 755 756/** 757 * Returns an array of bugnote stats 758 * @param integer $p_bug_id A bug identifier. 759 * @param string $p_from Starting date (yyyy-mm-dd) inclusive, if blank, then ignored. 760 * @param string $p_to Ending date (yyyy-mm-dd) inclusive, if blank, then ignored. 761 * @return array array of bugnote stats 762 * @access public 763 */ 764function bugnote_stats_get_events_array( $p_bug_id, $p_from, $p_to ) { 765 $c_to = strtotime( $p_to ) + SECONDS_PER_DAY - 1; 766 $c_from = strtotime( $p_from ); 767 768 if( !is_blank( $c_from ) ) { 769 $t_from_where = ' AND bn.date_submitted >= ' . $c_from; 770 } else { 771 $t_from_where = ''; 772 } 773 774 if( !is_blank( $c_to ) ) { 775 $t_to_where = ' AND bn.date_submitted <= ' . $c_to; 776 } else { 777 $t_to_where = ''; 778 } 779 780 $t_results = array(); 781 782 db_param_push(); 783 $t_query = 'SELECT u.id AS user_id, username, realname, SUM(time_tracking) AS sum_time_tracking 784 FROM {user} u, {bugnote} bn 785 WHERE u.id = bn.reporter_id AND bn.time_tracking != 0 AND 786 bn.bug_id = ' . db_param() . $t_from_where . $t_to_where . 787 ' GROUP BY u.id, u.username, u.realname'; 788 $t_result = db_query( $t_query, array( $p_bug_id ) ); 789 790 while( $t_row = db_fetch_array( $t_result ) ) { 791 $t_row['name'] = user_get_name_from_row( $t_row ); 792 $t_results[(int)$t_row['user_id']] = $t_row; 793 } 794 795 return $t_results; 796} 797 798/** 799 * Clear a bugnote from the cache or all bug notes if no bugnote id specified. 800 * @param integer $p_bugnote_id Identifier to clear (optional). 801 * @return boolean 802 * @access public 803 */ 804function bugnote_clear_cache( $p_bugnote_id = null ) { 805 global $g_cache_bugnotes_by_id, $g_cache_bugnotes_by_bug_id; 806 807 if( null === $p_bugnote_id ) { 808 $g_cache_bugnotes_by_id = array(); 809 $g_cache_bugnotes_by_bug_id = array(); 810 } else { 811 $p_bugnote_id = (int)$p_bugnote_id; 812 if( isset( $g_cache_bugnotes_by_id[$p_bugnote_id] ) ) { 813 $t_note_obj = $g_cache_bugnotes_by_id[$p_bugnote_id]; 814 unset($g_cache_bugnotes_by_id[$p_bugnote_id]); 815 816 # Clear the bug-level cache for the bugnote's parent bug 817 bugnote_clear_bug_cache( $t_note_obj->bug_id ); 818 } 819 } 820 821 return true; 822} 823 824/** 825 * Clear the bugnotes related to a bug, or all bugs if no bug id specified. 826 * @param integer $p_bug_id Identifier to clear (optional). 827 * @return boolean 828 * @access public 829 */ 830function bugnote_clear_bug_cache( $p_bug_id = null ) { 831 global $g_cache_bugnotes_by_bug_id, $g_cache_bugnotes_by_id; 832 833 if( null === $p_bug_id ) { 834 $g_cache_bugnotes_by_bug_id = array(); 835 $g_cache_bugnotes_by_id = array(); 836 } else { 837 if( isset( $g_cache_bugnotes_by_bug_id[(int)$p_bug_id] ) ) { 838 foreach( $g_cache_bugnotes_by_bug_id[(int)$p_bug_id] as $t_note_obj ) { 839 unset( $g_cache_bugnotes_by_id[(int)$t_note_obj->id] ); 840 } 841 unset( $g_cache_bugnotes_by_bug_id[(int)$p_bug_id] ); 842 } 843 } 844 845 return true; 846} 847