diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/admin/schema.php mantis-tagging/admin/schema.php
--- mantis-cvs/admin/schema.php	2007-08-07 10:54:56.000000000 -0400
+++ mantis-tagging/admin/schema.php	2007-08-17 15:19:22.000000000 -0400
@@ -330,6 +330,21 @@
 $upgrade[] = Array('CreateIndexSQL',Array('idx_diskfile',config_get('mantis_bug_file_table'),'diskfile'));
 $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_user_print_pref_table' ), "print_pref C(64) NOTNULL" ) );
 $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "field_name C(64) NOTNULL" ) );
+$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_tag_table' ), "
+	id				I		UNSIGNED NOTNULL PRIMARY AUTOINCREMENT,
+	user_id			I		UNSIGNED NOTNULL DEFAULT '0',
+	name			C(100)	NOTNULL DEFAULT \" '' \",
+	description		XL		NOTNULL,
+	date_created	T		NOTNULL DEFAULT '1970-01-01 00:00:01',
+	date_updated	T		NOTNULL DEFAULT '1970-01-01 00:00:01'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
+$upgrade[] = Array('CreateIndexSQL', Array( 'idx_tag_name', config_get( 'mantis_tag_table' ), 'name', Array( 'UNIQUE' ) ) );
+$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_bug_tag_table' ), "
+	bug_id			I	UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	tag_id			I	UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	user_id			I	UNSIGNED NOTNULL DEFAULT '0',
+	date_attached	T	NOTNULL DEFAULT '1970-01-01 00:00:01'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
 
 # Release marker: 1.1.0a4
 
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_advanced_page.php mantis-tagging/bug_view_advanced_page.php
--- mantis-cvs/bug_view_advanced_page.php	2007-08-07 10:47:14.000000000 -0400
+++ mantis-tagging/bug_view_advanced_page.php	2007-08-17 11:29:28.000000000 -0400
@@ -20,6 +20,7 @@
 	require_once( $t_core_path.'date_api.php' );
 	require_once( $t_core_path.'relationship_api.php' );
 	require_once( $t_core_path.'last_visited_api.php' );
+	require_once( $t_core_path.'tag_api.php' );
 
 	$f_bug_id		= gpc_get_int( 'bug_id' );
 	$f_history		= gpc_get_bool( 'history', config_get( 'history_default_visible' ) );
@@ -464,13 +465,35 @@
 	</td>
 </tr>
 
+<!-- Tagging -->
+<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?>
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tags' ) ?></td>
+	<td colspan="5">
+<?php
+	tag_display_attached( $f_bug_id );
+?>
+	</td>
+</tr>
+<?php } # has tag_view access ?>
+
+<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?>
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td>
+	<td colspan="5">
+<?php
+	print_tag_attach_form( $f_bug_id );
+?>
+	</td>
+</tr>
+<?php } # has tag attach access ?>
+
 
 <!-- spacer -->
 <tr class="spacer">
 	<td colspan="6"></td>
 </tr>
 
-
 <!-- Custom Fields -->
 <?php
 	$t_custom_fields_found = false;
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_page.php mantis-tagging/bug_view_page.php
--- mantis-cvs/bug_view_page.php	2007-08-07 10:47:14.000000000 -0400
+++ mantis-tagging/bug_view_page.php	2007-08-17 15:36:02.000000000 -0400
@@ -22,6 +22,7 @@
 	require_once( $t_core_path.'date_api.php' );
 	require_once( $t_core_path.'relationship_api.php' );
 	require_once( $t_core_path.'last_visited_api.php' );
+	require_once( $t_core_path.'tag_api.php' );
 ?>
 <?php
 	$f_bug_id	= gpc_get_int( 'bug_id' );
@@ -341,6 +342,29 @@
 	</td>
 </tr>
 
+<!-- Tagging -->
+<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?>
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tags' ) ?></td>
+	<td colspan="5">
+<?php
+	tag_display_attached( $f_bug_id );
+?>
+	</td>
+</tr>
+<?php } # has tag_view access ?>
+
+<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?>
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td>
+	<td colspan="5">
+<?php
+	print_tag_attach_form( $f_bug_id );
+?>
+	</td>
+</tr>
+<?php } # has tag attach access ?>
+
 
 <!-- spacer -->
 <tr class="spacer">
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/config_defaults_inc.php mantis-tagging/config_defaults_inc.php
--- mantis-cvs/config_defaults_inc.php	2007-08-16 13:23:56.000000000 -0400
+++ mantis-tagging/config_defaults_inc.php	2007-08-16 13:48:44.000000000 -0400
@@ -1331,6 +1331,7 @@
 	$g_mantis_bug_monitor_table				= '%db_table_prefix%_bug_monitor%db_table_suffix%';
 	$g_mantis_bug_relationship_table		= '%db_table_prefix%_bug_relationship%db_table_suffix%';
 	$g_mantis_bug_table						= '%db_table_prefix%_bug%db_table_suffix%';
+	$g_mantis_bug_tag_table					= '%db_table_prefix%_bug_tag%db_table_suffix%';
 	$g_mantis_bug_text_table				= '%db_table_prefix%_bug_text%db_table_suffix%';
 	$g_mantis_bugnote_table					= '%db_table_prefix%_bugnote%db_table_suffix%';
 	$g_mantis_bugnote_text_table			= '%db_table_prefix%_bugnote_text%db_table_suffix%';
@@ -1340,6 +1341,7 @@
 	$g_mantis_project_table					= '%db_table_prefix%_project%db_table_suffix%';
 	$g_mantis_project_user_list_table		= '%db_table_prefix%_project_user_list%db_table_suffix%';
 	$g_mantis_project_version_table			= '%db_table_prefix%_project_version%db_table_suffix%';
+	$g_mantis_tag_table						= '%db_table_prefix%_tag%db_table_suffix%';
 	$g_mantis_user_table					= '%db_table_prefix%_user%db_table_suffix%';
 	$g_mantis_user_profile_table			= '%db_table_prefix%_user_profile%db_table_suffix%';
 	$g_mantis_user_pref_table				= '%db_table_prefix%_user_pref%db_table_suffix%';
@@ -1835,6 +1837,34 @@
 	$g_recently_visited_count = 5;
 
 	#####################
+	# Bug Tagging
+	#####################
+
+	# String that will separate tags as entered for input
+	$g_tag_separator = ',';
+
+	# Access level required to view tags attached to a bug
+	$g_tag_view_threshold = VIEWER;
+
+	# Access level required to attach tags to a bug
+	$g_tag_attach_threshold = REPORTER;
+
+	# Access level required to detach tags from a bug
+	$g_tag_detach_threshold = DEVELOPER;
+
+	# Access level required to detach tags attached by the same user
+	$g_tag_detach_own_threshold = REPORTER;
+
+	# Access level required to create new tags
+	$g_tag_create_threshold = REPORTER;
+
+	# Access level required to edit tag names and descriptions
+	$g_tag_edit_threshold = DEVELOPER;
+
+	# Access level required to edit descriptions by the creating user
+	$g_tag_edit_own_threshold = REPORTER;
+
+	#####################
 	# Time tracking
 	#####################
 
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/constant_inc.php mantis-tagging/core/constant_inc.php
--- mantis-cvs/core/constant_inc.php	2007-08-07 10:47:13.000000000 -0400
+++ mantis-tagging/core/constant_inc.php	2007-08-16 13:48:43.000000000 -0400
@@ -153,6 +153,9 @@
 	define( 'CHECKIN',				22 );
 	define( 'BUG_REPLACE_RELATIONSHIP', 		23 );
 	define( 'BUG_PAID_SPONSORSHIP', 		24 );
