Merge branch 'develop' of https://github.com/bcit-ci/CodeIgniter into develop
diff --git a/application/config/autoload.php b/application/config/autoload.php
index 3f0bd24..4bc6bf0 100644
--- a/application/config/autoload.php
+++ b/application/config/autoload.php
@@ -45,8 +45,9 @@
 | -------------------------------------------------------------------
 |  Auto-load Libraries
 | -------------------------------------------------------------------
-| These are the classes located in the system/libraries folder
-| or in your application/libraries folder.
+| These are the classes located in system/libraries/ or your
+| application/libraries/ directory, with the addition of the
+| 'database' library, which is somewhat of a special case.
 |
 | Prototype:
 |
@@ -63,8 +64,9 @@
 | -------------------------------------------------------------------
 |  Auto-load Drivers
 | -------------------------------------------------------------------
-| These classes are located in the system/libraries folder or in your
-| application/libraries folder within their own subdirectory. They
+| These classes are located in system/libraries/ or in your
+| application/libraries/ directory, but are also placed inside their
+| own subdirectory and they extend the CI_Driver_Library class. They
 | offer multiple interchangeable driver options.
 |
 | Prototype:
diff --git a/application/config/config.php b/application/config/config.php
index 86ca312..f4ba70a 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -284,8 +284,15 @@
 | Cache Include Query String
 |--------------------------------------------------------------------------
 |
-| Set this to TRUE if you want to use different cache files depending on the
-| URL query string.  Please be aware this might result in numerous cache files.
+| Whether to take the URL query string into consideration when generating
+| output cache files. Valid options are:
+|
+|	FALSE      = Disabled
+|	TRUE       = Enabled, take all query parameters into account.
+|	             Please be aware that this may result in numerous cache
+|	             files generated for the same page over and over again.
+|	array('q') = Enabled, but only take into account the specified list
+|	             of query parameters.
 |
 */
 $config['cache_query_string'] = FALSE;
diff --git a/application/config/database.php b/application/config/database.php
index 84aab91..bf9857f 100644
--- a/application/config/database.php
+++ b/application/config/database.php
@@ -40,9 +40,21 @@
 | 				 Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
 |	['swap_pre'] A default table prefix that should be swapped with the dbprefix
 |	['encrypt']  Whether or not to use an encrypted connection.
+|
+|			'mysql' (deprecated), 'sqlsrv' and 'pdo/sqlsrv' drivers accept TRUE/FALSE
+|			'mysqli' and 'pdo/mysql' drivers accept an array with the following options:
+|
+|				'ssl_key'    - Path to the private key file
+|				'ssl_cert'   - Path to the public key certificate file
+|				'ssl_ca'     - Path to the certificate authority file
+|				'ssl_capath' - Path to a directory containing trusted CA certificats in PEM format
+|				'ssl_cipher' - List of *allowed* ciphers to be used for the encryption, separated by colons (':')
+|				'ssl_verify' - TRUE/FALSE; Whether verify the server certificate or not ('mysqli' only)
+|
 |	['compress'] Whether or not to use client compression (MySQL only)
 |	['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
 |							- good for ensuring strict SQL while developing
+|	['ssl_options']	Used to set various SSL options that can be used when making SSL connections.
 |	['failover'] array - A array with 0 or more data for connections if the main should fail.
 |	['save_queries'] TRUE/FALSE - Whether to "save" all executed queries.
 | 				NOTE: Disabling this will also effectively disable both
diff --git a/application/config/migration.php b/application/config/migration.php
index 083bf28..4b585a6 100644
--- a/application/config/migration.php
+++ b/application/config/migration.php
@@ -21,12 +21,12 @@
 | Migration file names may be based on a sequential identifier or on
 | a timestamp. Options are:
 |
-|   'sequential' = Default migration naming (001_add_blog.php)
+|   'sequential' = Sequential migration naming (001_add_blog.php)
 |   'timestamp'  = Timestamp migration naming (20121031104401_add_blog.php)
 |                  Use timestamp format YYYYMMDDHHIISS.
 |
-| If this configuration value is missing the Migration library defaults
-| to 'sequential' for backward compatibility.
+| Note: If this configuration value is missing the Migration library
+|       defaults to 'sequential' for backward compatibility with CI2.
 |
 */
 $config['migration_type'] = 'timestamp';
diff --git a/application/config/mimes.php b/application/config/mimes.php
index d0e1516..1f591ba 100644
--- a/application/config/mimes.php
+++ b/application/config/mimes.php
@@ -154,5 +154,5 @@
 	'vcf'	=>	'text/x-vcard',
 	'srt'	=>	array('text/srt', 'text/plain'),
 	'vtt'	=>	array('text/vtt', 'text/plain'),
-	'ico'	=>	'image/x-icon'
+	'ico'	=>	array('image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon')
 );
diff --git a/application/views/errors/cli/error_exception.php b/application/views/errors/cli/error_exception.php
index 75d7f0f..0203cf4 100644
--- a/application/views/errors/cli/error_exception.php
+++ b/application/views/errors/cli/error_exception.php
@@ -1,25 +1,21 @@
-<?php
-defined('BASEPATH') OR exit('No direct script access allowed');
-?>
+<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>
 
 An uncaught Exception was encountered
 
-Type: <?php echo get_class($exception); ?>
-Message: <?php echo $message; ?>
-Filename: <?php echo $exception->getFile(); ?>
-Line Number: <?php echo $exception->getLine(); ?>
+Type: <?php echo get_class($exception), "\n"; ?>
+Message: <?php echo $message, "\n"; ?>
+Filename: <?php echo $exception->getFile(), "\n"; ?>
+Line Number: <?php echo $exception->getLine(), "\n"; ?>
 
 <?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === TRUE): ?>
 
 Backtrace:
-	<?php foreach ($exception->getTrace() as $error): ?>
-		<?php if (isset($error['file']) && strpos($error['file'], realpath(BASEPATH)) !== 0): ?>
+<?php	foreach ($exception->getTrace() as $error): ?>
+<?php		if (isset($error['file']) && strpos($error['file'], realpath(BASEPATH)) !== 0): ?>
+	File: <?php echo $error['file'], "\n"; ?>
+	Line: <?php echo $error['line'], "\n"; ?>
+	Function: <?php echo $error['function'], "\n\n"; ?>
+<?php		endif ?>
+<?php	endforeach ?>
 
-	File: <?php echo $error['file']; ?>
-	Line: <?php echo $error['line']; ?>
-	Function: <?php echo $error['function']; ?>
-
-		<?php endif ?>
-
-	<?php endforeach ?>
-<?php endif ?>
\ No newline at end of file
+<?php endif ?>
diff --git a/application/views/errors/cli/error_php.php b/application/views/errors/cli/error_php.php
index fec91e5..0ea9109 100644
--- a/application/views/errors/cli/error_php.php
+++ b/application/views/errors/cli/error_php.php
@@ -1,25 +1,21 @@
-<?php
-defined('BASEPATH') OR exit('No direct script access allowed');
-?>
+<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>
 
 A PHP Error was encountered
 
