Merge branch 'develop' of github.com:philsturgeon/codeigniter-reactor into develop
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d740c49
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+
+application/cache/*
+!application/cache/index.html
+!application/cache/.htaccess
+
+application/logs/*
+!application/logs/index.html
+!application/logs/.htaccess
\ No newline at end of file
diff --git a/.hgflow b/.hgflow
deleted file mode 100644
index ad6e56e..0000000
--- a/.hgflow
+++ /dev/null
@@ -1,8 +0,0 @@
-[Basic]
-develop = develop
-feature = feature/
-version_tag = 
-publish = default
-release = release/
-hotfix = hotfix/
-
diff --git a/.hgignore b/.hgignore
deleted file mode 100644
index 5ee4d82..0000000
--- a/.hgignore
+++ /dev/null
@@ -1,7 +0,0 @@
-syntax: glob
-
-.DS_Store
-
-syntax: regexp
-application/cache/(?!index\.html|\.htaccess)
-application/logs/(?!index\.html|\.htaccess)
diff --git a/application/config/migration.php b/application/config/migration.php
index 509fd90..dba8700 100644
--- a/application/config/migration.php
+++ b/application/config/migration.php
@@ -9,7 +9,7 @@
 | and disable it back when you're done.

 |

 */

-$config['migration_enabled'] = TRUE;

+$config['migration_enabled'] = FALSE;

 

 

 /*

diff --git a/application/config/mimes.php b/application/config/mimes.php
index 8065794..82767d7 100644
--- a/application/config/mimes.php
+++ b/application/config/mimes.php
@@ -10,7 +10,7 @@
 
 $mimes = array(	'hqx'	=>	'application/mac-binhex40',
 				'cpt'	=>	'application/mac-compactpro',
-				'csv'	=>	array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel'),
+				'csv'	=>	array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel'),
 				'bin'	=>	'application/macbinary',
 				'dms'	=>	'application/octet-stream',
 				'lha'	=>	'application/octet-stream',
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index 7b92ddf..0a1391d 100755
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -27,10 +27,11 @@
  * @link		http://codeigniter.com/user_guide/
  */
 
-/*
- * ------------------------------------------------------
- *  Define the CodeIgniter Version
- * ------------------------------------------------------
+/**
+ * CodeIgniter Version
+ *
+ * @var string
+ *
  */
 	/**
 	 * CodeIgniter Version
@@ -40,10 +41,11 @@
 	 */
 	define('CI_VERSION', '2.0.2');
 
-/*
- * ------------------------------------------------------
- *  Define the CodeIgniter Branch (Core = TRUE, Reactor = FALSE)
- * ------------------------------------------------------
+/**
+ * CodeIgniter Branch (Core = TRUE, Reactor = FALSE)
+ *
+ * @var boolean
+ *
  */
 	/**
 	 * CodeIgniter Branch (Core = TRUE, Reactor = FALSE)
diff --git a/system/core/Config.php b/system/core/Config.php
index 5cacf86..714c466 100755
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -218,10 +218,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a config file item - adds slash after item
-	 *
-	 * The second parameter allows a slash to be added to the end of
-	 * the item, in the case of a path.
+	 * Fetch a config file item - adds slash after item (if item is not empty)
 	 *
 	 * @access	public
 	 * @param	string	the config item name
@@ -234,6 +231,10 @@
 		{
 			return FALSE;
 		}
+		if( trim($this->config[$item]) == '')
+		{
+			return '';
+		}
 
 		return rtrim($this->config[$item], '/').'/';
 	}
@@ -242,6 +243,7 @@
 
 	/**
 	 * Site URL
+	 * Returns base_url . index_page [. uri_string]
 	 *
 	 * @access	public
 	 * @param	string	the URI string
@@ -256,14 +258,48 @@
 
 		if ($this->item('enable_query_strings') == FALSE)
 		{
+			$suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix');
+			return $this->slash_item('base_url').$this->slash_item('index_page').$this->_uri_string($uri).$suffix;
+		}
+		else
+		{
+			return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri);
+		}
+	}
+
+	// -------------------------------------------------------------
+
+	/**
+	 * Base URL
+	 * Returns base_url [. uri_string]
+	 *
+	 * @access public
+	 * @param string $uri
+	 * @return string
+	 */
+	function base_url($uri = '')
+	{
+		return $this->slash_item('base_url').ltrim($this->_uri_string($uri),'/');
+	}
+
+	// -------------------------------------------------------------
+
+	/**
+	 * Build URI string for use in Config::site_url() and Config::base_url()
+	 *
+	 * @access protected
+	 * @param  $uri
+	 * @return string
+	 */
+	protected function _uri_string($uri)
+	{
+		if ($this->item('enable_query_strings') == FALSE)
+		{
 			if (is_array($uri))
 			{
 				$uri = implode('/', $uri);
 			}
-
-			$index = $this->item('index_page') == '' ? '' : $this->slash_item('index_page');
-			$suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix');
-			return $this->slash_item('base_url').$index.trim($uri, '/').$suffix;
+			$uri = trim($uri, '/');
 		}
 		else
 		{
@@ -277,12 +313,10 @@
 					$str .= $prefix.$key.'='.$val;
 					$i++;
 				}
-
 				$uri = $str;
 			}
-
-			return $this->slash_item('base_url').$this->item('index_page').'?'.$uri;
 		}
+	    return $uri;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/core/Input.php b/system/core/Input.php
index bc202b3..5a033e7 100755
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -710,7 +710,7 @@
 	 */
 	public function is_cli_request()
 	{
-		return (bool) defined('STDIN');
+		return (php_sapi_name() == 'cli') or defined('STDIN');
 	}
 
 }
