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 );
