[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/core/ -> bug_api.php (source)

   1  <?php
   2  # MantisBT - A PHP based bugtracking system
   3  
   4  # MantisBT is free software: you can redistribute it and/or modify
   5  # it under the terms of the GNU General Public License as published by
   6  # the Free Software Foundation, either version 2 of the License, or
   7  # (at your option) any later version.
   8  #
   9  # MantisBT is distributed in the hope that it will be useful,
  10  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  # GNU General Public License for more details.
  13  #
  14  # You should have received a copy of the GNU General Public License
  15  # along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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  }


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