diff --git a/system/core/Loader.php b/system/core/Loader.php
index 019de82..e7fa3d3 100755
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -1018,22 +1018,22 @@
 					// first, global next
 					if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'))
 					{
-						include_once($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
+						include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
 						break;
 					}
 					elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'))
 					{
-						include_once($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
+						include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
 						break;
 					}
 					elseif (file_exists($path .'config/'.strtolower($class).'.php'))
 					{
-						include_once($path .'config/'.strtolower($class).'.php');
+						include($path .'config/'.strtolower($class).'.php');
 						break;
 					}
 					elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php'))
 					{
-						include_once($path .'config/'.ucfirst(strtolower($class)).'.php');
+						include($path .'config/'.ucfirst(strtolower($class)).'.php');
 						break;
 					}
 				}
diff --git a/system/core/URI.php b/system/core/URI.php
index 10b0b7f..a3ae20c 100755
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -87,7 +87,7 @@
 		if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
 		{
 			// Is the request coming from the command line?
-			if (defined('STDIN'))
+			if (php_sapi_name() == 'cli' or defined('STDIN'))
 			{
 				$this->_set_uri_string($this->_parse_cli_args());
 				return;
diff --git a/system/database/DB_active_rec.php b/system/database/DB_active_rec.php
index 0a25b3c..7bab729 100644
--- a/system/database/DB_active_rec.php
+++ b/system/database/DB_active_rec.php
@@ -870,11 +870,11 @@
 	 */
 	public function limit($value, $offset = '')
 	{
-		$this->ar_limit = $value;
+		$this->ar_limit = (int) $value;
 
 		if ($offset != '')
 		{
-			$this->ar_offset = $offset;
+			$this->ar_offset = (int) $offset;
 		}
 
 		return $this;
@@ -1322,7 +1322,7 @@
 		{
 			if ($this->db_debug)
 			{
-				return $this->display_error('db_myst_use_index');
+				return $this->display_error('db_must_use_index');
 			}
 
 			return FALSE;
@@ -1694,7 +1694,8 @@
 				// is because until the user calls the from() function we don't know if there are aliases
 				foreach ($this->ar_select as $key => $val)
 				{
-					$this->ar_select[$key] = $this->_protect_identifiers($val, FALSE, $this->ar_no_escape[$key]);
+					$no_escape = isset($this->ar_no_escape[$key]) ? $this->ar_no_escape[$key] : NULL;
+					$this->ar_select[$key] = $this->_protect_identifiers($val, FALSE, $no_escape);
 				}
 
 				$sql .= implode(', ', $this->ar_select);
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index a71fca7..0dd29c2 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -234,7 +234,7 @@
 			show_error('A table name is required for that operation.');
 		}
 
-		$sql = $this->_rename_table($table_name, $new_table_name);
+		$sql = $this->_rename_table($this->db->dbprefix.$table_name, $this->db->dbprefix.$new_table_name);
 		return $this->db->query($sql);
 	}
 
diff --git a/system/database/drivers/mysql/mysql_utility.php b/system/database/drivers/mysql/mysql_utility.php
index e9747c5..48c4d63 100644
--- a/system/database/drivers/mysql/mysql_utility.php
+++ b/system/database/drivers/mysql/mysql_utility.php
@@ -96,7 +96,7 @@
 			}
 
 			// Get the table schema
-			$query = $this->db->query("SHOW CREATE TABLE `".$this->db->database.'`.'.$table);
+			$query = $this->db->query("SHOW CREATE TABLE `".$this->db->database.'`.`'.$table.'`');
 
 			// No result means the table name was invalid
 			if ($query === FALSE)
diff --git a/system/database/drivers/sqlsrv/index.html b/system/database/drivers/sqlsrv/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/database/drivers/sqlsrv/index.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php
new file mode 100644
index 0000000..1d32792
--- /dev/null
+++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php
@@ -0,0 +1,598 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		ExpressionEngine Dev Team
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * SQLSRV Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the active record
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		ExpressionEngine Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlsrv_driver extends CI_DB {
+
+	var $dbdriver = 'sqlsrv';
+
+	// The character used for escaping
+	var $_escape_char = '';
+
+	// clause and character used for LIKE escape sequences
+	var $_like_escape_str = " ESCAPE '%s' ";
+	var $_like_escape_chr = '!';
+
+	/**
+	 * The syntax to count rows is slightly different across different
+	 * database engines, so this string appears in each driver and is
+	 * used for the count_all() and count_all_results() functions.
+	 */
+	var $_count_string = "SELECT COUNT(*) AS ";
+	var $_random_keyword = ' ASC'; // not currently supported
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_connect($pooling = false)
+	{
+		// Check for a UTF-8 charset being passed as CI's default 'utf8'.
+		$character_set = (0 === strcasecmp('utf8', $this->char_set)) ? 'UTF-8' : $this->char_set;
+
+		$connection = array(
+			'UID'				=> empty($this->username) ? '' : $this->username,
+			'PWD'				=> empty($this->password) ? '' : $this->password,
+			'Database'			=> $this->database,
+			'ConnectionPooling' => $pooling ? 1 : 0,
+			'CharacterSet'		=> $character_set,
+			'ReturnDatesAsStrings' => 1
+		);
+		
+		// If the username and password are both empty, assume this is a 
+		// 'Windows Authentication Mode' connection.
+		if(empty($connection['UID']) && empty($connection['PWD'])) {
+			unset($connection['UID'], $connection['PWD']);
+		}
+
+		return sqlsrv_connect($this->hostname, $connection);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Persistent database connection
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_pconnect()
+	{
+		$this->db_connect(TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reconnect
+	 *
+	 * Keep / reestablish the db connection if no queries have been
+	 * sent for a length of time exceeding the server's idle timeout
+	 *
+	 * @access	public
+	 * @return	void
+	 */
+	function reconnect()
+	{
+		// not implemented in MSSQL
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select the database
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_select()
+	{
+		return $this->_execute('USE ' . $this->database);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set client character set
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	string
+	 * @return	resource
+	 */
+	function db_set_charset($charset, $collation)
+	{
+		// @todo - add support if needed
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @access	private called by the base class
+	 * @param	string	an SQL query
+	 * @return	resource
+	 */
+	function _execute($sql)
+	{
+		$sql = $this->_prep_query($sql);
+		return sqlsrv_query($this->conn_id, $sql, null, array(
+			'Scrollable'				=> SQLSRV_CURSOR_STATIC,
+			'SendStreamParamsAtExec'	=> true
+		));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Prep the query
+	 *
+	 * If needed, each database adapter can prep the query string
+	 *
+	 * @access	private called by execute()
+	 * @param	string	an SQL query
+	 * @return	string
+	 */
+	function _prep_query($sql)
+	{
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_begin($test_mode = FALSE)
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		// Reset the transaction failure flag.
+		// If the $test_mode flag is set to TRUE transactions will be rolled back
+		// even if the queries produce a successful result.
+		$this->_trans_failure = ($test_mode === TRUE) ? TRUE : FALSE;
+
+		return sqlsrv_begin_transaction($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_commit()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		return sqlsrv_commit($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_rollback()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		return sqlsrv_rollback($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape String
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @return	string
+	 */
+	function escape_str($str, $like = FALSE)
+	{
+		// Escape single quotes
+		return str_replace("'", "''", $str);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function affected_rows()
+	{
+		return @sqlrv_rows_affected($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	* Insert ID
+	*
+	* Returns the last id created in the Identity column.
+	*
+	* @access public
+	* @return integer
+	*/
+	function insert_id()
+	{
+		return $this->query('select @@IDENTITY as insert_id')->row('insert_id');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	* Parse major version
+	*
+	* Grabs the major version number from the
+	* database server version string passed in.
+	*
+	* @access private
+	* @param string $version
+	* @return int16 major version number
+	*/
+	function _parse_major_version($version)
+	{
+		preg_match('/([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $ver_info);
+		return $ver_info[1]; // return the major version b/c that's all we're interested in.
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	* Version number query string
+	*
+	* @access public
+	* @return string
+	*/
+	function _version()
+	{
+		$info = sqlsrv_server_info($this->conn_id);
+		return sprintf("select '%s' as ver", $info['SQLServerVersion']);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Count All" query
+	 *
+	 * Generates a platform-specific query string that counts all records in
+	 * the specified database
+	 *
+	 * @access	public
+	 * @param	string
+	 * @return	string
+	 */
+	function count_all($table = '')
+	{
+		if ($table == '')
+			return '0';
+	
+		$query = $this->query("SELECT COUNT(*) AS numrows FROM " . $this->dbprefix . $table);
+		
+		if ($query->num_rows() == 0)
+			return '0';
+
+		$row = $query->row();
+		return $row->numrows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @access	private
+	 * @param	boolean
+	 * @return	string
+	 */
+	function _list_tables($prefix_limit = FALSE)
+	{
+		return "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	function _list_columns($table = '')
+	{
+		return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->_escape_table($table)."'";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data query
+	 *
+	 * Generates a platform-specific query so that the column data can be retrieved
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @return	object
+	 */
+	function _field_data($table)
+	{
+		return "SELECT TOP 1 * FROM " . $this->_escape_table($table);	
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message string
+	 *
+	 * @access	private
+	 * @return	string
+	 */
+	function _error_message()
+	{
+		$error = array_shift(sqlsrv_errors());
+		return !empty($error['message']) ? $error['message'] : null;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message number
+	 *
+	 * @access	private
+	 * @return	integer
+	 */
+	function _error_number()
+	{
+		$error = array_shift(sqlsrv_errors());
+		return isset($error['SQLSTATE']) ? $error['SQLSTATE'] : null;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape Table Name
+	 *
+	 * This function adds backticks if the table name has a period
+	 * in it. Some DBs will get cranky unless periods are escaped
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	function _escape_table($table)
+	{
+		return $table;
+	}	
+
+
+	/**
+	 * Escape the SQL Identifiers
+	 *
+	 * This function escapes column and table names
+	 *
+	 * @access	private
+	 * @param	string
+	 * @return	string
+	 */
+	function _escape_identifiers($item)
+	{
+		return $item;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * From Tables
+	 *
+	 * This function implicitly groups FROM tables so there is no confusion
+	 * about operator precedence in harmony with SQL standards
+	 *
+	 * @access	public
+	 * @param	type
+	 * @return	type
+	 */
+	function _from_tables($tables)
+	{
+		if ( ! is_array($tables))
+		{
+			$tables = array($tables);
+		}
+
+		return implode(', ', $tables);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	function _insert($table, $keys, $values)
+	{	
+		return "INSERT INTO ".$this->_escape_table($table)." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the update data
+	 * @param	array	the where clause
+	 * @param	array	the orderby clause
+	 * @param	array	the limit clause
+	 * @return	string
+	 */
+	function _update($table, $values, $where)
+	{
+		foreach($values as $key => $val)
+		{
+			$valstr[] = $key." = ".$val;
+		}
+	
+		return "UPDATE ".$this->_escape_table($table)." SET ".implode(', ', $valstr)." WHERE ".implode(" ", $where);
+	}
+	
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 * If the database does not support the truncate() command
+	 * This function maps to "DELETE FROM table"
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	function _truncate($table)
+	{
+		return "TRUNCATE ".$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the where clause
+	 * @param	string	the limit clause
+	 * @return	string
+	 */
+	function _delete($table, $where)
+	{
+		return "DELETE FROM ".$this->_escape_table($table)." WHERE ".implode(" ", $where);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Limit string
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @access	public
+	 * @param	string	the sql query string
+	 * @param	integer	the number of rows to limit the query to
+	 * @param	integer	the offset value
+	 * @return	string
+	 */
+	function _limit($sql, $limit, $offset)
+	{
+		$i = $limit + $offset;
+	
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$i.' ', $sql);		
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @access	public
+	 * @param	resource
+	 * @return	void
+	 */
+	function _close($conn_id)
+	{
+		@sqlsrv_close($conn_id);
+	}
+
+}
+
+
+
+/* End of file mssql_driver.php */
+/* Location: ./system/database/drivers/mssql/mssql_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlsrv/sqlsrv_forge.php b/system/database/drivers/sqlsrv/sqlsrv_forge.php
new file mode 100644
index 0000000..cc88ec5
--- /dev/null
+++ b/system/database/drivers/sqlsrv/sqlsrv_forge.php
@@ -0,0 +1,248 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		ExpressionEngine Dev Team
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * SQLSRV Forge Class
+ *
+ * @category	Database
+ * @author		ExpressionEngine Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlsrv_forge extends CI_DB_forge {
+
+	/**
+	 * Create database
+	 *
+	 * @access	private
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	function _create_database($name)
+	{
+		return "CREATE DATABASE ".$name;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @access	private
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	function _drop_database($name)
+	{
+		return "DROP DATABASE ".$name;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * @access	private
+	 * @return	bool
+	 */
+	function _drop_table($table)
+	{
+		return "DROP TABLE ".$this->db->_escape_identifiers($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create Table
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @param	array	the fields
+	 * @param	mixed	primary key(s)
+	 * @param	mixed	key(s)
+	 * @param	boolean	should 'IF NOT EXISTS' be added to the SQL
+	 * @return	bool
+	 */
+	function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	{
+		$sql = 'CREATE TABLE ';
+
+		if ($if_not_exists === TRUE)
+		{
+			$sql .= 'IF NOT EXISTS ';
+		}
+
+		$sql .= $this->db->_escape_identifiers($table)." (";
+		$current_field_count = 0;
+
+		foreach ($fields as $field=>$attributes)
+		{
+			// Numeric field names aren't allowed in databases, so if the key is
+			// numeric, we know it was assigned by PHP and the developer manually
+			// entered the field information, so we'll simply add it to the list
+			if (is_numeric($field))
+			{
+				$sql .= "\n\t$attributes";
+			}
+			else
+			{
+				$attributes = array_change_key_case($attributes, CASE_UPPER);
+
+				$sql .= "\n\t".$this->db->_protect_identifiers($field);
+
+				$sql .=  ' '.$attributes['TYPE'];
+
+				if (array_key_exists('CONSTRAINT', $attributes))
+				{
+					$sql .= '('.$attributes['CONSTRAINT'].')';
+				}
+
+				if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE)
+				{
+					$sql .= ' UNSIGNED';
+				}
+
+				if (array_key_exists('DEFAULT', $attributes))
+				{
+					$sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\'';
+				}
+
+				if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE)
+				{
+					$sql .= ' NULL';
+				}
+				else
+				{
+					$sql .= ' NOT NULL';
+				}
+
+				if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE)
+				{
+					$sql .= ' AUTO_INCREMENT';
+				}
+			}
+
+			// don't add a comma on the end of the last field
+			if (++$current_field_count < count($fields))
+			{
+				$sql .= ',';
+			}
+		}
+
+		if (count($primary_keys) > 0)
+		{
+			$primary_keys = $this->db->_protect_identifiers($primary_keys);
+			$sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")";
+		}
+
+		if (is_array($keys) && count($keys) > 0)
+		{
+			foreach ($keys as $key)
+			{
+				if (is_array($key))
+				{
+					$key = $this->db->_protect_identifiers($key);
+				}
+				else
+				{
+					$key = array($this->db->_protect_identifiers($key));
+				}
+
+				$sql .= ",\n\tFOREIGN KEY (" . implode(', ', $key) . ")";
+			}
+		}
+
+		$sql .= "\n)";
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Alter table query
+	 *
+	 * Generates a platform-specific query so that a table can be altered
+	 * Called by add_column(), drop_column(), and column_alter(),
+	 *
+	 * @access	private
+	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
+	 * @param	string	the column name
+	 * @param	string	the table name
+	 * @param	string	the column definition
+	 * @param	string	the default value
+	 * @param	boolean	should 'NOT NULL' be added
+	 * @param	string	the field after which we should add the new field
+	 * @return	object
+	 */
+	function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	{
+		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name);
+
+		// DROP has everything it needs now.
+		if ($alter_type == 'DROP')
+		{
+			return $sql;
+		}
+
+		$sql .= " $column_definition";
+
+		if ($default_value != '')
+		{
+			$sql .= " DEFAULT \"$default_value\"";
+		}
+
+		if ($null === NULL)
+		{
+			$sql .= ' NULL';
+		}
+		else
+		{
+			$sql .= ' NOT NULL';
+		}
+
+		if ($after_field != '')
+		{
+			$sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field);
+		}
+
+		return $sql;
+
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rename a table
+	 *
+	 * Generates a platform-specific query so that a table can be renamed
+	 *
+	 * @access	private
+	 * @param	string	the old table name
+	 * @param	string	the new table name
+	 * @return	string
+	 */
+	function _rename_table($table_name, $new_table_name)
+	{
+		// I think this syntax will work, but can find little documentation on renaming tables in MSSQL
+		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name);
+		return $sql;
+	}
+
+}
+
+/* End of file mssql_forge.php */
+/* Location: ./system/database/drivers/mssql/mssql_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlsrv/sqlsrv_result.php b/system/database/drivers/sqlsrv/sqlsrv_result.php
new file mode 100644
index 0000000..bf0abd1
--- /dev/null
+++ b/system/database/drivers/sqlsrv/sqlsrv_result.php
@@ -0,0 +1,169 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		ExpressionEngine Dev Team
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * SQLSRV Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @category	Database
+ * @author		ExpressionEngine Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlsrv_result extends CI_DB_result {
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function num_rows()
+	{
+		return @sqlsrv_num_rows($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function num_fields()
+	{
+		return @sqlsrv_num_fields($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @access	public
+	 * @return	array
+	 */
+	function list_fields()
+	{
+		$field_names = array();
+		foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field)
+		{
+			$field_names[] = $field['Name'];
+		}
+		
+		return $field_names;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @access	public
+	 * @return	array
+	 */
+	function field_data()
+	{
+		$retval = array();
+		foreach(sqlsrv_field_metadata($this->result_id) as $offset => $field)
+		{
+			$F 				= new stdClass();
+			$F->name 		= $field['Name'];
+			$F->type 		= $field['Type'];
+			$F->max_length	= $field['Size'];
+			$F->primary_key = 0;
+			$F->default		= '';
+			
+			$retval[] = $F;
+		}
+		
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	null
+	 */
+	function free_result()
+	{
+		if (is_resource($this->result_id))
+		{
+			sqlsrv_free_stmt($this->result_id);
+			$this->result_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Data Seek
+	 *
+	 * Moves the internal pointer to the desired offset.  We call
+	 * this internally before fetching results to make sure the
+	 * result set starts at zero
+	 *
+	 * @access	private
+	 * @return	array
+	 */
+	function _data_seek($n = 0)
+	{
+		// Not implemented
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @access	private
+	 * @return	array
+	 */
+	function _fetch_assoc()
+	{
+		return sqlsrv_fetch_array($this->result_id, SQLSRV_FETCH_ASSOC);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @access	private
+	 * @return	object
+	 */
+	function _fetch_object()
+	{
+		return sqlsrv_fetch_object($this->result_id);
+	}
+
+}
+
+
+/* End of file mssql_result.php */
+/* Location: ./system/database/drivers/mssql/mssql_result.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlsrv/sqlsrv_utility.php b/system/database/drivers/sqlsrv/sqlsrv_utility.php
new file mode 100644
index 0000000..13a1850
--- /dev/null
+++ b/system/database/drivers/sqlsrv/sqlsrv_utility.php
@@ -0,0 +1,88 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		ExpressionEngine Dev Team
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * SQLSRV Utility Class
+ *
+ * @category	Database
+ * @author		ExpressionEngine Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlsrv_utility extends CI_DB_utility {
+
+	/**
+	 * List databases
+	 *
+	 * @access	private
+	 * @return	bool
+	 */
+	function _list_databases()
+	{
+		return "EXEC sp_helpdb"; // Can also be: EXEC sp_databases
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Optimize table query
+	 *
+	 * Generates a platform-specific query so that a table can be optimized
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	object
+	 */
+	function _optimize_table($table)
+	{
+		return FALSE; // Is this supported in MS SQL?
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Repair table query
+	 *
+	 * Generates a platform-specific query so that a table can be repaired
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	object
+	 */
+	function _repair_table($table)
+	{
+		return FALSE; // Is this supported in MS SQL?
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * MSSQL Export
+	 *
+	 * @access	private
+	 * @param	array	Preferences
+	 * @return	mixed
+	 */
+	function _backup($params = array())
+	{
+		// Currently unsupported
+		return $this->db->display_error('db_unsuported_feature');
+	}
+
+}
+
+/* End of file mssql_utility.php */
+/* Location: ./system/database/drivers/mssql/mssql_utility.php */
\ No newline at end of file
diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php
index 5720a06..47f93e7 100644
--- a/system/helpers/form_helper.php
+++ b/system/helpers/form_helper.php
@@ -249,7 +249,7 @@
 {
 	function form_textarea($data = '', $value = '', $extra = '')
 	{
-		$defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '90', 'rows' => '12');
+		$defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '40', 'rows' => '10');
 
 		if ( ! is_array($data) OR ! isset($data['value']))
 		{
diff --git a/system/helpers/html_helper.php b/system/helpers/html_helper.php
index 080f622..b64b606 100644
--- a/system/helpers/html_helper.php
+++ b/system/helpers/html_helper.php
@@ -124,6 +124,10 @@
 			}
 			$attributes = $atts;
 		}
+		elseif (is_string($attributes) AND strlen($attributes) > 0)
+		{
+			$attributes = ' '. $attributes;
+		}
 
 		// Write the opening list tag
 		$out .= "<".$type.$attributes.">\n";
diff --git a/system/helpers/inflector_helper.php b/system/helpers/inflector_helper.php
index c7c113b..7b99bc5 100644
--- a/system/helpers/inflector_helper.php
+++ b/system/helpers/inflector_helper.php
@@ -41,30 +41,48 @@
 {
 	function singular($str)
 	{
-		$str = trim($str);
-		$end = substr($str, -3);
-        
-        $str = preg_replace('/(.*)?([s|c]h)es/i','$1$2',$str);
-        
-		if (strtolower($end) == 'ies')
-		{
-			$str = substr($str, 0, strlen($str)-3).(preg_match('/[a-z]/',$end) ? 'y' : 'Y');
-		}
-		elseif (strtolower($end) == 'ses')
-		{
-			$str = substr($str, 0, strlen($str)-2);
-		}
-		else
-		{
-			$end = strtolower(substr($str, -1));
+		$result = strval($str);
 
-			if ($end == 's')
+		$singular_rules = array(
+			'/(matr)ices$/'         => '\1ix',
+			'/(vert|ind)ices$/'     => '\1ex',
+			'/^(ox)en/'             => '\1',
+			'/(alias)es$/'          => '\1',
+			'/([octop|vir])i$/'     => '\1us',
+			'/(cris|ax|test)es$/'   => '\1is',
+			'/(shoe)s$/'            => '\1',
+			'/(o)es$/'              => '\1',
+			'/(bus|campus)es$/'     => '\1',
+			'/([m|l])ice$/'         => '\1ouse',
+			'/(x|ch|ss|sh)es$/'     => '\1',
+			'/(m)ovies$/'           => '\1\2ovie',
+			'/(s)eries$/'           => '\1\2eries',
+			'/([^aeiouy]|qu)ies$/'  => '\1y',
+			'/([lr])ves$/'          => '\1f',
+			'/(tive)s$/'            => '\1',
+			'/(hive)s$/'            => '\1',
+			'/([^f])ves$/'          => '\1fe',
+			'/(^analy)ses$/'        => '\1sis',
+			'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/' => '\1\2sis',
+			'/([ti])a$/'            => '\1um',
+			'/(p)eople$/'           => '\1\2erson',
+			'/(m)en$/'              => '\1an',
+			'/(s)tatuses$/'         => '\1\2tatus',
+			'/(c)hildren$/'         => '\1\2hild',
+			'/(n)ews$/'             => '\1\2ews',
+			'/([^u])s$/'            => '\1',
+		);
+		
+		foreach ($singular_rules as $rule => $replacement)
+		{
+			if (preg_match($rule, $result))
 			{
-				$str = substr($str, 0, strlen($str)-1);
+				$result = preg_replace($rule, $replacement, $result);
+				break;
 			}
 		}
 
-		return $str;
+		return $result;
 	}
 }
 
@@ -83,40 +101,41 @@
 if ( ! function_exists('plural'))
 {
 	function plural($str, $force = FALSE)
-	{   
-        $str = trim($str);
-		$end = substr($str, -1);
+	{
+		$result = strval($str);
+	
+		$plural_rules = array(
+			'/^(ox)$/'                 => '\1\2en',     // ox
+			'/([m|l])ouse$/'           => '\1ice',      // mouse, louse
+			'/(matr|vert|ind)ix|ex$/'  => '\1ices',     // matrix, vertex, index
+			'/(x|ch|ss|sh)$/'          => '\1es',       // search, switch, fix, box, process, address
+			'/([^aeiouy]|qu)y$/'       => '\1ies',      // query, ability, agency
+			'/(hive)$/'                => '\1s',        // archive, hive
+			'/(?:([^f])fe|([lr])f)$/'  => '\1\2ves',    // half, safe, wife
+			'/sis$/'                   => 'ses',        // basis, diagnosis
+			'/([ti])um$/'              => '\1a',        // datum, medium
+			'/(p)erson$/'              => '\1eople',    // person, salesperson
+			'/(m)an$/'                 => '\1en',       // man, woman, spokesman
+			'/(c)hild$/'               => '\1hildren',  // child
+			'/(buffal|tomat)o$/'       => '\1\2oes',    // buffalo, tomato
+			'/(bu|campu)s$/'           => '\1\2ses',    // bus, campus
+			'/(alias|status|virus)/'   => '\1es',       // alias
+			'/(octop)us$/'             => '\1i',        // octopus
+			'/(ax|cris|test)is$/'      => '\1es',       // axis, crisis
+			'/s$/'                     => 's',          // no change (compatibility)
+			'/$/'                      => 's',
+		);
 
-		if (preg_match('/y/i',$end))
+		foreach ($plural_rules as $rule => $replacement)
 		{
-			// Y preceded by vowel => regular plural
-			$vowels = array('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U');
-			$str = in_array(substr($str, -2, 1), $vowels) ? $str.'s' : substr($str, 0, -1).'ies';
-		}
-		elseif (preg_match('/h/i',$end))
-		{
-            if(preg_match('/^[c|s]h$/i',substr($str, -2)))
+			if (preg_match($rule, $result))
 			{
-				$str .= 'es';
+				$result = preg_replace($rule, $replacement, $result);
+				break;
 			}
-			else
-			{
-				$str .= 's';
-			}
-		}
-		elseif (preg_match('/s/i',$end))
-		{
-			if ($force == TRUE)
-			{
-				$str .= 'es';
-			}
-		}
-		else
-		{
-			$str .= 's';
 		}
 
-		return $str;
+		return $result;
 	}
 }
 
diff --git a/system/helpers/string_helper.php b/system/helpers/string_helper.php
index 7765bba..9fa69f4 100644
--- a/system/helpers/string_helper.php
+++ b/system/helpers/string_helper.php
@@ -243,6 +243,23 @@
 // ------------------------------------------------------------------------
 
 /**
+ * Add's _1 to a string or increment the ending number to allow _2, _3, etc
+ *
+ * @param   string  $str  required
+ * @param   string  $separator  What should the duplicate number be appended with
+ * @param   string  $first  Which number should be used for the first dupe increment
+ * @return  string
+ */
+function increment_string($str, $separator = '_', $first = 1)
+{
+	preg_match('/(.+)'.$separator.'([0-9]+)$/', $str, $match);
+
+	return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first;
+}
+
+// ------------------------------------------------------------------------
+
+/**
  * Alternator
  *
  * Allows strings to be alternated.  See docs...
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index d0516ce..9f4b852 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -50,18 +50,21 @@
 
 /**
  * Base URL
- *
- * Returns the "base_url" item from your config file
+ * 
+ * Create a local URL based on your basepath.
+ * Segments can be passed in as a string or an array, same as site_url
+ * or a URL to a file can be passed in, e.g. to an image file.
  *
  * @access	public
+ * @param string
  * @return	string
  */
 if ( ! function_exists('base_url'))
 {
-	function base_url()
+	function base_url($uri = '')
 	{
 		$CI =& get_instance();
-		return $CI->config->slash_item('base_url');
+		return $CI->config->base_url($uri);
 	}
 }
 
diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php
index c32f2c1..9881c1e 100644
--- a/system/libraries/Driver.php
+++ b/system/libraries/Driver.php
@@ -54,7 +54,7 @@
 			if ( ! class_exists($child_class))
 			{
 				// check application path first
-				foreach (array(APPPATH, BASEPATH) as $path)
+				foreach (get_instance()->load->get_package_paths(TRUE) as $path)
 				{
 					// loves me some nesting!
 					foreach (array(ucfirst($driver_name), $driver_name) as $class)
@@ -226,4 +226,4 @@
 // END CI_Driver CLASS
 
 /* End of file Driver.php */
-/* Location: ./system/libraries/Driver.php */
+/* Location: ./system/libraries/Driver.php */
\ No newline at end of file
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 9b3bc75..fb9a750 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -395,7 +395,7 @@
 	public function attach($filename, $disposition = 'attachment')
 	{
 		$this->_attach_name[] = $filename;
-		$this->_attach_type[] = $this->_mime_types(next(explode('.', basename($filename))));
+		$this->_attach_type[] = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION));
 		$this->_attach_disp[] = $disposition; // Can also be 'inline'  Not sure if it matters
 		return $this;
 	}
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index fd95d76..a34809e 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -1378,4 +1378,4 @@
 // END Form Validation Class
 
 /* End of file Form_validation.php */
-/* Location: ./system/libraries/Form_validation.php */
\ No newline at end of file
+/* Location: ./system/libraries/Form_validation.php */
diff --git a/system/libraries/User_agent.php b/system/libraries/User_agent.php
index 016102a..0b77a7d 100644
--- a/system/libraries/User_agent.php
+++ b/system/libraries/User_agent.php
@@ -142,7 +142,7 @@
 	{
 		$this->_set_platform();
 
-		foreach (array('_set_browser', '_set_robot', '_set_mobile') as $function)
+		foreach (array('_set_robot', '_set_browser', '_set_mobile') as $function)
 		{
 			if ($this->$function() === TRUE)
 			{
diff --git a/user_guide/changelog.html b/user_guide/changelog.html
index e0cefc3..20b1c4e 100644
--- a/user_guide/changelog.html
+++ b/user_guide/changelog.html
@@ -59,6 +59,44 @@
 
 <p>The <img src="images/reactor-bullet.png" width="16" height="16" alt="Reactor Marker" /> indicates items that were contributed to CodeIgniter via CodeIgniter Reactor.</p>
 
+<h2>Version 2.1.0</h2>
+<p>Release Date: Not Released</p>
+
+<ul>
+	<li>General Changes
+		<ul>
+			<li class="reactor">Callback validation rules can now accept parameters like any other validation rule.</li>
+		</ul>
+	</li>
+	<li>Helpers
+		<ul>
+			<li class="reactor">Added <samp>increment_string()</samp> to <a href="helpers/string_helper.html">String Helper</a> to turn "foo" into "foo-1" or "foo-1" into "foo-2".</li>
+		</ul>
+	</li>
+	<li>Database
+		<ul>
+			<li class="reactor">Added a <a href="http://www.cubrid.org/" target="_blank">CUBRID</a> driver to the <a href="libraries/database.html">Database Driver</a>. Thanks to the CUBRID team for supplying this patch.</li>
+			<li class="reactor">Typecast limit and offset in the <a href="database/queries.html">Database Driver</a> to integers to avoid possible injection.</li>
+		</ul>
+	</li>
+	<li>Libraries
+		<ul>
+			<li class="reactor">Changed <kbd>$this->cart->insert()</kbd> in the <a href="libraries/cart.html">Cart Library</a> to return the Row ID if a single item was inserted successfully.</li>
+			<li class="reactor">Added support to set an optional parameter in your callback rules of validation using the <a href="libraries/form_validation.html">Form Validation Library</a>.</li>
+			<li class="reactor">Added a <a href="libraries/migration.html">Migration Library</a> to assist with applying incremental updates to your database schema.</li>
+			<li class="reactor">Driver children can be located in any package path.</li>
+		</ul>
+	</li>
+</ul>
+
+<h3>Bug fixes for 2.1.0</h3>
+<ul>
+	<li class="reactor">Fixed #378 Robots identified as regular browsers by the User Agent class.</li>
+	<li class="reactor">If a config class was loaded first then a library with the same name is loaded, the config would be ignored.</li>
+	<li class="reactor">Fixed a bug (Reactor #19) where 1) the 404_override route was being ignored in some cases, and 2) auto-loaded libraries were not available to the 404_override controller when a controller existed but the requested method did not.</li>
+	<li class="rector">Fixed a bug (Reactor #89) where MySQL export would fail if the table had hyphens or other non alphanumeric/underscore characters.</li>
+</ul>
+
 <h2>Version 2.0.3</h2>
 <p>Release Date: Not Released</p>
 
@@ -74,14 +112,15 @@
 			<li>Added Session Class userdata to the output profiler.  Additionally, added a show/hide toggle on HTTP Headers, Session Data and Config Variables.</li>
 			<li>Removed internal usage of the <samp>EXT</samp> constant.</li>
 			<li>Visual updates to the welcome_message view file and default error templates. Thanks to <a href="https://bitbucket.org/danijelb">danijelb</a> for the pull request.</li>
-			<li>Added <samp>insert_batch()</samp> function to the PostgreSQL database driver. Thanks to epallerols for the patch.</li>
-			<li class="reactor">Callback validation rules can now accept parameters like any other validation rule.</li>
+			<li class="reactor">Added <samp>insert_batch()</samp> function to the PostgreSQL database driver.  Thanks to epallerols for the patch.</li>
+			<li class="reactor">Added "application/x-csv" to mimes.php.</li>
 		</ul>
 	</li>
 	<li>Helpers
 		<ul>
 			<li>Added an optional third parameter to <samp>heading()</samp> which allows adding html attributes to the rendered heading tag.</li>
 			<li class="reactor"><kbd>form_open()</kbd> now only adds a hidden (Cross-site Reference Forgery) protection field when the form's action is internal and is set to the post method. (Reactor #165)</li>
+			<li class="reactor">Re-worked <samp>plural()</samp> and <samp>singular()</samp> functions in the <a href="helpers/inflector_helper.html">Inflector helper</a> to support considerably more words.</li>
 		</ul>
 	</li>
 	<li>Libraries
@@ -91,7 +130,7 @@
 			<li class="reactor">Added <kbd>$this->db->set_dbprefix()</kbd> to the <a href="database/queries.html">Database Driver</a>.</li>
 			<li class="reactor">Changed <kbd>$this->cart->insert()</kbd> in the <a href="libraries/cart.html">Cart Library</a> to return the Row ID if a single item was inserted successfully.</li>
 			<li class="reactor">Added <kbd>$this->load->get_var()</kbd> to the <a href="libraries/loader.html">Loader library</a> to retrieve global vars set with <kbd>$this->load->view()</kbd> and <kbd>$this->load->vars()</kbd>.</li>
-			<li>Changed <kbd>$this->db->having()</kbd> to insert quotes using escape() rather than escape_str().</li>
+			<li class="reactor">Changed <kbd>$this->db->having()</kbd> to insert quotes using escape() rather than escape_str().</li>
 		</ul>
 	</li>
 </ul>
@@ -104,9 +143,12 @@
 	<li>Fixed a bug (Reactor #231) where Sessions Library database table example SQL did not contain an index on last_activity. See <a href="installation/upgrade_203.html">Upgrade Notes</a>.</li>
 	<li>Fixed a bug (Reactor #229) where the Sessions Library example SQL in the documentation contained incorrect SQL.</li>
 	<li>Fixed a bug (Core #340) where when passing in the second parameter to $this->db->select(), column names in subsequent queries would not be properly escaped.</li>
+	<li class="reactor">Fixed issue #199 - Attributes passed as string does not include a space between it and the opening tag.</li>
 	<li class="reactor">Fixed a bug where the method <kbd>$this->cart->total_items()</kbd> from <a href="libraries/cart.html">Cart Library</a> now returns the sum of the quantity of all items in the cart instead of your total count.</li>
-	<li>Fixed a bug where not setting 'null' when adding fields in db_forge for mysql and mysqli drivers would default to NULL instead of NOT NULL as the docs suggest.</li>
-	<li class="reactor">Fixed a bug (Reactor #19) where 1) the 404_override route was being ignored in some cases, and 2) auto-loaded libraries were not available to the 404_override controller when a controller existed but the requested method did not.</li>
+	<li class="reactor">Fixed a bug where not setting 'null' when adding fields in db_forge for MySQL and MySQLi drivers would default to NULL instead of NOT NULL as the docs suggest.</li>
+	<li class="reactor">Fixed a bug where using <kbd>$this->db->select_max()</kdb>, <kbd>$this->db->select_min()</kdb>, etc could throw notices. Thanks to w43l for the patch.</li>
+	<li class="reactor">Replace checks for STDIN with php_sapi_name() == 'cli' which on the whole is more reliable. This should get parameters in crontab working.</li>
+	<li>Fixed a bug where <a href="libraries/email.html">Email library</a> attachments with a "." in the name would using invalid MIME-types.</li>
 </ul>
 
 <h2>Version 2.0.2</h2>
diff --git a/user_guide/general/cli.html b/user_guide/general/cli.html
index 962954b..ed6fc85 100644
--- a/user_guide/general/cli.html
+++ b/user_guide/general/cli.html
@@ -72,7 +72,7 @@
 <a name="what"></a>
 <h2>What is the CLI?</h2>
 
-<p><dfn>The command-line interface is a text-based method of interacting with computers that looks like what most people remember as DOS.</dfn></p>
+<p><dfn>The command-line interface is a text-based method of interacting with computers.</dfn> For more information, check the <a href="http://en.wikipedia.org/wiki/Command-line_interface">Wikipedia article</a>.</p>
 
 <a name="why"></a>
 
diff --git a/user_guide/helpers/string_helper.html b/user_guide/helpers/string_helper.html
index 169ee4e..7c1d30c 100644
--- a/user_guide/helpers/string_helper.html
+++ b/user_guide/helpers/string_helper.html
@@ -90,6 +90,17 @@
 <code>echo random_string('alnum', 16);</code>
 
 
+<h2>increment_string()</h2>
+
+<p>Increments a string by appending a number to it or increasing the number. Useful for creating "copies" or a file or duplicating database content which has unique titles or slugs.</p>
+
+<p>Usage example:</p>
+
+<code>echo increment_string('file', '_'); // "file_1"<br/>
+echo increment_string('file', '-', 2); // "file-2"<br/>
+echo increment_string('file-4'); // "file-5"<br/></code>
+
+
 <h2>alternator()</h2>
 
 <p>Allows two or more items to be alternated between, when cycling through a loop.  Example:</p>
diff --git a/user_guide/helpers/url_helper.html b/user_guide/helpers/url_helper.html
index d20f1b1..ac9d0a6 100644
--- a/user_guide/helpers/url_helper.html
+++ b/user_guide/helpers/url_helper.html
@@ -70,7 +70,7 @@
 <h2>site_url()</h2>
 
 <p>Returns your site URL, as specified in your config file.  The index.php file (or whatever you have set as your
-site <dfn>index_page</dfn> in your config file) will be added to the URL, as will any URI segments you pass to the function.</p>
+site <dfn>index_page</dfn> in your config file) will be added to the URL, as will any URI segments you pass to the function, and the <dfn>url_suffix</dfn> as set in your config file.</p>
 
 <p>You are encouraged to use this function any time you need to generate a local URL so that your pages become more portable
 in the event your URL changes.</p>
@@ -93,6 +93,20 @@
 <p>Returns your site base URL, as specified in your config file.  Example:</p>
 <code>echo base_url();</code>
 
+<p>This function returns the same thing as site_url, without the <dfn>index_page</dfn> or <dfn>url_suffix</dfn> being appended.</p>
+
+<p>Also like site_url, you can supply segments as a string or an array.  Here is a string example:</p>
+
+<code>echo base_url("blog/post/123");</code>
+
+<p>The above example would return something like: http://example.com/blog/post/123</p>
+
+<p>This is useful because unlike site_url(), you can supply a string to a file, such as an image or stylesheet.  For example:</p>
+
+<code>echo base_url("images/icons/edit.png");</code>
+
+<p>This would give you something like: http://example.com/images/icons/edit.png</p>
+
 
 <h2>current_url()</h2>
 <p>Returns the full URL (including segments) of the page being currently viewed.</p>
diff --git a/user_guide/libraries/cart.html b/user_guide/libraries/cart.html
index 81b43e3..2e2beed 100644
--- a/user_guide/libraries/cart.html
+++ b/user_guide/libraries/cart.html
@@ -109,7 +109,8 @@
 <li><strong>options</strong> - Any additional attributes that are needed to identify the product. These must be passed via an array.
 </ul>
 
-<p>In addition to the five indexes above, there are two reserved words: <dfn>rowid</dfn> and <dfn>subtotal</dfn>. These are used internally by the Cart class, so please do NOT use those words as index names when inserting data into the cart.</p>
+<p>In addition to the five indexes above, there are two reserved words: <dfn>rowid</dfn> and <dfn>subtotal</dfn>.  These are used internally by the Cart class, so
+please do NOT use those words as index names when inserting data into the cart.</p>
 
 <p>Your array may contain additional data. Anything you include in your array will be stored in the session. However, it is best to standardize your data among all your products in order to make displaying the information in a table easier.</p>
 
@@ -264,7 +265,7 @@
 
 </code>
 
-<p><strong>What is a Row ID?</strong>&nbsp; The <kbd>row ID</kbd> is a unique identifier that is generated by the cart code when an item is added to the cart. The reason a
+<p><strong>What is a Row ID?</strong>&nbsp; The <kbd>row ID</kbd> is a unique identifier that is generated by the cart code when an item is added to the cart.  The reason a
 unique ID is created is so that identical products with different options can be managed by the cart.</p>
 
 <p>For example, let's say someone buys two identical t-shirts (same product ID), but in different sizes. The product ID (and other attributes) will be
diff --git a/user_guide/libraries/config.html b/user_guide/libraries/config.html
index 2433ec4..be75801 100644
--- a/user_guide/libraries/config.html
+++ b/user_guide/libraries/config.html
@@ -194,6 +194,11 @@
 <h2>$this->config->site_url();</h2>
 <p>This function retrieves the URL to your site, along with the "index" value you've specified in the config file.</p>
 
+<h2>$this->config->base_url();</h2>
+<p>This function retrieves the URL to your site, plus an optional path such as to a stylesheet or image.</p>
+
+<p>The two functions above are normally accessed via the corresponding functions in the <a href="../helpers/url_helper.html">URL Helper.</a></p>    
+    
 <h2>$this->config->system_url();</h2>
 <p>This function retrieves the URL to your <dfn>system folder</dfn>.</p>
 
diff --git a/user_guide/libraries/form_validation.html b/user_guide/libraries/form_validation.html
index bba8f50..da2f5e5 100644
--- a/user_guide/libraries/form_validation.html
+++ b/user_guide/libraries/form_validation.html
@@ -508,11 +508,9 @@
 
 <code>$this->form_validation->set_rules('username', 'Username', '<kbd>callback_username_check</kbd>');</code>
 
-
 <p>Then add a new function called <dfn>username_check</dfn> to your controller.  Here's how your controller should now look:</p>
 
-
-<textarea class="textarea" style="width:100%" cols="50" rows="44">&lt;?php
+<textarea class="textarea" style="width:100%" cols="50" rows="40">&lt;?php
 
 class Form extends CI_Controller {
 
@@ -556,14 +554,13 @@
 <p><dfn>Reload your form and submit it with the word "test" as the username.  You can see that the form field data was passed to your
 callback function for you to process.</dfn></p>
 
-<p><strong>To invoke a callback just put the function name in a rule, with "callback_" as the rule prefix.</strong></p>
+<p>To invoke a callback just put the function name in a rule, with "callback_" as the rule <strong>prefix</strong>. If you need
+to receive an extra parameter in your callback function, just add it normally after the function name between square brackets,
+as in: "callback_foo<strong>[bar]</strong>", then it will be passed as the second argument of your callback function.</p>
 
-<p>You can also process the form data that is passed to your callback and return it.  If your callback returns anything other than a boolean TRUE/FALSE
+<p><strong>Note:</strong> You can also process the form data that is passed to your callback and return it.  If your callback returns anything other than a boolean TRUE/FALSE
 it is assumed that the data is your newly processed form data.</p>
 
-
-
-
 <a name="settingerrors"></a>
 <h2>Setting Error Messages</h2>