View Issue Details

IDProjectCategoryView StatusLast Update
0008435mantisbtfeaturepublic2011-10-04 03:59
Reporterjreese Assigned Tojreese  
PrioritynormalSeverityfeatureReproducibilityN/A
Status closedResolutionfixed 
Target Version1.2.0a1Fixed in Version1.2.0a1 
Summary0008435: Implement Global and Inheriting Categories Structure
Description

Implement a set of features for the Mantis bugtracker that will enable a simple and logical method of reducing and reusing project categories by:

  • converting categories to use a category_id key
  • creating global categories shared by all projects
  • creating category inheritance between parent and child projects

The overall requirements outline for this planned feature has been posted on the wiki:
http://www.mantisbt.org/wiki/doku.php/mantisbt:global_categories_requirements

TagsNo tags attached.
Attached Files
mantis-categories-2007-11-10.patch (59,115 bytes)   
diff --git a/admin/install.php b/admin/install.php
index be2ef6a..a74c593 100644
--- a/admin/install.php
+++ b/admin/install.php
@@ -28,6 +28,7 @@
 	$g_skip_open_db = true;  # don't open the database in database_api.php
 	define( 'PLUGINS_DISABLED', true );
 	@require_once( dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'core.php' );
+	@require_once( 'install_functions.php' );
 	$g_error_send_page_header = false; # bypass page headers in error handler
 
 	define( 'BAD', 0 );