-Severity: <?php echo $severity;?>
-Message:  <?php echo $message;?>
-Filename: <?php echo $filepath;?>
+Severity: <?php echo $severity, "\n";?>
+Message:  <?php echo $message, "\n";?>
+Filename: <?php echo $filepath, "\n";?>
 Line Number: <?php echo $line;?>
 
 <?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === TRUE): ?>
 
 Backtrace:
-	<?php foreach (debug_backtrace() as $error): ?>
-		<?php if (isset($error['file']) && strpos($error['file'], realpath(BASEPATH)) !== 0): ?>
+<?php	foreach (debug_backtrace() as $error): ?>
+<?php		if (isset($error['file']) && strpos($error['file'], realpath(BASEPATH)) !== 0): ?>
+	File: <?php echo $error['file'], "\n";?>
+	Line: <?php echo $error['line'], "\n";?>
+	Function: <?php echo $error['function'], "\n\n";?>
+<?php		endif ?>
+<?php	endforeach ?>
 
-	File: <?php echo $error['file'];?>
-	Line: <?php echo $error['line'];?>
-	Function: <?php echo $error['function'];?>
-
-		<?php endif ?>
-
-	<?php endforeach ?>
-<?php endif ?>
\ No newline at end of file
+<?php endif ?>
diff --git a/system/core/Output.php b/system/core/Output.php
index e7d559a..76c1329 100644
--- a/system/core/Output.php
+++ b/system/core/Output.php
@@ -556,9 +556,16 @@
 			.$CI->config->item('index_page')
 			.$CI->uri->uri_string();
 
