PageRenderTime 46ms CodeModel.GetById 15ms app.highlight 25ms RepoModel.GetById 0ms app.codeStats 1ms

/core/history_api.php

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