diff --git a/admin/install_functions.php b/admin/install_functions.php
new file mode 100644
index 0000000..0612550
--- /dev/null
+++ b/admin/install_functions.php
@@ -0,0 +1,59 @@
+<?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: $
+	# --------------------------------------------------------
+
+	/**
+	 * Update functions for the installation schema's 'UpdateFunction' option.
+	 * All functions must be name install_<function_name> and referenced as just <function_name>.
+	 */
+
+	/**
+	 * Migrate the legacy category data to the new category_id-based schema.
+	 */
+	function install_category_migrate() {
+		$t_bug_table = config_get( 'mantis_bug_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_category_table = config_get( 'mantis_project_category_table' );
+
+		$query = "SELECT project_id, category FROM $t_project_category_table ORDER BY project_id, category";
+		$cresult = db_query( $query );
+
+		$query = "SELECT id, project_id, category FROM $t_bug_table ORDER BY project_id, category";
+		$bresult = db_query( $query );
+
+		$t_data = Array();
+		while ( $row = db_fetch_array( $cresult ) ) {
+			$t_pid = $row['project_id'];
+			$t_name = $row['category'];
+			$t_data[$t_pid][$t_name] = Array();
+		}
+		while ( $row = db_fetch_array( $bresult ) ) {
+			$t_bid = $row['id'];
+			$t_pid = $row['project_id'];
+			$t_name = $row['category'];
+
+			$t_data[$t_pid][$t_name][] = $t_bid;
+		}
+
+		foreach ( $t_data as $t_pid => $t_categories ) {
+			foreach ( $t_categories as $t_name => $t_bugs ) {
+				$query = "INSERT INTO $t_category_table ( name, project_id ) VALUES ( '$t_name', '$t_pid' )";
+				db_query( $query );
+				$t_category_id = db_insert_id( $t_category_table );
+
+				$query = "UPDATE $t_bug_table SET category_id='$t_category_id'
+							WHERE project_id='$t_pid' AND category='$t_name'";
+				db_query( $query );
+			}
+		}
+
+		return 2;
+	}
+
diff --git a/admin/schema.php b/admin/schema.php
index 440504e..63bb06e 100644
--- a/admin/schema.php
+++ b/admin/schema.php
@@ -370,4 +370,21 @@ $upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_plugin_table' ),
 	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
 
 $upgrade[] = Array('AlterColumnSQL', Array( config_get_global( 'mantis_user_pref_table' ), "redirect_delay 	I NOTNULL DEFAULT 0" ) );
+
+$upgrade[] = Array( 'CreateTableSQL', Array( config_get( 'mantis_category_table' ), "
+	id				I		UNSIGNED NOTNULL PRIMARY AUTOINCREMENT,
+	project_id		I		UNSIGNED NOTNULL DEFAULT '0',
+	user_id			I		UNSIGNED NOTNULL DEFAULT '0',
+	name			C(100)	NOTNULL PRIMARY DEFAULT \" '' \",
+	status			I		UNSIGNED NOTNULL DEFAULT '0'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
+$upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_category', config_get( 'mantis_category_table' ), 'project_id, status' ) );
+$upgrade[] = Array( 'InsertData', Array( config_get( 'mantis_category_table' ), "
+	( project_id, user_id, name, status ) VALUES
+	( '0', '0', 'Unknown', '0' ) " ) );
+$upgrade[] = Array( 'AddColumnSQL', Array( config_get( 'mantis_bug_table' ), "category_id I UNSIGNED NOTNULL DEFAULT '0'" ) );
+$upgrade[] = Array( 'UpdateFunction', "category_migrate" );
+$upgrade[] = Array( 'DropColumnSQL', Array( config_get( 'mantis_bug_table' ), "category" ) );
+$upgrade[] = Array( 'DropTableSQL', Array( config_get( 'mantis_project_category_table' ) ) );
+
 ?>
diff --git a/bug_report.php b/bug_report.php
index 58588dc..a41fb29 100644
--- a/bug_report.php
+++ b/bug_report.php
@@ -44,7 +44,7 @@
 	$t_bug_data->handler_id			= gpc_get_int( 'handler_id', 0 );
 	$t_bug_data->view_state			= gpc_get_int( 'view_state', config_get( 'default_bug_view_status' ) );
 
-	$t_bug_data->category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+	$t_bug_data->category_id			= gpc_get_string( 'category_id', config_get( 'default_bug_category' ) );
 	$t_bug_data->reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 	$t_bug_data->severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 	$t_bug_data->priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -171,7 +171,7 @@
 ?>
 	<p>
 	<form method="post" action="<?php echo string_get_bug_report_url() ?>">
-		<input type="hidden" name="category" 		value="<?php echo $t_bug_data->category ?>" />
+		<input type="hidden" name="category_id" 	value="<?php echo $t_bug_data->category_id ?>" />
 		<input type="hidden" name="severity" 		value="<?php echo $t_bug_data->severity ?>" />
 		<input type="hidden" name="reproducibility" 	value="<?php echo $t_bug_data->reproducibility ?>" />
 		<input type="hidden" name="profile_id" 		value="<?php echo $t_bug_data->profile_id ?>" />
diff --git a/bug_report_advanced_page.php b/bug_report_advanced_page.php
index ed694e3..85117a7 100644
--- a/bug_report_advanced_page.php
+++ b/bug_report_advanced_page.php
@@ -82,7 +82,7 @@
 		$f_profile_id			= 0;
 		$f_handler_id			= $t_bug->handler_id;
 
-		$f_category				= $t_bug->category;
+		$f_category_id			= $t_bug->category_id;
 		$f_reproducibility		= $t_bug->reproducibility;
 		$f_severity				= $t_bug->severity;
 		$f_priority				= $t_bug->priority;
@@ -105,7 +105,7 @@
 		$f_profile_id			= gpc_get_int( 'profile_id', 0 );
 		$f_handler_id			= gpc_get_int( 'handler_id', 0 );
 
-		$f_category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+		$f_category_id			= gpc_get_string( 'category_id', config_get( 'default_bug_category' ) );
 		$f_reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 		$f_severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 		$f_priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -161,13 +161,13 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
 			<?php 
-				if ( is_blank( $f_category ) ) {
+				if ( is_blank( $f_category_id ) ) {
 					echo '<option value="" selected="selected">', string_attribute( lang_get( 'select_option' ) ), '</option>';
 				}
 
-				print_category_option_list( $f_category ); 
+				print_category_option_list( $f_category_id ); 
 			?>
 		</select>
 	</td>
@@ -555,7 +555,7 @@
 <?php if ( ON == config_get( 'use_javascript' ) ) { ?>
 <script type="text/javascript" language="JavaScript">
 <!--
-	window.document.report_bug_form.category.focus();
+	window.document.report_bug_form.category_id.focus();
 -->
 </script>
 <?php } ?>
diff --git a/bug_report_page.php b/bug_report_page.php
index 4446331..65bb875 100644
--- a/bug_report_page.php
+++ b/bug_report_page.php
@@ -73,7 +73,7 @@
 	    access_ensure_project_level( config_get( 'report_bug_threshold' ) );
 
 	    $f_product_version		= $t_bug->version;
-		$f_category				= $t_bug->category;
+		$f_category_id			= $t_bug->category_id;
 		$f_reproducibility		= $t_bug->reproducibility;
 		$f_severity				= $t_bug->severity;
 		$f_priority				= $t_bug->priority;
@@ -87,7 +87,7 @@
 	    access_ensure_project_level( config_get( 'report_bug_threshold' ) );
 
 		$f_product_version		= gpc_get_string( 'product_version', '' );
-		$f_category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+		$f_category_id			= gpc_get_string( 'category_id', config_get( 'default_bug_category' ) );
 		$f_reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 		$f_severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 		$f_priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -143,13 +143,13 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
 			<?php 
-				if ( is_blank( $f_category ) ) {
+				if ( is_blank( $f_category_id ) ) {
 					echo '<option value="" selected="selected">', string_attribute( lang_get( 'select_option' ) ), '</option>';
 				}
 
-				print_category_option_list( $f_category ); 
+				print_category_option_list( $f_category_id ); 
 			?>
 		</select>
 	</td>
@@ -385,7 +385,7 @@
 <?php if ( ON == config_get( 'use_javascript' ) ) { ?>
 <script type="text/javascript" language="JavaScript">
 <!--
-	window.document.report_bug_form.category.focus();
+	window.document.report_bug_form.category_id.focus();
 -->
 </script>
 <?php } ?>
diff --git a/bug_update.php b/bug_update.php
index dd0a2e6..5b16e80 100644
--- a/bug_update.php
+++ b/bug_update.php
@@ -67,7 +67,7 @@
 	$t_bug_data->status				= gpc_get_int( 'status', $t_bug_data->status );
 	$t_bug_data->resolution			= gpc_get_int( 'resolution', $t_bug_data->resolution );
 	$t_bug_data->projection			= gpc_get_int( 'projection', $t_bug_data->projection );
-	$t_bug_data->category			= gpc_get_string( 'category', $t_bug_data->category );
+	$t_bug_data->category_id		= gpc_get_int( 'category_id', $t_bug_data->category_id );
 	$t_bug_data->eta				= gpc_get_int( 'eta', $t_bug_data->eta );
 	$t_bug_data->os					= gpc_get_string( 'os', $t_bug_data->os );
 	$t_bug_data->os_build			= gpc_get_string( 'os_build', $t_bug_data->os_build );
diff --git a/bug_update_advanced_page.php b/bug_update_advanced_page.php
index cf429d3..03ed16d 100644
--- a/bug_update_advanced_page.php
+++ b/bug_update_advanced_page.php
@@ -118,8 +118,8 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
-		<?php print_category_option_list( $t_bug->category, $t_bug->project_id ) ?>
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
+		<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
 	</td>
 
diff --git a/bug_update_page.php b/bug_update_page.php
index 1af51da..151b1bc 100644
--- a/bug_update_page.php
+++ b/bug_update_page.php
@@ -121,8 +121,8 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
-			<?php print_category_option_list( $t_bug->category, $t_bug->project_id ) ?>
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
+			<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
 	</td>
 
diff --git a/bug_view_advanced_page.php b/bug_view_advanced_page.php
index d16f69e..b0581e7 100644
--- a/bug_view_advanced_page.php
+++ b/bug_view_advanced_page.php
@@ -168,8 +168,7 @@
 	<!-- Category -->
 	<td>
 		<?php
-			$t_project_name = string_display( project_get_field( $t_bug->project_id, 'name' ) );
-			echo "[$t_project_name] $t_bug->category";
+			echo string_display( category_full_name( $t_bug->category_id ) );
 		?>
 	</td>
 
diff --git a/bug_view_page.php b/bug_view_page.php
index 5ff445a..ad82077 100644
--- a/bug_view_page.php
+++ b/bug_view_page.php
@@ -171,8 +171,7 @@
 	<!-- Category -->
 	<td>
 		<?php
-			$t_project_name = string_display( project_get_field( $t_bug->project_id, 'name' ) );
-			echo "[$t_project_name] $t_bug->category";
+			echo string_display( category_full_name( $t_bug->category_id ) );
 		?>
 	</td>
 
diff --git a/config_defaults_inc.php b/config_defaults_inc.php
index 185a83e..8d79641 100644
--- a/config_defaults_inc.php
+++ b/config_defaults_inc.php
@@ -1345,9 +1345,10 @@
 	$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%';
+	$g_mantis_category_table				= '%db_table_prefix%_category%db_table_suffix%';
 	$g_mantis_news_table					= '%db_table_prefix%_news%db_table_suffix%';
 	$g_mantis_plugin_table					= '%db_table_prefix%_plugin%db_table_suffix%';
-	$g_mantis_project_category_table		= '%db_table_prefix%_project_category%db_table_suffix%';
+	$g_mantis_project_category_table		= '%db_table_prefix%_project_category%db_table_suffix%'; # Legacy table
 	$g_mantis_project_file_table			= '%db_table_prefix%_project_file%db_table_suffix%';
 	$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%';
diff --git a/core/bug_api.php b/core/bug_api.php
index f6fce02..6fd0542 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -52,7 +52,7 @@
 		var $status = NEW_;
 		var $resolution = OPEN;
 		var $projection = 10;
-		var $category = '';
+		var $category_id = 0;
 		var $date_submitted = '';
 		var $last_updated = '';
 		var $eta = 10;
@@ -353,7 +353,7 @@
 		$c_priority				= db_prepare_int( $p_bug_data->priority );
 		$c_severity				= db_prepare_int( $p_bug_data->severity );
 		$c_reproducibility		= db_prepare_int( $p_bug_data->reproducibility );
-		$c_category				= db_prepare_string( $p_bug_data->category );
+		$c_category_id			= db_prepare_int( $p_bug_data->category_id );
 		$c_os					= db_prepare_string( $p_bug_data->os );
 		$c_os_build				= db_prepare_string( $p_bug_data->os_build );
 		$c_platform				= db_prepare_string( $p_bug_data->platform );
@@ -378,11 +378,6 @@
 			trigger_error( ERROR_EMPTY_FIELD, ERROR );
 		}
 		
-		if ( is_blank( $c_category ) ) {
-			error_parameters( lang_get( 'category' ) );
-			trigger_error( ERROR_EMPTY_FIELD, ERROR );
-		}
-
 		# Only set target_version if user has access to do so
 		if ( access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) {
 			$c_target_version	= db_prepare_string( $p_bug_data->target_version );
@@ -392,7 +387,7 @@
 
 		$t_bug_table				= config_get_global( 'mantis_bug_table' );
 		$t_bug_text_table			= config_get_global( 'mantis_bug_text_table' );
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
+		$t_category_table			= config_get_global( 'mantis_category_table' );
 
 		# Insert text information
 		$query = "INSERT INTO $t_bug_text_table
@@ -415,9 +410,9 @@
 			# if a default user is associated with the category and we know at this point
 			# that that the bug was not assigned to somebody, then assign it automatically.
 			$query = "SELECT user_id
-					  FROM $t_project_category_table
-					  WHERE project_id=" .db_param(0) . " AND category=" . db_param(1);
-			$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
+					  FROM $t_category_table
+					  WHERE project_id='$c_project_id' AND id='$c_category_id'";
+			$result = db_query( $query );
 
 			if ( db_num_rows( $result ) > 0 ) {
 				$c_handler_id = $p_handler_id = db_result( $result );
@@ -438,7 +433,7 @@
 				      duplicate_id, priority,
 				      severity, reproducibility,
 				      status, resolution,
-				      projection, category,
+				      projection, category_id,
 				      date_submitted, last_updated,
 				      eta, bug_text_id,
 				      os, os_build,
@@ -453,7 +448,7 @@
 				      '0', '$c_priority',
 				      '$c_severity', '$c_reproducibility',
 				      '$t_status', '$t_resolution',
-				      10, '$c_category',
+				      10, '$c_category_id',
 				      " . db_now() . "," . db_now() . ",
 				      10, '$t_text_id',
 				      '$c_os', '$c_os_build',
@@ -817,7 +812,7 @@
 					status='$c_bug_data->status',
 					resolution='$c_bug_data->resolution',
 					projection='$c_bug_data->projection',
-					category='$c_bug_data->category',
+					category_id='$c_bug_data->category_id',
 					eta='$c_bug_data->eta',
 					os='$c_bug_data->os',
 					os_build='$c_bug_data->os_build',
@@ -854,7 +849,7 @@
 		history_log_event_direct( $p_bug_id, 'status', $t_old_data->status, $p_bug_data->status );
 		history_log_event_direct( $p_bug_id, 'resolution', $t_old_data->resolution, $p_bug_data->resolution );
 		history_log_event_direct( $p_bug_id, 'projection', $t_old_data->projection, $p_bug_data->projection );
-		history_log_event_direct( $p_bug_id, 'category', $t_old_data->category, $p_bug_data->category );
+		history_log_event_direct( $p_bug_id, 'category_id', $t_old_data->category_id, $p_bug_data->category_id );
 		history_log_event_direct( $p_bug_id, 'eta',	$t_old_data->eta, $p_bug_data->eta );
 		history_log_event_direct( $p_bug_id, 'os', $t_old_data->os, $p_bug_data->os );
 		history_log_event_direct( $p_bug_id, 'os_build', $t_old_data->os_build, $p_bug_data->os_build );
@@ -1429,7 +1424,7 @@
 		$t_bug_data->status				= db_prepare_int( $p_bug_data->status );
 		$t_bug_data->resolution			= db_prepare_int( $p_bug_data->resolution );
 		$t_bug_data->projection			= db_prepare_int( $p_bug_data->projection );
-		$t_bug_data->category			= db_prepare_string( $p_bug_data->category );
+		$t_bug_data->category_id		= db_prepare_string( $p_bug_data->category_id );
 		$t_bug_data->date_submitted		= db_prepare_string( $p_bug_data->date_submitted );
 		$t_bug_data->last_updated		= db_prepare_string( $p_bug_data->last_updated );
 		$t_bug_data->eta				= db_prepare_int( $p_bug_data->eta );
@@ -1456,7 +1451,7 @@
 	# Return a copy of the bug structure with all the instvars prepared for editing
 	#  in an HTML form
 	function bug_prepare_edit( $p_bug_data ) {
-		$p_bug_data->category			= string_attribute( $p_bug_data->category );
+		$p_bug_data->category_id		= string_attribute( $p_bug_data->category_id );
 		$p_bug_data->date_submitted		= string_attribute( $p_bug_data->date_submitted );
 		$p_bug_data->last_updated		= string_attribute( $p_bug_data->last_updated );
 		$p_bug_data->os					= string_attribute( $p_bug_data->os );
@@ -1480,7 +1475,7 @@
 	# Return a copy of the bug structure with all the instvars prepared for editing
 	#  in an HTML form
 	function bug_prepare_display( $p_bug_data ) {
-		$p_bug_data->category			= string_display_line( $p_bug_data->category );
+		$p_bug_data->category_id		= string_display_line( $p_bug_data->category_id );
 		$p_bug_data->date_submitted		= string_display_line( $p_bug_data->date_submitted );
 		$p_bug_data->last_updated		= string_display_line( $p_bug_data->last_updated );
 		$p_bug_data->os					= string_display_line( $p_bug_data->os );
diff --git a/core/category_api.php b/core/category_api.php
index 97c5b8d..64c047a 100644
--- a/core/category_api.php
+++ b/core/category_api.php
@@ -23,6 +23,9 @@
 
 	### Category API ###
 
+	# Category data cache (to prevent excessive db queries)
+	$g_category_cache = array();
+
 	#===================================
 	# Boolean queries and ensures
 	#===================================
@@ -30,31 +33,32 @@
 	# --------------------
 	# Check whether the category exists in the project
 	# Return true if the category exists, false otherwise
-	function category_exists( $p_project_id, $p_category ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+	function category_exists( $p_category_id ) {
+		global $g_category_cache;
+		if ( isset( $g_category_cache[$p_category_id] ) ) {
+			return true;
+		}
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$c_category_id	= db_prepare_int( $p_category_id );
 
-		$query = "SELECT COUNT(*)
-				  FROM $t_project_category_table
-				  WHERE project_id=" . db_param(0) . " AND
-						category=" . db_param(1);
-		$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
-		$category_count =  db_result( $result );
+		$t_category_table = config_get( 'mantis_category_table' );
 
-		if ( 0 < $category_count ) {
-			return true;
+		$query = "SELECT COUNT(*) FROM $t_category_table
+					WHERE id=" . db_param(0);
+		$count = db_result( db_query_bound( $query, array( $c_category_id ) ) );
+
+		if ( 0 < $count ) {
+				return true;
 		} else {
-			return false;
+				return false;
 		}
 	}
 
 	# --------------------
 	# Check whether the category exists in the project
 	# Trigger an error if it does not
-	function category_ensure_exists( $p_project_id, $p_category ) {
-		if ( !category_exists( $p_project_id, $p_category ) ) {
+	function category_ensure_exists( $p_category_id ) {
+		if ( !category_exists( $p_category_id ) ) {
 			trigger_error( ERROR_CATEGORY_NOT_FOUND, ERROR );
 		}
 	}
@@ -62,15 +66,28 @@
 	# --------------------
 	# Check whether the category is unique within a project
 	# Returns true if the category is unique, false otherwise
-	function category_is_unique( $p_project_id, $p_category ) {
-		return !category_exists( $p_project_id, $p_category );
+	function category_is_unique( $p_project_id, $p_name ) {
+		$c_project_id	= db_prepare_int( $p_project_id );
+		$c_name			= db_prepare_string( $p_name );
+
+		$t_category_table = config_get( 'mantis_category_table' );
+
+		$query = "SELECT COUNT(*) FROM $t_category_table
+					WHERE project_id=" . db_param(0) . " AND " . db_helper_like( 'name', db_param(1) );
+		$count = db_result( db_query_bound( $query, array( $c_project_id, $c_name ) ) );
+
+		if ( 0 < $count ) {
+				return false;
+		} else {
+				return true;
+		}
 	}
 
 	# --------------------
 	# Check whether the category is unique within a project
 	# Trigger an error if it is not
-	function category_ensure_unique( $p_project_id, $p_category ) {
-		if ( !category_is_unique( $p_project_id, $p_category ) ) {
+	function category_ensure_unique( $p_project_id, $p_name ) {
+		if ( !category_is_unique( $p_project_id, $p_name ) ) {
 			trigger_error( ERROR_CATEGORY_DUPLICATE, ERROR );
 		}
 	}
@@ -82,51 +99,41 @@
 
 	# --------------------
 	# Add a new category to the project
-	function category_add( $p_project_id, $p_category ) {
+	function category_add( $p_project_id, $p_name ) {
 		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+		$c_name			= db_prepare_string( $p_name );
 
-		category_ensure_unique( $p_project_id, $p_category );
+		category_ensure_unique( $p_project_id, $p_name );
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
 
-		$query = "INSERT INTO $t_project_category_table
-					( project_id, category )
+		$query = "INSERT INTO $t_category_table
+					( project_id, name )
 				  VALUES
-					( " . db_param(0) . ',' . db_param(1) . ')';
-		db_query_bound( $query, Array( $c_project_id, $c_category ) );
+					( " . db_param(0) . ', ' . db_param(1) . ' )';
+		db_query_bound( $query, array( $c_project_id, $c_name ) );
 
 		# db_query errors on failure so:
-		return true;
+		return db_insert_id( $t_category_table );
 	}
 
 	# --------------------
 	# Update the name and user associated with the category
-	function category_update( $p_project_id, $p_category, $p_new_category, $p_assigned_to ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
-		$c_new_category	= db_prepare_string( $p_new_category );
+	function category_update( $p_category_id, $p_name, $p_assigned_to ) {
+		$c_category_id	= db_prepare_int( $p_category_id );
+		$c_name			= db_prepare_string( $p_name );
 		$c_assigned_to	= db_prepare_int( $p_assigned_to );
 
-		category_ensure_exists( $p_project_id, $p_category );
+		category_ensure_exists( $p_category_id );
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "UPDATE $t_project_category_table
-				  SET category=" . db_param(0) . ",
-				  	  user_id=" . db_param(1) . "
-				  WHERE category=" . db_param(2) . " AND
-						project_id=" . db_param(3);
-		db_query_bound( $query, Array( $c_new_category, $c_assigned_to, $c_category, $c_project_id ) );
-
-		if ( $p_category != $p_new_category ) {
-			$query = "UPDATE $t_bug_table
-					  SET category=" . db_param(0) . "
-					  WHERE category=" . db_param(1) . " AND
-					  		project_id=" . db_param(2);
-			db_query_bound( $query, Array( $c_new_category, $c_category, $c_project_id ) );
-		}
+		$query = "UPDATE $t_category_table
+				  SET name=" . db_param(0) . ',
+					user_id=' . db_param(1) . '
+				  WHERE id=' . db_param(2);
+		db_query_bound( $query, array( $c_name, $c_assigned_to, $c_category_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -134,29 +141,26 @@
 
 	# --------------------
 	# Remove a category from the project
-	function category_remove( $p_project_id, $p_category, $p_new_category='' ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
-		$c_new_category	= db_prepare_string( $p_new_category );
+	function category_remove( $p_category_id, $p_new_category=0 ) {
+		$c_category_id	= db_prepare_int( $p_category_id );
+		$c_new_category	= db_prepare_int( $p_new_category );
 
-		category_ensure_exists( $p_project_id, $p_category );
-		if ( !is_blank( $p_new_category ) ) {
-			category_ensure_exists( $p_project_id, $p_new_category );
+		category_ensure_exists( $p_category_id );
+		if ( 0 != $p_new_category ) {
+			category_ensure_exists( $p_new_category );
 		}
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "DELETE FROM $t_project_category_table
-				  WHERE project_id=" . db_param(0) . " AND
-						category=" . db_param(1);
-		db_query_bound( $query, Array( $c_project_id, $c_category ) );
+		$query = "DELETE FROM $t_category_table
+				  WHERE id=" . db_param(0);
+		db_query_bound( $query, array( $c_category_id ) );
 
 		$query = "UPDATE $t_bug_table
-				  SET category=" . db_param(0) . "
-				  WHERE category=" . db_param(1) . " AND
-				  		project_id=" . db_param(2);
-		db_query_bound( $query, Array( $c_new_category, $c_category, $c_project_id ) );
+				  SET category_id=" . db_param(0) . "
+				  WHERE category_id=" . db_param(1);
+		db_query_bound( $query, array( $c_new_category, $c_category_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -164,22 +168,26 @@
 
 	# --------------------
 	# Remove all categories associated with a project
-	function category_remove_all( $p_project_id ) {
+	function category_remove_all( $p_project_id, $p_new_category=0 ) {
 		$c_project_id = db_prepare_int( $p_project_id );
+		$c_new_category = db_prepare_int( $p_new_category );
 
 		project_ensure_exists( $p_project_id );
+		if ( 0 != $p_new_category ) {
+			category_ensure_exists( $p_new_category );
+		}
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "DELETE FROM $t_project_category_table
-				  WHERE project_id=" . db_param(0);
-		db_query_bound( $query, Array( $c_project_id ) );
+		$query = "DELETE FROM $t_category_table
+				  WHERE project_id='$c_project_id'";
+		db_query_bound( $query, array( $c_project_id ) );
 
 		$query = "UPDATE $t_bug_table
-				  SET category=''
-				  WHERE project_id=" . db_param(0);
-		db_query_bound( $query, Array( $c_project_id ) );
+				  SET category='$c_new_category'
+				  WHERE project_id='$c_project_id'";
+		db_query_bound( $query, array( $c_new_category, $c_project_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -192,45 +200,74 @@
 
 	# --------------------
 	# Return the definition row for the category
-	function category_get_row( $p_project_id, $p_category ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+	function category_get_row( $p_category_id ) {
+		global $g_category_cache;
+		if ( isset( $g_category_cache[$p_category_id] ) ) {
+			return $g_category_cache[$p_category_id];
+		}
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$c_category_id	= db_prepare_int( $p_category_id );
 
-		$query = "SELECT category, user_id
-				FROM $t_project_category_table
-				WHERE project_id=" . db_param(0) . " AND
-					category=" . db_param(1);
-		$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
+
+		$query = "SELECT c.*, p.name AS project_name FROM $t_category_table AS c
+				JOIN $t_project_table AS p
+					ON c.project_id=p.id
+				WHERE c.id=" . db_param(0);
+		$result = db_query_bound( $query, array( $c_category_id ) );
 		$count = db_num_rows( $result );
 		if ( 0 == $count ) {
 			trigger_error( ERROR_CATEGORY_NOT_FOUND, ERROR );
 		}
 
-		return db_fetch_array( $result );
+		$row = db_fetch_array( $result );
+		$g_category_cache[$p_category_id] = $row;
+		return $row;
 	}
 
 	# --------------------
 	# Return all categories for the specified project id
 	function category_get_all_rows( $p_project_id ) {
+		global $g_category_cache;
+
 		$c_project_id	= db_prepare_int( $p_project_id );
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
-		$query = "SELECT category, user_id
-				FROM $t_project_category_table
-				WHERE project_id=" . db_param(0) . "
-				ORDER BY category";
-		$result = db_query_bound( $query, Array( $c_project_id ) );
+		$t_project_where = helper_project_specific_where( $c_project_id );
+
+		$query = "SELECT c.*, p.name AS project_name FROM $t_category_table AS c
+				JOIN $t_project_table AS p
+					ON c.project_id=p.id
+				WHERE $t_project_where
+				ORDER BY c.name ";
+		$result = db_query_bound( $query, array() );
 		$count = db_num_rows( $result );
 		$rows = array();
 		for ( $i = 0 ; $i < $count ; $i++ ) {
 			$row = db_fetch_array( $result );
 
 			$rows[] = $row;
+			$g_category_cache[$row['id']] = $row;
 		}
 
 		return $rows;
 	}
-?>
+
+	# Helpers
+
+	function category_full_name( $p_category_id, $p_show_project=true ) {
+		if ( $p_category_id == 0 ) {
+				return lang_get( 'no_category' );
+		}
+
+		$t_row = category_get_row( $p_category_id );
+
+		if ( $p_show_project ) {
+			return "[$t_row[project_name]] $t_row[name]";
+		} else {
+			return $t_row['name'];
+		}
+	}
diff --git a/core/columns_api.php b/core/columns_api.php
index 65bc271..8f08254 100644
--- a/core/columns_api.php
+++ b/core/columns_api.php
@@ -534,7 +534,7 @@
 			echo ']</small><br />';
 		}
 
-		echo string_display( $p_row['category'] );
+		echo string_display( category_full_name( $p_row['category_id'], false ) );
 		echo '</td>';
 	}
 
diff --git a/core/filter_api.php b/core/filter_api.php
index a7b3cd6..a60033d 100644
--- a/core/filter_api.php
+++ b/core/filter_api.php
@@ -413,6 +413,7 @@
 		$t_bug_table			= config_get_global( 'mantis_bug_table' );
 		$t_bug_text_table		= config_get_global( 'mantis_bug_text_table' );
 		$t_bugnote_table		= config_get_global( 'mantis_bugnote_table' );
+		$t_category_table		= config_get_global( 'mantis_category_table' );
 		$t_custom_field_string_table	= config_get_global( 'mantis_custom_field_string_table' );
 		$t_bugnote_text_table	= config_get_global( 'mantis_bugnote_text_table' );
 		$t_project_table		= config_get_global( 'mantis_project_table' );
@@ -696,9 +697,7 @@
 			$t_clauses = array();
 
 			foreach( $t_filter['show_category'] as $t_filter_member ) {
-				$t_filter_member = stripslashes( $t_filter_member );
 				if ( META_FILTER_NONE == $t_filter_member ) {
-					array_push( $t_clauses, "''" );
 				} else {
 					$c_show_category = db_prepare_string( $t_filter_member );
 					array_push( $t_clauses, "'$c_show_category'" );
@@ -706,9 +705,9 @@
 			}
 
 			if ( 1 < count( $t_clauses ) ) {
-				array_push( $t_where_clauses, "( $t_bug_table.category in (". implode( ', ', $t_clauses ) .") )" );
+				array_push( $t_where_clauses, "( $t_bug_table.category_id in ( SELECT id FROM $t_category_table WHERE name in (". implode( ', ', $t_clauses ) .") ) )" );
 			} else {
-				array_push( $t_where_clauses, "( $t_bug_table.category=$t_clauses[0] )" );
+				array_push( $t_where_clauses, "( $t_bug_table.category_id in ( SELECT id FROM $t_category_table WHERE name=$t_clauses[0] ) )" );
 			}
 		}
 
@@ -1765,13 +1764,11 @@
 								} else {
 									$t_first_flag = true;
 									foreach( $t_filter['show_category'] as $t_current ) {
-										$t_current = stripslashes( $t_current );
 										?>
-										<input type="hidden" name="show_category[]" value="<?php echo string_display( $t_current );?>" />
+										<input type="hidden" name="show_category[]" value="<?php echo $t_current;?>" />
 										<?php
 										$t_this_string = '';
-										if ( ( ( $t_current == META_FILTER_ANY ) && ( is_numeric( $t_current ) ) ) 
-												|| ( is_blank( $t_current ) ) ) {
+										if ( is_blank( $t_current ) || $t_current === "0" || $t_current === META_FILTER_ANY ) {
 											$t_any_found = true;
 										} else {
 											$t_this_string = string_display( $t_current );
@@ -3334,8 +3331,7 @@
 		<!-- Category -->
 		<select <?php PRINT $t_select_modifier;?> name="show_category[]">
 			<option value="<?php echo META_FILTER_ANY ?>" <?php check_selected( $t_filter['show_category'], META_FILTER_ANY ); ?>>[<?php echo lang_get( 'any' ) ?>]</option>
-			<?php # This shows orphaned categories as well as selectable categories ?>
-			<?php print_category_complete_option_list( $t_filter['show_category'] ) ?>
+			<?php print_category_filter_option_list( $t_filter['show_category'] ) ?>
 		</select>
 		<?php
 	}
diff --git a/core/graph_api.php b/core/graph_api.php
index 2393a11..df60a9a 100644
--- a/core/graph_api.php
+++ b/core/graph_api.php
@@ -591,30 +591,28 @@
 		global $category_name, $category_bug_count;
 
 		$t_project_id = helper_get_current_project();
-		$t_cat_table = config_get_global( 'mantis_project_category_table' );
-		$t_bug_table = config_get_global( 'mantis_bug_table' );
+		$t_cat_table = config_get( 'mantis_category_table' );
+		$t_bug_table = config_get( 'mantis_bug_table' );
 		$t_user_id = auth_get_current_user_id();
 		$specific_where = helper_project_specific_where( $t_project_id, $t_user_id );
 
-		$query = "SELECT DISTINCT category
+		$query = "SELECT id, name
 				FROM $t_cat_table
 				WHERE $specific_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
 		$category_count = db_num_rows( $result );
-		if ( 0 == $category_count ) {
-			return array();
-		}
 
+		$t_metrics = array();
 		for ($i=0;$i<$category_count;$i++) {
 			$row = db_fetch_array( $result );
-			$t_cat_name = $row['category'];
-			$c_category_name = addslashes($t_cat_name);
+			$t_cat_name = $row['name'];
+			$t_cat_id = $row['id'];
 			$query = "SELECT COUNT(*)
 					FROM $t_bug_table
-					WHERE category='$c_category_name' AND $specific_where";
+					WHERE category_id='$t_cat_id' AND $specific_where";
 			$result2 = db_query( $query );
-			$t_metrics[$t_cat_name] = db_result( $result2, 0, 0 );
+			$t_metrics[$t_cat_name] = $t_metrics[$t_cat_name] + db_result( $result2, 0, 0 );
 		} # end for
 		return $t_metrics;
 	}
diff --git a/core/history_api.php b/core/history_api.php
index 2a6b003..0f51a76 100644
--- a/core/history_api.php
+++ b/core/history_api.php
@@ -216,6 +216,11 @@
 			case 'category':
 				$t_field_localized = lang_get( 'category' );
 				break;
+			case 'category_id':
+				$t_field_localized = lang_get( 'category' );
+				$p_old_value = category_full_name( $p_old_value, false );
+				$p_new_value = category_full_name( $p_new_value, false );
+				break;
 			case 'status':
 				$p_old_value = get_enum_element( 'status', $p_old_value );
 				$p_new_value = get_enum_element( 'status', $p_new_value );
diff --git a/core/my_view_inc.php b/core/my_view_inc.php
index dbc86b3..e46e7ab 100644
--- a/core/my_view_inc.php
+++ b/core/my_view_inc.php
@@ -279,11 +279,10 @@
 			# type project name if viewing 'all projects' or bug is in subproject
 			if ( ON == config_get( 'show_bug_project_links' ) &&
 				helper_get_current_project() != $v_project_id ) {
-				echo '[';
-				print( $project_name );
-				echo '] ';
+				echo string_display( category_full_name( $v_category_id ) );
+			} else {
+				echo string_display( category_full_name( $v_category_id, false ) );
 			}
-			echo string_display( $v_category );
 
 			if ( $v_last_updated > strtotime( '-'.$t_filter['highlight_changed'].' hours' ) ) {
 				echo ' - <b>' . $t_last_updated . '</b>';
diff --git a/core/print_api.php b/core/print_api.php
index 65c4795..34ecb7c 100644
--- a/core/print_api.php
+++ b/core/print_api.php
@@ -424,7 +424,7 @@
 
 	# --------------------
 	# print a news item given a row in the news table.
-        function print_news_entry_from_row( $p_news_row ) {
+      function print_news_entry_from_row( $p_news_row ) {
 		extract( $p_news_row, EXTR_PREFIX_ALL, 'v' );
 		print_news_entry( $v_headline, $v_body, $v_poster_id, $v_view_state, $v_announcement, $v_date_posted );
 	}
@@ -673,12 +673,14 @@
 			PRINT ">$v_name</option>";
 		} # end for
 	}
+
 	# --------------------
 	# Since categories can be orphaned we need to grab all unique instances of category
 	# We check in the project category table and in the bug table
 	# We put them all in one array and make sure the entries are unique
-	function print_category_option_list( $p_category='', $p_project_id = null ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
+	function print_category_option_list( $p_category_id = 0, $p_project_id = null ) {
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
 		if ( null === $p_project_id ) {
 			$c_project_id = helper_get_current_project();
@@ -690,81 +692,61 @@
 
 		# grab all categories in the project category table
 		$cat_arr = array();
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_project_category_table
+		$query = "SELECT id,name FROM $t_category_table
 				WHERE $t_project_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
-		}
 
-		# Add the default option if not in the list retrieved from DB		
-		# This is useful for default categories and when updating an
-		# issue with a deleted category.
-		if ( !is_blank( $p_category ) && !in_array( $p_category, $cat_arr ) ) {
-			$cat_arr[] = $p_category;
+		while ( $row = db_fetch_array( $result ) ) {
+			$cat_arr[$row['id']] = $row['name'];
 		}
+		asort($cat_arr);
 
-		sort( $cat_arr );
-		$cat_arr = array_unique( $cat_arr );
-
-		foreach( $cat_arr as $t_category ) {
-			PRINT "<option value=\"$t_category\"";
-			check_selected( $t_category, $p_category );
-			PRINT ">$t_category</option>";
+		foreach( $cat_arr as $t_category_id => $t_name ) {
+			PRINT "<option value=\"$t_category_id\"";
+			check_selected( $p_category_id, $t_category_id );
+			PRINT ">$t_name</option>";
 		}
 	}
 	# --------------------
 	# Since categories can be orphaned we need to grab all unique instances of category
 	# We check in the project category table and in the bug table
 	# We put them all in one array and make sure the entries are unique
-	function print_category_complete_option_list( $p_category='', $p_project_id = null ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
-		$t_mantis_bug_table = config_get_global( 'mantis_bug_table' );
+	function print_category_complete_option_list( $p_category_id = 0, $p_project_id = null ) {
+		return print_category_option_list( $p_category_id, $p_project_id );
+	}
+
+	# ---------
+	# Now that categories are identified by numerical ID, we need an old-style name
+	# based option list to keep existing filter functionality.
+	function print_category_filter_option_list( $p_category_name = '', $p_project_id = null ) {
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
 		if ( null === $p_project_id ) {
-			$t_project_id = helper_get_current_project();
+			$c_project_id = helper_get_current_project();
 		} else {
-			$t_project_id = $p_project_id;
+			$c_project_id = db_prepare_int( $p_project_id );
 		}
 
-		$t_project_where = helper_project_specific_where( $t_project_id );
+		$t_project_where = helper_project_specific_where( $c_project_id );
 
 		# grab all categories in the project category table
 		$cat_arr = array();
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_project_category_table
+		$query = "SELECT DISTINCT name FROM $t_category_table
 				WHERE $t_project_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
-		}
 
-		# grab all categories in the bug table
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_bug_table
-				WHERE $t_project_where
-				ORDER BY category";
-		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
+		while ( $row = db_fetch_array( $result ) ) {
+			$cat_arr[] = $row['name'];
 		}
-		sort( $cat_arr );
-		$cat_arr = array_unique( $cat_arr );
+		sort($cat_arr);
 
-		foreach( $cat_arr as $t_category ) {
-			PRINT "<option value=\"$t_category\"";
-			check_selected( $p_category, $t_category );
-			PRINT ">$t_category</option>";
+		foreach( $cat_arr as $t_name ) {
+			PRINT "<option value=\"$t_name\"";
+			check_selected( $p_category_name, $t_name );
+			PRINT ">$t_name</option>";
 		}
 	}
 	
@@ -1257,26 +1239,26 @@
 	}
 	# --------------------
 	function print_project_category_string( $p_project_id ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_mantis_category_table = config_get( 'mantis_category_table' );
 
 		$c_project_id = db_prepare_int( $p_project_id );
 
-		$query = "SELECT category
-				FROM $t_mantis_project_category_table
-				WHERE project_id=" . db_param(0) . "
-				ORDER BY category";
-		$result = db_query_bound( $query, Array( $c_project_id ) );
+		$query = "SELECT name
+				FROM $t_mantis_category_table
+				WHERE project_id='$c_project_id'
+				ORDER BY name";
+		$result = db_query( $query );
 		$category_count = db_num_rows( $result );
 		$t_string = '';
 
 		for ($i=0;$i<$category_count;$i++) {
 			$row = db_fetch_array( $result );
-			$t_category = $row['category'];
+			$t_name = $row['name'];
 
 			if ( $i+1 < $category_count ) {
-				$t_string .= $t_category.', ';
+				$t_string .= $t_name.', ';
 			} else {
-				$t_string .= $t_category;
+				$t_string .= $t_name;
 			}
 		}
 
@@ -1500,7 +1482,7 @@
 		}
 		print_page_link( $p_page, $t_last, $p_end, $p_current, $p_temp_filter_id );
 
-    	print( " ]" );
+  	print( " ]" );
 	}
 	# --------------------
 	# print a mailto: href link
diff --git a/core/summary_api.php b/core/summary_api.php
index e0a8e4f..3d514da 100644
--- a/core/summary_api.php
+++ b/core/summary_api.php
@@ -536,6 +536,7 @@
 	# print a bug count per category
 	function summary_print_by_category() {
 		$t_mantis_bug_table = config_get_global( 'mantis_bug_table' );
+		$t_mantis_category_table = config_get( 'mantis_category_table' );
 		$t_mantis_project_table = config_get_global( 'mantis_project_table' );
 		$t_summary_category_include_project = config_get( 'summary_category_include_project' );
 
@@ -548,15 +549,17 @@
 		}
 		$t_project_query = ( ON == $t_summary_category_include_project ) ? 'project_id, ' : '';
 
-		$query = "SELECT COUNT(id) as bugcount, $t_project_query category, status
+		$query = "SELECT COUNT(id) as bugcount, $t_project_query c.name AS category_name, c.id AS categor_id status
 				FROM $t_mantis_bug_table
-				WHERE category>'' AND $specific_where
-				GROUP BY $t_project_query category, status
-				ORDER BY $t_project_query category, status";
+				JOIN $t_mantis_category_table AS c ON category_id=c.id
+				WHERE $specific_where
+				GROUP BY $t_project_query category_name, status
+				ORDER BY $t_project_query category_name, status";
 
 		$result = db_query( $query );
 
-		$last_category = -1;
+		$last_category_name = -1;
+		$last_category_id = -1;
 		$last_project = -1;
 		$t_bugs_open = 0;
 		$t_bugs_resolved = 0;
@@ -569,13 +572,13 @@
 		while ( $row = db_fetch_array( $result ) ) {
 			extract( $row, EXTR_PREFIX_ALL, 'v' );
 
-			if ( ( $v_category != $last_category ) && ( $last_category != -1 ) ) {
-				$label = $last_category;
+			if ( ( $v_category_id != $last_category_id ) && ( $last_category_id != -1 ) ) {
+				$label = $last_category_name;
 				if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 					$label = sprintf( '[%s] %s', project_get_name( $last_project ), $label );
 				}
 
-				$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category );
+				$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category_id );
 				if ( 0 < $t_bugs_open ) {
 					$t_bugs_open = $t_bug_link . '&amp;hide_status=' . RESOLVED . '">' . $t_bugs_open . '</a>';
 				}
@@ -606,19 +609,20 @@
 				$t_bugs_open += $row['bugcount'];
 			}
 
-			$last_category = $v_category;
+			$last_category_id = $v_category_id;
+			$last_category_name = $v_category_name;
 			if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 				$last_project = $v_project_id;
 			}
 		}
 
 		if ( 0 < $t_bugs_total ) {
-			$label = $last_category;
+			$label = $last_category_name;
 			if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 				$label = sprintf( '[%s] %s', project_get_name( $last_project ), $label );
 			}
 
-			$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category );
+			$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category_id );
 			if ( !is_blank( $t_bug_link ) ) {
 				if ( 0 < $t_bugs_open ) {
 					$t_bugs_open = $t_bug_link . '&amp;hide_status=' . RESOLVED . '">' . $t_bugs_open . '</a>';
diff --git a/graphs/graph_by_category.php b/graphs/graph_by_category.php
index a969d25..8e5f098 100644
--- a/graphs/graph_by_category.php
+++ b/graphs/graph_by_category.php
@@ -36,12 +36,13 @@
 
 	$data_category_arr = array();
 	$data_count_arr = array();
-	$query = "SELECT category, COUNT(category) as count
+	$query = "SELECT c.name AS name, COUNT(name) as count
 			FROM mantis_bug_table
-			WHERE project_id=" . db_param(0) . "
-			GROUP BY category
-			ORDER BY category";
-	$result = db_query_bound( $query, Array( $t_project_id ) );
+			JOIN mantis_category_table AS c
+			WHERE project_id='$t_project_id'
+			GROUP BY name
+			ORDER BY name";
+	$result = db_query( $query );
 	$category_count = db_num_rows( $result );
 	$total = 0;
 	$longest_size = 0;
@@ -50,11 +51,11 @@
 		extract( $row );
 
 		$total += $count;
-		$data_category_arr[] = $category;
+		$data_category_arr[] = $name;
 		$data_count_arr[] = $count;
 
-		if ( strlen( $category ) > $longest_size ) {
-			$longest_size = strlen( $category );
+		if ( strlen( $name ) > $longest_size ) {
+			$longest_size = strlen( $name );
 		}
 	}
 	$longest_size++;
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index 4d1527d..afc07b0 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -592,6 +592,7 @@ $s_update_simple_link = 'Update Simple';
 $s_updating_bug_advanced_title = 'Updating Issue Information';
 $s_id = 'ID';
 $s_category = 'Category';
+$s_no_category = 'N/A';
 $s_severity = 'Severity';
 $s_reproducibility = 'Reproducibility';
 $s_date_submitted = 'Date Submitted';
diff --git a/manage_proj_cat_add.php b/manage_proj_cat_add.php
index 27717f9..9675b08 100644
--- a/manage_proj_cat_add.php
+++ b/manage_proj_cat_add.php
@@ -30,25 +30,25 @@
 	auth_reauthenticate();
 
 	$f_project_id	= gpc_get_int( 'project_id' );
-	$f_category		= gpc_get_string( 'category' );
+	$f_name			= gpc_get_string( 'name' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	if ( is_blank( $f_category ) ) {
+	if ( is_blank( $f_name ) ) {
 		trigger_error( ERROR_EMPTY_FIELD, ERROR );
 	}
 
-	$t_categories = explode( '|', $f_category );
-	$t_category_count = count( $t_categories );
+	$t_names = explode( '|', $f_name );
+	$t_category_count = count( $t_names );
 
-	foreach ( $t_categories as $t_category ) {
-		if ( is_blank( $t_category ) ) {
+	foreach ( $t_names as $t_name ) {
+		if ( is_blank( $t_name ) ) {
 			continue;
 		}
 
-		$t_category = trim( $t_category );
-		if ( category_is_unique( $f_project_id, $t_category ) ) {
-			category_add( $f_project_id, $t_category );
+		$t_name = trim( $t_name );
+		if ( category_is_unique( $f_project_id, $t_name ) ) {
+			category_add( $f_project_id, $t_name );
 		} else if ( 1 == $t_category_count ) {
 			# We only error out on duplicates when a single value was
 			#  given.  If multiple values were given, we just add the
diff --git a/manage_proj_cat_copy.php b/manage_proj_cat_copy.php
index d7b7c12..f205c71 100644
--- a/manage_proj_cat_copy.php
+++ b/manage_proj_cat_copy.php
@@ -50,10 +50,10 @@
 	$rows = category_get_all_rows( $t_src_project_id );
 
 	foreach ( $rows as $row ) {
-		$t_category = $row['category'];
+		$t_name = $row['name'];
 
-		if ( category_is_unique( $t_dst_project_id, $t_category ) ) {
-			category_add( $t_dst_project_id, $t_category );
+		if ( category_is_unique( $t_dst_project_id, $t_name ) ) {
+			category_add( $t_dst_project_id, $t_name );
 		}
 	}
 
diff --git a/manage_proj_cat_delete.php b/manage_proj_cat_delete.php
index 6dc7966..934e7bf 100644
--- a/manage_proj_cat_delete.php
+++ b/manage_proj_cat_delete.php
@@ -29,19 +29,22 @@
 
 	auth_reauthenticate();
 
-	$f_project_id = gpc_get_int( 'project_id' );
-	$f_category = gpc_get_string( 'category' );
+	$f_category_id = gpc_get_string( 'id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
+	$t_row = category_get_row( $f_category_id );
+	$t_name = category_full_name( $f_category_id );
+	$t_project_id = $t_row['project_id'];
+
 	# Confirm with the user
 	helper_ensure_confirmed( lang_get( 'category_delete_sure_msg' ) .
-		'<br/>' . lang_get( 'category' ) . ': ' . $f_category,
+		'<br/>' . lang_get( 'category' ) . ': ' . $t_name,
 		lang_get( 'delete_category_button' ) );
 
-	category_remove( $f_project_id, $f_category );
+	category_remove( $f_category_id );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
 
 	html_page_top1();
 	html_meta_redirect( $t_redirect_url );
diff --git a/manage_proj_cat_edit_page.php b/manage_proj_cat_edit_page.php
index ca00e1b..ed69979 100644
--- a/manage_proj_cat_edit_page.php
+++ b/manage_proj_cat_edit_page.php
@@ -29,13 +29,14 @@
 
 	auth_reauthenticate();
 
-	$f_project_id	= gpc_get_int( 'project_id' );
-	$f_category		= gpc_get_string( 'category' );
+	$f_category_id		= gpc_get_string( 'id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	$t_row = category_get_row( $f_project_id, $f_category );
+	$t_row = category_get_row( $f_category_id );
 	$t_assigned_to = $t_row['user_id'];
+	$t_project_id = $t_row['project_id'];
+	$t_name = $t_row['name'];
 	
 	html_page_top1();
 	html_page_top2();
@@ -54,12 +55,11 @@
 </tr>
 <tr <?php echo helper_alternate_class() ?>>
 	<td class="category">
-		<input type="hidden" name="project_id" value="<?php echo string_attribute( $f_project_id ) ?>" />
-		<input type="hidden" name="category" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="hidden" name="category_id" value="<?php echo string_attribute( $f_category_id ) ?>" />
 		<?php echo lang_get( 'category' ) ?>
 	</td>
 	<td>
-		<input type="text" name="new_category" size="32" maxlength="64" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="text" name="name" size="32" maxlength="64" value="<?php echo string_attribute( $t_name ) ?>" />
 	</td>
 </tr>
 <tr <?php echo helper_alternate_class() ?>>
@@ -69,7 +69,7 @@
 	<td>
 		<select name="assigned_to">
 			<option value="0"></option>
-			<?php print_assign_to_option_list( $t_assigned_to, $f_project_id ) ?>
+			<?php print_assign_to_option_list( $t_assigned_to, $t_project_id ) ?>
 		</select>
 	</td>
 </tr>
@@ -89,8 +89,7 @@
 
 <div class="border-center">
 	<form method="post" action="manage_proj_cat_delete.php">
-		<input type="hidden" name="project_id" value="<?php echo string_attribute( $f_project_id ) ?>" />
-		<input type="hidden" name="category" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="hidden" name="category_id" value="<?php echo string_attribute( $f_category_id ) ?>" />
 		<input type="submit" class="button" value="<?php echo lang_get( 'delete_category_button' ) ?>" />
 	</form>
 </div>
diff --git a/manage_proj_cat_update.php b/manage_proj_cat_update.php
index 05afd1e..98610e8 100644
--- a/manage_proj_cat_update.php
+++ b/manage_proj_cat_update.php
@@ -29,29 +29,28 @@
 
 	auth_reauthenticate();
 
-	$f_project_id		= gpc_get_int( 'project_id' );
-	$f_category			= gpc_get_string( 'category' );
-	$f_new_category		= gpc_get_string( 'new_category' );
+	$f_category_id		= gpc_get_int( 'category_id' );
+	$f_name				= trim( gpc_get_string( 'name' ) );
 	$f_assigned_to		= gpc_get_int( 'assigned_to', 0 );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	if ( is_blank( $f_new_category ) ) {
+	if ( is_blank( $f_name ) ) {
 		trigger_error( ERROR_EMPTY_FIELD, ERROR );
 	}
 
-	$f_category		= trim( $f_category );
-	$f_new_category	= trim( $f_new_category );
+	$t_row = category_get_row( $f_category_id );
+	$t_old_name = $t_row['name'];
+	$t_project_id = $t_row['project_id'];
 
 	# check for duplicate
-	if ( strtolower( $f_category ) == strtolower( $f_new_category ) ||
-		 category_is_unique( $f_project_id, $f_new_category ) ) {
-		category_update( $f_project_id, $f_category, $f_new_category, $f_assigned_to );
-	} else {
-		trigger_error( ERROR_CATEGORY_DUPLICATE, ERROR );
+	if ( strtolower( $f_name ) != strtolower( $t_old_name ) ) {
+		category_ensure_unique( $t_project_id, $f_name );
 	}
+	
+	category_update( $f_category_id, $f_name, $f_assigned_to );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
 
 	html_page_top1();
 
diff --git a/manage_proj_edit_page.php b/manage_proj_edit_page.php
index 41c4304..13c83a2 100644
--- a/manage_proj_edit_page.php
+++ b/manage_proj_edit_page.php
@@ -302,7 +302,8 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	}
 
 	foreach ( $t_categories as $t_category ) {
-		$t_name = $t_category['category'];
+		$t_id = $t_category['id'];
+		$t_name = $t_category['name'];
 
 		if ( NO_USER != $t_category['user_id'] && user_exists( $t_category['user_id'] )) {
 			$t_user_name = user_get_name( $t_category['user_id'] );
@@ -320,11 +321,11 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 			</td>
 			<td class="center">
 				<?php
-					$t_name = urlencode( $t_name );
+					$t_id = urlencode( $t_id );
 
-					print_button( 'manage_proj_cat_edit_page.php?project_id=' . $f_project_id . '&amp;category=' . $t_name, lang_get( 'edit_link' ) );
+					print_button( 'manage_proj_cat_edit_page.php?id=' . $t_id, lang_get( 'edit_link' ) );
 					echo '&nbsp;';
-					print_button( 'manage_proj_cat_delete.php?project_id=' . $f_project_id . '&amp;category=' . $t_name, lang_get( 'delete_link' ) );
+					print_button( 'manage_proj_cat_delete.php?id=' . $t_id, lang_get( 'delete_link' ) );
 				?>
 			</td>
 		</tr>
@@ -337,7 +338,7 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	<td class="left" colspan="3">
 		<form method="post" action="manage_proj_cat_add.php">
 			<input type="hidden" name="project_id" value="<?php echo $f_project_id ?>" />
-			<input type="text" name="category" size="32" maxlength="64" />
+			<input type="text" name="name" size="32" maxlength="64" />
 			<input type="submit" class="button" value="<?php echo lang_get( 'add_category_button' ) ?>" />
 		</form>
 	</td>
diff --git a/my_view_page.php b/my_view_page.php
index febd47b..d286294 100644
--- a/my_view_page.php
+++ b/my_view_page.php
@@ -34,6 +34,9 @@
 
 	$t_current_user_id = auth_get_current_user_id();
 
+	# Improve performance by caching category data in one pass
+	category_get_all_rows( helper_get_current_project() );
+
 	compress_enable();
 
 	html_page_top1( lang_get( 'my_view_link' ) );
diff --git a/view_all_inc.php b/view_all_inc.php
index 16e4992..8131b17 100644
--- a/view_all_inc.php
+++ b/view_all_inc.php
@@ -43,6 +43,9 @@
 	$t_icon_path = config_get( 'icon_path' );
 	$t_update_bug_threshold = config_get( 'update_bug_threshold' );
 
+	# Improve performance by caching category data in one pass
+	category_get_all_rows( helper_get_current_project() );
+
 	$t_columns = helper_get_columns_to_view( COLUMNS_TARGET_VIEW_PAGE );
 
 	$col_count = sizeof( $t_columns );
mantis-categories-2007-11-12.patch (60,540 bytes)   
diff --git a/admin/install.php b/admin/install.php
index be2ef6a..a74c593 100644
--- a/admin/install.php
+++ b/admin/install.php
@@ -28,6 +28,7 @@
 	$g_skip_open_db = true;  # don't open the database in database_api.php
 	define( 'PLUGINS_DISABLED', true );
 	@require_once( dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'core.php' );
+	@require_once( 'install_functions.php' );
 	$g_error_send_page_header = false; # bypass page headers in error handler
 
 	define( 'BAD', 0 );
diff --git a/admin/install_functions.php b/admin/install_functions.php
new file mode 100644
index 0000000..ddda15f
--- /dev/null
+++ b/admin/install_functions.php
@@ -0,0 +1,64 @@
+<?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: $
+	# --------------------------------------------------------
+
+	/**
+	 * Update functions for the installation schema's 'UpdateFunction' option.
+	 * All functions must be name install_<function_name> and referenced as just <function_name>.
+	 */
+
+	/**
+	 * Migrate the legacy category data to the new category_id-based schema.
+	 */
+	function install_category_migrate() {
+		$t_bug_table = config_get( 'mantis_bug_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_category_table = config_get( 'mantis_project_category_table' );
+
+		$query = "SELECT project_id, category FROM $t_project_category_table ORDER BY project_id, category";
+		$t_category_result = db_query( $query );
+
+		$query = "SELECT project_id, category FROM $t_bug_table ORDER BY project_id, category";
+		$t_bug_result = db_query( $query );
+
+		$t_data = Array();
+
+		# Find categories specified by project
+		while ( $row = db_fetch_array( $t_category_result ) ) {
+			$t_project_id = $row['project_id'];
+			$t_name = $row['category'];
+			$t_data[$t_project_id][$t_name] = true;
+		}
+
+		# Find orphaned categories from bugs
+		while ( $row = db_fetch_array( $t_bug_result ) ) {
+			$t_project_id = $row['project_id'];
+			$t_name = $row['category'];
+
+			$t_data[$t_project_id][$t_name] = true;
+		}
+
+		# In every project, go through all the categories found, and create them and update the bug
+		foreach ( $t_data as $t_project_id => $t_categories ) {
+			foreach ( $t_categories as $t_name => $t_true ) {
+				$query = "INSERT INTO $t_category_table ( name, project_id ) VALUES ( " . db_param(0) . ', ' . db_param(1) . ' )';
+				db_query_bound( $query, array( $t_name, $t_project_id ) );
+				$t_category_id = db_insert_id( $t_category_table );
+
+				$query = "UPDATE $t_bug_table SET category_id=" . db_param(0) . '
+							WHERE project_id=' . db_param(1) . ' AND category=' . db_param(2);
+				db_query_bound( $query, array( $t_category_id, $t_project_id, $t_name ) );
+			}
+		}
+
+		# return 2 because that's what ADOdb/DataDict does when things happen properly
+		return 2;
+	}
+
diff --git a/admin/schema.php b/admin/schema.php
index 440504e..a186339 100644
--- a/admin/schema.php
+++ b/admin/schema.php
@@ -370,4 +370,22 @@ $upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_plugin_table' ),
 	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
 
 $upgrade[] = Array('AlterColumnSQL', Array( config_get_global( 'mantis_user_pref_table' ), "redirect_delay 	I NOTNULL DEFAULT 0" ) );
+
+$upgrade[] = Array( 'CreateTableSQL', Array( config_get( 'mantis_category_table' ), "
+	id				I		UNSIGNED NOTNULL PRIMARY AUTOINCREMENT,
+	project_id		I		UNSIGNED NOTNULL DEFAULT '0',
+	user_id			I		UNSIGNED NOTNULL DEFAULT '0',
+	name			C(128)	NOTNULL DEFAULT \" '' \",
+	status			I		UNSIGNED NOTNULL DEFAULT '0'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
+$upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_project_name', config_get( 'mantis_category_table' ), 'project_id, name', array( 'UNIQUE' ) ) );
+$upgrade[] = Array( 'InsertData', Array( config_get( 'mantis_category_table' ), "
+	( project_id, user_id, name, status ) VALUES
+	( '0', '0', 'None', '0' ) " ) );
+$upgrade[] = Array( 'AddColumnSQL', Array( config_get( 'mantis_bug_table' ), "category_id I UNSIGNED NOTNULL DEFAULT '0'" ) );
+$upgrade[] = Array( 'UpdateFunction', "category_migrate" );
+$upgrade[] = Array( 'DropColumnSQL', Array( config_get( 'mantis_bug_table' ), "category" ) );
+$upgrade[] = Array( 'DropTableSQL', Array( config_get( 'mantis_project_category_table' ) ) );
+$upgrade[] = Array( 'AddColumnSQL', Array( config_get( 'mantis_project_table' ), "category_id I UNSIGNED NOTNULL DEFAULT '0'" ) );
+
 ?>
diff --git a/bug_report.php b/bug_report.php
index 58588dc..81f7a0f 100644
--- a/bug_report.php
+++ b/bug_report.php
@@ -44,7 +44,7 @@
 	$t_bug_data->handler_id			= gpc_get_int( 'handler_id', 0 );
 	$t_bug_data->view_state			= gpc_get_int( 'view_state', config_get( 'default_bug_view_status' ) );
 
-	$t_bug_data->category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+	$t_bug_data->category_id			= gpc_get_int( 'category_id', 0 );
 	$t_bug_data->reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 	$t_bug_data->severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 	$t_bug_data->priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -171,7 +171,7 @@
 ?>
 	<p>
 	<form method="post" action="<?php echo string_get_bug_report_url() ?>">
-		<input type="hidden" name="category" 		value="<?php echo $t_bug_data->category ?>" />
+		<input type="hidden" name="category_id" 	value="<?php echo $t_bug_data->category_id ?>" />
 		<input type="hidden" name="severity" 		value="<?php echo $t_bug_data->severity ?>" />
 		<input type="hidden" name="reproducibility" 	value="<?php echo $t_bug_data->reproducibility ?>" />
 		<input type="hidden" name="profile_id" 		value="<?php echo $t_bug_data->profile_id ?>" />
diff --git a/bug_report_advanced_page.php b/bug_report_advanced_page.php
index ed694e3..f6d4c8a 100644
--- a/bug_report_advanced_page.php
+++ b/bug_report_advanced_page.php
@@ -82,7 +82,7 @@
 		$f_profile_id			= 0;
 		$f_handler_id			= $t_bug->handler_id;
 
-		$f_category				= $t_bug->category;
+		$f_category_id			= $t_bug->category_id;
 		$f_reproducibility		= $t_bug->reproducibility;
 		$f_severity				= $t_bug->severity;
 		$f_priority				= $t_bug->priority;
@@ -105,7 +105,7 @@
 		$f_profile_id			= gpc_get_int( 'profile_id', 0 );
 		$f_handler_id			= gpc_get_int( 'handler_id', 0 );
 
-		$f_category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+		$f_category_id			= gpc_get_int( 'category_id', 0 );
 		$f_reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 		$f_severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 		$f_priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -161,13 +161,13 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
 			<?php 
-				if ( is_blank( $f_category ) ) {
+				if ( 0 === $f_category_id ) {
 					echo '<option value="" selected="selected">', string_attribute( lang_get( 'select_option' ) ), '</option>';
 				}
 
-				print_category_option_list( $f_category ); 
+				print_category_option_list( $f_category_id ); 
 			?>
 		</select>
 	</td>
@@ -555,7 +555,7 @@
 <?php if ( ON == config_get( 'use_javascript' ) ) { ?>
 <script type="text/javascript" language="JavaScript">
 <!--
-	window.document.report_bug_form.category.focus();
+	window.document.report_bug_form.category_id.focus();
 -->
 </script>
 <?php } ?>
diff --git a/bug_report_page.php b/bug_report_page.php
index 4446331..7cc53aa 100644
--- a/bug_report_page.php
+++ b/bug_report_page.php
@@ -73,7 +73,7 @@
 	    access_ensure_project_level( config_get( 'report_bug_threshold' ) );
 
 	    $f_product_version		= $t_bug->version;
-		$f_category				= $t_bug->category;
+		$f_category_id			= $t_bug->category_id;
 		$f_reproducibility		= $t_bug->reproducibility;
 		$f_severity				= $t_bug->severity;
 		$f_priority				= $t_bug->priority;
@@ -87,7 +87,7 @@
 	    access_ensure_project_level( config_get( 'report_bug_threshold' ) );
 
 		$f_product_version		= gpc_get_string( 'product_version', '' );
-		$f_category				= gpc_get_string( 'category', config_get( 'default_bug_category' ) );
+		$f_category_id			= gpc_get_int( 'category_id', 0 );
 		$f_reproducibility		= gpc_get_int( 'reproducibility', config_get( 'default_bug_reproducibility' ) );
 		$f_severity				= gpc_get_int( 'severity', config_get( 'default_bug_severity' ) );
 		$f_priority				= gpc_get_int( 'priority', config_get( 'default_bug_priority' ) );
@@ -143,13 +143,13 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
 			<?php 
-				if ( is_blank( $f_category ) ) {
+				if ( 0 === $f_category_id ) {
 					echo '<option value="" selected="selected">', string_attribute( lang_get( 'select_option' ) ), '</option>';
 				}
 
-				print_category_option_list( $f_category ); 
+				print_category_option_list( $f_category_id ); 
 			?>
 		</select>
 	</td>
@@ -385,7 +385,7 @@
 <?php if ( ON == config_get( 'use_javascript' ) ) { ?>
 <script type="text/javascript" language="JavaScript">
 <!--
-	window.document.report_bug_form.category.focus();
+	window.document.report_bug_form.category_id.focus();
 -->
 </script>
 <?php } ?>
diff --git a/bug_update.php b/bug_update.php
index dd0a2e6..5b16e80 100644
--- a/bug_update.php
+++ b/bug_update.php
@@ -67,7 +67,7 @@
 	$t_bug_data->status				= gpc_get_int( 'status', $t_bug_data->status );
 	$t_bug_data->resolution			= gpc_get_int( 'resolution', $t_bug_data->resolution );
 	$t_bug_data->projection			= gpc_get_int( 'projection', $t_bug_data->projection );
-	$t_bug_data->category			= gpc_get_string( 'category', $t_bug_data->category );
+	$t_bug_data->category_id		= gpc_get_int( 'category_id', $t_bug_data->category_id );
 	$t_bug_data->eta				= gpc_get_int( 'eta', $t_bug_data->eta );
 	$t_bug_data->os					= gpc_get_string( 'os', $t_bug_data->os );
 	$t_bug_data->os_build			= gpc_get_string( 'os_build', $t_bug_data->os_build );
diff --git a/bug_update_advanced_page.php b/bug_update_advanced_page.php
index cf429d3..03ed16d 100644
--- a/bug_update_advanced_page.php
+++ b/bug_update_advanced_page.php
@@ -118,8 +118,8 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
-		<?php print_category_option_list( $t_bug->category, $t_bug->project_id ) ?>
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
+		<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
 	</td>
 
diff --git a/bug_update_page.php b/bug_update_page.php
index 1af51da..151b1bc 100644
--- a/bug_update_page.php
+++ b/bug_update_page.php
@@ -121,8 +121,8 @@
 		<?php if ( $t_changed_project ) {
 			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
 		} ?>
-		<select <?php echo helper_get_tab_index() ?> name="category">
-			<?php print_category_option_list( $t_bug->category, $t_bug->project_id ) ?>
+		<select <?php echo helper_get_tab_index() ?> name="category_id">
+			<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
 	</td>
 
diff --git a/bug_view_advanced_page.php b/bug_view_advanced_page.php
index d16f69e..b0581e7 100644
--- a/bug_view_advanced_page.php
+++ b/bug_view_advanced_page.php
@@ -168,8 +168,7 @@
 	<!-- Category -->
 	<td>
 		<?php
-			$t_project_name = string_display( project_get_field( $t_bug->project_id, 'name' ) );
-			echo "[$t_project_name] $t_bug->category";
+			echo string_display( category_full_name( $t_bug->category_id ) );
 		?>
 	</td>
 
diff --git a/bug_view_page.php b/bug_view_page.php
index 5ff445a..ad82077 100644
--- a/bug_view_page.php
+++ b/bug_view_page.php
@@ -171,8 +171,7 @@
 	<!-- Category -->
 	<td>
 		<?php
-			$t_project_name = string_display( project_get_field( $t_bug->project_id, 'name' ) );
-			echo "[$t_project_name] $t_bug->category";
+			echo string_display( category_full_name( $t_bug->category_id ) );
 		?>
 	</td>
 
diff --git a/config_defaults_inc.php b/config_defaults_inc.php
index d1da3ab..069dd3e 100644
--- a/config_defaults_inc.php
+++ b/config_defaults_inc.php
@@ -684,9 +684,6 @@
 	# Default bug reproducibility when reporting a new bug
 	$g_default_bug_reproducibility = REPRODUCIBILITY_HAVENOTTRIED;
 
-	# Default bug category when reporting a new bug
-	$g_default_bug_category = '';
-
 	# --- viewing defaults ------------
 	# site defaults for viewing preferences
 	$g_default_limit_view	= 50;
@@ -1370,9 +1367,10 @@
 	$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%';
+	$g_mantis_category_table				= '%db_table_prefix%_category%db_table_suffix%';
 	$g_mantis_news_table					= '%db_table_prefix%_news%db_table_suffix%';
 	$g_mantis_plugin_table					= '%db_table_prefix%_plugin%db_table_suffix%';
-	$g_mantis_project_category_table		= '%db_table_prefix%_project_category%db_table_suffix%';
+	$g_mantis_project_category_table		= '%db_table_prefix%_project_category%db_table_suffix%'; # Legacy table
 	$g_mantis_project_file_table			= '%db_table_prefix%_project_file%db_table_suffix%';
 	$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%';
diff --git a/core/bug_api.php b/core/bug_api.php
index 082b1d2..5f1a004 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -52,7 +52,7 @@
 		var $status = NEW_;
 		var $resolution = OPEN;
 		var $projection = 10;
-		var $category = '';
+		var $category_id = 0;
 		var $date_submitted = '';
 		var $last_updated = '';
 		var $eta = 10;
@@ -381,7 +381,7 @@
 		$c_priority				= db_prepare_int( $p_bug_data->priority );
 		$c_severity				= db_prepare_int( $p_bug_data->severity );
 		$c_reproducibility		= db_prepare_int( $p_bug_data->reproducibility );
-		$c_category				= db_prepare_string( $p_bug_data->category );
+		$c_category_id			= db_prepare_int( $p_bug_data->category_id );
 		$c_os					= db_prepare_string( $p_bug_data->os );
 		$c_os_build				= db_prepare_string( $p_bug_data->os_build );
 		$c_platform				= db_prepare_string( $p_bug_data->platform );
@@ -406,11 +406,6 @@
 			trigger_error( ERROR_EMPTY_FIELD, ERROR );
 		}
 		
-		if ( is_blank( $c_category ) ) {
-			error_parameters( lang_get( 'category' ) );
-			trigger_error( ERROR_EMPTY_FIELD, ERROR );
-		}
-
 		# Only set target_version if user has access to do so
 		if ( access_has_project_level( config_get( 'roadmap_update_threshold' ) ) ) {
 			$c_target_version	= db_prepare_string( $p_bug_data->target_version );
@@ -420,7 +415,7 @@
 
 		$t_bug_table				= config_get_global( 'mantis_bug_table' );
 		$t_bug_text_table			= config_get_global( 'mantis_bug_text_table' );
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
+		$t_category_table			= config_get_global( 'mantis_category_table' );
 
 		# Insert text information
 		$query = "INSERT INTO $t_bug_text_table
@@ -443,9 +438,9 @@
 			# if a default user is associated with the category and we know at this point
 			# that that the bug was not assigned to somebody, then assign it automatically.
 			$query = "SELECT user_id
-					  FROM $t_project_category_table
-					  WHERE project_id=" .db_param(0) . " AND category=" . db_param(1);
-			$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
+					  FROM $t_category_table
+					  WHERE project_id=" . db_param(0) . ' AND id=' . db_param(1);
+			$result = db_query_bound( $query, array( $c_project_id, $c_category_id ) );
 
 			if ( db_num_rows( $result ) > 0 ) {
 				$c_handler_id = $p_handler_id = db_result( $result );
@@ -466,7 +461,7 @@
 				      duplicate_id, priority,
 				      severity, reproducibility,
 				      status, resolution,
-				      projection, category,
+				      projection, category_id,
 				      date_submitted, last_updated,
 				      eta, bug_text_id,
 				      os, os_build,
@@ -481,7 +476,7 @@
 				      '0', '$c_priority',
 				      '$c_severity', '$c_reproducibility',
 				      '$t_status', '$t_resolution',
-				      10, '$c_category',
+				      10, '$c_category_id',
 				      " . db_now() . "," . db_now() . ",
 				      10, '$t_text_id',
 				      '$c_os', '$c_os_build',
@@ -782,8 +777,8 @@
 
 		$query = "SELECT id
 				  FROM $t_bug_table
-				  WHERE project_id='$c_project_id'";
-		$result = db_query( $query );
+				  WHERE project_id=" . db_param(0);
+		$result = db_query_bound( $query, array( $c_project_id ) );
 
 		$bug_count = db_num_rows( $result );
 
@@ -845,7 +840,7 @@
 					status='$c_bug_data->status',
 					resolution='$c_bug_data->resolution',
 					projection='$c_bug_data->projection',
-					category='$c_bug_data->category',
+					category_id='$c_bug_data->category_id',
 					eta='$c_bug_data->eta',
 					os='$c_bug_data->os',
 					os_build='$c_bug_data->os_build',
@@ -882,7 +877,7 @@
 		history_log_event_direct( $p_bug_id, 'status', $t_old_data->status, $p_bug_data->status );
 		history_log_event_direct( $p_bug_id, 'resolution', $t_old_data->resolution, $p_bug_data->resolution );
 		history_log_event_direct( $p_bug_id, 'projection', $t_old_data->projection, $p_bug_data->projection );
-		history_log_event_direct( $p_bug_id, 'category', $t_old_data->category, $p_bug_data->category );
+		history_log_event_direct( $p_bug_id, 'category_id', $t_old_data->category_id, $p_bug_data->category_id );
 		history_log_event_direct( $p_bug_id, 'eta',	$t_old_data->eta, $p_bug_data->eta );
 		history_log_event_direct( $p_bug_id, 'os', $t_old_data->os, $p_bug_data->os );
 		history_log_event_direct( $p_bug_id, 'os_build', $t_old_data->os_build, $p_bug_data->os_build );
@@ -1457,7 +1452,7 @@
 		$t_bug_data->status				= db_prepare_int( $p_bug_data->status );
 		$t_bug_data->resolution			= db_prepare_int( $p_bug_data->resolution );
 		$t_bug_data->projection			= db_prepare_int( $p_bug_data->projection );
-		$t_bug_data->category			= db_prepare_string( $p_bug_data->category );
+		$t_bug_data->category_id		= db_prepare_int( $p_bug_data->category_id );
 		$t_bug_data->date_submitted		= db_prepare_string( $p_bug_data->date_submitted );
 		$t_bug_data->last_updated		= db_prepare_string( $p_bug_data->last_updated );
 		$t_bug_data->eta				= db_prepare_int( $p_bug_data->eta );
@@ -1484,7 +1479,7 @@
 	# Return a copy of the bug structure with all the instvars prepared for editing
 	#  in an HTML form
 	function bug_prepare_edit( $p_bug_data ) {
-		$p_bug_data->category			= string_attribute( $p_bug_data->category );
+		$p_bug_data->category_id		= string_attribute( $p_bug_data->category_id );
 		$p_bug_data->date_submitted		= string_attribute( $p_bug_data->date_submitted );
 		$p_bug_data->last_updated		= string_attribute( $p_bug_data->last_updated );
 		$p_bug_data->os					= string_attribute( $p_bug_data->os );
@@ -1508,7 +1503,7 @@
 	# Return a copy of the bug structure with all the instvars prepared for editing
 	#  in an HTML form
 	function bug_prepare_display( $p_bug_data ) {
-		$p_bug_data->category			= string_display_line( $p_bug_data->category );
+		$p_bug_data->category_id		= string_display_line( $p_bug_data->category_id );
 		$p_bug_data->date_submitted		= string_display_line( $p_bug_data->date_submitted );
 		$p_bug_data->last_updated		= string_display_line( $p_bug_data->last_updated );
 		$p_bug_data->os					= string_display_line( $p_bug_data->os );
diff --git a/core/category_api.php b/core/category_api.php
index 97c5b8d..ec0c2c0 100644
--- a/core/category_api.php
+++ b/core/category_api.php
@@ -23,6 +23,9 @@
 
 	### Category API ###
 
+	# Category data cache (to prevent excessive db queries)
+	$g_category_cache = array();
+
 	#===================================
 	# Boolean queries and ensures
 	#===================================
@@ -30,31 +33,32 @@
 	# --------------------
 	# Check whether the category exists in the project
 	# Return true if the category exists, false otherwise
-	function category_exists( $p_project_id, $p_category ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+	function category_exists( $p_category_id ) {
+		global $g_category_cache;
+		if ( isset( $g_category_cache[$p_category_id] ) ) {
+			return true;
+		}
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$c_category_id	= db_prepare_int( $p_category_id );
 
-		$query = "SELECT COUNT(*)
-				  FROM $t_project_category_table
-				  WHERE project_id=" . db_param(0) . " AND
-						category=" . db_param(1);
-		$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
-		$category_count =  db_result( $result );
+		$t_category_table = config_get( 'mantis_category_table' );
 
-		if ( 0 < $category_count ) {
-			return true;
+		$query = "SELECT COUNT(*) FROM $t_category_table
+					WHERE id=" . db_param(0);
+		$count = db_result( db_query_bound( $query, array( $c_category_id ) ) );
+
+		if ( 0 < $count ) {
+				return true;
 		} else {
-			return false;
+				return false;
 		}
 	}
 
 	# --------------------
 	# Check whether the category exists in the project
 	# Trigger an error if it does not
-	function category_ensure_exists( $p_project_id, $p_category ) {
-		if ( !category_exists( $p_project_id, $p_category ) ) {
+	function category_ensure_exists( $p_category_id ) {
+		if ( !category_exists( $p_category_id ) ) {
 			trigger_error( ERROR_CATEGORY_NOT_FOUND, ERROR );
 		}
 	}
@@ -62,15 +66,28 @@
 	# --------------------
 	# Check whether the category is unique within a project
 	# Returns true if the category is unique, false otherwise
-	function category_is_unique( $p_project_id, $p_category ) {
-		return !category_exists( $p_project_id, $p_category );
+	function category_is_unique( $p_project_id, $p_name ) {
+		$c_project_id	= db_prepare_int( $p_project_id );
+		$c_name			= db_prepare_string( $p_name );
+
+		$t_category_table = config_get( 'mantis_category_table' );
+
+		$query = "SELECT COUNT(*) FROM $t_category_table
+					WHERE project_id=" . db_param(0) . " AND " . db_helper_like( 'name', $c_name );
+		$count = db_result( db_query_bound( $query, array( $c_project_id ) ) );
+
+		if ( 0 < $count ) {
+				return false;
+		} else {
+				return true;
+		}
 	}
 
 	# --------------------
 	# Check whether the category is unique within a project
 	# Trigger an error if it is not
-	function category_ensure_unique( $p_project_id, $p_category ) {
-		if ( !category_is_unique( $p_project_id, $p_category ) ) {
+	function category_ensure_unique( $p_project_id, $p_name ) {
+		if ( !category_is_unique( $p_project_id, $p_name ) ) {
 			trigger_error( ERROR_CATEGORY_DUPLICATE, ERROR );
 		}
 	}
@@ -82,51 +99,41 @@
 
 	# --------------------
 	# Add a new category to the project
-	function category_add( $p_project_id, $p_category ) {
+	function category_add( $p_project_id, $p_name ) {
 		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+		$c_name			= db_prepare_string( $p_name );
 
-		category_ensure_unique( $p_project_id, $p_category );
+		category_ensure_unique( $p_project_id, $p_name );
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
 
-		$query = "INSERT INTO $t_project_category_table
-					( project_id, category )
+		$query = "INSERT INTO $t_category_table
+					( project_id, name )
 				  VALUES
-					( " . db_param(0) . ',' . db_param(1) . ')';
-		db_query_bound( $query, Array( $c_project_id, $c_category ) );
+					( " . db_param(0) . ', ' . db_param(1) . ' )';
+		db_query_bound( $query, array( $c_project_id, $c_name ) );
 
 		# db_query errors on failure so:
-		return true;
+		return db_insert_id( $t_category_table );
 	}
 
 	# --------------------
 	# Update the name and user associated with the category
-	function category_update( $p_project_id, $p_category, $p_new_category, $p_assigned_to ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
-		$c_new_category	= db_prepare_string( $p_new_category );
+	function category_update( $p_category_id, $p_name, $p_assigned_to ) {
+		$c_category_id	= db_prepare_int( $p_category_id );
+		$c_name			= db_prepare_string( $p_name );
 		$c_assigned_to	= db_prepare_int( $p_assigned_to );
 
-		category_ensure_exists( $p_project_id, $p_category );
+		category_ensure_exists( $p_category_id );
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "UPDATE $t_project_category_table
-				  SET category=" . db_param(0) . ",
-				  	  user_id=" . db_param(1) . "
-				  WHERE category=" . db_param(2) . " AND
-						project_id=" . db_param(3);
-		db_query_bound( $query, Array( $c_new_category, $c_assigned_to, $c_category, $c_project_id ) );
-
-		if ( $p_category != $p_new_category ) {
-			$query = "UPDATE $t_bug_table
-					  SET category=" . db_param(0) . "
-					  WHERE category=" . db_param(1) . " AND
-					  		project_id=" . db_param(2);
-			db_query_bound( $query, Array( $c_new_category, $c_category, $c_project_id ) );
-		}
+		$query = "UPDATE $t_category_table
+				  SET name=" . db_param(0) . ',
+					user_id=' . db_param(1) . '
+				  WHERE id=' . db_param(2);
+		db_query_bound( $query, array( $c_name, $c_assigned_to, $c_category_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -134,29 +141,26 @@
 
 	# --------------------
 	# Remove a category from the project
-	function category_remove( $p_project_id, $p_category, $p_new_category='' ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
-		$c_new_category	= db_prepare_string( $p_new_category );
+	function category_remove( $p_category_id, $p_new_category_id = 0 ) {
+		$c_category_id	= db_prepare_int( $p_category_id );
+		$c_new_category_id	= db_prepare_int( $p_new_category_id );
 
-		category_ensure_exists( $p_project_id, $p_category );
-		if ( !is_blank( $p_new_category ) ) {
-			category_ensure_exists( $p_project_id, $p_new_category );
+		category_ensure_exists( $p_category_id );
+		if ( 0 != $p_new_category_id ) {
+			category_ensure_exists( $p_new_category_id );
 		}
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "DELETE FROM $t_project_category_table
-				  WHERE project_id=" . db_param(0) . " AND
-						category=" . db_param(1);
-		db_query_bound( $query, Array( $c_project_id, $c_category ) );
+		$query = "DELETE FROM $t_category_table
+				  WHERE id=" . db_param(0);
+		db_query_bound( $query, array( $c_category_id ) );
 
 		$query = "UPDATE $t_bug_table
-				  SET category=" . db_param(0) . "
-				  WHERE category=" . db_param(1) . " AND
-				  		project_id=" . db_param(2);
-		db_query_bound( $query, Array( $c_new_category, $c_category, $c_project_id ) );
+				  SET category_id=" . db_param(0) . "
+				  WHERE category_id=" . db_param(1);
+		db_query_bound( $query, array( $c_new_category_id, $c_category_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -164,22 +168,26 @@
 
 	# --------------------
 	# Remove all categories associated with a project
-	function category_remove_all( $p_project_id ) {
+	function category_remove_all( $p_project_id, $p_new_category_id = 0 ) {
 		$c_project_id = db_prepare_int( $p_project_id );
+		$c_new_category_id = db_prepare_int( $p_new_category_id );
 
 		project_ensure_exists( $p_project_id );
+		if ( 0 != $p_new_category_id ) {
+			category_ensure_exists( $p_new_category_id );
+		}
 
-		$t_project_category_table	= config_get_global( 'mantis_project_category_table' );
-		$t_bug_table				= config_get_global( 'mantis_bug_table' );
+		$t_category_table	= config_get( 'mantis_category_table' );
+		$t_bug_table		= config_get( 'mantis_bug_table' );
 
-		$query = "DELETE FROM $t_project_category_table
+		$query = "DELETE FROM $t_category_table
 				  WHERE project_id=" . db_param(0);
-		db_query_bound( $query, Array( $c_project_id ) );
+		db_query_bound( $query, array( $c_project_id ) );
 
 		$query = "UPDATE $t_bug_table
-				  SET category=''
-				  WHERE project_id=" . db_param(0);
-		db_query_bound( $query, Array( $c_project_id ) );
+				  SET category=" . db_param(0) . '
+				  WHERE project_id=' . db_param(1);
+		db_query_bound( $query, array( $c_new_category_id, $c_project_id ) );
 
 		# db_query errors on failure so:
 		return true;
@@ -192,45 +200,74 @@
 
 	# --------------------
 	# Return the definition row for the category
-	function category_get_row( $p_project_id, $p_category ) {
-		$c_project_id	= db_prepare_int( $p_project_id );
-		$c_category		= db_prepare_string( $p_category );
+	function category_get_row( $p_category_id ) {
+		global $g_category_cache;
+		if ( isset( $g_category_cache[$p_category_id] ) ) {
+			return $g_category_cache[$p_category_id];
+		}
+
+		$c_category_id	= db_prepare_int( $p_category_id );
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
-		$query = "SELECT category, user_id
-				FROM $t_project_category_table
-				WHERE project_id=" . db_param(0) . " AND
-					category=" . db_param(1);
-		$result = db_query_bound( $query, Array( $c_project_id, $c_category ) );
+		$query = "SELECT c.*, p.name AS project_name FROM $t_category_table AS c
+				JOIN $t_project_table AS p
+					ON c.project_id=p.id
+				WHERE c.id=" . db_param(0);
+		$result = db_query_bound( $query, array( $c_category_id ) );
 		$count = db_num_rows( $result );
 		if ( 0 == $count ) {
 			trigger_error( ERROR_CATEGORY_NOT_FOUND, ERROR );
 		}
 
-		return db_fetch_array( $result );
+		$row = db_fetch_array( $result );
+		$g_category_cache[$p_category_id] = $row;
+		return $row;
 	}
 
 	# --------------------
 	# Return all categories for the specified project id
 	function category_get_all_rows( $p_project_id ) {
+		global $g_category_cache;
+
 		$c_project_id	= db_prepare_int( $p_project_id );
 
-		$t_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
+
+		$t_project_where = helper_project_specific_where( $c_project_id );
 
-		$query = "SELECT category, user_id
-				FROM $t_project_category_table
-				WHERE project_id=" . db_param(0) . "
-				ORDER BY category";
-		$result = db_query_bound( $query, Array( $c_project_id ) );
+		$query = "SELECT c.*, p.name AS project_name FROM $t_category_table AS c
+				JOIN $t_project_table AS p
+					ON c.project_id=p.id
+				WHERE $t_project_where
+				ORDER BY c.name ";
+		$result = db_query_bound( $query );
 		$count = db_num_rows( $result );
 		$rows = array();
 		for ( $i = 0 ; $i < $count ; $i++ ) {
 			$row = db_fetch_array( $result );
 
 			$rows[] = $row;
+			$g_category_cache[$row['id']] = $row;
 		}
 
 		return $rows;
 	}
-?>
+
+	# Helpers
+
+	function category_full_name( $p_category_id, $p_show_project=true ) {
+		if ( $p_category_id == 0 ) {
+				return lang_get( 'no_category' );
+		}
+
+		$t_row = category_get_row( $p_category_id );
+
+		if ( $p_show_project ) {
+			return '[' . $t_row['project_name'] . '] ' . $t_row['name'];
+		} else {
+			return $t_row['name'];
+		}
+	}
diff --git a/core/columns_api.php b/core/columns_api.php
index 65bc271..8f08254 100644
--- a/core/columns_api.php
+++ b/core/columns_api.php
@@ -534,7 +534,7 @@
 			echo ']</small><br />';
 		}
 
-		echo string_display( $p_row['category'] );
+		echo string_display( category_full_name( $p_row['category_id'], false ) );
 		echo '</td>';
 	}
 
diff --git a/core/filter_api.php b/core/filter_api.php
index a7b3cd6..a60033d 100644
--- a/core/filter_api.php
+++ b/core/filter_api.php
@@ -413,6 +413,7 @@
 		$t_bug_table			= config_get_global( 'mantis_bug_table' );
 		$t_bug_text_table		= config_get_global( 'mantis_bug_text_table' );
 		$t_bugnote_table		= config_get_global( 'mantis_bugnote_table' );
+		$t_category_table		= config_get_global( 'mantis_category_table' );
 		$t_custom_field_string_table	= config_get_global( 'mantis_custom_field_string_table' );
 		$t_bugnote_text_table	= config_get_global( 'mantis_bugnote_text_table' );
 		$t_project_table		= config_get_global( 'mantis_project_table' );
@@ -696,9 +697,7 @@
 			$t_clauses = array();
 
 			foreach( $t_filter['show_category'] as $t_filter_member ) {
-				$t_filter_member = stripslashes( $t_filter_member );
 				if ( META_FILTER_NONE == $t_filter_member ) {
-					array_push( $t_clauses, "''" );
 				} else {
 					$c_show_category = db_prepare_string( $t_filter_member );
 					array_push( $t_clauses, "'$c_show_category'" );
@@ -706,9 +705,9 @@
 			}
 
 			if ( 1 < count( $t_clauses ) ) {
-				array_push( $t_where_clauses, "( $t_bug_table.category in (". implode( ', ', $t_clauses ) .") )" );
+				array_push( $t_where_clauses, "( $t_bug_table.category_id in ( SELECT id FROM $t_category_table WHERE name in (". implode( ', ', $t_clauses ) .") ) )" );
 			} else {
-				array_push( $t_where_clauses, "( $t_bug_table.category=$t_clauses[0] )" );
+				array_push( $t_where_clauses, "( $t_bug_table.category_id in ( SELECT id FROM $t_category_table WHERE name=$t_clauses[0] ) )" );
 			}
 		}
 
@@ -1765,13 +1764,11 @@
 								} else {
 									$t_first_flag = true;
 									foreach( $t_filter['show_category'] as $t_current ) {
-										$t_current = stripslashes( $t_current );
 										?>
-										<input type="hidden" name="show_category[]" value="<?php echo string_display( $t_current );?>" />
+										<input type="hidden" name="show_category[]" value="<?php echo $t_current;?>" />
 										<?php
 										$t_this_string = '';
-										if ( ( ( $t_current == META_FILTER_ANY ) && ( is_numeric( $t_current ) ) ) 
-												|| ( is_blank( $t_current ) ) ) {
+										if ( is_blank( $t_current ) || $t_current === "0" || $t_current === META_FILTER_ANY ) {
 											$t_any_found = true;
 										} else {
 											$t_this_string = string_display( $t_current );
@@ -3334,8 +3331,7 @@
 		<!-- Category -->
 		<select <?php PRINT $t_select_modifier;?> name="show_category[]">
 			<option value="<?php echo META_FILTER_ANY ?>" <?php check_selected( $t_filter['show_category'], META_FILTER_ANY ); ?>>[<?php echo lang_get( 'any' ) ?>]</option>
-			<?php # This shows orphaned categories as well as selectable categories ?>
-			<?php print_category_complete_option_list( $t_filter['show_category'] ) ?>
+			<?php print_category_filter_option_list( $t_filter['show_category'] ) ?>
 		</select>
 		<?php
 	}
diff --git a/core/graph_api.php b/core/graph_api.php
index 2393a11..df60a9a 100644
--- a/core/graph_api.php
+++ b/core/graph_api.php
@@ -591,30 +591,28 @@
 		global $category_name, $category_bug_count;
 
 		$t_project_id = helper_get_current_project();
-		$t_cat_table = config_get_global( 'mantis_project_category_table' );
-		$t_bug_table = config_get_global( 'mantis_bug_table' );
+		$t_cat_table = config_get( 'mantis_category_table' );
+		$t_bug_table = config_get( 'mantis_bug_table' );
 		$t_user_id = auth_get_current_user_id();
 		$specific_where = helper_project_specific_where( $t_project_id, $t_user_id );
 
-		$query = "SELECT DISTINCT category
+		$query = "SELECT id, name
 				FROM $t_cat_table
 				WHERE $specific_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
 		$category_count = db_num_rows( $result );
-		if ( 0 == $category_count ) {
-			return array();
-		}
 
+		$t_metrics = array();
 		for ($i=0;$i<$category_count;$i++) {
 			$row = db_fetch_array( $result );
-			$t_cat_name = $row['category'];
-			$c_category_name = addslashes($t_cat_name);
+			$t_cat_name = $row['name'];
+			$t_cat_id = $row['id'];
 			$query = "SELECT COUNT(*)
 					FROM $t_bug_table
-					WHERE category='$c_category_name' AND $specific_where";
+					WHERE category_id='$t_cat_id' AND $specific_where";
 			$result2 = db_query( $query );
-			$t_metrics[$t_cat_name] = db_result( $result2, 0, 0 );
+			$t_metrics[$t_cat_name] = $t_metrics[$t_cat_name] + db_result( $result2, 0, 0 );
 		} # end for
 		return $t_metrics;
 	}
diff --git a/core/history_api.php b/core/history_api.php
index 2a6b003..0f51a76 100644
--- a/core/history_api.php
+++ b/core/history_api.php
@@ -216,6 +216,11 @@
 			case 'category':
 				$t_field_localized = lang_get( 'category' );
 				break;
+			case 'category_id':
+				$t_field_localized = lang_get( 'category' );
+				$p_old_value = category_full_name( $p_old_value, false );
+				$p_new_value = category_full_name( $p_new_value, false );
+				break;
 			case 'status':
 				$p_old_value = get_enum_element( 'status', $p_old_value );
 				$p_new_value = get_enum_element( 'status', $p_new_value );
diff --git a/core/my_view_inc.php b/core/my_view_inc.php
index dbc86b3..e46e7ab 100644
--- a/core/my_view_inc.php
+++ b/core/my_view_inc.php
@@ -279,11 +279,10 @@
 			# type project name if viewing 'all projects' or bug is in subproject
 			if ( ON == config_get( 'show_bug_project_links' ) &&
 				helper_get_current_project() != $v_project_id ) {
-				echo '[';
-				print( $project_name );
-				echo '] ';
+				echo string_display( category_full_name( $v_category_id ) );
+			} else {
+				echo string_display( category_full_name( $v_category_id, false ) );
 			}
-			echo string_display( $v_category );
 
 			if ( $v_last_updated > strtotime( '-'.$t_filter['highlight_changed'].' hours' ) ) {
 				echo ' - <b>' . $t_last_updated . '</b>';
diff --git a/core/obsolete.php b/core/obsolete.php
index 30725c2..899ec87 100644
--- a/core/obsolete.php
+++ b/core/obsolete.php
@@ -120,4 +120,6 @@
 
 # changes in 1.1.0rc2
 	config_obsolete( 'wait_time', 'default_redirect_delay' );
+	config_obsolete( 'default_bug_category' );
+
 ?>
diff --git a/core/print_api.php b/core/print_api.php
index 65c4795..34ecb7c 100644
--- a/core/print_api.php
+++ b/core/print_api.php
@@ -424,7 +424,7 @@
 
 	# --------------------
 	# print a news item given a row in the news table.
-        function print_news_entry_from_row( $p_news_row ) {
+      function print_news_entry_from_row( $p_news_row ) {
 		extract( $p_news_row, EXTR_PREFIX_ALL, 'v' );
 		print_news_entry( $v_headline, $v_body, $v_poster_id, $v_view_state, $v_announcement, $v_date_posted );
 	}
@@ -673,12 +673,14 @@
 			PRINT ">$v_name</option>";
 		} # end for
 	}
+
 	# --------------------
 	# Since categories can be orphaned we need to grab all unique instances of category
 	# We check in the project category table and in the bug table
 	# We put them all in one array and make sure the entries are unique
-	function print_category_option_list( $p_category='', $p_project_id = null ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
+	function print_category_option_list( $p_category_id = 0, $p_project_id = null ) {
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
 		if ( null === $p_project_id ) {
 			$c_project_id = helper_get_current_project();
@@ -690,81 +692,61 @@
 
 		# grab all categories in the project category table
 		$cat_arr = array();
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_project_category_table
+		$query = "SELECT id,name FROM $t_category_table
 				WHERE $t_project_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
-		}
 
-		# Add the default option if not in the list retrieved from DB		
-		# This is useful for default categories and when updating an
-		# issue with a deleted category.
-		if ( !is_blank( $p_category ) && !in_array( $p_category, $cat_arr ) ) {
-			$cat_arr[] = $p_category;
+		while ( $row = db_fetch_array( $result ) ) {
+			$cat_arr[$row['id']] = $row['name'];
 		}
+		asort($cat_arr);
 
-		sort( $cat_arr );
-		$cat_arr = array_unique( $cat_arr );
-
-		foreach( $cat_arr as $t_category ) {
-			PRINT "<option value=\"$t_category\"";
-			check_selected( $t_category, $p_category );
-			PRINT ">$t_category</option>";
+		foreach( $cat_arr as $t_category_id => $t_name ) {
+			PRINT "<option value=\"$t_category_id\"";
+			check_selected( $p_category_id, $t_category_id );
+			PRINT ">$t_name</option>";
 		}
 	}
 	# --------------------
 	# Since categories can be orphaned we need to grab all unique instances of category
 	# We check in the project category table and in the bug table
 	# We put them all in one array and make sure the entries are unique
-	function print_category_complete_option_list( $p_category='', $p_project_id = null ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
-		$t_mantis_bug_table = config_get_global( 'mantis_bug_table' );
+	function print_category_complete_option_list( $p_category_id = 0, $p_project_id = null ) {
+		return print_category_option_list( $p_category_id, $p_project_id );
+	}
+
+	# ---------
+	# Now that categories are identified by numerical ID, we need an old-style name
+	# based option list to keep existing filter functionality.
+	function print_category_filter_option_list( $p_category_name = '', $p_project_id = null ) {
+		$t_category_table = config_get( 'mantis_category_table' );
+		$t_project_table = config_get( 'mantis_project_table' );
 
 		if ( null === $p_project_id ) {
-			$t_project_id = helper_get_current_project();
+			$c_project_id = helper_get_current_project();
 		} else {
-			$t_project_id = $p_project_id;
+			$c_project_id = db_prepare_int( $p_project_id );
 		}
 
-		$t_project_where = helper_project_specific_where( $t_project_id );
+		$t_project_where = helper_project_specific_where( $c_project_id );
 
 		# grab all categories in the project category table
 		$cat_arr = array();
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_project_category_table
+		$query = "SELECT DISTINCT name FROM $t_category_table
 				WHERE $t_project_where
-				ORDER BY category";
+				ORDER BY name";
 		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
-		}
 
-		# grab all categories in the bug table
-		$query = "SELECT DISTINCT category
-				FROM $t_mantis_bug_table
-				WHERE $t_project_where
-				ORDER BY category";
-		$result = db_query( $query );
-		$category_count = db_num_rows( $result );
-
-		for ($i=0;$i<$category_count;$i++) {
-			$row = db_fetch_array( $result );
-			$cat_arr[] = string_attribute( $row['category'] );
+		while ( $row = db_fetch_array( $result ) ) {
+			$cat_arr[] = $row['name'];
 		}
-		sort( $cat_arr );
-		$cat_arr = array_unique( $cat_arr );
+		sort($cat_arr);
 
-		foreach( $cat_arr as $t_category ) {
-			PRINT "<option value=\"$t_category\"";
-			check_selected( $p_category, $t_category );
-			PRINT ">$t_category</option>";
+		foreach( $cat_arr as $t_name ) {
+			PRINT "<option value=\"$t_name\"";
+			check_selected( $p_category_name, $t_name );
+			PRINT ">$t_name</option>";
 		}
 	}
 	
@@ -1257,26 +1239,26 @@
 	}
 	# --------------------
 	function print_project_category_string( $p_project_id ) {
-		$t_mantis_project_category_table = config_get_global( 'mantis_project_category_table' );
+		$t_mantis_category_table = config_get( 'mantis_category_table' );
 
 		$c_project_id = db_prepare_int( $p_project_id );
 
-		$query = "SELECT category
-				FROM $t_mantis_project_category_table
-				WHERE project_id=" . db_param(0) . "
-				ORDER BY category";
-		$result = db_query_bound( $query, Array( $c_project_id ) );
+		$query = "SELECT name
+				FROM $t_mantis_category_table
+				WHERE project_id='$c_project_id'
+				ORDER BY name";
+		$result = db_query( $query );
 		$category_count = db_num_rows( $result );
 		$t_string = '';
 
 		for ($i=0;$i<$category_count;$i++) {
 			$row = db_fetch_array( $result );
-			$t_category = $row['category'];
+			$t_name = $row['name'];
 
 			if ( $i+1 < $category_count ) {
-				$t_string .= $t_category.', ';
+				$t_string .= $t_name.', ';
 			} else {
-				$t_string .= $t_category;
+				$t_string .= $t_name;
 			}
 		}
 
@@ -1500,7 +1482,7 @@
 		}
 		print_page_link( $p_page, $t_last, $p_end, $p_current, $p_temp_filter_id );
 
-    	print( " ]" );
+  	print( " ]" );
 	}
 	# --------------------
 	# print a mailto: href link
diff --git a/core/summary_api.php b/core/summary_api.php
index 6241eec..2964af1 100644
--- a/core/summary_api.php
+++ b/core/summary_api.php
@@ -547,6 +547,7 @@
 	# print a bug count per category
 	function summary_print_by_category() {
 		$t_mantis_bug_table = config_get_global( 'mantis_bug_table' );
+		$t_mantis_category_table = config_get( 'mantis_category_table' );
 		$t_mantis_project_table = config_get_global( 'mantis_project_table' );
 		$t_summary_category_include_project = config_get( 'summary_category_include_project' );
 
@@ -559,15 +560,17 @@
 		}
 		$t_project_query = ( ON == $t_summary_category_include_project ) ? 'project_id, ' : '';
 
-		$query = "SELECT COUNT(id) as bugcount, $t_project_query category, status
+		$query = "SELECT COUNT(id) as bugcount, $t_project_query c.name AS category_name, c.id AS category_id, status
 				FROM $t_mantis_bug_table
-				WHERE category>'' AND $specific_where
-				GROUP BY $t_project_query category, status
-				ORDER BY $t_project_query category, status";
+				JOIN $t_mantis_category_table AS c ON category_id=c.id
+				WHERE $specific_where
+				GROUP BY $t_project_query category_id, category_name, status
+				ORDER BY $t_project_query category_id, category_name, status";
 
 		$result = db_query( $query );
 
-		$last_category = -1;
+		$last_category_name = -1;
+		$last_category_id = -1;
 		$last_project = -1;
 		$t_bugs_open = 0;
 		$t_bugs_resolved = 0;
@@ -580,13 +583,13 @@
 		while ( $row = db_fetch_array( $result ) ) {
 			extract( $row, EXTR_PREFIX_ALL, 'v' );
 
-			if ( ( $v_category != $last_category ) && ( $last_category != -1 ) ) {
-				$label = $last_category;
+			if ( ( $v_category_id != $last_category_id ) && ( $last_category_id != -1 ) ) {
+				$label = $last_category_name;
 				if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 					$label = sprintf( '[%s] %s', project_get_name( $last_project ), $label );
 				}
 
-				$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category );
+				$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category_id );
 				if ( 0 < $t_bugs_open ) {
 					$t_bugs_open = $t_bug_link . '&amp;hide_status=' . RESOLVED . '">' . $t_bugs_open . '</a>';
 				}
@@ -617,19 +620,20 @@
 				$t_bugs_open += $row['bugcount'];
 			}
 
-			$last_category = $v_category;
+			$last_category_id = $v_category_id;
+			$last_category_name = $v_category_name;
 			if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 				$last_project = $v_project_id;
 			}
 		}
 
 		if ( 0 < $t_bugs_total ) {
-			$label = $last_category;
+			$label = $last_category_name;
 			if ( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) {
 				$label = sprintf( '[%s] %s', project_get_name( $last_project ), $label );
 			}
 
-			$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category );
+			$t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&amp;show_category=' . urlencode( $last_category_id );
 			if ( !is_blank( $t_bug_link ) ) {
 				if ( 0 < $t_bugs_open ) {
 					$t_bugs_open = $t_bug_link . '&amp;hide_status=' . RESOLVED . '">' . $t_bugs_open . '</a>';
diff --git a/graphs/graph_by_category.php b/graphs/graph_by_category.php
index a969d25..8e5f098 100644
--- a/graphs/graph_by_category.php
+++ b/graphs/graph_by_category.php
@@ -36,12 +36,13 @@
 
 	$data_category_arr = array();
 	$data_count_arr = array();
-	$query = "SELECT category, COUNT(category) as count
+	$query = "SELECT c.name AS name, COUNT(name) as count
 			FROM mantis_bug_table
-			WHERE project_id=" . db_param(0) . "
-			GROUP BY category
-			ORDER BY category";
-	$result = db_query_bound( $query, Array( $t_project_id ) );
+			JOIN mantis_category_table AS c
+			WHERE project_id='$t_project_id'
+			GROUP BY name
+			ORDER BY name";
+	$result = db_query( $query );
 	$category_count = db_num_rows( $result );
 	$total = 0;
 	$longest_size = 0;
@@ -50,11 +51,11 @@
 		extract( $row );
 
 		$total += $count;
-		$data_category_arr[] = $category;
+		$data_category_arr[] = $name;
 		$data_count_arr[] = $count;
 
-		if ( strlen( $category ) > $longest_size ) {
-			$longest_size = strlen( $category );
+		if ( strlen( $name ) > $longest_size ) {
+			$longest_size = strlen( $name );
 		}
 	}
 	$longest_size++;
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index 4d1527d..afc07b0 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -592,6 +592,7 @@ $s_update_simple_link = 'Update Simple';
 $s_updating_bug_advanced_title = 'Updating Issue Information';
 $s_id = 'ID';
 $s_category = 'Category';
+$s_no_category = 'N/A';
 $s_severity = 'Severity';
 $s_reproducibility = 'Reproducibility';
 $s_date_submitted = 'Date Submitted';
diff --git a/manage_proj_cat_add.php b/manage_proj_cat_add.php
index 27717f9..9675b08 100644
--- a/manage_proj_cat_add.php
+++ b/manage_proj_cat_add.php
@@ -30,25 +30,25 @@
 	auth_reauthenticate();
 
 	$f_project_id	= gpc_get_int( 'project_id' );
-	$f_category		= gpc_get_string( 'category' );
+	$f_name			= gpc_get_string( 'name' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	if ( is_blank( $f_category ) ) {
+	if ( is_blank( $f_name ) ) {
 		trigger_error( ERROR_EMPTY_FIELD, ERROR );
 	}
 
-	$t_categories = explode( '|', $f_category );
-	$t_category_count = count( $t_categories );
+	$t_names = explode( '|', $f_name );
+	$t_category_count = count( $t_names );
 
-	foreach ( $t_categories as $t_category ) {
-		if ( is_blank( $t_category ) ) {
+	foreach ( $t_names as $t_name ) {
+		if ( is_blank( $t_name ) ) {
 			continue;
 		}
 
-		$t_category = trim( $t_category );
-		if ( category_is_unique( $f_project_id, $t_category ) ) {
-			category_add( $f_project_id, $t_category );
+		$t_name = trim( $t_name );
+		if ( category_is_unique( $f_project_id, $t_name ) ) {
+			category_add( $f_project_id, $t_name );
 		} else if ( 1 == $t_category_count ) {
 			# We only error out on duplicates when a single value was
 			#  given.  If multiple values were given, we just add the
diff --git a/manage_proj_cat_copy.php b/manage_proj_cat_copy.php
index d7b7c12..f205c71 100644
--- a/manage_proj_cat_copy.php
+++ b/manage_proj_cat_copy.php
@@ -50,10 +50,10 @@
 	$rows = category_get_all_rows( $t_src_project_id );
 
 	foreach ( $rows as $row ) {
-		$t_category = $row['category'];
+		$t_name = $row['name'];
 
-		if ( category_is_unique( $t_dst_project_id, $t_category ) ) {
-			category_add( $t_dst_project_id, $t_category );
+		if ( category_is_unique( $t_dst_project_id, $t_name ) ) {
+			category_add( $t_dst_project_id, $t_name );
 		}
 	}
 
diff --git a/manage_proj_cat_delete.php b/manage_proj_cat_delete.php
index 6dc7966..934e7bf 100644
--- a/manage_proj_cat_delete.php
+++ b/manage_proj_cat_delete.php
@@ -29,19 +29,22 @@
 
 	auth_reauthenticate();
 
-	$f_project_id = gpc_get_int( 'project_id' );
-	$f_category = gpc_get_string( 'category' );
+	$f_category_id = gpc_get_string( 'id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
+	$t_row = category_get_row( $f_category_id );
+	$t_name = category_full_name( $f_category_id );
+	$t_project_id = $t_row['project_id'];
+
 	# Confirm with the user
 	helper_ensure_confirmed( lang_get( 'category_delete_sure_msg' ) .
-		'<br/>' . lang_get( 'category' ) . ': ' . $f_category,
+		'<br/>' . lang_get( 'category' ) . ': ' . $t_name,
 		lang_get( 'delete_category_button' ) );
 
-	category_remove( $f_project_id, $f_category );
+	category_remove( $f_category_id );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
 
 	html_page_top1();
 	html_meta_redirect( $t_redirect_url );
diff --git a/manage_proj_cat_edit_page.php b/manage_proj_cat_edit_page.php
index ca00e1b..781d938 100644
--- a/manage_proj_cat_edit_page.php
+++ b/manage_proj_cat_edit_page.php
@@ -29,13 +29,14 @@
 
 	auth_reauthenticate();
 
-	$f_project_id	= gpc_get_int( 'project_id' );
-	$f_category		= gpc_get_string( 'category' );
+	$f_category_id		= gpc_get_string( 'id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	$t_row = category_get_row( $f_project_id, $f_category );
+	$t_row = category_get_row( $f_category_id );
 	$t_assigned_to = $t_row['user_id'];
+	$t_project_id = $t_row['project_id'];
+	$t_name = $t_row['name'];
 	
 	html_page_top1();
 	html_page_top2();
@@ -54,12 +55,11 @@
 </tr>
 <tr <?php echo helper_alternate_class() ?>>
 	<td class="category">
-		<input type="hidden" name="project_id" value="<?php echo string_attribute( $f_project_id ) ?>" />
-		<input type="hidden" name="category" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="hidden" name="category_id" value="<?php echo string_attribute( $f_category_id ) ?>" />
 		<?php echo lang_get( 'category' ) ?>
 	</td>
 	<td>
-		<input type="text" name="new_category" size="32" maxlength="64" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="text" name="name" size="32" maxlength="128" value="<?php echo string_attribute( $t_name ) ?>" />
 	</td>
 </tr>
 <tr <?php echo helper_alternate_class() ?>>
@@ -69,7 +69,7 @@
 	<td>
 		<select name="assigned_to">
 			<option value="0"></option>
-			<?php print_assign_to_option_list( $t_assigned_to, $f_project_id ) ?>
+			<?php print_assign_to_option_list( $t_assigned_to, $t_project_id ) ?>
 		</select>
 	</td>
 </tr>
@@ -89,8 +89,7 @@
 
 <div class="border-center">
 	<form method="post" action="manage_proj_cat_delete.php">
-		<input type="hidden" name="project_id" value="<?php echo string_attribute( $f_project_id ) ?>" />
-		<input type="hidden" name="category" value="<?php echo string_attribute( $f_category ) ?>" />
+		<input type="hidden" name="category_id" value="<?php echo string_attribute( $f_category_id ) ?>" />
 		<input type="submit" class="button" value="<?php echo lang_get( 'delete_category_button' ) ?>" />
 	</form>
 </div>
diff --git a/manage_proj_cat_update.php b/manage_proj_cat_update.php
index 05afd1e..98610e8 100644
--- a/manage_proj_cat_update.php
+++ b/manage_proj_cat_update.php
@@ -29,29 +29,28 @@
 
 	auth_reauthenticate();
 
-	$f_project_id		= gpc_get_int( 'project_id' );
-	$f_category			= gpc_get_string( 'category' );
-	$f_new_category		= gpc_get_string( 'new_category' );
+	$f_category_id		= gpc_get_int( 'category_id' );
+	$f_name				= trim( gpc_get_string( 'name' ) );
 	$f_assigned_to		= gpc_get_int( 'assigned_to', 0 );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	if ( is_blank( $f_new_category ) ) {
+	if ( is_blank( $f_name ) ) {
 		trigger_error( ERROR_EMPTY_FIELD, ERROR );
 	}
 
-	$f_category		= trim( $f_category );
-	$f_new_category	= trim( $f_new_category );
+	$t_row = category_get_row( $f_category_id );
+	$t_old_name = $t_row['name'];
+	$t_project_id = $t_row['project_id'];
 
 	# check for duplicate
-	if ( strtolower( $f_category ) == strtolower( $f_new_category ) ||
-		 category_is_unique( $f_project_id, $f_new_category ) ) {
-		category_update( $f_project_id, $f_category, $f_new_category, $f_assigned_to );
-	} else {
-		trigger_error( ERROR_CATEGORY_DUPLICATE, ERROR );
+	if ( strtolower( $f_name ) != strtolower( $t_old_name ) ) {
+		category_ensure_unique( $t_project_id, $f_name );
 	}
+	
+	category_update( $f_category_id, $f_name, $f_assigned_to );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
 
 	html_page_top1();
 
diff --git a/manage_proj_edit_page.php b/manage_proj_edit_page.php
index 41c4304..a2270e6 100644
--- a/manage_proj_edit_page.php
+++ b/manage_proj_edit_page.php
@@ -302,7 +302,8 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	}
 
 	foreach ( $t_categories as $t_category ) {
-		$t_name = $t_category['category'];
+		$t_id = $t_category['id'];
+		$t_name = $t_category['name'];
 
 		if ( NO_USER != $t_category['user_id'] && user_exists( $t_category['user_id'] )) {
 			$t_user_name = user_get_name( $t_category['user_id'] );
@@ -320,11 +321,11 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 			</td>
 			<td class="center">
 				<?php
-					$t_name = urlencode( $t_name );
+					$t_id = urlencode( $t_id );
 
-					print_button( 'manage_proj_cat_edit_page.php?project_id=' . $f_project_id . '&amp;category=' . $t_name, lang_get( 'edit_link' ) );
+					print_button( 'manage_proj_cat_edit_page.php?id=' . $t_id, lang_get( 'edit_link' ) );
 					echo '&nbsp;';
-					print_button( 'manage_proj_cat_delete.php?project_id=' . $f_project_id . '&amp;category=' . $t_name, lang_get( 'delete_link' ) );
+					print_button( 'manage_proj_cat_delete.php?id=' . $t_id, lang_get( 'delete_link' ) );
 				?>
 			</td>
 		</tr>
@@ -337,7 +338,7 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	<td class="left" colspan="3">
 		<form method="post" action="manage_proj_cat_add.php">
 			<input type="hidden" name="project_id" value="<?php echo $f_project_id ?>" />
-			<input type="text" name="category" size="32" maxlength="64" />
+			<input type="text" name="name" size="32" maxlength="128" />
 			<input type="submit" class="button" value="<?php echo lang_get( 'add_category_button' ) ?>" />
 		</form>
 	</td>
diff --git a/my_view_page.php b/my_view_page.php
index febd47b..d286294 100644
--- a/my_view_page.php
+++ b/my_view_page.php
@@ -34,6 +34,9 @@
 
 	$t_current_user_id = auth_get_current_user_id();
 
+	# Improve performance by caching category data in one pass
+	category_get_all_rows( helper_get_current_project() );
+
 	compress_enable();
 
 	html_page_top1( lang_get( 'my_view_link' ) );
diff --git a/view_all_inc.php b/view_all_inc.php
index 16e4992..8131b17 100644
--- a/view_all_inc.php
+++ b/view_all_inc.php
@@ -43,6 +43,9 @@
 	$t_icon_path = config_get( 'icon_path' );
 	$t_update_bug_threshold = config_get( 'update_bug_threshold' );
 
+	# Improve performance by caching category data in one pass
+	category_get_all_rows( helper_get_current_project() );
+
 	$t_columns = helper_get_columns_to_view( COLUMNS_TARGET_VIEW_PAGE );
 
 	$col_count = sizeof( $t_columns );
mantis-categories.2008-02-04.patch (30,956 bytes)   
diff --git a/admin/schema.php b/admin/schema.php
index 6aefc72..c8423ba 100644
--- a/admin/schema.php
+++ b/admin/schema.php
@@ -382,7 +382,7 @@ $upgrade[] = Array( 'CreateTableSQL', Array( db_get_table( 'mantis_category_tabl
 $upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_category_project_name', db_get_table( 'mantis_category_table' ), 'project_id, name', array( 'UNIQUE' ) ) );
 $upgrade[] = Array( 'InsertData', Array( db_get_table( 'mantis_category_table' ), "
 	( project_id, user_id, name, status ) VALUES
-	( '0', '0', 'None', '0' ) " ) );
+	( '0', '0', 'General', '0' ) " ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "category_id I UNSIGNED NOTNULL DEFAULT '1'" ) );
 $upgrade[] = Array( 'UpdateFunction', "category_migrate" );
 $upgrade[] = Array( 'DropColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "category" ) );
diff --git a/bug_update_advanced_page.php b/bug_update_advanced_page.php
index 03ed16d..8f7768f 100644
--- a/bug_update_advanced_page.php
+++ b/bug_update_advanced_page.php
@@ -115,9 +115,6 @@
 
 	<!-- Category -->
 	<td>
-		<?php if ( $t_changed_project ) {
-			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
-		} ?>
 		<select <?php echo helper_get_tab_index() ?> name="category_id">
 		<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
diff --git a/bug_update_page.php b/bug_update_page.php
index 151b1bc..abf3501 100644
--- a/bug_update_page.php
+++ b/bug_update_page.php
@@ -118,9 +118,6 @@
 
 	<!-- Category -->
 	<td>
-		<?php if ( $t_changed_project ) {
-			echo "[" . project_get_field( $t_bug->project_id, 'name' ) . "] ";
-		} ?>
 		<select <?php echo helper_get_tab_index() ?> name="category_id">
 			<?php print_category_option_list( $t_bug->category_id, $t_bug->project_id ) ?>
 		</select>
diff --git a/core/category_api.php b/core/category_api.php
index cabde42..a768fae 100644
--- a/core/category_api.php
+++ b/core/category_api.php
@@ -149,7 +149,7 @@
 
 	# --------------------
 	# Remove a category from the project
-	function category_remove( $p_category_id, $p_new_category_id = 1 ) {
+	function category_remove( $p_category_id, $p_new_category_id = 0 ) {
 		$t_category_row = category_get_row( $p_category_id );
 
 		$c_category_id	= db_prepare_int( $p_category_id );
@@ -172,7 +172,6 @@
 		$t_result = db_query_bound( $query, array( $c_category_id ) );
 
 		while ( $t_bug_row = db_fetch_array( $t_result ) ) {
-			var_dump( $t_bug_row );
 			history_log_event_direct( $t_bug_row['id'], 'category', $t_category_row['name'], category_full_name( $p_new_category_id, false ) );
 		}
 
@@ -188,7 +187,7 @@
 
 	# --------------------
 	# Remove all categories associated with a project
-	function category_remove_all( $p_project_id, $p_new_category_id = 1 ) {
+	function category_remove_all( $p_project_id, $p_new_category_id = 0 ) {
 		$c_project_id = db_prepare_int( $p_project_id );
 		$c_new_category_id = db_prepare_int( $p_new_category_id );
 
@@ -258,19 +257,55 @@
 	}
 
 	# --------------------
-	# Return all categories for the specified project id
-	function category_get_all_rows( $p_project_id ) {
+	# Sort categories based on what project they're in.
+	# Call beforehand with a single parameter to set a 'preferred' project.
+	function category_sort_rows_by_project( $p_category1, $p_category2=null ) {
+		static $p_project_id=null;
+		if ( is_null( $p_category2 ) ) { # Set a target project
+			$p_project_id = $p_category1;
+			return;
+		}
+
+		if ( !is_null( $p_project_id ) ) {
+			if ( $p_category1['project_id'] == $p_project_id &&
+				 $p_category2['project_id'] != $p_project_id ) {
+					 return -1;
+				 }
+			if ( $p_category1['project_id'] != $p_project_id &&
+				 $p_category2['project_id'] == $p_project_id ) {
+					 return 1;
+				 }
+		}
+
+		$t_proj_cmp = strcasecmp( $p_category1['project_name'], $p_category2['project_name'] );
+		if ( $t_proj_cmp != 0 ) {
+			return $t_proj_cmp;
+		}
+
+		return strcasecmp( $p_category1['name'], $p_category2['name'] );
+	}
+	# --------------------
+	# Return all categories for the specified project id.
+	# Obeys project hierarchies and such.
+	function category_get_all_rows( $p_project_id, $p_inherit=true, $p_sort_by_project=false ) {
 		global $g_category_cache;
 
+		project_hierarchy_cache();
+
 		$c_project_id	= db_prepare_int( $p_project_id );
 
 		$t_category_table = db_get_table( 'mantis_category_table' );
 		$t_project_table = db_get_table( 'mantis_project_table' );
 
-		$t_project_where = helper_project_specific_where( $c_project_id );
+		if ( $p_inherit ) {
+			$t_project_ids = project_hierarchy_inheritance( $p_project_id );
+			$t_project_where = ' project_id IN ( ' . implode( ', ', $t_project_ids ) . ' ) ';
+		} else {
+			$t_project_where = ' project_id=' . $p_project_id . ' ';
+		}
 
 		$query = "SELECT c.*, p.name AS project_name FROM $t_category_table AS c
-				JOIN $t_project_table AS p
+				LEFT JOIN $t_project_table AS p
 					ON c.project_id=p.id
 				WHERE $t_project_where
 				ORDER BY c.name ";
@@ -284,6 +319,12 @@
 			$g_category_cache[$row['id']] = $row;
 		}
 
+		if ( $p_sort_by_project ) {
+			category_sort_rows_by_project( $p_project_id );
+			usort( $rows, 'category_sort_rows_by_project' );
+			category_sort_rows_by_project( null );
+		}
+
 		return $rows;
 	}
 
@@ -311,7 +352,7 @@
 			$t_row = category_get_row( $p_category_id );
 			$t_project_id = $t_row['project_id'];
 
-			if ( $p_show_project && ALL_PROJECTS != $t_project_id ) {
+			if ( $p_show_project ) {
 				return '[' . project_get_name( $t_project_id ) . '] ' . $t_row['name'];
 			}
 
diff --git a/core/print_api.php b/core/print_api.php
index 536a644..894e9c4 100644
--- a/core/print_api.php
+++ b/core/print_api.php
@@ -696,29 +696,17 @@
 		$t_project_table = db_get_table( 'mantis_project_table' );
 
 		if ( null === $p_project_id ) {
-			$c_project_id = helper_get_current_project();
+			$t_project_id = helper_get_current_project();
 		} else {
-			$c_project_id = db_prepare_int( $p_project_id );
+			$t_project_id = $p_project_id;
 		}
 
-		$t_project_where = helper_project_specific_where( $c_project_id );
-
-		# grab all categories in the project category table
-		$cat_arr = array();
-		$query = "SELECT id,name FROM $t_category_table
-				WHERE $t_project_where
-				ORDER BY name";
-		$result = db_query( $query );
-
-		while ( $row = db_fetch_array( $result ) ) {
-			$cat_arr[$row['id']] = $row['name'];
-		}
-		asort($cat_arr);
+		$cat_arr = category_get_all_rows( $t_project_id, true, !is_null( $p_project_id ) );
 
-		foreach( $cat_arr as $t_category_id => $t_name ) {
-			PRINT "<option value=\"$t_category_id\"";
-			check_selected( $p_category_id, $t_category_id );
-			PRINT ">$t_name</option>";
+		foreach( $cat_arr as $t_category_row ) {
+			$t_category_id = $t_category_row['id'];
+			echo "<option value=\"$t_category_id\"", check_selected( $p_category_id, $t_category_id ), '>';
+			echo category_full_name( $t_category_id, $t_category_row['project_id'] != $p_project_id ), '</option>';
 		}
 	}
 	# --------------------
@@ -742,7 +730,16 @@
 			$c_project_id = db_prepare_int( $p_project_id );
 		}
 
-		$t_project_where = helper_project_specific_where( $c_project_id );
+		project_hierarchy_cache();
+		$t_project_ids = project_hierarchy_inheritance( $c_project_id );
+
+		$t_subproject_ids = array();
+		foreach ( $t_project_ids as $t_project_id ) {
+			$t_subproject_ids = array_merge( $t_subproject_ids, current_user_get_all_accessible_subprojects( $t_project_id ) );
+		}
+
+		$t_project_ids = array_merge( $t_project_ids, $t_subproject_ids );
+		$t_project_where = ' project_id IN ( ' . implode( ', ', $t_project_ids ) . ' ) ';
 
 		# grab all categories in the project category table
 		$cat_arr = array();
diff --git a/core/project_api.php b/core/project_api.php
index 66476a8..194ee44 100644
--- a/core/project_api.php
+++ b/core/project_api.php
@@ -211,7 +211,7 @@
 
 	# --------------------
 	# Create a new project
-	function project_create( $p_name, $p_description, $p_status, $p_view_state = VS_PUBLIC, $p_file_path = '', $p_enabled = true ) {
+	function project_create( $p_name, $p_description, $p_status, $p_view_state = VS_PUBLIC, $p_file_path = '', $p_enabled = true, $p_inherit_global = true ) {
 		# Make sure file path has trailing slash
 		$p_file_path = terminate_directory_path( $p_file_path );
 
@@ -221,6 +221,7 @@
 		$c_view_state	= db_prepare_int( $p_view_state );
 		$c_file_path	= db_prepare_string( $p_file_path );
 		$c_enabled		= db_prepare_bool( $p_enabled );
+		$c_inherit_global = db_prepare_bool( $p_inherit_global );
 
 		if ( is_blank( $p_name ) ) {
 			trigger_error( ERROR_PROJECT_NAME_INVALID, ERROR );
@@ -235,9 +236,9 @@
 		$t_project_table = db_get_table( 'mantis_project_table' );
 
 		$query = "INSERT INTO $t_project_table
-					( name, status, enabled, view_state, file_path, description )
+					( name, status, enabled, view_state, file_path, description, inherit_global )
 				  VALUES
-					( '$c_name', '$c_status', '$c_enabled', '$c_view_state', '$c_file_path', '$c_description' )";
+					( '$c_name', '$c_status', '$c_enabled', '$c_view_state', '$c_file_path', '$c_description', '$c_inherit_global' )";
 
 		db_query( $query );
 
@@ -302,7 +303,7 @@
 
 	# --------------------
 	# Update a project
-	function project_update( $p_project_id, $p_name, $p_description, $p_status, $p_view_state, $p_file_path, $p_enabled ) {
+	function project_update( $p_project_id, $p_name, $p_description, $p_status, $p_view_state, $p_file_path, $p_enabled, $p_inherit_global ) {
 		# Make sure file path has trailing slash
 		$p_file_path	= terminate_directory_path( $p_file_path );
 
@@ -313,6 +314,7 @@
 		$c_view_state	= db_prepare_int( $p_view_state );
 		$c_file_path	= db_prepare_string( $p_file_path );
 		$c_enabled		= db_prepare_bool( $p_enabled );
+		$c_inherit_global = db_prepare_bool( $p_inherit_global );
 
 		if ( is_blank( $p_name ) ) {
 			trigger_error( ERROR_PROJECT_NAME_INVALID, ERROR );
@@ -336,7 +338,8 @@
 					enabled='$c_enabled',
 					view_state='$c_view_state',
 					file_path='$c_file_path',
-					description='$c_description'
+					description='$c_description',
+					inherit_global='$c_inherit_global'
 				  WHERE id='$c_project_id'";
 		db_query( $query );
 
diff --git a/core/project_hierarchy_api.php b/core/project_hierarchy_api.php
index accb345..1be60ac 100644
--- a/core/project_hierarchy_api.php
+++ b/core/project_hierarchy_api.php
@@ -26,7 +26,7 @@
 	$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
 
 	# --------------------
-	function project_hierarchy_add( $p_child_id, $p_parent_id ) {
+	function project_hierarchy_add( $p_child_id, $p_parent_id, $p_inherit_parent = true ) {
 		if ( in_array( $p_parent_id, project_hierarchy_get_all_subprojects( $p_child_id ) ) ) {
 			trigger_error( ERROR_PROJECT_RECURSIVE_HIERARCHY, ERROR );
 		}
@@ -35,13 +35,28 @@
 
 		$c_child_id  = db_prepare_int( $p_child_id  );
 		$c_parent_id = db_prepare_int( $p_parent_id );
+		$c_inherit_parent = db_prepare_bool( $p_inherit_parent );
 
 		$query = "INSERT INTO $t_project_hierarchy_table
-		                ( child_id, parent_id )
+		                ( child_id, parent_id, inherit_parent )
 						VALUES
-						( " . db_param(0) . ", " . db_param(1) . " )";
+						( " . db_param(0) . ', ' . db_param(1) . ', ' . db_param(2) . ' )';
 
-		db_query_bound($query, Array( $c_child_id, $c_parent_id ) );
+		db_query_bound($query, Array( $c_child_id, $c_parent_id, $c_inherit_parent ) );
+	}
+
+	function project_hierarchy_update( $p_child_id, $p_parent_id, $p_inherit_parent = true ) {
+		$t_project_hierarchy_table = db_get_table( 'mantis_project_hierarchy_table' );
+
+		$c_child_id  = db_prepare_int( $p_child_id  );
+		$c_parent_id = db_prepare_int( $p_parent_id );
+		$c_inherit_parent = db_prepare_bool( $p_inherit_parent );
+
+		$query = "UPDATE $t_project_hierarchy_table
+					SET inherit_parent=" . db_param(0) . '
+					WHERE child_id=' . db_param(1) . '
+						AND parent_id=' . db_param(2);
+		db_query_bound( $query, Array( $c_inherit_parent, $c_child_id, $c_parent_id ) );
 	}
 
 	# --------------------
@@ -87,16 +102,21 @@
 	}
 
 	$g_cache_project_hierarchy = null;
+	$g_cache_project_inheritance = null;
 
 	# --------------------
 	function project_hierarchy_cache( $p_show_disabled = false ) {
-		global $g_cache_project_hierarchy;
+		global $g_cache_project_hierarchy, $g_cache_project_inheritance;
+
+		if ( !is_null( $g_cache_project_hierarchy ) ) {
+			return;
+		}
 
 		$t_project_table			= db_get_table( 'mantis_project_table' );
 		$t_project_hierarchy_table	= db_get_table( 'mantis_project_hierarchy_table' );
 		$t_enabled_clause = $p_show_disabled ? '1=1' : 'p.enabled = ' . db_param(0);
 
-		$query = "SELECT DISTINCT p.id, ph.parent_id, p.name
+		$query = "SELECT DISTINCT p.id, ph.parent_id, p.name, p.inherit_global, ph.inherit_parent
 				  FROM $t_project_table p
 				  LEFT JOIN $t_project_hierarchy_table ph
 				    ON ph.child_id = p.id
@@ -107,6 +127,7 @@
 		$row_count = db_num_rows( $result );
 
 		$g_cache_project_hierarchy = array();
+		$g_cache_project_inheritance = array();
 
 		for ( $i=0 ; $i < $row_count ; $i++ ){
 			$row = db_fetch_array( $result );
@@ -120,9 +141,57 @@
 			} else {
 				$g_cache_project_hierarchy[ $row['parent_id'] ] = array( $row['id'] );
 			}
+
+			if ( !isset( $g_cache_project_inheritance[ $row['id'] ] ) ) {
+				$g_cache_project_inheritance[ $row['id'] ] = array();
+			}
+
+			if ( $row['inherit_global'] && !isset( $g_cache_project_inheritance[ $row['id'] ][ ALL_PROJECTS ] ) ) {
+				$g_cache_project_inheritance[ $row['id'] ][] = ALL_PROJECTS;
+			}
+			if ( $row['inherit_parent'] && !isset( $g_cache_project_inheritance[ $row['id'] ][ $row['parent_id'] ] ) ) {
+				$g_cache_project_inheritance[ $row['id'] ][] = (int)$row['parent_id'];
+			}
 		}
 	}
 
+	# --------------------
+	# Returns true if the child project inherits categories from the parent.
+	function project_hierarchy_inherit_parent( $p_child_id, $p_parent_id ) {
+		global $g_cache_project_inheritance;
+
+		return in_array( $p_parent_id, $g_cache_project_inheritance[ $p_child_id ] );
+	}
+
+	# --------------------
+	# Generate an array of project's the given project inherits from,
+	# including the original project in the result.
+	function project_hierarchy_inheritance( $p_project_id ) {
+		global $g_cache_project_inheritance;
+
+		$t_project_ids = array( (int)$p_project_id );
+		$t_lookup_ids = array( (int)$p_project_id );
+
+		while( count( $t_lookup_ids ) > 0 ) {
+			$t_project_id = array_shift( $t_lookup_ids );
+
+			if ( !isset( $g_cache_project_inheritance[ $t_project_id ] ) ) {
+				continue;
+			}
+
+			foreach( $g_cache_project_inheritance[ $t_project_id ] as $t_parent_id ) {
+				if ( !in_array( $t_parent_id, $t_project_ids ) ) {
+					$t_project_ids[] = $t_parent_id;
+
+					if ( !in_array( $t_lookup_ids, $t_project_ids ) ) {
+						$t_lookup_ids[] = $t_parent_id;
+					}
+				}
+			}
+		}
+
+		return $t_project_ids;
+	}
 
 	# --------------------
 	function project_hierarchy_get_subprojects( $p_project_id, $p_show_disabled = false ) {
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index 5c1a2dc..f791f53 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -99,7 +99,13 @@ $s_empty_password_sure_msg = 'The user has an empty password.  Are you sure that
 $s_empty_password_button = 'Use Empty Password';
 $s_reauthenticate_title = 'Authenticate';
 $s_reauthenticate_message = 'You are visting a secure page, and your secure session has expired.  Please authenticate yourself to continue.';
+
 $s_no_category = 'No Category';
+$s_global_categories = 'Global Categories';
+$s_inherit = 'Inherit Categories';
+$s_inherit_global = 'Inherit Global Categories';
+$s_inherit_parent = 'Inherit Parent Categories';
+$s_update_subproject_inheritance = 'Update Subproject Inheritance';
 
 $s_duplicate_of = "duplicate of";
 $s_has_duplicate = "has duplicate";
diff --git a/manage_proj_cat_add.php b/manage_proj_cat_add.php
index 9675b08..92764b8 100644
--- a/manage_proj_cat_add.php
+++ b/manage_proj_cat_add.php
@@ -59,7 +59,11 @@
 		}
 	}
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	if ( $f_project_id == ALL_PROJECTS ) {
+		$t_redirect_url = 'manage_proj_page.php';
+	} else {
+		$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	}
 
 	print_header_redirect( $t_redirect_url );
 ?>
diff --git a/manage_proj_cat_copy.php b/manage_proj_cat_copy.php
index f205c71..43e6b9f 100644
--- a/manage_proj_cat_copy.php
+++ b/manage_proj_cat_copy.php
@@ -57,5 +57,11 @@
 		}
 	}
 
-	print_header_redirect( 'manage_proj_edit_page.php?project_id=' . $f_project_id );
+	if ( $f_project_id == ALL_PROJECTS ) {
+		$t_redirect_url = 'manage_proj_page.php';
+	} else {
+		$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	}
+
+	print_header_redirect( $t_redirect_url );
 ?>
diff --git a/manage_proj_cat_delete.php b/manage_proj_cat_delete.php
index 934e7bf..971b8cd 100644
--- a/manage_proj_cat_delete.php
+++ b/manage_proj_cat_delete.php
@@ -30,6 +30,7 @@
 	auth_reauthenticate();
 
 	$f_category_id = gpc_get_string( 'id' );
+	$f_project_id = gpc_get_int( 'project_id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
@@ -44,7 +45,11 @@
 
 	category_remove( $f_category_id );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
+	if ( $f_project_id == ALL_PROJECTS ) {
+		$t_redirect_url = 'manage_proj_page.php';
+	} else {
+		$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	}
 
 	html_page_top1();
 	html_meta_redirect( $t_redirect_url );
diff --git a/manage_proj_cat_edit_page.php b/manage_proj_cat_edit_page.php
index 781d938..2491518 100644
--- a/manage_proj_cat_edit_page.php
+++ b/manage_proj_cat_edit_page.php
@@ -30,6 +30,7 @@
 	auth_reauthenticate();
 
 	$f_category_id		= gpc_get_string( 'id' );
+	$f_project_id		= gpc_get_string( 'project_id' );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
diff --git a/manage_proj_cat_update.php b/manage_proj_cat_update.php
index 98610e8..7c736f4 100644
--- a/manage_proj_cat_update.php
+++ b/manage_proj_cat_update.php
@@ -30,6 +30,7 @@
 	auth_reauthenticate();
 
 	$f_category_id		= gpc_get_int( 'category_id' );
+	$f_project_id		= gpc_get_int( 'project_id' );
 	$f_name				= trim( gpc_get_string( 'name' ) );
 	$f_assigned_to		= gpc_get_int( 'assigned_to', 0 );
 
@@ -50,7 +51,11 @@
 	
 	category_update( $f_category_id, $f_name, $f_assigned_to );
 
-	$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $t_project_id;
+	if ( $f_project_id == ALL_PROJECTS ) {
+		$t_redirect_url = 'manage_proj_page.php';
+	} else {
+		$t_redirect_url = 'manage_proj_edit_page.php?project_id=' . $f_project_id;
+	}
 
 	html_page_top1();
 
diff --git a/manage_proj_create.php b/manage_proj_create.php
index 3dcd7a4..9d7d87b 100644
--- a/manage_proj_create.php
+++ b/manage_proj_create.php
@@ -36,8 +36,10 @@
 	$f_view_state	= gpc_get_int( 'view_state' );
 	$f_status		= gpc_get_int( 'status' );
 	$f_file_path	= gpc_get_string( 'file_path', '' );
+	$f_inherit_global = gpc_get_bool( 'inherit_global', 1 );
+	$f_inherit_parent = gpc_get_bool( 'inherit_parent', 1 );
 
-	$t_project_id = project_create( strip_tags( $f_name ), $f_description, $f_status, $f_view_state, $f_file_path );
+	$t_project_id = project_create( strip_tags( $f_name ), $f_description, $f_status, $f_view_state, $f_file_path, true, $f_inherit_global );
 
 	if ( ( $f_view_state == VS_PRIVATE ) && ( false === current_user_is_administrator() ) ) {
 		$t_access_level = access_get_global_level();
@@ -48,7 +50,7 @@
 	$f_parent_id	= gpc_get_int( 'parent_id', 0 );
 
 	if ( 0 != $f_parent_id ) {
-		project_hierarchy_add( $t_project_id, $f_parent_id );
+		project_hierarchy_add( $t_project_id, $f_parent_id, $f_inherit_parent );
 	}
 
 	$t_redirect_url = 'manage_proj_page.php';
diff --git a/manage_proj_create_page.php b/manage_proj_create_page.php
index bf6f7c2..6714c0c 100644
--- a/manage_proj_create_page.php
+++ b/manage_proj_create_page.php
@@ -84,6 +84,24 @@
 		</select>
 	</td>
 </tr>
+<tr class="row-2">
+	<td class="category">
+		<?php echo lang_get( 'inherit_global' ) ?>
+	</td>
+	<td>
+		<input type="checkbox" name="inherit_global" checked="checked" />
+	</td>
+</tr>
+<?php if ( !is_null( $f_parent_id ) ) { ?>
+<tr class="row-1">
+	<td class="category">
+		<?php echo lang_get( 'inherit_parent' ) ?>
+	</td>
+	<td>
+		<input type="checkbox" name="inherit_parent" checked="checked" />
+	</td>
+</tr>
+<?php } ?>
 <?php
 	if ( config_get( 'allow_file_upload' ) ) {
 	?>
diff --git a/manage_proj_edit_page.php b/manage_proj_edit_page.php
index 5292cdd..c819219 100644
--- a/manage_proj_edit_page.php
+++ b/manage_proj_edit_page.php
@@ -95,6 +95,16 @@
 	</td>
 </tr>
 
+<!-- Category Inheritance -->
+<tr <?php echo helper_alternate_class() ?>>
+	<td class="category">
+		<?php echo lang_get( 'inherit_global' ) ?>
+	</td>
+	<td>
+		<input type="checkbox" name="inherit_global" <?php check_checked( $row['inherit_global'], ON ); ?> />
+	</td>
+</tr>
+
 <!-- View Status (public/private) -->
 <tr <?php echo helper_alternate_class() ?>>
 	<td class="category">
@@ -179,7 +189,10 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 </tr>
 
 <!-- Subprojects -->
+<form name="update_children_form" action="manage_proj_update_children.php" method="post">
+<input type="hidden" name="project_id" value="<?php echo $f_project_id ?>" />
 <?php
+	project_hierarchy_cache();
 	$t_subproject_ids = current_user_get_accessible_subprojects( $f_project_id, /* show_disabled */ true );
 
 	if ( Array() != $t_subproject_ids ) {
@@ -195,9 +208,12 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 		<?php echo lang_get( 'enabled' ) ?>
 	</td>
 	<td width="10%">
+		<?php echo lang_get( 'inherit' ) ?>
+	</td>
+	<td width="10%">
 		<?php echo lang_get( 'view_status' ) ?>
 	</td>
-	<td width="30%">
+	<td width="20%">
 		<?php echo lang_get( 'description' ) ?>
 	</td>
 	<td width="20%">
@@ -208,18 +224,22 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 <?php
 		foreach ( $t_subproject_ids as $t_subproject_id ) {
 			$t_subproject = project_get_row( $t_subproject_id );
+			$t_inherit_parent = project_hierarchy_inherit_parent( $t_subproject_id, $f_project_id );
 ?>
 <tr <?php echo helper_alternate_class() ?>>
 	<td>
 		<a href="manage_proj_edit_page.php?project_id=<?php echo $t_subproject['id'] ?>"><?php echo string_display( $t_subproject['name'] ) ?></a>
 	</td>
-	<td>
+	<td class="center">
 		<?php echo get_enum_element( 'project_status', $t_subproject['status'] ) ?>
 	</td>
-	<td>
+	<td class="center">
 		<?php echo trans_bool( $t_subproject['enabled'] ) ?>
 	</td>
-	<td>
+	<td class="center">
+		<input type="checkbox" name="inherit_child_<?php echo $t_subproject_id ?>" <?php echo ( $t_inherit_parent ? 'checked="checked"' : '' ) ?> />
+	</td>
+	<td class="center">
 		<?php echo get_enum_element( 'project_view_state', $t_subproject['view_state'] ) ?>
 	</td>
 	<td>
@@ -227,9 +247,8 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	</td>
 	<td class="center">
 		<?php
-				print_button( 'manage_proj_edit_page.php?project_id=' . $t_subproject['id'], lang_get( 'edit_link' ) );
-				echo '&nbsp;';
-				print_button( 'manage_proj_subproj_delete.php?project_id=' . $f_project_id . '&amp;subproject_id=' . $t_subproject['id'], lang_get( 'unlink_link' ) );
+				print_bracket_link( 'manage_proj_edit_page.php?project_id=' . $t_subproject['id'], lang_get( 'edit_link' ) );
+				print_bracket_link( 'manage_proj_subproj_delete.php?project_id=' . $f_project_id . '&amp;subproject_id=' . $t_subproject['id'], lang_get( 'unlink_link' ) );
 		?>
 	</td>
 </tr>
@@ -238,9 +257,16 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 	} # End of hiding subproject listing if there are no subprojects
 ?>
 
+<tr>
+	<td colspan="6">
+	<input type="submit" value="<?php echo lang_get( 'update_subproject_inheritance' ) ?>" />
+		</form>
+	</td>
+</tr>
+
 <!-- Add subproject -->
 <tr>
-	<td class="left" colspan="2">
+	<td colspan="7">
 		<form method="post" action="manage_proj_subproj_add.php">
 			<input type="hidden" name="project_id" value="<?php echo $f_project_id ?>" />
 			<select name="subproject_id">
@@ -307,8 +333,14 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 
 	foreach ( $t_categories as $t_category ) {
 		$t_id = $t_category['id'];
-		$t_name = $t_category['name'];
 
+		if ( $t_category['project_id'] != $f_project_id ) {
+			$t_inherited = true;
+		} else {
+			$t_inherited = false;
+		}
+
+		$t_name = $t_category['name'];
 		if ( NO_USER != $t_category['user_id'] && user_exists( $t_category['user_id'] )) {
 			$t_user_name = user_get_name( $t_category['user_id'] );
 		} else {
@@ -318,19 +350,20 @@ if ( access_has_global_level ( config_get( 'delete_project_threshold' ) ) ) { ?>
 <!-- Repeated Info Row -->
 		<tr <?php echo helper_alternate_class() ?>>
 			<td>
-				<?php echo string_display( $t_name ) ?>
+				<?php echo string_display( category_full_name( $t_category['id'] , $t_inherited ) )  ?>
 			</td>
 			<td>
 				<?php echo $t_user_name ?>
 			</td>
 			<td class="center">
-				<?php
+				<?php if ( !$t_inherited ) {
 					$t_id = urlencode( $t_id );
+					$t_project_id = urlencode( $f_project_id );
 
-					print_button( 'manage_proj_cat_edit_page.php?id=' . $t_id, lang_get( 'edit_link' ) );
+					print_button( 'manage_proj_cat_edit_page.php?id=' . $t_id . '&project_id=' . $t_project_id, lang_get( 'edit_link' ) );
 					echo '&nbsp;';
-					print_button( 'manage_proj_cat_delete.php?id=' . $t_id, lang_get( 'delete_link' ) );
-				?>
+					print_button( 'manage_proj_cat_delete.php?id=' . $t_id . '&project_id=' . $t_project_id, lang_get( 'delete_link' ) );
+				} ?>
 			</td>
 		</tr>
 <?php
diff --git a/manage_proj_page.php b/manage_proj_page.php
index c0b1b25..fe004b8 100644
--- a/manage_proj_page.php
+++ b/manage_proj_page.php
@@ -140,5 +140,83 @@
 	}
 ?>
 </table>
+<br/>
+
+<!-- GLOBAL CATEGORIES -->
+<a name="categories" />
+<div align="center">
+<table class="width75" cellspacing="1">
+
+<!-- Title -->
+<tr>
+	<td class="form-title" colspan="3">
+		<?php echo lang_get( 'global_categories' ) ?>
+	</td>
+</tr>
+<?php
+	$t_categories = category_get_all_rows( ALL_PROJECTS, false );
+
+	if ( count( $t_categories ) > 0 ) {
+?>
+		<tr class="row-category">
+			<td>
+				<?php echo lang_get( 'category' ) ?>
+			</td>
+			<td>
+				<?php echo lang_get( 'assign_to' ) ?>
+			</td>
+			<td class="center">
+				<?php echo lang_get( 'actions' ) ?>
+			</td>
+		</tr>
+<?php
+	}
+
+	foreach ( $t_categories as $t_category ) {
+		$t_id = $t_category['id'];
+
+		$t_name = $t_category['name'];
+		if ( NO_USER != $t_category['user_id'] && user_exists( $t_category['user_id'] )) {
+			$t_user_name = user_get_name( $t_category['user_id'] );
+		} else {
+			$t_user_name = '';
+		}
+?>
+<!-- Repeated Info Row -->
+		<tr <?php echo helper_alternate_class() ?>>
+			<td>
+				<?php echo string_display( category_full_name( $t_category['id'], false ) )  ?>
+			</td>
+			<td>
+				<?php echo $t_user_name ?>
+			</td>
+			<td class="center">
+				<?php
+					$t_id = urlencode( $t_id );
+					$t_project_id = urlencode( ALL_PROJECTS );
+
+					print_button( 'manage_proj_cat_edit_page.php?id=' . $t_id . '&project_id=' . $t_project_id, lang_get( 'edit_link' ) );
+					echo '&nbsp;';
+					print_button( 'manage_proj_cat_delete.php?id=' . $t_id . '&project_id=' . $t_project_id, lang_get( 'delete_link' ) );
+				?>
+			</td>
+		</tr>
+<?php
+	} # end for loop
+?>
+
+<!-- Add Category Form -->
+<tr>
+	<td class="left" colspan="3">
+		<form method="post" action="manage_proj_cat_add.php">
+			<input type="hidden" name="project_id" value="<?php echo ALL_PROJECTS ?>" />
+			<input type="text" name="name" size="32" maxlength="128" />
+			<input type="submit" class="button" value="<?php echo lang_get( 'add_category_button' ) ?>" />
+		</form>
+	</td>
+</tr>
+
+</table>
+</div>
 
 <?php html_page_bottom1( __FILE__ ) ?>
diff --git a/manage_proj_update.php b/manage_proj_update.php
index 2ddbdd2..3cbd2e6 100644
--- a/manage_proj_update.php
+++ b/manage_proj_update.php
@@ -32,10 +32,11 @@
 	$f_view_state 	= gpc_get_int( 'view_state' );
 	$f_file_path 	= gpc_get_string( 'file_path', '' );
 	$f_enabled	 	= gpc_get_bool( 'enabled' );
+	$f_inherit_global = gpc_get_bool( 'inherit_global', 1 );
 
 	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
 
-	project_update( $f_project_id, $f_name, $f_description, $f_status, $f_view_state, $f_file_path, $f_enabled );
+	project_update( $f_project_id, $f_name, $f_description, $f_status, $f_view_state, $f_file_path, $f_enabled, $f_inherit_global );
 
 	print_header_redirect( 'manage_proj_page.php' );
 ?>
diff --git a/manage_proj_update_children.php b/manage_proj_update_children.php
new file mode 100644
index 0000000..3a7cbe6
--- /dev/null
+++ b/manage_proj_update_children.php
@@ -0,0 +1,39 @@
+<?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
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+	require_once( 'core.php' );
+
+	$t_core_path = config_get( 'core_path' );
+
+	require_once( $t_core_path.'project_hierarchy_api.php' );
+
+	auth_reauthenticate();
+
+	$f_project_id = gpc_get_int( 'project_id' );
+
+	access_ensure_project_level( config_get( 'manage_project_threshold' ), $f_project_id );
+
+	$t_subproject_ids = current_user_get_accessible_subprojects( $f_project_id, true );
+	foreach ( $t_subproject_ids as $t_subproject_id ) {
+		$f_inherit_child = gpc_get_bool( 'inherit_child_' . $t_subproject_id, false );
+		var_dump( $t_subproject_id, $f_project_id, $f_inherit_child );
+		project_hierarchy_update( $t_subproject_id, $f_project_id, $f_inherit_child );
+	}
+
+	print_successful_redirect( 'manage_proj_edit_page.php?project_id=' . $f_project_id );

Relationships

parent of 0008771 closedjreese Port 0008738: Allow bugs to have no category 
has duplicate 0006048 closedjreese mantis_bug_table: 'category' field should have ID of a category 
has duplicate 0007841 closedjreese Categories for subproject 
has duplicate 0006039 closedgiallu global categories 
related to 0000946 closedjreese Implementation of sub-categories 
related to 0003370 closedjreese No category filter in "View Bugs" view, when in "All Projects" mode 
related to 0008582 assigneddregad Default Category for Project / Global default Category 

Activities

jreese

jreese

2007-11-10 15:42

reporter   ~0016149

I've attached a patch containing the first step in this process. The patch includes:

  • Converts Mantis category system and API to use unique ID's for every category.
  • Updates/upgrades existing installations to the new category schema.
  • Handles legacy category history entries alongside new style entries.
  • Maintains all existing category functionality as visible to users.

This patch will require you to upgrade your database to work properly. Make sure you backup data before upgrading so that you can revert when you are done testing.

vboctor

vboctor

2007-11-10 17:25

manager   ~0016150

Partial Review:

install_category_migrate()

  1. $cresult / $bresult / $t_bid / $t_pid | $t_name -> would be better to use more meaningful names.
  2. Use db_query_bound() instead of db_query().
  3. Consider refactoring the code so you don't have to store the list of bugs in memory. We don't want the script to run out of memory with big installation.

schema.php

  1. The index name "idx_category" doesn't match the fields of the index.
  2. Are we going to have a unique index on project_id + name or are we going to depend on the PHP code to enforce that? This index can also be useful when we are listing the list of categories belonging to a project in an ascending order.
  3. Do we want to keep the default category for a project as a config? Or do we want to move this to the project table?
  4. How about "None" vs. "Unknown"?

Configuration

  1. Obsolete default_bug_category and add a new one that has "_id" in it. If we want to make the name more standard with other configs then now is the time to do it. Remember to add the obsoleted config to the obsolete.php and refer to the new one.

bug_report_page.php

  • $f_category_id = gpc_get_string( 'category_id' ) -- shouldn't this be gpc_get_int()?
  • is_blank ( $f_category_id ) doesn't make sense.

bug_api.php

  • Use db_query_bound() instead of db_query().
  • '$c_category_id' -> $c_category_id?
  • category_id = db_prepare_string() -> db_prepare_int()?
  • db_prepare_display(): doesn't make sense to call string_display_line() on an int.

category_api.php

  • Would be nice to update the documentation as part of this change or as a follow up change.
  • category_remove()/category_remove_all() -> rename $p_new_category -> $p_new_category_id.

strings_english.txt
$s_no_cateogry - N/A -> None??

stopped @ filter_api.php

jreese

jreese

2007-11-12 13:05

reporter   ~0016173

Latest patch attached with changes suggested by Victor:

category_migrate()

  • variable names expanded
  • used db_query_bound()
  • refactored out previous refactoring's bug array leftovers

schema.php

  • Renamed index
  • Added project_id as part of primary key
  • Renamed category 0 to 'None'
  • Added category_id column to project table for default category selection.

config

  • obsoleted default_bug_category
  • later work on global categories will introduce replacement configuration

bug_report_page.php

  • Fixed gpc to correct type
  • Fixed no category check to 0

bug_api.php

  • used db_query_bound()
  • Fixed int/string mistakes
  • string_display_line() is called for all int parameters already, so ?

category_api.php

  • Changed variable names in remove/remove_all

other

  • waiting on issues with db_helper_like() (0008571) before finalizing some API routines
  • waiting on finalized categories with global/inheriting features before overhauling documentation
jreese

jreese

2007-11-12 17:00

reporter   ~0016176

New version of today's patch uploaded with numerous fixes suggested by Paul and others. Same name, more fixes.

jreese

jreese

2008-02-04 11:16

reporter   ~0016941

Patch mantis-categories.2008-02-04.patch attached.

I've finalized my work and completed implementation of global and inheriting categories. This is a feature-complete patch, but I would like the eyes of everyone to make sure I'm not making any dumb mistakes.

Cheers

jreese

jreese

2008-02-22 16:42

reporter   ~0017161

Fixed in trunk 1.2.x r5032.

Related Changesets

MantisBT: master 829889da

2007-11-19 20:09

jreese


Details Diff
First phase of bug 0008435: Implement Global and Inheriting Categories Structure.
Implemented category ID behaviors to replicate all existing functionality.

git-svn-id: http://mantisbt.svn.sourceforge.net/svnroot/mantisbt/trunk@4770 <a class="text" href="/?p=mantisbt.git;a=object;h=f5dc347c">f5dc347c</a>-c33d-0410-90a0-b07cc1902cb9
Affected Issues
0008435
mod - bug_update_advanced_page.php Diff File
mod - bug_update_page.php Diff File
mod - core/print_api.php Diff File
mod - core/filter_api.php Diff File
mod - bug_update.php Diff File
mod - core/category_api.php Diff File
mod - manage_proj_cat_edit_page.php Diff File
mod - bug_report_page.php Diff File
mod - view_all_inc.php Diff File
mod - bug_report.php Diff File
mod - config_defaults_inc.php Diff File
mod - core/obsolete.php Diff File
mod - graphs/graph_by_category.php Diff File
mod - core/columns_api.php Diff File
add - admin/install_functions.php Diff File
mod - admin/schema.php Diff File
mod - manage_proj_cat_delete.php Diff File
mod - manage_proj_cat_add.php Diff File
mod - manage_proj_cat_update.php Diff File
mod - core/summary_api.php Diff File
mod - manage_proj_edit_page.php Diff File
mod - admin/install.php Diff File
mod - core/bug_api.php Diff File
mod - core/graph_api.php Diff File
mod - bug_view_page.php Diff File
mod - bug_view_advanced_page.php Diff File
mod - my_view_page.php Diff File
mod - core/my_view_inc.php Diff File
mod - manage_proj_cat_copy.php Diff File
mod - bug_report_advanced_page.php Diff File