PageRenderTime 64ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/bug_api.php

https://github.com/markkimsal/mantisbt
PHP | 1846 lines | 1040 code | 286 blank | 520 comment | 163 complexity | d676e4a25066121831d08468ee7f47f5 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1

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

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