-		if ($CI->config->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
+		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
 		{
-			$uri .= '?'.$_SERVER['QUERY_STRING'];
+			if (is_array($cache_query_string))
+			{
+				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+			}
+			else
+			{
+				$uri .= '?'.$_SERVER['QUERY_STRING'];
+			}
 		}
 
 		$cache_path .= md5($uri);
@@ -646,9 +653,16 @@
 		// Build the file path. The file name is an MD5 hash of the full URI
 		$uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
 
-		if ($CFG->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
+		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
 		{
-			$uri .= '?'.$_SERVER['QUERY_STRING'];
+			if (is_array($cache_query_string))
+			{
+				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+			}
+			else
+			{
+				$uri .= '?'.$_SERVER['QUERY_STRING'];
+			}
 		}
 
 		$filepath = $cache_path.md5($uri);
@@ -729,13 +743,20 @@
 		{
 			$uri = $CI->uri->uri_string();
 
-			if ($CI->config->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
+			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
 			{
-				$uri .= '?'.$_SERVER['QUERY_STRING'];
+				if (is_array($cache_query_string))
+				{
+					$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
+				}
+				else
+				{
+					$uri .= '?'.$_SERVER['QUERY_STRING'];
+				}
 			}
 		}
 
-		$cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri);
+		$cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
 
 		if ( ! @unlink($cache_path))
 		{
diff --git a/system/core/Router.php b/system/core/Router.php
index f91d3f6..af87a30 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -83,7 +83,7 @@
 	 *
 	 * @var	string
 	 */
-	public $directory =	'';
+	public $directory;
 
 	/**
 	 * Default controller (and method if specific)
@@ -105,7 +105,7 @@
 	/**
 	 * Enable query strings flag
 	 *
-	 * Determines wether to use GET parameters or segment URIs
+	 * Determines whether to use GET parameters or segment URIs
 	 *
 	 * @var	bool
 	 */
@@ -126,25 +126,16 @@
 		$this->uri =& load_class('URI', 'core');
 
 		$this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE);
+
+		// If a directory override is configured, it has to be set before any dynamic routing logic
+		is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']);
 		$this->_set_routing();
 
 		// Set any routing overrides that may exist in the main index file
 		if (is_array($routing))
 		{
-			if (isset($routing['directory']))
-			{
-				$this->set_directory($routing['directory']);
-			}
-
-			if ( ! empty($routing['controller']))
-			{
-				$this->set_class($routing['controller']);
-			}
-
-			if ( ! empty($routing['function']))
-			{
-				$this->set_method($routing['function']);
-			}
+			empty($routing['controller']) OR $this->set_class($routing['controller']);
+			empty($routing['function'])   OR $this->set_method($routing['function']);
 		}
 
 		log_message('info', 'Router Class Initialized');
@@ -167,12 +158,17 @@
 		// If this feature is enabled, we will gather the directory/class/method a little differently
 		if ($this->enable_query_strings)
 		{
-			$_d = $this->config->item('directory_trigger');
-			$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
-			if ($_d !== '')
+			// If the directory is set at this time, it means an override exists, so skip the checks
+			if ( ! isset($this->directory))
 			{
-				$this->uri->filter_uri($_d);
-				$this->set_directory($_d);
+				$_d = $this->config->item('directory_trigger');
+				$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
+
+				if ($_d !== '')
+				{
+					$this->uri->filter_uri($_d);
+					$this->set_directory($_d);
+				}
 			}
 
 			$_c = trim($this->config->item('controller_trigger'));
@@ -333,6 +329,8 @@
 	protected function _validate_request($segments)
 	{
 		$c = count($segments);
+		$directory_override = isset($this->directory);
+
 		// Loop through our segments and return as soon as a controller
 		// is found or when such a directory doesn't exist
 		while ($c-- > 0)
@@ -340,7 +338,10 @@
 			$test = $this->directory
 				.ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
 
-			if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
+			if ( ! file_exists(APPPATH.'controllers/'.$test.'.php')
+				&& $directory_override === FALSE
+				&& is_dir(APPPATH.'controllers/'.$this->directory.$segments[0])
+			)
 			{
 				$this->set_directory(array_shift($segments), TRUE);
 				continue;
diff --git a/system/core/Security.php b/system/core/Security.php
index 9cef424..7c51992 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -275,7 +275,7 @@
 			$secure_cookie,
 			config_item('cookie_httponly')
 		);
-		log_message('info', 'CRSF cookie sent');
+		log_message('info', 'CSRF cookie sent');
 
 		return $this;
 	}
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index d99fd00..865498f 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -239,7 +239,7 @@
 	 */
 	public function add_key($key, $primary = FALSE)
 	{
-		if ($primary === TRUE && is_array($key))
+		if (is_array($key))
 		{
 			foreach ($key as $one)
 			{
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index a8b5b35..fc2d590 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -657,10 +657,7 @@
 
 			if ($v !== NULL)
 			{
-				if ($escape === TRUE)
-				{
-					$v = ' '.$this->escape($v);
-				}
+				$v = ' '.$this->escape($v);
 
 				if ( ! $this->_has_operator($k))
 				{
@@ -1736,7 +1733,7 @@
 			return FALSE;
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
+		$sql = $this->_update($this->qb_from[0], $this->qb_set);
 
 		if ($reset === TRUE)
 		{
@@ -1784,7 +1781,7 @@
 			$this->limit($limit);
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
+		$sql = $this->_update($this->qb_from[0], $this->qb_set);
 		$this->_reset_write();
 		return $this->query($sql);
 	}
@@ -1801,7 +1798,7 @@
 	 * @param	string	the table to update data on
 	 * @return	bool
 	 */
-	protected function _validate_update($table = '')
+	protected function _validate_update($table)
 	{
 		if (count($this->qb_set) === 0)
 		{
@@ -1810,7 +1807,7 @@
 
 		if ($table !== '')
 		{
-			$this->qb_from[0] = $table;
+			$this->qb_from = array($this->protect_identifiers($table, TRUE, NULL, FALSE));
 		}
 		elseif ( ! isset($this->qb_from[0]))
 		{
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index e953db0..dd3cc77 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -102,7 +102,6 @@
 	 *
 	 * @param	bool	$persistent
 	 * @return	object
-	 * @todo	SSL support
 	 */
 	public function db_connect($persistent = FALSE)
 	{
@@ -132,8 +131,52 @@
 			$mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode="STRICT_ALL_TABLES"');
 		}
 
-		return $mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags)
-			? $mysqli : FALSE;
+		if (is_array($this->encrypt))
+		{
+			$ssl = array();
+			empty($this->encrypt['ssl_key'])    OR $ssl['key']    = $this->encrypt['ssl_key'];
+			empty($this->encrypt['ssl_cert'])   OR $ssl['cert']   = $this->encrypt['ssl_cert'];
+			empty($this->encrypt['ssl_ca'])     OR $ssl['ca']     = $this->encrypt['ssl_ca'];
+			empty($this->encrypt['ssl_capath']) OR $ssl['capath'] = $this->encrypt['ssl_capath'];
+			empty($this->encrypt['ssl_cipher']) OR $ssl['cipher'] = $this->encrypt['ssl_cipher'];
+
+			if ( ! empty($ssl))
+			{
+				if ( ! empty($this->encrypt['ssl_verify']) && defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT'))
+				{
+					$mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, TRUE);
+				}
+
+				$client_flags |= MYSQLI_CLIENT_SSL;
+				$mysqli->ssl_set(
+					isset($ssl['key'])    ? $ssl['key']    : NULL,
+					isset($ssl['cert'])   ? $ssl['cert']   : NULL,
+					isset($ssl['ca'])     ? $ssl['ca']     : NULL,
+					isset($ssl['capath']) ? $ssl['capath'] : NULL,
+					isset($ssl['cipher']) ? $ssl['cipher'] : NULL
+				);
+			}
+		}
+
+		if ($mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags))
+		{
+			// Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
+			if (
+				($client_flags & MYSQLI_CLIENT_SSL)
+				&& version_compare($mysqli->client_info, '5.7.3', '<=')
+				&& empty($mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")->fetch_object()->Value)
+			)
+			{
+				$mysqli->close();
+				$message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!';
+				log_message('error', $message);
+				return ($this->db->db_debug) ? $this->db->display_error($message, '', TRUE) : FALSE;
+			}
+
+			return $mysqli;
+		}
+
+		return FALSE;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php
index b5cf265..3c57777 100644
--- a/system/database/drivers/oci8/oci8_driver.php
+++ b/system/database/drivers/oci8/oci8_driver.php
@@ -102,6 +102,14 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Reset $stmt_id flag
+	 *
+	 * Used by stored_procedure() to prevent _execute() from
+	 * re-setting the statement ID.
+	 */
+	protected $_reset_stmt_id = TRUE;
+
+	/**
 	 * List of reserved identifiers
 	 *
 	 * Identifiers that must NOT be escaped.
@@ -265,26 +273,13 @@
 		/* Oracle must parse the query before it is run. All of the actions with
 		 * the query are based on the statement id returned by oci_parse().
 		 */
-		$this->stmt_id = FALSE;
-		$this->_set_stmt_id($sql);
-		oci_set_prefetch($this->stmt_id, 1000);
-		return oci_execute($this->stmt_id, $this->commit_mode);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Generate a statement ID
-	 *
-	 * @param	string	$sql	an SQL query
-	 * @return	void
-	 */
-	protected function _set_stmt_id($sql)
-	{
-		if ( ! is_resource($this->stmt_id))
+		if ($this->_reset_stmt_id === TRUE)
 		{
 			$this->stmt_id = oci_parse($this->conn_id, $sql);
 		}
+
+		oci_set_prefetch($this->stmt_id, 1000);
+		return oci_execute($this->stmt_id, $this->commit_mode);
 	}
 
 	// --------------------------------------------------------------------
@@ -318,15 +313,15 @@
 	 * type		yes	the type of the parameter
 	 * length	yes	the max size of the parameter
 	 */
-	public function stored_procedure($package, $procedure, $params)
+	public function stored_procedure($package, $procedure, array $params)
 	{
-		if ($package === '' OR $procedure === '' OR ! is_array($params))
+		if ($package === '' OR $procedure === '')
 		{
 			log_message('error', 'Invalid query: '.$package.'.'.$procedure);
 			return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE;
 		}
 
-		// build the query string
+		// Build the query string
 		$sql = 'BEGIN '.$package.'.'.$procedure.'(';
 
 		$have_cursor = FALSE;
@@ -341,10 +336,12 @@
 		}
 		$sql = trim($sql, ',').'); END;';
 
-		$this->stmt_id = FALSE;
-		$this->_set_stmt_id($sql);
+		$this->_reset_stmt_id = FALSE;
+		$this->stmt_id = oci_parse($this->conn_id, $sql);
 		$this->_bind_params($params);
-		return $this->query($sql, FALSE, $have_cursor);
+		$result = $this->query($sql, FALSE, $have_cursor);
+		$this->_reset_stmt_id = TRUE;
+		return $result;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
index 206d835..e9d25ce 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
@@ -119,7 +119,6 @@
 	 *
 	 * @param	bool	$persistent
 	 * @return	object
-	 * @todo	SSL support
 	 */
 	public function db_connect($persistent = FALSE)
 	{
@@ -151,7 +150,35 @@
 			$this->options[PDO::MYSQL_ATTR_COMPRESS] = TRUE;
 		}
 
-		return parent::db_connect($persistent);
+		// SSL support was added to PDO_MYSQL in PHP 5.3.7
+		if (is_array($this->encrypt) && is_php('5.3.7'))
+		{
+			$ssl = array();
+			empty($this->encrypt['ssl_key'])    OR $ssl[PDO::MYSQL_ATTR_SSL_KEY]    = $this->encrypt['ssl_key'];
+			empty($this->encrypt['ssl_cert'])   OR $ssl[PDO::MYSQL_ATTR_SSL_CERT]   = $this->encrypt['ssl_cert'];
+			empty($this->encrypt['ssl_ca'])     OR $ssl[PDO::MYSQL_ATTR_SSL_CA]     = $this->encrypt['ssl_ca'];
+			empty($this->encrypt['ssl_capath']) OR $ssl[PDO::MYSQL_ATTR_SSL_CAPATH] = $this->encrypt['ssl_capath'];
+			empty($this->encrypt['ssl_cipher']) OR $ssl[PDO::MYSQL_ATTR_SSL_CIPHER] = $this->encrypt['ssl_cipher'];
+
+			// DO NOT use array_merge() here!
+			// It re-indexes numeric keys and the PDO_MYSQL_ATTR_SSL_* constants are integers.
+			empty($ssl) OR $this->options += $ssl;
+		}
+
+		// Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
+		if (
+			($pdo = parent::db_connect($persistent)) !== FALSE
+			&& ! empty($ssl)
+			&& version_compare($pdo->getAttribute(PDO::ATTR_CLIENT_VERSION), '5.7.3', '<=')
+			&& empty($pdo->query("SHOW STATUS LIKE 'ssl_cipher'")->fetchObject()->Value)
+		)
+		{
+			$message = 'PDO_MYSQL was configured for an SSL connection, but got an unencrypted connection instead!';
+			log_message('error', $message);
+			return ($this->db->db_debug) ? $this->db->display_error($message, '', TRUE) : FALSE;
+		}
+
+		return $pdo;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php
index d5ca741..409e650 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php
@@ -140,7 +140,7 @@
 		}
 
 		$this->data_cache['field_names'][$table] = array();
-		foreach ($result as $row)
+		foreach ($result->result_array() as $row)
 		{
 			$this->data_cache['field_names'][$table][] = $row['name'];
 		}
diff --git a/system/database/drivers/sqlite3/sqlite3_driver.php b/system/database/drivers/sqlite3/sqlite3_driver.php
index a7c6420..31e37de 100644
--- a/system/database/drivers/sqlite3/sqlite3_driver.php
+++ b/system/database/drivers/sqlite3/sqlite3_driver.php
@@ -266,7 +266,7 @@
 		}
 
 		$this->data_cache['field_names'][$table] = array();
-		foreach ($result as $row)
+		foreach ($result->result_array() as $row)
 		{
 			$this->data_cache['field_names'][$table][] = $row['name'];
 		}
diff --git a/system/helpers/download_helper.php b/system/helpers/download_helper.php
index 95c94a1..73f6456 100644
--- a/system/helpers/download_helper.php
+++ b/system/helpers/download_helper.php
@@ -69,16 +69,14 @@
 		}
 		elseif ($data === NULL)
 		{
-			if (@is_file($filename) && ($filesize = @filesize($filename)) !== FALSE)
-			{
-				$filepath = $filename;
-				$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
-				$filename = end($filename);
-			}
-			else
+			if ( ! @is_file($filename) OR ($filesize = @filesize($filename)) === FALSE)
 			{
 				return;
 			}
+
+			$filepath = $filename;
+			$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
+			$filename = end($filename);
 		}
 		else
 		{
@@ -140,14 +138,7 @@
 		header('Expires: 0');
 		header('Content-Transfer-Encoding: binary');
 		header('Content-Length: '.$filesize);
-
-		// Internet Explorer-specific headers
-		if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE)
-		{
-			header('Cache-Control: no-cache, no-store, must-revalidate');
-		}
-
-		header('Pragma: no-cache');
+		header('Cache-Control: private, no-transform, no-store, must-revalidate');
 
 		// If we have raw data - just dump it
 		if ($data !== NULL)
diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php
index 53ee8eb..fd80776 100644
--- a/system/helpers/form_helper.php
+++ b/system/helpers/form_helper.php
@@ -197,7 +197,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_input($data = '', $value = '', $extra = '')
@@ -208,7 +208,7 @@
 			'value' => $value
 		);
 
-		return '<input '._parse_form_attributes($data, $defaults).$extra." />\n";
+		return '<input '._parse_form_attributes($data, $defaults)._attributes_to_string($extra)." />\n";
 	}
 }
 
@@ -223,7 +223,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_password($data = '', $value = '', $extra = '')
@@ -245,7 +245,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_upload($data = '', $value = '', $extra = '')
@@ -253,7 +253,8 @@
 		$defaults = array('type' => 'file', 'name' => '');
 		is_array($data) OR $data = array('name' => $data);
 		$data['type'] = 'file';
-		return '<input '._parse_form_attributes($data, $defaults).$extra." />\n";
+
+		return '<input '._parse_form_attributes($data, $defaults)._attributes_to_string($extra)." />\n";
 	}
 }
 
@@ -266,7 +267,7 @@
 	 *
 	 * @param	mixed	$data
 	 * @param	string	$value
-	 * @param	string	$extra
+	 * @param	mixed	$extra
 	 * @return	string
 	 */
 	function form_textarea($data = '', $value = '', $extra = '')
@@ -287,7 +288,9 @@
 			unset($data['value']); // textareas don't use the value attribute
 		}
 
-		return '<textarea '._parse_form_attributes($data, $defaults).$extra.'>'.html_escape($val)."</textarea>\n";
+		return '<textarea '._parse_form_attributes($data, $defaults)._attributes_to_string($extra).'>'
+			.html_escape($val)
+			."</textarea>\n";
 	}
 }
 
