Index: wp-admin/includes/plugin.php
===================================================================
--- wp-admin/includes/plugin.php	(revision 6603)
+++ wp-admin/includes/plugin.php	(working copy)
@@ -190,6 +190,109 @@
 	return 0;
 }
 
+/**
+* get_plugin_assets() retrieves the plugin's registered assets from the option
+*
+* Part of the uninstall process. 
+* This function checks whether the plugin author has registered any assets
+* that can be uninstalled and if so returns a named array with those options.
+*
+* @link http://trac.wordpress.org/ticket/5625
+*
+* @param string $plugin_file the path to the plugin file, usually given by __FILE__
+* @return array|bool the plugin's uninstallable assets, or false if there are none
+*
+*/
+function get_plugin_assets( $plugin_file ) {
+	$plugin_assets = get_option( 'plugin_assets' );
+	$plugin = plugin_basename( $plugin_file );
+	if ( !is_array( $plugin_assets ) )
+		return false;
+	if ( isset( $plugin_assets[$plugin] ) ) {
+		return $plugin_assets[$plugin];
+	} else {
+		return false;
+	}
+}
+
+/**
+* is_plugin_uninstallable() checks if a plugin has registered any uninstallable assets
+*
+* Part of the uninstall process. 
+* This function checks whether the plugin author has registered any assets
+* that can be uninstalled.
+*
+* @link http://trac.wordpress.org/ticket/5625
+*
+* @param string $plugin_file the path to the plugin file, usually given by __FILE__
+* @return bool true if the plugin has assets that can be uninstalled
+*
+*/	
+function is_plugin_uninstallable( $plugin_file ){
+	$uninstallable_plugins = get_option( 'plugin_assets' );
+	$plugin = plugin_basename( $plugin_file );
+	if ( isset( $uninstallable_plugins[$plugin] ) ) {
+		return true;
+	} else {
+		return false;
+	}
+}
+
+
+/**
+ * delete_plugin_assets() - Delete the database tables and options created by a plugin
+ *
+ * Part of the uninstall process. 
+ * This function retrieves the list of assets the plugin author registered, using
+ * register_plugin_assets and removes the database tables and options specified.
+ *
+ * @link http://trac.wordpress.org/ticket/5625
+ *
+ * @param string $plugin_file the path to the plugin
+ * @return bool returns true if the tables and options are no longer present
+ */
+function delete_plugin_assets( $plugin_file ){
+	global $wpdb;
+	$deletion_complete = true;
+	$uninstallable_plugins = get_option( 'plugin_assets' );
+	$plugin = plugin_basename( $plugin_file );
+	if ( isset( $uninstallable_plugins[$plugin] ) ) {
+		$plugin_assets = $uninstallable_plugins[$plugin];
+		//remove tables
+		$remove_table_sql = 'DROP TABLE IF EXISTS `%s`';
+		$check_table_sql = 'SELECT 1 FROM `%s` LIMIT 0';
+		foreach( $plugin_assets['tables'] as $table ){
+			//filter
+			$table = str_replace( '`' , '' , $table);
+			$table = str_replace( ';' , '' , $table);
+			$table = str_replace( '*' , '' , $table);
+			
+			//check against core tables
+			if ( !in_array( str_replace( $wpdb->prefix , '' , $table ) , $wpdb->tables ) ) {
+				$sql = sprintf( $remove_table_sql , $table );
+				$wpdb->query( $sql );
+				//check if table is now gone
+				if ( $wpdb->query( sprintf( $check_table_sql , $table ) ) ) {
+					$deletion_complete = false;
+				}
+			}
+		}
+		//remove options
+		foreach( $plugin_assets['options'] as $option ){
+			delete_option( $option );
+			if ( get_option( $option ) ) {
+				$deletion_complete = false;
+			};
+		}
+		//remove assets from asset list
+		if ( $deletion_complete ) {
+			 unregister_plugin_assets( $plugin_file );
+		}
+	} 
+	return $deletion_complete;
+}
+
+
 //
 // Menu
 //