+	define( 'TAG_ATTACHED', 				25 );
+	define( 'TAG_DETACHED', 				26 );
+	define( 'TAG_RENAMED', 					27 );
 
 	# bug relationship constants
 	define( 'BUG_DUPLICATE',	0 );
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/filter_api.php mantis-tagging/core/filter_api.php
--- mantis-cvs/core/filter_api.php	2007-08-07 10:47:13.000000000 -0400
+++ mantis-tagging/core/filter_api.php	2007-08-17 14:31:51.000000000 -0400
@@ -56,6 +56,7 @@
 	define( 'FILTER_PROPERTY_FILTER_BY_DATE', 'do_filter_by_date' );
 	define( 'FILTER_PROPERTY_RELATIONSHIP_TYPE', 'relationship_type' );
 	define( 'FILTER_PROPERTY_RELATIONSHIP_BUG', 'relationship_bug' );
+	define( 'FILTER_PROPERTY_TAG_STRING', 'tag_string' );
 
 	###########################################################################
 	# Filter Query Parameter Names
@@ -96,6 +97,7 @@
 	define( 'FILTER_SEARCH_FILTER_BY_DATE', 'filter_by_date' );
 	define( 'FILTER_SEARCH_RELATIONSHIP_TYPE', 'relationship_type' );
 	define( 'FILTER_SEARCH_RELATIONSHIP_BUG', 'relationship_bug' );
+	define( 'FILTER_SEARCH_TAG_STRING', 'tag_string' );
 
 	# Checks the supplied value to see if it is an ANY value.
 	# $p_field_value - The value to check.
@@ -306,6 +308,10 @@
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] );
 		}
 
+		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) {
+			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] );
+		}
+
 		if ( isset( $p_custom_filter['custom_fields'] ) ) {
 			foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) {
 				if ( !filter_str_field_is_any( $t_custom_field_values ) ) {
@@ -1057,6 +1063,59 @@
 			array_push( $t_where_clauses, '('. implode( ' OR ', $t_clauses ) .')' );
 		}
 
+		# tags
+		$c_tag_string = trim( $t_filter['tag_string'] );
+		if ( !is_blank( $c_tag_string ) ) {
+			require_once( $t_core_path . 'tag_api.php' );
+			$t_tags = tag_parse_filters( $c_tag_string );
+
+			if ( !count( $t_tags ) ) { break; }
+
+			$t_tags_all = array();
+			$t_tags_any = array();
+			$t_tags_none = array();
+
+			foreach( $t_tags as $t_tag_row ) {
+				switch ( $t_tag_row['filter'] ) {
+					case 1:
+						$t_tags_all[] = $t_tag_row;
+						break;
+					case 0:
+						$t_tags_any[] = $t_tag_row;
+						break;
+					case -1:
+						$t_tags_none[] = $t_tag_row;
+						break;
+				}
+			}
+
+			$t_bug_tag_table = config_get( 'mantis_bug_tag_table' );
+			
+			if ( count( $t_tags_all ) ) {
+				$t_clauses = array();
+				foreach ( $t_tags_all as $t_tag_row ) {
+					array_push( $t_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE $t_bug_tag_table.tag_id = $t_tag_row[id] )" );
+				}
+				array_push( $t_where_clauses, '('. implode( ' AND ', $t_clauses ) .')' );
+			}
+			
+			if ( count( $t_tags_any ) ) {
+				$t_clauses = array();
+				foreach ( $t_tags_any as $t_tag_row ) {
+					array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" );
+				}
+				array_push( $t_where_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' );
+			}
+			
+			if ( count( $t_tags_none ) ) {
+				$t_clauses = array();
+				foreach ( $t_tags_none as $t_tag_row ) {
+					array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" );
+				}
+				array_push( $t_where_clauses, "$t_bug_table.id NOT IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' );
+			}
+		}
+
 		# custom field filters
 		if( ON == config_get( 'filter_by_custom_fields' ) ) {
 			# custom field filtering
@@ -2291,6 +2350,7 @@
 				<a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a>
 			</td>
 			<td class="small-caption" valign="top" colspan="5">
+				<a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a>
 			</td>
 			<?php if ( $t_filter_cols > 8 ) {
 				echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '">&nbsp;</td>';
@@ -2312,7 +2372,12 @@
 					print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] );
 				?>
 			</td>
-			<td class="small-caption" colspan="5">
+			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5">
+				<?php PRINT $t_filter['tag_string'] ?>
+				<input type="hidden" name="tag_string" value="<?php echo $t_filter['tag_string'] ?>"/>
+				<?php
+						//print_tag_input();
+				?>
 			</td>
 		</tr>
 		<?php
@@ -3025,6 +3090,9 @@
 		if ( !isset( $p_filter_arr['target_version'] ) ) {
 			$p_filter_arr['target_version'] = META_FILTER_ANY;
 		}
+		if ( !isset( $p_filter_arr['tag_string'] ) ) {
+			$p_filter_arr['tag_string'] = gpc_get_string( 'tag_string', '' );
+		}
 
 		$t_custom_fields 		= custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project
 		$f_custom_fields_data 	= array();
@@ -3531,6 +3599,17 @@
 
 	}
 
+	function print_filter_tag_string() {
+		global $t_filter;
+		?>
+		<input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" />
+		<input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $t_filter['tag_string'] ?>" />
+		<select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select">
+			<?php print_tag_option_list( $p_bug_id ); ?>
+		</select>
+		<?php
+	}
+
 	function print_filter_custom_field($p_field_id){
 		global $t_filter, $t_accessible_custom_fields_names, $t_accessible_custom_fields_types, $t_accessible_custom_fields_values, $t_accessible_custom_fields_ids, $t_select_modifier;
 
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/history_api.php mantis-tagging/core/history_api.php
--- mantis-cvs/core/history_api.php	2007-08-07 10:47:13.000000000 -0400
+++ mantis-tagging/core/history_api.php	2007-08-17 15:41:19.000000000 -0400
@@ -165,6 +165,15 @@
 				}
 			}
 
+			// tags
+			if ( $v_type == TAG_ATTACHED ||
+				$v_type == TAG_DETACHED ||
+				$v_type == TAG_RENAMED ) {
+				if ( !access_has_global_level( config_get( 'tag_view_threshold' ) ) ) {
+					continue;
+				}
+			}
+
 			$raw_history[$j]['date']	= db_unixtimestamp( $v_date_modified );
 			$raw_history[$j]['userid']	= $v_user_id;
 
@@ -390,6 +399,16 @@
 				case CHECKIN:
 					$t_note = lang_get( 'checkin' );
 					break;
+				case TAG_ATTACHED:
+					$t_note = lang_get( 'tag_history_attached' ) .': '. $p_old_value;
+					break;
+				case TAG_DETACHED:
+					$t_note = lang_get( 'tag_history_detached' ) .': '. $p_old_value;
+					break;
+				case TAG_RENAMED:
+					$t_note = lang_get( 'tag_history_renamed' );
+					$t_change = $p_old_value . ' => ' . $p_new_value;
+					break;
 			}
 		}
 
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/html_api.php mantis-tagging/core/html_api.php
--- mantis-cvs/core/html_api.php	2007-08-07 10:47:13.000000000 -0400
+++ mantis-tagging/core/html_api.php	2007-08-17 11:56:56.000000000 -0400
@@ -1218,4 +1218,24 @@
 
 		echo '</tr></table>';
 	}