@@ -301,12 +304,13 @@
 	 * @param	string
 	 * @param	array
 	 * @param	mixed
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_multiselect($name = '', $options = array(), $selected = array(), $extra = '')
 	{
-		if ( ! strpos($extra, 'multiple'))
+		$extra = _attributes_to_string($extra);
+		if (stripos($extra, 'multiple') === FALSE)
 		{
 			$extra .= ' multiple="multiple"';
 		}
@@ -372,7 +376,7 @@
 
 		$extra = _attributes_to_string($extra);
 
-		$multiple = (count($selected) > 1 && strpos($extra, 'multiple') === FALSE) ? ' multiple="multiple"' : '';
+		$multiple = (count($selected) > 1 && stripos($extra, 'multiple') === FALSE) ? ' multiple="multiple"' : '';
 
 		$form = '<select '.rtrim(_parse_form_attributes($data, $defaults)).$extra.$multiple.">\n";
 
@@ -420,7 +424,7 @@
 	 * @param	mixed
 	 * @param	string
 	 * @param	bool
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_checkbox($data = '', $value = '', $checked = FALSE, $extra = '')
@@ -450,7 +454,7 @@
 			unset($defaults['checked']);
 		}
 
-		return '<input '._parse_form_attributes($data, $defaults).$extra." />\n";
+		return '<input '._parse_form_attributes($data, $defaults)._attributes_to_string($extra)." />\n";
 	}
 }
 