Index: wp-admin/plugins.php
===================================================================
--- wp-admin/plugins.php	(revision 6603)
+++ wp-admin/plugins.php	(working copy)
@@ -27,6 +27,29 @@
 		check_admin_referer('reactivate-all');
 		reactivate_all_plugins('plugins.php?errors=true');
 		wp_redirect('plugins.php?reactivate-all=true'); // overrides the ?error=true one above
+	} elseif ( 'uninstall' == $_GET['action'] ) {
+		check_admin_referer('uninstall-plugin_' . $_GET['plugin']);
+		$current = get_option('active_plugins');
+		$plugin = trim($_GET['plugin']);
+		if ( validate_file($plugin) )
+			wp_die(__('Invalid plugin.'));
+		if ( ! file_exists(ABSPATH . PLUGINDIR . '/' . $plugin) )
+			wp_die(__('Plugin file does not exist.'));	
+		if ( !in_array($plugin, $current ) && is_plugin_uninstallable( $plugin ) ) {	
+			$plugin_assets = get_plugin_assets( $plugin );
+			//check for a callback function
+			if ( $plugin_assets['callback'] != NULL ) {
+				//include the plugin file
+				include_once( ABSPATH . PLUGINDIR . '/' . $plugin );
+				//create and call the action
+				add_action( 'uninstall_plugin_' . $plugin , $plugin_assets['callback'] );
+				do_action( 'uninstall_plugin_' . $plugin );
+			}
+			if ( !delete_plugin_assets( $plugin ) ) {
+				wp_redirect('plugins.php?uninstalled=false');
+			}
+		}
+		wp_redirect('plugins.php?uninstalled=true');
 	}
 
 	exit;
@@ -59,6 +82,12 @@
 	<div id="message" class="updated fade"><p><?php _e('All plugins <strong>deactivated</strong>.'); ?></p></div>
 <?php elseif (isset($_GET['reactivate-all'])) : ?>
 	<div id="message" class="updated fade"><p><?php _e('All plugins <strong>reactivated</strong>.'); ?></p></div>
+<?php elseif (isset($_GET['uninstalled']) && $_GET['uninstalled'] == 'true') : ?>
+<div id="message" class="updated fade"><p><?php _e('Plugin <strong>uninstalled</strong>.'); ?></p></div>
+<?php elseif (isset($_GET['uninstalled']) && $_GET['uninstalled'] != 'true') : ?>
+<div id="message" class="updated fade">
+<p><?php _e('The plugin could not be completely uninstalled. Some database tables, options or files may still exist.'); ?></p>
+</div>
 <?php endif; ?>
 
 <div class="wrap">
@@ -128,6 +157,19 @@
 		<td>$edit</td>";
 		echo"
 	</tr>";
+	
+	if ( is_plugin_uninstallable( $plugin_file ) && (empty( $current_plugins ) || !in_array($plugin_file, $current_plugins) ) ) {
+		$plugin_data = get_plugin_data( ABSPATH . PLUGINDIR . '/' . $plugin_file );
+		$toggle = "<a href='" . wp_nonce_url("plugins.php?action=uninstall&amp;plugin=$plugin_file", 'uninstall-plugin_' . $plugin_file) . "' title='".__('Uninstall this plugin')."' class='delete'><strong>".__("Uninstall all the options and settings (including database tables ) relating to ").$plugin_data['Name']."</strong></a>";
+			?>
+			<tr>
+			<td colspan="5" style="border-top:1px solid #ccc; border-bottom:1px solid #ccc; background-color:#F9B7E0"><?php echo $toggle; ?></td>
+			</tr>
+			<?php
+			}		
+	
+	
+	
 	do_action( 'after_plugin_row', $plugin_file );
 	}
 ?>
Index: wp-includes/plugin.php
===================================================================
--- wp-includes/plugin.php	(revision 6603)
+++ wp-includes/plugin.php	(working copy)
@@ -593,4 +593,115 @@
 		return $function[0].$function[1];
 }
 