+
+	function html_button_tag_update( $p_tag_id ) {
+		if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) 
+			|| ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' )
+				&& access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) )
+		{
+			html_button( 'tag_update_page.php', lang_get( 'tag_update_button' ), array( 'tag_id' => $p_tag_id ) );
+		}
+	}
+
+	function html_button_tag_delete( $p_tag_id ) {
+		if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) {
+			html_button( 'tag_delete.php', lang_get( 'tag_delete_button' ), array( 'tag_id' => $p_tag_id ) );
+		}
+	}
+
+	function html_buttons_tag_view_page( $p_tag_id ) {
+		html_button_tag_update( $p_tag_id );
+		html_button_tag_delete( $p_tag_id );
+	}
 ?>
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/print_api.php mantis-tagging/core/print_api.php
--- mantis-cvs/core/print_api.php	2007-08-14 10:12:41.000000000 -0400
+++ mantis-tagging/core/print_api.php	2007-08-17 11:02:32.000000000 -0400
@@ -11,6 +11,7 @@
 
 	$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
 
+	require_once( $t_core_dir . 'ajax_api.php' );
 	require_once( $t_core_dir . 'current_user_api.php' );
 	require_once( $t_core_dir . 'string_api.php' );
 	require_once( $t_core_dir . 'prepare_api.php' );
@@ -255,6 +256,54 @@
 			PRINT "<option value=\"$t_duplicate_id\">".$t_duplicate_id."</option>";
 		}
 	}