@@ -464,13 +468,14 @@
 	 * @param	mixed
 	 * @param	string
 	 * @param	bool
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_radio($data = '', $value = '', $checked = FALSE, $extra = '')
 	{
 		is_array($data) OR $data = array('name' => $data);
 		$data['type'] = 'radio';
+
 		return form_checkbox($data, $value, $checked, $extra);
 	}
 }
@@ -484,7 +489,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_submit($data = '', $value = '', $extra = '')
@@ -495,7 +500,7 @@
 			'value' => $value
 		);
 
-		return '<input '._parse_form_attributes($data, $defaults).$extra." />\n";
+		return '<input '._parse_form_attributes($data, $defaults)._attributes_to_string($extra)." />\n";
 	}
 }
 
@@ -508,7 +513,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_reset($data = '', $value = '', $extra = '')
@@ -519,7 +524,7 @@
 			'value' => $value
 		);
 
-		return '<input '._parse_form_attributes($data, $defaults).$extra." />\n";
+		return '<input '._parse_form_attributes($data, $defaults)._attributes_to_string($extra)." />\n";
 	}
 }
 
@@ -532,7 +537,7 @@
 	 *
 	 * @param	mixed
 	 * @param	string
-	 * @param	string
+	 * @param	mixed
 	 * @return	string
 	 */
 	function form_button($data = '', $content = '', $extra = '')
@@ -548,7 +553,9 @@
 			unset($data['content']); // content is not an attribute
 		}
 
-		return '<button '._parse_form_attributes($data, $defaults).$extra.'>'.$content."</button>\n";
+		return '<button '._parse_form_attributes($data, $defaults)._attributes_to_string($extra).'>'
+			.$content
+			."</button>\n";
 	}
 }
 
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index 6a033d6..d65f92f 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -492,7 +492,7 @@
 
 		$trans = array(
 			'&.+?;'			=> '',
-			'[^a-z0-9 _-]'		=> '',
+			'[^\w\d _-]'		=> '',
 			'\s+'			=> $separator,
 			'('.$q_separator.')+'	=> $separator
 		);
@@ -500,7 +500,7 @@
 		$str = strip_tags($str);
 		foreach ($trans as $key => $val)
 		{
-			$str = preg_replace('#'.$key.'#i', $val, $str);
+			$str = preg_replace('#'.$key.'#i'.(UTF8_ENABLED ? 'u' : ''), $val, $str);
 		}
 
 		if ($lowercase === TRUE)
diff --git a/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php
index 06403b6..0c87a56 100644
--- a/system/libraries/Cache/Cache.php
+++ b/system/libraries/Cache/Cache.php
@@ -178,7 +178,7 @@
 	 */
 	public function increment($id, $offset = 1)
 	{
-		return $this->{$this->_adapter}->increment($id, $offset);
+		return $this->{$this->_adapter}->increment($this->key_prefix.$id, $offset);
 	}
 
 	// ------------------------------------------------------------------------
@@ -192,7 +192,7 @@
 	 */
 	public function decrement($id, $offset = 1)
 	{
-		return $this->{$this->_adapter}->decrement($id, $offset);
+		return $this->{$this->_adapter}->decrement($this->key_prefix.$id, $offset);
 	}
 
 	// ------------------------------------------------------------------------
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
index d63f61d..76437f4 100644
--- a/system/libraries/Pagination.php
+++ b/system/libraries/Pagination.php
@@ -644,7 +644,7 @@
 
 		// Kill double slashes. Note: Sometimes we can end up with a double slash
 		// in the penultimate link so we'll kill all double slashes.
-		$output = preg_replace('#([^:])//+#', '\\1/', $output);
+		$output = preg_replace('#([^:"])//+#', '\\1/', $output);
 
 		// Add the wrapper HTML if exists
 		return $this->full_tag_open.$output.$this->full_tag_close;
diff --git a/system/libraries/Unit_test.php b/system/libraries/Unit_test.php
index 7b744ad..3f986f3 100644
--- a/system/libraries/Unit_test.php
+++ b/system/libraries/Unit_test.php
@@ -55,14 +55,14 @@
 	 *
 	 * @var	bool
 	 */
-	public $active			= TRUE;
+	public $active = TRUE;
 
 	/**
 	 * Test results
 	 *
 	 * @var	array
 	 */
-	public $results			= array();
+	public $results = array();
 
 	/**
 	 * Strict comparison flag
@@ -71,21 +71,21 @@
 	 *
 	 * @var	bool
 	 */
-	public $strict			= FALSE;
+	public $strict = FALSE;
 
 	/**
 	 * Template
 	 *
 	 * @var	string
 	 */
-	protected $_template		= NULL;
+	protected $_template = NULL;
 
 	/**
 	 * Template rows
 	 *
 	 * @var	string
 	 */
-	protected $_template_rows	= NULL;
+	protected $_template_rows = NULL;
 
 	/**
 	 * List of visible test items
@@ -93,13 +93,13 @@
 	 * @var	array
 	 */
 	protected $_test_items_visible	= array(
-			'test_name',
-			'test_datatype',
-			'res_datatype',
-			'result',
-			'file',
-			'line',
-			'notes'
+		'test_name',
+		'test_datatype',
+		'res_datatype',
+		'result',
+		'file',
+		'line',
+		'notes'
 	);
 
 	// --------------------------------------------------------------------
@@ -152,7 +152,7 @@
 			return FALSE;
 		}
 
-		if (in_array($expected, array('is_object', 'is_string', 'is_bool', 'is_true', 'is_false', 'is_int', 'is_numeric', 'is_float', 'is_double', 'is_array', 'is_null'), TRUE))
+		if (in_array($expected, array('is_object', 'is_string', 'is_bool', 'is_true', 'is_false', 'is_int', 'is_numeric', 'is_float', 'is_double', 'is_array', 'is_null', 'is_resource'), TRUE))
 		{
 			$expected = str_replace('is_double', 'is_float', $expected);
 			$result = $expected($test);
@@ -167,14 +167,14 @@
 		$back = $this->_backtrace();
 
 		$report = array (
-							'test_name'			=> $test_name,
-							'test_datatype'		=> gettype($test),
-							'res_datatype'		=> $extype,
-							'result'			=> ($result === TRUE) ? 'passed' : 'failed',
-							'file'				=> $back['file'],
-							'line'				=> $back['line'],
-							'notes'				=> $notes
-						);
+			'test_name'     => $test_name,
+			'test_datatype' => gettype($test),
+			'res_datatype'  => $extype,
+			'result'        => ($result === TRUE) ? 'passed' : 'failed',
+			'file'          => $back['file'],
+			'line'          => $back['line'],
+			'notes'         => $notes
+		);
 
 		$this->results[] = $report;
 
@@ -291,10 +291,12 @@
 				{
 					continue;
 				}
-
-				if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val), FALSE)))
+				elseif (in_array($key, array('test_name', 'test_datatype', 'test_res_datatype', 'result'), TRUE))
 				{
-					$val = $line;
+					if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val), FALSE)))
+					{
+						$val = $line;
+					}
 				}
 
 				$temp[$CI->lang->line('ut_'.$key, FALSE)] = $val;
