PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/bug_update.php

https://github.com/markkimsal/mantisbt
PHP | 406 lines | 270 code | 42 blank | 94 comment | 67 complexity | 733cd17d98a53ea450075e5a074b1ef6 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  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. * Update bug data then redirect to the appropriate viewing page
  17. *
  18. * @package MantisBT
  19. * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  20. * @copyright Copyright (C) 2002 - 2010 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  21. * @link http://www.mantisbt.org
  22. *
  23. * @uses core.php
  24. * @uses access_api.php
  25. * @uses authentication_api.php
  26. * @uses bug_api.php
  27. * @uses bugnote_api.php
  28. * @uses config_api.php
  29. * @uses constant_inc.php
  30. * @uses custom_field_api.php
  31. * @uses email_api.php
  32. * @uses error_api.php
  33. * @uses event_api.php
  34. * @uses form_api.php
  35. * @uses gpc_api.php
  36. * @uses helper_api.php
  37. * @uses history_api.php
  38. * @uses lang_api.php
  39. * @uses print_api.php
  40. * @uses relationship_api.php
  41. * @uses twitter_api.php
  42. */
  43. require_once( 'core.php' );
  44. require_api( 'access_api.php' );
  45. require_api( 'authentication_api.php' );
  46. require_api( 'bug_api.php' );
  47. require_api( 'bugnote_api.php' );
  48. require_api( 'config_api.php' );
  49. require_api( 'constant_inc.php' );
  50. require_api( 'custom_field_api.php' );
  51. require_api( 'email_api.php' );
  52. require_api( 'error_api.php' );
  53. require_api( 'event_api.php' );
  54. require_api( 'form_api.php' );
  55. require_api( 'gpc_api.php' );
  56. require_api( 'helper_api.php' );
  57. require_api( 'history_api.php' );
  58. require_api( 'lang_api.php' );
  59. require_api( 'print_api.php' );
  60. require_api( 'relationship_api.php' );
  61. require_api( 'twitter_api.php' );
  62. form_security_validate( 'bug_update' );
  63. $f_bug_id = gpc_get_int( 'bug_id' );
  64. $t_existing_bug = bug_get( $f_bug_id, true );
  65. if ( helper_get_current_project() !== $t_existing_bug->project_id ) {
  66. $g_project_override = $t_existing_bug->project_id;
  67. }
  68. # Ensure that the user has permission to update bugs. This check also factors
  69. # in whether the user has permission to view private bugs. The
  70. # $g_limit_reporters option is also taken into consideration.
  71. access_ensure_bug_level( config_get( 'update_bug_threshold' ), $f_bug_id );
  72. # Check if the bug is in a read-only state and whether the current user has
  73. # permission to update read-only bugs.
  74. if ( bug_is_readonly( $f_bug_id ) ) {
  75. error_parameters( $f_bug_id );
  76. trigger_error( ERROR_BUG_READ_ONLY_ACTION_DENIED, ERROR );
  77. }
  78. $t_updated_bug = clone $t_existing_bug;
  79. $t_updated_bug->additional_information = gpc_get_string( 'additional_information', $t_existing_bug->additional_information );
  80. $t_updated_bug->build = gpc_get_string( 'build', $t_existing_bug->build );
  81. $t_updated_bug->category_id = gpc_get_int( 'category_id', $t_existing_bug->category_id );
  82. $t_updated_bug->description = gpc_get_string( 'description', $t_existing_bug->description );
  83. $t_due_date = gpc_get_string( 'due_date', null );
  84. if ( $t_due_date !== null) {
  85. if ( is_blank ( $t_due_date ) ) {
  86. $t_updated_bug->due_date = 1;
  87. } else {
  88. $t_updated_bug->due_date = strtotime( $t_due_date );
  89. }
  90. }
  91. $t_updated_bug->duplicate_id = gpc_get_int( 'duplicate_id', 0 );
  92. $t_updated_bug->eta = gpc_get_int( 'eta', $t_existing_bug->eta );
  93. $t_updated_bug->fixed_in_version = gpc_get_string( 'fixed_in_version', $t_existing_bug->fixed_in_version );
  94. $t_updated_bug->handler_id = gpc_get_int( 'handler_id', $t_existing_bug->handler_id );
  95. $t_updated_bug->os = gpc_get_string( 'os', $t_existing_bug->os );
  96. $t_updated_bug->os_build = gpc_get_string( 'os_build', $t_existing_bug->os_build );
  97. $t_updated_bug->platform = gpc_get_string( 'platform', $t_existing_bug->platform );
  98. $t_updated_bug->priority = gpc_get_int( 'priority', $t_existing_bug->priority );
  99. $t_updated_bug->projection = gpc_get_int( 'projection', $t_existing_bug->projection );
  100. $t_updated_bug->reporter_id = gpc_get_int( 'reporter_id', $t_existing_bug->reporter_id );
  101. $t_updated_bug->reproducibility = gpc_get_int( 'reproducibility', $t_existing_bug->reproducibility );
  102. $t_updated_bug->resolution = gpc_get_int( 'resolution', $t_existing_bug->resolution );
  103. $t_updated_bug->severity = gpc_get_int( 'severity', $t_existing_bug->severity );
  104. $t_updated_bug->status = gpc_get_int( 'status', $t_existing_bug->status );
  105. $t_updated_bug->steps_to_reproduce = gpc_get_string( 'steps_to_reproduce', $t_existing_bug->steps_to_reproduce );
  106. $t_updated_bug->summary = gpc_get_string( 'summary', $t_existing_bug->summary );
  107. $t_updated_bug->target_version = gpc_get_string( 'target_version', $t_existing_bug->target_version );
  108. $t_updated_bug->version = gpc_get_string( 'version', $t_existing_bug->version );
  109. $t_updated_bug->view_state = gpc_get_int( 'view_state', $t_existing_bug->view_state );
  110. $t_bug_note = new BugNoteData();
  111. $t_bug_note->note = gpc_get_string( 'bugnote_text', '' );
  112. $t_bug_note->view_state = gpc_get_bool( 'private', config_get( 'default_bugnote_view_status' ) );
  113. $t_bug_note->time_tracking = gpc_get_string( 'time_tracking', '0:00' );
  114. # Determine whether the new status will reopen, resolve or close the issue.
  115. # Note that multiple resolved or closed states can exist and thus we need to
  116. # look at a range of statuses when performing this check.
  117. $t_resolved_status = config_get( 'bug_resolved_status_threshold' );
  118. $t_closed_status = config_get( 'bug_closed_status_threshold' );
  119. $t_resolve_issue = false;
  120. $t_close_issue = false;
  121. $t_reopen_issue = false;
  122. if ( $t_existing_bug->status < $t_resolved_status &&
  123. $t_updated_bug->status >= $t_resolved_status &&
  124. $t_updated_bug->status < $t_closed_status ) {
  125. $t_resolve_issue = true;
  126. } else if ( $t_existing_bug->status < $t_closed_status &&
  127. $t_updated_bug->status >= $t_closed_status ) {
  128. $t_close_issue = true;
  129. } else if ( $t_existing_bug->status >= $t_resolved_status &&
  130. $t_updated_bug->status <= config_get( 'bug_reopen_status' ) ) {
  131. $t_reopen_issue = true;
  132. }
  133. # If resolving or closing, ensure that all dependant issues have been resolved.
  134. if ( ( $t_resolve_issue || $t_close_issue ) &&
  135. !relationship_can_resolve_bug( $f_bug_id ) ) {
  136. trigger_error( ERROR_BUG_RESOLVE_DEPENDANTS_BLOCKING, ERROR );
  137. }
  138. # Validate any change to the status of the issue.
  139. if ( $t_existing_bug->status !== $t_updated_bug->status ) {
  140. access_ensure_bug_level( config_get( 'update_bug_status_threshold' ), $f_bug_id );
  141. if ( !bug_check_workflow( $t_existing_bug->status, $t_updated_bug->status ) ) {
  142. error_parameters( lang_get( 'status' ) );
  143. trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
  144. }
  145. if ( !access_has_bug_level( access_get_status_threshold( $t_updated_bug->status, $t_updated_bug->project_id ), $f_bug_id ) ) {
  146. # The reporter may be allowed to close or reopen the issue regardless.
  147. $t_can_bypass_status_access_thresholds = false;
  148. if ( $t_close_issue &&
  149. $t_existing_bug->status >= $t_resolved_status &&
  150. $t_existing_bug->reporter_id === auth_get_current_user_id() &&
  151. config_get( 'allow_reporter_close' ) ) {
  152. $t_can_bypass_status_access_thresholds = true;
  153. } else if ( $t_reopen_issue &&
  154. $t_existing_bug->status < $t_closed_status &&
  155. $t_existing_bug->reporter_id === auth_get_current_user_id() &&
  156. config_get( 'allow_reporter_reopen' ) ) {
  157. $t_can_bypass_status_access_thresholds = true;
  158. $t_updated_bug->resolution = config_get( 'bug_reopen_resolution' );
  159. }
  160. if ( !$t_can_bypass_status_access_thresholds ) {
  161. trigger_error( ERROR_ACCESS_DENIED, ERROR );
  162. }
  163. }
  164. }
  165. # Validate any change to the handler of an issue.
  166. $t_issue_is_sponsored = sponsorship_get_amount( sponsorship_get_all_ids( $f_bug_id ) ) > 0;
  167. if ( $t_existing_bug->handler_id !== $t_updated_bug->handler_id ) {
  168. access_ensure_bug_level( config_get( 'update_bug_assign_threshold' ), $f_bug_id );
  169. if ( $t_issue_is_sponsored && !access_has_bug_level( config_get( 'handle_sponsored_bugs_threshold' ), $f_bug_id ) ) {
  170. trigger_error( ERROR_SPONSORSHIP_HANDLER_ACCESS_LEVEL_TOO_LOW, ERROR );
  171. }
  172. if ( $t_updated_bug->handler_id !== NO_USER ) {
  173. if ( !access_has_bug_level( config_get( 'handle_bug_threshold' ), $f_bug_id, $t_updated_bug->handler_id ) ) {
  174. trigger_error( ERROR_HANDLER_ACCESS_TOO_LOW, ERROR );
  175. }
  176. if ( $t_issue_is_sponsored && !access_has_bug_level( config_get( 'assign_sponsored_bugs_threshold' ), $f_bug_id ) ) {
  177. trigger_error( ERROR_SPONSORSHIP_ASSIGNER_ACCESS_LEVEL_TOO_LOW, ERROR );
  178. }
  179. }
  180. }
  181. # Check whether the category has been undefined when it's compulsory.
  182. if ( $t_existing_bug->category_id !== $t_updated_bug->category_id ) {
  183. if ( $t_updated_bug->category_id === 0 &&
  184. !config_get( 'allow_no_category' ) ) {
  185. error_parameters( lang_get( 'category' ) );
  186. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  187. }
  188. }
  189. # Don't allow resolutions denoting completion of issue to be used if the issue
  190. # has yet to be resolved or closed.
  191. if ( $t_existing_bug->resolution !== $t_updated_bug->resolution &&
  192. $t_updated_bug->resolution >= config_get( 'bug_resolution_fixed_threshold' ) &&
  193. $t_updated_bug->status < $t_resolved_status ) {
  194. error_parameters( lang_get( 'resolution' ) );
  195. trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
  196. }
  197. # Ensure that the user has permission to change the target version of the issue.
  198. if ( $t_existing_bug->target_version !== $t_updated_bug->target_version ) {
  199. access_ensure_bug_level( config_get( 'roadmap_update_threshold' ), $f_bug_id );
  200. }
  201. # Ensure that the user has permission to change the view status of the issue.
  202. if ( $t_existing_bug->view_state !== $t_updated_bug->view_state ) {
  203. access_ensure_bug_level( config_get( 'change_view_status_threshold' ), $f_bug_id );
  204. }
  205. # Determine the custom field "require check" to use for validating
  206. # whether fields can be undefined during this bug update.
  207. if ( $t_close_issue ) {
  208. $t_cf_require_check = 'require_closed';
  209. } else if ( $t_resolve_issue ) {
  210. $t_cf_require_check = 'require_resolved';
  211. } else {
  212. $t_cf_require_check = 'require_update';
  213. }
  214. $t_related_custom_field_ids = custom_field_get_linked_ids( $t_existing_bug->project_id );
  215. $t_custom_fields_to_set = array();
  216. foreach ( $t_related_custom_field_ids as $t_cf_id ) {
  217. $t_cf_def = custom_field_get_definition( $t_cf_id );
  218. if ( !gpc_isset_custom_field( $t_cf_id, $t_cf_def['type'] ) ) {
  219. if ( $t_cf_def[$t_cf_require_check] ) {
  220. # A value for the custom field was expected however
  221. # no value was given by the user.
  222. error_parameters( lang_get_defaulted( custom_field_get_field( $t_cf_id, 'name' ) ) );
  223. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  224. } else {
  225. # The custom field isn't compulsory and the user did
  226. # not supply a value. Therefore we can just ignore this
  227. # custom field completely (ie. don't attempt to update
  228. # the field).
  229. continue;
  230. }
  231. }
  232. if( !custom_field_has_write_access( $t_cf_id, $f_bug_id ) ) {
  233. trigger_error( ACCESS_DENIED, ERROR );
  234. }
  235. $t_new_custom_field_value = gpc_get_custom_field( "custom_field_$t_cf_id", $t_cf_def['type'], null );
  236. $t_old_custom_field_value = custom_field_get_value( $t_cf_id, $f_bug_id );
  237. # Validate the value of the field against current validation rules.
  238. # This may cause an error if validation rules have recently been
  239. # modified such that old values that were once OK are now considered
  240. # invalid.
  241. if ( !custom_field_validate( $t_cf_id, $t_new_custom_field_value ) ) {
  242. error_parameters( lang_get_defaulted( custom_field_get_field( $t_cf_id, 'name' ) ) );
  243. trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
  244. }
  245. # Remember the new custom field values so we can set them when updating
  246. # the bug (done after all data passed to this update page has been
  247. # validated).
  248. $t_custom_fields_to_set[] = array( 'id' => $t_cf_id, 'value' => $t_new_custom_field_value );
  249. }
  250. # Perform validation of the duplicate ID of the bug.
  251. if ( $t_updated_bug->duplicate_id !== 0 ) {
  252. if ( $t_updated_bug->duplicate_id === $f_bug_id ) {
  253. trigger_error( ERROR_BUG_DUPLICATE_SELF, ERROR );
  254. }
  255. bug_ensure_exists( $t_updated_bug->duplicate_id );
  256. if ( !access_has_bug_level( config_get( 'update_bug_threshold' ), $t_updated_bug->duplicate_id ) ) {
  257. trigger_error( ERROR_RELATIONSHIP_ACCESS_LEVEL_TO_DEST_BUG_TOO_LOW, ERROR );
  258. }
  259. if ( relationship_exists( $f_bug_id, $t_updated_bug->duplicate_id ) ) {
  260. trigger_error( ERROR_RELATIONSHIP_ALREADY_EXISTS, ERROR );
  261. }
  262. }
  263. # Validate the new bug note (if any is provided).
  264. if ( $t_bug_note->note ||
  265. ( config_get( 'time_tracking_enabled' ) &&
  266. helper_duration_to_minutes( $t_bug_note->time_tracking ) > 0 ) ) {
  267. access_ensure_bug_level( config_get( 'add_bugnote_threshold' ), $f_bug_id );
  268. if ( !$t_bug_note->note &&
  269. !config_get( 'time_tracking_without_note' ) ) {
  270. error_parameters( lang_get( 'bugnote' ) );
  271. trigger_error( ERROR_EMPTY_FIELD, ERROR );
  272. }
  273. if ( $t_bug_note->view_state !== config_get( 'default_bugnote_view_status' ) ) {
  274. access_ensure_bug_level( config_get( 'set_view_status_threshold' ), $f_bug_id );
  275. }
  276. }
  277. # Handle the reassign on feedback feature. Note that this feature generally
  278. # won't work very well with custom workflows as it makes a lot of assumptions
  279. # that may not be true. It assumes you don't have any statuses in the workflow
  280. # between 'bug_submit_status' and 'bug_feedback_status'. It assumes you only
  281. # have one feedback, assigned and submitted status.
  282. if ( $t_bug_note->note &&
  283. config_get( 'reassign_on_feedback' ) &&
  284. $t_existing_bug->status === config_get( 'bug_feedback_status' ) &&
  285. $t_updated_bug->reporter_id === auth_get_current_user_id() ) {
  286. if ( $t_updated_bug->handler_id !== NO_USER ) {
  287. $t_updated_bug->status = config_get( 'bug_assigned_status' );
  288. } else {
  289. $t_updated_bug->status = config_get( 'bug_submit_status' );
  290. }
  291. }
  292. # Handle automatic assignment of issues.
  293. if ( $t_existing_bug->handler_id === NO_USER &&
  294. $t_updated_bug->handler_id !== NO_USER &&
  295. $t_updated_bug->status < config_get( 'bug_assigned_status' ) &&
  296. config_get( 'auto_set_status_to_assigned' ) ) {
  297. $t_updated_bug->status = config_get( 'bug_assigned_status' );
  298. }
  299. # Allow a custom function to validate the proposed bug updates. Note that
  300. # custom functions are being deprecated in MantisBT. You should migrate to
  301. # the new plugin system instead.
  302. helper_call_custom_function( 'issue_update_validate', array( $f_bug_id, $t_updated_bug, $t_bug_note->note ) );
  303. # Allow plugins to validate/modify the update prior to it being committed.
  304. $t_event_updated_bug = event_signal( 'EVENT_UPDATE_BUG_DATA', array( $t_existing_bug, $t_updated_bug ) );
  305. $t_existing_bug = $t_event_updated_bug[0];
  306. $t_updated_bug = $t_event_updated_bug[1];
  307. # Commit the bug updates to the database.
  308. $t_text_field_update_required = ( $t_existing_bug->description !== $t_updated_bug->description ) ||
  309. ( $t_existing_bug->additional_information !== $t_updated_bug->additional_information ) ||
  310. ( $t_existing_bug->steps_to_reproduce !== $t_updated_bug->steps_to_reproduce );
  311. $t_updated_bug->update( $t_text_field_update_required, true );
  312. # Update custom field values.
  313. foreach ( $t_custom_fields_to_set as $t_custom_field_to_set ) {
  314. custom_field_set_value( $t_custom_field_to_set['id'], $f_bug_id, $t_custom_field_to_set['value'] );
  315. }
  316. # Add a bug note if there is one.
  317. if ( $t_bug_note->note || helper_duration_to_minutes( $t_bug_note->time_tracking ) > 0 ) {
  318. bugnote_add( $f_bug_id, $t_bug_note->note, $t_bug_note->time_tracking, $t_bug_note->view_state, 0, '', null, false );
  319. }
  320. # Add a duplicate relationship if requested.
  321. if ( $t_updated_bug->duplicate_id !== 0 ) {
  322. relationship_add( $f_bug_id, $t_updated_bug->duplicate_id, BUG_DUPLICATE );
  323. history_log_event_special( $f_bug_id, BUG_ADD_RELATIONSHIP, BUG_DUPLICATE, $t_updated_bug->duplicate_id );
  324. history_log_event_special( $t_updated_bug->duplicate_id, BUG_ADD_RELATIONSHIP, BUG_HAS_DUPLICATE, $f_bug_id );
  325. if ( user_exists( $t_existing_bug->reporter_id ) ) {
  326. bug_monitor( $f_bug_id, $t_existing_bug->reporter_id );
  327. }
  328. if ( user_exists ( $t_existing_bug->handler_id ) ) {
  329. bug_monitor( $f_bug_id, $t_existing_bug->handler_id );
  330. }
  331. bug_monitor_copy( $f_bug_id, $t_updated_bug->duplicate_id );
  332. }
  333. event_signal( 'EVENT_UPDATE_BUG', array( $t_existing_bug, $t_updated_bug ) );
  334. # Allow a custom function to respond to the modifications made to the bug. Note
  335. # that custom functions are being deprecated in MantisBT. You should migrate to
  336. # the new plugin system instead.
  337. helper_call_custom_function( 'issue_update_notify', array( $f_bug_id ) );
  338. # Send a notification of changes via email.
  339. if ( $t_resolve_issue ) {
  340. email_resolved( $f_bug_id );
  341. email_relationship_child_resolved( $f_bug_id );
  342. } else if ( $t_close_issue ) {
  343. email_close( $f_bug_id );
  344. email_relationship_child_closed( $f_bug_id );
  345. } else if ( $t_reopen_issue ) {
  346. email_reopen( $f_bug_id );
  347. } else if ( $t_existing_bug->handler_id === NO_USER &&
  348. $t_updated_bug->handler_id !== NO_USER ) {
  349. email_assign( $f_bug_id );
  350. } else if ( $t_existing_bug->status !== $t_updated_bug->status ) {
  351. $t_new_status_label = MantisEnum::getLabel( config_get( 'status_enum_string' ), $t_updated_bug->status );
  352. $t_new_status_label = str_replace( ' ', '_', $t_new_status_label );
  353. email_generic( $f_bug_id, $t_new_status_label, 'email_notification_title_for_status_bug_' . $t_new_status_label );
  354. } else {
  355. email_generic( $f_bug_id, 'updated', 'email_notification_title_for_action_bug_updated' );
  356. }
  357. # Twitter notification of bug update.
  358. if ( $t_resolve_issue &&
  359. $t_updated_bug->resolution >= config_get( 'bug_resolution_fixed_threshold' ) &&
  360. $t_updated_bug->resolution < config_get( 'bug_resolution_not_fixed_threshold' ) ) {
  361. twitter_issue_resolved( $f_bug_id );
  362. }
  363. form_security_purge( 'bug_update' );
  364. print_successful_redirect_to_bug( $f_bug_id );