+
+	function print_tag_attach_form( $p_bug_id, $p_string="" ) {
+		?>
+		<small><?php echo sprintf( lang_get( 'tag_separate_by' ), config_get('tag_separator') ) ?></small> 
+		<form method="post" action="tag_attach.php">
+		<input type="hidden" name="bug_id" value="<?php echo $p_bug_id ?>" />
+		<?php
+			print_tag_input( $p_bug_id, $p_string );
+		?>
+		<input type="submit" value="<?php echo lang_get( 'tag_attach' ) ?>" class="button" />
+		</form>
+		<?php
+		return true;
+	}
+
+	function print_tag_input( $p_bug_id = 0, $p_string="" ) {
+		?>
+		<input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" />
+		<input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $p_string ?>" />
+		<select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select">
+			<?php print_tag_option_list( $p_bug_id ); ?>
+		</select>
+		<?php
+
+		return true;
+	}
+
+	function print_tag_option_list( $p_bug_id = 0 ) {
+		$t_tag_table = config_get( 'mantis_tag_table' );
+
+		$query = "SELECT id, name FROM $t_tag_table ";
+		if ( 0 != $p_bug_id ) {
+			$c_bug_id = db_prepare_int( $p_bug_id );
+			$t_bug_tag_table = config_get( 'mantis_bug_tag_table' );
+			
+			$query .= "	WHERE id NOT IN ( 
+						SELECT tag_id FROM $t_bug_tag_table WHERE bug_id='$c_bug_id' ) ";
+		}
+
+		$query .= " ORDER BY name ASC ";
+		$result = db_query( $query );
+
+		echo '<option value="0">',lang_get( 'tag_existing' ),'</option>';
+		while ( $row = db_fetch_array( $result ) ) {
+			echo '<option value="',$row['id'],'" onclick="tag_string_append(\'',$row['name'],'\')">',$row['name'],'</option>';
+		}
+	}
+
 	# --------------------
 	# Get current headlines and id  prefix with v_
 	function print_news_item_option_list() {
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/tag_api.php mantis-tagging/core/tag_api.php
--- mantis-cvs/core/tag_api.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/core/tag_api.php	2007-08-17 17:01:13.000000000 -0400
@@ -0,0 +1,474 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: tag_api.php,v 1.3 2007/04/20 08:28:23 vboctor Exp $
+	# --------------------------------------------------------
+
+	$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
+
+	require_once( $t_core_dir . 'bug_api.php' );
+	require_once( $t_core_dir . 'history_api.php' );
+	
+	### Tag API ###
+
+	function tag_exists( $p_tag_id ) {
+		$c_tag_id = db_prepare_int( $p_tag_id );
+		$t_tag_table = config_get( 'mantis_tag_table' );
+
+		$query = "SELECT * FROM $t_tag_table WHERE id='$c_tag_id'";
+		$result = db_query( $query ) ;
+
+		return db_num_rows( $result ) > 0;
+	}
+
+	function tag_ensure_exists( $p_tag_id ) {
+		if ( !tag_exists( $p_tag_id ) ) {
+			error_parameters( $p_tag_id );
+			trigger_error( ERROR_TAG_NOT_FOUND, ERROR );
+		}
+	}
+
+	function tag_is_unique( $p_name ) {
+		$c_name = trim( db_prepare_string( $p_name ) );
+		$t_tag_table = config_get( 'mantis_tag_table' );
+
+		$query = "SELECT id FROM $t_tag_table WHERE name like '$c_name'";
+		$result = db_query( $query ) ;
+
+		return db_num_rows( $result ) == 0;
+	}
+
+	function tag_ensure_unique( $p_name ) {
+		if ( !tag_is_unique( $p_name ) ) {
+			trigger_error( ERROR_TAG_DUPLICATE, ERROR );
+		}
+	}
+
+	# Name must start with letter/number and consist of letters, numbers, hyphen, underscore, period, or spaces
+	function tag_name_is_valid( $p_name, $p_prefix="", &$p_matches=null ) {
+		$t_pattern = "/^$p_prefix([a-zA-Z0-9][a-zA-Z0-9-_. ]*)$/";
+		return preg_match( $t_pattern, $p_name, $p_matches );
+	}
+
+	function tag_ensure_name_is_valid( $p_name ) {
+		if ( !tag_name_is_valid( $p_name ) ) {
+			trigger_error( ERROR_TAG_NAME_INVALID, ERROR );
+		}
+	}
+
+	function tag_cmp_name( $p_tag1, $p_tag2 ) {
+		return strcasecmp( $p_tag1['name'], $p_tag2['name'] );
+	}
+
+	function tag_parse_string( $p_string ) {
+		$t_tags = array();
+
+		$t_strings = explode( config_get( 'tag_separator' ), $p_string );
+		foreach( $t_strings as $t_name ) {
+			$t_name = trim( $t_name );
+			if ( "" == trim( $t_name ) ) { continue; }
+			
+			$t_tag_row = tag_get_by_name( $t_name );
+			if ( $t_tag_row !== false ) {
+				$t_tags[] = $t_tag_row;
+			} else {
+				if ( tag_name_is_valid( $t_name ) ) {
+					$t_id = -1;
+				} else {
+					$t_id = -2;
+				}
+				$t_tags[] = array( 'id' => $t_id, 'name' => $t_name );
+			}
+		}
+		usort( $t_tags, "tag_cmp_name" );
+		return $t_tags;
+	}
+
+	function tag_parse_filters( $p_string ) {
+		$t_tags = array();
+		$t_prefix = "[+-]{0,1}";
+
+		$t_strings = explode( config_get( 'tag_separator' ), $p_string );
+		foreach( $t_strings as $t_name ) {
+			$t_name = trim( $t_name );
+			if ( "" == trim( $t_name ) || !tag_name_is_valid( $t_name, $t_prefix ) ) { continue; }
+			
+			$t_matches = array();
+			if ( tag_name_is_valid( $t_name, $t_prefix, $t_matches ) ) {
+				$t_tag_row = tag_get_by_name( $t_matches[1] );
+				if ( $t_tag_row !== false ) {
+					$t_filter = substr( $t_name, 0, 1 );
+
+					if ( "+" == $t_filter ) {
+						$t_tag_row['filter'] = 1;
+					} elseif ( "-" == $t_filter ) {
+						$t_tag_row['filter'] = -1;
+					} else {
+						$t_tag_row['filter'] = 0;
+					}
+
+					$t_tags[] = $t_tag_row;
+				}
+			}
+		}
+		usort( $t_tags, "tag_cmp_name" );
+		return $t_tags;
+	}
+
+	# CRUD
+
+	function tag_get( $p_tag_id ) {
+		tag_ensure_exists( $p_tag_id );
+
+		$c_tag_id		= db_prepare_int( $p_tag_id );
+		
+		$t_tag_table	= config_get( 'mantis_tag_table' );
+
+		$query = "SELECT * FROM $t_tag_table
+					WHERE id='$c_tag_id'";
+		$result = db_query( $query );
+
+		if ( 0 == db_num_rows( $result ) ) {
+			return false;
+		}
+		$row = db_fetch_array( $result );
+
+		return $row;
+	}
+
+	function tag_get_by_name( $p_name ) {
+		$c_name 		= db_prepare_string( $p_name );
+
+		$t_tag_table	= config_get( 'mantis_tag_table' );
+
+		$query = "SELECT * FROM $t_tag_table
+					WHERE name LIKE '$c_name'";
+		$result = db_query( $query );
+
+		if ( 0 == db_num_rows( $result ) ) {
+			return false;
+		}
+		$row = db_fetch_array( $result );
+
+		return $row;
+	}
+
+	function tag_get_field( $p_tag_id, $p_field_name ) {
+		$row = tag_get( $p_tag_id );
+
+		if ( isset( $row[$p_field_name] ) ) {
+			return $row[$p_field_name];
+		} else {
+			error_parameters( $p_field_name );
+			trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
+			return '';
+		}
+	}
+
+	function tag_create( $p_name, $p_user_id, $p_description='' ) {
+		
+		tag_ensure_name_is_valid( $p_name );
+		tag_ensure_unique( $p_name );
+
+		$c_name			= trim( db_prepare_string( $p_name ) );
+		$c_description	= db_prepare_string( $p_description );
+		$c_user_id		= db_prepare_int( $p_user_id );
+		$c_date_created	= db_now();
+		
+		$t_tag_table	= config_get( 'mantis_tag_table' );
+
+		$query = "INSERT INTO $t_tag_table
+				( user_id, 	
+				  name, 
+				  description, 
+				  date_created, 
+				  date_updated 
+				)
+				VALUES
+				( '$c_user_id', 
+				  '$c_name', 
+				  '$c_description', 
+				  ".$c_date_created.", 
+				  ".$c_date_created."
+				)";
+
+		db_query( $query );
+		return db_insert_id( $t_tag_table );
+	}
+
+	function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) {
+		tag_ensure_exists( $p_tag_id );
+		user_ensure_exists( $p_user_id );
+		
+		tag_ensure_name_is_valid( $p_name );
+
+		$t_tag_name = tag_get_field( $p_tag_id, 'name' );
+
+		$t_rename = false;
+		if ( strtolower($p_name) != strtolower($t_tag_name) ) {
+			tag_ensure_unique( $p_name );
+			$t_rename = true;
+		}
+		
+		$c_tag_id		= trim( db_prepare_int( $p_tag_id ) );
+		$c_user_id		= db_prepare_string( $p_user_id );
+		$c_name			= db_prepare_string( $p_name );
+		$c_description	= db_prepare_string( $p_description );
+		$c_date_updated	= db_now();
+
+		$t_tag_table	= config_get( 'mantis_tag_table' );
+
+		$query = "UPDATE $t_tag_table
+					SET user_id='$c_user_id',
+						name='$c_name',
+						description='$c_description',
+						date_updated=".$c_date_updated."
+					WHERE id='$c_tag_id'";
+		db_query( $query );
+
+		if ( $t_rename ) {
+			$t_bugs = tag_get_bugs_attached( $p_tag_id );
+
+			foreach ( $t_bugs as $t_bug_id ) {
+				history_log_event_special( $t_bug_id, TAG_RENAMED, $t_tag_name, $c_name );
+			}
+		}
+
+		return true;
+	}
+
+	function tag_delete( $p_tag_id ) {
+		tag_ensure_exists( $p_tag_id );
+		
+		$t_bugs = tag_get_bugs_attached( $p_tag_id );
+		foreach ( $t_bugs as $t_bug_id ) {
+			tag_bug_detach( $p_tag_id, $t_bug_id );
+		}
+		
+		$c_tag_id			= db_prepare_int( $p_tag_id );
+
+		$t_tag_table		= config_get( 'mantis_tag_table' );
+		$t_bug_tag_table	= config_get( 'mantis_bug_tag_table' );
+
+		$query = "DELETE FROM $t_tag_table
+					WHERE id='$c_tag_id'";
+		db_query( $query );
+
+		return true;
+	}
+	
+	# Associative
+
+	function tag_bug_is_attached( $p_tag_id, $p_bug_id ) {
+		$c_tag_id 		= db_prepare_int( $p_tag_id );
+		$c_bug_id 		= db_prepare_int( $p_bug_id );
+
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "SELECT * FROM $t_bug_tag_table
+					WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'";
+		$result = db_query( $query );
+		return ( db_num_rows( $result ) > 0 );
+	}
+
+	function tag_bug_get_row( $p_tag_id, $p_bug_id ) {
+		$c_tag_id 		= db_prepare_int( $p_tag_id );
+		$c_bug_id 		= db_prepare_int( $p_bug_id );
+
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "SELECT * FROM $t_bug_tag_table
+					WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'";
+		$result = db_query( $query );
+
+		if ( db_num_rows( $result ) == 0 ) {
+			trigger_error( TAG_NOT_ATTACHED, ERROR );
+		}
+		return db_fetch_array( $result );
+	}
+
+	function tag_bug_get_attached( $p_bug_id ) {
+		$c_bug_id 		= db_prepare_int( $p_bug_id );
+
+		$t_tag_table	= config_get( 'mantis_tag_table' );
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "SELECT t.*, b.user_id as user_attached, b.date_attached
+					FROM $t_tag_table as t
+					LEFT JOIN $t_bug_tag_table as b
+						on t.id=b.tag_id
+					WHERE b.bug_id='$c_bug_id'";
+		$result = db_query( $query );
+
+		$rows = array();
+		while ( $row = db_fetch_array( $result ) ) {
+			$rows[] = $row;
+		}
+		
+		usort( $rows, "tag_cmp_name" );
+		return $rows;
+	}
+
+	function tag_get_bugs_attached( $p_tag_id ) {
+		$c_tag_id 		= db_prepare_int( $p_tag_id );
+
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "SELECT bug_id FROM $t_bug_tag_table
+					WHERE tag_id='$c_tag_id'";
+		$result = db_query( $query );
+
+		$bugs = array();
+		while ( $row = db_fetch_array( $result ) ) {
+			$bugs[] = $row['bug_id'];
+		}
+
+		return $bugs;
+	}
+
+	function tag_bug_attach( $p_tag_id, $p_bug_id, $p_user_id ) {
+		tag_ensure_exists( $p_tag_id );
+		bug_ensure_exists( $p_bug_id );
+		user_ensure_exists( $p_user_id );
+		
+		if ( tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) {
+			trigger_error( TAG_ALREADY_ATTACHED, ERROR );
+		}
+
+		$c_tag_id 		= db_prepare_int( $p_tag_id );
+		$c_bug_id 		= db_prepare_int( $p_bug_id );
+		$c_user_id	 	= db_prepare_int( $p_user_id );
+		$c_date_attached= db_now();
+
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "INSERT INTO $t_bug_tag_table
+					( tag_id,
+					  bug_id,
+					  user_id,
+					  date_attached
+					)
+					VALUES
+					( '$c_tag_id',
+					  '$c_bug_id',
+					  '$c_user_id',
+					  ".$c_date_attached."
+					)";
+		db_query( $query );
+
+		$t_tag_name = tag_get_field( $p_tag_id, 'name' );
+		history_log_event_special( $p_bug_id, TAG_ATTACHED, $t_tag_name );
+
+		return true;
+	}
+
+	function tag_bug_detach( $p_tag_id, $p_bug_id ) {
+		tag_ensure_exists( $p_tag_id );
+		bug_ensure_exists( $p_bug_id );
+
+		if ( !tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) {
+			trigger_error( TAG_NOT_ATTACHED, ERROR );
+		}
+
+		$c_tag_id 		= db_prepare_int( $p_tag_id );
+		$c_bug_id 		= db_prepare_int( $p_bug_id );
+
+		$t_bug_tag_table= config_get( 'mantis_bug_tag_table' );
+
+		$query = "DELETE FROM $t_bug_tag_table 
+					WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'";
+		db_query( $query );
+
+		$t_tag_name = tag_get_field( $p_tag_id, 'name' );
+		history_log_event_special( $p_bug_id, TAG_DETACHED, $t_tag_name );
+
+		return true;
+	}
+
+	# Display
+
+	function tag_display_link( $p_tag_row, $p_bug_id=0 ) {
+		if ( auth_get_current_user_id() == $p_tag_row[user_attached] ) {
+			$t_detach = config_get( 'tag_detach_own_threshold' );
+		} else {
+			$t_detach = config_get( 'tag_detach_threshold' );
+		}
+		
+		$t_name = string_display_line( $p_tag_row['name'] );
+		$t_description = string_display_line( $p_tag_row['description'] );
+		
+		echo "<a href='tag_view_page.php?tag_id=$p_tag_row[id]' title='$t_description'>$t_name</a>";
+		
+		if ( access_has_global_level($t_detach) ) {
+			$t_tooltip = sprintf( lang_get( 'tag_detach' ), $t_name );
+			echo " [<a href='tag_detach.php?bug_id=$p_bug_id&tag_id=$p_tag_row[id]' title='$t_tooltip'>x</a>]";
+		}
+		
+		return true;
+	}
+
+	function tag_display_attached( $p_bug_id ) {
+		$t_tag_rows = tag_bug_get_attached( $p_bug_id );
+
+		if ( count( $t_tag_rows ) == 0 ) {
+			echo lang_get( 'tag_none_attached' );
+		} else {
+			$i = 0;
+			foreach ( $t_tag_rows as $t_tag ) {
+				echo ( $i > 0 ? config_get('tag_separator')." " : "" );
+				tag_display_link( $t_tag, $p_bug_id );
+				$i++;
+			}
+		}
+
+		return true;
+	}
+
+	# Statistics
+
+	function tag_stats_attached( $p_tag_id ) {
+		$c_tag_id = db_prepare_int( $p_tag_id );
+		$t_bug_tag_table = config_get( 'mantis_bug_tag_table' );
+
+		$query = "SELECT bug_id FROM $t_bug_tag_table
+					WHERE tag_id='$c_tag_id'";
+		$result = db_query( $query );
+
+		return db_num_rows( $result );
+	}
+
+	function tag_stats_related( $p_tag_id, $p_limit=5 ) {
+		$t_tag_table = config_get( 'mantis_tag_table' );
+		$t_bug_tag_table = config_get( 'mantis_bug_tag_table' );
+		$c_tag_id = db_prepare_int( $p_tag_id );
+
+		$query = "SELECT * FROM $t_bug_tag_table
+					WHERE tag_id != $c_tag_id
+						AND bug_id IN ( SELECT bug_id FROM $t_bug_tag_table
+									WHERE tag_id=$c_tag_id ) ";
+		$result = db_query( $query );
+
+		$t_tag_counts = array();
+		while ( $row = db_fetch_array( $result ) ) {
+			$t_tag_counts[$row['tag_id']]++;
+		}
+
+		asort( $t_tag_counts );
+
+		$t_tags = array();
+		$i = 1;
+		foreach ( $t_tag_counts as $t_tag_id => $t_count ) {
+			$t_tag_row = tag_get($t_tag_id);
+			$t_tag_row['count'] = $t_count;
+			$t_tags[] = $t_tag_row;
+			$i++;
+			if ( $i > $p_limit ) { break; }
+		}
+
+		return $t_tags;
+	}
Binary files mantis-cvs/core/.tag_api.php.swp and mantis-tagging/core/.tag_api.php.swp differ
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/xmlhttprequest_api.php mantis-tagging/core/xmlhttprequest_api.php
--- mantis-cvs/core/xmlhttprequest_api.php	2007-08-07 10:47:13.000000000 -0400
+++ mantis-tagging/core/xmlhttprequest_api.php	2007-08-16 13:48:44.000000000 -0400
@@ -31,6 +31,15 @@
 		echo '</select>';
 	}
 