+/**
+* register_plugin_assets() - Add the plugin's database tables and option names as assets so they can be removed later.
+*
+* Part of the uninstall process. 
+* When a plugin activates, and creates the database tables, and WordPress options it needs to run the plugin author
+* can register those database tables and WordPress options so that they will be removed when the plugin is uninstalled.
+* A callback can also be registered to handle more complex uninstall requirements.
+*
+* Note: This does not allow files to be registered as there is danger of getting paths wrong and removing
+* similarly named files in different paths.
+*
+* @link http://trac.wordpress.org/ticket/5625
+*
+* @param string $plugin_file the path to the plugin file, usually given by __FILE__
+* @param callback $callback function name of the plugin's uninstaller, can be NULL
+* @param array $options array of option names that should be deleted when the plugin is uninstalled
+* @param array $tables array of table names that should be dropped when the plugin is uninstalled
+*
+*/
+function register_plugin_assets( $plugin_file , $callback , $options = array() , $tables = array() ) {
+	$plugin_assets = get_option( 'plugin_assets' );
+	$plugin = plugin_basename( $plugin_file );
+
+	if ( !is_array( $plugin_assets ) )
+		$plugin_assets = array();
+
+	if( !isset($plugin_assets[$plugin]) )
+		$plugin_assets[$plugin] = array( 'callback' => null, 'tables' => array(), 'options' => array() );
+
+	if( !is_null( $callback ) ) {
+		$plugin_assets[$plugin]['callback'] = $callback;
+	}
+
+	if( is_array($options) && !empty($options) )
+		$plugin_assets[$plugin]['options'] = array_merge( $options, $plugin_assets[$plugin]['options']);
+
+	if( is_array($tables) && !empty($tables) )
+		$plugin_assets[$plugin]['tables'] = array_merge( $tables, $plugin_assets[$plugin]['tables']);
+
+	update_option( 'plugin_assets' , $plugin_assets );
+}
+
+/**
+ * register_uninstall_hook() - Registers an uninstall callback that will be run when uninstalling the plugin.
+ *
+ * @param string $plugin_file Plugin file path
+ * @param callback $callback The hook that will be called during the plugin's uninstall process
+ */
+function register_uninstall_hook($plugin_file, $callback) {
+	register_plugin_assets($plugin_file, $callback);
+}
+
+/**
+ * register_plugin_option_asset() - Registers option(s) that will be uninstalled.
+ *
+ * If there are already options available that will be uninstalled, then the $options
+ * will be added to the list. If there are no options already, then the options will
+ * just be set.
+ *
+ * This function is useful for dynamically adding options that should be uninstalled
+ * during the uninstall process and the plugin won't be used any longer.
+ *
+ * The options should be given as a list without any keys.
+ *
+ * @param string $plugin_file Plugin file path
+ * @param string|array $options Single option or list of options to uninstall
+ */
+function register_plugin_option_asset($plugin_file, $options) {
+	if( !is_array($options) )
+		$options = array( $options );
+
+	register_plugin_assets($plugin_file, null, $options);
+}
+
+/**
+ * register_plugin_table_asset() - Registers table(s) that will be uninstalled.
+ *
+ * The table(s) should only be the ones that were added by your plugin and should
+ * not include the core plugins that WordPress installs. If you need to remove
+ * options then do so using the register_plugin_option_asset() or
+ * register_plugin_asset() function.
+ *
+ * The tables should be given as a list without any keys.
+ *
+ * @param string $plugin_file The path to the plugin file, usually given by __FILE__
+ * @param string|array $tables Table or tables to uninstall
+ */
+function register_plugin_table_asset($plugin_file, $tables) {
+	if( !is_array($tables) )
+		$tables = array( $tables );
+
+	register_plugin_assets($plugin_file, null, null, $tables);
+}
+
+/**
+* unregister_plugin_assets() - Unregisters the plugins uninstallable assets from the option
+*
+* @link http://trac.wordpress.org/ticket/5625
+*
+* @param string $plugin_file The path to the plugin file, usually given by __FILE__
+*
+*/
+function unregister_plugin_assets( $plugin_file ){
+	$plugin_assets = get_option( 'plugin_assets' );
+	$plugin = plugin_basename( $plugin_file );
+	if ( isset( $plugin_assets[$plugin] ) ) {
+		unset($plugin_assets[$plugin]);
+		update_option( 'plugin_assets' , $plugin_assets );
+	}
+}
+
 ?>
