PageRenderTime 59ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/core/bug_api.php

https://github.com/rlerdorf/mantisbt
PHP | 1908 lines | 1040 code | 298 blank | 570 comment | 170 complexity | 2e2b46f435e344617799a421688844b4 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. # MantisBT - a php based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Bug API
  17. * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  18. * @copyright Copyright (C) 2002 - 2013 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  19. * @link http://www.mantisbt.org
  20. * @package CoreAPI
  21. * @subpackage BugAPI
  22. */
  23. /**
  24. * requires history_api
  25. */
  26. require_once( 'history_api.php' );
  27. /**
  28. * requires email_api
  29. */
  30. require_once( 'email_api.php' );
  31. /**
  32. * requires bugnote_api
  33. */
  34. require_once( 'bugnote_api.php' );
  35. /**
  36. * requires file_api
  37. */
  38. require_once( 'file_api.php' );
  39. /**
  40. * requires string_api
  41. */
  42. require_once( 'string_api.php' );
  43. /**
  44. * requires sponsorship_api
  45. */
  46. require_once( 'sponsorship_api.php' );
  47. /**
  48. * requires twitter_api
  49. */
  50. require_once( 'twitter_api.php' );
  51. /**
  52. * requires tag_api
  53. */
  54. require_once( 'tag_api.php' );
  55. /**
  56. * requires relationship_api
  57. */
  58. require_once( 'relationship_api.php' );
  59. /**
  60. * requires bug_revision_api
  61. */
  62. require_once( 'bug_revision_api.php' );
  63. /**
  64. * Bug Data Structure Definition
  65. * @package MantisBT
  66. * @subpackage classes
  67. */
  68. class BugData {
  69. protected $id;
  70. protected $project_id = null;
  71. protected $reporter_id = 0;
  72. protected $handler_id = 0;
  73. protected $duplicate_id = 0;
  74. protected $priority = NORMAL;
  75. protected $severity = MINOR;
  76. protected $reproducibility = 10;
  77. protected $status = NEW_;
  78. protected $resolution = OPEN;
  79. protected $projection = 10;
  80. protected $category_id = 1;
  81. protected $date_submitted = '';
  82. protected $last_updated = '';
  83. protected $eta = 10;
  84. protected $os = '';
  85. protected $os_build = '';
  86. protected $platform = '';
  87. protected $version = '';
  88. protected $fixed_in_version = '';
  89. protected $target_version = '';
  90. protected $build = '';
  91. protected $view_state = VS_PUBLIC;
  92. protected $summary = '';
  93. protected $sponsorship_total = 0;
  94. protected $sticky = 0;
  95. protected $due_date = 0;
  96. # omitted:
  97. # var $bug_text_id
  98. protected $profile_id = 0;
  99. # extended info
  100. protected $description = '';
  101. protected $steps_to_reproduce = '';
  102. protected $additional_information = '';
  103. # internal helper objects
  104. private $_stats = null;
  105. public $attachment_count = null;
  106. public $bugnotes_count = null;
  107. private $loading = false;
  108. /**
  109. * return number of file attachment's linked to current bug
  110. * @return int
  111. */
  112. public function get_attachment_count() {
  113. if ( $this->attachment_count === null ) {
  114. $this->attachment_count = file_bug_attachment_count( $this->id );
  115. return $this->attachment_count;
  116. } else {
  117. return $this->attachment_count;
  118. }
  119. }
  120. /**
  121. * return number of bugnotes's linked to current bug
  122. * @return int
  123. */
  124. public function get_bugnotes_count() {
  125. if ( $this->bugnotes_count === null ) {
  126. $this->bugnotes_count = self::bug_get_bugnote_count();
  127. return $this->bugnotes_count;
  128. } else {
  129. return $this->bugnotes_count;
  130. }
  131. }
  132. /**
  133. * @private
  134. */
  135. public function __set($name, $value) {
  136. switch ($name) {
  137. // integer types
  138. case 'id':
  139. case 'project_id':
  140. case 'reporter_id':
  141. case 'handler_id':
  142. case 'duplicate_id':
  143. case 'priority':
  144. case 'severity':
  145. case 'reproducibility':
  146. case 'status':
  147. case 'resolution':
  148. case 'projection':
  149. case 'category_id':
  150. $value = (int)$value;
  151. break;
  152. case 'target_version':
  153. if ( !$this->loading ) {
  154. # Only set target_version if user has access to do so
  155. if( !access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) {
  156. trigger_error( ERROR_ACCESS_DENIED, ERROR );
  157. }
  158. }
  159. break;
  160. case 'due_date':
  161. if ( !is_numeric( $value ) ) {
  162. $value = strtotime($value);
  163. }
  164. break;
  165. }
  166. $this->$name = $value;
  167. }
  168. /**
  169. * @private
  170. */
  171. public function __get($name) {
  172. if( $this->is_extended_field($name) )
  173. $this->fetch_extended_info();
  174. return $this->{$name};
  175. }
  176. /**
  177. * @private
  178. */
  179. public function __isset($name) {
  180. return isset( $this->{$name} );
  181. }
  182. /**
  183. * fast-load database row into bugobject
  184. * @param array $p_row
  185. */
  186. public function loadrow( $p_row ) {
  187. $this->loading = true;
  188. foreach( $p_row as $var => $val ) {
  189. $this->__set( $var, $p_row[$var] );
  190. }
  191. $this->loading = false;
  192. }
  193. /**
  194. * Retrieves extended information for bug (e.g. bug description)
  195. * @return null
  196. */
  197. private function fetch_extended_info() {
  198. if ( $this->description == '' ) {
  199. $t_text = bug_text_cache_row($this->id);
  200. $this->description = $t_text['description'];
  201. $this->steps_to_reproduce = $t_text['steps_to_reproduce'];
  202. $this->additional_information = $t_text['additional_information'];
  203. }
  204. }
  205. /**
  206. * Returns if the field is an extended field which needs fetch_extended_info()
  207. * @return boolean
  208. */
  209. private function is_extended_field( $p_field_name ) {
  210. switch( $p_field_name ) {
  211. case 'description':
  212. case 'steps_to_reproduce':
  213. case 'additional_information':
  214. return true;
  215. default:
  216. return false;
  217. }
  218. }
  219. /**
  220. * Returns the number of bugnotes for the given bug_id
  221. * @return int number of bugnotes
  222. * @access private
  223. * @uses database_api.php
  224. */
  225. private function bug_get_bugnote_count() {
  226. if( !access_has_project_level( config_get( 'private_bugnote_threshold' ), $this->project_id ) ) {
  227. $t_restriction = 'AND view_state=' . VS_PUBLIC;
  228. } else {
  229. $t_restriction = '';
  230. }
  231. $t_bugnote_table = db_get_table( 'mantis_bugnote_table' );
  232. $query = "SELECT COUNT(*)
  233. FROM $t_bugnote_table
  234. WHERE bug_id =" . db_param() . " $t_restriction";
  235. $result = db_query_bound( $query, Array( $this->bug_id ) );
  236. return db_result( $result );
  237. }
  238. /**
  239. * validate current bug object for database insert/update
  240. * triggers error on failure
  241. * @param bool $p_update_extended
  242. */
  243. function validate( $p_update_extended = true) {
  244. # Summary cannot be blank
  245. if( is_blank( $this->summary ) ) {
  246. error_parameters( lang_get( 'summary' ) );
  247. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  248. }
  249. if( $p_update_extended ) {
  250. # Description field cannot be empty
  251. if( is_blank( $this->description ) ) {
  252. error_parameters( lang_get( 'description' ) );
  253. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  254. }
  255. }
  256. # Make sure a category is set
  257. if( 0 == $this->category_id && !config_get( 'allow_no_category' ) ) {
  258. error_parameters( lang_get( 'category' ) );
  259. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  260. }
  261. if( !is_blank( $this->duplicate_id ) && ( $this->duplicate_id != 0 ) && ( $this->id == $this->duplicate_id ) ) {
  262. trigger_error( ERROR_BUG_DUPLICATE_SELF, ERROR );
  263. # never returns
  264. }
  265. }
  266. /**
  267. * Insert a new bug into the database
  268. * @return int integer representing the bug id that was created
  269. * @access public
  270. * @uses database_api.php
  271. * @uses lang_api.php
  272. */
  273. function create() {
  274. self::validate( true );
  275. # check due_date format
  276. if( is_blank( $this->due_date ) ) {
  277. $this_due_date = date_get_null();
  278. }
  279. $t_bug_table = db_get_table( 'mantis_bug_table' );
  280. $t_bug_text_table = db_get_table( 'mantis_bug_text_table' );
  281. $t_category_table = db_get_table( 'mantis_category_table' );
  282. # Insert text information
  283. $query = "INSERT INTO $t_bug_text_table
  284. ( description, steps_to_reproduce, additional_information )
  285. VALUES
  286. ( " . db_param() . ',' . db_param() . ',' . db_param() . ')';
  287. db_query_bound( $query, Array( $this->description, $this->steps_to_reproduce, $this->additional_information ) );
  288. # Get the id of the text information we just inserted
  289. # NOTE: this is guarranteed to be the correct one.
  290. # The value LAST_INSERT_ID is stored on a per connection basis.
  291. $t_text_id = db_insert_id( $t_bug_text_table );
  292. # check to see if we want to assign this right off
  293. $t_starting_status = config_get( 'bug_submit_status' );
  294. $t_original_status = $this->status;
  295. # if not assigned, check if it should auto-assigned.
  296. if( 0 == $this->handler_id ) {
  297. # if a default user is associated with the category and we know at this point
  298. # that that the bug was not assigned to somebody, then assign it automatically.
  299. $query = "SELECT user_id
  300. FROM $t_category_table
  301. WHERE id=" . db_param();
  302. $result = db_query_bound( $query, array( $this->category_id ) );
  303. if( db_num_rows( $result ) > 0 ) {
  304. $this->handler_id = db_result( $result );
  305. }
  306. }
  307. # Check if bug was pre-assigned or auto-assigned.
  308. if( ( $this->handler_id != 0 ) && ( $this->status == $t_starting_status ) && ( ON == config_get( 'auto_set_status_to_assigned' ) ) ) {
  309. $t_status = config_get( 'bug_assigned_status' );
  310. } else {
  311. $t_status = $this->status;
  312. }
  313. # Insert the rest of the data
  314. $query = "INSERT INTO $t_bug_table
  315. ( project_id,reporter_id, handler_id,duplicate_id,
  316. priority,severity, reproducibility,status,
  317. resolution,projection, category_id,date_submitted,
  318. last_updated,eta, bug_text_id,
  319. os, os_build,platform, version,build,
  320. profile_id, summary, view_state, sponsorship_total, sticky, fixed_in_version,
  321. target_version, due_date
  322. )
  323. VALUES
  324. ( " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  325. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  326. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  327. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  328. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  329. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
  330. " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')';
  331. 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, db_now(), db_now(), $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 ) );
  332. $this->id = db_insert_id( $t_bug_table );
  333. # log new bug
  334. history_log_event_special( $this->id, NEW_BUG );
  335. # log changes, if any (compare happens in history_log_event_direct)
  336. history_log_event_direct( $this->id, 'status', $t_original_status, $t_status );
  337. history_log_event_direct( $this->id, 'handler_id', 0, $this->handler_id );
  338. return $this->id;
  339. }
  340. /**
  341. * Update a bug from the given data structure
  342. * If the third parameter is true, also update the longer strings table
  343. * @param bool p_update_extended
  344. * @param bool p_bypass_email Default false, set to true to avoid generating emails (if sending elsewhere)
  345. * @return bool (always true)
  346. * @access public
  347. */
  348. function update( $p_update_extended = false, $p_bypass_mail = false ) {
  349. self::validate( $p_update_extended );
  350. $c_bug_id = $this->id;
  351. if( is_blank( $this->due_date ) ) {
  352. $this->due_date = date_get_null();
  353. }
  354. $t_old_data = bug_get( $this->id, true );
  355. $t_bug_table = db_get_table( 'mantis_bug_table' );
  356. # Update all fields
  357. # Ignore date_submitted and last_updated since they are pulled out
  358. # as unix timestamps which could confuse the history log and they
  359. # shouldn't get updated like this anyway. If you really need to change
  360. # them use bug_set_field()
  361. $query = "UPDATE $t_bug_table
  362. SET project_id=" . db_param() . ', reporter_id=' . db_param() . ",
  363. handler_id=" . db_param() . ', duplicate_id=' . db_param() . ",
  364. priority=" . db_param() . ', severity=' . db_param() . ",
  365. reproducibility=" . db_param() . ', status=' . db_param() . ",
  366. resolution=" . db_param() . ', projection=' . db_param() . ",
  367. category_id=" . db_param() . ', eta=' . db_param() . ",
  368. os=" . db_param() . ', os_build=' . db_param() . ",
  369. platform=" . db_param() . ', version=' . db_param() . ",
  370. build=" . db_param() . ', fixed_in_version=' . db_param() . ',';
  371. $t_fields = Array(
  372. $this->project_id, $this->reporter_id,
  373. $this->handler_id, $this->duplicate_id,
  374. $this->priority, $this->severity,
  375. $this->reproducibility, $this->status,
  376. $this->resolution, $this->projection,
  377. $this->category_id, $this->eta,
  378. $this->os, $this->os_build,
  379. $this->platform, $this->version,
  380. $this->build, $this->fixed_in_version,
  381. );
  382. $t_roadmap_updated = false;
  383. if( access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) {
  384. $query .= "
  385. target_version=" . db_param() . ",";
  386. $t_fields[] = $this->target_version;
  387. $t_roadmap_updated = true;
  388. }
  389. $query .= "
  390. view_state=" . db_param() . ",
  391. summary=" . db_param() . ",
  392. sponsorship_total=" . db_param() . ",
  393. sticky=" . db_param() . ",
  394. due_date=" . db_param() . "
  395. WHERE id=" . db_param();
  396. $t_fields[] = $this->view_state;
  397. $t_fields[] = $this->summary;
  398. $t_fields[] = $this->sponsorship_total;
  399. $t_fields[] = (bool)$this->sticky;
  400. $t_fields[] = $this->due_date;
  401. $t_fields[] = $this->id;
  402. db_query_bound( $query, $t_fields );
  403. bug_clear_cache( $this->id );
  404. # log changes
  405. history_log_event_direct( $c_bug_id, 'project_id', $t_old_data->project_id, $this->project_id );
  406. history_log_event_direct( $c_bug_id, 'reporter_id', $t_old_data->reporter_id, $this->reporter_id );
  407. history_log_event_direct( $c_bug_id, 'handler_id', $t_old_data->handler_id, $this->handler_id );
  408. history_log_event_direct( $c_bug_id, 'priority', $t_old_data->priority, $this->priority );
  409. history_log_event_direct( $c_bug_id, 'severity', $t_old_data->severity, $this->severity );
  410. history_log_event_direct( $c_bug_id, 'reproducibility', $t_old_data->reproducibility, $this->reproducibility );
  411. history_log_event_direct( $c_bug_id, 'status', $t_old_data->status, $this->status );
  412. history_log_event_direct( $c_bug_id, 'resolution', $t_old_data->resolution, $this->resolution );
  413. history_log_event_direct( $c_bug_id, 'projection', $t_old_data->projection, $this->projection );
  414. history_log_event_direct( $c_bug_id, 'category', category_full_name( $t_old_data->category_id, false ), category_full_name( $this->category_id, false ) );
  415. history_log_event_direct( $c_bug_id, 'eta', $t_old_data->eta, $this->eta );
  416. history_log_event_direct( $c_bug_id, 'os', $t_old_data->os, $this->os );
  417. history_log_event_direct( $c_bug_id, 'os_build', $t_old_data->os_build, $this->os_build );
  418. history_log_event_direct( $c_bug_id, 'platform', $t_old_data->platform, $this->platform );
  419. history_log_event_direct( $c_bug_id, 'version', $t_old_data->version, $this->version );
  420. history_log_event_direct( $c_bug_id, 'build', $t_old_data->build, $this->build );
  421. history_log_event_direct( $c_bug_id, 'fixed_in_version', $t_old_data->fixed_in_version, $this->fixed_in_version );
  422. if( $t_roadmap_updated ) {
  423. history_log_event_direct( $c_bug_id, 'target_version', $t_old_data->target_version, $this->target_version );
  424. }
  425. history_log_event_direct( $c_bug_id, 'view_state', $t_old_data->view_state, $this->view_state );
  426. history_log_event_direct( $c_bug_id, 'summary', $t_old_data->summary, $this->summary );
  427. history_log_event_direct( $c_bug_id, 'sponsorship_total', $t_old_data->sponsorship_total, $this->sponsorship_total );
  428. history_log_event_direct( $c_bug_id, 'sticky', $t_old_data->sticky, $this->sticky );
  429. 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 );
  430. # Update extended info if requested
  431. if( $p_update_extended ) {
  432. $t_bug_text_table = db_get_table( 'mantis_bug_text_table' );
  433. $t_bug_text_id = bug_get_field( $c_bug_id, 'bug_text_id' );
  434. $query = "UPDATE $t_bug_text_table
  435. SET description=" . db_param() . ",
  436. steps_to_reproduce=" . db_param() . ",
  437. additional_information=" . db_param() . "
  438. WHERE id=" . db_param();
  439. db_query_bound( $query, Array( $this->description, $this->steps_to_reproduce, $this->additional_information, $t_bug_text_id ) );
  440. bug_text_clear_cache( $c_bug_id );
  441. $t_current_user = auth_get_current_user_id();
  442. if( $t_old_data->description != $this->description ) {
  443. if ( bug_revision_count( $c_bug_id, REV_DESCRIPTION ) < 1 ) {
  444. $t_revision_id = bug_revision_add( $c_bug_id, $t_old_data->reporter_id, REV_DESCRIPTION, $t_old_data->description, 0, $t_old_data->date_submitted );
  445. }
  446. $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_DESCRIPTION, $this->description );
  447. history_log_event_special( $c_bug_id, DESCRIPTION_UPDATED, $t_revision_id );
  448. }
  449. if( $t_old_data->steps_to_reproduce != $this->steps_to_reproduce ) {
  450. if ( bug_revision_count( $c_bug_id, REV_STEPS_TO_REPRODUCE ) < 1 ) {
  451. $t_revision_id = bug_revision_add( $c_bug_id, $t_old_data->reporter_id, REV_STEPS_TO_REPRODUCE, $t_old_data->steps_to_reproduce, 0, $t_old_data->date_submitted );
  452. }
  453. $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_STEPS_TO_REPRODUCE, $this->steps_to_reproduce );
  454. history_log_event_special( $c_bug_id, STEP_TO_REPRODUCE_UPDATED, $t_revision_id );
  455. }
  456. if( $t_old_data->additional_information != $this->additional_information ) {
  457. if ( bug_revision_count( $c_bug_id, REV_ADDITIONAL_INFO ) < 1 ) {
  458. $t_revision_id = bug_revision_add( $c_bug_id, $t_old_data->reporter_id, REV_ADDITIONAL_INFO, $t_old_data->additional_information, 0, $t_old_data->date_submitted );
  459. }
  460. $t_revision_id = bug_revision_add( $c_bug_id, $t_current_user, REV_ADDITIONAL_INFO, $this->additional_information );
  461. history_log_event_special( $c_bug_id, ADDITIONAL_INFO_UPDATED, $t_revision_id );
  462. }
  463. }
  464. # Update the last update date
  465. bug_update_date( $c_bug_id );
  466. # allow bypass if user is sending mail separately
  467. if( false == $p_bypass_mail ) {
  468. # bug assigned
  469. if( $t_old_data->handler_id != $this->handler_id ) {
  470. email_generic( $c_bug_id, 'owner', 'email_notification_title_for_action_bug_assigned' );
  471. return true;
  472. }
  473. # status changed
  474. if( $t_old_data->status != $this->status ) {
  475. $t_status = MantisEnum::getLabel( config_get( 'status_enum_string' ), $this->status );
  476. $t_status = str_replace( ' ', '_', $t_status );
  477. email_generic( $c_bug_id, $t_status, 'email_notification_title_for_status_bug_' . $t_status );
  478. return true;
  479. }
  480. # @todo handle priority change if it requires special handling
  481. # generic update notification
  482. email_generic( $c_bug_id, 'updated', 'email_notification_title_for_action_bug_updated' );
  483. }
  484. return true;
  485. }
  486. }
  487. $g_cache_bug = array();
  488. $g_cache_bug_text = array();
  489. /**
  490. * Cache a database result-set containing full contents of bug_table row.
  491. * @param array p_bug_database_result database row containing all columns from mantis_bug_table
  492. * @param array p_stats (optional) array representing bugnote stats
  493. * @return array returns an array representing the bug row if bug exists
  494. * @access public
  495. */
  496. function bug_cache_database_result( $p_bug_database_result, $p_stats = null ) {
  497. global $g_cache_bug;
  498. if( !is_array( $p_bug_database_result ) || isset( $g_cache_bug[(int) $p_bug_database_result['id']] ) ) {
  499. return $g_cache_bug[(int) $p_bug_database_result['id']];
  500. }
  501. return bug_add_to_cache( $p_bug_database_result, $p_stats );
  502. }
  503. /**
  504. * Cache a bug row if necessary and return the cached copy
  505. * @param array p_bug_id id of bug to cache from mantis_bug_table
  506. * @param array p_trigger_errors set to true to trigger an error if the bug does not exist.
  507. * @return bool|array returns an array representing the bug row if bug exists or false if bug does not exist
  508. * @access public
  509. * @uses database_api.php
  510. */
  511. function bug_cache_row( $p_bug_id, $p_trigger_errors = true ) {
  512. global $g_cache_bug;
  513. if( isset( $g_cache_bug[$p_bug_id] ) ) {
  514. return $g_cache_bug[$p_bug_id];
  515. }
  516. $c_bug_id = (int) $p_bug_id;
  517. $t_bug_table = db_get_table( 'mantis_bug_table' );
  518. $query = "SELECT *
  519. FROM $t_bug_table
  520. WHERE id=" . db_param();
  521. $result = db_query_bound( $query, Array( $c_bug_id ) );
  522. if( 0 == db_num_rows( $result ) ) {
  523. $g_cache_bug[$c_bug_id] = false;
  524. if( $p_trigger_errors ) {
  525. error_parameters( $p_bug_id );
  526. trigger_error( ERROR_BUG_NOT_FOUND, ERROR );
  527. } else {
  528. return false;
  529. }
  530. }
  531. $row = db_fetch_array( $result );
  532. return bug_add_to_cache( $row );
  533. }
  534. /**
  535. * Cache a set of bugs
  536. * @param array p_bug_id_array integer array representing bug ids to cache
  537. * @return null
  538. * @access public
  539. * @uses database_api.php
  540. */
  541. function bug_cache_array_rows( $p_bug_id_array ) {
  542. global $g_cache_bug;
  543. $c_bug_id_array = array();
  544. foreach( $p_bug_id_array as $t_bug_id ) {
  545. if( !isset( $g_cache_bug[(int) $t_bug_id] ) ) {
  546. $c_bug_id_array[] = (int) $t_bug_id;
  547. }
  548. }
  549. if( empty( $c_bug_id_array ) ) {
  550. return;
  551. }
  552. $t_bug_table = db_get_table( 'mantis_bug_table' );
  553. $query = "SELECT *
  554. FROM $t_bug_table
  555. WHERE id IN (" . implode( ',', $c_bug_id_array ) . ')';
  556. $result = db_query_bound( $query );
  557. while( $row = db_fetch_array( $result ) ) {
  558. bug_add_to_cache( $row );
  559. }
  560. return;
  561. }
  562. /**
  563. * Inject a bug into the bug cache
  564. * @param array p_bug_row bug row to cache
  565. * @param array p_stats bugnote stats to cache
  566. * @return null
  567. * @access private
  568. */
  569. function bug_add_to_cache( $p_bug_row, $p_stats = null ) {
  570. global $g_cache_bug;
  571. $g_cache_bug[(int) $p_bug_row['id']] = $p_bug_row;
  572. if( !is_null( $p_stats ) ) {
  573. $g_cache_bug[(int) $p_bug_row['id']]['_stats'] = $p_stats;
  574. }
  575. return $g_cache_bug[(int) $p_bug_row['id']];
  576. }
  577. /**
  578. * Clear a bug from the cache or all bugs if no bug id specified.
  579. * @param int bug id to clear (optional)
  580. * @return null
  581. * @access public
  582. */
  583. function bug_clear_cache( $p_bug_id = null ) {
  584. global $g_cache_bug;
  585. if( null === $p_bug_id ) {
  586. $g_cache_bug = array();
  587. } else {
  588. unset( $g_cache_bug[(int) $p_bug_id] );
  589. }
  590. return true;
  591. }
  592. /**
  593. * Cache a bug text row if necessary and return the cached copy
  594. * @param int p_bug_id integer bug id to retrieve text for
  595. * @param bool p_trigger_errors If the second parameter is true (default), trigger an error if bug text not found.
  596. * @return bool|array returns false if not bug text found or array of bug text
  597. * @access public
  598. * @uses database_api.php
  599. */
  600. function bug_text_cache_row( $p_bug_id, $p_trigger_errors = true ) {
  601. global $g_cache_bug_text;
  602. $c_bug_id = (int) $p_bug_id;
  603. $t_bug_table = db_get_table( 'mantis_bug_table' );
  604. $t_bug_text_table = db_get_table( 'mantis_bug_text_table' );
  605. if( isset( $g_cache_bug_text[$c_bug_id] ) ) {
  606. return $g_cache_bug_text[$c_bug_id];
  607. }
  608. $query = "SELECT bt.*
  609. FROM $t_bug_text_table bt, $t_bug_table b
  610. WHERE b.id=" . db_param() . " AND
  611. b.bug_text_id = bt.id";
  612. $result = db_query_bound( $query, Array( $c_bug_id ) );
  613. if( 0 == db_num_rows( $result ) ) {
  614. $g_cache_bug_text[$c_bug_id] = false;
  615. if( $p_trigger_errors ) {
  616. error_parameters( $p_bug_id );
  617. trigger_error( ERROR_BUG_NOT_FOUND, ERROR );
  618. } else {
  619. return false;
  620. }
  621. }
  622. $row = db_fetch_array( $result );
  623. $g_cache_bug_text[$c_bug_id] = $row;
  624. return $row;
  625. }
  626. /**
  627. * Clear a bug's bug text from the cache or all bug text if no bug id specified.
  628. * @param int bug id to clear (optional)
  629. * @return null
  630. * @access public
  631. */
  632. function bug_text_clear_cache( $p_bug_id = null ) {
  633. global $g_cache_bug_text;
  634. if( null === $p_bug_id ) {
  635. $g_cache_bug_text = array();
  636. } else {
  637. unset( $g_cache_bug_text[(int) $p_bug_id] );
  638. }
  639. return true;
  640. }
  641. /**
  642. * Check if a bug exists
  643. * @param int p_bug_id integer representing bug id
  644. * @return bool true if bug exists, false otherwise
  645. * @access public
  646. */
  647. function bug_exists( $p_bug_id ) {
  648. if( false == bug_cache_row( $p_bug_id, false ) ) {
  649. return false;
  650. } else {
  651. return true;
  652. }
  653. }
  654. /**
  655. * Check if a bug exists. If it doesn't then trigger an error
  656. * @param int p_bug_id integer representing bug id
  657. * @return null
  658. * @access public
  659. */
  660. function bug_ensure_exists( $p_bug_id ) {
  661. if( !bug_exists( $p_bug_id ) ) {
  662. error_parameters( $p_bug_id );
  663. trigger_error( ERROR_BUG_NOT_FOUND, ERROR );
  664. }
  665. }
  666. /**
  667. * check if the given user is the reporter of the bug
  668. * @param int p_bug_id integer representing bug id
  669. * @param int p_user_id integer reprenting a user id
  670. * @return bool return true if the user is the reporter, false otherwise
  671. * @access public
  672. */
  673. function bug_is_user_reporter( $p_bug_id, $p_user_id ) {
  674. if( bug_get_field( $p_bug_id, 'reporter_id' ) == $p_user_id ) {
  675. return true;
  676. } else {
  677. return false;
  678. }
  679. }
  680. /**
  681. * check if the given user is the handler of the bug
  682. * @param int p_bug_id integer representing bug id
  683. * @param int p_user_id integer reprenting a user id
  684. * @return bool return true if the user is the handler, false otherwise
  685. * @access public
  686. */
  687. function bug_is_user_handler( $p_bug_id, $p_user_id ) {
  688. if( bug_get_field( $p_bug_id, 'handler_id' ) == $p_user_id ) {
  689. return true;
  690. } else {
  691. return false;
  692. }
  693. }
  694. /**
  695. * Check if the bug is readonly and shouldn't be modified
  696. * For a bug to be readonly the status has to be >= bug_readonly_status_threshold and
  697. * current user access level < update_readonly_bug_threshold.
  698. * @param int p_bug_id integer representing bug id
  699. * @return bool
  700. * @access public
  701. * @uses access_api.php
  702. * @uses config_api.php
  703. */
  704. function bug_is_readonly( $p_bug_id ) {
  705. $t_status = bug_get_field( $p_bug_id, 'status' );
  706. if( $t_status < config_get( 'bug_readonly_status_threshold' ) ) {
  707. return false;
  708. }
  709. if( access_has_bug_level( config_get( 'update_readonly_bug_threshold' ), $p_bug_id ) ) {
  710. return false;
  711. }
  712. return true;
  713. }
  714. /**
  715. * Check if a given bug is resolved
  716. * @param int p_bug_id integer representing bug id
  717. * @return bool true if bug is resolved, false otherwise
  718. * @access public
  719. * @uses config_api.php
  720. */
  721. function bug_is_resolved( $p_bug_id ) {
  722. $t_bug = bug_get( $p_bug_id );
  723. return( $t_bug->status >= config_get( 'bug_resolved_status_threshold', null, null, $t_bug->project_id ) );
  724. }
  725. /**
  726. * Check if a given bug is closed
  727. * @param int p_bug_id integer representing bug id
  728. * @return bool true if bug is closed, false otherwise
  729. * @access public
  730. * @uses config_api.php
  731. */
  732. function bug_is_closed( $p_bug_id ) {
  733. $t_bug = bug_get( $p_bug_id );
  734. return( $t_bug->status >= config_get( 'bug_closed_status_threshold', null, null, $t_bug->project_id ) );
  735. }
  736. /**
  737. * Check if a given bug is overdue
  738. * @param int p_bug_id integer representing bug id
  739. * @return bool true if bug is overdue, false otherwise
  740. * @access public
  741. * @uses database_api.php
  742. */
  743. function bug_is_overdue( $p_bug_id ) {
  744. $t_due_date = bug_get_field( $p_bug_id, 'due_date' );
  745. if( !date_is_null( $t_due_date ) ) {
  746. $t_now = db_now();
  747. if( $t_now > $t_due_date ) {
  748. if( !bug_is_resolved( $p_bug_id ) ) {
  749. return true;
  750. }
  751. }
  752. }
  753. return false;
  754. }
  755. /**
  756. * Validate workflow state to see if bug can be moved to requested state
  757. * @param int p_bug_status current bug status
  758. * @param int p_wanted_status new bug status
  759. * @return bool
  760. * @access public
  761. * @uses config_api.php
  762. * @uses utility_api.php
  763. */
  764. function bug_check_workflow( $p_bug_status, $p_wanted_status ) {
  765. $t_status_enum_workflow = config_get( 'status_enum_workflow' );
  766. if( count( $t_status_enum_workflow ) < 1 ) {
  767. # workflow not defined, use default enum
  768. return true;
  769. }
  770. if ( $p_bug_status == $p_wanted_status ) {
  771. # no change in state, allow the transition
  772. return true;
  773. }
  774. # There should always be a possible next status, if not defined, then allow all.
  775. if ( !isset( $t_status_enum_workflow[$p_bug_status] ) ) {
  776. return true;
  777. }
  778. # workflow defined - find allowed states
  779. $t_allowed_states = $t_status_enum_workflow[$p_bug_status];
  780. return MantisEnum::hasValue( $t_allowed_states, $p_wanted_status );
  781. }
  782. /**
  783. * Copy a bug from one project to another. Also make copies of issue notes, attachments, history,
  784. * email notifications etc.
  785. * @todo Not managed FTP file upload
  786. * @param array p_bug_id integer representing bug id
  787. * @param int p_target_project_id
  788. * @param bool p_copy_custom_fields
  789. * @param bool p_copy_relationships
  790. * @param bool p_copy_history
  791. * @param bool p_copy_attachments
  792. * @param bool p_copy_bugnotes
  793. * @param bool p_copy_monitoring_users
  794. * @return int representing the new bugid
  795. * @access public
  796. */
  797. 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 ) {
  798. global $g_db;
  799. $t_mantis_custom_field_string_table = db_get_table( 'mantis_custom_field_string_table' );
  800. $t_mantis_bug_file_table = db_get_table( 'mantis_bug_file_table' );
  801. $t_mantis_bugnote_table = db_get_table( 'mantis_bugnote_table' );
  802. $t_mantis_bugnote_text_table = db_get_table( 'mantis_bugnote_text_table' );
  803. $t_mantis_bug_history_table = db_get_table( 'mantis_bug_history_table' );
  804. $t_mantis_db = $g_db;
  805. $t_bug_id = db_prepare_int( $p_bug_id );
  806. $t_target_project_id = db_prepare_int( $p_target_project_id );
  807. $t_bug_data = bug_get( $t_bug_id, true );
  808. # retrieve the project id associated with the bug
  809. if(( $p_target_project_id == null ) || is_blank( $p_target_project_id ) ) {
  810. $t_target_project_id = $t_bug_data->project_id;
  811. }
  812. $t_bug_data->project_id = $t_target_project_id;
  813. $t_bug_data->reporter_id = auth_get_current_user_id();
  814. $t_new_bug_id = $t_bug_data->create();
  815. # MASC ATTENTION: IF THE SOURCE BUG HAS TO HANDLER THE bug_create FUNCTION CAN TRY TO AUTO-ASSIGN THE BUG
  816. # WE FORCE HERE TO DUPLICATE THE SAME HANDLER OF THE SOURCE BUG
  817. # @todo VB: Shouldn't we check if the handler in the source project is also a handler in the destination project?
  818. bug_set_field( $t_new_bug_id, 'handler_id', $t_bug_data->handler_id );
  819. bug_set_field( $t_new_bug_id, 'duplicate_id', $t_bug_data->duplicate_id );
  820. bug_set_field( $t_new_bug_id, 'status', $t_bug_data->status );
  821. bug_set_field( $t_new_bug_id, 'resolution', $t_bug_data->resolution );
  822. bug_set_field( $t_new_bug_id, 'projection', $t_bug_data->projection );
  823. bug_set_field( $t_new_bug_id, 'eta', $t_bug_data->eta );
  824. bug_set_field( $t_new_bug_id, 'fixed_in_version', $t_bug_data->fixed_in_version );
  825. bug_set_field( $t_new_bug_id, 'target_version', $t_bug_data->target_version );
  826. bug_set_field( $t_new_bug_id, 'sponsorship_total', 0 );
  827. bug_set_field( $t_new_bug_id, 'sticky', 0 );
  828. bug_set_field( $t_new_bug_id, 'due_date', $t_bug_data->due_date );
  829. # COPY CUSTOM FIELDS
  830. if( $p_copy_custom_fields ) {
  831. $query = "SELECT field_id, bug_id, value
  832. FROM $t_mantis_custom_field_string_table
  833. WHERE bug_id=" . db_param();
  834. $result = db_query_bound( $query, Array( $t_bug_id ) );
  835. $t_count = db_num_rows( $result );
  836. for( $i = 0;$i < $t_count;$i++ ) {
  837. $t_bug_custom = db_fetch_array( $result );
  838. $c_field_id = db_prepare_int( $t_bug_custom['field_id'] );
  839. $c_new_bug_id = db_prepare_int( $t_new_bug_id );
  840. $c_value = $t_bug_custom['value'];
  841. $query = "INSERT INTO $t_mantis_custom_field_string_table
  842. ( field_id, bug_id, value )
  843. VALUES (" . db_param() . ', ' . db_param() . ', ' . db_param() . ')';
  844. db_query_bound( $query, Array( $c_field_id, $c_new_bug_id, $c_value ) );
  845. }
  846. }
  847. # Copy Relationships
  848. if( $p_copy_relationships ) {
  849. relationship_copy_all( $t_bug_id, $t_new_bug_id );
  850. }
  851. # Copy bugnotes
  852. if( $p_copy_bugnotes ) {
  853. $query = "SELECT *
  854. FROM $t_mantis_bugnote_table
  855. WHERE bug_id=" . db_param();
  856. $result = db_query_bound( $query, Array( $t_bug_id ) );
  857. $t_count = db_num_rows( $result );
  858. for( $i = 0;$i < $t_count;$i++ ) {
  859. $t_bug_note = db_fetch_array( $result );
  860. $t_bugnote_text_id = $t_bug_note['bugnote_text_id'];
  861. $query2 = "SELECT *
  862. FROM $t_mantis_bugnote_text_table
  863. WHERE id=" . db_param();
  864. $result2 = db_query_bound( $query2, Array( $t_bugnote_text_id ) );
  865. $t_count2 = db_num_rows( $result2 );
  866. $t_bugnote_text_insert_id = -1;
  867. if( $t_count2 > 0 ) {
  868. $t_bugnote_text = db_fetch_array( $result2 );
  869. $query2 = "INSERT INTO $t_mantis_bugnote_text_table
  870. ( note )
  871. VALUES ( " . db_param() . ' )';
  872. db_query_bound( $query2, Array( $t_bugnote_text['note'] ) );
  873. $t_bugnote_text_insert_id = db_insert_id( $t_mantis_bugnote_text_table );
  874. }
  875. $query2 = "INSERT INTO $t_mantis_bugnote_table
  876. ( bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified )
  877. VALUES ( " . db_param() . ",
  878. " . db_param() . ",
  879. " . db_param() . ",
  880. " . db_param() . ",
  881. " . db_param() . ",
  882. " . db_param() . ')';
  883. 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'] ) );
  884. }
  885. }
  886. # Copy attachments
  887. if( $p_copy_attachments ) {
  888. file_copy_attachments( $t_bug_id , $t_new_bug_id );
  889. }
  890. # Copy users monitoring bug
  891. if( $p_copy_monitoring_users ) {
  892. bug_monitor_copy( $t_bug_id, $t_new_bug_id );
  893. }
  894. # COPY HISTORY
  895. history_delete( $t_new_bug_id ); # should history only be deleted inside the if statement below?
  896. if( $p_copy_history ) {
  897. # @todo problem with this code: the generated history trail is incorrect because the note IDs are those of the original bug, not the copied ones
  898. # @todo actually, does it even make sense to copy the history ?
  899. $query = "SELECT *
  900. FROM $t_mantis_bug_history_table
  901. WHERE bug_id = " . db_param();
  902. $result = db_query_bound( $query, Array( $t_bug_id ) );
  903. $t_count = db_num_rows( $result );
  904. for( $i = 0;$i < $t_count;$i++ ) {
  905. $t_bug_history = db_fetch_array( $result );
  906. $query = "INSERT INTO $t_mantis_bug_history_table
  907. ( user_id, bug_id, date_modified, field_name, old_value, new_value, type )
  908. VALUES ( " . db_param() . ",
  909. " . db_param() . ",
  910. " . db_param() . ",
  911. " . db_param() . ",
  912. " . db_param() . ",
  913. " . db_param() . ",
  914. " . db_param() . " );";
  915. 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'] ) );
  916. }
  917. } else {
  918. # Create a "New Issue" history entry
  919. history_log_event_special( $t_new_bug_id, NEW_BUG );
  920. }
  921. # Create history entries to reflect the copy operation
  922. history_log_event_special( $t_new_bug_id, BUG_CREATED_FROM, '', $t_bug_id );
  923. history_log_event_special( $t_bug_id, BUG_CLONED_TO, '', $t_new_bug_id );
  924. return $t_new_bug_id;
  925. }
  926. /**
  927. * Moves an issue from a project to another.
  928. *
  929. * @todo Validate with sub-project / category inheritance scenarios.
  930. * @param int p_bug_id The bug to be moved.
  931. * @param int p_target_project_id The target project to move the bug to.
  932. * @access public
  933. */
  934. function bug_move( $p_bug_id, $p_target_project_id ) {
  935. // Attempt to move disk based attachments to new project file directory.
  936. file_move_bug_attachments( $p_bug_id, $p_target_project_id );
  937. // Move the issue to the new project.
  938. bug_set_field( $p_bug_id, 'project_id', $p_target_project_id );
  939. // Update the category if needed
  940. $t_category_id = bug_get_field( $p_bug_id, 'category_id' );
  941. // Bug has no category
  942. if( $t_category_id == 0 ) {
  943. // Category is required in target project, set it to default
  944. if( ON != config_get( 'allow_no_category', null, null, $p_target_project_id ) ) {
  945. bug_set_field( $p_bug_id, 'category_id', config_get( 'default_category_for_moves' ) );
  946. }
  947. }
  948. // Check if the category is global, and if not attempt mapping it to the new project
  949. else {
  950. $t_category_project_id = category_get_field( $t_category_id, 'project_id' );
  951. if ( $t_category_project_id != ALL_PROJECTS
  952. && !project_hierarchy_inherit_parent( $p_target_project_id, $t_category_project_id )
  953. ) {
  954. // Map by name
  955. $t_category_name = category_get_field( $t_category_id, 'name' );
  956. $t_target_project_category_id = category_get_id_by_name( $t_category_name, $p_target_project_id, /* triggerErrors */ false );
  957. if ( $t_target_project_category_id === false ) {
  958. // Use default category after moves, since there is no match by name.
  959. $t_target_project_category_id = config_get( 'default_category_for_moves' );
  960. }
  961. bug_set_field( $p_bug_id, 'category_id', $t_target_project_category_id );
  962. }
  963. }
  964. }
  965. /**
  966. * allows bug deletion :
  967. * delete the bug, bugtext, bugnote, and bugtexts selected
  968. * @param array p_bug_id integer representing bug id
  969. * @return bool (always true)
  970. * @access public
  971. */
  972. function bug_delete( $p_bug_id ) {
  973. $c_bug_id = (int) $p_bug_id;
  974. $t_bug_table = db_get_table( 'mantis_bug_table' );
  975. $t_bug_text_table = db_get_table( 'mantis_bug_text_table' );
  976. # call pre-deletion custom function
  977. helper_call_custom_function( 'issue_delete_validate', array( $p_bug_id ) );
  978. # log deletion of bug
  979. history_log_event_special( $p_bug_id, BUG_DELETED, bug_format_id( $p_bug_id ) );
  980. email_bug_deleted( $p_bug_id );
  981. # call post-deletion custom function. We call this here to allow the custom function to access the details of the bug before
  982. # 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
  983. # provide it with bug data rather than an id, but this will break backward compatibility.
  984. helper_call_custom_function( 'issue_delete_notify', array( $p_bug_id ) );
  985. # Unmonitor bug for all users
  986. bug_unmonitor( $p_bug_id, null );
  987. # Delete custom fields
  988. custom_field_delete_all_values( $p_bug_id );
  989. # Delete bugnotes
  990. bugnote_delete_all( $p_bug_id );
  991. # Delete all sponsorships
  992. sponsorship_delete_all( $p_bug_id );
  993. # MASC RELATIONSHIP
  994. # we delete relationships even if the feature is currently off.
  995. relationship_delete_all( $p_bug_id );
  996. # MASC RELATIONSHIP
  997. # Delete files
  998. file_delete_attachments( $p_bug_id );
  999. # Detach tags
  1000. tag_bug_detach_all( $p_bug_id, false );
  1001. # Delete the bug history
  1002. history_delete( $p_bug_id );
  1003. # Delete bug info revisions
  1004. bug_revision_delete( $p_bug_id );
  1005. # Delete the bugnote text
  1006. $t_bug_text_id = bug_get_field( $p_bug_id, 'bug_text_id' );
  1007. $query = "DELETE FROM $t_bug_text_table
  1008. WHERE id=" . db_param();
  1009. db_query_bound( $query, Array( $t_bug_text_id ) );
  1010. # Delete the bug entry
  1011. $query = "DELETE FROM $t_bug_table
  1012. WHERE id=" . db_param();
  1013. db_query_bound( $query, Array( $c_bug_id ) );
  1014. bug_clear_cache( $p_bug_id );
  1015. bug_text_clear_cache( $p_bug_id );
  1016. # db_query errors on failure so:
  1017. return true;
  1018. }
  1019. /**
  1020. * Delete all bugs associated with a project
  1021. * @param array p_project_id integer representing a projectid
  1022. * @return bool always true
  1023. * @access public
  1024. * @uses database_api.php
  1025. */
  1026. function bug_delete_all( $p_project_id ) {
  1027. $c_project_id = (int) $p_project_id;
  1028. $t_bug_table = db_get_table( 'mantis_bug_table' );
  1029. $query = "SELECT id
  1030. FROM $t_bug_table
  1031. WHERE project_id=" . db_param();
  1032. $result = db_query_bound( $query, array( $c_project_id ) );
  1033. $bug_count = db_num_rows( $result );
  1034. for( $i = 0;$i < $bug_count;$i++ ) {
  1035. $row = db_fetch_array( $result );
  1036. bug_delete( $row['id'] );
  1037. }
  1038. # @todo should we check the return value of each bug_delete() and
  1039. # return false if any of them return false? Presumable bug_delete()
  1040. # will eventually trigger an error on failure so it won't matter...
  1041. return true;
  1042. }
  1043. /**
  1044. * Returns the extended record of the specified bug, this includes
  1045. * the bug text fields
  1046. * @todo include reporter name and handler name, the problem is that
  1047. * handler can be 0, in this case no corresponding name will be
  1048. * found. Use equivalent of (+) in Oracle.
  1049. * @param int p_bug_id integer representing bug id
  1050. * @return array
  1051. * @access public
  1052. */
  1053. function bug_get_extended_row( $p_bug_id ) {
  1054. $t_base = bug_cache_row( $p_bug_id );
  1055. $t_text = bug_text_cache_row( $p_bug_id );
  1056. # merge $t_text first so that the 'id' key has the bug id not the bug text id
  1057. return array_merge( $t_text, $t_base );
  1058. }
  1059. /**
  1060. * Returns the record of the specified bug
  1061. * @param int p_bug_id integer representing bug id
  1062. * @return array
  1063. * @access public
  1064. */
  1065. function bug_get_row( $p_bug_id ) {
  1066. return bug_cache_row( $p_bug_id );
  1067. }
  1068. /**
  1069. * Returns an object representing the specified bug
  1070. * @param int p_bug_id integer representing bug id
  1071. * @param bool p_get_extended included extended information (including bug_text)
  1072. * @return object BugData Object
  1073. * @access public
  1074. */
  1075. function bug_get( $p_bug_id, $p_get_extended = false ) {
  1076. if( $p_get_extended ) {
  1077. $row = bug_get_extended_row( $p_bug_id );
  1078. } else {
  1079. $row = bug_get_row( $p_bug_id );
  1080. }
  1081. $t_bug_data = new BugData;
  1082. $t_bug_data->loadrow( $row );
  1083. return $t_bug_data;
  1084. }
  1085. function bug_row_to_object( $p_row ) {
  1086. $t_bug_data = new BugData;
  1087. $t_bug_data->loadrow( $p_row );
  1088. return $t_bug_data;
  1089. }
  1090. /**
  1091. * return the specified field of the given bug
  1092. * if the field does not exist, display a warning and return ''
  1093. * @param int p_bug_id integer representing bug id
  1094. * @param string p_fieldname field name
  1095. * @return string
  1096. * @access public
  1097. */
  1098. function bug_get_field( $p_bug_id, $p_field_name ) {
  1099. $row = bug_get_row( $p_bug_id );
  1100. if( isset( $row[$p_field_name] ) ) {
  1101. return $row[$p_field_name];
  1102. } else {
  1103. error_parameters( $p_field_name );
  1104. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  1105. return '';
  1106. }
  1107. }
  1108. /**
  1109. * return the specified text field of the given bug
  1110. * if the field does not exist, display a warning and return ''
  1111. * @param int p_bug_id integer representing bug id
  1112. * @param string p_fieldname field name
  1113. * @return string
  1114. * @access public
  1115. */
  1116. function bug_get_text_field( $p_bug_id, $p_field_name ) {
  1117. $row = bug_text_cache_row( $p_bug_id );
  1118. if( isset( $row[$p_field_name] ) ) {
  1119. return $row[$p_field_name];
  1120. } else {
  1121. error_parameters( $p_field_name );
  1122. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  1123. return '';
  1124. }
  1125. }
  1126. /**
  1127. * return the bug summary
  1128. * this is a wrapper for the custom function
  1129. * @param int p_bug_id integer representing bug id
  1130. * @param int p_context representing SUMMARY_CAPTION, SUMMARY_FIELD
  1131. * @return string
  1132. * @access public
  1133. * @uses helper_api.php
  1134. */
  1135. function bug_format_summary( $p_bug_id, $p_context ) {
  1136. return helper_call_custom_function( 'format_issue_summary', array( $p_bug_id, $p_context ) );
  1137. }
  1138. /**
  1139. * return the timestamp for the most recent time at which a bugnote
  1140. * associated with the bug was modified
  1141. * @param int p_bug_id integer representing bug id
  1142. * @return bool|int false or timestamp in integer format representing newest bugnote timestamp
  1143. * @access public
  1144. * @uses database_api.php
  1145. */
  1146. function bug_get_newest_bugnote_timestamp( $p_bug_id ) {
  1147. $c_bug_id = db_prepare_int( $p_bug_id );
  1148. $t_bugnote_table = db_get_table( 'mantis_bugnote_table' );
  1149. $query = "SELECT last_modified
  1150. FROM $t_bugnote_table
  1151. WHERE bug_id=" . db_param() . "
  1152. ORDER BY last_modified DESC";
  1153. $result = db_query_bound( $query, Array( $c_bug_id ), 1 );
  1154. $row = db_result( $result );
  1155. if( false === $row ) {
  1156. return false;
  1157. } else {
  1158. return $row;
  1159. }
  1160. }
  1161. /**
  1162. * return the timestamp for the most recent time at which a bugnote
  1163. * associated with the bug was modified and the total bugnote
  1164. * count in one db query
  1165. * @param int p_bug_id integer representing bug id
  1166. * @return object consisting of bugnote stats
  1167. * @access public
  1168. * @uses database_api.php
  1169. */
  1170. function bug_get_bugnote_stats( $p_bug_id ) {
  1171. global $g_cache_bug;
  1172. $c_bug_id = db_prepare_int( $p_bug_id );
  1173. if( !is_null( $g_cache_bug[$c_bug_id]['_stats'] ) ) {
  1174. if( $g_cache_bug[$c_bug_id]['_stats'] === false ) {
  1175. return false;
  1176. } else {
  1177. $t_stats = $g_cache_bug[$c_bug_id]['_stats'];
  1178. }
  1179. return $t_stats;
  1180. }
  1181. $t_bugnote_table = db_get_table( 'mantis_bugnote_table' );
  1182. $query = "SELECT last_modified
  1183. FROM $t_bugnote_table
  1184. WHERE bug_id=" . db_param() . "
  1185. ORDER BY last_modified DESC";
  1186. $result = db_query_bound( $query, Array( $c_bug_id ) );
  1187. $row = db_fetch_array( $result );
  1188. if( false === $row ) {
  1189. return false;
  1190. }
  1191. $t_stats['last_modified'] = $row['last_modified'];
  1192. $t_stats['count'] = db_num_rows( $result );
  1193. return $t_stats;
  1194. }
  1195. /**
  1196. * Get array of attachments associated with the specified bug id. The array will be
  1197. * sorted in terms of date added (ASC). The array will include the following fields:
  1198. * id, title, diskfile, filename, filesize, file_type, date_added, user_id.
  1199. * @param int p_bug_id integer representing bug id
  1200. * @return array array of results or null
  1201. * @access public
  1202. * @uses database_api.php
  1203. * @uses file_api.php
  1204. */
  1205. function bug_get_attachments( $p_bug_id ) {
  1206. $c_bug_id = db_prepare_int( $p_bug_id );
  1207. $t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
  1208. $query = "SELECT id, title, diskfile, filename, filesize, file_type, date_added, user_id
  1209. FROM $t_bug_file_table
  1210. WHERE bug_id=" . db_param() . "
  1211. ORDER BY date_added";
  1212. $db_result = db_query_bound( $query, Array( $c_bug_id ) );
  1213. $num_files = db_num_rows( $db_result );
  1214. $t_result = array();
  1215. for( $i = 0;$i < $num_files;$i++ ) {
  1216. $t_result[] = db_fetch_array( $db_result );
  1217. }
  1218. return $t_result;
  1219. }
  1220. # ===================================
  1221. # Data Modification
  1222. # ===================================
  1223. /**
  1224. * Set the value of a bug field
  1225. * @param int p_bug_id integer representing bug id
  1226. * @param string p_field_name pre-defined field name
  1227. * @param any p_value value to set
  1228. * @return bool (always true)
  1229. * @access public
  1230. * @uses database_api.php
  1231. * @uses history_api.php
  1232. */
  1233. function bug_set_field( $p_bug_id, $p_field_name, $p_value ) {
  1234. $c_bug_id = db_prepare_int( $p_bug_id );
  1235. $c_value = null;
  1236. switch( $p_field_name ) {
  1237. # bool
  1238. case 'sticky':
  1239. $c_value = $p_value;
  1240. break;
  1241. # int
  1242. case 'project_id':
  1243. case 'reporter_id':
  1244. case 'handler_id':
  1245. case 'duplicate_id':
  1246. case 'priority':
  1247. case 'severity':
  1248. case 'reproducibility':
  1249. case 'status':
  1250. case 'resolution':
  1251. case 'projection':
  1252. case 'category_id':
  1253. case 'eta':
  1254. case 'view_state':
  1255. case 'profile_id':
  1256. case 'sponsorship_total':
  1257. $c_value = (int) $p_value;
  1258. break;
  1259. # string
  1260. case 'os':
  1261. case 'os_build':
  1262. case 'platform':
  1263. case 'version':
  1264. case 'fixed_in_version':
  1265. case 'target_version':
  1266. case 'build':
  1267. case 'summary':
  1268. $c_value = $p_value;
  1269. break;
  1270. # dates
  1271. case 'last_updated':
  1272. case 'date_submitted':
  1273. case 'due_date':
  1274. if ( !is_numeric( $p_value ) ) {
  1275. trigger_error( ERROR_GENERIC, ERROR );
  1276. }
  1277. $c_value = $p_value;
  1278. break;
  1279. default:
  1280. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  1281. break;
  1282. }
  1283. $t_current_value = bug_get_field( $p_bug_id, $p_field_name );
  1284. # return if status is already set
  1285. if( $c_value == $t_current_value ) {
  1286. return true;
  1287. }
  1288. $t_bug_table = db_get_table( 'mantis_bug_table' );
  1289. # Update fields
  1290. $query = "UPDATE $t_bug_table
  1291. SET $p_field_name=" . db_param() . "
  1292. WHERE id=" . db_param();
  1293. db_query_bound( $query, Array( $c_value, $c_bug_id ) );
  1294. # updated the last_updated date
  1295. bug_update_date( $p_bug_id );
  1296. # log changes except for duplicate_id which is obsolete and should be removed in
  1297. # MantisBT 1.3.
  1298. switch( $p_field_name ) {
  1299. case 'duplicate_id':
  1300. break;
  1301. case 'category_id':
  1302. history_log_event_direct( $p_bug_id, 'category', category_full_name( $t_current_value, false ), category_full_name( $c_value, false ) );
  1303. break;
  1304. default:
  1305. history_log_event_direct( $p_bug_id, $p_field_name, $t_current_value, $c_value );
  1306. }
  1307. bug_clear_cache( $p_bug_id );
  1308. return true;
  1309. }
  1310. /**
  1311. * assign the bug to the given user
  1312. * @param array p_bug_id_array integer array representing bug ids to cache
  1313. * @return null
  1314. * @access public
  1315. * @uses database_api.php
  1316. */
  1317. function bug_assign( $p_bug_id, $p_user_id, $p_bugnote_text = '', $p_bugnote_private = false ) {
  1318. $c_bug_id = db_prepare_int( $p_bug_id );
  1319. $c_user_id = db_prepare_int( $p_user_id );
  1320. if(( $c_user_id != NO_USER ) && !access_has_bug_level( config_get( 'handle_bug_threshold' ), $p_bug_id, $p_user_id ) ) {
  1321. trigger_error( ERROR_USER_DOES_NOT_HAVE_REQ_ACCESS );
  1322. }
  1323. # extract current information into history variables
  1324. $h_status = bug_get_field( $p_bug_id, 'status' );
  1325. $h_handler_id = bug_get_field( $p_bug_id, 'handler_id' );
  1326. if(( ON == config_get( 'auto_set_status_to_assigned' ) ) && ( NO_USER != $p_user_id ) ) {
  1327. $t_ass_val = config_get( 'bug_assigned_status' );
  1328. } else {
  1329. $t_ass_val = $h_status;
  1330. }
  1331. $t_bug_table = db_get_table( 'mantis_bug_table' );
  1332. if(( $t_ass_val != $h_status ) || ( $p_user_id != $h_handler_id ) ) {
  1333. # get user id
  1334. $query = "UPDATE $t_bug_table
  1335. SET handler_id=" . db_param() . ", status=" . db_param() . "
  1336. WHERE id=" . db_param();
  1337. db_query_bound( $query, Array( $c_user_id, $t_ass_val, $c_bug_id ) );
  1338. # log changes
  1339. history_log_event_direct( $c_bug_id, 'status', $h_status, $t_ass_val );
  1340. history_log_event_direct( $c_bug_id, 'handler_id', $h_handler_id, $p_user_id );
  1341. # Add bugnote if supplied ignore false return
  1342. bugnote_add( $p_bug_id, $p_bugnote_text, 0, $p_bugnote_private, 0, '', NULL, FALSE );
  1343. # updated the last_updated date
  1344. bug_update_date( $p_bug_id );
  1345. bug_clear_cache( $p_bug_id );
  1346. # send assigned to email
  1347. email_ass

Large files files are truncated, but you can click here to view the full file