+	function xmlhttprequest_user_combobox() {
+		$f_user_id = gpc_get_int( 'user_id' );
+		$f_user_access = gpc_get_int( 'access_level' );
+		
+		echo '<select name="user_id">';
+		print_user_option_list( $f_user_id, ALL_PROJECTS, $f_user_access );
+		echo '</select>';
+	}
+
 	# ---------------
 	# Echos a serialized list of platforms starting with the prefix specified in the $_POST
 	function xmlhttprequest_platform_get_with_prefix() {
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/javascript/common.js mantis-tagging/javascript/common.js
--- mantis-cvs/javascript/common.js	2007-08-14 10:12:43.000000000 -0400
+++ mantis-tagging/javascript/common.js	2007-08-16 14:57:40.000000000 -0400
@@ -163,3 +163,16 @@
   setDisplay( idTag, (document.getElementById(idTag).style.display == 'none')?1:0 );
 }
 
+/* Tag functionality */
+function tag_string_append( p_string ) {
+	t_tag_separator = document.getElementById('tag_separator').value;
+	t_tag_string = document.getElementById('tag_string');
+	t_tag_select = document.getElementById('tag_select');
+	if ( t_tag_string.value != '' ) {
+		t_tag_string.value = t_tag_string.value + t_tag_separator + p_string;
+	} else {
+		t_tag_string.value = t_tag_string.value + p_string;
+	}
+	t_tag_select.selectedIndex=0;
+}
+
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/lang/strings_english.txt mantis-tagging/lang/strings_english.txt
--- mantis-cvs/lang/strings_english.txt	2007-08-16 13:24:00.000000000 -0400
+++ mantis-tagging/lang/strings_english.txt	2007-08-17 16:59:16.000000000 -0400
@@ -275,6 +275,11 @@
 $MANTIS_ERROR[ERROR_USER_CHANGE_LAST_ADMIN] = 'You cannot change the access level of the only ADMINISTRATOR in the system.';
 $MANTIS_ERROR[ERROR_PAGE_REDIRECTION] = 'Page redirection error, ensure that there are no spaces outside the PHP block (&lt;?php ?&gt;) in config_inc.php or custom_*.php files.';
 $MANTIS_ERROR[ERROR_TWITTER_NO_CURL_EXT] = 'Twitter integration requires PHP CURL extension which is not installed.';
