PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/core/history_api.php

https://github.com/giallu/mantisbt
PHP | 770 lines | 531 code | 77 blank | 162 comment | 116 complexity | f325d12198923b0f660a029985d11db6 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. * History API
  17. *
  18. * @package CoreAPI
  19. * @subpackage HistoryAPI
  20. * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  21. * @copyright Copyright 2002 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 bug_api.php
  27. * @uses bug_revision_api.php
  28. * @uses bugnote_api.php
  29. * @uses columns_api.php
  30. * @uses config_api.php
  31. * @uses constant_inc.php
  32. * @uses custom_field_api.php
  33. * @uses database_api.php
  34. * @uses gpc_api.php
  35. * @uses helper_api.php
  36. * @uses lang_api.php
  37. * @uses project_api.php
  38. * @uses relationship_api.php
  39. * @uses sponsorship_api.php
  40. * @uses user_api.php
  41. * @uses utility_api.php
  42. */
  43. require_api( 'access_api.php' );
  44. require_api( 'authentication_api.php' );
  45. require_api( 'bug_api.php' );
  46. require_api( 'bug_revision_api.php' );
  47. require_api( 'bugnote_api.php' );
  48. require_api( 'columns_api.php' );
  49. require_api( 'config_api.php' );
  50. require_api( 'constant_inc.php' );
  51. require_api( 'custom_field_api.php' );
  52. require_api( 'database_api.php' );
  53. require_api( 'gpc_api.php' );
  54. require_api( 'helper_api.php' );
  55. require_api( 'lang_api.php' );
  56. require_api( 'project_api.php' );
  57. require_api( 'relationship_api.php' );
  58. require_api( 'sponsorship_api.php' );
  59. require_api( 'user_api.php' );
  60. require_api( 'utility_api.php' );
  61. /**
  62. * log the changes (old / new value are supplied to reduce db access)
  63. * events should be logged *after* the modification
  64. * @param integer $p_bug_id The bug identifier of the bug being modified.
  65. * @param string $p_field_name The field name of the field being modified.
  66. * @param string $p_old_value The old value of the field.
  67. * @param string $p_new_value The new value of the field.
  68. * @param integer $p_user_id The user identifier of the user modifying the bug.
  69. * @param integer $p_type The type of the modification.
  70. * @return void
  71. */
  72. function history_log_event_direct( $p_bug_id, $p_field_name, $p_old_value, $p_new_value, $p_user_id = null, $p_type = 0 ) {
  73. # Only log events that change the value
  74. if( $p_new_value != $p_old_value ) {
  75. if( null === $p_user_id ) {
  76. $p_user_id = auth_get_current_user_id();
  77. }
  78. $c_field_name = $p_field_name;
  79. $c_old_value = ( is_null( $p_old_value ) ? '' : (string)$p_old_value );
  80. $c_new_value = ( is_null( $p_new_value ) ? '' : (string)$p_new_value );
  81. db_param_push();
  82. $t_query = 'INSERT INTO {bug_history}
  83. ( user_id, bug_id, date_modified, field_name, old_value, new_value, type )
  84. VALUES
  85. ( ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ' )';
  86. db_query( $t_query, array( $p_user_id, $p_bug_id, db_now(), $c_field_name, $c_old_value, $c_new_value, $p_type ) );
  87. }
  88. }
  89. /**
  90. * log the changes
  91. * events should be logged *after* the modification
  92. * @param integer $p_bug_id The bug identifier of the bug being modified.
  93. * @param string $p_field_name The field name of the field being modified.
  94. * @param string $p_old_value The old value of the field.
  95. * @return void
  96. */
  97. function history_log_event( $p_bug_id, $p_field_name, $p_old_value ) {
  98. history_log_event_direct( $p_bug_id, $p_field_name, $p_old_value, bug_get_field( $p_bug_id, $p_field_name ) );
  99. }
  100. /**
  101. * log the changes
  102. * events should be logged *after* the modification
  103. * These are special case logs (new bug, deleted bugnote, etc.)
  104. * @param integer $p_bug_id The bug identifier of the bug being modified.
  105. * @param integer $p_type The type of the modification.
  106. * @param string $p_old_value The optional value to store in the old_value field.
  107. * @param string $p_new_value The optional value to store in the new_value field.
  108. * @return void
  109. */
  110. function history_log_event_special( $p_bug_id, $p_type, $p_old_value = '', $p_new_value = '' ) {
  111. $t_user_id = auth_get_current_user_id();
  112. if( is_null( $p_old_value ) ) {
  113. $p_old_value = '';
  114. }
  115. if( is_null( $p_new_value ) ) {
  116. $p_new_value = '';
  117. }
  118. db_param_push();
  119. $t_query = 'INSERT INTO {bug_history}
  120. ( user_id, bug_id, date_modified, type, old_value, new_value, field_name )
  121. VALUES
  122. ( ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ',' . db_param() . ', ' . db_param() . ')';
  123. db_query( $t_query, array( $t_user_id, $p_bug_id, db_now(), $p_type, $p_old_value, $p_new_value, '' ) );
  124. }
  125. /**
  126. * Retrieves the history events for the specified bug id and returns it in an array
  127. * The array is indexed from 0 to N-1. The second dimension is: 'date', 'username',
  128. * 'note', 'change'.
  129. * @param integer $p_bug_id A valid bug identifier.
  130. * @param integer $p_user_id A valid user identifier.
  131. * @return array
  132. */
  133. function history_get_events_array( $p_bug_id, $p_user_id = null ) {
  134. $t_normal_date_format = config_get( 'normal_date_format' );
  135. $t_raw_history = history_get_raw_events_array( $p_bug_id, $p_user_id );
  136. $t_history = array();
  137. foreach( $t_raw_history as $k => $t_item ) {
  138. extract( $t_item, EXTR_PREFIX_ALL, 'v' );
  139. $t_history[$k] = history_localize_item( $v_field, $v_type, $v_old_value, $v_new_value );
  140. $t_history[$k]['date'] = date( $t_normal_date_format, $v_date );
  141. $t_history[$k]['userid'] = $v_userid;
  142. $t_history[$k]['username'] = $v_username;
  143. }
  144. return( $t_history );
  145. }
  146. /**
  147. * Counts the number of changes done by the specified user within specified time window.
  148. * @param integer $p_duration_in_seconds The time window in seconds.
  149. * @param [type] $p_user_id The user id or null for logged in user.
  150. * @return integer The number of changes done by user in the specified time window.
  151. */
  152. function history_count_user_recent_events( $p_duration_in_seconds, $p_user_id = null ) {
  153. $t_user_id = ( ( null === $p_user_id ) ? auth_get_current_user_id() : $p_user_id );
  154. $t_params = array( db_now() - $p_duration_in_seconds, $t_user_id );
  155. db_param_push();
  156. $t_query = 'SELECT count(*) as event_count FROM {bug_history} WHERE date_modified > ' . db_param() .
  157. ' AND user_id = ' . db_param();
  158. $t_result = db_query( $t_query, $t_params );
  159. $t_row = db_fetch_array( $t_result );
  160. return $t_row['event_count'];
  161. }
  162. /**
  163. * Creates and executes a query for the history rows related to bugs matched by the provided filter
  164. * @param array $p_filter Filter array
  165. * @param integer $p_start_time The start time to filter by, or null for all.
  166. * @param integer $p_end_time The end time to filter by, or null for all.
  167. * @param string $p_history_order The sort order.
  168. * @return database result to pass into history_get_event_from_row().
  169. */
  170. function history_get_range_result_filter( $p_filter, $p_start_time = null, $p_end_time = null, $p_history_order = null ) {
  171. if ( $p_history_order === null ) {
  172. $t_history_order = config_get( 'history_order' );
  173. } else {
  174. $t_history_order = $p_history_order;
  175. }
  176. # Note: filter_get_bug_rows_query_clauses() calls db_param_push();
  177. $t_query_clauses = filter_get_bug_rows_query_clauses( $p_filter, null, null, null );
  178. # if the query can't be formed, there are no results
  179. if( empty( $t_query_clauses ) ) {
  180. # reset the db_param stack that was initialized by "filter_get_bug_rows_query_clauses()"
  181. db_param_pop();
  182. return db_empty_result();
  183. }
  184. $t_select_string = 'SELECT DISTINCT {bug}.id ';
  185. $t_from_string = ' FROM ' . implode( ', ', $t_query_clauses['from'] );
  186. $t_join_string = count( $t_query_clauses['join'] ) > 0 ? implode( ' ', $t_query_clauses['join'] ) : ' ';
  187. $t_where_string = ' WHERE '. implode( ' AND ', $t_query_clauses['project_where'] );
  188. if( count( $t_query_clauses['where'] ) > 0 ) {
  189. $t_where_string .= ' AND ( ';
  190. $t_where_string .= implode( $t_query_clauses['operator'], $t_query_clauses['where'] );
  191. $t_where_string .= ' ) ';
  192. }
  193. $t_query = 'SELECT * FROM {bug_history} JOIN'
  194. . ' ( ' . $t_select_string . $t_from_string . $t_join_string . $t_where_string . ' ) B'
  195. . ' ON {bug_history}.bug_id=B.id';
  196. $t_params = $t_query_clauses['where_values'];
  197. $t_where = array();
  198. if ( $p_start_time !== null ) {
  199. $t_where[] = 'date_modified >= ' . db_param();
  200. $t_params[] = $p_start_time;
  201. }
  202. if ( $p_end_time !== null ) {
  203. $t_where[] = 'date_modified < ' . db_param();
  204. $t_params[] = $p_end_time;
  205. }
  206. if ( count( $t_where ) > 0 ) {
  207. $t_query .= ' WHERE ' . implode( ' AND ', $t_where );
  208. }
  209. $t_query .= ' ORDER BY {bug_history}.date_modified ' . $t_history_order . ', {bug_history}.id ' . $t_history_order;
  210. $t_result = db_query( $t_query, $t_params );
  211. return $t_result;
  212. }
  213. /**
  214. * Creates and executes a query for the history rows matching the specified criteria.
  215. * @param integer $p_bug_id The bug id or null for matching any bug.
  216. * @param integer $p_start_time The start time to filter by, or null for all.
  217. * @param integer $p_end_time The end time to filter by, or null for all.
  218. * @param string $p_history_order The sort order.
  219. * @return IteratorAggregate|boolean database result to pass into history_get_event_from_row().
  220. */
  221. function history_get_range_result( $p_bug_id = null, $p_start_time = null, $p_end_time = null, $p_history_order = null ) {
  222. if ( $p_history_order === null ) {
  223. $t_history_order = config_get( 'history_order' );
  224. } else {
  225. $t_history_order = $p_history_order;
  226. }
  227. db_param_push();
  228. $t_query = 'SELECT * FROM {bug_history}';
  229. $t_params = array();
  230. $t_where = array();
  231. if ( $p_bug_id !== null ) {
  232. $t_where[] = 'bug_id=' . db_param();
  233. $t_params = array( $p_bug_id );
  234. }
  235. if ( $p_start_time !== null ) {
  236. $t_where[] = 'date_modified >= ' . db_param();
  237. $t_params[] = $p_start_time;
  238. }
  239. if ( $p_end_time !== null ) {
  240. $t_where[] = 'date_modified < ' . db_param();
  241. $t_params[] = $p_end_time;
  242. }
  243. if ( count( $t_where ) > 0 ) {
  244. $t_query .= ' WHERE ' . implode( ' AND ', $t_where );
  245. }
  246. $t_query .= ' ORDER BY date_modified ' . $t_history_order . ',id ' . $t_history_order;
  247. $t_result = db_query( $t_query, $t_params );
  248. return $t_result;
  249. }
  250. /**
  251. * Gets the next accessible history event for current user and specified db result.
  252. * @param object $p_result The database result.
  253. * @param integer $p_user_id The user id or null for logged in user.
  254. * @param boolean $p_check_access_to_issue true: check that user has access to bugs,
  255. * false otherwise.
  256. * @return array containing the history event or false if no more matches.
  257. */
  258. function history_get_event_from_row( $p_result, $p_user_id = null, $p_check_access_to_issue = true ) {
  259. static $s_bug_visible = array();
  260. $t_user_id = ( null === $p_user_id ) ? auth_get_current_user_id() : $p_user_id;
  261. while ( $t_row = db_fetch_array( $p_result ) ) {
  262. extract( $t_row, EXTR_PREFIX_ALL, 'v' );
  263. # Ignore entries related to non-existing bugs (see #20727)
  264. if( !bug_exists( $v_bug_id ) ) {
  265. continue;
  266. }
  267. if( $p_check_access_to_issue ) {
  268. if( !isset( $s_bug_visible[$v_bug_id] ) ) {
  269. $s_bug_visible[$v_bug_id] = access_has_bug_level( VIEWER, $v_bug_id );
  270. }
  271. if( !$s_bug_visible[$v_bug_id] ) {
  272. continue;
  273. }
  274. }
  275. if( $v_type == NORMAL_TYPE ) {
  276. if( !in_array( $v_field_name, columns_get_standard() ) ) {
  277. # check that the item should be visible to the user
  278. $t_field_id = custom_field_get_id_from_name( $v_field_name );
  279. if( false !== $t_field_id && !custom_field_has_read_access( $t_field_id, $v_bug_id, $t_user_id ) ) {
  280. continue;
  281. }
  282. }
  283. if( ( $v_field_name == 'target_version' ) && !access_has_bug_level( config_get( 'roadmap_view_threshold' ), $v_bug_id, $t_user_id ) ) {
  284. continue;
  285. }
  286. if( ( $v_field_name == 'due_date' ) && !access_has_bug_level( config_get( 'due_date_view_threshold' ), $v_bug_id, $t_user_id ) ) {
  287. continue;
  288. }
  289. if( ( $v_field_name == 'handler_id' ) && !access_has_bug_level( config_get( 'view_handler_threshold' ), $v_bug_id, $t_user_id ) ) {
  290. continue;
  291. }
  292. }
  293. # bugnotes
  294. if( $t_user_id != $v_user_id ) {
  295. # bypass if user originated note
  296. if( ( $v_type == BUGNOTE_ADDED ) || ( $v_type == BUGNOTE_UPDATED ) || ( $v_type == BUGNOTE_DELETED ) ) {
  297. if( !access_has_bug_level( config_get( 'private_bugnote_threshold' ), $v_bug_id, $t_user_id ) && ( bugnote_get_field( $v_old_value, 'view_state' ) == VS_PRIVATE ) ) {
  298. continue;
  299. }
  300. }
  301. if( $v_type == BUGNOTE_STATE_CHANGED ) {
  302. if( !access_has_bug_level( config_get( 'private_bugnote_threshold' ), $v_bug_id, $t_user_id ) && ( bugnote_get_field( $v_new_value, 'view_state' ) == VS_PRIVATE ) ) {
  303. continue;
  304. }
  305. }
  306. }
  307. # tags
  308. if( $v_type == TAG_ATTACHED || $v_type == TAG_DETACHED || $v_type == TAG_RENAMED ) {
  309. if( !access_has_bug_level( config_get( 'tag_view_threshold' ), $v_bug_id, $t_user_id ) ) {
  310. continue;
  311. }
  312. }
  313. # attachments
  314. if( $v_type == FILE_ADDED || $v_type == FILE_DELETED ) {
  315. if( !access_has_bug_level( config_get( 'view_attachments_threshold' ), $v_bug_id, $t_user_id ) ) {
  316. continue;
  317. }
  318. }
  319. # monitoring
  320. if( $v_type == BUG_MONITOR || $v_type == BUG_UNMONITOR ) {
  321. if( !access_has_bug_level( config_get( 'show_monitor_list_threshold' ), $v_bug_id, $t_user_id ) ) {
  322. continue;
  323. }
  324. }
  325. # relationships
  326. if( $v_type == BUG_ADD_RELATIONSHIP || $v_type == BUG_DEL_RELATIONSHIP || $v_type == BUG_REPLACE_RELATIONSHIP ) {
  327. $t_related_bug_id = $v_new_value;
  328. # If bug doesn't exist, then we don't know whether to expose it or not based on the fact whether it was
  329. # accessible to user or not. This also simplifies client code that is accessing the history log.
  330. if( !bug_exists( $t_related_bug_id ) || !access_has_bug_level( config_get( 'view_bug_threshold' ), $t_related_bug_id, $t_user_id ) ) {
  331. continue;
  332. }
  333. }
  334. $t_event = array();
  335. $t_event['bug_id'] = $v_bug_id;
  336. $t_event['date'] = $v_date_modified;
  337. $t_event['userid'] = $v_user_id;
  338. # user_get_name handles deleted users, and username vs realname
  339. $t_event['username'] = user_get_name( $v_user_id );
  340. $t_event['field'] = $v_field_name;
  341. $t_event['type'] = $v_type;
  342. $t_event['old_value'] = $v_old_value;
  343. $t_event['new_value'] = $v_new_value;
  344. return $t_event;
  345. }
  346. return false;
  347. }
  348. /**
  349. * Retrieves the raw history events for the specified bug id and returns it in an array
  350. * The array is indexed from 0 to N-1. The second dimension is: 'date', 'userid', 'username',
  351. * 'field','type','old_value','new_value'
  352. * @param integer $p_bug_id A valid bug identifier or null to not filter by bug. If no bug id is specified,
  353. * then returned array will have a field for bug_id, otherwise it won't.
  354. * @param integer $p_user_id A valid user identifier.
  355. * @param integer $p_start_time The start time to filter by, or null for all.
  356. * @param integer $p_end_time The end time to filter by, or null for all.
  357. * @return array
  358. */
  359. function history_get_raw_events_array( $p_bug_id, $p_user_id = null, $p_start_time = null, $p_end_time = null ) {
  360. $t_user_id = (( null === $p_user_id ) ? auth_get_current_user_id() : $p_user_id );
  361. # grab history and display by date_modified then field_name
  362. # @@@ by MASC I guess it's better by id then by field_name. When we have more history lines with the same
  363. # date, it's better to respect the storing order otherwise we should risk to mix different information
  364. # I give you an example. We create a child of a bug with different custom fields. In the history of the child
  365. # bug we will find the line related to the relationship mixed with the custom fields (the history is creted
  366. # for the new bug with the same timestamp...)
  367. $t_result = history_get_range_result( $p_bug_id, $p_start_time, $p_end_time );
  368. $t_raw_history = array();
  369. $j = 0;
  370. while( true ) {
  371. $t_event = history_get_event_from_row( $t_result, $t_user_id, /* check access */ true );
  372. if ( $t_event === false ) {
  373. break;
  374. }
  375. $t_raw_history[$j] = $t_event;
  376. $j++;
  377. }
  378. # end for loop
  379. return $t_raw_history;
  380. }
  381. /**
  382. * Localizes one raw history item specified by set the next parameters: $p_field_name, $p_type, $p_old_value, $p_new_value
  383. * Returns array with two elements indexed as 'note' and 'change'
  384. * @param string $p_field_name The field name of the field being localized.
  385. * @param integer $p_type The type of the history entry.
  386. * @param string $p_old_value The old value of the field.
  387. * @param string $p_new_value The new value of the field.
  388. * @param boolean $p_linkify Whether to return a string containing hyperlinks.
  389. * @return array
  390. */
  391. function history_localize_item( $p_field_name, $p_type, $p_old_value, $p_new_value, $p_linkify = true ) {
  392. $t_note = '';
  393. $t_change = '';
  394. $t_field_localized = $p_field_name;
  395. $t_raw = true;
  396. if( PLUGIN_HISTORY == $p_type ) {
  397. $t_note = lang_get_defaulted( 'plugin_' . $p_field_name, $p_field_name );
  398. $t_change = ( isset( $p_new_value ) ? $p_old_value . ' => ' . $p_new_value : $p_old_value );
  399. return array( 'note' => $t_note, 'change' => $t_change, 'raw' => true );
  400. }
  401. switch( $p_field_name ) {
  402. case 'category':
  403. $t_field_localized = lang_get( 'category' );
  404. break;
  405. case 'status':
  406. $p_old_value = get_enum_element( 'status', $p_old_value );
  407. $p_new_value = get_enum_element( 'status', $p_new_value );
  408. $t_field_localized = lang_get( 'status' );
  409. break;
  410. case 'severity':
  411. $p_old_value = get_enum_element( 'severity', $p_old_value );
  412. $p_new_value = get_enum_element( 'severity', $p_new_value );
  413. $t_field_localized = lang_get( 'severity' );
  414. break;
  415. case 'reproducibility':
  416. $p_old_value = get_enum_element( 'reproducibility', $p_old_value );
  417. $p_new_value = get_enum_element( 'reproducibility', $p_new_value );
  418. $t_field_localized = lang_get( 'reproducibility' );
  419. break;
  420. case 'resolution':
  421. $p_old_value = get_enum_element( 'resolution', $p_old_value );
  422. $p_new_value = get_enum_element( 'resolution', $p_new_value );
  423. $t_field_localized = lang_get( 'resolution' );
  424. break;
  425. case 'priority':
  426. $p_old_value = get_enum_element( 'priority', $p_old_value );
  427. $p_new_value = get_enum_element( 'priority', $p_new_value );
  428. $t_field_localized = lang_get( 'priority' );
  429. break;
  430. case 'eta':
  431. $p_old_value = get_enum_element( 'eta', $p_old_value );
  432. $p_new_value = get_enum_element( 'eta', $p_new_value );
  433. $t_field_localized = lang_get( 'eta' );
  434. break;
  435. case 'view_state':
  436. $p_old_value = get_enum_element( 'view_state', $p_old_value );
  437. $p_new_value = get_enum_element( 'view_state', $p_new_value );
  438. $t_field_localized = lang_get( 'view_status' );
  439. break;
  440. case 'projection':
  441. $p_old_value = get_enum_element( 'projection', $p_old_value );
  442. $p_new_value = get_enum_element( 'projection', $p_new_value );
  443. $t_field_localized = lang_get( 'projection' );
  444. break;
  445. case 'sticky':
  446. $p_old_value = gpc_string_to_bool( $p_old_value ) ? lang_get( 'yes' ) : lang_get( 'no' );
  447. $p_new_value = gpc_string_to_bool( $p_new_value ) ? lang_get( 'yes' ) : lang_get( 'no' );
  448. $t_field_localized = lang_get( 'sticky_issue' );
  449. break;
  450. case 'project_id':
  451. if( project_exists( $p_old_value ) ) {
  452. $p_old_value = project_get_field( $p_old_value, 'name' );
  453. } else {
  454. $p_old_value = '@' . $p_old_value . '@';
  455. }
  456. # Note that the new value maybe an intermediately project and not the
  457. # current one.
  458. if( project_exists( $p_new_value ) ) {
  459. $p_new_value = project_get_field( $p_new_value, 'name' );
  460. } else {
  461. $p_new_value = '@' . $p_new_value . '@';
  462. }
  463. $t_field_localized = lang_get( 'email_project' );
  464. break;
  465. case 'handler_id':
  466. $t_field_localized = lang_get( 'assigned_to' );
  467. case 'reporter_id':
  468. if( 'reporter_id' == $p_field_name ) {
  469. $t_field_localized = lang_get( 'reporter' );
  470. }
  471. if( 0 == $p_old_value ) {
  472. $p_old_value = '';
  473. } else {
  474. $p_old_value = user_get_name( $p_old_value );
  475. }
  476. if( 0 == $p_new_value ) {
  477. $p_new_value = '';
  478. } else {
  479. $p_new_value = user_get_name( $p_new_value );
  480. }
  481. break;
  482. case 'version':
  483. $t_field_localized = lang_get( 'product_version' );
  484. break;
  485. case 'fixed_in_version':
  486. $t_field_localized = lang_get( 'fixed_in_version' );
  487. break;
  488. case 'target_version':
  489. $t_field_localized = lang_get( 'target_version' );
  490. break;
  491. case 'date_submitted':
  492. $p_old_value = date( config_get( 'normal_date_format' ), $p_old_value );
  493. $p_new_value = date( config_get( 'normal_date_format' ), $p_new_value );
  494. $t_field_localized = lang_get( 'date_submitted' );
  495. break;
  496. case 'last_updated':
  497. $p_old_value = date( config_get( 'normal_date_format' ), $p_old_value );
  498. $p_new_value = date( config_get( 'normal_date_format' ), $p_new_value );
  499. $t_field_localized = lang_get( 'last_update' );
  500. break;
  501. case 'os':
  502. $t_field_localized = lang_get( 'os' );
  503. break;
  504. case 'os_build':
  505. $t_field_localized = lang_get( 'os_version' );
  506. break;
  507. case 'build':
  508. $t_field_localized = lang_get( 'build' );
  509. break;
  510. case 'platform':
  511. $t_field_localized = lang_get( 'platform' );
  512. break;
  513. case 'summary':
  514. $t_field_localized = lang_get( 'summary' );
  515. break;
  516. case 'duplicate_id':
  517. $t_field_localized = lang_get( 'duplicate_id' );
  518. break;
  519. case 'sponsorship_total':
  520. $t_field_localized = lang_get( 'sponsorship_total' );
  521. break;
  522. case 'due_date':
  523. if( $p_old_value !== '' ) {
  524. $p_old_value = date( config_get( 'normal_date_format' ), (int)$p_old_value );
  525. }
  526. if( $p_new_value !== '' ) {
  527. $p_new_value = date( config_get( 'normal_date_format' ), (int)$p_new_value );
  528. }
  529. $t_field_localized = lang_get( 'due_date' );
  530. break;
  531. default:
  532. # assume it's a custom field name
  533. $t_field_id = custom_field_get_id_from_name( $p_field_name );
  534. if( false !== $t_field_id ) {
  535. $t_cf_type = custom_field_type( $t_field_id );
  536. if( '' != $p_old_value ) {
  537. $p_old_value = string_custom_field_value_for_email( $p_old_value, $t_cf_type );
  538. }
  539. $p_new_value = string_custom_field_value_for_email( $p_new_value, $t_cf_type );
  540. $t_field_localized = lang_get_defaulted( $p_field_name );
  541. }
  542. }
  543. if( NORMAL_TYPE != $p_type ) {
  544. switch( $p_type ) {
  545. case NEW_BUG:
  546. $t_note = lang_get( 'new_bug' );
  547. break;
  548. case BUGNOTE_ADDED:
  549. $t_note = lang_get( 'bugnote_added' ) . ': ' . $p_old_value;
  550. break;
  551. case BUGNOTE_UPDATED:
  552. $t_note = lang_get( 'bugnote_edited' ) . ': ' . $p_old_value;
  553. $t_old_value = (int)$p_old_value;
  554. $t_new_value = (int)$p_new_value;
  555. if( $p_linkify && bug_revision_exists( $t_new_value ) ) {
  556. if( bugnote_exists( $t_old_value ) ) {
  557. $t_bug_revision_view_page_argument = 'bugnote_id=' . $t_old_value . '#r' . $t_new_value;
  558. } else {
  559. $t_bug_revision_view_page_argument = 'rev_id=' . $t_new_value;
  560. }
  561. $t_change = '<a href="bug_revision_view_page.php?' . $t_bug_revision_view_page_argument . '">' .
  562. lang_get( 'view_revisions' ) . '</a>';
  563. $t_raw = false;
  564. }
  565. break;
  566. case BUGNOTE_DELETED:
  567. $t_note = lang_get( 'bugnote_deleted' ) . ': ' . $p_old_value;
  568. break;
  569. case DESCRIPTION_UPDATED:
  570. $t_note = lang_get( 'description_updated' );
  571. $t_old_value = (int)$p_old_value;
  572. if( $p_linkify && bug_revision_exists( $t_old_value ) ) {
  573. $t_change = '<a href="bug_revision_view_page.php?rev_id=' . $t_old_value . '#r' . $t_old_value . '">' .
  574. lang_get( 'view_revisions' ) . '</a>';
  575. $t_raw = false;
  576. }
  577. break;
  578. case ADDITIONAL_INFO_UPDATED:
  579. $t_note = lang_get( 'additional_information_updated' );
  580. $t_old_value = (int)$p_old_value;
  581. if( $p_linkify && bug_revision_exists( $t_old_value ) ) {
  582. $t_change = '<a href="bug_revision_view_page.php?rev_id=' . $t_old_value . '#r' . $t_old_value . '">' .
  583. lang_get( 'view_revisions' ) . '</a>';
  584. $t_raw = false;
  585. }
  586. break;
  587. case STEP_TO_REPRODUCE_UPDATED:
  588. $t_note = lang_get( 'steps_to_reproduce_updated' );
  589. $t_old_value = (int)$p_old_value;
  590. if( $p_linkify && bug_revision_exists( $t_old_value ) ) {
  591. $t_change = '<a href="bug_revision_view_page.php?rev_id=' . $t_old_value . '#r' . $t_old_value . '">' .
  592. lang_get( 'view_revisions' ) . '</a>';
  593. $t_raw = false;
  594. }
  595. break;
  596. case FILE_ADDED:
  597. $t_note = lang_get( 'file_added' ) . ': ' . $p_old_value;
  598. break;
  599. case FILE_DELETED:
  600. $t_note = lang_get( 'file_deleted' ) . ': ' . $p_old_value;
  601. break;
  602. case BUGNOTE_STATE_CHANGED:
  603. $p_old_value = get_enum_element( 'view_state', $p_old_value );
  604. $t_note = lang_get( 'bugnote_view_state' ) . ': ' . $p_new_value . ': ' . $p_old_value;
  605. break;
  606. case BUG_MONITOR:
  607. $p_old_value = user_get_name( $p_old_value );
  608. $t_note = lang_get( 'bug_monitor' ) . ': ' . $p_old_value;
  609. break;
  610. case BUG_UNMONITOR:
  611. if( $p_old_value !== '' ) {
  612. $p_old_value = user_get_name( $p_old_value );
  613. }
  614. $t_note = lang_get( 'bug_end_monitor' ) . ': ' . $p_old_value;
  615. break;
  616. case BUG_DELETED:
  617. $t_note = lang_get( 'bug_deleted' ) . ': ' . $p_old_value;
  618. break;
  619. case BUG_ADD_SPONSORSHIP:
  620. $t_note = lang_get( 'sponsorship_added' );
  621. $t_change = user_get_name( $p_old_value ) . ': ' . sponsorship_format_amount( $p_new_value );
  622. break;
  623. case BUG_UPDATE_SPONSORSHIP:
  624. $t_note = lang_get( 'sponsorship_updated' );
  625. $t_change = user_get_name( $p_old_value ) . ': ' . sponsorship_format_amount( $p_new_value );
  626. break;
  627. case BUG_DELETE_SPONSORSHIP:
  628. $t_note = lang_get( 'sponsorship_deleted' );
  629. $t_change = user_get_name( $p_old_value ) . ': ' . sponsorship_format_amount( $p_new_value );
  630. break;
  631. case BUG_PAID_SPONSORSHIP:
  632. $t_note = lang_get( 'sponsorship_paid' );
  633. $t_change = user_get_name( $p_old_value ) . ': ' . get_enum_element( 'sponsorship', $p_new_value );
  634. break;
  635. case BUG_ADD_RELATIONSHIP:
  636. $t_note = lang_get( 'relationship_added' );
  637. $t_change = relationship_get_description_for_history( $p_old_value ) . ' ' . bug_format_id( $p_new_value );
  638. break;
  639. case BUG_REPLACE_RELATIONSHIP:
  640. $t_note = lang_get( 'relationship_replaced' );
  641. $t_change = relationship_get_description_for_history( $p_old_value ) . ' ' . bug_format_id( $p_new_value );
  642. break;
  643. case BUG_DEL_RELATIONSHIP:
  644. $t_note = lang_get( 'relationship_deleted' );
  645. # Fix for #7846: There are some cases where old value is empty, this may be due to an old bug.
  646. if( !is_blank( $p_old_value ) && $p_old_value > 0 ) {
  647. $t_change = relationship_get_description_for_history( $p_old_value ) . ' ' . bug_format_id( $p_new_value );
  648. } else {
  649. $t_change = bug_format_id( $p_new_value );
  650. }
  651. break;
  652. case BUG_CLONED_TO:
  653. $t_note = lang_get( 'bug_cloned_to' ) . ': ' . bug_format_id( $p_new_value );
  654. break;
  655. case BUG_CREATED_FROM:
  656. $t_note = lang_get( 'bug_created_from' ) . ': ' . bug_format_id( $p_new_value );
  657. break;
  658. case TAG_ATTACHED:
  659. $t_note = lang_get( 'tag_history_attached' ) . ': ' . $p_old_value;
  660. break;
  661. case TAG_DETACHED:
  662. $t_note = lang_get( 'tag_history_detached' ) . ': ' . $p_old_value;
  663. break;
  664. case TAG_RENAMED:
  665. $t_note = lang_get( 'tag_history_renamed' );
  666. $t_change = $p_old_value . ' => ' . $p_new_value;
  667. break;
  668. case BUG_REVISION_DROPPED:
  669. $t_note = lang_get( 'bug_revision_dropped_history' ) . ': ' . bug_revision_get_type_name( $p_new_value ) . ': ' . $p_old_value;
  670. break;
  671. case BUGNOTE_REVISION_DROPPED:
  672. $t_note = lang_get( 'bugnote_revision_dropped_history' ) . ': ' . $p_new_value . ': ' . $p_old_value;
  673. break;
  674. }
  675. }
  676. # output special cases
  677. if( NORMAL_TYPE == $p_type ) {
  678. $t_note = $t_field_localized;
  679. $t_change = $p_old_value . ' => ' . $p_new_value;
  680. }
  681. # end if DEFAULT
  682. return array( 'note' => $t_note, 'change' => $t_change, 'raw' => $t_raw );
  683. }
  684. /**
  685. * delete all history associated with a bug
  686. * @param integer $p_bug_id A valid bug identifier.
  687. * @return void
  688. */
  689. function history_delete( $p_bug_id ) {
  690. db_param_push();
  691. $t_query = 'DELETE FROM {bug_history} WHERE bug_id=' . db_param();
  692. db_query( $t_query, array( $p_bug_id ) );
  693. }