@@ -334,9 +336,9 @@
 	{
 		$back = debug_backtrace();
 		return array(
-				'file' => (isset($back[1]['file']) ? $back[1]['file'] : ''),
-				'line' => (isset($back[1]['line']) ? $back[1]['line'] : '')
-			);
+			'file' => (isset($back[1]['file']) ? $back[1]['file'] : ''),
+			'line' => (isset($back[1]['line']) ? $back[1]['line'] : '')
+		);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php
index a1bd149..51232f8 100644
--- a/system/libraries/Upload.php
+++ b/system/libraries/Upload.php
@@ -533,15 +533,9 @@
 		 * If it returns false there was a problem.
 		 */
 		$this->orig_name = $this->file_name;
-
-		if ($this->overwrite === FALSE)
+		if (FALSE === ($this->file_name = $this->set_filename($this->upload_path, $this->file_name)))
 		{
-			$this->file_name = $this->set_filename($this->upload_path, $this->file_name);
-
-			if ($this->file_name === FALSE)
-			{
-				return FALSE;
-			}
+			return FALSE;
 		}
 
 		/*
@@ -656,7 +650,7 @@
 			$filename = md5(uniqid(mt_rand())).$this->file_ext;
 		}
 
-		if ( ! file_exists($path.$filename))
+		if ($this->overwrite === TRUE OR ! file_exists($path.$filename))
 		{
 			return $filename;
 		}
diff --git a/user_guide_src/source/_themes/sphinx_rtd_theme/static/css/citheme.css b/user_guide_src/source/_themes/sphinx_rtd_theme/static/css/citheme.css
new file mode 100644
index 0000000..10e7d04
--- /dev/null
+++ b/user_guide_src/source/_themes/sphinx_rtd_theme/static/css/citheme.css
@@ -0,0 +1,7 @@
+@import 'theme.css';
+
+.highlighted {
+	padding: 0px !important;
+	font-weight: inherit !important;
+	background-color: #f1d40f !important;	
+}
\ No newline at end of file
diff --git a/user_guide_src/source/_themes/sphinx_rtd_theme/theme.conf b/user_guide_src/source/_themes/sphinx_rtd_theme/theme.conf
index dcfbf8c..5814ac9 100644
--- a/user_guide_src/source/_themes/sphinx_rtd_theme/theme.conf
+++ b/user_guide_src/source/_themes/sphinx_rtd_theme/theme.conf
@@ -1,6 +1,6 @@
 [theme]
 inherit = basic
-stylesheet = css/theme.css
+stylesheet = css/citheme.css
 
 [options]
 typekit_id = hiw1hhg
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index edbcf2f..22243cf 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -14,6 +14,7 @@
 -  Database
 
    -  Added ``list_fields()`` support for SQLite ('sqlite3' and 'pdo_sqlite' drivers).
+   -  Added SSL connection support for the 'mysqli' and 'pdo_mysql' drivers.
 
 -  Libraries
 
@@ -23,6 +24,16 @@
       - Errors "no_file_selected", "file_partial", "stopped_by_extension", "no_file_types", "invalid_filetype", "bad_filename" are now logged at the 'debug' level.
       - Errors "file_exceeds_limit", "file_exceeds_form_limit", "invalid_filesize", "invalid_dimensions" are now logged at the 'info' level.
 
+   -  Added 'is_resource' to the available expectations in :doc:`Unit Testing Library <libraries/unit_testing>`.
+
+-  Helpers
+
+   -  Added Unicode support to :doc:`URL Helper <helpers/url_helper>` function :php:func:`url_title()`.
+   -  Added support for passing the "extra" parameter as an array to all :doc:`Form Helper <helpers/form_helper>` functions that use it.
+
+-  Core
+
+   -  Added support for defining a list of specific query parameters in ``$config['cache_query_string']`` for the :doc:`Output Library <libraries/output>`.
 
 Bug fixes for 3.0.1
 -------------------
@@ -46,6 +57,18 @@
 -  Fixed a bug (#3913) - :doc:`Cache Library <libraries/caching>` didn't work with the direct ``$this->cache->$driver_name->method()`` syntax with Redis and Memcache(d).
 -  Fixed a bug (#3932) - :doc:`Query Builder <database/query_builder>` didn't properly compile WHERE and HAVING conditions for field names that end with "and", "or".
 -  Fixed a bug in :doc:`Query Builder <database/query_builder>` where ``delete()`` didn't properly work on multiple tables with a WHERE condition previously set via ``where()``.
+-  Fixed a bug (#3952) - :doc:`Database <database/index>` method ``list_fields()`` didn't work with SQLite3.
+-  Fixed a bug (#3955) - :doc:`Cache Library <libraries/caching>` methods ``increment()`` and ``decrement()`` ignored the 'key_prefix' setting.
+-  Fixed a bug (#3963) - :doc:`Unit Testing Library <libraries/unit_testing>` wrongly tried to translate filenames, line numbers and notes values in test results.
+-  Fixed a bug (#3965) - :doc:`File Uploading Library <libraries/file_uploading>` ignored the "encrypt_name" setting when "overwrite" is enabled.
+-  Fixed a bug (#3968) - :doc:`Database Forge <database/forge>` method ``add_key()`` didn't treat array inputs as composite keys unless it's a PRIMARY KEY.
+-  Fixed a bug (#3715) - :doc:`Pagination Library <libraries/pagination>` could generate broken link when a protocol-relative base URL is used.
+-  Fixed a bug (#3828) - :doc:`Output Library <libraries/output>` method ``delete_cache()`` couldn't delete index page caches.
+-  Fixed a bug (#3704) - :doc:`Database <database/index>` method ``stored_procedure()`` in the 'oci8' driver didn't properly bind parameters.
+-  Fixed a bug (#3778) - :doc:`Download Helper <helpers/download_helper>` function :php:func:`force_download()` incorrectly sent a *Pragma* response header.
+-  Fixed a bug (#3752) - ``$routing['directory']`` overrides were not properly handled and always resulted in a 404 "Not Found" error.
+-  Fixed an internal bug in :doc:`Query Builder <database/query_builder>` escaping logic where if field name escaping is force-disabled, methods ``where()`` and ``having()`` will also treat values as fields.
+-  Fixed a bug (#3279) - :doc:`Query Builder <database/query_builder>` methods ``update()`` and ``get_compiled_update()`` did double escaping on the table name if it was provided via ``from()``.
 
 Version 3.0.0
 =============
@@ -819,6 +842,30 @@
 -  Fixed a bug (#3573) - :doc:`Email Library <libraries/email>` violated `RFC5321 <https://tools.ietf.org/rfc/rfc5321.txt>`_ by sending 'localhost.localdomain' as a hostname.
 -  Fixed a bug (#3572) - ``CI_Security::_remove_evil_attributes()`` failed for large-sized inputs due to *pcre.backtrack_limit* and didn't properly match HTML tags.
 
+Version 2.2.3
+=============
+
+Release Date: July 14, 2015
+
+-  Security
+
+   - Removed a fallback to ``mysql_escape_string()`` in the 'mysql' database driver (``escape_str()`` method) when there's no active database connection.
+
+Version 2.2.2
+=============
+
+Release Date: April 15, 2015
+
+-  General Changes
+
+   - Added HTTP "Host" header character validation to prevent cache poisoning attacks when *base_url* auto-detection is used.
+   - Added *FSCommand* and *seekSegmentTime* to the "evil attributes" list in ``CI_Security::xss_clean()``.
+
+Bug fixes for 2.2.2
+-------------------
+
+-  Fixed a bug (#3665) - ``CI_Security::entity_decode()`` triggered warnings under some circumstances.
+
 Version 2.2.1
 =============
 
diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst
index d21c79e..8026be6 100644
--- a/user_guide_src/source/database/configuration.rst
+++ b/user_guide_src/source/database/configuration.rst
@@ -152,9 +152,9 @@
 Explanation of Values:
 ----------------------
 
-======================  ==================================================================================================
+======================  ===========================================================================================================
  Name Config             Description
-======================  ==================================================================================================
+======================  ===========================================================================================================
 **dsn**			The DSN connect string (an all-in-one configuration sequence).
 **hostname** 		The hostname of your database server. Often this is 'localhost'.
 **username**		The username used to connect to the database.
@@ -179,6 +179,17 @@
 			customizable by the end user.
 **schema**		The database schema, defaults to 'public'. Used by PostgreSQL and ODBC drivers.
 **encrypt**		Whether or not to use an encrypted connection.
+
+			  - 'mysql' (deprecated), 'sqlsrv' and 'pdo/sqlsrv' drivers accept TRUE/FALSE
+			  - 'mysqli' and 'pdo/mysql' drivers accept an array with the following options:
+			  
+			    - 'ssl_key'    - Path to the private key file
+			    - 'ssl_cert'   - Path to the public key certificate file
+			    - 'ssl_ca'     - Path to the certificate authority file
+			    - 'ssl_capath' - Path to a directory containing trusted CA certificats in PEM format
+			    - 'ssl_cipher' - List of *allowed* ciphers to be used for the encryption, separated by colons (':')
+			    - 'ssl_verify' - TRUE/FALSE; Whether to verify the server certificate or not ('mysqli' only)
+
 **compress**		Whether or not to use client compression (MySQL only).
 **stricton**		TRUE/FALSE (boolean) - Whether to force "Strict Mode" connections, good for ensuring strict SQL
 			while developing an application.
@@ -186,10 +197,11 @@
 			::
 
 				$db['default']['port'] = 5432;
-======================  ==================================================================================================
+
+======================  ===========================================================================================================
 
 .. note:: Depending on what database platform you are using (MySQL, PostgreSQL,
 	etc.) not all values will be needed. For example, when using SQLite you
 	will not need to supply a username or password, and the database name
 	will be the path to your database file. The information above assumes
-	you are using MySQL.
\ No newline at end of file
+	you are using MySQL.
diff --git a/user_guide_src/source/database/forge.rst b/user_guide_src/source/database/forge.rst
index a875f74..646e3a5 100644
--- a/user_guide_src/source/database/forge.rst
+++ b/user_guide_src/source/database/forge.rst
@@ -143,6 +143,8 @@
 	$this->dbforge->add_field("label varchar(100) NOT NULL DEFAULT 'default label'");
 
 
+.. note:: Passing raw strings as fields cannot be followed by ``add_key()`` calls on those fields.
+
 .. note:: Multiple calls to add_field() are cumulative.
 
 Creating an id field
diff --git a/user_guide_src/source/general/environments.rst b/user_guide_src/source/general/environments.rst
index 7f030b6..ac6f323 100644
--- a/user_guide_src/source/general/environments.rst
+++ b/user_guide_src/source/general/environments.rst
@@ -49,4 +49,4 @@
 configuration files. This may be useful for managing things like
 differing API keys across multiple environments. This is described in
 more detail in the environment section of the :doc:`Config Class
-<../libraries/config>`_ documentation.
\ No newline at end of file
+<../libraries/config>` documentation.
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/form_helper.rst b/user_guide_src/source/helpers/form_helper.rst
index 9ddca89..6317f08 100644
--- a/user_guide_src/source/helpers/form_helper.rst
+++ b/user_guide_src/source/helpers/form_helper.rst
@@ -191,7 +191,7 @@
 
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML text input field tag
 	:rtype:	string
 
@@ -226,11 +226,16 @@
 		$js = 'onClick="some_function()"';
 		echo form_input('username', 'johndoe', $js);
 
+	Or you can pass it as an array::
+
+		$js = array('onClick' => 'some_function();');
+		echo form_input('username', 'johndoe', $js);
+
 .. php:function:: form_password([$data = ''[, $value = ''[, $extra = '']]])
 
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML password input field tag
 	:rtype:	string
 
@@ -242,7 +247,7 @@
 
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML file upload input field tag
 	:rtype:	string
 
@@ -255,7 +260,7 @@
 
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML textarea tag
 	:rtype:	string
 
@@ -270,7 +275,7 @@
 	:param	string	$name: Field name
 	:param	array	$options: An associative array of options to be listed
 	:param	array	$selected: List of fields to mark with the *selected* attribute
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML dropdown select field tag
 	:rtype:	string
 
@@ -324,6 +329,14 @@
 		$js = 'id="shirts" onChange="some_function();"';
 		echo form_dropdown('shirts', $options, 'large', $js);
 
+	Or you can pass it as an array::
+
+		$js = array(
+			'id'       => 'shirts',
+			'onChange' => 'some_function();'
+		);
+		echo form_dropdown('shirts', $options, 'large', $js);
+
 	If the array passed as ``$options`` is a multidimensional array, then
 	``form_dropdown()`` will produce an <optgroup> with the array key as the
 	label.
@@ -334,7 +347,7 @@
 	:param	string	$name: Field name
 	:param	array	$options: An associative array of options to be listed
 	:param	array	$selected: List of fields to mark with the *selected* attribute
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML dropdown multiselect field tag
 	:rtype:	string
 
@@ -417,7 +430,7 @@
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
 	:param	bool	$checked: Whether to mark the checkbox as being *checked*
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML checkbox input tag
 	:rtype:	string
 
@@ -450,13 +463,18 @@
 		$js = 'onClick="some_function()"';
 		echo form_checkbox('newsletter', 'accept', TRUE, $js)
 
+	Or you can pass it as an array::
+
+		$js = array('onClick' => 'some_function();');
+		echo form_checkbox('newsletter', 'accept', TRUE, $js)
+
 
 .. php:function:: form_radio([$data = ''[, $value = ''[, $checked = FALSE[, $extra = '']]]])
 
 	:param	array	$data: Field attributes data
 	:param	string	$value: Field value
 	:param	bool	$checked: Whether to mark the radio button as being *checked*
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML radio input tag
 	:rtype:	string
 
@@ -495,7 +513,7 @@
 
 	:param	string	$data: Button name
 	:param	string	$value: Button value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML input submit tag
 	:rtype:	string
 
@@ -513,7 +531,7 @@
 
 	:param	string	$data: Button name
 	:param	string	$value: Button value
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML input reset button tag
 	:rtype:	string
 
@@ -525,7 +543,7 @@
 
 	:param	string	$data: Button name
 	:param	string	$content: Button label
-	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:param	mixed	$extra: Extra attributes to be added to the tag either as an array or a literal string
 	:returns:	An HTML button tag
 	:rtype:	string
 
diff --git a/user_guide_src/source/installation/downloads.rst b/user_guide_src/source/installation/downloads.rst
index e2b6a9c..16c8e53 100644
--- a/user_guide_src/source/installation/downloads.rst
+++ b/user_guide_src/source/installation/downloads.rst
@@ -2,7 +2,10 @@
 Downloading CodeIgniter
 #######################
 
--  `CodeIgniter v3.0.0 (Current version) <https://codeload.github.com/bcit-ci/CodeIgniter/zip/develop>`_
+-  `CodeIgniter v3.0.1-dev (Current version) <https://codeload.github.com/bcit-ci/CodeIgniter/zip/develop>`_
+-  `CodeIgniter v3.0.0 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/3.0.0>`_
+-  `CodeIgniter v2.2.3 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.2.3>`_
+-  `CodeIgniter v2.2.2 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.2.2>`_
 -  `CodeIgniter v2.2.1 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.2.1>`_
 -  `CodeIgniter v2.2.0 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.2.0>`_
 -  `CodeIgniter v2.1.4 <https://codeload.github.com/bcit-ci/CodeIgniter/zip/2.1.4>`_
diff --git a/user_guide_src/source/installation/upgrade_220.rst b/user_guide_src/source/installation/upgrade_220.rst
index b2e9432..91f9e00 100644
--- a/user_guide_src/source/installation/upgrade_220.rst
+++ b/user_guide_src/source/installation/upgrade_220.rst
@@ -1,5 +1,5 @@
 #############################
-Upgrading from 2.1.4 to 2.2.0
+Upgrading from 2.1.4 to 2.2.x
 #############################
 
 .. note:: The :doc:`Encrypt Class </libraries/encrypt>` now requires the
diff --git a/user_guide_src/source/installation/upgrade_222.rst b/user_guide_src/source/installation/upgrade_222.rst
new file mode 100644
index 0000000..9dcc61d
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_222.rst
@@ -0,0 +1,14 @@
+#############################
+Upgrading from 2.2.1 to 2.2.2
+#############################
+
+Before performing an update you should take your site offline by
+replacing the index.php file with a static one.
+
+Step 1: Update your CodeIgniter files
+=====================================
+
+Replace all files and directories in your "system" folder.
+
+.. note:: If you have any custom developed files in these folders please
+	make copies of them first.
\ No newline at end of file
diff --git a/user_guide_src/source/installation/upgrade_223.rst b/user_guide_src/source/installation/upgrade_223.rst
new file mode 100644
index 0000000..252318a
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_223.rst
@@ -0,0 +1,14 @@
+#############################
+Upgrading from 2.2.2 to 2.2.3
+#############################
+
+Before performing an update you should take your site offline by
+replacing the index.php file with a static one.
+
+Step 1: Update your CodeIgniter files
+=====================================
+
+Replace all files and directories in your "system" folder.
+
+.. note:: If you have any custom developed files in these folders please
+	make copies of them first.
\ No newline at end of file
diff --git a/user_guide_src/source/installation/upgrade_301.rst b/user_guide_src/source/installation/upgrade_301.rst
new file mode 100644
index 0000000..f38d340
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_301.rst
@@ -0,0 +1,19 @@
+#############################
+Upgrading from 3.0.0 to 3.0.1
+#############################
+
+Before performing an update you should take your site offline by
+replacing the index.php file with a static one.
+
+Step 1: Update your CodeIgniter files
+=====================================
+
+Replace all files and directories in your *system/* directory.
+
+.. note:: If you have any custom developed files in these directories,
+	please make copies of them first.
+
+Step 2: Update your CLI error templates
+=======================================
+
+Replace all files under your *application/errors/cli/* directory.
\ No newline at end of file
diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst
index 89e90e7..e0f0dd5 100644
--- a/user_guide_src/source/installation/upgrading.rst
+++ b/user_guide_src/source/installation/upgrading.rst
@@ -8,9 +8,12 @@
 .. toctree::
 	:titlesonly:
 
-	Upgrading from 2.2.x to 3.0.0 <upgrade_300>
+	Upgrading from 3.0.0 to 3.0.1 <upgrade_301>
+	Upgrading from 2.2.x to 3.0.x <upgrade_300>
+	Upgrading from 2.2.2 to 2.2.3 <upgrade_223>
+	Upgrading from 2.2.1 to 2.2.2 <upgrade_222>
 	Upgrading from 2.2.0 to 2.2.1 <upgrade_221>
-	Upgrading from 2.1.4 to 2.2.0 <upgrade_220>
+	Upgrading from 2.1.4 to 2.2.x <upgrade_220>
 	Upgrading from 2.1.3 to 2.1.4 <upgrade_214>
 	Upgrading from 2.1.2 to 2.1.3 <upgrade_213>
 	Upgrading from 2.1.1 to 2.1.2 <upgrade_212>
diff --git a/user_guide_src/source/libraries/output.rst b/user_guide_src/source/libraries/output.rst
index 4b36d2a..84529f7 100644
--- a/user_guide_src/source/libraries/output.rst
+++ b/user_guide_src/source/libraries/output.rst
@@ -160,7 +160,7 @@
 
 		Permits you to manually set a server status header. Example::
 
-			$this->output->set_status_header('401');
+			$this->output->set_status_header(401);
 			// Sets the header as:  Unauthorized
 
 		`See here <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>`_ for a full list of headers.
@@ -230,4 +230,4 @@
 				->_display();
 			exit;
 
-		.. note:: Calling this method manually without aborting script execution will result in duplicated output.
\ No newline at end of file
+		.. note:: Calling this method manually without aborting script execution will result in duplicated output.
diff --git a/user_guide_src/source/libraries/unit_testing.rst b/user_guide_src/source/libraries/unit_testing.rst
index 026781c..57934cb 100644
--- a/user_guide_src/source/libraries/unit_testing.rst
+++ b/user_guide_src/source/libraries/unit_testing.rst
@@ -76,6 +76,7 @@
 -  is_double
 -  is_array
 -  is_null
+-  is_resource
 
 Generating Reports
 ==================