+$MANTIS_ERROR[ERROR_TAG_NOT_FOUND] = 'Could not find a tag with that name.';
+$MANTIS_ERROR[ERROR_TAG_DUPLICATE] = 'A tag already exists with that name.';
+$MANTIS_ERROR[ERROR_TAG_NAME_INVALID] = 'That tag name is invalid.';
+$MANTIS_ERROR[ERROR_TAG_NOT_ATTACHED] = 'That tag is not attached to that bug.';
+$MANTIS_ERROR[ERROR_TAG_ALREADY_ATTACHED] = 'That tag already attached to that bug.';
 
 $s_login_error = 'Your account may be disabled or blocked or the username/password you entered is incorrect.';
 $s_login_cookies_disabled = 'Your browser either doesn\'t know how to handle cookies, or refuses to handle them.';
@@ -1347,6 +1352,38 @@
 # wiki related strings
 $s_wiki = 'Wiki';
 
+# Tagging
+$s_tags = 'Tags';
+$s_tag_details = 'Tag Details: %s';
+$s_tag_id = 'Tag ID';
+$s_tag_name = 'Name';
+$s_tag_creator = 'Creator';
+$s_tag_created = 'Date Created';
+$s_tag_updated = 'Last Updated';
+$s_tag_description = 'Tag Description';
+$s_tag_statistics = 'Usage Statistics';
+$s_tag_update = 'Update Tag: %s';
+$s_tag_update_return = 'Back to Tag';
+$s_tag_update_button = 'Update Tag';
+$s_tag_delete_button = 'Delete Tag';
+$s_tag_delete_message = 'Are you sure you wish to delete this tag?';
+$s_tag_existing = 'Existing tags';
+$s_tag_none_attached = 'No tags attached.';
+$s_tag_attach = 'Attach';
+$s_tag_attach_long = 'Attach Tags';
+$s_tag_attach_failed = 'Tag attachment failed.';
+$s_tag_detach = 'Detach %s';
+$s_tag_separate_by = "(Separate by '%s')";
+$s_tag_invalid_name = 'Invalid tag name.';
+$s_tag_create_denied = 'Create permission denied.';
+$s_tag_filter_default = 'Attached Issues (%s)';
+$s_tag_history_attached = 'Tag Attached';
+$s_tag_history_detached = 'Tag Detached';
+$s_tag_history_renamed = 'Tag Renamed';
+$s_tag_related = 'Related Tags';
+$s_tag_related_issues = 'Shared Issues';
+$s_tag_stats_attached = 'Issues attached: %s';
+
 # Time Tracking
 $s_time_tracking_billing_link = 'Billing';
 $s_time_tracking = 'Time tracking';
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_attach.php mantis-tagging/tag_attach.php
--- mantis-cvs/tag_attach.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_attach.php	2007-08-17 11:30:39.000000000 -0400
@@ -0,0 +1,103 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2006  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'tag_api.php' );
+
+	$f_bug_id = gpc_get_int( 'bug_id' );
+	$t_user_id = auth_get_current_user_id();
+
+	access_ensure_global_level( config_get( 'tag_attach_threshold' ) );
+
+	$t_tags = tag_parse_string( gpc_get_string( 'tag_string' ) );
+	$t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) );
+	
+	$t_tags_create = array();
+	$t_tags_attach = array();
+	$t_tags_failed = array();
+
+	foreach ( $t_tags as $t_tag_row ) {
+		if ( -1 == $t_tag_row['id'] ) {
+			if ( $t_can_create ) {
+				$t_tags_create[] = $t_tag_row;
+			} else {
+				$t_tags_failed[] = $t_tag_row;
+			}
+		} elseif ( -2 == $t_tag_row['id'] ) {
+			$t_tags_failed[] = $t_tag_row;
+		} else {
+			$t_tags_attach[] = $t_tag_row;
+		}
+	}
+
+	if ( count( $t_tags_failed ) > 0 ) {
+		html_page_top1( lang_get( 'tag_attach_long' ).' '.bug_format_summary( $f_bug_id, SUMMARY_CAPTION ) );
+		html_page_top2();
+?>
+<br/>
+<table class="width75" align="center">
+	<tr class="row-category">
+	<td colspan="2"><?php echo lang_get( 'tag_attach_failed' ) ?></td>
+	</tr>
+	<tr class="spacer"><td colspan="2"></td></tr>
+<?php		
+		$t_tag_string = "";
+		foreach( $t_tags_attach as $t_tag_row ) {
+			if ( "" != $t_tag_string ) {
+				$t_tag_string .= config_get( 'tag_separator' );
+			}
+			$t_tag_string .= $t_tag_row['name'];
+		}
+
+		foreach( $t_tags_failed as $t_tag_row ) {
+			echo '<tr ',helper_alternate_class(),'>';
+			if ( -1 == $t_tag_row['id'] ) {
+				echo '<td class="category">',lang_get( 'tag_invalid_name' ),'</td>';
+			} elseif ( -2 == $t_tag_row['id'] ) {
+				echo '<td class="category">',lang_get( 'tag_create_denied' ),'</td>';
+			}
+			echo '<td>',$t_tag_row['name'],'</td></tr>';
+			
+			if ( "" != $t_tag_string ) {
+				$t_tag_string .= config_get( 'tag_separator' );
+			}
+			$t_tag_string .= $t_tag_row['name'];
+		}
+?>
+	<tr class="spacer"><td colspan="2"></td></tr>
+	<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td>
+	<td>
+<?php
+		print_tag_input( $f_bug_id, $t_tag_string );
+?>	
+	</td>
+	</tr>
+</table>
+<?php
+		html_page_bottom1(__FILE__);
+	} else {
+		foreach( $t_tags_create as $t_tag_row ) {
+			$t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id );
+			$t_tags_attach[] = $t_tag_row;
+		}
+
+		foreach( $t_tags_attach as $t_tag_row ) {
+			if ( ! tag_bug_is_attached( $t_tag_row['id'], $f_bug_id ) ) {
+				tag_bug_attach( $t_tag_row['id'], $f_bug_id, $t_user_id );
+			}
+		}
+
+		print_successful_redirect_to_bug( $f_bug_id );
+	}
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_delete.php mantis-tagging/tag_delete.php
--- mantis-cvs/tag_delete.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_delete.php	2007-08-17 13:34:13.000000000 -0400
@@ -0,0 +1,27 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2006  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'tag_api.php' );
+
+	access_ensure_global_level( config_get( 'tag_edit_threshold' ) );
+
+	$f_tag_id = gpc_get_int( 'tag_id' );
+	$t_tag_row = tag_get( $f_tag_id );
+
+	helper_ensure_confirmed( lang_get( 'tag_delete_message' ), lang_get( 'tag_delete_button' ) );
+
+	tag_delete( $f_tag_id );
+	
+	print_successful_redirect( config_get( 'default_home_page' ) );
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_detach.php mantis-tagging/tag_detach.php
--- mantis-cvs/tag_detach.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_detach.php	2007-08-17 11:34:04.000000000 -0400
@@ -0,0 +1,33 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2006  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'tag_api.php' );
+
+	$f_tag_id = gpc_get_int( 'tag_id' );
+	$f_bug_id = gpc_get_int( 'bug_id' );
+
+	$t_tag_row = tag_get( $f_tag_id );
+	$t_tag_bug_row = tag_bug_get_row( $f_tag_id, $f_bug_id );
+
+	if ( ! ( access_has_global_level( config_get( 'tag_detach_threshold' ) ) 
+		|| ( auth_get_current_user_id() == $t_tag_bug_row['user_id'] )
+			&& access_has_global_level( config_get( 'tag_detach_own_threshold' ) ) ) ) 
+	{
+		access_denied();
+	}
+
+	tag_bug_detach( $f_tag_id, $f_bug_id );
+	
+	print_successful_redirect_to_bug( $f_bug_id );
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update_page.php mantis-tagging/tag_update_page.php
--- mantis-cvs/tag_update_page.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_update_page.php	2007-08-17 13:37:45.000000000 -0400
@@ -0,0 +1,105 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2006  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'ajax_api.php' );
+	require_once( $t_core_path . 'tag_api.php' );
+
+	compress_enable();
+	
+	$f_tag_id = gpc_get_int( 'tag_id' );
+	$t_tag_row = tag_get( $f_tag_id );
+
+	if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) 
+		|| ( auth_get_current_user_id() == $t_tag_row['user_id'] )
+			&& access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) 
+	{
+		access_denied();
+	}
+	
+	html_page_top1( sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) );
+	html_page_top2();
+?>
+
+<br/>
+<form method="post" action="tag_update.php">
+<table class="width100" cellspacing="1">
+
+<!-- Title -->
+<tr>
+	<td class="form-title" colspan="2">
+		<?php echo sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ?>
+		<input type="hidden" name="tag_id" value="<?php echo $f_tag_id ?>"/>
+	</td>
+	<td class="right" colspan="3">
+		<?php print_bracket_link( 'tag_view_page.php?tag_id='.$f_tag_id, lang_get( 'tag_update_return' ) ); ?>
+	</td>
+</tr>
+
+<!-- Info -->
+<tr class="row-category">
+	<td width="15%"><?php echo lang_get( 'tag_id' ) ?></td>
+	<td width="25%"><?php echo lang_get( 'tag_name' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_creator' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_created' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_updated' ) ?></td>
+</tr>
+
+<tr <?php echo helper_alternate_class() ?>>
+	<td><?php echo $t_tag_row['id'] ?></td>
+	<td><input type="text" <?php echo helper_get_tab_index() ?> name="name" value="<?php echo $t_tag_row['name'] ?>"/></td>
+	<td><?php
+			if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) {
+				if ( ON == config_get( 'use_javascript' ) ) {
+					$t_username = prepare_user_name( $t_tag_row['user_id'] );
+					echo ajax_click_to_edit( $t_username, 'user_id', 'entrypoint=user_combobox&user_id=' . $t_tag_row['user_id'] . '&access_level=' . config_get( 'tag_create_threshold' ) );
+				} else {
+					echo '<select ', helper_get_tab_index(), ' name="user_id">';
+					print_user_option_list( $t_tag_row['user_id'], ALL_PROJECTS, config_get( 'tag_create_threshold' ) );
+					echo '</select>';
+				}
+			} else {
+				echo user_get_name($t_tag_row['user_id']);
+			}
+		?></td>
+	<td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td>
+	<td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td>
+</tr>
+
+<!-- spacer -->
+<tr class="spacer">
+	<td colspan="5"></td>
+</tr>
+
+<!-- Description -->
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tag_description' ) ?></td>
+	<td colspan="4">
+		<textarea name="description" <?php echo helper_get_tab_index() ?> cols="80" rows="6"><?php echo $t_tag_row['description'] ?></textarea>
+	</td>
+</tr>
+
+<!-- Submit Button -->
+<tr>
+	<td class="center" colspan="6">
+		<input <?php echo helper_get_tab_index() ?> type="submit" class="button" value="<?php echo lang_get( 'tag_update_button' ) ?>" />
+	</td>
+</tr>
+
+</table>
+</form>
+
+<?php
+	html_page_bottom1( __FILE__ );
+?>
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update.php mantis-tagging/tag_update.php
--- mantis-cvs/tag_update.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_update.php	2007-08-16 13:48:44.000000000 -0400
@@ -0,0 +1,55 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2006  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'tag_api.php' );
+
+	compress_enable();
+
+	$f_tag_id = gpc_get_int( 'tag_id' );
+	$t_tag_row = tag_get( $f_tag_id );
+
+	if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) 
+		|| ( auth_get_current_user_id() == $t_tag_row['user_id'] )
+			&& access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) 
+	{
+		access_denied();
+	}
+
+	if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) {
+		$f_new_user_id = gpc_get_int( 'user_id', $t_tag_row['user_id'] );
+	} else {
+		$f_new_user_id = $t_tag_row['user_id'];
+	}
+
+	$f_new_name = gpc_get_string( 'name', $t_tag_row['name'] );
+	$f_new_description = gpc_get_string( 'description', $t_tag_row['description'] );
+
+	$t_update = false;
+
+	if ( $t_tag_row['user_id'] != $f_new_user_id ) {
+		user_ensure_exists( $f_new_user_id );
+		$t_update = true;
+	}
+
+	if ( 	$t_tag_row['name'] != $f_new_name ||
+			$t_tag_row['description'] != $f_new_description ) {
+
+		$t_update = true;
+	}
+
+	tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description );
+		
+	$t_url = 'tag_view_page.php?tag_id='.$f_tag_id;
+	print_successful_redirect( $t_url );
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_view_page.php mantis-tagging/tag_view_page.php
--- mantis-cvs/tag_view_page.php	1969-12-31 19:00:00.000000000 -0500
+++ mantis-tagging/tag_view_page.php	2007-08-17 17:00:45.000000000 -0400
@@ -0,0 +1,107 @@
+<?php
+	# Mantis - a php based bugtracking system
+	# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+	# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+	# This program is distributed under the terms and conditions of the GPL
+	# See the README and LICENSE files for details
+
+	# --------------------------------------------------------
+	# $Id:  $
+	# --------------------------------------------------------
+
+	require_once( 'core.php' );
+	
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path . 'tag_api.php' );
+
+	access_ensure_global_level( config_get( 'tag_view_threshold' ) );
+	compress_enable();
+
+	$f_tag_id = gpc_get_int( 'tag_id' );
+	$t_tag_row = tag_get( $f_tag_id );
+
+	$t_name = string_display_line( $t_tag_row['name'] );
+	$t_description = string_display( $t_tag_row['description'] );
+
+	html_page_top1( sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) );
+	html_page_top2();
+?>
+
+<br/>
+<table class="width100" cellspacing="1">
+
+<!-- Title -->
+<tr>
+	<td class="form-title" colspan="2">
+		<?php echo sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ?>
+
+	</td>
+	<td class="right" colspan="3">
+		<?php print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode($t_tag_row['name']), sprintf( lang_get( 'tag_filter_default' ), tag_stats_attached( $f_tag_id ) ) ); ?>
+	</td>
+</tr>
+
+<!-- Info -->
+<tr class="row-category">
+	<td width="15%"><?php echo lang_get( 'tag_id' ) ?></td>
+	<td width="25%"><?php echo lang_get( 'tag_name' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_creator' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_created' ) ?></td>
+	<td width="20%"><?php echo lang_get( 'tag_updated' ) ?></td>
+</tr>
+
+<tr <?php echo helper_alternate_class() ?>>
+	<td><?php echo $t_tag_row['id'] ?></td>
+	<td><?php echo $t_name ?></td>
+	<td><?php echo user_get_name($t_tag_row['user_id']) ?></td>
+	<td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td>
+	<td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td>
+</tr>
+
+<!-- spacer -->
+<tr class="spacer">
+	<td colspan="5"></td>
+</tr>
+
+<!-- Description -->
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category"><?php echo lang_get( 'tag_description' ) ?></td>
+	<td colspan="4"><?php echo $t_description ?></td>
+</tr>
+
+<!-- Statistics -->
+<?php
+	$t_tags_related = tag_stats_related( $f_tag_id );
+	if ( count( $t_tags_related ) ) { 
+		echo '<tr ',helper_alternate_class(),'>';
+		echo '<td class="category" rowspan="',count( $t_tags_related ),'">',lang_get( 'tag_related' ),'</td>';
+		
+		$i = 0;
+		foreach( $t_tags_related as $t_tag ) {
+			$t_name = string_display_line( $t_tag['name'] );
+			$t_description = string_display_line( $t_tag['description'] );
+			$t_count = $t_tag['count'];
+
+			echo ( $i > 0 ? '<tr '.helper_alternate_class().'>' : '' );
+			echo "<td><a href='tag_view_page.php?tag_id=$t_tag[id]' title='$t_description'>$t_name</a></td>\n";
+			echo '<td colspan="3">';
+			print_bracket_link( "search.php?hide_status_is=90&tag_string=+$t_tag_row[name]".config_get('tag_separator')."+$t_name", lang_get( 'tag_related_issues' ) );
+			echo '</a></td></tr>';
+			
+			$i++;
+		}
+	}
+?>
+
+<!-- Buttons -->
+<tr>
+	<td colspan="5">
+		<?php html_buttons_tag_view_page( $f_tag_id ); ?>
+	</td>
+</tr>
+
+</table>
+<?php
+	html_page_bottom1( __FILE__ );
+?>
Binary files mantis-cvs/.tag_view_page.php.swp and mantis-tagging/.tag_view_page.php.swp differ
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/view_all_set.php mantis-tagging/view_all_set.php
--- mantis-cvs/view_all_set.php	2007-08-07 10:47:14.000000000 -0400
+++ mantis-tagging/view_all_set.php	2007-08-16 16:27:27.000000000 -0400
@@ -203,6 +203,8 @@
 	$f_do_filter_by_date	= gpc_get_bool( 'do_filter_by_date' );
 	$f_view_state			= gpc_get_int( 'view_state', META_FILTER_ANY );
 
+	$f_tag_string			= gpc_get_string( 'tag_string', '' );
+
 	$t_custom_fields 		= custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project
 	$f_custom_fields_data 	= array();
 	if ( is_array( $t_custom_fields ) && ( sizeof( $t_custom_fields ) > 0 ) ) {
@@ -414,6 +416,7 @@
 				$t_setting_arr['platform'] = $f_platform;
 				$t_setting_arr['os'] = $f_os;
 				$t_setting_arr['os_build'] = $f_os_build;
+				$t_setting_arr['tag_string'] = $f_tag_string;
 				break;
 		# Set the sort order and direction
 		case '2':
