Merge pull request #2069 from willmendesneto/develop

Minify output in css and javascript code
diff --git a/.gitignore b/.gitignore
index 11fb6d6..a035c2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@
 user_guide_src/build/*
 user_guide_src/cilexer/build/*
 user_guide_src/cilexer/dist/*
-user_guide_src/cilexer/pycilexer.egg-info/*
\ No newline at end of file
+user_guide_src/cilexer/pycilexer.egg-info/*
+/vendor/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 31b74b1..070b23c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,9 +3,10 @@
 php:
   - 5.3
   - 5.4
-
+  
 env:
   - DB=mysql
+  - DB=mysqli
   - DB=pgsql
   - DB=sqlite
   - DB=pdo/mysql
@@ -17,10 +18,11 @@
   - php composer.phar install
   - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS ci_test;' -U postgres; fi"
   - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'create database ci_test;' -U postgres; fi"
-  - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi"
+  - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'mysqli' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi"
 
 script: phpunit --coverage-text --configuration tests/travis/$DB.phpunit.xml
 
 branches:
   only:
-    - develop
\ No newline at end of file
+    - develop
+    - /^feature\/.+$/
\ No newline at end of file
diff --git a/application/.htaccess b/application/.htaccess
index 14249c5..6c63ed4 100644
--- a/application/.htaccess
+++ b/application/.htaccess
@@ -1 +1,6 @@
-Deny from all
\ No newline at end of file
+<IfModule authz_core_module>
+    Require all denied
+</IfModule>
+<IfModule !authz_core_module>
+    Deny from all
+</IfModule>
\ No newline at end of file
diff --git a/application/cache/.htaccess b/application/cache/.htaccess
index 3418e55..6c63ed4 100644
--- a/application/cache/.htaccess
+++ b/application/cache/.htaccess
@@ -1 +1,6 @@
-deny from all
\ No newline at end of file
+<IfModule authz_core_module>
+    Require all denied
+</IfModule>
+<IfModule !authz_core_module>
+    Deny from all
+</IfModule>
\ No newline at end of file
diff --git a/application/config/autoload.php b/application/config/autoload.php
index b3e63cb..ff153fb 100644
--- a/application/config/autoload.php
+++ b/application/config/autoload.php
@@ -46,10 +46,11 @@
 |
 | 1. Packages
 | 2. Libraries
-| 3. Helper files
-| 4. Custom config files
-| 5. Language files
-| 6. Models
+| 3. Drivers
+| 4. Helper files
+| 5. Custom config files
+| 6. Language files
+| 7. Models
 |
 */
 
@@ -75,7 +76,7 @@
 |
 | Prototype:
 |
-|	$autoload['libraries'] = array('database', 'session', 'xmlrpc');
+|	$autoload['libraries'] = array('database', 'email', 'xmlrpc');
 */
 
 $autoload['libraries'] = array();
@@ -83,6 +84,22 @@
 
 /*
 | -------------------------------------------------------------------
+|  Auto-load Drivers
+| -------------------------------------------------------------------
+| These classes are located in the system/libraries folder or in your
+| application/libraries folder within their own subdirectory. They
+| offer multiple interchangeable driver options.
+|
+| Prototype:
+|
+|	$autoload['drivers'] = array('session', 'cache');
+*/
+
+$autoload['drivers'] = array();
+
+
+/*
+| -------------------------------------------------------------------
 |  Auto-load Helper Files
 | -------------------------------------------------------------------
 | Prototype:
@@ -139,4 +156,4 @@
 
 
 /* End of file autoload.php */
-/* Location: ./application/config/autoload.php */
\ No newline at end of file
+/* Location: ./application/config/autoload.php */
diff --git a/application/config/config.php b/application/config/config.php
index 28fc406..0562953 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -62,11 +62,11 @@
 | URI string.  The default setting of 'AUTO' works for most servers.
 | If your links do not seem to work, try one of the other delicious flavors:
 |
-| 'AUTO'			Default - auto detects
-| 'PATH_INFO'		Uses the PATH_INFO
-| 'QUERY_STRING'	Uses the QUERY_STRING
-| 'REQUEST_URI'		Uses the REQUEST_URI
-| 'ORIG_PATH_INFO'	Uses the ORIG_PATH_INFO
+| 'AUTO'		Default - auto detects
+| 'CLI' or 'argv'	Uses $_SERVER['argv'] (for php-cli only)
+| 'PATH_INFO'		Uses $_SERVER['PATH_INFO']
+| 'REQUEST_URI'		Uses $_SERVER['REQUEST_URI']
+| 'QUERY_STRING'	Uses $_SERVER['QUERY_STRING']
 |
 */
 $config['uri_protocol']	= 'AUTO';
@@ -265,6 +265,9 @@
 | Session Variables
 |--------------------------------------------------------------------------
 |
+| 'sess_driver'				= the driver to load: cookie (Classic), native (PHP sessions),
+|	or your custom driver name
+| 'sess_valid_drivers'		= additional valid drivers which may be loaded
 | 'sess_cookie_name'		= the name you want for the cookie
 | 'sess_expiration'			= the number of SECONDS you want the session to last.
 |   by default sessions last 7200 seconds (two hours).  Set to zero for no expiration.
@@ -278,6 +281,8 @@
 | 'sess_time_to_update'		= how many seconds between CI refreshing Session Information
 |
 */
+$config['sess_driver']			= 'cookie';
+$config['sess_valid_drivers']	= array();
 $config['sess_cookie_name']		= 'ci_session';
 $config['sess_expiration']		= 7200;
 $config['sess_expire_on_close']	= FALSE;
@@ -401,15 +406,19 @@
 | Reverse Proxy IPs
 |--------------------------------------------------------------------------
 |
-| If your server is behind a reverse proxy, you must whitelist the proxy IP
-| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR
-| header in order to properly identify the visitor's IP address.
-| Comma-delimited, e.g. '10.0.1.200,10.0.1.201'
+| If your server is behind a reverse proxy, you must whitelist the proxy
+| IP addresses from which CodeIgniter should trust headers such as
+| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
+| the visitor's IP address.
 |
+| You can use both an array or a comma-separated list of proxy addresses,
+| as well as specifying whole subnets. Here are a few examples:
+|
+| Comma-separated:	'10.0.1.200,192.168.5.0/24'
+| Array:		array('10.0.1.200', '192.168.5.0/24')
 */
 $config['proxy_ips'] = '';
 
 
-
 /* End of file config.php */
-/* Location: ./application/config/config.php */
+/* Location: ./application/config/config.php */
\ No newline at end of file
diff --git a/application/config/constants.php b/application/config/constants.php
index d22d296..62a18e7 100644
--- a/application/config/constants.php
+++ b/application/config/constants.php
@@ -52,14 +52,14 @@
 |
 */
 
-define('FOPEN_READ',							'rb');
-define('FOPEN_READ_WRITE',						'r+b');
-define('FOPEN_WRITE_CREATE_DESTRUCTIVE',		'wb'); // truncates existing file data, use with care
-define('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE',	'w+b'); // truncates existing file data, use with care
-define('FOPEN_WRITE_CREATE',					'ab');
-define('FOPEN_READ_WRITE_CREATE',				'a+b');
-define('FOPEN_WRITE_CREATE_STRICT',				'xb');
-define('FOPEN_READ_WRITE_CREATE_STRICT',		'x+b');
+define('FOPEN_READ', 'rb');
+define('FOPEN_READ_WRITE', 'r+b');
+define('FOPEN_WRITE_CREATE_DESTRUCTIVE', 'wb'); // truncates existing file data, use with care
+define('FOPEN_READ_WRITE_CREATE_DESTRUCTIVE', 'w+b'); // truncates existing file data, use with care
+define('FOPEN_WRITE_CREATE', 'ab');
+define('FOPEN_READ_WRITE_CREATE', 'a+b');
+define('FOPEN_WRITE_CREATE_STRICT', 'xb');
+define('FOPEN_READ_WRITE_CREATE_STRICT', 'x+b');
 
 /*
 |--------------------------------------------------------------------------
diff --git a/application/config/database.php b/application/config/database.php
index bb0d87b..3234026 100644
--- a/application/config/database.php
+++ b/application/config/database.php
@@ -43,7 +43,7 @@
 |	['password'] The password used to connect to the database
 |	['database'] The name of the database you want to connect to
 |	['dbdriver'] The database driver. e.g.: mysqli.
-			Currently supported:
+|			Currently supported:
 |				 cubrid, ibase, mssql, mysql, mysqli, oci8,
 |				 odbc, pdo, postgre, sqlite, sqlite3, sqlsrv
 |	['dbprefix'] You can add an optional prefix, which will be added
@@ -63,6 +63,8 @@
 | 				 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
 |	['autoinit'] Whether or not to automatically initialize the database.
+|	['encrypt']  Whether or not to use an encrypted connection.
+|	['compress'] Whether or not to use client compression (MySQL only)
 |	['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
 |							- good for ensuring strict SQL while developing
 |	['failover'] array - A array with 0 or more data for connections if the main should fail.
@@ -71,7 +73,7 @@
 | make active.  By default there is only one group (the 'default' group).
 |
 | The $query_builder variables lets you determine whether or not to load
-| the query builder class
+| the query builder class.
 */
 
 $active_group = 'default';
@@ -93,6 +95,8 @@
 	'dbcollat' => 'utf8_general_ci',
 	'swap_pre' => '',
 	'autoinit' => TRUE,
+	'encrypt' => FALSE,
+	'compress' => FALSE,
 	'stricton' => FALSE,
 	'failover' => array()
 );
diff --git a/application/config/foreign_chars.php b/application/config/foreign_chars.php
index 41de123..5a2bb0c 100644
--- a/application/config/foreign_chars.php
+++ b/application/config/foreign_chars.php
@@ -40,44 +40,56 @@
 	'/Ä/' => 'Ae',
 	'/Ü/' => 'Ue',
 	'/Ö/' => 'Oe',
-	'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά/' => 'A',
-	'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά/' => 'a',
+	'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A',
+	'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a',
+	'/Б/' => 'B',
+	'/б/' => 'b',
 	'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
 	'/ç|ć|ĉ|ċ|č/' => 'c',
+	'/Д/' => 'D',
+	'/д/' => 'd',
 	'/Ð|Ď|Đ|Δ/' => 'Dj',
 	'/ð|ď|đ|δ/' => 'dj',
-	'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ/' => 'E',
-	'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε/' => 'e',
-	'/Ĝ|Ğ|Ġ|Ģ|Γ/' => 'G',
-	'/ĝ|ğ|ġ|ģ|γ/' => 'g',
+	'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Ё|Э/' => 'E',
+	'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|ё|э/' => 'e',
+	'/Ф/' => 'F',
+	'/ф/' => 'f',
+	'/Ĝ|Ğ|Ġ|Ģ|Γ|Г/' => 'G',
+	'/ĝ|ğ|ġ|ģ|γ|г/' => 'g',
 	'/Ĥ|Ħ/' => 'H',
 	'/ĥ|ħ/' => 'h',
-	'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ/' => 'I',
-	'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ/' => 'i',
+	'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Й/' => 'I',
+	'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|й/' => 'i',
 	'/Ĵ/' => 'J',
 	'/ĵ/' => 'j',
-	'/Ķ|Κ/' => 'K',
-	'/ķ|κ/' => 'k',
-	'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ/' => 'L',
-	'/ĺ|ļ|ľ|ŀ|ł|λ/' => 'l',
-	'/Ñ|Ń|Ņ|Ň|Ν/' => 'N',
-	'/ñ|ń|ņ|ň|ʼn|ν/' => 'n',
-	'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ/' => 'O',
-	'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ/' => 'o',
-	'/Ŕ|Ŗ|Ř|Ρ/' => 'R',
-	'/ŕ|ŗ|ř|ρ/' => 'r',
-	'/Ś|Ŝ|Ş|Ș|Š|Σ/' => 'S',
-	'/ś|ŝ|ş|ș|š|ſ|σ|ς/' => 's',
-	'/Ț|Ţ|Ť|Ŧ|τ/' => 'T',
-	'/ț|ţ|ť|ŧ/' => 't',
-	'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U',
-	'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ/' => 'u',
-	'/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ/' => 'Y',
-	'/ý|ÿ|ŷ/' => 'y',
+	'/Ķ|Κ|К/' => 'K',
+	'/ķ|κ|к/' => 'k',
+	'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L',
+	'/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l',
+	'/М/' => 'M',
+	'/м/' => 'm',
+	'/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N',
+	'/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n',
+	'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O',
+	'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o',
+	'/П/' => 'P',
+	'/п/' => 'p',
+	'/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R',
+	'/ŕ|ŗ|ř|ρ|р/' => 'r',
+	'/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S',
+	'/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's',
+	'/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T',
+	'/ț|ţ|ť|ŧ|т/' => 't',
+	'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U',
+	'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u',
+	'/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ/' => 'Y',
+	'/ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ/' => 'y',
+	'/В/' => 'V',
+	'/в/' => 'v',
 	'/Ŵ/' => 'W',
 	'/ŵ/' => 'w',
-	'/Ź|Ż|Ž|Ζ/' => 'Z',
-	'/ź|ż|ž|ζ/' => 'z',
+	'/Ź|Ż|Ž|Ζ|З/' => 'Z',
+	'/ź|ż|ž|ζ|з/' => 'z',
 	'/Æ|Ǽ/' => 'AE',
 	'/ß/'=> 'ss',
 	'/IJ/' => 'IJ',
@@ -89,6 +101,22 @@
 	'/β/' => 'v',
 	'/μ/' => 'm',
 	'/ψ/' => 'ps',
+	'/Ж/'=>'Zh',
+	'/ж/'=>'zh',
+	'/Х/'=>'Kh',
+	'/х/'=>'kh',
+	'/Ц/'=>'Tc',
+	'/ц/'=>'tc',
+	'/Ч/'=>'Ch',
+	'/ч/'=>'ch',
+	'/Ш/'=>'Sh',
+	'/ш/'=>'sh',
+	'/Щ/'=>'Shch',
+	'/щ/'=>'shch',
+	'/Ю/'=>'Iu',
+	'/ю/'=>'iu',
+	'/Я/'=>'Ia',
+	'/я/'=>'ia'
 );
 
 /* End of file foreign_chars.php */
diff --git a/application/config/migration.php b/application/config/migration.php
index 7645ade..3ea3ef9 100644
--- a/application/config/migration.php
+++ b/application/config/migration.php
@@ -39,6 +39,24 @@
 
 /*
 |--------------------------------------------------------------------------
+| Migration Type
+|--------------------------------------------------------------------------
+|
+| Migration file names may be based on a sequential identifier or on
+| a timestamp. Options are:
+|
+|   'sequential' = Default 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.
+|
+*/
+$config['migration_type'] = 'timestamp';
+
+/*
+|--------------------------------------------------------------------------
 | Migrations table
 |--------------------------------------------------------------------------
 |
diff --git a/application/config/mimes.php b/application/config/mimes.php
index 4b1d6a8..ffbc2ee 100644
--- a/application/config/mimes.php
+++ b/application/config/mimes.php
@@ -37,7 +37,7 @@
 return array(
 	'hqx'	=>	array('application/mac-binhex40', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40'),
 	'cpt'	=>	'application/mac-compactpro',
-	'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'),
+	'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', 'text/plain'),
 	'bin'	=>	array('application/macbinary', 'application/mac-binary', 'application/octet-stream', 'application/x-binary', 'application/x-macbinary'),
 	'dms'	=>	'application/octet-stream',
 	'lha'	=>	'application/octet-stream',
@@ -49,14 +49,14 @@
 	'sea'	=>	'application/octet-stream',
 	'dll'	=>	'application/octet-stream',
 	'oda'	=>	'application/oda',
-	'pdf'	=>	array('application/pdf', 'application/x-download'),
+	'pdf'	=>	array('application/pdf', 'application/x-download', 'binary/octet-stream'),
 	'ai'	=>	'application/postscript',
 	'eps'	=>	'application/postscript',
 	'ps'	=>	'application/postscript',
 	'smi'	=>	'application/smil',
 	'smil'	=>	'application/smil',
 	'mif'	=>	'application/vnd.mif',
-	'xls'	=>	array('application/excel', 'application/vnd.ms-excel', 'application/msexcel'),
+	'xls'	=>	array('application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/excel', 'application/download', 'application/vnd.ms-office', 'application/msword'),
 	'ppt'	=>	array('application/powerpoint', 'application/vnd.ms-powerpoint'),
 	'pptx'	=> 	'application/vnd.openxmlformats-officedocument.presentationml.presentation',
 	'wbxml'	=>	'application/wbxml',
@@ -123,8 +123,10 @@
 	'avi'	=>	array('video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'),
 	'movie'	=>	'video/x-sgi-movie',
 	'doc'	=>	array('application/msword', 'application/vnd.ms-office'),
-	'docx'	=>	array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'),
-	'xlsx'	=>	array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'),
+	'docx'	=>	array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword'),
+	'dot'	=>	array('application/msword', 'application/vnd.ms-office'),
+	'dotx'	=>	array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword'),
+	'xlsx'	=>	array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/vnd.ms-excel', 'application/msword'),
 	'word'	=>	array('application/msword', 'application/octet-stream'),
 	'xl'	=>	'application/excel',
 	'eml'	=>	'message/rfc822',
@@ -164,7 +166,12 @@
 	'ogg'   =>	'audio/ogg',
 	'kmz'	=>	array('application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'),
 	'kml'	=>	array('application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'),
-	'ics'	=>	'text/calendar'
+	'ics'	=>	'text/calendar',
+	'zsh'	=>	'text/x-scriptzsh',
+	'7zip'	=>	array('application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'),
+	'cdr'	=>	array('application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'),
+	'wma'	=>	array('audio/x-ms-wma', 'video/x-ms-asf'),
+	'jar'	=>	array('application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed')
 );
 
 /* End of file mimes.php */
diff --git a/application/config/routes.php b/application/config/routes.php
index 0011986..d1a4419 100644
--- a/application/config/routes.php
+++ b/application/config/routes.php
@@ -59,8 +59,8 @@
 |
 |	$route['404_override'] = 'errors/page_missing';
 |
-| This route will tell the Router what URI segments to use if those provided
-| in the URL cannot be matched to a valid route.
+| This route will tell the Router which controller/method to use if those
+| provided in the URL cannot be matched to a valid route.
 |
 */
 
diff --git a/application/config/user_agents.php b/application/config/user_agents.php
index 416ef56..78e4c8c 100644
--- a/application/config/user_agents.php
+++ b/application/config/user_agents.php
@@ -36,6 +36,7 @@
 */
 
 $platforms = array(
+	'windows nt 6.2'	=> 'Windows 8',
 	'windows nt 6.1'	=> 'Windows 7',
 	'windows nt 6.0'	=> 'Windows Vista',
 	'windows nt 5.2'	=> 'Windows 2003',
@@ -146,6 +147,7 @@
 	'ipaq'			=> 'HP iPaq',
 	'mot-'			=> 'Motorola',
 	'playstation portable'	=> 'PlayStation Portable',
+	'playstation 3'		=> 'PlayStation 3',
 	'hiptop'		=> 'Danger Hiptop',
 	'nec-'			=> 'NEC',
 	'panasonic'		=> 'Panasonic',
@@ -155,10 +157,10 @@
 	'spv'			=> 'SPV',
 	'zte'			=> 'ZTE',
 	'sendo'			=> 'Sendo',
-	'dsi'			=> 'Nintendo DSi',
-	'ds'			=> 'Nintendo DS',
+	'nintendo dsi'	=> 'Nintendo DSi',
+	'nintendo ds'	=> 'Nintendo DS',
+	'nintendo 3ds'	=> 'Nintendo 3DS',
 	'wii'			=> 'Nintendo Wii',
-	'3ds'			=> 'Nintendo 3DS',
 	'open web'		=> 'Open Web',
 	'openweb'		=> 'OpenWeb',
 
diff --git a/application/controllers/welcome.php b/application/controllers/welcome.php
index 1ed82d2..f70dd78 100644
--- a/application/controllers/welcome.php
+++ b/application/controllers/welcome.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 class Welcome extends CI_Controller {
 
diff --git a/application/views/errors/error_404.php b/application/views/errors/error_404.php
index c19bedf..4a8823d 100644
--- a/application/views/errors/error_404.php
+++ b/application/views/errors/error_404.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 ?><!DOCTYPE html>
 <html lang="en">
 <head>
@@ -31,9 +32,9 @@
 <title>404 Page Not Found</title>
 <style type="text/css">
 
-::selection{ background-color: #E13300; color: white; }
-::moz-selection{ background-color: #E13300; color: white; }
-::webkit-selection{ background-color: #E13300; color: white; }
+::selection { background-color: #E13300; color: white; }
+::-moz-selection { background-color: #E13300; color: white; }
+::-webkit-selection { background-color: #E13300; color: white; }
 
 body {
 	background-color: #fff;
@@ -73,6 +74,8 @@
 	margin: 10px;
 	border: 1px solid #D0D0D0;
 	box-shadow: 0 0 8px #D0D0D0;
+	-moz-box-shadow: 0 0 8px #D0D0D0;
+	-webkit-box-shadow: 0 0 8px #D0D0D0;
 }
 
 p {
diff --git a/application/views/errors/error_db.php b/application/views/errors/error_db.php
index 3b244e0..21d5d86 100644
--- a/application/views/errors/error_db.php
+++ b/application/views/errors/error_db.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 ?><!DOCTYPE html>
 <html lang="en">
 <head>
@@ -31,9 +32,9 @@
 <title>Database Error</title>
 <style type="text/css">
 
-::selection{ background-color: #E13300; color: white; }
-::moz-selection{ background-color: #E13300; color: white; }
-::webkit-selection{ background-color: #E13300; color: white; }
+::selection { background-color: #E13300; color: white; }
+::-moz-selection { background-color: #E13300; color: white; }
+::-webkit-selection { background-color: #E13300; color: white; }
 
 body {
 	background-color: #fff;
@@ -73,6 +74,8 @@
 	margin: 10px;
 	border: 1px solid #D0D0D0;
 	box-shadow: 0 0 8px #D0D0D0;
+	-moz-box-shadow: 0 0 8px #D0D0D0;
+	-webkit-box-shadow: 0 0 8px #D0D0D0;
 }
 
 p {
diff --git a/application/views/errors/error_general.php b/application/views/errors/error_general.php
index c88afe1..5bf3611 100644
--- a/application/views/errors/error_general.php
+++ b/application/views/errors/error_general.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 ?><!DOCTYPE html>
 <html lang="en">
 <head>
@@ -31,9 +32,9 @@
 <title>Error</title>
 <style type="text/css">
 
-::selection{ background-color: #E13300; color: white; }
-::moz-selection{ background-color: #E13300; color: white; }
-::webkit-selection{ background-color: #E13300; color: white; }
+::selection { background-color: #E13300; color: white; }
+::-moz-selection { background-color: #E13300; color: white; }
+::-webkit-selection { background-color: #E13300; color: white; }
 
 body {
 	background-color: #fff;
@@ -73,6 +74,8 @@
 	margin: 10px;
 	border: 1px solid #D0D0D0;
 	box-shadow: 0 0 8px #D0D0D0;
+	-moz-box-shadow: 0 0 8px #D0D0D0;
+	-webkit-box-shadow: 0 0 8px #D0D0D0;
 }
 
 p {
diff --git a/application/views/errors/error_php.php b/application/views/errors/error_php.php
index b76dc8a..c0c4bd6 100644
--- a/application/views/errors/error_php.php
+++ b/application/views/errors/error_php.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 ?>
 
 <div style="border:1px solid #990000;padding-left:20px;margin:0 0 10px 0;">
diff --git a/application/views/welcome_message.php b/application/views/welcome_message.php
index 65f62a9..27332f4 100644
--- a/application/views/welcome_message.php
+++ b/application/views/welcome_message.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 ?><!DOCTYPE html>
 <html lang="en">
 <head>
@@ -32,9 +33,9 @@
 
 	<style type="text/css">
 
-	::selection{ background-color: #E13300; color: white; }
-	::moz-selection{ background-color: #E13300; color: white; }
-	::webkit-selection{ background-color: #E13300; color: white; }
+	::selection { background-color: #E13300; color: white; }
+	::-moz-selection { background-color: #E13300; color: white; }
+	::-webkit-selection { background-color: #E13300; color: white; }
 
 	body {
 		background-color: #fff;
@@ -70,11 +71,11 @@
 		padding: 12px 10px 12px 10px;
 	}
 
-	#body{
+	#body {
 		margin: 0 15px 0 15px;
 	}
 
-	p.footer{
+	p.footer {
 		text-align: right;
 		font-size: 11px;
 		border-top: 1px solid #D0D0D0;
@@ -83,9 +84,10 @@
 		margin: 20px 0 0 0;
 	}
 
-	#container{
+	#container {
 		margin: 10px;
 		border: 1px solid #D0D0D0;
+		-moz-box-shadow: 0 0 8px #D0D0D0;
 		-webkit-box-shadow: 0 0 8px #D0D0D0;
 	}
 	</style>
diff --git a/composer.json b/composer.json
index fa6dc02..7d60020 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,8 @@
 {
     "require": {
         "mikey179/vfsStream": "*"
-    }
+    },
+    "require-dev": {
+		"phpunit/phpunit": "*"
+	}
 }
\ No newline at end of file
diff --git a/contributing.md b/contributing.md
new file mode 100644
index 0000000..b6d486d
--- /dev/null
+++ b/contributing.md
@@ -0,0 +1,92 @@
+# Contributing to CodeIgniter
+
+
+CodeIgniter is a community driven project and accepts contributions of code and documentation from the community. These contributions are made in the form of Issues or [Pull Requests](http://help.github.com/send-pull-requests/) on the [EllisLab CodeIgniter repository](https://github.com/EllisLab/CodeIgniter>) on GitHub.
+
+Issues are a quick way to point out a bug. If you find a bug or documentation error in CodeIgniter then please check a few things first:
+
+1. There is not already an open Issue
+2. The issue has already been fixed (check the develop branch, or look for closed Issues)
+3. Is it something really obvious that you can fix yourself?
+
+Reporting issues is helpful but an even better approach is to send a Pull Request, which is done by "Forking" the main repository and committing to your own copy. This will require you to use the version control system called Git.
+
+## Guidelines
+
+Before we look into how, here are the guidelines. If your Pull Requests fail
+to pass these guidelines it will be declined and you will need to re-submit
+when you’ve made the changes. This might sound a bit tough, but it is required
+for us to maintain quality of the code-base.
+
+### PHP Style
+
+All code must meet the [Style Guide](http://codeigniter.com/user_guide/general/styleguide.html), which is
+essentially the [Allman indent style](http://en.wikipedia.org/wiki/Indent_style#Allman_style), underscores and readable operators. This makes certain that all code is the same format as the existing code and means it will be as readable as possible.
+
+### Documentation
+
+If you change anything that requires a change to documentation then you will need to add it. New classes, methods, parameters, changing default values, etc are all things that will require a change to documentation. The change-log must also be updated for every change. Also PHPDoc blocks must be maintained.
+
+### Compatibility
+
+CodeIgniter is compatible with PHP 5.2.4 so all code supplied must stick to
+this requirement. If PHP 5.3 or 5.4 functions or features are used then there
+must be a fallback for PHP 5.2.4.
+
+### Branching
+
+CodeIgniter uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model which requires all pull requests to be sent to the "develop" branch. This is
+where the next planned version will be developed. The "master" branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g: an emergency security patch) can be applied to master to create a new version, without worrying about other features holding it up. For this reason all commits need to be made to "develop" and any sent to "master" will be closed automatically. If you have multiple changes to submit, please place all changes into their own branch on your fork.
+
+One thing at a time: A pull request should only contain one change. That does not mean only one commit, but one change - however many commits it took. The reason for this is that if you change X and Y but send a pull request for both at the same time, we might really want X but disagree with Y, meaning we cannot merge the request. Using the Git-Flow branching model you can create new branches for both of these features and send two requests.
+
+### Signing
+
+You must sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open source project. git makes this trivial as you merely have to use `--signoff` on your commits to your CodeIgniter fork.
+
+`git commit --signoff`
+
+or simply
+
+`git commit -s`
+
+This will sign your commits with the information setup in your git config, e.g.
+
+`Signed-off-by: John Q Public <john.public@example.com>`
+
+If you are using [Tower](http://www.git-tower.com/) there is a "Sign-Off" checkbox in the commit window. You could even alias git commit to use the `-s` flag so you don’t have to think about it.
+
+By signing your work in this manner, you certify to a "Developer's Certificate of Origin". The current version of this certificate is in the `DCO.txt` file in the root of this repository.
+
+
+## How-to Guide
+
+There are two ways to make changes, the easy way and the hard way. Either way you will need to [create a GitHub account](https://github.com/signup/free).
+
+Easy way GitHub allows in-line editing of files for making simple typo changes and quick-fixes. This is not the best way as you are unable to test the code works. If you do this you could be introducing syntax errors, etc, but for a Git-phobic user this is good for a quick-fix.
+
+Hard way The best way to contribute is to "clone" your fork of CodeIgniter to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it".
+
+1. Set up Git (Windows, Mac & Linux)
+2. Go to the CodeIgniter repo
+3. Fork it
+4. Clone your CodeIgniter repo: git@github.com:<your-name>/CodeIgniter.git
+5. Checkout the "develop" branch At this point you are ready to start making changes. 
+6. Fix existing bugs on the Issue tracker after taking a look to see nobody else is working on them.
+7. Commit the files
+8. Push your develop branch to your fork
+9. Send a pull request [http://help.github.com/send-pull-requests/](http://help.github.com/send-pull-requests/)
+
+The Reactor Engineers will now be alerted about the change and at least one of the team will respond. If your change fails to meet the guidelines it will be bounced, or feedback will be provided to help you improve it.
+
+Once the Reactor Engineer handling your pull request is happy with it they will post it to the internal EllisLab discussion area to be double checked by the other Engineers and EllisLab developers. If nobody has a problem with the change then it will be merged into develop and will be part of the next release. Keeping your fork up-to-date
+
+Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for a URL of a Git repository. By default your fork will have a remote named "origin" which points to your fork, but you can add another remote named "codeigniter" which points to `git://github.com/EllisLab/CodeIgniter.git`. This is a read-only remote but you can pull from this develop branch to update your own.
+
+If you are using command-line you can do the following:
+
+1. `git remote add codeigniter git://github.com/EllisLab/CodeIgniter.git`
+2. `git pull codeigniter develop`
+3. `git push origin develop`
+
+Now your fork is up to date. This should be done regularly, or before you send a pull request at least.
\ No newline at end of file
diff --git a/index.php b/index.php
old mode 100644
new mode 100755
index ad98013..107a209
--- a/index.php
+++ b/index.php
@@ -43,6 +43,7 @@
  * NOTE: If you change these, also change the error_reporting() code below
  */
 	define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'development');
+
 /*
  *---------------------------------------------------------------
  * ERROR REPORTING
@@ -51,21 +52,22 @@
  * Different environments will require different levels of error reporting.
  * By default development will show errors but testing and live will hide them.
  */
-
-if (defined('ENVIRONMENT'))
+switch (ENVIRONMENT)
 {
-	switch (ENVIRONMENT)
-	{
-		case 'development':
-			error_reporting(-1);
-		break;
-		case 'testing':
-		case 'production':
-			error_reporting(0);
-		break;
-		default:
-			exit('The application environment is not set correctly.');
-	}
+	case 'development':
+		error_reporting(-1);
+		ini_set('display_errors', 1);
+	break;
+
+	case 'testing':
+	case 'production':
+		error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_STRICT);
+		ini_set('display_errors', 0);
+	break;
+
+	default:
+		header('HTTP/1.1 503 Service Unavailable.', TRUE, 503);
+		exit('The application environment is not set correctly.');
 }
 
 /*
@@ -133,7 +135,7 @@
 	// if your controller is not in a sub-folder within the "controllers" folder
 	// $routing['directory'] = '';
 
-	// The controller class file name.  Example:  Mycontroller
+	// The controller class file name.  Example:  mycontroller
 	// $routing['controller'] = '';
 
 	// The controller function you wish to be called.
@@ -268,4 +270,4 @@
 require_once BASEPATH.'core/CodeIgniter.php';
 
 /* End of file index.php */
-/* Location: ./index.php */
\ No newline at end of file
+/* Location: ./index.php */
diff --git a/readme.rst b/readme.rst
index b211ad7..8628645 100644
--- a/readme.rst
+++ b/readme.rst
@@ -38,166 +38,6 @@
 Please see the `installation section <http://codeigniter.com/user_guide/installation/index.html>`_
 of the CodeIgniter User Guide.
 
-************
-Contributing
-************
-
-CodeIgniter is a community driven project and accepts contributions of code
-and documentation from the community. These contributions are made in the form
-of Issues or `Pull Requests <http://help.github.com/send-pull-requests/>`_ on
-the `EllisLab CodeIgniter repository
-<https://github.com/EllisLab/CodeIgniter>`_ on GitHub.
-
-Issues are a quick way to point out a bug. If you find a bug or documentation
-error in CodeIgniter then please check a few things first:
-
-- There is not already an open Issue
-- The issue has already been fixed (check the develop branch, or look for
-  closed Issues)
-- Is it something really obvious that you fix it yourself?
-
-Reporting issues is helpful but an even better approach is to send a Pull
-Request, which is done by "Forking" the main repository and committing to your
-own copy. This will require you to use the version control system called Git.
-
-**********
-Guidelines
-**********
-
-Before we look into how, here are the guidelines. If your Pull Requests fail
-to pass these guidelines it will be declined and you will need to re-submit
-when you’ve made the changes. This might sound a bit tough, but it is required
-for us to maintain quality of the code-base.
-
-PHP Style
-=========
-
-All code must meet the `Style Guide
-<http://codeigniter.com/user_guide/general/styleguide.html>`_, which is
-essentially the `Allman indent style
-<http://en.wikipedia.org/wiki/Indent_style#Allman_style>`_, underscores and
-readable operators. This makes certain that all code is the same format as the
-existing code and means it will be as readable as possible.
-
-Documentation
-=============
-
-If you change anything that requires a change to documentation then you will
-need to add it. New classes, methods, parameters, changing default values, etc
-are all things that will require a change to documentation. The change-log
-must also be updated for every change. Also PHPDoc blocks must be maintained.
-
-Compatibility
-=============
-
-CodeIgniter is compatible with PHP 5.2.4 so all code supplied must stick to
-this requirement. If PHP 5.3 or 5.4 functions or features are used then there
-must be a fallback for PHP 5.2.4.
-
-Branching
-=========
-
-CodeIgniter uses the `Git-Flow
-<http://nvie.com/posts/a-successful-git-branching-model/>`_ branching model
-which requires all pull requests to be sent to the "develop" branch. This is
-where the next planned version will be developed. The "master" branch will
-always contain the latest stable version and is kept clean so a "hotfix" (e.g:
-an emergency security patch) can be applied to master to create a new version,
-without worrying about other features holding it up. For this reason all
-commits need to be made to "develop" and any sent to "master" will be closed
-automatically. If you have multiple changes to submit, please place all
-changes into their own branch on your fork.
-
-One thing at a time: A pull request should only contain one change. That does
-not mean only one commit, but one change - however many commits it took. The
-reason for this is that if you change X and Y but send a pull request for both
-at the same time, we might really want X but disagree with Y, meaning we
-cannot merge the request. Using the Git-Flow branching model you can create
-new branches for both of these features and send two requests.
-
-Signing
-=======
-You must sign your work, certifying that you either wrote the work or
-otherwise have the right to pass it on to an open source project. git makes
-this trivial as you merely have to use `--signoff` on your commits to your
-CodeIgniter fork.
-
-::
-
-	git commit --signoff
-
-or simply::
-
-	git commit -s
-
-This will sign your commits with the information setup in your git config, e.g.
-
-	Signed-off-by: John Q Public <john.public@example.com>
-
-If you are using Tower there is a "Sign-Off" checkbox in the commit window. You 
-could even alias git commit to use the -s flag so you don’t have to think about 
-it.
-
-By signing your work in this manner, you certify to a "Developer's Certificate 
-or Origin". The current version of this certificate is in the `DCO.txt` file
-in the root of this repository.
-
-
-************
-How-to Guide
-************
-
-There are two ways to make changes, the easy way and the hard way. Either way
-you will need to `create a GitHub account <https://github.com/signup/free>`_.
-
-Easy way GitHub allows in-line editing of files for making simple typo changes
-and quick-fixes. This is not the best way as you are unable to test the code
-works. If you do this you could be introducing syntax errors, etc, but for a
-Git-phobic user this is good for a quick-fix.
-
-Hard way The best way to contribute is to "clone" your fork of CodeIgniter to
-your development area. That sounds like some jargon, but "forking" on GitHub
-means "making a copy of that repo to your account" and "cloning" means
-"copying that code to your environment so you can work on it".
-
-#. Set up Git (Windows, Mac & Linux)
-#. Go to the CodeIgniter repo
-#. Fork it
-#. Clone your CodeIgniter repo: git@github.com:<your-name>/CodeIgniter.git
-#. Checkout the "develop" branch At this point you are ready to start making
-   changes. 
-#. Fix existing bugs on the Issue tracker after taking a look to see nobody
-   else is working on them.
-#. Commit the files
-#. Push your develop branch to your fork
-#. Send a pull request http://help.github.com/send-pull-requests/
-
-The Reactor Engineers will now be alerted about the change and at least one of
-the team will respond. If your change fails to meet the guidelines it will be
-bounced, or feedback will be provided to help you improve it.
-
-Once the Reactor Engineer handling your pull request is happy with it they
-will post it to the internal EllisLab discussion area to be double checked by
-the other Engineers and EllisLab developers. If nobody has a problem with the
-change then it will be merged into develop and will be part of the next
-release. Keeping your fork up-to-date
-
-Unlike systems like Subversion, Git can have multiple remotes. A remote is the
-name for a URL of a Git repository. By default your fork will have a remote
-named "origin" which points to your fork, but you can add another remote named
-"codeigniter" which points to git://github.com/EllisLab/CodeIgniter.git. This
-is a read-only remote but you can pull from this develop branch to update your
-own.
-
-If you are using command-line you can do the following:
-
-#. git remote add codeigniter git://github.com/EllisLab/CodeIgniter.git
-#. git pull codeigniter develop
-#. git push origin develop
-
-Now your fork is up to date. This should be done regularly, or before you send
-a pull request at least.
-
 *******
 License
 *******
diff --git a/system/.htaccess b/system/.htaccess
index 14249c5..97c65d2 100644
--- a/system/.htaccess
+++ b/system/.htaccess
@@ -1 +1,6 @@
-Deny from all
\ No newline at end of file
+<IfModule authz_core_module>
+	Require all denied
+</IfModule>
+<IfModule !authz_core_module>
+	Deny from all
+</IfModule>
\ No newline at end of file
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php
index 2fabdf4..e80ee54 100644
--- a/system/core/Benchmark.php
+++ b/system/core/Benchmark.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,9 +24,10 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * CodeIgniter Benchmark Class
+ * Benchmark Class
  *
  * This class enables you to mark points and calculate the time difference
  * between them. Memory consumption can also be displayed.
@@ -40,21 +41,19 @@
 class CI_Benchmark {
 
 	/**
-	 * List of all benchmark markers and when they were added
+	 * List of all benchmark markers
 	 *
-	 * @var array
+	 * @var	array
 	 */
-	public $marker =	array();
-
-	// --------------------------------------------------------------------
+	public $marker = array();
 
 	/**
 	 * Set a benchmark marker
 	 *
 	 * Multiple calls to this function can be made so that several
-	 * execution points can be timed
+	 * execution points can be timed.
 	 *
-	 * @param	string	$name	name of the marker
+	 * @param	string	$name	Marker name
 	 * @return	void
 	 */
 	public function mark($name)
@@ -65,6 +64,8 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Elapsed time
+	 *
 	 * Calculates the time difference between two marked points.
 	 *
 	 * If the first parameter is empty this function instead returns the
@@ -72,10 +73,13 @@
 	 * execution time to be shown in a template. The output class will
 	 * swap the real value for this variable.
 	 *
-	 * @param	string	a particular marked point
-	 * @param	string	a particular marked point
-	 * @param	integer	the number of decimal places
-	 * @return	mixed
+	 * @param	string	$point1		A particular marked point
+	 * @param	string	$point2		A particular marked point
+	 * @param	int	$decimals	Number of decimal places
+	 *
+	 * @return	string	Calculated elapsed time on success,
+	 *			an '{elapsed_string}' if $point1 is empty
+	 *			or an empty string if $point1 is not found.
 	 */
 	public function elapsed_time($point1 = '', $point2 = '', $decimals = 4)
 	{
@@ -102,12 +106,13 @@
 	/**
 	 * Memory Usage
 	 *
-	 * This function returns the {memory_usage} pseudo-variable.
+	 * Simply returns the {memory_usage} marker.
+	 *
 	 * This permits it to be put it anywhere in a template
 	 * without the memory being calculated until the end.
 	 * The output class will swap the real value for this variable.
 	 *
-	 * @return	string
+	 * @return	string	'{memory_usage}'
 	 */
 	public function memory_usage()
 	{
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index 8159b19..89081b5 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * System Initialization File
@@ -50,7 +51,7 @@
  *  Load the global functions
  * ------------------------------------------------------
  */
-	require(BASEPATH.'core/Common.php');
+	require_once(BASEPATH.'core/Common.php');
 
 /*
  * ------------------------------------------------------
@@ -130,9 +131,12 @@
 	$CFG =& load_class('Config', 'core');
 
 	// Do we have any manually set config items in the index.php file?
-	if (isset($assign_to_config))
+	if (isset($assign_to_config) && is_array($assign_to_config))
 	{
-		$CFG->_assign_to_config($assign_to_config);
+		foreach ($assign_to_config as $key => $value)
+		{
+			$CFG->set_item($key, $value);
+		}
 	}
 
 /*
@@ -229,7 +233,6 @@
 		return CI_Controller::get_instance();
 	}
 
-
 	if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
 	{
 		require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
@@ -253,23 +256,22 @@
  *  Security check
  * ------------------------------------------------------
  *
- *  None of the functions in the app controller or the
+ *  None of the methods in the app controller or the
  *  loader class can be called via the URI, nor can
- *  controller functions that begin with an underscore
+ *  controller functions that begin with an underscore.
  */
 	$class  = $RTR->fetch_class();
 	$method = $RTR->fetch_method();
 
-	if ( ! class_exists($class)
-		OR strpos($method, '_') === 0
-		OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller')))
-		)
+	if ( ! class_exists($class) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
 	{
 		if ( ! empty($RTR->routes['404_override']))
 		{
-			$x = explode('/', $RTR->routes['404_override'], 2);
-			$class = $x[0];
-			$method = isset($x[1]) ? $x[1] : 'index';
+			if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
+			{
+				$method = 'index';
+			}
+
 			if ( ! class_exists($class))
 			{
 				if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
@@ -286,6 +288,42 @@
 		}
 	}
 
+	if (method_exists($class, '_remap'))
+	{
+		$params = array($method, array_slice($URI->rsegments, 2));
+		$method = '_remap';
+	}
+	else
+	{
+		// WARNING: It appears that there are issues with is_callable() even in PHP 5.2!
+		// Furthermore, there are bug reports and feature/change requests related to it
+		// that make it unreliable to use in this context. Please, DO NOT change this
+		// work-around until a better alternative is available.
+		if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE))
+		{
+			if (empty($RTR->routes['404_override']))
+			{
+				show_404($class.'/'.$method);
+			}
+			elseif (sscanf($RTR->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
+			{
+				$method = 'index';
+			}
+
+			if ( ! class_exists($class))
+			{
+				if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
+				{
+					show_404($class.'/'.$method);
+				}
+
+				include_once(APPPATH.'controllers/'.$class.'.php');
+			}
+		}
+
+		$params = array_slice($URI->rsegments, 2);
+	}
+
 /*
  * ------------------------------------------------------
  *  Is there a "pre_controller" hook?
@@ -315,45 +353,7 @@
  *  Call the requested method
  * ------------------------------------------------------
  */
-	// Is there a "remap" function? If so, we call it instead
-	if (method_exists($CI, '_remap'))
-	{
-		$CI->_remap($method, array_slice($URI->rsegments, 2));
-	}
-	else
-	{
-		// is_callable() returns TRUE on some versions of PHP 5 for private and protected
-		// methods, so we'll use this workaround for consistent behavior
-		if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
-		{
-			// Check and see if we are using a 404 override and use it.
-			if ( ! empty($RTR->routes['404_override']))
-			{
-				$x = explode('/', $RTR->routes['404_override'], 2);
-				$class = $x[0];
-				$method = isset($x[1]) ? $x[1] : 'index';
-				if ( ! class_exists($class))
-				{
-					if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
-					{
-						show_404($class.'/'.$method);
-					}
-
-					include_once(APPPATH.'controllers/'.$class.'.php');
-					unset($CI);
-					$CI = new $class();
-				}
-			}
-			else
-			{
-				show_404($class.'/'.$method);
-			}
-		}
-
-		// Call the requested method.
-		// Any URI segments present (besides the class/function) will be passed to the method for convenience
-		call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
-	}
+	call_user_func_array(array(&$CI, $method), $params);
 
 	// Mark a benchmark end point
 	$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end');
diff --git a/system/core/Common.php b/system/core/Common.php
index c309d41..c7ab387 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Common Functions
@@ -150,7 +151,7 @@
 
 				if (class_exists($name) === FALSE)
 				{
-					require($path.$directory.'/'.$class.'.php');
+					require_once($path.$directory.'/'.$class.'.php');
 				}
 
 				break;
@@ -164,7 +165,7 @@
 
 			if (class_exists($name) === FALSE)
 			{
-				require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php');
+				require_once(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php');
 			}
 		}
 
@@ -172,7 +173,7 @@
 		if ($name === FALSE)
 		{
 			// Note: We use exit() rather then show_error() in order to avoid a
-			// self-referencing loop with the Excptions class
+			// self-referencing loop with the Exceptions class
 			set_status_header(503);
 			exit('Unable to locate the specified class: '.$class.'.php');
 		}
@@ -330,6 +331,24 @@
 
 // ------------------------------------------------------------------------
 
+if ( ! function_exists('is_https'))
+{
+	/**
+	 * Is HTTPS?
+	 *
+	 * Determines if the application is accessed via an encrypted
+	 * (HTTPS) connection.
+	 *
+	 * @return	bool
+	 */
+	function is_https()
+	{
+		return ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off');
+	}
+}
+
+// ------------------------------------------------------------------------
+
 if ( ! function_exists('show_error'))
 {
 	/**
@@ -401,7 +420,7 @@
 			return;
 		}
 
-		$_log =& load_class('Log');
+		$_log =& load_class('Log', 'core');
 		$_log->write_log($level, $message, $php_error);
 	}
 }
@@ -488,13 +507,9 @@
 		{
 			header('Status: '.$code.' '.$text, TRUE);
 		}
-		elseif ($server_protocol === 'HTTP/1.0')
-		{
-			header('HTTP/1.0 '.$code.' '.$text, TRUE, $code);
-		}
 		else
 		{
-			header('HTTP/1.1 '.$code.' '.$text, TRUE, $code);
+			header(($server_protocol ? $server_protocol : 'HTTP/1.1').' '.$code.' '.$text, TRUE, $code);
 		}
 	}
 }
@@ -524,19 +539,19 @@
 	{
 		$_error =& load_class('Exceptions', 'core');
 
-		// Should we display the error? We'll get the current error_reporting
+		// Should we ignore the error? We'll get the current error_reporting
 		// level and add its bits with the severity bits to find out.
-		if (($severity & error_reporting()) === $severity)
-		{
-			$_error->show_php_error($severity, $message, $filepath, $line);
-		}
-
-		// Should we log the error? No? We're done...
-		if (config_item('log_threshold') === 0)
+		if (($severity & error_reporting()) !== $severity)
 		{
 			return;
 		}
 
+		// Should we display the error?
+		if ((bool) ini_get('display_errors') === TRUE)
+		{
+			$_error->show_php_error($severity, $message, $filepath, $line);
+		}
+
 		$_error->log_exception($severity, $message, $filepath, $line);
 	}
 }
@@ -597,5 +612,96 @@
 	}
 }
 
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('_stringify_attributes'))
+{
+	/**
+	 * Stringify attributes for use in HTML tags.
+	 *
+	 * Helper function used to convert a string, array, or object
+	 * of attributes to a string.
+	 *
+	 * @param	mixed	string, array, object
+	 * @param	bool
+	 * @return	string
+	 */
+	function _stringify_attributes($attributes, $js = FALSE)
+	{
+		$atts = NULL;
+
+		if (empty($attributes))
+		{
+			return $atts;
+		}
+
+		if (is_string($attributes))
+		{
+			return ' '.$attributes;
+		}
+
+		$attributes = (array) $attributes;
+
+		foreach ($attributes as $key => $val)
+		{
+			$atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"';
+		}
+
+		return rtrim($atts, ',');
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('function_usable'))
+{
+	/**
+	 * Function usable
+	 *
+	 * Executes a function_exists() check, and if the Suhosin PHP
+	 * extension is loaded - checks whether the function that is
+	 * checked might be disabled in there as well.
+	 *
+	 * This is useful as function_exists() will return FALSE for
+	 * functions disabled via the *disable_functions* php.ini
+	 * setting, but not for *suhosin.executor.func.blacklist* and
+	 * *suhosin.executor.disable_eval*. These settings will just
+	 * terminate script execution if a disabled function is executed.
+	 *
+	 * @link	http://www.hardened-php.net/suhosin/
+	 * @param	string	$function_name	Function to check for
+	 * @return	bool	TRUE if the function exists and is safe to call,
+	 *			FALSE otherwise.
+	 */
+	function function_usable($function_name)
+	{
+		static $_suhosin_func_blacklist;
+
+		if (function_exists($function_name))
+		{
+			if ( ! isset($_suhosin_func_blacklist))
+			{
+				if (extension_loaded('suhosin'))
+				{
+					$_suhosin_func_blacklist = explode(',', trim(@ini_get('suhosin.executor.func.blacklist')));
+
+					if ( ! in_array('eval', $_suhosin_func_blacklist, TRUE) && @ini_get('suhosin.executor.disable_eval'))
+					{
+						$_suhosin_func_blacklist[] = 'eval';
+					}
+				}
+				else
+				{
+					$_suhosin_func_blacklist = array();
+				}
+			}
+
+			return ! in_array($function_name, $_suhosin_func_blacklist, TRUE);
+		}
+
+		return FALSE;
+	}
+}
+
 /* End of file Common.php */
 /* Location: ./system/core/Common.php */
\ No newline at end of file
diff --git a/system/core/Config.php b/system/core/Config.php
index 2f6a9e0..38bcd5c 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,9 +24,10 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * CodeIgniter Config Class
+ * Config Class
  *
  * This class contains functions that enable config files to be managed
  *
@@ -41,29 +42,31 @@
 	/**
 	 * List of all loaded config values
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $config = array();
 
 	/**
 	 * List of all loaded config files
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $is_loaded =	array();
 
 	/**
 	 * List of paths to search when trying to load a config file.
-	 * This must be public as it's used by the Loader class.
 	 *
-	 * @var array
+	 * @used-by	CI_Loader
+	 * @var		array
 	 */
 	public $_config_paths =	array(APPPATH);
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * Sets the $config data from the primary config.php file as a class variable
+	 * Sets the $config data from the primary config.php file as a class variable.
+	 *
+	 * @return	void
 	 */
 	public function __construct()
 	{
@@ -75,7 +78,7 @@
 		{
 			if (isset($_SERVER['HTTP_HOST']))
 			{
-				$base_url = ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') ? 'https' : 'http';
+				$base_url = is_https() ? 'https' : 'http';
 				$base_url .= '://'.$_SERVER['HTTP_HOST']
 					.str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
 			}
@@ -93,16 +96,16 @@
 	/**
 	 * Load Config File
 	 *
-	 * @param	string	the config file name
-	 * @param	bool	if configuration values should be loaded into their own section
-	 * @param	bool	true if errors should just return false, false if an error message should be displayed
-	 * @return	bool	if the file was loaded correctly
+	 * @param	string	$file			Configuration file name
+	 * @param	bool	$use_sections		Whether configuration values should be loaded into their own section
+	 * @param	bool	$fail_gracefully	Whether to just return FALSE or display an error message
+	 * @return	bool	TRUE if the file was loaded correctly or FALSE on failure
 	 */
 	public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
 	{
 		$file = ($file === '') ? 'config' : str_replace('.php', '', $file);
 		$found = $loaded = FALSE;
-		
+
 		$check_locations = defined('ENVIRONMENT')
 			? array(ENVIRONMENT.'/'.$file, $file)
 			: array($file);
@@ -183,9 +186,9 @@
 	/**
 	 * Fetch a config file item
 	 *
-	 * @param	string	the config item name
-	 * @param	string	the index name
-	 * @return	string
+	 * @param	string	$item	Config item name
+	 * @param	string	$index	Index name
+	 * @return	string|bool	The configuration item or FALSE on failure
 	 */
 	public function item($item, $index = '')
 	{
@@ -200,10 +203,10 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a config file item - adds slash after item (if item is not empty)
+	 * Fetch a config file item with slash appended (if not empty)
 	 *
-	 * @param	string	the config item name
-	 * @return	string
+	 * @param	string		$item	Config item name
+	 * @return	string|bool	The configuration item or FALSE on failure
 	 */
 	public function slash_item($item)
 	{
@@ -223,9 +226,12 @@
 
 	/**
 	 * Site URL
+	 *
 	 * Returns base_url . index_page [. uri_string]
 	 *
-	 * @param	mixed	the URI string or an array of segments
+	 * @uses	CI_Config::_uri_string()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
 	 * @return	string
 	 */
 	public function site_url($uri = '')
@@ -239,15 +245,18 @@
 
 		if ($this->item('enable_query_strings') === FALSE)
 		{
-			$suffix = ($this->item('url_suffix') === FALSE) ? '' : $this->item('url_suffix');
+			$suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : '';
 
-			if ($suffix !== '' && ($offset = strpos($uri, '?')) !== FALSE)
+			if ($suffix !== '')
 			{
-				$uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset);
-			}
-			else
-			{
-				$uri .= $suffix;
+				if (($offset = strpos($uri, '?')) !== FALSE)
+				{
+					$uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset);
+				}
+				else
+				{
+					$uri .= $suffix;
+				}
 			}
 
 			return $this->slash_item('base_url').$this->slash_item('index_page').$uri;
@@ -264,9 +273,12 @@
 
 	/**
 	 * Base URL
+	 *
 	 * Returns base_url [. uri_string]
 	 *
-	 * @param	string	$uri
+	 * @uses	CI_Config::_uri_string()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
 	 * @return	string
 	 */
 	public function base_url($uri = '')
@@ -277,9 +289,12 @@
 	// -------------------------------------------------------------
 
 	/**
-	 * Build URI string for use in Config::site_url() and Config::base_url()
+	 * Build URI string
 	 *
-	 * @param	mixed	$uri
+	 * @used-by	CI_Config::site_url()
+	 * @used-by	CI_Config::base_url()
+	 *
+	 * @param	string|string[]	$uri	URI string or an array of segments
 	 * @return	string
 	 */
 	protected function _uri_string($uri)
@@ -318,8 +333,8 @@
 	/**
 	 * Set a config file item
 	 *
-	 * @param	string	the config item key
-	 * @param	string	the config item value
+	 * @param	string	$item	Config item key
+	 * @param	string	$value	Config item value
 	 * @return	void
 	 */
 	public function set_item($item, $value)
@@ -327,29 +342,6 @@
 		$this->config[$item] = $value;
 	}
 
-	// --------------------------------------------------------------------
-
-	/**
-	 * Assign to Config
-	 *
-	 * This function is called by the front controller (CodeIgniter.php)
-	 * after the Config class is instantiated. It permits config items
-	 * to be assigned or overriden by variables contained in the index.php file
-	 *
-	 * @param	array
-	 * @return	void
-	 */
-	public function _assign_to_config($items = array())
-	{
-		if (is_array($items))
-		{
-			foreach ($items as $key => $val)
-			{
-				$this->set_item($key, $val);
-			}
-		}
-	}
-
 }
 
 /* End of file Config.php */
diff --git a/system/core/Controller.php b/system/core/Controller.php
index 4914148..ee6fec8 100644
--- a/system/core/Controller.php
+++ b/system/core/Controller.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,9 +24,10 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * CodeIgniter Application Controller Class
+ * Application Controller Class
  *
  * This class object is the super class that every library in
  * CodeIgniter will be assigned to.
@@ -40,14 +41,14 @@
 class CI_Controller {
 
 	/**
-	 * Reference to the global CI instance
+	 * Reference to the CI singleton
 	 *
 	 * @var	object
 	 */
 	private static $instance;
 
 	/**
-	 * Set up controller properties and methods
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -68,9 +69,12 @@
 		log_message('debug', 'Controller Class Initialized');
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * Return the CI object
+	 * Get the CI singleton
 	 *
+	 * @static
 	 * @return	object
 	 */
 	public static function &get_instance()
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index bd9178d..ced65ec 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Exceptions Class
@@ -64,7 +65,7 @@
 	);
 
 	/**
-	 * Initialize execption class
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -79,12 +80,12 @@
 	/**
 	 * Exception Logger
 	 *
-	 * This function logs PHP generated error messages
+	 * Logs PHP generated error messages
 	 *
-	 * @param	string	the error severity
-	 * @param	string	the error string
-	 * @param	string	the error filepath
-	 * @param	string	the error line number
+	 * @param	int	$severity	Log level
+	 * @param	string	$message	Error message
+	 * @param	string	$filepath	File path
+	 * @param	int	$line		Line number
 	 * @return	void
 	 */
 	public function log_exception($severity, $message, $filepath, $line)
@@ -96,11 +97,13 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * 404 Page Not Found Handler
+	 * 404 Error Handler
 	 *
-	 * @param	string	the page
-	 * @param 	bool	log error yes/no
-	 * @return	string
+	 * @uses	CI_Exceptions::show_error()
+	 *
+	 * @param	string	$page		Page URI
+	 * @param 	bool	$log_error	Whether to log the error
+	 * @return	void
 	 */
 	public function show_404($page = '', $log_error = TRUE)
 	{
@@ -122,15 +125,15 @@
 	/**
 	 * General Error Page
 	 *
-	 * This function takes an error message as input
-	 * (either as a string or an array) and displays
-	 * it using the specified template.
+	 * Takes an error message as input (either as a string or an array)
+	 * and displays it using the specified template.
 	 *
-	 * @param	string	the heading
-	 * @param	string	the message
-	 * @param	string	the template name
-	 * @param 	int	the status code
-	 * @return	string
+	 * @param	string		$heading	Page heading
+	 * @param	string|string[]	$message	Error message
+	 * @param	string		$template	Template name
+	 * @param 	int		$status_code	(default: 500)
+	 *
+	 * @return	string	Error page output
 	 */
 	public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
 	{
@@ -154,11 +157,11 @@
 	/**
 	 * Native PHP error handler
 	 *
-	 * @param	string	the error severity
-	 * @param	string	the error string
-	 * @param	string	the error filepath
-	 * @param	string	the error line number
-	 * @return	string
+	 * @param	int	$severity	Error level
+	 * @param	string	$message	Error message
+	 * @param	string	$filepath	File path
+	 * @param	int	$line		Line number
+	 * @return	string	Error page output
 	 */
 	public function show_php_error($severity, $message, $filepath, $line)
 	{
diff --git a/system/core/Hooks.php b/system/core/Hooks.php
index afbf4b4..3c28ec9 100644
--- a/system/core/Hooks.php
+++ b/system/core/Hooks.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,9 +24,10 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * CodeIgniter Hooks Class
+ * Hooks Class
  *
  * Provides a mechanism to extend the base system without hacking.
  *
@@ -41,26 +42,28 @@
 	/**
 	 * Determines whether hooks are enabled
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
-	public $enabled =	FALSE;
+	public $enabled = FALSE;
 
 	/**
 	 * List of all hooks set in config/hooks.php
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $hooks =	array();
 
 	/**
+	 * In progress flag
+	 *
 	 * Determines whether hook is in progress, used to prevent infinte loops
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
-	public $in_progress	=	FALSE;
+	protected $_in_progress = FALSE;
 
 	/**
-	 * Initialize the Hooks Preferences
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -104,8 +107,10 @@
 	 *
 	 * Calls a particular hook. Called by CodeIgniter.php.
 	 *
-	 * @param	string	the hook name
-	 * @return	mixed
+	 * @uses	CI_Hooks::_run_hook()
+	 *
+	 * @param	string	$which	Hook name
+	 * @return	bool	TRUE on success or FALSE on failure
 	 */
 	public function call_hook($which = '')
 	{
@@ -136,8 +141,8 @@
 	 *
 	 * Runs a particular hook
 	 *
-	 * @param	array	the hook details
-	 * @return	bool
+	 * @param	array	$data	Hook details
+	 * @return	bool	TRUE on success or FALSE on failure
 	 */
 	protected function _run_hook($data)
 	{
@@ -152,7 +157,7 @@
 
 		// If the script being called happens to have the same
 		// hook call within it a loop can happen
-		if ($this->in_progress === TRUE)
+		if ($this->_in_progress === TRUE)
 		{
 			return;
 		}
@@ -173,44 +178,20 @@
 			return FALSE;
 		}
 
-		// -----------------------------------
-		// Set class/function name
-		// -----------------------------------
-
-		$class		= FALSE;
-		$function	= FALSE;
-		$params		= '';
-
-		if ( ! empty($data['class']))
-		{
-			$class = $data['class'];
-		}
-
-		if ( ! empty($data['function']))
-		{
-			$function = $data['function'];
-		}
-
-		if (isset($data['params']))
-		{
-			$params = $data['params'];
-		}
+		// Determine and class and/or function names
+		$class		= empty($data['class']) ? FALSE : $data['class'];
+		$function	= empty($data['function']) ? FALSE : $data['function'];
+		$params		= isset($data['params']) ? $data['params'] : '';
 
 		if ($class === FALSE && $function === FALSE)
 		{
 			return FALSE;
 		}
 
-		// -----------------------------------
-		// Set the in_progress flag
-		// -----------------------------------
+		// Set the _in_progress flag
+		$this->_in_progress = TRUE;
 
-		$this->in_progress = TRUE;
-
-		// -----------------------------------
 		// Call the requested class and/or function
-		// -----------------------------------
-
 		if ($class !== FALSE)
 		{
 			if ( ! class_exists($class))
@@ -218,7 +199,7 @@
 				require($filepath);
 			}
 
-			$HOOK = new $class;
+			$HOOK = new $class();
 			$HOOK->$function($params);
 		}
 		else
@@ -231,7 +212,7 @@
 			$function($params);
 		}
 
-		$this->in_progress = FALSE;
+		$this->_in_progress = FALSE;
 		return TRUE;
 	}
 
diff --git a/system/core/Input.php b/system/core/Input.php
index 162e40c..a3ad14e 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Input Class
@@ -41,59 +42,78 @@
 	/**
 	 * IP address of the current user
 	 *
-	 * @var string
+	 * @var	string
 	 */
-	public $ip_address =	FALSE;
+	public $ip_address = FALSE;
 
 	/**
-	 * user agent (web browser) being used by the current user
+	 * User agent strin
 	 *
-	 * @var string
+	 * @var	string
 	 */
-	public $user_agent =	FALSE;
+	public $user_agent = FALSE;
 
 	/**
-	 * If FALSE, then $_GET will be set to an empty array
+	 * Allow GET array flag
 	 *
-	 * @var bool
-	 */
-	protected $_allow_get_array =	TRUE;
-
-	/**
-	 * If TRUE, then newlines are standardized
+	 * If set to FALSE, then $_GET will be set to an empty array.
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
-	protected $_standardize_newlines =	TRUE;
+	protected $_allow_get_array = TRUE;
 
 	/**
-	 * Determines whether the XSS filter is always active when GET, POST or COOKIE data is encountered
-	 * Set automatically based on config setting
+	 * Standartize new lines flag
 	 *
-	 * @var bool
+	 * If set to TRUE, then newlines are standardized.
+	 *
+	 * @var	bool
 	 */
-	protected $_enable_xss =	FALSE;
+	protected $_standardize_newlines = TRUE;
 
 	/**
+	 * Enable XSS flag
+	 *
+	 * Determines whether the XSS filter is always active when
+	 * GET, POST or COOKIE data is encountered.
+	 * Set automatically based on config setting.
+	 *
+	 * @var	bool
+	 */
+	protected $_enable_xss = FALSE;
+
+	/**
+	 * Enable CSRF flag
+	 *
 	 * Enables a CSRF cookie token to be set.
-	 * Set automatically based on config setting
+	 * Set automatically based on config setting.
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
-	protected $_enable_csrf =	FALSE;
+	protected $_enable_csrf = FALSE;
 
 	/**
 	 * List of all HTTP request headers
 	 *
 	 * @var array
 	 */
-	protected $headers =	array();
+	protected $headers = array();
 
 	/**
-	 * Constructor
+	 * Input stream data
 	 *
-	 * Sets whether to globally enable the XSS processing
-	 * and whether to allow the $_GET array
+	 * Parsed from php://input at runtime
+	 *
+	 * @see	CI_Input::input_stream()
+	 * @var	array
+	 */
+	protected $_input_stream = NULL;
+
+	/**
+	 * Class constructor
+	 *
+	 * Determines whether to globally enable the XSS processing
+	 * and whether to allow the $_GET array.
 	 *
 	 * @return	void
 	 */
@@ -124,12 +144,12 @@
 	/**
 	 * Fetch from array
 	 *
-	 * This is a helper function to retrieve values from global arrays
+	 * Internal method used to retrieve values from global arrays.
 	 *
-	 * @param	array
-	 * @param	string
-	 * @param	bool
-	 * @return	string
+	 * @param	array	&$array		$_GET, $_POST, $_COOKIE, $_SERVER, etc.
+	 * @param	string	$index		Index for item to be fetched from $array
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
 	 */
 	protected function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE)
 	{
@@ -151,15 +171,20 @@
 	/**
 	 * Fetch an item from the GET array
 	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	string
+	 * @param	string	$index		Index for item to be fetched from $_GET
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
 	 */
 	public function get($index = NULL, $xss_clean = FALSE)
 	{
 		// Check if a field has been provided
-		if ($index === NULL && ! empty($_GET))
+		if ($index === NULL)
 		{
+			if (empty($_GET))
+			{
+				return array();
+			}
+
 			$get = array();
 
 			// loop through the full _GET array
@@ -178,15 +203,20 @@
 	/**
 	 * Fetch an item from the POST array
 	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	string
+	 * @param	string	$index		Index for item to be fetched from $_POST
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
 	 */
 	public function post($index = NULL, $xss_clean = FALSE)
 	{
 		// Check if a field has been provided
-		if ($index === NULL && ! empty($_POST))
+		if ($index === NULL)
 		{
+			if (empty($_POST))
+			{
+				return array();
+			}
+
 			$post = array();
 
 			// Loop through the full _POST array and return it
@@ -200,15 +230,14 @@
 		return $this->_fetch_from_array($_POST, $index, $xss_clean);
 	}
 
-
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch an item from either the GET array or the POST
+	 * Fetch an item from POST data with fallback to GET
 	 *
-	 * @param	string	The index key
-	 * @param	bool	XSS cleaning
-	 * @return	string
+	 * @param	string	$index		Index for item to be fetched from $_POST or $_GET
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
 	 */
 	public function get_post($index = '', $xss_clean = FALSE)
 	{
@@ -222,31 +251,76 @@
 	/**
 	 * Fetch an item from the COOKIE array
 	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	string
+	 * @param	string	$index		Index for item to be fetched from $_COOKIE
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
 	 */
 	public function cookie($index = '', $xss_clean = FALSE)
 	{
 		return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
 	}
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch an item from the SERVER array
+	 *
+	 * @param	string	$index		Index for item to be fetched from $_SERVER
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
+	 */
+	public function server($index = '', $xss_clean = FALSE)
+	{
+		return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch an item from the php://input stream
+	 *
+	 * Useful when you need to access PUT, DELETE or PATCH request data.
+	 *
+	 * @param	string	$index		Index for item to be fetched
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
+	 * @return	mixed
+	 */
+	public function input_stream($index = '', $xss_clean = FALSE)
+	{
+		// The input stream can only be read once, so we'll need to check
+		// if we have already done that first.
+		if (is_array($this->_input_stream))
+		{
+			return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean);
+		}
+
+		// Parse the input stream in our cache var
+		parse_str(file_get_contents('php://input'), $this->_input_stream);
+		if ( ! is_array($this->_input_stream))
+		{
+			$this->_input_stream = array();
+			return NULL;
+		}
+
+		return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean);
+	}
+
 	// ------------------------------------------------------------------------
 
 	/**
 	 * Set cookie
 	 *
-	 * Accepts seven parameters, or you can submit an associative
+	 * Accepts an arbitrary number of parameters (up to 7) or an associative
 	 * array in the first parameter containing all the values.
 	 *
-	 * @param	mixed
-	 * @param	string	the value of the cookie
-	 * @param	string	the number of seconds until expiration
-	 * @param	string	the cookie domain.  Usually:  .yourdomain.com
-	 * @param	string	the cookie path
-	 * @param	string	the cookie prefix
-	 * @param	bool	true makes the cookie secure
-	 * @param	bool	true makes the cookie accessible via http(s) only (no javascript)
+	 * @param	string|mixed[]	$name		Cookie name or an array containing parameters
+	 * @param	string		$value		Cookie value
+	 * @param	int		$expire		Cookie expiration time in seconds
+	 * @param	string		$domain		Cookie domain (e.g.: '.yourdomain.com')
+	 * @param	string		$path		Cookie path (default: '/')
+	 * @param	string		$prefix		Cookie name prefix
+	 * @param	bool		$secure		Whether to only transfer cookies via SSL
+	 * @param	bool		$httponly	Whether to only makes the cookie accessible via HTTP (no javascript)
 	 * @return	void
 	 */
 	public function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = FALSE)
@@ -303,23 +377,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch an item from the SERVER array
-	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	string
-	 */
-	public function server($index = '', $xss_clean = FALSE)
-	{
-		return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Fetch the IP Address
 	 *
-	 * @return	string
+	 * Determines and validates the visitor's IP address.
+	 *
+	 * @return	string	IP address
 	 */
 	public function ip_address()
 	{
@@ -328,39 +390,117 @@
 			return $this->ip_address;
 		}
 
-		if (config_item('proxy_ips') != '' && $this->server('HTTP_X_FORWARDED_FOR') && $this->server('REMOTE_ADDR'))
+		$proxy_ips = config_item('proxy_ips');
+		if ( ! empty($proxy_ips) && ! is_array($proxy_ips))
 		{
-			$proxies = preg_split('/[\s,]/', config_item('proxy_ips'), -1, PREG_SPLIT_NO_EMPTY);
-			$proxies = is_array($proxies) ? $proxies : array($proxies);
-
-			$this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
-		}
-		elseif ( ! $this->server('HTTP_CLIENT_IP') && $this->server('REMOTE_ADDR'))
-		{
-			$this->ip_address = $_SERVER['REMOTE_ADDR'];
-		}
-		elseif ($this->server('REMOTE_ADDR') && $this->server('HTTP_CLIENT_IP'))
-		{
-			$this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
-		}
-		elseif ($this->server('HTTP_CLIENT_IP'))
-		{
-			$this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
-		}
-		elseif ($this->server('HTTP_X_FORWARDED_FOR'))
-		{
-			$this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
+			$proxy_ips = explode(',', str_replace(' ', '', $proxy_ips));
 		}
 
-		if ($this->ip_address === FALSE)
-		{
-			return $this->ip_address = '0.0.0.0';
-		}
+		$this->ip_address = $this->server('REMOTE_ADDR');
 
-		if (strpos($this->ip_address, ',') !== FALSE)
+		if ($proxy_ips)
 		{
-			$x = explode(',', $this->ip_address);
-			$this->ip_address = trim(end($x));
+			foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header)
+			{
+				if (($spoof = $this->server($header)) !== NULL)
+				{
+					// Some proxies typically list the whole chain of IP
+					// addresses through which the client has reached us.
+					// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
+					sscanf($spoof, '%[^,]', $spoof);
+
+					if ( ! $this->valid_ip($spoof))
+					{
+						$spoof = NULL;
+					}
+					else
+					{
+						break;
+					}
+				}
+			}
+
+			if ($spoof)
+			{
+				for ($i = 0, $c = count($proxy_ips); $i < $c; $i++)
+				{
+					// Check if we have an IP address or a subnet
+					if (strpos($proxy_ips[$i], '/') === FALSE)
+					{
+						// An IP address (and not a subnet) is specified.
+						// We can compare right away.
+						if ($proxy_ips[$i] === $this->ip_address)
+						{
+							$this->ip_address = $spoof;
+							break;
+						}
+
+						continue;
+					}
+
+					// We have a subnet ... now the heavy lifting begins
+					isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.';
+
+					// If the proxy entry doesn't match the IP protocol - skip it
+					if (strpos($proxy_ips[$i], $separator) === FALSE)
+					{
+						continue;
+					}
+
+					// Convert the REMOTE_ADDR IP address to binary, if needed
+					if ( ! isset($ip, $sprintf))
+					{
+						if ($separator === ':')
+						{
+							// Make sure we're have the "full" IPv6 format
+							$ip = explode(':',
+								str_replace('::',
+									str_repeat(':', 9 - substr_count($this->ip_address, ':')),
+									$this->ip_address
+								)
+							);
+
+							for ($i = 0; $i < 8; $i++)
+							{
+								$ip[$i] = intval($ip[$i], 16);
+							}
+
+							$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
+						}
+						else
+						{
+							$ip = explode('.', $this->ip_address);
+							$sprintf = '%08b%08b%08b%08b';
+						}
+
+						$ip = vsprintf($sprintf, $ip);
+					}
+
+					// Split the netmask length off the network address
+					sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen);
+
+					// Again, an IPv6 address is most likely in a compressed form
+					if ($separator === ':')
+					{
+						$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
+						for ($i = 0; $i < 8; $i++)
+						{
+							$netaddr[$i] = intval($netaddr[$i], 16);
+						}
+					}
+					else
+					{
+						$netaddr = explode('.', $netaddr);
+					}
+
+					// Convert to binary and finally compare
+					if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0)
+					{
+						$this->ip_address = $spoof;
+						break;
+					}
+				}
+			}
 		}
 
 		if ( ! $this->valid_ip($this->ip_address))
@@ -376,8 +516,8 @@
 	/**
 	 * Validate IP Address
 	 *
-	 * @param	string
-	 * @param	string	'ipv4' or 'ipv6'
+	 * @param	string	$ip	IP address
+	 * @param	string	$which	IP protocol: 'ipv4' or 'ipv6'
 	 * @return	bool
 	 */
 	public function valid_ip($ip, $which = '')
@@ -401,9 +541,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * User Agent
+	 * Fetch User Agent string
 	 *
-	 * @return	string
+	 * @return	string|null	User Agent string or NULL if it doesn't exist
 	 */
 	public function user_agent()
 	{
@@ -412,7 +552,7 @@
 			return $this->user_agent;
 		}
 
-		return $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : FALSE;
+		return $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL;
 	}
 
 	// --------------------------------------------------------------------
@@ -420,11 +560,12 @@
 	/**
 	 * Sanitize Globals
 	 *
-	 * This function does the following:
+	 * Internal method serving for the following purposes:
 	 *
-	 * - Unsets $_GET data (if query strings are not enabled)
-	 * - Unsets all globals if register_globals is enabled
-	 * - Standardizes newline characters to \n
+	 *	- Unsets $_GET data (if query strings are not enabled)
+	 *	- Unsets all globals if register_globals is enabled
+	 *	- Cleans POST, COOKIE and SERVER data
+	 * 	- Standardizes newline characters to PHP_EOL
 	 *
 	 * @return	void
 	 */
@@ -452,25 +593,29 @@
 			'IN'
 		);
 
-		// Unset globals for securiy.
+		// Unset globals for security.
 		// This is effectively the same as register_globals = off
-		foreach (array($_GET, $_POST, $_COOKIE) as $global)
+		// PHP 5.4 no longer has the register_globals functionality.
+		if ( ! is_php('5.4'))
 		{
-			if (is_array($global))
+			foreach (array($_GET, $_POST, $_COOKIE) as $global)
 			{
-				foreach ($global as $key => $val)
+				if (is_array($global))
 				{
-					if ( ! in_array($key, $protected))
+					foreach ($global as $key => $val)
 					{
-						global $$key;
-						$$key = NULL;
+						if ( ! in_array($key, $protected))
+						{
+							global $$key;
+							$$key = NULL;
+						}
 					}
 				}
-			}
-			elseif ( ! in_array($global, $protected))
-			{
-				global $$global;
-				$$global = NULL;
+				elseif ( ! in_array($global, $protected))
+				{
+					global $$global;
+					$$global = NULL;
+				}
 			}
 		}
 
@@ -518,7 +663,7 @@
 		$_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
 
 		// CSRF Protection check
-		if ($this->_enable_csrf === TRUE)
+		if ($this->_enable_csrf === TRUE && ! $this->is_cli_request())
 		{
 			$this->security->csrf_verify();
 		}
@@ -531,10 +676,10 @@
 	/**
 	 * Clean Input Data
 	 *
-	 * This is a helper function. It escapes data and
-	 * standardizes newline characters to \n
+	 * Internal method that aids in escaping data and
+	 * standardizing newline characters to PHP_EOL.
 	 *
-	 * @param	string
+	 * @param	string|string[]	$str	Input string(s)
 	 * @return	string
 	 */
 	protected function _clean_input_data($str)
@@ -542,9 +687,9 @@
 		if (is_array($str))
 		{
 			$new_array = array();
-			foreach ($str as $key => $val)
+			foreach (array_keys($str) as $key)
 			{
-				$new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
+				$new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]);
 			}
 			return $new_array;
 		}
@@ -588,16 +733,16 @@
 	/**
 	 * Clean Keys
 	 *
-	 * This is a helper function. To prevent malicious users
+	 * Internal method that helps to prevent malicious users
 	 * from trying to exploit keys we make sure that keys are
 	 * only named with alpha-numeric text and a few other items.
 	 *
-	 * @param	string
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
 	protected function _clean_input_keys($str)
 	{
-		if ( ! preg_match('/^[a-z0-9:_\/-]+$/i', $str))
+		if ( ! preg_match('/^[a-z0-9:_\/|-]+$/i', $str))
 		{
 			set_status_header(503);
 			exit('Disallowed Key Characters.');
@@ -617,15 +762,12 @@
 	/**
 	 * Request Headers
 	 *
-	 * In Apache, you can simply call apache_request_headers(), however for
-	 * people running other webservers the function is undefined.
-	 *
-	 * @param	bool	XSS cleaning
+	 * @param	bool	$xss_clean	Whether to apply XSS filtering
 	 * @return	array
 	 */
 	public function request_headers($xss_clean = FALSE)
 	{
-		// Look at Apache go!
+		// In Apache, you can simply call apache_request_headers()
 		if (function_exists('apache_request_headers'))
 		{
 			$headers = apache_request_headers();
@@ -636,9 +778,9 @@
 
 			foreach ($_SERVER as $key => $val)
 			{
-				if (strpos($key, 'HTTP_') === 0)
+				if (sscanf($key, 'HTTP_%s', $header) === 1)
 				{
-					$headers[substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean);
+					$headers[$header] = $this->_fetch_from_array($_SERVER, $key, $xss_clean);
 				}
 			}
 		}
@@ -662,9 +804,9 @@
 	 *
 	 * Returns the value of a single member of the headers class member
 	 *
-	 * @param	string	array key for $this->headers
-	 * @param	bool	XSS Clean or not
-	 * @return	mixed	FALSE on failure, string on success
+	 * @param	string		$index		Header name
+	 * @param	bool		$xss_clean	Whether to apply XSS filtering
+	 * @return	string|bool	The requested header on success or FALSE on failure
 	 */
 	public function get_request_header($index, $xss_clean = FALSE)
 	{
@@ -686,9 +828,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Is ajax Request?
+	 * Is AJAX request?
 	 *
-	 * Test to see if a request contains the HTTP_X_REQUESTED_WITH header
+	 * Test to see if a request contains the HTTP_X_REQUESTED_WITH header.
 	 *
 	 * @return 	bool
 	 */
@@ -700,9 +842,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Is cli Request?
+	 * Is CLI request?
 	 *
-	 * Test to see if a request was made from the command line
+	 * Test to see if a request was made from the command line.
 	 *
 	 * @return 	bool
 	 */
@@ -716,10 +858,11 @@
 	/**
 	 * Get Request Method
 	 *
-	 * Return the Request Method
+	 * Return the request method
 	 *
-	 * @param	bool	uppercase or lowercase
-	 * @return 	bool
+	 * @param	bool	$upper	Whether to return in upper or lower case
+	 *				(default: FALSE)
+	 * @return 	string
 	 */
 	public function method($upper = FALSE)
 	{
diff --git a/system/core/Lang.php b/system/core/Lang.php
index 3001f1b..9e6f437 100644
--- a/system/core/Lang.php
+++ b/system/core/Lang.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Language Class
@@ -39,19 +40,19 @@
 	/**
 	 * List of translations
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $language =	array();
 
 	/**
 	 * List of loaded language files
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $is_loaded =	array();
 
 	/**
-	 * Initialize language class
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -65,12 +66,13 @@
 	/**
 	 * Load a language file
 	 *
-	 * @param	mixed	the name of the language file to be loaded
-	 * @param	string	the language (english, etc.)
-	 * @param	bool	return loaded array of translations
-	 * @param 	bool	add suffix to $langfile
-	 * @param 	string	alternative path to look for language file
-	 * @return	mixed
+	 * @param	mixed	$langfile	Language file name
+	 * @param	string	$idiom		Language name (english, etc.)
+	 * @param	bool	$return		Whether to return the loaded array of translations
+	 * @param 	bool	$add_suffix	Whether to add suffix to $langfile
+	 * @param 	string	$alt_path	Alternative path to look for the language file
+	 *
+	 * @return	void|string[]	Array containing translations, if $return is set to TRUE
 	 */
 	public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '')
 	{
@@ -83,10 +85,10 @@
 
 		$langfile .= '.php';
 
-		if ($idiom === '')
+		if (empty($idiom) OR ! ctype_alpha($idiom))
 		{
 			$config =& get_config();
-			$idiom = ( ! empty($config['language'])) ? $config['language'] : 'english';
+			$idiom = empty($config['language']) ? 'english' : $config['language'];
 		}
 
 		if ($return === FALSE && isset($this->is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom)
@@ -94,31 +96,41 @@
 			return;
 		}
 
-		// Determine where the language file is and load it
-		if ($alt_path !== '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile))
+		// Load the base file, so any others found can override it
+		$basepath = BASEPATH.'language/'.$idiom.'/'.$langfile;
+		if (($found = file_exists($basepath)) === TRUE)
 		{
-			include($alt_path.'language/'.$idiom.'/'.$langfile);
+			include($basepath);
+		}
+
+		// Do we have an alternative path to look in?
+		if ($alt_path !== '')
+		{
+			$alt_path .= 'language/'.$idiom.'/'.$langfile;
+			if (file_exists($alt_path))
+			{
+				include($alt_path);
+				$found = TRUE;
+			}
 		}
 		else
 		{
-			$found = FALSE;
-
 			foreach (get_instance()->load->get_package_paths(TRUE) as $package_path)
 			{
-				if (file_exists($package_path.'language/'.$idiom.'/'.$langfile))
+				$package_path .= 'language/'.$idiom.'/'.$langfile;
+				if ($basepath !== $package_path && file_exists($package_path))
 				{
-					include($package_path.'language/'.$idiom.'/'.$langfile);
+					include($package_path);
 					$found = TRUE;
 					break;
 				}
 			}
-
-			if ($found !== TRUE)
-			{
-				show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile);
-			}
 		}
 
+		if ($found !== TRUE)
+		{
+			show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile);
+		}
 
 		if ( ! isset($lang) OR ! is_array($lang))
 		{
@@ -146,17 +158,20 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a single line of text from the language array
+	 * Language line
 	 *
-	 * @param	string	$line	the language line
-	 * @return	string
+	 * Fetches a single line of text from the language array
+	 *
+	 * @param	string	$line		Language line key
+	 * @param	bool	$log_errors	Whether to log an error message if the line is not found
+	 * @return	string	Translation
 	 */
-	public function line($line = '')
+	public function line($line = '', $log_errors = TRUE)
 	{
 		$value = ($line === '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line];
 
 		// Because killer robots like unicorns!
-		if ($value === FALSE)
+		if ($value === FALSE && $log_errors === TRUE)
 		{
 			log_message('error', 'Could not find the language line "'.$line.'"');
 		}
diff --git a/system/core/Loader.php b/system/core/Loader.php
index d51ee0b..6515074 100644
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,11 +24,12 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Loader Class
  *
- * Loads views and files
+ * Loads framework components.
  *
  * @package		CodeIgniter
  * @subpackage	Libraries
@@ -42,84 +43,84 @@
 	/**
 	 * Nesting level of the output buffering mechanism
 	 *
-	 * @var int
+	 * @var	int
 	 */
 	protected $_ci_ob_level;
 
 	/**
 	 * List of paths to load views from
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_view_paths =	array();
 
 	/**
 	 * List of paths to load libraries from
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_library_paths =	array();
 
 	/**
 	 * List of paths to load models from
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_model_paths =	array();
 
 	/**
 	 * List of paths to load helpers from
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_helper_paths =	array();
 
 	/**
 	 * List of loaded base classes
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_base_classes =	array(); // Set by the controller class
 
 	/**
 	 * List of cached variables
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_cached_vars =	array();
 
 	/**
 	 * List of loaded classes
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_classes =	array();
 
 	/**
 	 * List of loaded files
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_loaded_files =	array();
 
 	/**
 	 * List of loaded models
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_models =	array();
 
 	/**
 	 * List of loaded helpers
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_helpers =	array();
 
 	/**
 	 * List of class name mappings
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_ci_varmap =	array(
 		'unit_test' => 'unit',
@@ -127,9 +128,9 @@
 	);
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * Sets the path to the view files and gets the initial output buffering level
+	 * Sets component load paths, gets the initial output buffering level.
 	 *
 	 * @return	void
 	 */
@@ -147,21 +148,18 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Initialize the Loader
+	 * Initializer
 	 *
-	 * This method is called once in CI_Controller.
-	 *
-	 * @return 	object
+	 * @todo	Figure out a way to move this to the constructor
+	 *		without breaking *package_path*() methods.
+	 * @uses	CI_Loader::_ci_autoloader()
+	 * @used-by	CI_Controller::__construct()
+	 * @return	void
 	 */
 	public function initialize()
 	{
-		$this->_ci_classes = array();
-		$this->_ci_loaded_files = array();
-		$this->_ci_models = array();
 		$this->_base_classes =& is_loaded();
-
 		$this->_ci_autoloader();
-		return $this;
 	}
 
 	// --------------------------------------------------------------------
@@ -169,14 +167,12 @@
 	/**
 	 * Is Loaded
 	 *
-	 * A utility function to test if a class is in the self::$_ci_classes array.
-	 * This function returns the object name if the class tested for is loaded,
-	 * and returns FALSE if it isn't.
+	 * A utility method to test if a class is in the self::$_ci_classes array.
 	 *
-	 * It is mainly used in the form_helper -> _get_validation_object()
+	 * @used-by	Mainly used by Form Helper function _get_validation_object().
 	 *
-	 * @param 	string	class being checked for
-	 * @return 	mixed	class object name on the CI SuperObject or FALSE
+	 * @param 	string		$class	Class name to check for
+	 * @return 	string|bool	Class object name if loaded or FALSE
 	 */
 	public function is_loaded($class)
 	{
@@ -186,14 +182,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Class Loader
+	 * Library Loader
 	 *
-	 * This function lets users load and instantiate classes.
-	 * It is designed to be called from a user's app controllers.
+	 * Loads and instantiates libraries.
+	 * Designed to be called from application controllers.
 	 *
-	 * @param	string	the name of the class
-	 * @param	mixed	the optional parameters
-	 * @param	string	an optional object name
+	 * @param	string	$library	Library name
+	 * @param	array	$params		Optional parameters to pass to the library class constructor
+	 * @param	string	$object_name	An optional object name to assign to
 	 * @return	void
 	 */
 	public function library($library = '', $params = NULL, $object_name = NULL)
@@ -210,7 +206,7 @@
 
 		if ($library === '' OR isset($this->_base_classes[$library]))
 		{
-			return FALSE;
+			return;
 		}
 
 		if ( ! is_null($params) && ! is_array($params))
@@ -226,26 +222,25 @@
 	/**
 	 * Model Loader
 	 *
-	 * This function lets users load and instantiate models.
+	 * Loads and instantiates libraries.
 	 *
-	 * @param	string	the name of the class
-	 * @param	string	name for the model
-	 * @param	bool	database connection
+	 * @param	string	$model		Model name
+	 * @param	string	$name		An optional object name to assign to
+	 * @param	bool	$db_conn	An optional database connection configuration to initialize
 	 * @return	void
 	 */
 	public function model($model, $name = '', $db_conn = FALSE)
 	{
-		if (is_array($model))
+		if (empty($model))
 		{
-			foreach ($model as $babe)
-			{
-				$this->model($babe);
-			}
 			return;
 		}
-
-		if ($model === '')
+		elseif (is_array($model))
 		{
+			foreach ($model as $class)
+			{
+				$this->model($class);
+			}
 			return;
 		}
 
@@ -318,10 +313,13 @@
 	/**
 	 * Database Loader
 	 *
-	 * @param	string	the DB credentials
-	 * @param	bool	whether to return the DB object
-	 * @param	bool	whether to enable query builder (this allows us to override the config setting)
-	 * @return	object
+	 * @param	mixed	$params		Database configuration options
+	 * @param	bool	$return 	Whether to return the database object
+	 * @param	bool	$query_builder	Whether to enable Query Builder
+	 *					(overrides the configuration setting)
+	 *
+	 * @return	void|object|bool	Database object if $return is set to TRUE,
+	 *					FALSE on failure, void in any other case
 	 */
 	public function database($params = '', $return = FALSE, $query_builder = NULL)
 	{
@@ -329,7 +327,7 @@
 		$CI =& get_instance();
 
 		// Do we even need to load the database class?
-		if (class_exists('CI_DB') && $return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db))
+		if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id))
 		{
 			return FALSE;
 		}
@@ -352,28 +350,32 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Load the Utilities Class
+	 * Load the Database Utilities Class
 	 *
-	 * @return	string
+	 * @param	object	$db	Database object
+	 * @param	bool	$return	Whether to return the DB Forge class object or not
+	 * @return	void|object
 	 */
-	public function dbutil()
+	public function dbutil($db = NULL, $return = FALSE)
 	{
-		if ( ! class_exists('CI_DB'))
-		{
-			$this->database();
-		}
-
 		$CI =& get_instance();
 
-		// for backwards compatibility, load dbforge so we can extend dbutils off it
-		// this use is deprecated and strongly discouraged
-		$CI->load->dbforge();
+		if ( ! is_object($db) OR ! ($db instanceof CI_DB))
+		{
+			class_exists('CI_DB', FALSE) OR $this->database();
+			$db =& $CI->db;
+		}
 
 		require_once(BASEPATH.'database/DB_utility.php');
-		require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php');
-		$class = 'CI_DB_'.$CI->db->dbdriver.'_utility';
+		require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_utility.php');
+		$class = 'CI_DB_'.$db->dbdriver.'_utility';
 
-		$CI->dbutil = new $class();
+		if ($return === TRUE)
+		{
+			return new $class($db);
+		}
+
+		$CI->dbutil = new $class($db);
 	}
 
 	// --------------------------------------------------------------------
@@ -381,40 +383,56 @@
 	/**
 	 * Load the Database Forge Class
 	 *
-	 * @return	string
+	 * @param	object	$db	Database object
+	 * @param	bool	$return	Whether to return the DB Forge class object or not
+	 * @return	void|object
 	 */
-	public function dbforge()
+	public function dbforge($db = NULL, $return = FALSE)
 	{
-		if ( ! class_exists('CI_DB'))
+		$CI =& get_instance();
+		if ( ! is_object($db) OR ! ($db instanceof CI_DB))
 		{
-			$this->database();
+			class_exists('CI_DB', FALSE) OR $this->database();
+			$db =& $CI->db;
 		}
 
-		$CI =& get_instance();
-
 		require_once(BASEPATH.'database/DB_forge.php');
-		require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php');
-		$class = 'CI_DB_'.$CI->db->dbdriver.'_forge';
+		require_once(BASEPATH.'database/drivers/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php');
 
-		$CI->dbforge = new $class();
+		if ( ! empty($db->subdriver))
+		{
+			$driver_path = BASEPATH.'database/drivers/'.$db->dbdriver.'/subdrivers/'.$db->dbdriver.'_'.$db->subdriver.'_forge.php';
+			if (file_exists($driver_path))
+			{
+				require_once($driver_path);
+				$class = 'CI_DB_'.$db->dbdriver.'_'.$db->subdriver.'_forge';
+			}
+		}
+		else
+		{
+			$class = 'CI_DB_'.$db->dbdriver.'_forge';
+		}
+
+		if ($return === TRUE)
+		{
+			return new $class($db);
+		}
+
+		$CI->dbforge = new $class($db);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Load View
+	 * View Loader
 	 *
-	 * This function is used to load a "view" file. It has three parameters:
+	 * Loads "view" files.
 	 *
-	 * 1. The name of the "view" file to be included.
-	 * 2. An associative array of data to be extracted for use in the view.
-	 * 3. TRUE/FALSE - whether to return the data or load it. In
-	 *    some cases it's advantageous to be able to return data so that
-	 *    a developer can process it in some way.
-	 *
-	 * @param	string
-	 * @param	array
-	 * @param	bool
+	 * @param	string	$view	View name
+	 * @param	array	$vars	An associative array of data
+	 *				to be extracted for use in the view
+	 * @param	bool	$return	Whether to return the view output
+	 *				or leave it to the Output class
 	 * @return	void
 	 */
 	public function view($view, $vars = array(), $return = FALSE)
@@ -425,13 +443,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Load File
+	 * Generic File Loader
 	 *
-	 * This is a generic file loader
-	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	string
+	 * @param	string	$path	File path
+	 * @param	bool	$return	Whether to return the file output
+	 * @return	void|string
 	 */
 	public function file($path, $return = FALSE)
 	{
@@ -446,8 +462,10 @@
 	 * Once variables are set they become available within
 	 * the controller class and its "view" files.
 	 *
-	 * @param	array
-	 * @param 	string
+	 * @param	array|object|string	$vars
+	 *					An associative array or object containing values
+	 *					to be set, or a value's name if string
+	 * @param 	string	$val	Value to set, only used if $vars is a string
 	 * @return	void
 	 */
 	public function vars($vars = array(), $val = '')
@@ -475,8 +493,8 @@
 	 *
 	 * Check if a variable is set and retrieve it.
 	 *
-	 * @param	array
-	 * @return	void
+	 * @param	string	$key	Variable name
+	 * @return	mixed	The variable or NULL if not found
 	 */
 	public function get_var($key)
 	{
@@ -488,7 +506,7 @@
 	/**
 	 * Get Variables
 	 *
-	 * Retrieve all loaded variables
+	 * Retrieves all loaded variables.
 	 *
 	 * @return	array
 	 */
@@ -500,11 +518,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Load Helper
+	 * Helper Loader
 	 *
-	 * This function loads the specified helper file.
-	 *
-	 * @param	mixed
+	 * @param	string|string[]	$helpers	Helper name(s)
 	 * @return	void
 	 */
 	public function helper($helpers = array())
@@ -516,27 +532,34 @@
 				continue;
 			}
 
-			$ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php';
-
 			// Is this a helper extension request?
-			if (file_exists($ext_helper))
+			$ext_helper = config_item('subclass_prefix').$helper;
+			$ext_loaded = FALSE;
+			foreach ($this->_ci_helper_paths as $path)
+			{
+				if (file_exists($path.'helpers/'.$ext_helper.'.php'))
+				{
+					include_once($path.'helpers/'.$ext_helper.'.php');
+					$ext_loaded = TRUE;
+				}
+			}
+
+			// If we have loaded extensions - check if the base one is here
+			if ($ext_loaded === TRUE)
 			{
 				$base_helper = BASEPATH.'helpers/'.$helper.'.php';
-
 				if ( ! file_exists($base_helper))
 				{
 					show_error('Unable to load the requested file: helpers/'.$helper.'.php');
 				}
 
-				include_once($ext_helper);
 				include_once($base_helper);
-
 				$this->_ci_helpers[$helper] = TRUE;
 				log_message('debug', 'Helper loaded: '.$helper);
 				continue;
 			}
 
-			// Try to load the helper
+			// No extensions found ... try loading regular helpers and/or overrides
 			foreach ($this->_ci_helper_paths as $path)
 			{
 				if (file_exists($path.'helpers/'.$helper.'.php'))
@@ -562,10 +585,11 @@
 	/**
 	 * Load Helpers
 	 *
-	 * This is simply an alias to the above function in case the
-	 * user has written the plural form of this function.
+	 * An alias for the helper() method in case the developer has
+	 * written the plural form of it.
 	 *
-	 * @param	array
+	 * @uses	CI_Loader::helper()
+	 * @param	string|string[]	$helpers	Helper name(s)
 	 * @return	void
 	 */
 	public function helpers($helpers = array())
@@ -576,22 +600,21 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Loads a language file
+	 * Language Loader
 	 *
-	 * @param	array
-	 * @param	string
+	 * Loads language files.
+	 *
+	 * @param	string|string[]	$files	List of language file names to load
+	 * @param	string		Language name
 	 * @return	void
 	 */
-	public function language($file = array(), $lang = '')
+	public function language($files = array(), $lang = '')
 	{
 		$CI =& get_instance();
 
-		if ( ! is_array($file))
-		{
-			$file = array($file);
-		}
+		is_array($files) OR $files = array($files);
 
-		foreach ($file as $langfile)
+		foreach ($files as $langfile)
 		{
 			$CI->lang->load($langfile, $lang);
 		}
@@ -600,30 +623,35 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Loads a config file
+	 * Config Loader
 	 *
-	 * @param	string
-	 * @param	bool
-	 * @param 	bool
-	 * @return	void
+	 * Loads a config file (an alias for CI_Config::load()).
+	 *
+	 * @uses	CI_Config::load()
+	 * @param	string	$file			Configuration file name
+	 * @param	bool	$use_sections		Whether configuration values should be loaded into their own section
+	 * @param	bool	$fail_gracefully	Whether to just return FALSE or display an error message
+	 * @return	bool	TRUE if the file was loaded correctly or FALSE on failure
 	 */
 	public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
 	{
 		$CI =& get_instance();
-		$CI->config->load($file, $use_sections, $fail_gracefully);
+		return $CI->config->load($file, $use_sections, $fail_gracefully);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Driver
+	 * Driver Loader
 	 *
-	 * Loads a driver library
+	 * Loads a driver library.
 	 *
-	 * @param	mixed	the name of the class or array of classes
-	 * @param	mixed	the optional parameters
-	 * @param	string	an optional object name
-	 * @return	void
+	 * @param	string|string[]	$library	Driver name(s)
+	 * @param	array		$params		Optional parameters to pass to the driver
+	 * @param	string		$object_name	An optional object name to assign to
+	 *
+	 * @return	void|object|bool	Object or FALSE on failure if $library is a string
+	 *					and $object_name is set. void otherwise.
 	 */
 	public function driver($library = '', $params = NULL, $object_name = NULL)
 	{
@@ -633,13 +661,7 @@
 			{
 				$this->driver($driver);
 			}
-			return FALSE;
-		}
-
-		if ( ! class_exists('CI_Driver_Library'))
-		{
-			// we aren't instantiating an object here, that'll be done by the Library itself
-			require BASEPATH.'libraries/Driver.php';
+			return;
 		}
 
 		if ($library === '')
@@ -647,6 +669,12 @@
 			return FALSE;
 		}
 
+		if ( ! class_exists('CI_Driver_Library'))
+		{
+			// We aren't instantiating an object here, just making the base class available
+			require BASEPATH.'libraries/Driver.php';
+		}
+
 		// We can save the loader some time since Drivers will *always* be in a subfolder,
 		// and typically identically named to the library
 		if ( ! strpos($library, '/'))
@@ -662,10 +690,16 @@
 	/**
 	 * Add Package Path
 	 *
-	 * Prepends a parent path to the library, model, helper, and config path arrays
+	 * Prepends a parent path to the library, model, helper and config
+	 * path arrays.
 	 *
-	 * @param	string
-	 * @param 	bool
+	 * @see	CI_Loader::$_ci_library_paths
+	 * @see	CI_Loader::$_ci_model_paths
+	 * @see CI_Loader::$_ci_helper_paths
+	 * @see CI_Config::$_config_paths
+	 *
+	 * @param	string	$path		Path to add
+	 * @param 	bool	$view_cascade	(default: TRUE)
 	 * @return	void
 	 */
 	public function add_package_path($path, $view_cascade = TRUE)
@@ -688,14 +722,14 @@
 	/**
 	 * Get Package Paths
 	 *
-	 * Return a list of all package paths, by default it will ignore BASEPATH.
+	 * Return a list of all package paths.
 	 *
-	 * @param	string
-	 * @return	void
+	 * @param	bool	$include_base	Whether to include BASEPATH (default: TRUE)
+	 * @return	array
 	 */
 	public function get_package_paths($include_base = FALSE)
 	{
-		return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths;
+		return ($include_base === TRUE) ? $this->_ci_library_paths : $this->_ci_model_paths;
 	}
 
 	// --------------------------------------------------------------------
@@ -703,14 +737,14 @@
 	/**
 	 * Remove Package Path
 	 *
-	 * Remove a path from the library, model, and helper path arrays if it exists
-	 * If no path is provided, the most recently added path is removed.
+	 * Remove a path from the library, model, helper and/or config
+	 * path arrays if it exists. If no path is provided, the most recently
+	 * added path will be removed removed.
 	 *
-	 * @param	string
-	 * @param 	bool
+	 * @param	string	$path	Path to remove
 	 * @return	void
 	 */
-	public function remove_package_path($path = '', $remove_config_path = TRUE)
+	public function remove_package_path($path = '')
 	{
 		$config =& $this->_ci_get_component('config');
 
@@ -755,13 +789,16 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Loader
+	 * Internal CI Data Loader
 	 *
-	 * This function is used to load views and files.
+	 * Used to load views and files.
+	 *
 	 * Variables are prefixed with _ci_ to avoid symbol collision with
-	 * variables made available to view files
+	 * variables made available to view files.
 	 *
-	 * @param	array
+	 * @used-by	CI_Loader::view()
+	 * @used-by	CI_Loader::file()
+	 * @param	array	$_ci_data	Data to load
 	 * @return	void
 	 */
 	protected function _ci_load($_ci_data)
@@ -785,11 +822,11 @@
 			$_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION);
 			$_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view;
 
-			foreach ($this->_ci_view_paths as $view_file => $cascade)
+			foreach ($this->_ci_view_paths as $_ci_view_file => $cascade)
 			{
-				if (file_exists($view_file.$_ci_file))
+				if (file_exists($_ci_view_file.$_ci_file))
 				{
-					$_ci_path = $view_file.$_ci_file;
+					$_ci_path = $_ci_view_file.$_ci_file;
 					$file_exists = TRUE;
 					break;
 				}
@@ -837,17 +874,19 @@
 		 * We buffer the output for two reasons:
 		 * 1. Speed. You get a significant speed boost.
 		 * 2. So that the final rendered template can be post-processed by
-		 *    the output class. Why do we need post processing? For one thing,
-		 *    in order to show the elapsed page load time. Unless we can
-		 *    intercept the content right before it's sent to the browser and
-		 *    then stop the timer it won't be accurate.
+		 *	the output class. Why do we need post processing? For one thing,
+		 *	in order to show the elapsed page load time. Unless we can
+		 *	intercept the content right before it's sent to the browser and
+		 *	then stop the timer it won't be accurate.
 		 */
 		ob_start();
 
 		// If the PHP installation does not support short tags we'll
 		// do a little string replacement, changing the short tags
 		// to standard PHP echo statements.
-		if ( ! is_php('5.4') && (bool) @ini_get('short_open_tag') === FALSE && config_item('rewrite_short_tags') === TRUE)
+		if ( ! is_php('5.4') && (bool) @ini_get('short_open_tag') === FALSE
+			&& config_item('rewrite_short_tags') === TRUE && function_usable('eval')
+		)
 		{
 			echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('<?=', '<?php echo ', file_get_contents($_ci_path))));
 		}
@@ -889,13 +928,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Load class
+	 * Internal CI Class Loader
 	 *
-	 * This function loads the requested class.
+	 * @used-by	CI_Loader::library()
+	 * @uses	CI_Loader::_ci_init_class()
 	 *
-	 * @param	string	the item that is being loaded
-	 * @param	mixed	any additional parameters
-	 * @param	string	an optional object name
+	 * @param	string	$class		Class name to load
+	 * @param	mixed	$params		Optional parameters to pass to the class constructor
+	 * @param	string	$object_name	Optional object name to assign to
 	 * @return	void
 	 */
 	protected function _ci_load_class($class, $params = NULL, $object_name = NULL)
@@ -996,14 +1036,19 @@
 				$this->_ci_loaded_files[] = $filepath;
 				return $this->_ci_init_class($class, '', $params, $object_name);
 			}
-
 		} // END FOREACH
 
 		// One last attempt. Maybe the library is in a subdirectory, but it wasn't specified?
 		if ($subdir === '')
 		{
 			$path = strtolower($class).'/'.$class;
-			return $this->_ci_load_class($path, $params);
+			return $this->_ci_load_class($path, $params, $object_name);
+		}
+		elseif (ucfirst($subdir) != $subdir)
+		{
+			// Lowercase subdir failed - retry capitalized
+			$path = ucfirst($subdir).$class;
+			return $this->_ci_load_class($path, $params, $object_name);
 		}
 
 		// If we got this far we were unable to find the requested class.
@@ -1018,12 +1063,17 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Instantiates a class
+	 * Internal CI Class Instantiator
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	bool
-	 * @param	string	an optional object name
+	 * @used-by	CI_Loader::_ci_load_class()
+	 *
+	 * @param	string		$class		Class name
+	 * @param	string		$prefix		Class name prefix
+	 * @param	array|null|bool	$config		Optional configuration to pass to the class constructor:
+	 *						FALSE to skip;
+	 *						NULL to search in config paths;
+	 *						array containing configuration data
+	 * @param	string		$object_name	Optional object name to assign to
 	 * @return	void
 	 */
 	protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL)
@@ -1091,7 +1141,7 @@
 		if ( ! class_exists($name))
 		{
 			log_message('error', 'Non-existent class: '.$name);
-			show_error('Non-existent class: '.$class);
+			show_error('Non-existent class: '.$name);
 		}
 
 		// Set the variable name we will assign the class to
@@ -1125,11 +1175,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Autoloader
+	 * CI Autoloader
 	 *
-	 * The config/autoload.php file contains an array that permits sub-systems,
-	 * libraries, and helpers to be loaded automatically.
+	 * Loads component listed in the config/autoload.php file.
 	 *
+	 * @used-by	CI_Loader::initialize()
 	 * @return	void
 	 */
 	protected function _ci_autoloader()
@@ -1193,6 +1243,15 @@
 			}
 		}
 
+		// Autoload drivers
+		if (isset($autoload['drivers']))
+		{
+			foreach ($autoload['drivers'] as $item)
+			{
+				$this->driver($item);
+			}
+		}
+
 		// Autoload models
 		if (isset($autoload['model']))
 		{
@@ -1203,11 +1262,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Object to Array
+	 * CI Object to Array translator
 	 *
-	 * Takes an object as input and converts the class variables to array key/vals
+	 * Takes an object as input and converts the class variables to
+	 * an associative array with key/value pairs.
 	 *
-	 * @param	object
+	 * @param	object	$object	Object data to translate
 	 * @return	array
 	 */
 	protected function _ci_object_to_array($object)
@@ -1218,9 +1278,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Get a reference to a specific library or model
+	 * CI Component getter
 	 *
-	 * @param 	string
+	 * Get a reference to a specific library or model.
+	 *
+	 * @param 	string	$component	Component name
 	 * @return	bool
 	 */
 	protected function &_ci_get_component($component)
@@ -1234,10 +1296,11 @@
 	/**
 	 * Prep filename
 	 *
-	 * This function preps the name of various items to make loading them more reliable.
+	 * This function prepares filenames of various items to
+	 * make their loading more reliable.
 	 *
-	 * @param	mixed
-	 * @param 	string
+	 * @param	string|string[]	$filename	Filename(s)
+	 * @param 	string		$extension	Filename extension
 	 * @return	array
 	 */
 	protected function _ci_prep_filename($filename, $extension)
diff --git a/system/libraries/Log.php b/system/core/Log.php
similarity index 93%
rename from system/libraries/Log.php
rename to system/core/Log.php
index baac801..718f505 100644
--- a/system/libraries/Log.php
+++ b/system/core/Log.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Logging Class
@@ -83,7 +84,7 @@
 	 *
 	 * @var array
 	 */
-	protected $_levels		= array('ERROR' => 1, 'DEBUG' => 2,  'INFO' => 3, 'ALL' => 4);
+	protected $_levels		= array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4);
 
 	/**
 	 * Initialize Logging class
@@ -144,14 +145,13 @@
 			return FALSE;
 		}
 
-
 		$filepath = $this->_log_path.'log-'.date('Y-m-d').'.php';
 		$message  = '';
 
 		if ( ! file_exists($filepath))
 		{
 			$newfile = TRUE;
-			$message .= '<'."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?".">\n\n";
+			$message .= '<'."?php defined('BASEPATH') OR exit('No direct script access allowed'); ?".">\n\n";
 		}
 
 		if ( ! $fp = @fopen($filepath, FOPEN_WRITE_CREATE))
diff --git a/system/core/Model.php b/system/core/Model.php
index 9bc9f87..28fdfbb 100644
--- a/system/core/Model.php
+++ b/system/core/Model.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,9 +24,10 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
- * CodeIgniter Model Class
+ * Model Class
  *
  * @package		CodeIgniter
  * @subpackage	Libraries
@@ -37,7 +38,7 @@
 class CI_Model {
 
 	/**
-	 * Initialize CI_Model Class
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -46,13 +47,15 @@
 		log_message('debug', 'Model Class Initialized');
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * __get
+	 * __get magic
 	 *
 	 * Allows models to access CI's loaded classes using the same
 	 * syntax as controllers.
 	 *
-	 * @param	string
+	 * @param	string	$key
 	 */
 	public function __get($key)
 	{
diff --git a/system/core/Output.php b/system/core/Output.php
index 11f4840..2e239c7 100644
--- a/system/core/Output.php
+++ b/system/core/Output.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,11 +24,12 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Output Class
  *
- * Responsible for sending final output to browser
+ * Responsible for sending final output to the browser.
  *
  * @package		CodeIgniter
  * @subpackage	Libraries
@@ -39,70 +40,74 @@
 class CI_Output {
 
 	/**
-	 * Current output string
+	 * Final output string
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $final_output;
 
 	/**
 	 * Cache expiration time
 	 *
-	 * @var int
+	 * @var	int
 	 */
-	public $cache_expiration =	0;
+	public $cache_expiration = 0;
 
 	/**
 	 * List of server headers
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $headers =	array();
 
 	/**
 	 * List of mime types
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $mimes =		array();
 
 	/**
 	 * Mime-type for the current page
 	 *
-	 * @var string
+	 * @var	string
 	 */
-	protected $mime_type		= 'text/html';
+	protected $mime_type	= 'text/html';
 
 	/**
-	 * Determines whether profiler is enabled
+	 * Enable Profiler flag
 	 *
-	 * @var book
+	 * @var	bool
 	 */
-	public $enable_profiler =	FALSE;
+	public $enable_profiler = FALSE;
 
 	/**
-	 * Determines if output compression is enabled
+	 * zLib output compression flag
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
 	protected $_zlib_oc =		FALSE;
 
 	/**
 	 * List of profiler sections
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_profiler_sections =	array();
 
 	/**
-	 * Whether or not to parse variables like {elapsed_time} and {memory_usage}
+	 * Parse markers flag
 	 *
-	 * @var bool
+	 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
+	 *
+	 * @var	bool
 	 */
 	public $parse_exec_vars =	TRUE;
 
 	/**
-	 * Set up Output class
+	 * Class constructor
+	 *
+	 * Determines whether zLib output compression will be used.
 	 *
 	 * @return	void
 	 */
@@ -121,7 +126,7 @@
 	/**
 	 * Get Output
 	 *
-	 * Returns the current output string
+	 * Returns the current output string.
 	 *
 	 * @return	string
 	 */
@@ -135,10 +140,10 @@
 	/**
 	 * Set Output
 	 *
-	 * Sets the output string
+	 * Sets the output string.
 	 *
-	 * @param	string
-	 * @return	void
+	 * @param	string	$output	Output data
+	 * @return	object	$this
 	 */
 	public function set_output($output)
 	{
@@ -151,14 +156,14 @@
 	/**
 	 * Append Output
 	 *
-	 * Appends data onto the output string
+	 * Appends data onto the output string.
 	 *
-	 * @param	string
-	 * @return	void
+	 * @param	string	$output	Data to append
+	 * @return	object	$this
 	 */
 	public function append_output($output)
 	{
-		if ($this->final_output == '')
+		if (empty($this->final_output))
 		{
 			$this->final_output = $output;
 		}
@@ -175,14 +180,14 @@
 	/**
 	 * Set Header
 	 *
-	 * Lets you set a server header which will be outputted with the final display.
+	 * Lets you set a server header which will be sent with the final output.
 	 *
-	 * Note: If a file is cached, headers will not be sent. We need to figure out
-	 * how to permit header data to be saved with the cache data...
+	 * Note: If a file is cached, headers will not be sent.
+	 * @todo	We need to figure out how to permit headers to be cached.
 	 *
-	 * @param	string
-	 * @param	bool
-	 * @return	void
+	 * @param	string	$header		Header
+	 * @param	bool	$replace	Whether to replace the old header value, if already set
+	 * @return	object	$this
 	 */
 	public function set_header($header, $replace = TRUE)
 	{
@@ -192,7 +197,7 @@
 		// We'll just skip content-length in those cases.
 		if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0)
 		{
-			return;
+			return $this;
 		}
 
 		$this->headers[] = array($header, $replace);
@@ -202,10 +207,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set Content Type Header
+	 * Set Content-Type Header
 	 *
-	 * @param	string	extension of the file we're outputting
-	 * @return	void
+	 * @param	string	$mime_type	Extension of the file we're outputting
+	 * @param	string	$charset	Character set (default: NULL)
+	 * @return	object	$this
 	 */
 	public function set_content_type($mime_type, $charset = NULL)
 	{
@@ -242,7 +248,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Get Current Content Type Header
+	 * Get Current Content-Type Header
 	 *
 	 * @return	string	'text/html', if not already set
 	 */
@@ -250,9 +256,9 @@
 	{
 		for ($i = 0, $c = count($this->headers); $i < $c; $i++)
 		{
-			if (preg_match('/^Content-Type:\s(.+)$/', $this->headers[$i][0], $matches))
+			if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1)
 			{
-				return $matches[1];
+				return $content_type;
 			}
 		}
 
@@ -262,12 +268,47 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set HTTP Status Header
-	 * moved to Common procedural functions in 1.7.2
+	 * Get Header
 	 *
-	 * @param	int	the status code
-	 * @param	string
-	 * @return	void
+	 * @param	string	$header_name
+	 * @return	string
+	 */
+	public function get_header($header)
+	{
+		// Combine headers already sent with our batched headers
+		$headers = array_merge(
+			// We only need [x][0] from our multi-dimensional array
+			array_map('array_shift', $this->headers),
+			headers_list()
+		);
+
+		if (empty($headers) OR empty($header))
+		{
+			return NULL;
+		}
+
+		for ($i = 0, $c = count($headers); $i < $c; $i++)
+		{
+			if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0)
+			{
+				return trim(substr($headers[$i], $l+1));
+			}
+		}
+
+		return NULL;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set HTTP Status Header
+	 *
+	 * As of version 1.7.2, this is an alias for common function
+	 * set_status_header().
+	 *
+	 * @param	int	$code	Status code (default: 200)
+	 * @param	string	$text	Optional message
+	 * @return	object	$this
 	 */
 	public function set_status_header($code = 200, $text = '')
 	{
@@ -280,8 +321,8 @@
 	/**
 	 * Enable/disable Profiler
 	 *
-	 * @param	bool
-	 * @return	void
+	 * @param	bool	$val	TRUE to enable or FALSE to disable
+	 * @return	object	$this
 	 */
 	public function enable_profiler($val = TRUE)
 	{
@@ -294,10 +335,11 @@
 	/**
 	 * Set Profiler Sections
 	 *
-	 * Allows override of default / config settings for Profiler section display
+	 * Allows override of default/config settings for
+	 * Profiler section display.
 	 *
-	 * @param	array
-	 * @return	void
+	 * @param	array	$sections	Profiler sections
+	 * @return	object	$this
 	 */
 	public function set_profiler_sections($sections)
 	{
@@ -320,8 +362,8 @@
 	/**
 	 * Set Cache
 	 *
-	 * @param	int
-	 * @return	void
+	 * @param	int	$time	Cache expiration time in seconds
+	 * @return	object	$this
 	 */
 	public function cache($time)
 	{
@@ -334,16 +376,16 @@
 	/**
 	 * Display Output
 	 *
-	 * All "view" data is automatically put into this variable by the controller class:
+	 * Processes sends the sends finalized output data to the browser along
+	 * with any server headers and profile data. It also stops benchmark
+	 * timers so the page rendering speed and memory usage can be shown.
 	 *
-	 * $this->final_output
+	 * Note: All "view" data is automatically put into $this->final_output
+	 *	 by controller class.
 	 *
-	 * This function sends the finalized output data to the browser along
-	 * with any server headers and profile data. It also stops the
-	 * benchmark timer so the page rendering speed and memory usage can be shown.
-	 *
-	 * @param	string
-	 * @return	mixed
+	 * @uses	CI_Output::$final_output
+	 * @param	string	$output	Output data override
+	 * @return	void
 	 */
 	public function _display($output = '')
 	{
@@ -374,10 +416,9 @@
 			$output = $this->minify($output, $this->mime_type);
 		}
 
-
 		// --------------------------------------------------------------------
 
-		// Do we need to write a cache file?  Only if the controller does not have its
+		// Do we need to write a cache file? Only if the controller does not have its
 		// own _output() method and we are not dealing with a cache file, which we
 		// can determine by the existence of the $CI object above
 		if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
@@ -430,7 +471,7 @@
 			echo $output;
 			log_message('debug', 'Final output sent to browser');
 			log_message('debug', 'Total execution time: '.$elapsed);
-			return TRUE;
+			return;
 		}
 
 		// --------------------------------------------------------------------
@@ -472,9 +513,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Write a Cache File
+	 * Write Cache
 	 *
-	 * @param	string
+	 * @param	string	$output	Output data to cache
 	 * @return	void
 	 */
 	public function _write_cache($output)
@@ -525,11 +566,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Update/serve a cached file
+	 * Update/serve cached output
 	 *
-	 * @param	object	config class
-	 * @param	object	uri class
-	 * @return	bool
+	 * @uses	CI_Config
+	 * @uses	CI_URI
+	 *
+	 * @param	object	&$CFG	CI_Config class instance
+	 * @param	object	&$URI	CI_URI class instance
+	 * @return	bool	TRUE on success or FALSE on failure
 	 */
 	public function _display_cache(&$CFG, &$URI)
 	{
@@ -552,13 +596,13 @@
 		fclose($fp);
 
 		// Strip out the embedded timestamp
-		if ( ! preg_match('/(\d+TS--->)/', $cache, $match))
+		if ( ! preg_match('/^(\d+)TS--->/', $cache, $match))
 		{
 			return FALSE;
 		}
 
 		$last_modified = filemtime($cache_path);
-		$expire = trim(str_replace('TS--->', '', $match[1]));
+		$expire = $match[1];
 
 		// Has the file expired?
 		if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
@@ -575,7 +619,7 @@
 		}
 
 		// Display the cache
-		$this->_display(str_replace($match[0], '', $cache));
+		$this->_display(substr($cache, strlen($match[0])));
 		log_message('debug', 'Cache file is current. Sending it to browser.');
 		return TRUE;
 	}
@@ -583,11 +627,52 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Delete cache
+	 *
+	 * @param	string	$uri	URI string
+	 * @return	bool
+	 */
+	public function delete_cache($uri = '')
+	{
+		$CI =& get_instance();
+		$cache_path = $CI->config->item('cache_path');
+		if ($cache_path === '')
+		{
+			$cache_path = APPPATH.'cache/';
+		}
+
+		if ( ! is_dir($cache_path))
+		{
+			log_message('error', 'Unable to find cache path: '.$cache_path);
+			return FALSE;
+		}
+
+		if (empty($uri))
+		{
+			$uri = $CI->uri->uri_string();
+		}
+
+		$cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri);
+
+		if ( ! @unlink($cache_path))
+		{
+			log_message('error', 'Unable to delete cache file for '.$uri);
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set Cache Header
+	 *
 	 * Set the HTTP headers to match the server-side file cache settings
 	 * in order to reduce bandwidth.
 	 *
-	 * @param	int	timestamp of when the page was last modified
-	 * @param	int	timestamp of when should the requested page expire from cache
+	 * @param	int	$last_modified	Timestamp of when the page was last modified
+	 * @param	int	$expiration	Timestamp of when should the requested page expire from cache
 	 * @return	void
 	 */
 	public function set_cache_header($last_modified, $expiration)
@@ -611,11 +696,13 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Reduce excessive size of HTML content.
+	 * Minify
 	 *
-	 * @param	string
-	 * @param	string
-	 * @return	string
+	 * Reduce excessive size of HTML/CSS/JavaScript content.
+	 *
+	 * @param	string	$output	Output to minify
+	 * @param	string	$type	Output content MIME type
+	 * @return	string	Minified output
 	 */
 	public function minify($output, $type = 'text/html')
 	{
diff --git a/system/core/Router.php b/system/core/Router.php
index 5bc0530..76772a0 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Router Class
@@ -39,56 +40,49 @@
 class CI_Router {
 
 	/**
-	 * Config class
+	 * CI_Config class object
 	 *
-	 * @var object
+	 * @var	object
 	 */
 	public $config;
 
 	/**
 	 * List of routes
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $routes =	array();
 
 	/**
-	 * List of error routes
-	 *
-	 * @var array
-	 */
-	public $error_routes =	array();
-
-	/**
 	 * Current class name
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $class =		'';
 
 	/**
 	 * Current method name
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $method =	'index';
 
 	/**
 	 * Sub-directory that contains the requested controller class
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $directory =	'';
 
 	/**
 	 * Default controller (and method if specific)
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $default_controller;
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
 	 * Runs the route mapping function.
 	 *
@@ -104,9 +98,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the route mapping
+	 * Set route mapping
 	 *
-	 * This function determines what should be served based on the URI request,
+	 * Determines what should be served based on the URI request,
 	 * as well as any "routes" that have been set in the routing config file.
 	 *
 	 * @return	void
@@ -117,21 +111,21 @@
 		// since URI segments are more search-engine friendly, but they can optionally be used.
 		// If this feature is enabled, we will gather the directory/class/method a little differently
 		$segments = array();
-		if ($this->config->item('enable_query_strings') === TRUE && isset($_GET[$this->config->item('controller_trigger')]))
+		if ($this->config->item('enable_query_strings') === TRUE
+			&& ! empty($_GET[$this->config->item('controller_trigger')])
+			&& is_string($_GET[$this->config->item('controller_trigger')])
+		)
 		{
-			if (isset($_GET[$this->config->item('directory_trigger')]))
+			if (isset($_GET[$this->config->item('directory_trigger')]) && is_string($_GET[$this->config->item('directory_trigger')]))
 			{
 				$this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
 				$segments[] = $this->fetch_directory();
 			}
 
-			if (isset($_GET[$this->config->item('controller_trigger')]))
-			{
-				$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
-				$segments[] = $this->fetch_class();
-			}
+			$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
+			$segments[] = $this->fetch_class();
 
-			if (isset($_GET[$this->config->item('function_trigger')]))
+			if ( ! empty($_GET[$this->config->item('function_trigger')]) && is_string($_GET[$this->config->item('function_trigger')]))
 			{
 				$this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
 				$segments[] = $this->fetch_method();
@@ -148,12 +142,12 @@
 			include(APPPATH.'config/routes.php');
 		}
 
-		$this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
+		$this->routes = (empty($route) OR ! is_array($route)) ? array() : $route;
 		unset($route);
 
 		// Set the default controller so we can display it in the event
 		// the URI doesn't correlated to a valid controller.
-		$this->default_controller = empty($this->routes['default_controller']) ? FALSE : strtolower($this->routes['default_controller']);
+		$this->default_controller = empty($this->routes['default_controller']) ? FALSE : $this->routes['default_controller'];
 
 		// Were there any query string segments? If so, we'll validate them and bail out since we're done.
 		if (count($segments) > 0)
@@ -179,30 +173,26 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the default controller
+	 * Set default controller
 	 *
 	 * @return	void
 	 */
 	protected function _set_default_controller()
 	{
-		if ($this->default_controller === FALSE)
+		if (empty($this->default_controller))
 		{
 			show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');
 		}
+
 		// Is the method being specified?
-		if (strpos($this->default_controller, '/') !== FALSE)
+		if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)
 		{
-			$x = explode('/', $this->default_controller);
-			$this->set_class($x[0]);
-			$this->set_method($x[1]);
-			$this->_set_request($x);
+			$method = 'index';
 		}
-		else
-		{
-			$this->set_class($this->default_controller);
-			$this->set_method('index');
-			$this->_set_request(array($this->default_controller, 'index'));
-		}
+
+		$this->set_class($class);
+		$this->set_method($method);
+		$this->_set_request(array($class, $method));
 
 		// re-index the routed segments array so it starts with 1 rather than 0
 		$this->uri->_reindex_segments();
@@ -213,12 +203,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the Route
+	 * Set request route
 	 *
-	 * This function takes an array of URI segments as
-	 * input, and sets the current class/method
+	 * Takes an array of URI segments as input and sets the class/method
+	 * to be called.
 	 *
-	 * @param	array
+	 * @param	array	$segments	URI segments
 	 * @return	void
 	 */
 	protected function _set_request($segments = array())
@@ -232,17 +222,8 @@
 
 		$this->set_class($segments[0]);
 
-		if (isset($segments[1]))
-		{
-			// A standard method request
-			$this->set_method($segments[1]);
-		}
-		else
-		{
-			// This lets the "routed" segment array identify that the default
-			// index method is being used.
-			$segments[1] = 'index';
-		}
+		isset($segments[1]) OR $segments[1] = 'index';
+		$this->set_method($segments[1]);
 
 		// Update our "routed" segment array to contain the segments.
 		// Note: If there is no custom routing, this array will be
@@ -253,11 +234,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Validates the supplied segments.
-	 * Attempts to determine the path to the controller.
+	 * Validate request
 	 *
-	 * @param	array
-	 * @return	array
+	 * Attempts validate the URI request and determine the controller path.
+	 *
+	 * @param	array	$segments	URI segments
+	 * @return	array	URI segments
 	 */
 	protected function _validate_request($segments)
 	{
@@ -266,9 +248,13 @@
 			return $segments;
 		}
 
+		$temp = str_replace('-', '_', $segments[0]);
+
 		// Does the requested controller exist in the root folder?
-		if (file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
+		if (file_exists(APPPATH.'controllers/'.$temp.'.php'))
 		{
+			$segments[0] = $temp;
+			empty($segments[1]) OR $segments[1] = str_replace('-', '_', $segments[1]);
 			return $segments;
 		}
 
@@ -276,22 +262,19 @@
 		if (is_dir(APPPATH.'controllers/'.$segments[0]))
 		{
 			// Set the directory and remove it from the segment array
-			$this->set_directory($segments[0]);
-			$segments = array_slice($segments, 1);
-
+			$this->set_directory(array_shift($segments));
 			if (count($segments) > 0)
 			{
+				$segments[0] = str_replace('-', '_', $segments[0]);
+				empty($segments[1]) OR $segments[1] = str_replace('-', '_', $segments[1]);
+
 				// Does the requested controller exist in the sub-folder?
 				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
 				{
 					if ( ! empty($this->routes['404_override']))
 					{
-						$x = explode('/', $this->routes['404_override']);
-						$this->set_directory('');
-						$this->set_class($x[0]);
-						$this->set_method(isset($x[1]) ? $x[1] : 'index');
-
-						return $x;
+						$this->directory = '';
+						return explode('/', $this->routes['404_override'], 2);
 					}
 					else
 					{
@@ -302,40 +285,26 @@
 			else
 			{
 				// Is the method being specified in the route?
-				if (strpos($this->default_controller, '/') !== FALSE)
-				{
-					$x = explode('/', $this->default_controller);
-					$this->set_class($x[0]);
-					$this->set_method($x[1]);
-				}
-				else
-				{
-					$this->set_class($this->default_controller);
-					$this->set_method('index');
-				}
-
-				// Does the default controller exist in the sub-folder?
-				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
+				$segments = explode('/', $this->default_controller);
+				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
 				{
 					$this->directory = '';
-					return array();
 				}
-
 			}
 
 			return $segments;
 		}
 
-
 		// If we've gotten this far it means that the URI does not correlate to a valid
 		// controller class. We will now see if there is an override
 		if ( ! empty($this->routes['404_override']))
 		{
-			$x = explode('/', $this->routes['404_override']);
-			$this->set_class($x[0]);
-			$this->set_method(isset($x[1]) ? $x[1] : 'index');
+			if (sscanf($this->routes['404_override'], '%[^/]/%s', $class, $method) !== 2)
+			{
+				$method = 'index';
+			}
 
-			return $x;
+			return array($class, $method);
 		}
 
 		// Nothing else to do at this point but show a 404
@@ -347,9 +316,8 @@
 	/**
 	 * Parse Routes
 	 *
-	 * This function matches any routes that may exist in
-	 * the config/routes.php file against the URI to
-	 * determine if the class/method need to be remapped.
+	 * Matches any routes that may exist in the config/routes.php file
+	 * against the URI to determine if the class/method need to be remapped.
 	 *
 	 * @return	void
 	 */
@@ -359,7 +327,7 @@
 		$uri = implode('/', $this->uri->segments);
 
 		// Is there a literal match?  If so we're done
-		if (isset($this->routes[$uri]))
+		if (isset($this->routes[$uri]) && is_string($this->routes[$uri]))
 		{
 			return $this->_set_request(explode('/', $this->routes[$uri]));
 		}
@@ -368,13 +336,51 @@
 		foreach ($this->routes as $key => $val)
 		{
 			// Convert wild-cards to RegEx
-			$key = str_replace(array(':any', ':num'), array('.+', '[0-9]+'), $key);
+			$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
 
 			// Does the RegEx match?
-			if (preg_match('#^'.$key.'$#', $uri))
+			if (preg_match('#^'.$key.'$#', $uri, $matches))
 			{
-				// Do we have a back-reference?
-				if (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
+				// Are we using callbacks to process back-references?
+				if ( ! is_string($val) && is_callable($val))
+				{
+					// Remove the original string from the matches array.
+					array_shift($matches);
+
+					// Get the match count.
+					$match_count = count($matches);
+
+					// Determine how many parameters the callback has.
+					$reflection = new ReflectionFunction($val);
+					$param_count = $reflection->getNumberOfParameters();
+
+					// Are there more parameters than matches?
+					if ($param_count > $match_count)
+					{
+						// Any params without matches will be set to an empty string.
+						$matches = array_merge($matches, array_fill($match_count, $param_count - $match_count, ''));
+
+						$match_count = $param_count;
+					}
+
+					// Get the parameters so we can use their default values.
+					$params = $reflection->getParameters();
+
+					for ($m = 0; $m < $match_count; $m++)
+					{
+						// Is the match empty and does a default value exist?
+						if (empty($matches[$m]) && $params[$m]->isDefaultValueAvailable())
+						{
+							// Substitute the empty match for the default value.
+							$matches[$m] = $params[$m]->getDefaultValue();
+						}
+					}
+
+					// Execute the callback using the values in matches as its parameters.
+					$val = call_user_func_array($val, $matches);
+				}
+				// Are we using the default routing method for back-references?
+				elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
 				{
 					$val = preg_replace('#^'.$key.'$#', $val, $uri);
 				}
@@ -391,9 +397,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the class name
+	 * Set class name
 	 *
-	 * @param	string
+	 * @param	string	$class	Class name
 	 * @return	void
 	 */
 	public function set_class($class)
@@ -416,9 +422,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the method name
+	 * Set method name
 	 *
-	 * @param	string
+	 * @param	string	$method	Method name
 	 * @return	void
 	 */
 	public function set_method($method)
@@ -441,9 +447,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the directory name
+	 * Set directory name
 	 *
-	 * @param	string
+	 * @param	string	$dir	Directory name
 	 * @return	void
 	 */
 	public function set_directory($dir)
@@ -454,7 +460,10 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch the sub-directory (if any) that contains the requested controller class
+	 * Fetch directory
+	 *
+	 * Feches the sub-directory (if any) that contains the requested
+	 * controller class.
 	 *
 	 * @return	string
 	 */
@@ -466,9 +475,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the controller overrides
+	 * Set controller overrides
 	 *
-	 * @param	array
+	 * @param	array	$routing	Route overrides
 	 * @return	void
 	 */
 	public function _set_overrides($routing)
@@ -490,7 +499,7 @@
 
 		if (isset($routing['function']))
 		{
-			$routing['function'] = ($routing['function'] == '') ? 'index' : $routing['function'];
+			$routing['function'] = empty($routing['function']) ? 'index' : $routing['function'];
 			$this->set_method($routing['function']);
 		}
 	}
diff --git a/system/core/Security.php b/system/core/Security.php
index b22d2cf..c415544 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Security Class
@@ -37,45 +38,55 @@
 class CI_Security {
 
 	/**
-	 * Random Hash for protecting URLs
+	 * XSS Hash
 	 *
-	 * @var string
+	 * Random Hash for protecting URLs.
+	 *
+	 * @var	string
 	 */
 	protected $_xss_hash =	'';
 
 	/**
-	 * Random Hash for Cross Site Request Forgery Protection Cookie
+	 * CSRF Hash
 	 *
-	 * @var string
+	 * Random hash for Cross Site Request Forgery protection cookie
+	 *
+	 * @var	string
 	 */
 	protected $_csrf_hash =	'';
 
 	/**
-	 * Expiration time for Cross Site Request Forgery Protection Cookie
-	 * Defaults to two hours (in seconds)
+	 * CSRF Expire time
 	 *
-	 * @var int
+	 * Expiration time for Cross Site Request Forgery protection cookie.
+	 * Defaults to two hours (in seconds).
+	 *
+	 * @var	int
 	 */
 	protected $_csrf_expire =	7200;
 
 	/**
-	 * Token name for Cross Site Request Forgery Protection Cookie
+	 * CSRF Token name
 	 *
-	 * @var string
+	 * Token name for Cross Site Request Forgery protection cookie.
+	 *
+	 * @var	string
 	 */
 	protected $_csrf_token_name =	'ci_csrf_token';
 
 	/**
-	 * Cookie name for Cross Site Request Forgery Protection Cookie
+	 * CSRF Cookie name
 	 *
-	 * @var string
+	 * Cookie name for Cross Site Request Forgery protection cookie.
+	 *
+	 * @var	string
 	 */
 	protected $_csrf_cookie_name =	'ci_csrf_token';
 
 	/**
 	 * List of never allowed strings
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_never_allowed_str =	array(
 		'document.cookie'	=> '[removed]',
@@ -91,9 +102,9 @@
 	);
 
 	/**
-	 * List of never allowed regex replacement
+	 * List of never allowed regex replacements
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	protected $_never_allowed_regex = array(
 		'javascript\s*:',
@@ -104,7 +115,7 @@
 	);
 
 	/**
-	 * Initialize security class
+	 * Class constructor
 	 *
 	 * @return	void
 	 */
@@ -138,7 +149,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Verify Cross Site Request Forgery Protection
+	 * CSRF Verify
 	 *
 	 * @return	object
 	 */
@@ -161,7 +172,7 @@
 		}
 
 		// Do the tokens exist in both the _POST and _COOKIE arrays?
-		if ( ! isset($_POST[$this->_csrf_token_name]) OR ! isset($_COOKIE[$this->_csrf_cookie_name])
+		if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
 			OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match?
 		{
 			$this->csrf_show_error();
@@ -188,17 +199,17 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set Cross Site Request Forgery Protection Cookie
+	 * CSRF Set Cookie
 	 *
-	 * @return	object
 	 * @codeCoverageIgnore
+	 * @return	object
 	 */
 	public function csrf_set_cookie()
 	{
 		$expire = time() + $this->_csrf_expire;
 		$secure_cookie = (bool) config_item('cookie_secure');
 
-		if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off'))
+		if ($secure_cookie && ! is_https())
 		{
 			return FALSE;
 		}
@@ -234,9 +245,8 @@
 	/**
 	 * Get CSRF Hash
 	 *
-	 * Getter Method
-	 *
-	 * @return 	string 	self::_csrf_hash
+	 * @see		CI_Security::$_csrf_hash
+	 * @return 	string	CSRF hash
 	 */
 	public function get_csrf_hash()
 	{
@@ -248,9 +258,8 @@
 	/**
 	 * Get CSRF Token Name
 	 *
-	 * Getter Method
-	 *
-	 * @return 	string 	self::_csrf_token_name
+	 * @see		CI_Security::$_csrf_token_name
+	 * @return	string	CSRF token name
 	 */
 	public function get_csrf_token_name()
 	{
@@ -263,26 +272,26 @@
 	 * XSS Clean
 	 *
 	 * Sanitizes data so that Cross Site Scripting Hacks can be
-	 * prevented.  This function does a fair amount of work but
+	 * prevented.  This method does a fair amount of work but
 	 * it is extremely thorough, designed to prevent even the
 	 * most obscure XSS attempts.  Nothing is ever 100% foolproof,
 	 * of course, but I haven't been able to get anything passed
 	 * the filter.
 	 *
-	 * Note: This function should only be used to deal with data
-	 * upon submission. It's not something that should
-	 * be used for general runtime processing.
+	 * Note: Should only be used to deal with data upon submission.
+	 *	 It's not something that should be used for general
+	 *	 runtime processing.
 	 *
-	 * This function was based in part on some code and ideas I
-	 * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention
+	 * @link	http://channel.bitflux.ch/wiki/XSS_Prevention
+	 * 		Based in part on some code and ideas from Bitflux.
 	 *
-	 * To help develop this script I used this great list of
-	 * vulnerabilities along with a few other hacks I've
-	 * harvested from examining vulnerabilities in other programs:
-	 * http://ha.ckers.org/xss.html
+	 * @link	http://ha.ckers.org/xss.html
+	 * 		To help develop this script I used this great list of
+	 *		vulnerabilities along with a few other hacks I've
+	 *		harvested from examining vulnerabilities in other programs.
 	 *
-	 * @param	mixed	string or array
-	 * @param 	bool
+	 * @param	string|string[]	$str		Input data
+	 * @param 	bool		$is_image	Whether the input is an image
 	 * @return	string
 	 */
 	public function xss_clean($str, $is_image = FALSE)
@@ -359,7 +368,7 @@
 		}
 		else
 		{
-			$str = str_replace(array('<?', '?'.'>'),  array('&lt;?', '?&gt;'), $str);
+			$str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
 		}
 
 		/*
@@ -373,7 +382,6 @@
 			'applet', 'alert', 'document', 'write', 'cookie', 'window'
 		);
 
-
 		foreach ($words as $word)
 		{
 			$word = implode('\s*', str_split($word)).'\s*';
@@ -469,9 +477,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Random Hash for protecting URLs
+	 * XSS Hash
 	 *
-	 * @return	string
+	 * Generates the XSS hash if needed and returns it.
+	 *
+	 * @see		CI_Security::$_xss_hash
+	 * @return	string	XSS hash
 	 */
 	public function xss_hash()
 	{
@@ -489,7 +500,7 @@
 	/**
 	 * HTML Entities Decode
 	 *
-	 * This function is a replacement for html_entity_decode()
+	 * A replacement for html_entity_decode()
 	 *
 	 * The reason we are not using html_entity_decode() by itself is because
 	 * while it is not technically correct to leave out the semicolon
@@ -497,8 +508,10 @@
 	 * correctly. html_entity_decode() does not convert entities without
 	 * semicolons, so we are left with our own little solution here. Bummer.
 	 *
-	 * @param	string
-	 * @param	string
+	 * @link	http://php.net/html-entity-decode
+	 *
+	 * @param	string	$str		Input
+	 * @param	string	$charset	Character set
 	 * @return	string
 	 */
 	public function entity_decode($str, $charset = NULL)
@@ -521,10 +534,10 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Filename Security
+	 * Sanitize Filename
 	 *
-	 * @param	string
-	 * @param 	bool
+	 * @param	string	$str		Input file name
+	 * @param 	bool	$relative_path	Whether to preserve paths
 	 * @return	string
 	 */
 	public function sanitize_filename($str, $relative_path = FALSE)
@@ -563,7 +576,7 @@
 	/**
 	 * Strip Image Tags
 	 *
-	 * @param	string
+	 * @param	string	$str
 	 * @return	string
 	 */
 	public function strip_image_tags($str)
@@ -576,10 +589,11 @@
 	/**
 	 * Compact Exploded Words
 	 *
-	 * Callback function for xss_clean() to remove whitespace from
-	 * things like j a v a s c r i p t
+	 * Callback method for xss_clean() to remove whitespace from
+	 * things like 'j a v a s c r i p t'.
 	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$matches
 	 * @return	string
 	 */
 	protected function _compact_exploded_words($matches)
@@ -593,16 +607,22 @@
 	 * Remove Evil HTML Attributes (like event handlers and style)
 	 *
 	 * It removes the evil attribute and either:
-	 * 	- Everything up until a space
-	 *		For example, everything between the pipes:
-	 *		<a |style=document.write('hello');alert('world');| class=link>
-	 * 	- Everything inside the quotes
-	 *		For example, everything between the pipes:
-	 *		<a |style="document.write('hello'); alert('world');"| class="link">
 	 *
-	 * @param string $str The string to check
-	 * @param boolean $is_image TRUE if this is an image
-	 * @return string The string with the evil attributes removed
+	 *  - Everything up until a space. For example, everything between the pipes:
+	 *
+	 *	<code>
+	 *		<a |style=document.write('hello');alert('world');| class=link>
+	 *	</code>
+	 *
+	 *  - Everything inside the quotes. For example, everything between the pipes:
+	 *
+	 *	<code>
+	 *		<a |style="document.write('hello'); alert('world');"| class="link">
+	 *	</code>
+	 *
+	 * @param	string	$str		The string to check
+	 * @param	bool	$is_image	Whether the input is an image
+	 * @return	string	The string with the evil attributes removed
 	 */
 	protected function _remove_evil_attributes($str, $is_image)
 	{
@@ -655,9 +675,10 @@
 	/**
 	 * Sanitize Naughty HTML
 	 *
-	 * Callback function for xss_clean() to remove naughty HTML elements
+	 * Callback method for xss_clean() to remove naughty HTML elements.
 	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$matches
 	 * @return	string
 	 */
 	protected function _sanitize_naughty_html($matches)
@@ -672,12 +693,14 @@
 	/**
 	 * JS Link Removal
 	 *
-	 * Callback function for xss_clean() to sanitize links
+	 * Callback method for xss_clean() to sanitize links.
+	 *
 	 * This limits the PCRE backtracks, making it more performance friendly
 	 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
-	 * PHP 5.2+ on link-heavy strings
+	 * PHP 5.2+ on link-heavy strings.
 	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
 	 * @return	string
 	 */
 	protected function _js_link_removal($match)
@@ -695,12 +718,14 @@
 	/**
 	 * JS Image Removal
 	 *
-	 * Callback function for xss_clean() to sanitize image tags
+	 * Callback method for xss_clean() to sanitize image tags.
+	 *
 	 * This limits the PCRE backtracks, making it more performance friendly
 	 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
-	 * PHP 5.2+ on image tag heavy strings
+	 * PHP 5.2+ on image tag heavy strings.
 	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
 	 * @return	string
 	 */
 	protected function _js_img_removal($match)
@@ -718,9 +743,8 @@
 	/**
 	 * Attribute Conversion
 	 *
-	 * Used as a callback for XSS Clean
-	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
 	 * @return	string
 	 */
 	protected function _convert_attribute($match)
@@ -733,9 +757,11 @@
 	/**
 	 * Filter Attributes
 	 *
-	 * Filters tag attributes for consistency and safety
+	 * Filters tag attributes for consistency and safety.
 	 *
-	 * @param	string
+	 * @used-by	CI_Security::_js_img_removal()
+	 * @used-by	CI_Security::_js_link_removal()
+	 * @param	string	$str
 	 * @return	string
 	 */
 	protected function _filter_attributes($str)
@@ -757,9 +783,8 @@
 	/**
 	 * HTML Entity Decode Callback
 	 *
-	 * Used as a callback for XSS Clean
-	 *
-	 * @param	array
+	 * @used-by	CI_Security::xss_clean()
+	 * @param	array	$match
 	 * @return	string
 	 */
 	protected function _decode_entity($match)
@@ -772,9 +797,8 @@
 	/**
 	 * Validate URL entities
 	 *
-	 * Called by xss_clean()
-	 *
-	 * @param 	string
+	 * @used-by	CI_Security::xss_clean()
+	 * @param 	string	$str
 	 * @return 	string
 	 */
 	protected function _validate_entities($str)
@@ -812,8 +836,7 @@
 	/**
 	 * Do Never Allowed
 	 *
-	 * A utility function for xss_clean()
-	 *
+	 * @used-by	CI_Security::xss_clean()
 	 * @param 	string
 	 * @return 	string
 	 */
@@ -832,7 +855,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set Cross Site Request Forgery Protection Cookie
+	 * Set CSRF Hash and Cookie
 	 *
 	 * @return	string
 	 */
diff --git a/system/core/URI.php b/system/core/URI.php
index 6a8b1a5..900472b 100644
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * URI Class
@@ -39,36 +40,37 @@
 class CI_URI {
 
 	/**
-	 * List of cached uri segments
+	 * List of cached URI segments
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $keyval =	array();
 
 	/**
-	 * Current uri string
+	 * Current URI string
 	 *
-	 * @var string
+	 * @var	string
 	 */
 	public $uri_string;
 
 	/**
-	 * List of uri segments
+	 * List of URI segments
 	 *
-	 * @var array
+	 * @var	array
 	 */
 	public $segments =	array();
 
 	/**
-	 * Re-indexed list of uri segments
-	 * Starts at 1 instead of 0
+	 * Re-indexed list of URI segments
 	 *
-	 * @var array
+	 * Starts at 1 instead of 0.
+	 *
+	 * @var	array
 	 */
 	public $rsegments =	array();
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
 	 * Simply globalizes the $RTR object. The front
 	 * loads the Router class early on so it's not available
@@ -85,10 +87,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Get the URI String
+	 * Fetch URI String
 	 *
-	 * Called by CI_Router
-	 *
+	 * @used-by	CI_Router
 	 * @return	void
 	 */
 	public function _fetch_uri_string()
@@ -98,31 +99,28 @@
 			// Is the request coming from the command line?
 			if ($this->_is_cli_request())
 			{
-				$this->_set_uri_string($this->_parse_cli_args());
+				$this->_set_uri_string($this->_parse_argv());
 				return;
 			}
 
-			// Let's try the REQUEST_URI first, this will work in most situations
-			if ($uri = $this->_detect_uri())
+			// Is there a PATH_INFO variable? This should be the easiest solution.
+			if (isset($_SERVER['PATH_INFO']))
+			{
+				$this->_set_uri_string($_SERVER['PATH_INFO']);
+				return;
+			}
+
+			// Let's try REQUEST_URI then, this will work in most situations
+			if (($uri = $this->_parse_request_uri()) !== '')
 			{
 				$this->_set_uri_string($uri);
 				return;
 			}
 
-			// Is there a PATH_INFO variable?
-			// Note: some servers seem to have trouble with getenv() so we'll test it two ways
-			$path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
-			if (trim($path, '/') !== '' && $path !== '/'.SELF)
+			// No REQUEST_URI either?... What about QUERY_STRING?
+			if (($uri = $this->_parse_query_string()) !== '')
 			{
-				$this->_set_uri_string($path);
-				return;
-			}
-
-			// No PATH_INFO?... What about QUERY_STRING?
-			$path = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
-			if (trim($path, '/') !== '')
-			{
-				$this->_set_uri_string($path);
+				$this->_set_uri_string($uri);
 				return;
 			}
 
@@ -140,106 +138,155 @@
 
 		$uri = strtoupper($this->config->item('uri_protocol'));
 
-		if ($uri === 'REQUEST_URI')
+		if ($uri === 'CLI')
 		{
-			$this->_set_uri_string($this->_detect_uri());
+			$this->_set_uri_string($this->_parse_argv());
 			return;
 		}
-		elseif ($uri === 'CLI')
+		elseif (method_exists($this, ($method = '_parse_'.strtolower($uri))))
 		{
-			$this->_set_uri_string($this->_parse_cli_args());
+			$this->_set_uri_string($this->$method());
 			return;
 		}
 
-		$path = isset($_SERVER[$uri]) ? $_SERVER[$uri] : @getenv($uri);
-		$this->_set_uri_string($path);
+		$uri = isset($_SERVER[$uri]) ? $_SERVER[$uri] : @getenv($uri);
+		$this->_set_uri_string($uri);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Set the URI String
+	 * Set URI String
 	 *
-	 * @param 	string
+	 * @param 	string	$str
 	 * @return	void
 	 */
 	protected function _set_uri_string($str)
 	{
-		// Filter out control characters
-		$str = remove_invisible_characters($str, FALSE);
-
-		// If the URI contains only a slash we'll kill it
-		$this->uri_string = ($str === '/') ? '' : $str;
+		// Filter out control characters and trim slashes
+		$this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Detects the URI
+	 * Parse REQUEST_URI
 	 *
-	 * This function will detect the URI automatically
-	 * and fix the query string if necessary.
+	 * Will parse REQUEST_URI and automatically detect the URI from it,
+	 * while fixing the query string if necessary.
 	 *
+	 * @used-by	CI_URI::_fetch_uri_string()
 	 * @return	string
 	 */
-	protected function _detect_uri()
+	protected function _parse_request_uri()
 	{
 		if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
 		{
 			return '';
 		}
 
-		if (strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']) === 0)
+		$uri = parse_url($_SERVER['REQUEST_URI']);
+		$query = isset($uri['query']) ? $uri['query'] : '';
+		$uri = isset($uri['path']) ? rawurldecode($uri['path']) : '';
+
+		if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
 		{
-			$uri = substr($_SERVER['REQUEST_URI'], strlen($_SERVER['SCRIPT_NAME']));
+			$uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME']));
 		}
-		elseif (strpos($_SERVER['REQUEST_URI'], dirname($_SERVER['SCRIPT_NAME'])) === 0)
+		elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
 		{
-			$uri = substr($_SERVER['REQUEST_URI'], strlen(dirname($_SERVER['SCRIPT_NAME'])));
-		}
-		else
-		{
-			$uri = $_SERVER['REQUEST_URI'];
+			$uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
 		}
 
 		// This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct
 		// URI is found, and also fixes the QUERY_STRING server var and $_GET array.
-		if (strpos($uri, '?/') === 0)
+		if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0)
 		{
-			$uri = substr($uri, 2);
-		}
-
-		$parts = explode('?', $uri, 2);
-		$uri = $parts[0];
-		if (isset($parts[1]))
-		{
-			$_SERVER['QUERY_STRING'] = $parts[1];
-			parse_str($_SERVER['QUERY_STRING'], $_GET);
+			$query = explode('?', $query, 2);
+			$uri = rawurldecode($query[0]);
+			$_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : '';
 		}
 		else
 		{
-			$_SERVER['QUERY_STRING'] = '';
-			$_GET = array();
+			$_SERVER['QUERY_STRING'] = $query;
 		}
 
-		if ($uri === '/' OR empty($uri))
+		parse_str($_SERVER['QUERY_STRING'], $_GET);
+
+		if ($uri === '/' OR $uri === '')
 		{
 			return '/';
 		}
 
-		$uri = parse_url('pseudo://hostname/'.$uri, PHP_URL_PATH);
-
 		// Do some final cleaning of the URI and return it
-		return str_replace(array('//', '../'), '/', trim($uri, '/'));
+		return $this->_remove_relative_directory($uri);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Is cli Request?
+	 * Remove relative directory (../) and multi slashes (///)
 	 *
-	 * Duplicate of function from the Input class to test to see if a request was made from the command line
+	 * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
 	 *
+	 * @param	string	$url
+	 * @return	string
+	 */
+	protected function _remove_relative_directory($uri)
+	{
+		$uris = array();
+		$tok = strtok($uri, '/');
+		while ($tok !== FALSE)
+		{
+			if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
+			{
+				$uris[] = $tok;
+			}
+			$tok = strtok('/');
+		}
+		return implode('/', $uris);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Parse QUERY_STRING
+	 *
+	 * Will parse QUERY_STRING and automatically detect the URI from it.
+	 *
+	 * @used-by	CI_URI::_fetch_uri_string()
+	 * @return	string
+	 */
+	protected function _parse_query_string()
+	{
+		$uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
+
+		if (trim($uri, '/') === '')
+		{
+			return '';
+		}
+		elseif (strncmp($uri, '/', 1) === 0)
+		{
+			$uri = explode('?', $uri, 2);
+			$_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : '';
+			$uri = rawurldecode($uri[0]);
+		}
+
+		parse_str($_SERVER['QUERY_STRING'], $_GET);
+
+		return $this->_remove_relative_directory($uri);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Is CLI Request?
+	 *
+	 * Duplicate of method from the Input class to test to see if
+	 * a request was made from the command line.
+	 *
+	 * @see		CI_Input::is_cli_request()
+	 * @used-by	CI_URI::_fetch_uri_string()
 	 * @return 	bool
 	 */
 	protected function _is_cli_request()
@@ -250,26 +297,27 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Parse cli arguments
+	 * Parse CLI arguments
 	 *
 	 * Take each command line argument and assume it is a URI segment.
 	 *
 	 * @return	string
 	 */
-	protected function _parse_cli_args()
+	protected function _parse_argv()
 	{
 		$args = array_slice($_SERVER['argv'], 1);
-		return $args ? '/'.implode('/', $args) : '';
+		return $args ? implode('/', $args) : '';
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Filter segments for malicious characters
+	 * Filter URI
 	 *
-	 * Called by CI_Router
+	 * Filters segments for malicious characters.
 	 *
-	 * @param	string
+	 * @used-by	CI_Router
+	 * @param	string	$str
 	 * @return	string
 	 */
 	public function _filter_uri($str)
@@ -278,7 +326,7 @@
 		{
 			// preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards
 			// compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern
-			if ( ! preg_match('|^['.str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-')).']+$|i', urldecode($str)))
+			if ( ! preg_match('|^['.str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-')).']+$|i', $str))
 			{
 				show_error('The URI you submitted has disallowed characters.', 400);
 			}
@@ -294,10 +342,11 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Remove the suffix from the URL if needed
+	 * Remove URL suffix
 	 *
-	 * Called by CI_Router
+	 * Removes the suffix from the URL if needed.
 	 *
+	 * @used-by	CI_Router
 	 * @return	void
 	 */
 	public function _remove_url_suffix()
@@ -313,11 +362,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Explode the URI Segments. The individual segments will
-	 * be stored in the $this->segments array.
+	 * Explode URI segments
 	 *
-	 * Called by CI_Router
+	 * The individual segments will be stored in the $this->segments array.
 	 *
+	 * @see		CI_URI::$segments
+	 * @used-by	CI_Router
 	 * @return	void
 	 */
 	public function _explode_segments()
@@ -339,13 +389,12 @@
 	/**
 	 * Re-index Segments
 	 *
-	 * This function re-indexes the $this->segment array so that it
-	 * starts at 1 rather than 0. Doing so makes it simpler to
-	 * use functions like $this->uri->segment(n) since there is
-	 * a 1:1 relationship between the segment array and the actual segments.
+	 * Re-indexes the CI_URI::$segment array so that it starts at 1 rather
+	 * than 0. Doing so makes it simpler to use methods like
+	 * CI_URI::segment(n) since there is a 1:1 relationship between the
+	 * segment array and the actual segments.
 	 *
-	 * Called by CI_Router
-	 *
+	 * @used-by	CI_Router
 	 * @return	void
 	 */
 	public function _reindex_segments()
@@ -359,13 +408,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a URI Segment
+	 * Fetch URI Segment
 	 *
-	 * This function returns the URI segment based on the number provided.
-	 *
-	 * @param	int
-	 * @param	mixed
-	 * @return	string
+	 * @see		CI_URI::$segments
+	 * @param	int		$n		Index
+	 * @param	mixed		$no_result	What to return if the segment index is not found
+	 * @return	mixed
 	 */
 	public function segment($n, $no_result = NULL)
 	{
@@ -375,15 +423,17 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a URI "routed" Segment
+	 * Fetch URI "routed" Segment
 	 *
-	 * This function returns the re-routed URI segment (assuming routing rules are used)
-	 * based on the number provided. If there is no routing this function returns the
-	 * same result as $this->segment()
+	 * Returns the re-routed URI segment (assuming routing rules are used)
+	 * based on the index provided. If there is no routing, will return
+	 * the same result as CI_URI::segment().
 	 *
-	 * @param	int
-	 * @param	mixed
-	 * @return	string
+	 * @see		CI_URI::$rsegments
+	 * @see		CI_URI::segment()
+	 * @param	int		$n		Index
+	 * @param	mixed		$no_result	What to return if the segment index is not found
+	 * @return	mixed
 	 */
 	public function rsegment($n, $no_result = NULL)
 	{
@@ -393,23 +443,23 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Generate a key value pair from the URI string
+	 * URI to assoc
 	 *
-	 * This function generates and associative array of URI data starting
-	 * at the supplied segment. For example, if this is your URI:
+	 * Generates an associative array of URI data starting at the supplied
+	 * segment index. For example, if this is your URI:
 	 *
 	 *	example.com/user/search/name/joe/location/UK/gender/male
 	 *
-	 * You can use this function to generate an array with this prototype:
+	 * You can use this method to generate an array with this prototype:
 	 *
-	 * array (
-	 *			name => joe
-	 *			location => UK
-	 *			gender => male
-	 *		 )
+	 *	array (
+	 *		name => joe
+	 *		location => UK
+	 *		gender => male
+	 *	 )
 	 *
-	 * @param	int	the starting segment number
-	 * @param	array	an array of default values
+	 * @param	int	$n		Index (default: 3)
+	 * @param	array	$default	Default values
 	 * @return	array
 	 */
 	public function uri_to_assoc($n = 3, $default = array())
@@ -420,10 +470,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Identical to above only it uses the re-routed segment array
+	 * Routed URI to assoc
 	 *
-	 * @param 	int	the starting segment number
-	 * @param 	array	an array of default values
+	 * Identical to CI_URI::uri_to_assoc(), only it uses the re-routed
+	 * segment array.
+	 *
+	 * @see		CI_URI::uri_to_assoc()
+	 * @param 	int	$n		Index (default: 3)
+	 * @param 	array	$default	Default values
 	 * @return 	array
 	 */
 	public function ruri_to_assoc($n = 3, $default = array())
@@ -434,11 +488,15 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Generate a key value pair from the URI string or Re-routed URI string
+	 * Internal URI-to-assoc
 	 *
-	 * @param	int	the starting segment number
-	 * @param	array	an array of default values
-	 * @param	string	which array we should use
+	 * Generates a key/value pair from the URI string or re-routed URI string.
+	 *
+	 * @used-by	CI_URI::uri_to_assoc()
+	 * @used-by	CI_URI::ruri_to_assoc()
+	 * @param	int	$n		Index (default: 3)
+	 * @param	array	$default	Default values
+	 * @param	string	$which		Array name ('segment' or 'rsegment')
 	 * @return	array
 	 */
 	protected function _uri_to_assoc($n = 3, $default = array(), $which = 'segment')
@@ -448,9 +506,11 @@
 			return $default;
 		}
 
-		if (isset($this->keyval[$n]))
+		in_array($which, array('segment', 'rsegment'), TRUE) OR $which = 'segment';
+
+		if (isset($this->keyval[$which], $this->keyval[$which][$n]))
 		{
-			return $this->keyval[$n];
+			return $this->keyval[$which][$n];
 		}
 
 		if ($which === 'segment')
@@ -474,7 +534,7 @@
 		$segments = array_slice($this->$segment_array(), ($n - 1));
 		$i = 0;
 		$lastval = '';
-		$retval  = array();
+		$retval = array();
 		foreach ($segments as $seg)
 		{
 			if ($i % 2)
@@ -502,17 +562,20 @@
 		}
 
 		// Cache the array for reuse
-		$this->keyval[$n] = $retval;
+		isset($this->keyval[$which]) OR $this->keyval[$which] = array();
+		$this->keyval[$which][$n] = $retval;
 		return $retval;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Generate a URI string from an associative array
+	 * Assoc to URI
 	 *
-	 * @param	array	an associative array of key/values
-	 * @return	array
+	 * Generates a URI string from an associative array.
+	 *
+	 * @param	array	$array	Input array of key/value pairs
+	 * @return	string	URI string
 	 */
 	public function assoc_to_uri($array)
 	{
@@ -529,10 +592,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a URI Segment and add a trailing slash
+	 * Slash segment
 	 *
-	 * @param	int
-	 * @param	string
+	 * Fetches an URI segment with a slash.
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
 	 * @return	string
 	 */
 	public function slash_segment($n, $where = 'trailing')
@@ -543,10 +608,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a URI Segment and add a trailing slash
+	 * Slash routed segment
 	 *
-	 * @param	int
-	 * @param	string
+	 * Fetches an URI routed segment with a slash.
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
 	 * @return	string
 	 */
 	public function slash_rsegment($n, $where = 'trailing')
@@ -557,11 +624,16 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch a URI Segment and add a trailing slash - helper function
+	 * Internal Slash segment
 	 *
-	 * @param	int
-	 * @param	string
-	 * @param	string
+	 * Fetches an URI Segment and adds a slash to it.
+	 *
+	 * @used-by	CI_URI::slash_segment()
+	 * @used-by	CI_URI::slash_rsegment()
+	 *
+	 * @param	int	$n	Index
+	 * @param	string	$where	Where to add the slash ('trailing' or 'leading')
+	 * @param	string	$which	Array name ('segment' or 'rsegment')
 	 * @return	string
 	 */
 	protected function _slash_segment($n, $where = 'trailing', $which = 'segment')
@@ -585,7 +657,7 @@
 	/**
 	 * Segment Array
 	 *
-	 * @return	array
+	 * @return	array	CI_URI::$segments
 	 */
 	public function segment_array()
 	{
@@ -597,7 +669,7 @@
 	/**
 	 * Routed Segment Array
 	 *
-	 * @return	array
+	 * @return	array	CI_URI::$rsegments
 	 */
 	public function rsegment_array()
 	{
@@ -631,26 +703,32 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch the entire URI string
+	 * Fetch URI string
 	 *
-	 * @return	string
+	 * @return	string	CI_URI::$uri_string
 	 */
 	public function uri_string()
 	{
 		return $this->uri_string;
 	}
 
-
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch the entire Re-routed URI string
+	 * Fetch Re-routed URI string
 	 *
 	 * @return	string
 	 */
 	public function ruri_string()
 	{
-		return implode('/', $this->rsegment_array());
+		global $RTR;
+
+		if (($dir = $RTR->fetch_directory()) === '/')
+		{
+			$dir = '';
+		}
+
+		return $dir.implode('/', $this->rsegment_array());
 	}
 
 }
diff --git a/system/core/Utf8.php b/system/core/Utf8.php
index 0a7ec50..5bc2dd5 100644
--- a/system/core/Utf8.php
+++ b/system/core/Utf8.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Utf8 Class
@@ -39,9 +40,9 @@
 class CI_Utf8 {
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * Determines if UTF-8 support is to be enabled
+	 * Determines if UTF-8 support is to be enabled.
 	 *
 	 * @return	void
 	 */
@@ -49,30 +50,30 @@
 	{
 		log_message('debug', 'Utf8 Class Initialized');
 
-		global $CFG;
+		$charset = strtoupper(config_item('charset'));
+
+		// set internal encoding for multibyte string functions if necessary
+		// and set a flag so we don't have to repeatedly use extension_loaded()
+		// or function_exists()
+		if (extension_loaded('mbstring'))
+		{
+			define('MB_ENABLED', TRUE);
+			mb_internal_encoding($charset);
+		}
+		else
+		{
+			define('MB_ENABLED', FALSE);
+		}
 
 		if (
-			@preg_match('/./u', 'é') === 1		// PCRE must support UTF-8
-			&& function_exists('iconv')			// iconv must be installed
-			&& (bool) @ini_get('mbstring.func_overload') !== TRUE	// Multibyte string function overloading cannot be enabled
-			&& $CFG->item('charset') === 'UTF-8'		// Application charset must be UTF-8
+			@preg_match('/./u', 'é') === 1	// PCRE must support UTF-8
+			&& function_exists('iconv')	// iconv must be installed
+			&& MB_ENABLED === TRUE		// mbstring must be enabled
+			&& $charset === 'UTF-8'		// Application charset must be UTF-8
 			)
 		{
 			define('UTF8_ENABLED', TRUE);
 			log_message('debug', 'UTF-8 Support Enabled');
-
-			// set internal encoding for multibyte string functions if necessary
-			// and set a flag so we don't have to repeatedly use extension_loaded()
-			// or function_exists()
-			if (extension_loaded('mbstring'))
-			{
-				define('MB_ENABLED', TRUE);
-				mb_internal_encoding('UTF-8');
-			}
-			else
-			{
-				define('MB_ENABLED', FALSE);
-			}
 		}
 		else
 		{
@@ -86,9 +87,11 @@
 	/**
 	 * Clean UTF-8 strings
 	 *
-	 * Ensures strings are UTF-8
+	 * Ensures strings contain only valid UTF-8 characters.
 	 *
-	 * @param	string
+	 * @uses	CI_Utf8::_is_ascii()	Decide whether a conversion is needed
+	 *
+	 * @param	string	$str	String to clean
 	 * @return	string
 	 */
 	public function clean_string($str)
@@ -108,9 +111,9 @@
 	 *
 	 * Removes all ASCII control characters except horizontal tabs,
 	 * line feeds, and carriage returns, as all others can cause
-	 * problems in XML
+	 * problems in XML.
 	 *
-	 * @param	string
+	 * @param	string	$str	String to clean
 	 * @return	string
 	 */
 	public function safe_ascii_for_xml($str)
@@ -123,11 +126,11 @@
 	/**
 	 * Convert to UTF-8
 	 *
-	 * Attempts to convert a string to UTF-8
+	 * Attempts to convert a string to UTF-8.
 	 *
-	 * @param	string
-	 * @param	string	input encoding
-	 * @return	string
+	 * @param	string	$str		Input string
+	 * @param	string	$encoding	Input encoding
+	 * @return	string	$str encoded in UTF-8 or FALSE on failure
 	 */
 	public function convert_to_utf8($str, $encoding)
 	{
@@ -135,7 +138,7 @@
 		{
 			return @iconv($encoding, 'UTF-8', $str);
 		}
-		elseif (function_exists('mb_convert_encoding'))
+		elseif (MB_ENABLED === TRUE)
 		{
 			return @mb_convert_encoding($str, 'UTF-8', $encoding);
 		}
@@ -148,9 +151,9 @@
 	/**
 	 * Is ASCII?
 	 *
-	 * Tests if a string is standard 7-bit ASCII or not
+	 * Tests if a string is standard 7-bit ASCII or not.
 	 *
-	 * @param	string
+	 * @param	string	$str	String to check
 	 * @return	bool
 	 */
 	protected function _is_ascii($str)
diff --git a/system/database/DB.php b/system/database/DB.php
index 032cf1b..f4062fa 100644
--- a/system/database/DB.php
+++ b/system/database/DB.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Initialize the database
@@ -31,8 +32,10 @@
  * @category	Database
  * @author	EllisLab Dev Team
  * @link	http://codeigniter.com/user_guide/database/
- * @param 	string
- * @param 	bool	Determines if query builder should be used or not
+ *
+ * @param 	string|string[]	$params
+ * @param 	bool		$query_builder_override
+ *				Determines if query builder should be used or not
  */
 function &DB($params = '', $query_builder_override = NULL)
 {
@@ -47,18 +50,18 @@
 		}
 
 		include($file_path);
-		//make packages contain database config files
-		foreach(get_instance()->load->get_package_paths() as $path)
+		// Make packages contain database config files
+		foreach (get_instance()->load->get_package_paths() as $path)
 		{
 			if ($path !== APPPATH)
 			{
-				if (file_exists ($file_path = $path.'config/'.ENVIRONMENT.'/database.php'))
+				if (file_exists($file_path = $path.'config/'.ENVIRONMENT.'/database.php'))
 				{
-					include ($file_path);
+					include($file_path);
 				}
-				elseif ( file_exists ($file_path = $path.'config/database.php'))
+				elseif (file_exists($file_path = $path.'config/database.php'))
 				{
-					include ($file_path);
+					include($file_path);
 				}
 			}
 		}
@@ -82,12 +85,12 @@
 	}
 	elseif (is_string($params))
 	{
-
-		/* parse the URL from the DSN string
-		 *  Database settings can be passed as discreet
-		 *  parameters or as a data source name in the first
-		 *  parameter. DSNs must have this prototype:
-		 *  $dsn = 'driver://username:password@hostname/database';
+		/**
+		 * Parse the URL from the DSN string
+		 * Database settings can be passed as discreet
+		 * parameters or as a data source name in the first
+		 * parameter. DSNs must have this prototype:
+		 * $dsn = 'driver://username:password@hostname/database';
 		 */
 		if (($dsn = @parse_url($params)) === FALSE)
 		{
@@ -103,7 +106,7 @@
 				'database'	=> isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : ''
 			);
 
-		// were additional config items set?
+		// Were additional config items set?
 		if (isset($dsn['query']))
 		{
 			parse_str($dsn['query'], $extra);
@@ -148,11 +151,22 @@
 		require_once(BASEPATH.'database/DB_query_builder.php');
 		if ( ! class_exists('CI_DB'))
 		{
+			/**
+			 * CI_DB
+			 *
+			 * Acts as an alias for both CI_DB_driver and CI_DB_query_builder.
+			 *
+			 * @see	CI_DB_query_builder
+			 * @see	CI_DB_driver
+			 */
 			class CI_DB extends CI_DB_query_builder { }
 		}
 	}
 	elseif ( ! class_exists('CI_DB'))
 	{
+		/**
+	 	 * @ignore
+		 */
 		class CI_DB extends CI_DB_driver { }
 	}
 
@@ -170,6 +184,19 @@
 	$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
 	$DB = new $driver($params);
 
+	// Check for a subdriver
+	if ( ! empty($DB->subdriver))
+	{
+		$driver_file = BASEPATH.'database/drivers/'.$DB->dbdriver.'/subdrivers/'.$DB->dbdriver.'_'.$DB->subdriver.'_driver.php';
+
+		if (file_exists($driver_file))
+		{
+			require_once($driver_file);
+			$driver = 'CI_DB_'.$DB->dbdriver.'_'.$DB->subdriver.'_driver';
+			$DB = new $driver($params);
+		}
+	}
+
 	if ($DB->autoinit === TRUE)
 	{
 		$DB->initialize();
diff --git a/system/database/DB_cache.php b/system/database/DB_cache.php
index ba91103..fd01dc2 100644
--- a/system/database/DB_cache.php
+++ b/system/database/DB_cache.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Database Cache Class
@@ -34,15 +35,39 @@
  */
 class CI_DB_Cache {
 
+	/**
+	 * CI Singleton
+	 *
+	 * @var	object
+	 */
 	public $CI;
-	public $db;	// allows passing of db object so that multiple database connections and returned db objects can be supported
 
+	/**
+	 * Database object
+	 *
+	 * Allows passing of DB object so that multiple database connections
+	 * and returned DB objects can be supported.
+	 *
+	 * @var	object
+	 */
+	public $db;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	object	&$db
+	 * @return	void
+	 */
 	public function __construct(&$db)
 	{
 		// Assign the main CI object to $this->CI and load the file helper since we use it a lot
 		$this->CI =& get_instance();
 		$this->db =& $db;
 		$this->CI->load->helper('file');
+
+		$this->check_path();
 	}
 
 	// --------------------------------------------------------------------
@@ -50,7 +75,7 @@
 	/**
 	 * Set Cache Directory Path
 	 *
-	 * @param	string	the path to the cache directory
+	 * @param	string	$path	Path to the cache directory
 	 * @return	bool
 	 */
 	public function check_path($path = '')
@@ -66,14 +91,26 @@
 		}
 
 		// Add a trailing slash to the path if needed
-		$path = preg_replace('/(.+?)\/*$/', '\\1/',  $path);
+		$path = realpath($path)
+			? rtrim(realpath($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR
+			: rtrim($path, '/').'/';
 
-		if ( ! is_dir($path) OR ! is_really_writable($path))
+		if ( ! is_dir($path))
 		{
+			log_message('debug', 'DB cache path error: '.$path);
+
 			// If the path is wrong we'll turn off caching
 			return $this->db->cache_off();
 		}
 
+		if ( ! is_really_writable($path))
+		{
+			log_message('debug', 'DB cache dir not writable: '.$path);
+
+			// If the path is not really writable we'll turn off caching
+			return $this->db->cache_off();
+		}
+
 		$this->db->cachedir = $path;
 		return TRUE;
 	}
@@ -84,22 +121,18 @@
 	 * Retrieve a cached query
 	 *
 	 * The URI being requested will become the name of the cache sub-folder.
-	 * An MD5 hash of the SQL statement will become the cache file name
+	 * An MD5 hash of the SQL statement will become the cache file name.
 	 *
+	 * @param	string	$sql
 	 * @return	string
 	 */
 	public function read($sql)
 	{
-		if ( ! $this->check_path())
-		{
-			return $this->db->cache_off();
-		}
-
 		$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
 		$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
 		$filepath = $this->db->cachedir.$segment_one.'+'.$segment_two.'/'.md5($sql);
 
-		if (FALSE === ($cachedata = file_get_contents($filepath)))
+		if (FALSE === ($cachedata = @file_get_contents($filepath)))
 		{
 			return FALSE;
 		}
@@ -112,15 +145,12 @@
 	/**
 	 * Write a query to a cache file
 	 *
+	 * @param	string	$sql
+	 * @param	object	$object
 	 * @return	bool
 	 */
 	public function write($sql, $object)
 	{
-		if ( ! $this->check_path())
-		{
-			return $this->db->cache_off();
-		}
-
 		$segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
 		$segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
 		$dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
@@ -150,7 +180,9 @@
 	/**
 	 * Delete cache files within a particular directory
 	 *
-	 * @return	bool
+	 * @param	string	$segment_one
+	 * @param	string	$segment_two
+	 * @return	void
 	 */
 	public function delete($segment_one = '', $segment_two = '')
 	{
@@ -173,11 +205,11 @@
 	/**
 	 * Delete all existing cache files
 	 *
-	 * @return	bool
+	 * @return	void
 	 */
 	public function delete_all()
 	{
-		delete_files($this->db->cachedir, TRUE, 0, TRUE);
+		delete_files($this->db->cachedir, TRUE, TRUE);
 	}
 
 }
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 1060ecc..2d5b915 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Database Driver Class
@@ -40,54 +41,314 @@
  */
 abstract class CI_DB_driver {
 
+	/**
+	 * Data Source Name / Connect string
+	 *
+	 * @var	string
+	 */
 	public $dsn;
-	public $username;
-	public $password;
-	public $hostname;
-	public $database;
-	public $dbdriver		= 'mysqli';
-	public $dbprefix		= '';
-	public $char_set		= 'utf8';
-	public $dbcollat		= 'utf8_general_ci';
-	public $autoinit		= TRUE; // Whether to automatically initialize the DB
-	public $swap_pre		= '';
-	public $port			= '';
-	public $pconnect		= FALSE;
-	public $conn_id			= FALSE;
-	public $result_id		= FALSE;
-	public $db_debug		= FALSE;
-	public $benchmark		= 0;
-	public $query_count		= 0;
-	public $bind_marker		= '?';
-	public $save_queries		= TRUE;
-	public $queries			= array();
-	public $query_times		= array();
-	public $data_cache		= array();
-
-	public $trans_enabled		= TRUE;
-	public $trans_strict		= TRUE;
-	protected $_trans_depth		= 0;
-	protected $_trans_status	= TRUE; // Used with transactions to determine if a rollback should occur
-
-	public $cache_on		= FALSE;
-	public $cachedir		= '';
-	public $cache_autodel		= FALSE;
-	public $CACHE; // The cache class object
-
-	protected $_protect_identifiers		= TRUE;
-	protected $_reserved_identifiers	= array('*'); // Identifiers that should NOT be escaped
 
 	/**
-	 * 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.
+	 * Username
+	 *
+	 * @var	string
+	 */
+	public $username;
+
+	/**
+	 * Password
+	 *
+	 * @var	string
+	 */
+	public $password;
+
+	/**
+	 * Hostname
+	 *
+	 * @var	string
+	 */
+	public $hostname;
+
+	/**
+	 * Database name
+	 *
+	 * @var	string
+	 */
+	public $database;
+
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
+	public $dbdriver		= 'mysqli';
+
+	/**
+	 * Sub-driver
+	 *
+	 * @used-by	CI_DB_pdo_driver
+	 * @var	string
+	 */
+	public $subdriver;
+
+	/**
+	 * Table prefix
+	 *
+	 * @var	string
+	 */
+	public $dbprefix		= '';
+
+	/**
+	 * Character set
+	 *
+	 * @var	string
+	 */
+	public $char_set		= 'utf8';
+
+	/**
+	 * Collation
+	 *
+	 * @var	string
+	 */
+	public $dbcollat		= 'utf8_general_ci';
+
+	/**
+	 * Auto-init flag
+	 *
+	 * Whether to automatically initialize the DB connection.
+	 *
+	 * @var	bool
+	 */
+	public $autoinit		= TRUE;
+
+	/**
+	 * Encryption flag/data
+	 *
+	 * @var	mixed
+	 */
+	public $encrypt			= FALSE;
+
+	/**
+	 * Swap Prefix
+	 *
+	 * @var	string
+	 */
+	public $swap_pre		= '';
+
+	/**
+	 * Database port
+	 *
+	 * @var	int
+	 */
+	public $port			= '';
+
+	/**
+	 * Persistent connection flag
+	 *
+	 * @var	bool
+	 */
+	public $pconnect		= FALSE;
+
+	/**
+	 * Connection ID
+	 *
+	 * @var	object|resource
+	 */
+	public $conn_id			= FALSE;
+
+	/**
+	 * Result ID
+	 *
+	 * @var	object|resource
+	 */
+	public $result_id		= FALSE;
+
+	/**
+	 * Debug flag
+	 *
+	 * Whether to display error messages.
+	 *
+	 * @var	bool
+	 */
+	public $db_debug		= FALSE;
+
+	/**
+	 * Benchmark time
+	 *
+	 * @var	int
+	 */
+	public $benchmark		= 0;
+
+	/**
+	 * Executed queries count
+	 *
+	 * @var	int
+	 */
+	public $query_count		= 0;
+
+	/**
+	 * Bind marker
+	 *
+	 * Character used to identify values in a prepared statement.
+	 *
+	 * @var	string
+	 */
+	public $bind_marker		= '?';
+
+	/**
+	 * Save queries flag
+	 *
+	 * Whether to keep an in-memory history of queries for debugging purposes.
+	 *
+	 * @var	bool
+	 */
+	public $save_queries		= TRUE;
+
+	/**
+	 * Queries list
+	 *
+	 * @see	CI_DB_driver::$save_queries
+	 * @var	string[]
+	 */
+	public $queries			= array();
+
+	/**
+	 * Query times
+	 *
+	 * A list of times that queries took to execute.
+	 *
+	 * @var	array
+	 */
+	public $query_times		= array();
+
+	/**
+	 * Data cache
+	 *
+	 * An internal generic value cache.
+	 *
+	 * @var	array
+	 */
+	public $data_cache		= array();
+
+	/**
+	 * Transaction enabled flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_enabled		= TRUE;
+
+	/**
+	 * Strict transaction mode flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_strict		= TRUE;
+
+	/**
+	 * Transaction depth level
+	 *
+	 * @var	int
+	 */
+	protected $_trans_depth		= 0;
+
+	/**
+	 * Transaction status flag
+	 *
+	 * Used with transactions to determine if a rollback should occur.
+	 *
+	 * @var	bool
+	 */
+	protected $_trans_status	= TRUE;
+
+	/**
+	 * Cache On flag
+	 *
+	 * @var	bool
+	 */
+	public $cache_on		= FALSE;
+
+	/**
+	 * Cache directory path
+	 *
+	 * @var	bool
+	 */
+	public $cachedir		= '';
+
+	/**
+	 * Cache auto-delete flag
+	 *
+	 * @var	bool
+	 */
+	public $cache_autodel		= FALSE;
+
+	/**
+	 * DB Cache object
+	 *
+	 * @see	CI_DB_cache
+	 * @var	object
+	 */
+	public $CACHE;
+
+	/**
+	 * Protect identifiers flag
+	 *
+	 * @var	bool
+	 */
+	protected $_protect_identifiers		= TRUE;
+
+	/**
+	 * List of reserved identifiers
+	 *
+	 * Identifiers that must NOT be escaped.
+	 *
+	 * @var	string[]
+	 */
+	protected $_reserved_identifiers	= array('*');
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '"';
+
+	/**
+	 * ESCAPE statement string
+	 *
+	 * @var	string
+	 */
+	protected $_like_escape_str = " ESCAPE '%s' ";
+
+	/**
+	 * ESCAPE character
+	 *
+	 * @var	string
+	 */
+	protected $_like_escape_chr = '!';
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RAND()', 'RAND(%d)');
+
+	/**
+	 * COUNT string
+	 *
+	 * @used-by	CI_DB_driver::count_all()
+	 * @used-by	CI_DB_query_builder::count_all_results()
+	 *
+	 * @var	string
 	 */
 	protected $_count_string = 'SELECT COUNT(*) AS ';
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * @param	array
+	 * @param	array	$params
 	 * @return	void
 	 */
 	public function __construct($params)
@@ -304,8 +565,9 @@
 	 * FALSE upon failure, and if the $db_debug variable is set to TRUE
 	 * will raise an error.
 	 *
-	 * @param	string	An SQL query string
-	 * @param	array	An array of binding data
+	 * @param	string	$sql
+	 * @param	array	$binds = FALSE		An array of binding data
+	 * @param	bool	$return_object = NULL
 	 * @return	mixed
 	 */
 	public function query($sql, $binds = FALSE, $return_object = NULL)
@@ -430,7 +692,7 @@
 			// resource ID won't be any good once we've cached the
 			// result object, so we'll have to compile the data
 			// and save it)
-			$CR = new CI_DB_result();
+			$CR = new CI_DB_result($this);
 			$CR->result_object	= $RES->result_object();
 			$CR->result_array	= $RES->result_array();
 			$CR->num_rows		= $RES->num_rows();
@@ -508,6 +770,7 @@
 	 * If strict mode is disabled, each group is treated autonomously, meaning
 	 * a failure of one group will not affect any others
 	 *
+	 * @param	bool	$mode = TRUE
 	 * @return	void
 	 */
 	public function trans_strict($mode = TRUE)
@@ -520,6 +783,7 @@
 	/**
 	 * Start Transaction
 	 *
+	 * @param	bool	$test_mode = FALSE
 	 * @return	void
 	 */
 	public function trans_start($test_mode = FALSE)
@@ -631,7 +895,7 @@
 		// Make sure not to replace a chunk inside a string that happens to match the bind marker
 		if ($c = preg_match_all("/'[^']*'/i", $sql, $matches))
 		{
-			$c = preg_match_all('/'.preg_quote($this->bind_marker).'/i',
+			$c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i',
 				str_replace($matches[0],
 					str_replace($this->bind_marker, str_repeat(' ', $ml), $matches[0]),
 					$sql, $c),
@@ -643,7 +907,7 @@
 				return $sql;
 			}
 		}
-		elseif (($c = preg_match_all('/'.preg_quote($this->bind_marker).'/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count)
+		elseif (($c = preg_match_all('/'.preg_quote($this->bind_marker, '/').'/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count)
 		{
 			return $sql;
 		}
@@ -668,7 +932,7 @@
 	 */
 	public function is_write_type($sql)
 	{
-		return (bool) preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD DATA|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s+/i', $sql);
+		return (bool) preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s+/i', $sql);
 	}
 
 	// --------------------------------------------------------------------
@@ -721,7 +985,7 @@
 	 */
 	public function escape($str)
 	{
-		if (is_string($str) OR method_exists($str, '__toString'))
+		if (is_string($str) OR (is_object($str) && method_exists($str, '__toString')))
 		{
 			return "'".$this->escape_str($str)."'";
 		}
@@ -804,6 +1068,7 @@
 	/**
 	 * Returns an array of table names
 	 *
+	 * @param	string	$constrain_by_prefix = FALSE
 	 * @return	array
 	 */
 	public function list_tables($constrain_by_prefix = FALSE)
@@ -858,6 +1123,7 @@
 	/**
 	 * Determine if a particular table exists
 	 *
+	 * @param	string	$table_name
 	 * @return	bool
 	 */
 	public function table_exists($table_name)
@@ -970,7 +1236,7 @@
 	 */
 	public function escape_identifiers($item)
 	{
-		if ($this->_escape_char === '' OR empty($item))
+		if ($this->_escape_char === '' OR empty($item) OR in_array($item, $this->_reserved_identifiers))
 		{
 			return $item;
 		}
@@ -984,7 +1250,7 @@
 			return $item;
 		}
 		// Avoid breaking functions and literal values inside queries
-		elseif (ctype_digit($item) OR $item[0] === "'" OR strpos($item, '(') !== FALSE)
+		elseif (ctype_digit($item) OR $item[0] === "'" OR ($this->_escape_char !== '"' && $item[0] === '"') OR strpos($item, '(') !== FALSE)
 		{
 			return $item;
 		}
@@ -996,13 +1262,13 @@
 			if (is_array($this->_escape_char))
 			{
 				$preg_ec = array(
-						preg_quote($this->_escape_char[0]), preg_quote($this->_escape_char[1]),
+						preg_quote($this->_escape_char[0], '/'), preg_quote($this->_escape_char[1], '/'),
 						$this->_escape_char[0], $this->_escape_char[1]
 						);
 			}
 			else
 			{
-				$preg_ec[0] = $preg_ec[1] = preg_quote($this->_escape_char);
+				$preg_ec[0] = $preg_ec[1] = preg_quote($this->_escape_char, '/');
 				$preg_ec[2] = $preg_ec[3] = $this->_escape_char;
 			}
 		}
@@ -1069,44 +1335,20 @@
 	 */
 	public function update_string($table, $data, $where)
 	{
-		if ($where === '')
+		if (empty($where))
 		{
 			return FALSE;
 		}
 
+		$this->where($where);
+
 		$fields = array();
 		foreach ($data as $key => $val)
 		{
 			$fields[$this->protect_identifiers($key)] = $this->escape($val);
 		}
 
-		if ( ! is_array($where))
-		{
-			$dest = array($where);
-		}
-		else
-		{
-			$dest = array();
-			foreach ($where as $key => $val)
-			{
-				$prefix = (count($dest) === 0) ? '' : ' AND ';
-				$key = $this->protect_identifiers($key);
-
-				if ($val !== '')
-				{
-					if ( ! $this->_has_operator($key))
-					{
-						$key .= ' =';
-					}
-
-					$val = ' '.$this->escape($val);
-				}
-
-				$dest[] = $prefix.$key.$val;
-			}
-		}
-
-		return $this->_update($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields, $dest);
+		return $this->_update($this->protect_identifiers($table, TRUE, NULL, FALSE), $fields);
 	}
 
 	// --------------------------------------------------------------------
@@ -1118,30 +1360,19 @@
 	 *
 	 * @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
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
 		foreach ($values as $key => $val)
 		{
 			$valstr[] = $key.' = '.$val;
 		}
 
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
 		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
-			.$where
-			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '')
-			.($limit ? ' LIMIT '.$limit : '');
+			.$this->_compile_wh('qb_where')
+			.$this->_compile_order_by()
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
 	}
 
 	// --------------------------------------------------------------------
@@ -1154,7 +1385,7 @@
 	 */
 	protected function _has_operator($str)
 	{
-		return (bool) preg_match('/(\s|<|>|!|=|IS NULL|IS NOT NULL|BETWEEN)/i', trim($str));
+		return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str));
 	}
 
 	// --------------------------------------------------------------------
@@ -1167,8 +1398,30 @@
 	 */
 	protected function _get_operator($str)
 	{
-		return preg_match('/(=|!|<|>| IS NULL| IS NOT NULL| BETWEEN)/i', $str, $match)
-			? $match[1] : FALSE;
+		static $_operators;
+
+		if (empty($_operators))
+		{
+			$_les = ($this->_like_escape_str !== '')
+				? '\s+'.preg_quote(trim(sprintf($this->_like_escape_str, $this->_like_escape_chr)), '/')
+				: '';
+			$_operators = array(
+				'\s*(?:<|>|!)?=\s*',		// =, <=, >=, !=
+				'\s*<>?\s*',			// <, <>
+				'\s*>\s*',			// >
+				'\s+IS NULL',			// IS NULL
+				'\s+IS NOT NULL',		// IS NOT NULL
+				'\s+BETWEEN\s+\S+\s+AND\s+\S+',	// BETWEEN value AND value
+				'\s+IN\s*\([^\)]+\)',		// IN(list)
+				'\s+NOT IN\s*\([^\)]+\)',	// NOT IN (list)
+				'\s+LIKE\s+\S+'.$_les,		// LIKE 'expr'[ ESCAPE '%s']
+				'\s+NOT LIKE\s+\S+'.$_les	// NOT LIKE 'expr'[ ESCAPE '%s']
+			);
+
+		}
+
+		return preg_match('/'.implode('|', $_operators).'/i', $str, $match)
+			? $match[0] : FALSE;
 	}
 
 	// --------------------------------------------------------------------
@@ -1176,8 +1429,7 @@
 	/**
 	 * Enables a native PHP function to be run, using a platform agnostic wrapper.
 	 *
-	 * @param	string	the function name
-	 * @param	mixed	any parameters needed by the function
+	 * @param	string	$function	Function name
 	 * @return	mixed
 	 */
 	public function call_function($function)
@@ -1241,6 +1493,8 @@
 	/**
 	 * Delete the cache files associated with a particular URI
 	 *
+	 * @param	string	$segment_one = ''
+	 * @param	string	$segment_two = ''
 	 * @return	bool
 	 */
 	public function cache_delete($segment_one = '', $segment_two = '')
@@ -1342,7 +1596,7 @@
 		}
 		else
 		{
-			$message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
+			$message = is_array($error) ? $error : array(str_replace('%s', $swap, $LANG->line($error)));
 		}
 
 		// Find the most likely culprit of the error by going through
@@ -1351,12 +1605,21 @@
 		$trace = debug_backtrace();
 		foreach ($trace as $call)
 		{
-			if (isset($call['file']) && strpos($call['file'], BASEPATH.'database') === FALSE)
+			if (isset($call['file'], $call['class']))
 			{
-				// Found it - use a relative path for safety
-				$message[] = 'Filename: '.str_replace(array(APPPATH, BASEPATH), '', $call['file']);
-				$message[] = 'Line Number: '.$call['line'];
-				break;
+				// We'll need this on Windows, as APPPATH and BASEPATH will always use forward slashes
+				if (DIRECTORY_SEPARATOR !== '/')
+				{
+					$call['file'] = str_replace('\\', '/', $call['file']);
+				}
+
+				if (strpos($call['file'], BASEPATH.'database') === FALSE && strpos($call['class'], 'Loader') === FALSE)
+				{
+					// Found it - use a relative path for safety
+					$message[] = 'Filename: '.str_replace(array(APPPATH, BASEPATH), '', $call['file']);
+					$message[] = 'Line Number: '.$call['line'];
+					break;
+				}
 			}
 		}
 
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index 91f9d56..59c3baf 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Database Forge Class
@@ -34,22 +35,127 @@
  */
 abstract class CI_DB_forge {
 
+	/**
+	 * Database object
+	 *
+	 * @var	object
+	 */
+	protected $db;
+
+	/**
+	 * Fields data
+	 *
+	 * @var	array
+	 */
 	public $fields		= array();
+
+	/**
+	 * Keys data
+	 *
+	 * @var	array
+	 */
 	public $keys		= array();
+
+	/**
+	 * Primary Keys data
+	 *
+	 * @var	array
+	 */
 	public $primary_keys	= array();
-	public $db_char_set	=	'';
 
-	// Platform specific SQL strings
+	/**
+	 * Database character set
+	 *
+	 * @var	string
+	 */
+	public $db_char_set	= '';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_create_database	= 'CREATE DATABASE %s';
-	protected $_drop_database	= 'DROP DATABASE %s';
-	protected $_drop_table		= 'DROP TABLE IF EXISTS %s';
-	protected $_rename_table	= 'ALTER TABLE %s RENAME TO %s';
 
-	public function __construct()
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= 'DROP DATABASE %s';
+
+	/**
+	 * CREATE TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table	= "%s %s (%s\n)";
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= FALSE;
+
+	/**
+	 * DROP TABLE IF EXISTS statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= 'ALTER TABLE %s RENAME TO %s;';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= TRUE;
+
+	/**
+	 * NULL value representatin in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= '';
+
+	/**
+	 * DEFAULT value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_default		= ' DEFAULT ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
 	{
-		// Assign the main database object to $this->db
-		$CI =& get_instance();
-		$this->db =& $CI->db;
+		$this->db =& $db;
 		log_message('debug', 'Database Forge Class Initialized');
 	}
 
@@ -58,14 +164,14 @@
 	/**
 	 * Create database
 	 *
-	 * @param	string	the database name
+	 * @param	string	$db_name
 	 * @return	bool
 	 */
 	public function create_database($db_name)
 	{
 		if ($this->_create_database === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 		elseif ( ! $this->db->query(sprintf($this->_create_database, $db_name, $this->db->char_set, $this->db->dbcollat)))
 		{
@@ -85,7 +191,7 @@
 	/**
 	 * Drop database
 	 *
-	 * @param	string	the database name
+	 * @param	string	$db_name
 	 * @return	bool
 	 */
 	public function drop_database($db_name)
@@ -97,7 +203,7 @@
 		}
 		elseif ($this->_drop_database === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 		elseif ( ! $this->db->query(sprintf($this->_drop_database, $db_name)))
 		{
@@ -121,25 +227,25 @@
 	/**
 	 * Add Key
 	 *
-	 * @param	string	key
-	 * @param	string	type
+	 * @param	string	$key
+	 * @param	bool	$primary
 	 * @return	object
 	 */
 	public function add_key($key = '', $primary = FALSE)
 	{
-		if (is_array($key))
+		if (empty($key))
+		{
+			show_error('Key information is required for that operation.');
+		}
+
+		if ($primary === TRUE && is_array($key))
 		{
 			foreach ($key as $one)
 			{
 				$this->add_key($one, $primary);
 			}
 
-			return;
-		}
-
-		if ($key === '')
-		{
-			show_error('Key information is required for that operation.');
+			return $this;
 		}
 
 		if ($primary === TRUE)
@@ -159,12 +265,12 @@
 	/**
 	 * Add Field
 	 *
-	 * @param	string	collation
+	 * @param	array	$field
 	 * @return	object
 	 */
 	public function add_field($field = '')
 	{
-		if ($field === '')
+		if (empty($field))
 		{
 			show_error('Field information is required.');
 		}
@@ -206,7 +312,8 @@
 	/**
 	 * Create Table
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_not_exists	Whether to add IF NOT EXISTS condition
 	 * @return	bool
 	 */
 	public function create_table($table = '', $if_not_exists = FALSE)
@@ -215,51 +322,129 @@
 		{
 			show_error('A table name is required for that operation.');
 		}
+		else
+		{
+			$table = $this->db->dbprefix.$table;
+		}
 
 		if (count($this->fields) === 0)
 		{
 			show_error('Field information is required.');
 		}
 
-		$sql = $this->_create_table($this->db->dbprefix.$table, $this->fields, $this->primary_keys, $this->keys, $if_not_exists);
-		$this->_reset();
+		$sql = $this->_create_table($table, $if_not_exists);
 
 		if (is_bool($sql))
 		{
-			return $sql;
+			$this->_reset();
+			if ($sql === FALSE)
+			{
+				return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+			}
 		}
 
-		if (($result = $this->db->query($sql)) !== FALSE && ! empty($this->db->data_cache['table_names']))
+		if (($result = $this->db->query($sql)) !== FALSE)
 		{
-			$this->db->data_cache['table_names'][] = $this->db->dbprefix.$table;
+			empty($this->db->data_cache['table_names']) OR $this->db->data_cache['table_names'][] = $table;
+
+			// Most databases don't support creating indexes from within the CREATE TABLE statement
+			if ( ! empty($this->keys))
+			{
+				for ($i = 0, $sqls = $this->_process_indexes($table), $c = count($sqls); $i < $c; $i++)
+				{
+					$this->db->query($sqls[$i]);
+				}
+			}
 		}
 
+		$this->_reset();
 		return $result;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
+	 * Create Table
+	 *
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_not_exists	Whether to add 'IF NOT EXISTS' condition
+	 * @return	mixed
+	 */
+	protected function _create_table($table, $if_not_exists)
+	{
+		if ($if_not_exists === TRUE && $this->_create_table_if === FALSE)
+		{
+			if ($this->db->table_exists($table))
+			{
+				return TRUE;
+			}
+			else
+			{
+				$if_not_exists = FALSE;
+			}
+		}
+
+		$sql = ($if_not_exists)
+			? sprintf($this->_create_table_if, $this->db->escape_identifiers($table))
+			: 'CREATE TABLE';
+
+		$columns = $this->_process_fields(TRUE);
+		for ($i = 0, $c = count($columns); $i < $c; $i++)
+		{
+			$columns[$i] = ($columns[$i]['_literal'] !== FALSE)
+					? "\n\t".$columns[$i]['_literal']
+					: "\n\t".$this->_process_column($columns[$i]);
+		}
+
+		$columns = implode(',', $columns)
+				.$this->_process_primary_keys($table);
+
+		// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
+		if ($this->_create_table_keys === TRUE)
+		{
+			$columns .= $this->_process_indexes($table);
+		}
+
+		// _create_table will usually have the following format: "%s %s (%s\n)"
+		$sql = sprintf($this->_create_table.';',
+			$sql,
+			$this->db->escape_identifiers($table),
+			$columns
+		);
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Drop Table
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table_name	Table name
+	 * @param	bool	$if_exists	Whether to add an IF EXISTS condition
 	 * @return	bool
 	 */
-	public function drop_table($table_name)
+	public function drop_table($table_name, $if_exists = FALSE)
 	{
 		if ($table_name === '')
 		{
 			return ($this->db->db_debug) ? $this->db->display_error('db_table_name_required') : FALSE;
 		}
-		elseif ($this->_drop_table === FALSE)
+
+		$query = $this->_drop_table($this->db->dbprefix.$table_name, $if_exists);
+		if ($query === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+		elseif ($query === TRUE)
+		{
+			return TRUE;
 		}
 
-		$result = $this->db->query(sprintf($this->_drop_table, $this->db->escape_identifiers($this->db->dbprefix.$table_name)));
+		$query = $this->db->query($query);
 
 		// Update table list cache
-		if ($result && ! empty($this->db->data_cache['table_names']))
+		if ($query && ! empty($this->db->data_cache['table_names']))
 		{
 			$key = array_search(strtolower($this->db->dbprefix.$table_name), array_map('strtolower', $this->db->data_cache['table_names']), TRUE);
 			if ($key !== FALSE)
@@ -268,7 +453,40 @@
 			}
 		}
 
-		return $result;
+		return $query;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * Generates a platform-specific DROP TABLE string
+	 *
+	 * @param	string	$table		Table name
+	 * @param	bool	$if_exists	Whether to add an IF EXISTS condition
+	 * @return	string
+	 */
+	protected function _drop_table($table, $if_exists)
+	{
+		$sql = 'DROP TABLE';
+
+		if ($if_exists)
+		{
+			if ($this->_drop_table_if === FALSE)
+			{
+				if ( ! $this->db->table_exists($table))
+				{
+					return TRUE;
+				}
+			}
+			else
+			{
+				$sql = sprintf($this->_drop_table_if, $this->db->escape_identifiers($table));
+			}
+		}
+
+		return $sql.' '.$this->db->escape_identifiers($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -276,8 +494,8 @@
 	/**
 	 * Rename Table
 	 *
-	 * @param	string	the old table name
-	 * @param	string	the new table name
+	 * @param	string	$table_name	Old table name
+	 * @param	string	$new_table_name	New table name
 	 * @return	bool
 	 */
 	public function rename_table($table_name, $new_table_name)
@@ -289,7 +507,7 @@
 		}
 		elseif ($this->_rename_table === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 
 		$result = $this->db->query(sprintf($this->_rename_table,
@@ -314,33 +532,46 @@
 	/**
 	 * Column Add
 	 *
-	 * @param	string	the table name
-	 * @param	string	the column name
-	 * @param	string	the column definition
+	 * @todo	Remove deprecated $_after option in 3.1+
+	 * @param	string	$table	Table name
+	 * @param	array	$field	Column definition
+	 * @param	string	$_after	Column for AFTER clause (deprecated)
 	 * @return	bool
 	 */
-	public function add_column($table = '', $field = array(), $after_field = '')
+	public function add_column($table = '', $field = array(), $_after = NULL)
 	{
 		if ($table === '')
 		{
 			show_error('A table name is required for that operation.');
 		}
 
-		// add field info into field array, but we can only do one at a time
-		// so we cycle through
+		// Work-around for literal column definitions
+		if ( ! is_array($field))
+		{
+			$field = array($field);
+		}
+
 		foreach (array_keys($field) as $k)
 		{
-			$this->add_field(array($k => $field[$k]));
-
-			if (count($this->fields) === 0)
+			// Backwards-compatibility work-around for MySQL/CUBRID AFTER clause (remove in 3.1+)
+			if ($_after !== NULL && is_array($field[$k]) && ! isset($field[$k]['after']))
 			{
-				show_error('Field information is required.');
+				$field[$k]['after'] = $_after;
 			}
 
-			$sql = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->fields, $after_field);
-			$this->_reset();
+			$this->add_field(array($k => $field[$k]));
+		}
 
-			if ($this->db->query($sql) === FALSE)
+		$sqls = $this->_alter_table('ADD', $this->db->dbprefix.$table, $this->_process_fields());
+		$this->_reset();
+		if ($sqls === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		for ($i = 0, $c = count($sqls); $i < $c; $i++)
+		{
+			if ($this->db->query($sqls[$i]) === FALSE)
 			{
 				return FALSE;
 			}
@@ -354,8 +585,8 @@
 	/**
 	 * Column Drop
 	 *
-	 * @param	string	the table name
-	 * @param	string	the column name
+	 * @param	string	$table		Table name
+	 * @param	string	$column_name	Column name
 	 * @return	bool
 	 */
 	public function drop_column($table = '', $column_name = '')
@@ -370,7 +601,13 @@
 			show_error('A column name is required for that operation.');
 		}
 
-		return $this->db->query($this->_alter_table('DROP', $this->db->dbprefix.$table, $column_name));
+		$sql = $this->_alter_table('DROP', $this->db->dbprefix.$table, $column_name);
+		if ($sql === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		return $this->db->query($sql);
 	}
 
 	// --------------------------------------------------------------------
@@ -378,9 +615,8 @@
 	/**
 	 * Column Modify
 	 *
-	 * @param	string	the table name
-	 * @param	string	the column name
-	 * @param	string	the column definition
+	 * @param	string	$table	Table name
+	 * @param	string	$field	Column definition
 	 * @return	bool
 	 */
 	public function modify_column($table = '', $field = array())
@@ -390,26 +626,32 @@
 			show_error('A table name is required for that operation.');
 		}
 
-		// add field info into field array, but we can only do one at a time
-		// so we cycle through
+		// Work-around for literal column definitions
+		if ( ! is_array($field))
+		{
+			$field = array($field);
+		}
+
 		foreach (array_keys($field) as $k)
 		{
-			// If no name provided, use the current name
-			if ( ! isset($field[$k]['name']))
-			{
-				$field[$k]['name'] = $k;
-			}
-
 			$this->add_field(array($k => $field[$k]));
-			if (count($this->fields) === 0)
-			{
-				show_error('Field information is required.');
-			}
+		}
 
-			$sql = $this->_alter_table('CHANGE', $this->db->dbprefix.$table, $this->fields);
-			$this->_reset();
+		if (count($this->fields) === 0)
+		{
+			show_error('Field information is required.');
+		}
 
-			if ($this->db->query($sql) === FALSE)
+		$sqls = $this->_alter_table('CHANGE', $this->db->dbprefix.$table, $this->_process_fields());
+		$this->_reset();
+		if ($sqls === FALSE)
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+		}
+
+		for ($i = 0, $c = count($sqls); $i < $c; $i++)
+		{
+			if ($this->db->query($sqls[$i]) === FALSE)
 			{
 				return FALSE;
 			}
@@ -421,6 +663,342 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ';
+
+		// DROP has everything it needs now.
+		if ($alter_type === 'DROP')
+		{
+			return $sql.'DROP COLUMN '.$this->db->escape_identifiers($field);
+		}
+
+		$sqls = array();
+		for ($i = 0, $c = count($field), $sql .= $alter_type.' COLUMN '; $i < $c; $i++)
+		{
+			$sqls[] = $sql
+				.($field[$i]['_literal'] !== FALSE ? $field[$i]['_literal'] : $this->_process_column($field[$i]));
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process fields
+	 *
+	 * @param	bool	$create_table
+	 * @return	array
+	 */
+	protected function _process_fields($create_table = FALSE)
+	{
+		$fields = array();
+
+		foreach ($this->fields as $key => $attributes)
+		{
+			if (is_int($key) && ! is_array($attributes))
+			{
+				$fields[] = array('_literal' => $attributes);
+				continue;
+			}
+
+			$attributes = array_change_key_case($attributes, CASE_UPPER);
+
+			if ($create_table === TRUE && empty($attributes['TYPE']))
+			{
+				continue;
+			}
+
+			if (isset($attributes['TYPE']))
+			{
+				$this->_attr_type($attributes);
+				$this->_attr_unsigned($attributes, $field);
+			}
+
+			$field = array(
+					'name'			=> $key,
+					'new_name'		=> isset($attributes['NAME']) ? $attributes['NAME'] : NULL,
+					'type'			=> isset($attributes['TYPE']) ? $attributes['TYPE'] : NULL,
+					'length'		=> '',
+					'unsigned'		=> '',
+					'null'			=> '',
+					'unique'		=> '',
+					'default'		=> '',
+					'auto_increment'	=> '',
+					'_literal'		=> FALSE
+			);
+
+			$this->_attr_default($attributes, $field);
+
+			if (isset($attributes['NULL']))
+			{
+				if ($attributes['NULL'] === TRUE)
+				{
+					$field['null'] = empty($this->_null) ? '' : ' '.$this->_null;
+				}
+				elseif ($create_table === TRUE)
+				{
+					$field['null'] = ' NOT NULL';
+				}
+			}
+
+			$this->_attr_auto_increment($attributes, $field);
+			$this->_attr_unique($attributes, $field);
+
+			if (isset($attributes['TYPE']) && ! empty($attributes['CONSTRAINT']))
+			{
+				switch (strtoupper($attributes['TYPE']))
+				{
+					case 'ENUM':
+					case 'SET':
+						$attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']);
+					default:
+						$field['length'] = is_array($attributes['CONSTRAINT'])
+								? "('".implode("','", $attributes['CONSTRAINT'])."')"
+								: '('.$attributes['CONSTRAINT'].')';
+						break;
+				}
+			}
+
+			$fields[] = $field;
+		}
+
+		return $fields;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['default']
+			.$field['null']
+			.$field['auto_increment']
+			.$field['unique'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		// Usually overriden by drivers
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNSIGNED
+	 *
+	 * Depending on the _unsigned property value:
+	 *
+	 *	- TRUE will always set $field['unsigned'] to 'UNSIGNED'
+	 *	- FALSE will always set $field['unsigned'] to ''
+	 *	- array(TYPE) will set $field['unsigned'] to 'UNSIGNED',
+	 *		if $attributes['TYPE'] is found in the array
+	 *	- array(TYPE => UTYPE) will change $field['type'],
+	 *		from TYPE to UTYPE in case of a match
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unsigned(&$attributes, &$field)
+	{
+		if (empty($attributes['UNSIGNED']) OR $attributes['UNSIGNED'] !== TRUE)
+		{
+			return;
+		}
+
+		// Reset the attribute in order to avoid issues if we do type conversion
+		$attributes['UNSIGNED'] = FALSE;
+
+		if (is_array($this->_unsigned))
+		{
+			foreach (array_keys($this->_unsigned) as $key)
+			{
+				if (is_int($key) && strcasecmp($attributes['TYPE'], $this->_unsigned[$key]) === 0)
+				{
+					$field['unsigned'] = ' UNSIGNED';
+					return;
+				}
+				elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0)
+				{
+					$field['type'] = $key;
+					return;
+				}
+			}
+
+			return;
+		}
+
+		$field['unsigned'] = ($this->_unsigned === TRUE) ? ' UNSIGNED' : '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute DEFAULT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_default(&$attributes, &$field)
+	{
+		if ($this->_default === FALSE)
+		{
+			return;
+		}
+
+		if (array_key_exists('DEFAULT', $attributes))
+		{
+			if ($attributes['DEFAULT'] === NULL)
+			{
+				$field['default'] = empty($this->_null) ? '' : $this->_default.$this->_null;
+
+				// Override the NULL attribute if that's our default
+				$attributes['NULL'] = NULL;
+				$field['null'] = empty($this->_null) ? '' : ' '.$this->_null;
+			}
+			else
+			{
+				$field['default'] = $this->_default.$this->db->escape($attributes['DEFAULT']);
+			}
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNIQUE
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unique(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
+		{
+			$field['unique'] = ' UNIQUE';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' AUTO_INCREMENT';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process primary keys
+	 *
+	 * @param	string	$table	Table name
+	 * @return	string
+	 */
+	protected function _process_primary_keys($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->primary_keys); $i < $c; $i++)
+		{
+			if ( ! isset($this->fields[$this->primary_keys[$i]]))
+			{
+				unset($this->primary_keys[$i]);
+			}
+		}
+
+		if (count($this->primary_keys) > 0)
+		{
+			$sql .= ",\n\tCONSTRAINT ".$this->db->escape_identifiers('pk_'.$table)
+				.' PRIMARY KEY('.implode(', ', $this->db->escape_identifiers($this->primary_keys)).')';
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$table = $this->db->escape_identifiers($table);
+		$sqls = array();
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sqls[] = 'CREATE INDEX '.$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ON '.$this->db->escape_identifiers($table)
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).');';
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Reset
 	 *
 	 * Resets table creation vars
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 479b7f2..b0e86ed 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Query Builder Class
@@ -39,44 +40,213 @@
 
 abstract class CI_DB_query_builder extends CI_DB_driver {
 
+	/**
+	 * Return DELETE SQL flag
+	 *
+	 * @var	bool
+	 */
 	protected $return_delete_sql		= FALSE;
+
+	/**
+	 * Reset DELETE data flag
+	 *
+	 * @var	bool
+	 */
 	protected $reset_delete_data		= FALSE;
 
+	/**
+	 * QB SELECT data
+	 *
+	 * @var	array
+	 */
 	protected $qb_select			= array();
+
+	/**
+	 * QB DISTINCT flag
+	 *
+	 * @var	bool
+	 */
 	protected $qb_distinct			= FALSE;
+
+	/**
+	 * QB FROM data
+	 *
+	 * @var	array
+	 */
 	protected $qb_from			= array();
+
+	/**
+	 * QB JOIN data
+	 *
+	 * @var	array
+	 */
 	protected $qb_join			= array();
+
+	/**
+	 * QB WHERE data
+	 *
+	 * @var	array
+	 */
 	protected $qb_where			= array();
-	protected $qb_like			= array();
+
+	/**
+	 * QB GROUP BY data
+	 *
+	 * @var	array
+	 */
 	protected $qb_groupby			= array();
+
+	/**
+	 * QB HAVING data
+	 *
+	 * @var	array
+	 */
 	protected $qb_having			= array();
+
+	/**
+	 * QB keys
+	 *
+	 * @var	array
+	 */
 	protected $qb_keys			= array();
+
+	/**
+	 * QB LIMIT data
+	 *
+	 * @var	int
+	 */
 	protected $qb_limit			= FALSE;
+
+	/**
+	 * QB OFFSET data
+	 *
+	 * @var	int
+	 */
 	protected $qb_offset			= FALSE;
+
+	/**
+	 * QB ORDER BY data
+	 *
+	 * @var	array
+	 */
 	protected $qb_orderby			= array();
+
+	/**
+	 * QB data sets
+	 *
+	 * @var	array
+	 */
 	protected $qb_set			= array();
-	protected $qb_wherein			= array();
+
+	/**
+	 * QB aliased tables list
+	 *
+	 * @var	array
+	 */
 	protected $qb_aliased_tables		= array();
-	protected $qb_store_array		= array();
+
+	/**
+	 * QB WHERE group started flag
+	 *
+	 * @var	bool
+	 */
 	protected $qb_where_group_started	= FALSE;
+
+	/**
+	 * QB WHERE group count
+	 *
+	 * @var	int
+	 */
 	protected $qb_where_group_count		= 0;
 
 	// Query Builder Caching variables
+
+	/**
+	 * QB Caching flag
+	 *
+	 * @var	bool
+	 */
 	protected $qb_caching				= FALSE;
+
+	/**
+	 * QB Cache exists list
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_exists			= array();
+
+	/**
+	 * QB Cache SELECT data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_select			= array();
+
+	/**
+	 * QB Cache FROM data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_from			= array();
+
+	/**
+	 * QB Cache JOIN data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_join			= array();
+
+	/**
+	 * QB Cache WHERE data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_where			= array();
-	protected $qb_cache_like			= array();
+
+	/**
+	 * QB Cache GROUP BY data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_groupby			= array();
+
+	/**
+	 * QB Cache HAVING data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_having			= array();
+
+	/**
+	 * QB Cache ORDER BY data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_orderby			= array();
+
+	/**
+	 * QB Cache data sets
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_set				= array();
 
+	/**
+	 * QB No Escape data
+	 *
+	 * @var	array
+	 */
 	protected $qb_no_escape 			= array();
+
+	/**
+	 * QB Cache No Escape data
+	 *
+	 * @var	array
+	 */
 	protected $qb_cache_no_escape			= array();
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Select
 	 *
@@ -184,15 +354,16 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Processing Function for the four functions above:
+	 * SELECT [MAX|MIN|AVG|SUM]()
 	 *
-	 *	select_max()
-	 *	select_min()
-	 *	select_avg()
-	 *	select_sum()
+	 * @used-by	select_max()
+	 * @used-by	select_min()
+	 * @used-by	select_avg()
+	 * @used-by	select_sum()
 	 *
-	 * @param	string	the field
-	 * @param	string	an alias
+	 * @param	string	$select	Field name
+	 * @param	string	$alias
+	 * @param	string	$type
 	 * @return	object
 	 */
 	protected function _max_min_avg_sum($select = '', $alias = '', $type = 'MAX')
@@ -233,7 +404,7 @@
 	/**
 	 * Determines the alias name based on the table
 	 *
-	 * @param	string
+	 * @param	string	$item
 	 * @return	string
 	 */
 	protected function _create_alias_from_table($item)
@@ -254,7 +425,7 @@
 	 *
 	 * Sets a flag which tells the query string compiler to add DISTINCT
 	 *
-	 * @param	bool
+	 * @param	bool	$val
 	 * @return	object
 	 */
 	public function distinct($val = TRUE)
@@ -270,7 +441,7 @@
 	 *
 	 * Generates the FROM portion of the query
 	 *
-	 * @param	mixed	can be a string or array
+	 * @param	mixed	$from	can be a string or array
 	 * @return	object
 	 */
 	public function from($from)
@@ -317,7 +488,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Join
+	 * JOIN
 	 *
 	 * Generates the JOIN portion of the query
 	 *
@@ -405,10 +576,10 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where
+	 * WHERE
 	 *
-	 * Generates the WHERE portion of the query. Separates
-	 * multiple calls with AND
+	 * Generates the WHERE portion of the query.
+	 * Separates multiple calls with 'AND'.
 	 *
 	 * @param	mixed
 	 * @param	mixed
@@ -417,16 +588,16 @@
 	 */
 	public function where($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_where($key, $value, 'AND ', $escape);
+		return $this->_wh('qb_where', $key, $value, 'AND ', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * OR Where
+	 * OR WHERE
 	 *
-	 * Generates the WHERE portion of the query. Separates
-	 * multiple calls with OR
+	 * Generates the WHERE portion of the query.
+	 * Separates multiple calls with 'OR'.
 	 *
 	 * @param	mixed
 	 * @param	mixed
@@ -435,24 +606,30 @@
 	 */
 	public function or_where($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_where($key, $value, 'OR ', $escape);
+		return $this->_wh('qb_where', $key, $value, 'OR ', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where
+	 * WHERE, HAVING
 	 *
-	 * Called by where() or or_where()
+	 * @used-by	where()
+	 * @used-by	or_where()
+	 * @used-by	having()
+	 * @used-by	or_having()
 	 *
-	 * @param	mixed
-	 * @param	mixed
-	 * @param	string
-	 * @param	mixed
+	 * @param	string	$qb_key	'qb_where' or 'qb_having'
+	 * @param	mixed	$key
+	 * @param	mixed	$value
+	 * @param	string	$type
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
+	protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL)
 	{
+		$qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where';
+
 		if ( ! is_array($key))
 		{
 			$key = array($key => $value);
@@ -463,23 +640,10 @@
 
 		foreach ($key as $k => $v)
 		{
-			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			$prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0)
 				? $this->_group_get_type('')
 				: $this->_group_get_type($type);
 
-			if ($escape === TRUE)
-			{
-				$k = (($op = $this->_get_operator($k)) !== FALSE)
-					? $this->escape_identifiers(trim(substr($k, 0, strpos($k, $op)))).' '.strstr($k, $op)
-					: $this->escape_identifiers(trim($k));
-			}
-
-			if (is_null($v) && ! $this->_has_operator($k))
-			{
-				// value appears not to have been set, assign the test to IS NULL
-				$k .= ' IS NULL';
-			}
-
 			if ( ! is_null($v))
 			{
 				if ($escape === TRUE)
@@ -492,12 +656,17 @@
 					$k .= ' = ';
 				}
 			}
+			elseif ( ! $this->_has_operator($k))
+			{
+				// value appears not to have been set, assign the test to IS NULL
+				$k .= ' IS NULL';
+			}
 
-			$this->qb_where[] = $prefix.$k.$v;
+			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_where[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'where';
+				$this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+				$this->qb_cache_exists[] = substr($qb_key, 3);
 			}
 
 		}
@@ -508,13 +677,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where_in
+	 * WHERE IN
 	 *
-	 * Generates a WHERE field IN ('item', 'item') SQL query joined with
-	 * AND if appropriate
+	 * Generates a WHERE field IN('item', 'item') SQL query,
+	 * joined with 'AND' if appropriate.
 	 *
-	 * @param	string	The field to search
-	 * @param	array	The values searched on
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	public function where_in($key = NULL, $values = NULL, $escape = NULL)
@@ -525,13 +695,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where_in_or
+	 * OR WHERE IN
 	 *
-	 * Generates a WHERE field IN ('item', 'item') SQL query joined with
-	 * OR if appropriate
+	 * Generates a WHERE field IN('item', 'item') SQL query,
+	 * joined with 'OR' if appropriate.
 	 *
-	 * @param	string	The field to search
-	 * @param	array	The values searched on
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	public function or_where_in($key = NULL, $values = NULL, $escape = NULL)
@@ -542,13 +713,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where_not_in
+	 * WHERE NOT IN
 	 *
-	 * Generates a WHERE field NOT IN ('item', 'item') SQL query joined
-	 * with AND if appropriate
+	 * Generates a WHERE field NOT IN('item', 'item') SQL query,
+	 * joined with 'AND' if appropriate.
 	 *
-	 * @param	string	The field to search
-	 * @param	array	The values searched on
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	public function where_not_in($key = NULL, $values = NULL, $escape = NULL)
@@ -559,13 +731,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where_not_in_or
+	 * OR WHERE NOT IN
 	 *
-	 * Generates a WHERE field NOT IN ('item', 'item') SQL query joined
-	 * with OR if appropriate
+	 * Generates a WHERE field NOT IN('item', 'item') SQL query,
+	 * joined with 'OR' if appropriate.
 	 *
-	 * @param	string	The field to search
-	 * @param	array	The values searched on
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	public function or_where_not_in($key = NULL, $values = NULL, $escape = NULL)
@@ -576,14 +749,18 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where_in
+	 * Internal WHERE IN
 	 *
-	 * Called by where_in, where_in_or, where_not_in, where_not_in_or
+	 * @used-by	where_in()
+	 * @used-by	or_where_in()
+	 * @used-by	where_not_in()
+	 * @used-by	or_where_not_in()
 	 *
-	 * @param	string	The field to search
-	 * @param	array	The values searched on
-	 * @param	bool	If the statement would be IN or NOT IN
-	 * @param	string
+	 * @param	string	$key	The field to search
+	 * @param	array	$values	The values searched on
+	 * @param	bool	$not	If the statement would be IN or NOT IN
+	 * @param	string	$type
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ', $escape = NULL)
@@ -602,153 +779,167 @@
 
 		$not = ($not) ? ' NOT' : '';
 
+		$where_in = array();
 		foreach ($values as $value)
 		{
-			$this->qb_wherein[] = $this->escape($value);
-		}
-
-		if ($escape === TRUE)
-		{
-			$key = $this->escape_identifiers(trim($key));
+			$where_in[] = $this->escape($value);
 		}
 
 		$prefix = (count($this->qb_where) === 0) ? $this->_group_get_type('') : $this->_group_get_type($type);
-		$this->qb_where[] = $where_in = $prefix.$key.$not.' IN ('.implode(', ', $this->qb_wherein).') ';
+		$where_in = array(
+			'condition' => $prefix.$key.$not.' IN('.implode(', ', $where_in).')',
+			'escape' => $escape
+		);
 
+		$this->qb_where[] = $where_in;
 		if ($this->qb_caching === TRUE)
 		{
 			$this->qb_cache_where[] = $where_in;
 			$this->qb_cache_exists[] = 'where';
 		}
 
-		// reset the array for multiple calls
-		$this->qb_wherein = array();
 		return $this;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Like
+	 * LIKE
 	 *
-	 * Generates a %LIKE% portion of the query. Separates
-	 * multiple calls with AND
+	 * Generates a %LIKE% portion of the query.
+	 * Separates multiple calls with 'AND'.
 	 *
-	 * @param	mixed
-	 * @param	mixed
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function like($field, $match = '', $side = 'both')
+	public function like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'AND ', $side);
+		return $this->_like($field, $match, 'AND ', $side, '', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Not Like
+	 * NOT LIKE
 	 *
-	 * Generates a NOT LIKE portion of the query. Separates
-	 * multiple calls with AND
+	 * Generates a NOT LIKE portion of the query.
+	 * Separates multiple calls with 'AND'.
 	 *
-	 * @param	mixed
-	 * @param	mixed
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function not_like($field, $match = '', $side = 'both')
+	public function not_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'AND ', $side, 'NOT');
+		return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * OR Like
+	 * OR LIKE
 	 *
-	 * Generates a %LIKE% portion of the query. Separates
-	 * multiple calls with OR
+	 * Generates a %LIKE% portion of the query.
+	 * Separates multiple calls with 'OR'.
 	 *
-	 * @param	mixed
-	 * @param	mixed
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function or_like($field, $match = '', $side = 'both')
+	public function or_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'OR ', $side);
+		return $this->_like($field, $match, 'OR ', $side, '', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * OR Not Like
+	 * OR NOT LIKE
 	 *
-	 * Generates a NOT LIKE portion of the query. Separates
-	 * multiple calls with OR
+	 * Generates a NOT LIKE portion of the query.
+	 * Separates multiple calls with 'OR'.
 	 *
-	 * @param	mixed
-	 * @param	mixed
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$side
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function or_not_like($field, $match = '', $side = 'both')
+	public function or_not_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'OR ', $side, 'NOT');
+		return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Like
+	 * Internal LIKE
 	 *
-	 * Called by like() or orlike()
+	 * @used-by	like()
+	 * @used-by	or_like()
+	 * @used-by	not_like()
+	 * @used-by	or_not_like()
 	 *
-	 * @param	mixed
-	 * @param	mixed
-	 * @param	string
+	 * @param	mixed	$field
+	 * @param	string	$match
+	 * @param	string	$type
+	 * @param	string	$side
+	 * @param	string	$not
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '')
+	protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = NULL)
 	{
 		if ( ! is_array($field))
 		{
 			$field = array($field => $match);
 		}
 
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
 		foreach ($field as $k => $v)
 		{
-			$k = $this->protect_identifiers($k);
-			$prefix = (count($this->qb_like) === 0) ? $this->_group_get_type('') : $this->_group_get_type($type);
+			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+				? $this->_group_get_type('') : $this->_group_get_type($type);
+
 			$v = $this->escape_like_str($v);
 
 			if ($side === 'none')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '{$v}'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}'";
 			}
 			elseif ($side === 'before')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '%{$v}'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}'";
 			}
 			elseif ($side === 'after')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '{$v}%'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}%'";
 			}
 			else
 			{
-				$like_statement = "{$prefix} $k $not LIKE '%{$v}%'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}%'";
 			}
 
 			// some platforms require an escape sequence definition for LIKE wildcards
 			if ($this->_like_escape_str !== '')
 			{
-				$like_statement = $like_statement.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+				$like_statement .= sprintf($this->_like_escape_str, $this->_like_escape_chr);
 			}
 
-			$this->qb_like[] = $like_statement;
+			$this->qb_where[] = array('condition' => $like_statement, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_like[] = $like_statement;
-				$this->qb_cache_exists[] = 'like';
+				$this->qb_cache_where[] = $like_statement;
+				$this->qb_cache_exists[] = 'where';
 			}
-
 		}
 
 		return $this;
@@ -759,8 +950,8 @@
 	/**
 	 * Starts a query group.
 	 *
-	 * @param	string (Internal use only)
-	 * @param	string (Internal use only)
+	 * @param	string	$not	(Internal use only)
+	 * @param	string	$type	(Internal use only)
 	 * @return	object
 	 */
 	public function group_start($not = '', $type = 'AND ')
@@ -769,11 +960,15 @@
 
 		$this->qb_where_group_started = TRUE;
 		$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) ? '' : $type;
-		$this->qb_where[] = $value = $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (';
+		$where = array(
+			'condition' => $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (',
+			'escape' => FALSE
+		);
 
+		$this->qb_where[] = $where;
 		if ($this->qb_caching)
 		{
-			$this->qb_cache_where[] = $value;
+			$this->qb_cache_where[] = $where;
 		}
 
 		return $this;
@@ -825,11 +1020,15 @@
 	public function group_end()
 	{
 		$this->qb_where_group_started = FALSE;
-		$this->qb_where[] = $value = str_repeat(' ', $this->qb_where_group_count--) . ')';
+		$where = array(
+			'condition' => str_repeat(' ', $this->qb_where_group_count--).')',
+			'escape' => FALSE
+		);
 
+		$this->qb_where[] = $where;
 		if ($this->qb_caching)
 		{
-			$this->qb_cache_where[] = $value;
+			$this->qb_cache_where[] = $where;
 		}
 
 		return $this;
@@ -840,9 +1039,12 @@
 	/**
 	 * Group_get_type
 	 *
-	 * Called by group_start(), _like(), _where() and _where_in()
+	 * @used-by	group_start()
+	 * @used-by	_like()
+	 * @used-by	_wh()
+	 * @used-by	_where_in()
 	 *
-	 * @param	string
+	 * @param	string	$type
 	 * @return	string
 	 */
 	protected function _group_get_type($type)
@@ -861,14 +1063,19 @@
 	/**
 	 * GROUP BY
 	 *
-	 * @param	string
+	 * @param	string	$by
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function group_by($by)
+	public function group_by($by, $escape = NULL)
 	{
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
 		if (is_string($by))
 		{
-			$by = explode(',', $by);
+			$by = ($escape === TRUE)
+				? explode(',', $by)
+				: array($by);
 		}
 
 		foreach ($by as $val)
@@ -877,8 +1084,9 @@
 
 			if ($val !== '')
 			{
-				$this->qb_groupby[] = $val = $this->protect_identifiers($val);
+				$val = array('field' => $val, 'escape' => $escape);
 
+				$this->qb_groupby[] = $val;
 				if ($this->qb_caching === TRUE)
 				{
 					$this->qb_cache_groupby[] = $val;
@@ -893,142 +1101,90 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Sets the HAVING value
+	 * HAVING
 	 *
-	 * Separates multiple calls with AND
+	 * Separates multiple calls with 'AND'.
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	bool
+	 * @param	string	$key
+	 * @param	string	$value
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function having($key, $value = '', $escape = NULL)
+	public function having($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_having($key, $value, 'AND ', $escape);
+		return $this->_wh('qb_having', $key, $value, 'AND ', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Sets the OR HAVING value
+	 * OR HAVING
 	 *
-	 * Separates multiple calls with OR
+	 * Separates multiple calls with 'OR'.
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	bool
+	 * @param	string	$key
+	 * @param	string	$value
+	 * @param	bool	$escape
 	 * @return	object
 	 */
-	public function or_having($key, $value = '', $escape = NULL)
+	public function or_having($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_having($key, $value, 'OR ', $escape);
+		return $this->_wh('qb_having', $key, $value, 'OR ', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Sets the HAVING values
+	 * ORDER BY
 	 *
-	 * Called by having() or or_having()
-	 *
-	 * @param	string
-	 * @param	string
-	 * @param	string
-	 * @param	bool
-	 * @return	object
-	 */
-	protected function _having($key, $value = '', $type = 'AND ', $escape = NULL)
-	{
-		if ( ! is_array($key))
-		{
-			$key = array($key => $value);
-		}
-
-		is_bool($escape) OR $escape = $this->_protect_identifiers;
-
-		foreach ($key as $k => $v)
-		{
-			$prefix = (count($this->qb_having) === 0) ? '' : $type;
-
-			$k = $this->_has_operator($k)
-				? $this->protect_identifiers(substr($k, 0, strpos(rtrim($k), ' ')), FALSE, $escape).strchr(rtrim($k), ' ')
-				: $this->protect_identifiers($k, FALSE, $escape);
-
-			if ( ! $this->_has_operator($k))
-			{
-				$k .= ' = ';
-			}
-
-			if ($v !== '')
-			{
-				$v = ' '.$this->escape($v);
-			}
-
-			$this->qb_having[] = $prefix.$k.$v;
-			if ($this->qb_caching === TRUE)
-			{
-				$this->qb_cache_having[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'having';
-			}
-		}
-
-		return $this;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Sets the ORDER BY value
-	 *
-	 * @param	string
-	 * @param	string	direction: asc or desc
-	 * @param	bool	enable field name escaping
+	 * @param	string	$orderby
+	 * @param	string	$direction	ASC or DESC
+	 * @param	bool	$escape
 	 * @return	object
 	 */
 	public function order_by($orderby, $direction = '', $escape = NULL)
 	{
-		if (strtolower($direction) === 'random')
+		$direction = strtoupper(trim($direction));
+
+		if ($direction === 'RANDOM')
 		{
-			$orderby = ''; // Random results want or don't need a field name
-			$direction = $this->_random_keyword;
+			$direction = '';
+
+			// Do we have a seed value?
+			$orderby = ctype_digit((string) $orderby)
+				? $orderby = sprintf($this->_random_keyword[1], $orderby)
+				: $this->_random_keyword[0];
 		}
-		elseif (trim($direction) !== '')
+		elseif (empty($orderby))
 		{
-			$direction = in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE) ? ' '.$direction : ' ASC';
+			return $this;
+		}
+		elseif ($direction !== '')
+		{
+			$direction = in_array($direction, array('ASC', 'DESC'), TRUE) ? ' '.$direction : '';
 		}
 
 		is_bool($escape) OR $escape = $this->_protect_identifiers;
 
-		if ($escape === TRUE && strpos($orderby, ',') !== FALSE)
+		if ($escape === FALSE)
 		{
-			$temp = array();
-			foreach (explode(',', $orderby) as $part)
+			$qb_orderby[] = array('field' => $orderby, 'direction' => $direction, 'escape' => FALSE);
+		}
+		else
+		{
+			$qb_orderby = array();
+			foreach (explode(',', $orderby) as $field)
 			{
-				$part = trim($part);
-				if ( ! in_array($part, $this->qb_aliased_tables))
-				{
-					$part = preg_match('/^(.+)\s+(ASC|DESC)$/i', $part, $matches)
-						? $this->protect_identifiers(rtrim($matches[1])).' '.$matches[2]
-						: $this->protect_identifiers($part);
-				}
-
-				$temp[] = $part;
+				$qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE))
+					? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE)
+					: array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE);
 			}
-
-			$orderby = implode(', ', $temp);
-		}
-		elseif ($direction !== $this->_random_keyword && $escape === TRUE)
-		{
-			$orderby = preg_match('/^(.+)\s+(ASC|DESC)$/i', $orderby, $matches)
-				? $this->protect_identifiers(rtrim($matches[1])).' '.$matches[2]
-				: $this->protect_identifiers($orderby);
 		}
 
-		$this->qb_orderby[] = $orderby_statement = $orderby.$direction;
-
+		$this->qb_orderby = array_merge($this->qb_orderby, $qb_orderby);
 		if ($this->qb_caching === TRUE)
 		{
-			$this->qb_cache_orderby[] = $orderby_statement;
+			$this->qb_cache_orderby = array_merge($this->qb_cache_orderby, $qb_orderby);
 			$this->qb_cache_exists[] = 'orderby';
 		}
 
@@ -1038,13 +1194,13 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Sets the LIMIT value
+	 * LIMIT
 	 *
-	 * @param	int	the limit value
-	 * @param	int	the offset value
+	 * @param	int	$value	LIMIT value
+	 * @param	int	$offset	OFFSET value
 	 * @return	object
 	 */
-	public function limit($value, $offset = NULL)
+	public function limit($value, $offset = FALSE)
 	{
 		is_null($value) OR $this->qb_limit = (int) $value;
 		empty($offset) OR $this->qb_offset = (int) $offset;
@@ -1057,7 +1213,7 @@
 	/**
 	 * Sets the OFFSET value
 	 *
-	 * @param	int	the offset value
+	 * @param	int	$offset	OFFSET value
 	 * @return	object
 	 */
 	public function offset($offset)
@@ -1069,18 +1225,16 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT string
 	 *
-	 * Generates a platform-specific LIMIT clause
+	 * Generates a platform-specific LIMIT clause.
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.($offset ? $offset.', ' : '').$limit;
+		return $sql.' LIMIT '.($this->qb_offset ? $this->qb_offset.', ' : '').$this->qb_limit;
 	}
 
 	// --------------------------------------------------------------------
@@ -1194,7 +1348,9 @@
 			$this->from($table);
 		}
 
-		$result = $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows')));
+		$result = ($this->qb_distinct === TRUE)
+			? $this->query($this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_compile_select()."\n) CI_count_all_results")
+			: $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows')));
 		$this->_reset_select();
 
 		if ($result->num_rows() === 0)
@@ -1213,9 +1369,10 @@
 	 *
 	 * Allows the where clause, limit and offset to be added directly
 	 *
-	 * @param	string	the where clause
-	 * @param	string	the limit clause
-	 * @param	string	the offset clause
+	 * @param	string	$table
+	 * @param	string	$where
+	 * @param	int	$limit
+	 * @param	int	$offset
 	 * @return	object
 	 */
 	public function get_where($table = '', $where = NULL, $limit = NULL, $offset = NULL)
@@ -1247,25 +1404,22 @@
 	 *
 	 * Compiles batch insert strings and runs the queries
 	 *
-	 * @param	string	the table to retrieve the results from
-	 * @param	array	an associative array of insert values
-	 * @return	object
+	 * @param	string	$table	Table to insert into
+	 * @param	array	$set 	An associative array of insert values
+	 * @param	bool	$escape	Whether to escape values and identifiers
+	 * @return	int	Number of rows inserted or FALSE on failure
 	 */
-	public function insert_batch($table = '', $set = NULL)
+	public function insert_batch($table = '', $set = NULL, $escape = NULL)
 	{
 		if ( ! is_null($set))
 		{
-			$this->set_insert_batch($set);
+			$this->set_insert_batch($set, '', $escape);
 		}
 
 		if (count($this->qb_set) === 0)
 		{
-			if ($this->db_debug)
-			{
-				// No valid data array. Folds in cases where keys and values did not match up
-				return $this->display_error('db_must_use_set');
-			}
-			return FALSE;
+			// No valid data array. Folds in cases where keys and values did not match up
+			return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
 		}
 
 		if ($table === '')
@@ -1279,25 +1433,27 @@
 		}
 
 		// Batch this baby
+		$affected_rows = 0;
 		for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
 		{
-			$this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, 100)));
+			$this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, 100)));
+			$affected_rows += $this->affected_rows();
 		}
 
 		$this->_reset_write();
-		return TRUE;
+		return $affected_rows;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Insert_batch statement
+	 * Insert batch statement
 	 *
 	 * Generates a platform-specific insert string from the supplied data.
 	 *
-	 * @param	string	the table name
-	 * @param	array	the insert keys
-	 * @param	array	the insert values
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
 	 * @return	string
 	 */
 	protected function _insert_batch($table, $keys, $values)
@@ -1352,7 +1508,7 @@
 				$row = $clean;
 			}
 
-			$this->qb_set[] =  '('.implode(',', $row).')';
+			$this->qb_set[] = '('.implode(',', $row).')';
 		}
 
 		foreach ($keys as $k)
@@ -1406,13 +1562,14 @@
 	 *
 	 * @param	string	the table to insert data into
 	 * @param	array	an associative array of insert values
+	 * @param	bool	$escape	Whether to escape values and identifiers
 	 * @return	object
 	 */
-	public function insert($table = '', $set = NULL)
+	public function insert($table = '', $set = NULL, $escape = NULL)
 	{
 		if ( ! is_null($set))
 		{
-			$this->set($set);
+			$this->set($set, '', $escape);
 		}
 
 		if ($this->_validate_insert($table) === FALSE)
@@ -1422,7 +1579,7 @@
 
 		$sql = $this->_insert(
 			$this->protect_identifiers(
-				$this->qb_from[0], TRUE, NULL, FALSE
+				$this->qb_from[0], TRUE, $escape, FALSE
 			),
 			array_keys($this->qb_set),
 			array_values($this->qb_set)
@@ -1522,19 +1679,18 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
+	 * FROM tables
 	 *
-	 * This public function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
 	 *
-	 * @param       array
-	 * @return      string
+	 * Note: This is only used (and overriden) by MySQL and CUBRID.
+	 *
+	 * @return	string
 	 */
-	protected function _from_tables($tables)
+	protected function _from_tables()
 	{
-		is_array($tables) OR $tables = array($tables);
-
-		return (count($tables) === 1) ? $tables[0] : '('.implode(', ', $tables).')';
+		return implode(', ', $this->qb_from);
 	}
 
 	// --------------------------------------------------------------------
@@ -1558,7 +1714,7 @@
 			return FALSE;
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set, $this->qb_where, $this->qb_orderby, $this->qb_limit);
+		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
 
 		if ($reset === TRUE)
 		{
@@ -1571,13 +1727,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Update
+	 * UPDATE
 	 *
-	 * Compiles an update string and runs the query
+	 * Compiles an update string and runs the query.
 	 *
-	 * @param	string	the table to retrieve the results from
-	 * @param	array	an associative array of update values
-	 * @param	mixed	the where clause
+	 * @param	string	$table
+	 * @param	array	$set	An associative array of update values
+	 * @param	mixed	$where
+	 * @param	int	$limit
 	 * @return	object
 	 */
 	public function update($table = '', $set = NULL, $where = NULL, $limit = NULL)
@@ -1605,8 +1762,7 @@
 			$this->limit($limit);
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set, $this->qb_where, $this->qb_orderby, $this->qb_limit, $this->qb_like);
-
+		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
 		$this->_reset_write();
 		return $this->query($sql);
 	}
@@ -1652,7 +1808,7 @@
 	 * @param	string	the table to retrieve the results from
 	 * @param	array	an associative array of update values
 	 * @param	string	the where key
-	 * @return	bool
+	 * @return	int	number of rows affected or FALSE on failure
 	 */
 	public function update_batch($table = '', $set = NULL, $index = NULL)
 	{
@@ -1685,13 +1841,15 @@
 		}
 
 		// Batch this baby
+		$affected_rows = 0;
 		for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
 		{
-			$this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, 100), $this->protect_identifiers($index), $this->qb_where));
+			$this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, 100), $this->protect_identifiers($index)));
+			$affected_rows += $this->affected_rows();
 		}
 
 		$this->_reset_write();
-		return TRUE;
+		return $affected_rows;
 	}
 
 	// --------------------------------------------------------------------
@@ -1852,7 +2010,7 @@
 	 * @param	mixed	the where clause
 	 * @param	mixed	the limit clause
 	 * @param	bool
-	 * @return	object
+	 * @return	mixed
 	 */
 	public function delete($table = '', $where = '', $limit = NULL, $reset_data = TRUE)
 	{
@@ -1872,10 +2030,8 @@
 		{
 			foreach ($table as $single_table)
 			{
-				$this->delete($single_table, $where, $limit, FALSE);
+				$this->delete($single_table, $where, $limit, $reset_data);
 			}
-
-			$this->_reset_write();
 			return;
 		}
 		else
@@ -1893,12 +2049,12 @@
 			$this->limit($limit);
 		}
 
-		if (count($this->qb_where) === 0 && count($this->qb_wherein) === 0 && count($this->qb_like) === 0)
+		if (count($this->qb_where) === 0)
 		{
 			return ($this->db_debug) ? $this->display_error('db_del_must_use_where') : FALSE;
 		}
 
-		$sql = $this->_delete($table, $this->qb_where, $this->qb_like, $this->qb_limit);
+		$sql = $this->_delete($table);
 		if ($reset_data)
 		{
 			$this->_reset_write();
@@ -1915,21 +2071,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table
-			.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '')
-			.($limit ? ' LIMIT '.(int) $limit : '');
+		return 'DELETE FROM '.$table.$this->_compile_wh('qb_where')
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
 	}
 
 	// --------------------------------------------------------------------
@@ -2018,8 +2165,9 @@
 	 * Compile the SELECT statement
 	 *
 	 * Generates a query string based on which functions were used.
-	 * Should not be called directly.  The get() function calls it.
+	 * Should not be called directly.
 	 *
+	 * @param	bool	$select_override
 	 * @return	string
 	 */
 	protected function _compile_select($select_override = FALSE)
@@ -2058,7 +2206,7 @@
 		// Write the "FROM" portion of the query
 		if (count($this->qb_from) > 0)
 		{
-			$sql .= "\nFROM ".$this->_from_tables($this->qb_from);
+			$sql .= "\nFROM ".$this->_from_tables();
 		}
 
 		// Write the "JOIN" portion of the query
@@ -2067,47 +2215,15 @@
 			$sql .= "\n".implode("\n", $this->qb_join);
 		}
 
-		// Write the "WHERE" portion of the query
-		if (count($this->qb_where) > 0 OR count($this->qb_like) > 0)
+		$sql .= $this->_compile_wh('qb_where')
+			.$this->_compile_group_by()
+			.$this->_compile_wh('qb_having')
+			.$this->_compile_order_by(); // ORDER BY
+
+		// LIMIT
+		if ($this->qb_limit)
 		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $this->qb_where);
-
-		// Write the "LIKE" portion of the query
-		if (count($this->qb_like) > 0)
-		{
-			if (count($this->qb_where) > 0)
-			{
-				$sql .= "\nAND ";
-			}
-
-			$sql .= implode("\n", $this->qb_like);
-		}
-
-		// Write the "GROUP BY" portion of the query
-		if (count($this->qb_groupby) > 0)
-		{
-			$sql .= "\nGROUP BY ".implode(', ', $this->qb_groupby);
-		}
-
-		// Write the "HAVING" portion of the query
-		if (count($this->qb_having) > 0)
-		{
-			$sql .= "\nHAVING ".implode("\n", $this->qb_having);
-		}
-
-		// Write the "ORDER BY" portion of the query
-		if (count($this->qb_orderby) > 0)
-		{
-			$sql .= "\nORDER BY ".implode(', ', $this->qb_orderby);
-		}
-
-		// Write the "LIMIT" portion of the query
-		if (is_numeric($this->qb_limit))
-		{
-			return $this->_limit($sql."\n", $this->qb_limit, $this->qb_offset);
+			return $this->_limit($sql."\n");
 		}
 
 		return $sql;
@@ -2116,6 +2232,144 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Compile WHERE, HAVING statements
+	 *
+	 * Escapes identifiers in WHERE and HAVING statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * where(), or_where(), having(), or_having are called prior to from(),
+	 * join() and dbprefix is added only if needed.
+	 *
+	 * @param	string	$qb_key	'qb_where' or 'qb_having'
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_wh($qb_key)
+	{
+		if (count($this->$qb_key) > 0)
+		{
+			for ($i = 0, $c = count($this->$qb_key); $i < $c; $i++)
+			{
+				if ($this->{$qb_key}[$i]['escape'] === FALSE)
+				{
+					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
+					continue;
+				}
+
+				// Split multiple conditions
+				$conditions = preg_split(
+					'/(\s*AND\s+|\s*OR\s+)/i',
+					$this->{$qb_key}[$i]['condition'],
+					-1,
+					PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
+				);
+
+				for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci++)
+				{
+					if (($op = $this->_get_operator($conditions[$ci])) === FALSE
+						OR ! preg_match('/^(\(?)(.*)('.preg_quote($op, '/').')\s*(.*(?<!\)))?(\)?)$/i', $conditions[$ci], $matches))
+					{
+						continue;
+					}
+
+					// $matches = array(
+					//	0 => '(test <= foo)',	/* the whole thing */
+					//	1 => '(',		/* optional */
+					//	2 => 'test',		/* the field name */
+					//	3 => ' <= ',		/* $op */
+					//	4 => 'foo',		/* optional, if $op is e.g. 'IS NULL' */
+					//	5 => ')'		/* optional */
+					// );
+
+					if ( ! empty($matches[4]))
+					{
+						$this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim($matches[4]));
+						$matches[4] = ' '.$matches[4];
+					}
+
+					$conditions[$ci] = $matches[1].$this->protect_identifiers(trim($matches[2]))
+						.' '.trim($matches[3]).$matches[4].$matches[5];
+				}
+
+				$this->{$qb_key}[$i] = implode('', $conditions);
+			}
+
+			return ($qb_key === 'qb_having' ? "\nHAVING " : "\nWHERE ")
+				.implode("\n", $this->$qb_key);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile GROUP BY
+	 *
+	 * Escapes identifiers in GROUP BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * group_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_group_by()
+	{
+		if (count($this->qb_groupby) > 0)
+		{
+			for ($i = 0, $c = count($this->qb_groupby); $i < $c; $i++)
+			{
+				$this->qb_groupby[$i] = ($this->qb_groupby[$i]['escape'] === FALSE OR $this->_is_literal($this->qb_groupby[$i]['field']))
+					? $this->qb_groupby[$i]['field']
+					: $this->protect_identifiers($this->qb_groupby[$i]['field']);
+			}
+
+			return "\nGROUP BY ".implode(', ', $this->qb_groupby);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile ORDER BY
+	 *
+	 * Escapes identifiers in ORDER BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * order_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_order_by()
+	{
+		if (is_array($this->qb_orderby) && count($this->qb_orderby) > 0)
+		{
+			for ($i = 0, $c = count($this->qb_orderby); $i < $c; $i++)
+			{
+				if ($this->qb_orderby[$i]['escape'] !== FALSE && ! $this->_is_literal($this->qb_orderby[$i]['field']))
+				{
+					$this->qb_orderby[$i]['field'] = $this->protect_identifiers($this->qb_orderby[$i]['field']);
+				}
+
+				$this->qb_orderby[$i] = $this->qb_orderby[$i]['field'].$this->qb_orderby[$i]['direction'];
+			}
+
+			return $this->qb_orderby = "\nORDER BY ".implode(', ', $this->qb_orderby);
+		}
+		elseif (is_string($this->qb_orderby))
+		{
+			return $this->qb_orderby;
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Object to Array
 	 *
 	 * Takes an object as input and converts the class variables to array key/vals
@@ -2224,7 +2478,6 @@
 			'qb_cache_from'			=> array(),
 			'qb_cache_join'			=> array(),
 			'qb_cache_where'		=> array(),
-			'qb_cache_like'			=> array(),
 			'qb_cache_groupby'		=> array(),
 			'qb_cache_having'		=> array(),
 			'qb_cache_orderby'		=> array(),
@@ -2260,8 +2513,7 @@
 			{
 				continue;
 			}
-
-			$this->$qb_variable = array_unique(array_merge($this->$qb_cache_var, $this->$qb_variable));
+			$this->$qb_variable = array_merge($this->$qb_variable, array_diff($this->$qb_cache_var, $this->$qb_variable));
 		}
 
 		// If we are "protecting identifiers" we need to examine the "from"
@@ -2271,7 +2523,37 @@
 			$this->_track_aliases($this->qb_from);
 		}
 
-		$this->qb_no_escape = $this->qb_cache_no_escape;
+		$this->qb_no_escape = array_merge($this->qb_no_escape, array_diff($this->qb_cache_no_escape, $this->qb_no_escape));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Is literal
+	 *
+	 * Determines if a string represents a literal value or a field name
+	 *
+	 * @param	string	$str
+	 * @return	bool
+	 */
+	protected function _is_literal($str)
+	{
+		$str = trim($str);
+
+		if (empty($str) OR ctype_digit($str) OR (string) (float) $str === $str OR in_array(strtoupper($str), array('TRUE', 'FALSE'), TRUE))
+		{
+			return TRUE;
+		}
+
+		static $_str;
+
+		if (empty($_str))
+		{
+			$_str = ($this->_escape_char !== '"')
+				? array('"', "'") : array("'");
+		}
+
+		return in_array($str[0], $_str, TRUE);
 	}
 
 	// --------------------------------------------------------------------
@@ -2301,10 +2583,7 @@
 	{
 		foreach ($qb_reset_items as $item => $default_value)
 		{
-			if ( ! in_array($item, $this->qb_store_array))
-			{
-				$this->$item = $default_value;
-			}
+			$this->$item = $default_value;
 		}
 	}
 
@@ -2322,11 +2601,9 @@
 					'qb_from'		=> array(),
 					'qb_join'		=> array(),
 					'qb_where'		=> array(),
-					'qb_like'		=> array(),
 					'qb_groupby'		=> array(),
 					'qb_having'		=> array(),
 					'qb_orderby'		=> array(),
-					'qb_wherein'		=> array(),
 					'qb_aliased_tables'	=> array(),
 					'qb_no_escape'		=> array(),
 					'qb_distinct'		=> FALSE,
@@ -2351,7 +2628,6 @@
 			'qb_set'	=> array(),
 			'qb_from'	=> array(),
 			'qb_where'	=> array(),
-			'qb_like'	=> array(),
 			'qb_orderby'	=> array(),
 			'qb_keys'	=> array(),
 			'qb_limit'	=> FALSE
diff --git a/system/database/DB_result.php b/system/database/DB_result.php
index 94782b2..e1ef341 100644
--- a/system/database/DB_result.php
+++ b/system/database/DB_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Database Result Class
@@ -38,19 +39,68 @@
  */
 class CI_DB_result {
 
+	/**
+	 * Connection ID
+	 *
+	 * @var	resource|object
+	 */
 	public $conn_id;
+
+	/**
+	 * Result ID
+	 *
+	 * @var	resource|object
+	 */
 	public $result_id;
+
+	/**
+	 * Result Array
+	 *
+	 * @var	array[]
+	 */
 	public $result_array			= array();
+
+	/**
+	 * Result Object
+	 *
+	 * @var	object[]
+	 */
 	public $result_object			= array();
+
+	/**
+	 * Custom Result Object
+	 *
+	 * @var	object[]
+	 */
 	public $custom_result_object		= array();
+
+	/**
+	 * Current Row index
+	 *
+	 * @var	int
+	 */
 	public $current_row			= 0;
+
+	/**
+	 * Number of rows
+	 *
+	 * @var	int
+	 */
 	public $num_rows;
+
+	/**
+	 * Row data
+	 *
+	 * @var	array
+	 */
 	public $row_data;
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	object
+	 * @param	object	$driver_object
 	 * @return	void
 	 */
 	public function __construct(&$driver_object)
@@ -89,7 +139,7 @@
 	/**
 	 * Query result. Acts as a wrapper function for the following functions.
 	 *
-	 * @param	string	'object', 'array' or a custom class name
+	 * @param	string	$type	'object', 'array' or a custom class name
 	 * @return	array
 	 */
 	public function result($type = 'object')
@@ -113,8 +163,8 @@
 	/**
 	 * Custom query result.
 	 *
-	 * @param	string	A string that represents the type of object you want back
-	 * @return	array	of objects
+	 * @param	string	$class_name
+	 * @return	array
 	 */
 	public function custom_result_object($class_name)
 	{
@@ -144,7 +194,7 @@
 			{
 				$this->custom_result_object[$class_name][$i] = new $class_name();
 
-				foreach ($this->$_data as $key => $value)
+				foreach ($this->{$_data}[$i] as $key => $value)
 				{
 					$this->custom_result_object[$class_name][$i]->$key = $value;
 				}
@@ -153,7 +203,7 @@
 			return $this->custom_result_object[$class_name];
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		$this->custom_result_object[$class_name] = array();
 
 		while ($row = $this->_fetch_object($class_name))
@@ -196,7 +246,7 @@
 			return $this->result_object;
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		while ($row = $this->_fetch_object())
 		{
 			$this->result_object[] = $row;
@@ -237,7 +287,7 @@
 			return $this->result_array;
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		while ($row = $this->_fetch_assoc())
 		{
 			$this->result_array[] = $row;
@@ -249,29 +299,28 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Query result.  Acts as a wrapper function for the following functions.
+	 * Row
 	 *
-	 * @param	string
-	 * @param	string	can be "object" or "array"
-	 * @return	mixed	either a result object or array
+	 * A wrapper method.
+	 *
+	 * @param	mixed	$n
+	 * @param	string	$type	'object' or 'array'
+	 * @return	mixed
 	 */
 	public function row($n = 0, $type = 'object')
 	{
 		if ( ! is_numeric($n))
 		{
 			// We cache the row data for subsequent uses
-			if ( ! is_array($this->row_data))
+			is_array($this->row_data) OR $this->row_data = $this->row_array(0);
+
+			// array_key_exists() instead of isset() to allow for NULL values
+			if (empty($this->row_data) OR ! array_key_exists($n, $this->row_data))
 			{
-				$this->row_data = $this->row_array(0);
+				return NULL;
 			}
 
-			// array_key_exists() instead of isset() to allow for MySQL NULL values
-			if (array_key_exists($n, $this->row_data))
-			{
-				return $this->row_data[$n];
-			}
-			// reset the $n variable if the result was not achieved
-			$n = 0;
+			return $this->row_data[$n];
 		}
 
 		if ($type === 'object') return $this->row_object($n);
@@ -284,6 +333,8 @@
 	/**
 	 * Assigns an item into a particular column slot
 	 *
+	 * @param	mixed	$key
+	 * @param	mixed	$value
 	 * @return	void
 	 */
 	public function set_row($key, $value = NULL)
@@ -314,6 +365,8 @@
 	/**
 	 * Returns a single result row - custom object version
 	 *
+	 * @param	int	$n
+	 * @param	string	$type
 	 * @return	object
 	 */
 	public function custom_row_object($n, $type)
@@ -338,6 +391,7 @@
 	/**
 	 * Returns a single result row - object version
 	 *
+	 * @param	int	$n
 	 * @return	object
 	 */
 	public function row_object($n = 0)
@@ -361,6 +415,7 @@
 	/**
 	 * Returns a single result row - array version
 	 *
+	 * @param	int	$n
 	 * @return	array
 	 */
 	public function row_array($n = 0)
@@ -384,7 +439,8 @@
 	/**
 	 * Returns the "first" row
 	 *
-	 * @return	object
+	 * @param	string	$type
+	 * @return	mixed
 	 */
 	public function first_row($type = 'object')
 	{
@@ -397,7 +453,8 @@
 	/**
 	 * Returns the "last" row
 	 *
-	 * @return	object
+	 * @param	string	$type
+	 * @return	mixed
 	 */
 	public function last_row($type = 'object')
 	{
@@ -410,7 +467,8 @@
 	/**
 	 * Returns the "next" row
 	 *
-	 * @return	object
+	 * @param	string	$type
+	 * @return	mixed
 	 */
 	public function next_row($type = 'object')
 	{
@@ -433,7 +491,8 @@
 	/**
 	 * Returns the "previous" row
 	 *
-	 * @return	object
+	 * @param	string	$type
+	 * @return	mixed
 	 */
 	public function previous_row($type = 'object')
 	{
@@ -455,8 +514,8 @@
 	/**
 	 * Returns an unbuffered row and move pointer to next row
 	 *
-	 * @param	string	'array', 'object' or a custom class name
-	 * @return	mixed	either a result object or array
+	 * @param	string	$type	'array', 'object' or a custom class name
+	 * @return	mixed
 	 */
 	public function unbuffered_row($type = 'object')
 	{
@@ -475,7 +534,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * The following functions are normally overloaded by the identically named
+	 * The following methods are normally overloaded by the identically named
 	 * methods in the platform-specific driver -- except when query caching
 	 * is used. When caching is enabled we do not load the other driver.
 	 * These functions are primarily here to prevent undefined function errors
@@ -483,13 +542,118 @@
 	 * operational due to the unavailability of the database resource IDs with
 	 * cached results.
 	 */
-	public function num_fields() { return 0; }
-	public function list_fields() { return array(); }
-	public function field_data() { return array(); }
-	public function free_result() { $this->result_id = FALSE; }
-	protected function _data_seek() { return FALSE; }
-	protected function _fetch_assoc() { return array(); }
-	protected function _fetch_object() { return array(); }
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return 0;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names.
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data.
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		$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.
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @param	int	$n
+	 * @return	bool
+	 */
+	public function data_seek($n = 0)
+	{
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array.
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object.
+	 *
+	 * Overriden by driver result classes.
+	 *
+	 * @param	string	$class_name
+	 * @return	object
+	 */
+	protected function _fetch_object($class_name = 'stdClass')
+	{
+		return array();
+	}
 
 }
 
diff --git a/system/database/DB_utility.php b/system/database/DB_utility.php
index 6a3b407..c4140ae 100644
--- a/system/database/DB_utility.php
+++ b/system/database/DB_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Database Utility Class
@@ -32,20 +33,49 @@
  * @author		EllisLab Dev Team
  * @link		http://codeigniter.com/user_guide/database/
  */
-abstract class CI_DB_utility extends CI_DB_forge {
+abstract class CI_DB_utility {
 
-	public $db;
+	/**
+	 * Database object
+	 *
+	 * @var	object
+	 */
+	protected $db;
 
-	// Platform specific SQL strings
-	// Just setting those defaults to FALSE as they are mostly MySQL-specific
+	// --------------------------------------------------------------------
+
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
+	protected $_list_databases		= FALSE;
+
+	/**
+	 * OPTIMIZE TABLE statement
+	 *
+	 * @var	string
+	 */
 	protected $_optimize_table	= FALSE;
+
+	/**
+	 * REPAIR TABLE statement
+	 *
+	 * @var	string
+	 */
 	protected $_repair_table	= FALSE;
 
-	public function __construct()
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
 	{
-		// Assign the main database object to $this->db
-		$CI =& get_instance();
-		$this->db =& $CI->db;
+		$this->db =& $db;
 		log_message('debug', 'Database Utility Class Initialized');
 	}
 
@@ -65,7 +95,7 @@
 		}
 		elseif ($this->_list_databases === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 
 		$this->db->data_cache['db_names'] = array();
@@ -76,9 +106,9 @@
 			return $this->db->data_cache['db_names'];
 		}
 
-		for ($i = 0, $c = count($query); $i < $c; $i++)
+		for ($i = 0, $query = $query->result_array(), $c = count($query); $i < $c; $i++)
 		{
-			$this->db->data_cache['db_names'] = current($query[$i]);
+			$this->db->data_cache['db_names'][] = current($query[$i]);
 		}
 
 		return $this->db->data_cache['db_names'];
@@ -89,7 +119,7 @@
 	/**
 	 * Determine if a particular database exists
 	 *
-	 * @param	string
+	 * @param	string	$database_name
 	 * @return	bool
 	 */
 	public function database_exists($database_name)
@@ -102,21 +132,21 @@
 	/**
 	 * Optimize Table
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table_name
 	 * @return	mixed
 	 */
 	public function optimize_table($table_name)
 	{
 		if ($this->_optimize_table === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 
 		$query = $this->db->query(sprintf($this->_optimize_table, $this->db->escape_identifiers($table_name)));
 		if ($query !== FALSE)
 		{
 			$query = $query->result_array();
-			return current($res);
+			return current($query);
 		}
 
 		return FALSE;
@@ -133,7 +163,7 @@
 	{
 		if ($this->_optimize_table === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 
 		$result = array();
@@ -163,14 +193,14 @@
 	/**
 	 * Repair Table
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table_name
 	 * @return	mixed
 	 */
 	public function repair_table($table_name)
 	{
 		if ($this->_repair_table === FALSE)
 		{
-			return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+			return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 		}
 
 		$query = $this->db->query(sprintf($this->_repair_table, $this->db->escape_identifiers($table_name)));
@@ -188,10 +218,10 @@
 	/**
 	 * Generate CSV from a query result object
 	 *
-	 * @param	object	The query result object
-	 * @param	string	The delimiter - comma by default
-	 * @param	string	The newline character - \n by default
-	 * @param	string	The enclosure - double quote by default
+	 * @param	object	$query		Query result object
+	 * @param	string	$delim		Delimiter (default: ,)
+	 * @param	string	$newline	Newline character (default: \n)
+	 * @param	string	$enclosure	Enclosure (default: ")
 	 * @return	string
 	 */
 	public function csv_from_result($query, $delim = ',', $newline = "\n", $enclosure = '"')
@@ -228,8 +258,8 @@
 	/**
 	 * Generate XML data from a query result object
 	 *
-	 * @param	object	The query result object
-	 * @param	array	Any preferences
+	 * @param	object	$query	Query result object
+	 * @param	array	$params	Any preferences
 	 * @return	string
 	 */
 	public function xml_from_result($query, $params = array())
@@ -275,6 +305,7 @@
 	/**
 	 * Database Backup
 	 *
+	 * @param	array	$params
 	 * @return	void
 	 */
 	public function backup($params = array())
@@ -287,8 +318,6 @@
 			$params = array('tables' => $params);
 		}
 
-		// ------------------------------------------------------
-
 		// Set up our default preferences
 		$prefs = array(
 				'tables'		=> array(),
@@ -332,7 +361,7 @@
 		{
 			if ($this->db->db_debug)
 			{
-				return $this->db->display_error('db_unsuported_compression');
+				return $this->db->display_error('db_unsupported_compression');
 			}
 
 			$prefs['format'] = 'txt';
diff --git a/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php
index a3d0287..21994b0 100644
--- a/system/database/drivers/cubrid/cubrid_driver.php
+++ b/system/database/drivers/cubrid/cubrid_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CUBRID Database Adapter Class
@@ -40,20 +41,44 @@
  */
 class CI_DB_cubrid_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'cubrid';
 
-	// The character used for escaping - no need in CUBRID
-	protected $_escape_char = '`';
-
-	// clause and character used for LIKE escape sequences - not used in CUBRID
-	protected $_like_escape_str = '';
-	protected $_like_escape_chr = '';
-
-	protected $_random_keyword = ' RAND()'; // database specific random keyword
-
-	// CUBRID-specific properties
+	/**
+	 * Auto-commit flag
+	 *
+	 * @var	bool
+	 */
 	public $auto_commit = TRUE;
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '`';
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM(%d)');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		parent::__construct($params);
@@ -72,6 +97,8 @@
 		}
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Non-persistent database connection
 	 *
@@ -109,7 +136,7 @@
 	 * Except for determining if a persistent connection should be used,
 	 * the rest of the logic is the same for db_connect() and db_pconnect().
 	 *
-	 * @param	bool
+	 * @param	bool	$persistent
 	 * @return	resource
 	 */
 	protected function _cubrid_connect($persistent = FALSE)
@@ -159,9 +186,18 @@
 	 */
 	public function version()
 	{
-		return isset($this->data_cache['version'])
-			? $this->data_cache['version']
-			: $this->data_cache['version'] = cubrid_get_server_info($this->conn_id);
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
+
+		return ( ! $this->conn_id OR ($version = cubrid_get_server_info($this->conn_id)) === FALSE)
+			? FALSE
+			: $this->data_cache['version'] = $version;
 	}
 
 	// --------------------------------------------------------------------
@@ -169,7 +205,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -182,6 +218,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -260,8 +297,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -327,7 +364,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -349,7 +386,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -360,16 +397,40 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT * FROM '.$table.' LIMIT 1';
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->Field;
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
+			$retval[$i]->default		= $query[$i]->Default;
+			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -394,12 +455,12 @@
 	 *
 	 * Generates a platform-specific batch update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -423,9 +484,29 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN ('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php
index d328aa2..db1ce5f 100644
--- a/system/database/drivers/cubrid/cubrid_forge.php
+++ b/system/database/drivers/cubrid/cubrid_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CUBRID Forge Class
@@ -34,181 +35,182 @@
  */
 class CI_DB_cubrid_forge extends CI_DB_forge {
 
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_create_database	= FALSE;
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= TRUE;
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_drop_database	= FALSE;
 
 	/**
-	 * Process Fields
+	 * CREATE TABLE IF statement
 	 *
-	 * @param	mixed	the fields
-	 * @return	string
+	 * @var	string
 	 */
-	protected function _process_fields($fields)
-	{
-		$current_field_count = 0;
-		$sql = '';
+	protected $_create_table_if	= FALSE;
 
-		foreach ($fields as $field => $attributes)
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SHORT'		=> 'INTEGER',
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'BIGINT'	=> 'NUMERIC',
+		'FLOAT'		=> 'DOUBLE',
+		'REAL'		=> 'DOUBLE'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
 		{
-			// 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))
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
 			{
-				$sql .= "\n\t".$attributes;
+				$sqls[] = $sql.' CHANGE '.$field[$i]['_literal'];
 			}
 			else
 			{
-				$attributes = array_change_key_case($attributes, CASE_UPPER);
-
-				$sql .= "\n\t".$this->db->escape_identifiers($field);
-
-				empty($attributes['NAME']) OR $sql .= ' '.$this->db->escape_identifiers($attributes['NAME']).' ';
-
-				if ( ! empty($attributes['TYPE']))
-				{
-					$sql .= ' '.$attributes['TYPE'];
-
-					if ( ! empty($attributes['CONSTRAINT']))
-					{
-						switch (strtolower($attributes['TYPE']))
-						{
-							case 'decimal':
-							case 'float':
-							case 'numeric':
-								$sql .= '('.implode(',', $attributes['CONSTRAINT']).')';
-								break;
-							case 'enum':
-								// Will be supported in the future as part a part of
-								// MySQL compatibility features.
-								break;
-							case 'set':
-								$sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")';
-								break;
-							default:
-								$sql .= '('.$attributes['CONSTRAINT'].')';
-						}
-					}
-				}
-
-			/* As of version 8.4.1 CUBRID does not support UNSIGNED INTEGER data type.
-			 * Will be supported in the next release as a part of MySQL Compatibility.
-			 *
-				if (isset($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-			 */
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE)
-				{
-					$sql .= ' AUTO_INCREMENT';
-				}
-
-				if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
-				{
-					$sql .= ' UNIQUE';
-				}
-			}
-
-			// don't add a comma on the end of the last field
-			if (++$current_field_count < count($fields))
-			{
-				$sql .= ',';
+				$alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE ';
+				$sqls[] = $sql.$alter_type.$this->_process_column($field[$i]);
 			}
 		}
 
-		return $sql;
+		return $sqls;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Create Table
+	 * Process column
 	 *
-	 * @param	string	the table name
-	 * @param	mixed	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
-	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
-	{
-		$sql = 'CREATE TABLE ';
-
-		/* As of version 8.4.1 CUBRID does not support this SQL syntax.
-		if ($if_not_exists === TRUE)
-		{
-			$sql .= 'IF NOT EXISTS ';
-		}
-		*/
-
-		$sql .= $this->db->escape_identifiers($table).' ('.$this->_process_fields($fields);
-
-		// If there is a PK defined
-		if (count($primary_keys) > 0)
-		{
-			$key_name = $this->db->escape_identifiers('pk_'.$table.'_'.implode('_', $primary_keys));
-			$sql .= ",\n\tCONSTRAINT ".$key_name.' PRIMARY KEY('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				if (is_array($key))
-				{
-					$key_name = $this->db->escape_identifiers('idx_'.$table.implode('_', $key));
-					$key = $this->db->escape_identifiers($key);
-				}
-				else
-				{
-					$key_name = $this->db->escape_identifiers('idx_'.$table.$key);
-					$key = array($key_name);
-				}
-
-				$sql .= ",\n\tKEY ".$key_name.' ('.implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n);";
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Alter table query
-	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
-	 * @param	string	the column name
-	 * @param	array	fields
-	 * @param	string	the field after which we should add the new field
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _alter_table($alter_type, $table, $fields, $after_field = '')
+	protected function _process_column($field)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' ';
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
 
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
 		{
-			return $sql.$this->db->escape_identifiers($fields);
+			$extra_clause = ' FIRST';
 		}
 
-		return $sql.$this->_process_fields($fields)
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
 	}
 
 }
diff --git a/system/database/drivers/cubrid/cubrid_result.php b/system/database/drivers/cubrid/cubrid_result.php
index 4a06a2d..793b35b 100644
--- a/system/database/drivers/cubrid/cubrid_result.php
+++ b/system/database/drivers/cubrid/cubrid_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CUBRID Result Class
@@ -87,20 +88,14 @@
 	public function field_data()
 	{
 		$retval = array();
-		$i = 0;
 
-		while ($field = cubrid_fetch_field($this->result_id))
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 		{
 			$retval[$i]			= new stdClass();
-			$retval[$i]->name		= $field->name;
-			// CUBRID returns type as e.g. varchar(100),
-			// so we need to remove all digits and brackets.
-			$retval[$i]->type		= preg_replace('/[\d()]/', '', $field->type);
-			$retval[$i]->default		= $field->def;
-			// Use CUBRID's native API to obtain column's max_length,
-			// otherwise $field->max_length has incorrect info
+			$retval[$i]->name		= cubrid_field_name($this->result_id, $i);
+			$retval[$i]->type		= cubrid_field_type($this->result_id, $i);
 			$retval[$i]->max_length		= cubrid_field_len($this->result_id, $i);
-			$retval[$i++]->primary_key	= $field->primary_key;
+			$retval[$i]->primary_key	= (int) (strpos(cubrid_field_flags($this->result_id, $i), 'primary_key') !== FALSE);
 		}
 
 		return $retval;
@@ -130,11 +125,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return cubrid_data_seek($this->result_id, $n);
 	}
@@ -160,7 +156,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/cubrid/cubrid_utility.php b/system/database/drivers/cubrid/cubrid_utility.php
index ea8feb4..d76eca4 100644
--- a/system/database/drivers/cubrid/cubrid_utility.php
+++ b/system/database/drivers/cubrid/cubrid_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CUBRID Utility Class
@@ -62,7 +63,7 @@
 		// No SQL based support in CUBRID as of version 8.4.0. Database or
 		// table backup can be performed using CUBRID Manager
 		// database administration tool.
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 }
 
diff --git a/system/database/drivers/ibase/ibase_driver.php b/system/database/drivers/ibase/ibase_driver.php
index c902767..15c2a46 100644
--- a/system/database/drivers/ibase/ibase_driver.php
+++ b/system/database/drivers/ibase/ibase_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Firebird/Interbase Database Adapter Class
@@ -40,19 +41,30 @@
  */
 class CI_DB_ibase_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'ibase';
 
-	// The character used to escape with
-	protected $_escape_char = '"';
+	// --------------------------------------------------------------------
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RAND()', 'RAND()');
 
-	protected $_random_keyword = ' Random()'; // database specific random keyword
+	/**
+	 * IBase Transaction status flag
+	 *
+	 * @var	resource
+	 */
+	protected $_ibase_trans;
 
-	// Keeps track of the resource for the current transaction
-	protected $trans;
+	// --------------------------------------------------------------------
 
 	/**
 	 * Non-persistent database connection
@@ -107,7 +119,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -120,6 +132,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -135,7 +148,7 @@
 		// even if the queries produce a successful result.
 		$this->_trans_failure = ($test_mode === TRUE);
 
-		$this->trans = @ibase_trans($this->conn_id);
+		$this->_ibase_trans = @ibase_trans($this->conn_id);
 
 		return TRUE;
 	}
@@ -155,7 +168,7 @@
 			return TRUE;
 		}
 
-		return @ibase_commit($this->trans);
+		return @ibase_commit($this->_ibase_trans);
 	}
 
 	// --------------------------------------------------------------------
@@ -173,7 +186,7 @@
 			return TRUE;
 		}
 
-		return @ibase_rollback($this->trans);
+		return @ibase_rollback($this->_ibase_trans);
 	}
 
 	// --------------------------------------------------------------------
@@ -181,8 +194,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -242,7 +255,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -265,7 +278,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -276,16 +289,46 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return $this->_limit('SELECT * FROM '.$this->protect_identifiers($table), 1, NULL);
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name",
+				CASE "fields"."RDB$FIELD_TYPE"
+					WHEN 7 THEN \'SMALLINT\'
+					WHEN 8 THEN \'INTEGER\'
+					WHEN 9 THEN \'QUAD\'
+					WHEN 10 THEN \'FLOAT\'
+					WHEN 11 THEN \'DFLOAT\'
+					WHEN 12 THEN \'DATE\'
+					WHEN 13 THEN \'TIME\'
+					WHEN 14 THEN \'CHAR\'
+					WHEN 16 THEN \'INT64\'
+					WHEN 27 THEN \'DOUBLE\'
+					WHEN 35 THEN \'TIMESTAMP\'
+					WHEN 37 THEN \'VARCHAR\'
+					WHEN 40 THEN \'CSTRING\'
+					WHEN 261 THEN \'BLOB\'
+					ELSE NULL
+				END AS "type",
+				"fields"."RDB$FIELD_LENGTH" AS "max_length",
+				"rfields"."RDB$DEFAULT_VALUE" AS "default"
+			FROM "RDB$RELATION_FIELDS" "rfields"
+				JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME"
+			WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).'
+			ORDER BY "rfields"."RDB$FIELD_POSITION"';
+
+		return (($query = $this->query($sql)) !== FALSE)
+			? $query->result_object()
+			: FALSE;
 	}
 
 	// --------------------------------------------------------------------
@@ -306,51 +349,18 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
-	 *
-	 * This public function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _from_tables($tables)
-	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Update statement
 	 *
 	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @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 (ignored)
-	 * @param	array	the like clause
+	 * @param	string	$table
+	 * @param	array	$values
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
-			.$where
-			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '');
+		$this->qb_limit = FALSE;
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -360,10 +370,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
@@ -378,46 +388,37 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
+	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
 		// Limit clause depends on if Interbase or Firebird
 		if (stripos($this->version(), 'firebird') !== FALSE)
 		{
-			$select = 'FIRST '. (int) $limit
-				.($offset ? ' SKIP '. (int) $offset : '');
+			$select = 'FIRST '.$this->qb_limit
+				.($this->qb_offset ? ' SKIP '.$this->qb_offset : '');
 		}
 		else
 		{
 			$select = 'ROWS '
-				.($offset ? (int) $offset.' TO '.($limit + $offset) : (int) $limit);
+				.($this->qb_offset ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
 		}
 
 		return preg_replace('`SELECT`i', 'SELECT '.$select, $sql);
diff --git a/system/database/drivers/ibase/ibase_forge.php b/system/database/drivers/ibase/ibase_forge.php
index da75eb9..39c7828 100644
--- a/system/database/drivers/ibase/ibase_forge.php
+++ b/system/database/drivers/ibase/ibase_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Interbase/Firebird Forge Class
@@ -34,12 +35,51 @@
  */
 class CI_DB_ibase_forge extends CI_DB_forge {
 
-	protected $_drop_table	= 'DROP TABLE %s';
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= FALSE;
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SMALLINT'	=> 'INTEGER',
+		'INTEGER'	=> 'INT64',
+		'FLOAT'		=> 'DOUBLE PRECISION'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
 
 	/**
 	 * Create database
 	 *
-	 * @param	string	the database name
+	 * @param	string	$db_name
 	 * @return	string
 	 */
 	public function create_database($db_name)
@@ -57,8 +97,7 @@
 	/**
 	 * Drop database
 	 *
-	 * @param	string	the database name
-	 *		- not used in this driver, the current db is dropped
+	 * @param	string	$db_name	(ignored)
 	 * @return	bool
 	 */
 	public function drop_database($db_name = '')
@@ -82,110 +121,120 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Create Table
+	 * ALTER TABLE
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+ 	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				return FALSE;
+			}
+
+			if (isset($field[$i]['type']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identififers($field[$i]['name'])
+					.' TYPE '.$field[$i]['type'].$field[$i]['length'];
+			}
+
+			if ( ! empty($field[$i]['default']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' SET DEFAULT '.$field[$i]['default'];
+			}
+
+			if (isset($field[$i]['null']))
+			{
+				$sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = '
+					.($field[$i]['null'] === TRUE ? 'NULL' : '1')
+					.' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name'])
+					.' AND "RDB$RELATION_NAME" = '.$this->db->escape($table);
+			}
+
+			if ( ! empty($field[$i]['new_name']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
+			}
+		}
+
+		return $sqls;
+ 	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	protected function _process_column($field)
 	{
-		$sql = 'CREATE TABLE ';
-
-		$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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				empty($attributes['CONSTRAINT']) OR $sql .= '('.$attributes['CONSTRAINT'].')';
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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->escape_identifiers($primary_keys);
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $primary_keys).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Field attribute TYPE
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
+	 * Performs a data type mapping between different databases.
 	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
+	 * @param	array	&$attributes
+	 * @return	void
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _attr_type(&$attributes)
 	{
-		return 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name)
-			.' '.$column_definition
-			.($default_value !== '' ? ' DEFAULT "'.$default_value.'"' : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INT':
+				$attributes['TYPE'] = 'INTEGER';
+				return;
+			case 'BIGINT':
+				$attributes['TYPE'] = 'INT64';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported
 	}
 
 }
diff --git a/system/database/drivers/ibase/ibase_result.php b/system/database/drivers/ibase/ibase_result.php
index 95e5571..4f5637a 100644
--- a/system/database/drivers/ibase/ibase_result.php
+++ b/system/database/drivers/ibase/ibase_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Interbase/Firebird Result Class
@@ -88,8 +89,6 @@
 			$retval[$i]->name		= $info['name'];
 			$retval[$i]->type		= $info['type'];
 			$retval[$i]->max_length		= $info['length'];
-			$retval[$i]->primary_key	= 0;
-			$retval[$i]->default		= '';
 		}
 
 		return $retval;
@@ -128,7 +127,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/ibase/ibase_utility.php b/system/database/drivers/ibase/ibase_utility.php
index d0e84a7..5eb209a 100644
--- a/system/database/drivers/ibase/ibase_utility.php
+++ b/system/database/drivers/ibase/ibase_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Interbase/Firebird Utility Class
@@ -34,10 +35,8 @@
  */
 class CI_DB_ibase_utility extends CI_DB_utility {
 
-	protected $_list_databases	= FALSE;
-
 	/**
-	 * Interbase/Firebird Export
+	 * Export
 	 *
 	 * @param	string	$filename
 	 * @return	mixed
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index 1714704..d3e439d 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MS SQL Database Adapter Class
@@ -40,26 +41,40 @@
  */
 class CI_DB_mssql_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'mssql';
 
-	// The character used for escaping
-	protected $_escape_char = '"';
+	// --------------------------------------------------------------------
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
 
-	protected $_random_keyword = ' NEWID()';
-
-	// MSSQL-specific properties
+	/**
+	 * Quoted identifier flag
+	 *
+	 * Whether to use SQL-92 standard quoted identifier
+	 * (double quotes) or brackets for identifier escaping.
+	 *
+	 * @var	bool
+	 */
 	protected $_quoted_identifier = TRUE;
 
-	/*
-	 * Constructor
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
 	 *
 	 * Appends the port number to the hostname, if needed.
 	 *
-	 * @param	array
+	 * @param	array	$params
 	 * @return	void
 	 */
 	public function __construct($params)
@@ -77,7 +92,7 @@
 	/**
 	 * Non-persistent database connection
 	 *
-	 * @param	bool
+	 * @param	bool	$persistent
 	 * @return	resource
 	 */
 	public function db_connect($persistent = FALSE)
@@ -117,7 +132,7 @@
 	/**
 	 * Select the database
 	 *
-	 * @param	string	database name
+	 * @param	string	$database
 	 * @return	bool
 	 */
 	public function db_select($database = '')
@@ -143,7 +158,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	mixed	resource if rows are returned, bool otherwise
 	 */
 	protected function _execute($sql)
@@ -156,6 +171,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -215,8 +231,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -282,6 +298,19 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Set client character set
+	 *
+	 * @param	string	$charset
+	 * @return	bool
+	 */
+	protected function _db_set_charset($charset)
+	{
+		return (@ini_set('mssql.charset', $charset) !== FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Version number query string
 	 *
 	 * @return	string
@@ -298,7 +327,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -323,27 +352,52 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
 	{
-		return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$table."'";
+		return 'SELECT COLUMN_NAME
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT TOP 1 * FROM '.$this->protect_identifiers($table);
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -366,49 +420,19 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
-	 *
-	 * This function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _from_tables($tables)
-	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Update statement
 	 *
 	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
+	 * @param	string	$table
+	 * @param	array	$values
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).' WHERE '.$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -418,10 +442,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
@@ -436,61 +460,65 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
+	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
-		// As of SQL Server 2012 (11.0.*) OFFSET is supported
-		if (version_compare($this->version(), '11', '>='))
-		{
-			return $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY';
-		}
-
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
 		// however an ORDER BY clause is required for it to work
-		if (version_compare($this->version(), '9', '>=') && $offset && ! empty($this->qb_orderby))
+		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
-			$sql = trim(substr($sql, 0, strrpos($sql, 'ORDER BY '.$orderby)));
+			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
-			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
-				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
+			if (count($this->qb_select) === 0)
+			{
+				$select = '*'; // Inevitable
+			}
+			else
+			{
+				// Use only field names and their aliases, everything else is out of our scope.
+				$select = array();
+				$field_regexp = ($this->_quoted_identifier)
+					? '("[^\"]+")' : '(\[[^\]]+\])';
+				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
+				{
+					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
+						? $m[1] : $this->qb_select[$i];
+				}
+				$select = implode(', ', $select);
+			}
+
+			return 'SELECT '.$select." FROM (\n\n"
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				."\n\n) ".$this->escape_identifiers('CI_subquery')
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
@@ -499,6 +527,29 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		// Multiple-value inserts are only supported as of SQL Server 2008
+		if (version_compare($this->version(), '10', '>='))
+		{
+			return parent::_insert_batch($table, $keys, $values);
+		}
+
+		return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Close DB Connection
 	 *
 	 * @return	void
diff --git a/system/database/drivers/mssql/mssql_forge.php b/system/database/drivers/mssql/mssql_forge.php
index 3a3528f..5eebedb 100644
--- a/system/database/drivers/mssql/mssql_forge.php
+++ b/system/database/drivers/mssql/mssql_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MS SQL Forge Class
@@ -34,124 +35,99 @@
  */
 class CI_DB_mssql_forge extends CI_DB_forge {
 
-	protected $_drop_table	= 'DROP TABLE %s';
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
 
 	/**
-	 * Create Table
+	 * DROP TABLE IF statement
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	string
+	 * @var	string
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	protected $_drop_table_if	= "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT'	=> 'SMALLINT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'BIGINT',
+		'REAL'		=> 'FLOAT'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
 	{
-		$sql = ($if_not_exists === TRUE)
-			? "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'".$table."') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"
-			: '';
-
-		$sql .= 'CREATE TABLE '.$this->db->escape_identifiers($table).' (';
-
-		$current_field_count = 0;
-		foreach ($fields as $field => $attributes)
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
 		{
-			// 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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				if (stripos($attributes['TYPE'], 'INT') === FALSE && ! empty($attributes['CONSTRAINT']))
-				{
-					$sql .= '('.$attributes['CONSTRAINT'].')';
-				}
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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 .= ',';
-			}
+			return parent::_alter_table($alter_type, $table, $field);
 		}
 
-		if (count($primary_keys) > 0)
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
+			$sqls[] = $sql.$this->_process_column($field[$i]);
 		}
 
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		return $sqls;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Field attribute TYPE
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
+	 * Performs a data type mapping between different databases.
 	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
+	 * @param	array	&$attributes
+	 * @return	void
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _attr_type(&$attributes)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name);
-
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
+		switch (strtoupper($attributes['TYPE']))
 		{
-			return $sql;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			default: return;
 		}
+	}
 
-		return $sql.' '.$column_definition
-			.($default_value != '' ? ' DEFAULT "'.$default_value.'"' : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field != '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' IDENTITY(1,1)';
+		}
 	}
 
 }
diff --git a/system/database/drivers/mssql/mssql_result.php b/system/database/drivers/mssql/mssql_result.php
index aeede3f..ca222ae 100644
--- a/system/database/drivers/mssql/mssql_result.php
+++ b/system/database/drivers/mssql/mssql_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MSSQL Result Class
@@ -93,16 +94,14 @@
 	public function field_data()
 	{
 		$retval = array();
-		while ($field = mssql_fetch_field($this->result_id))
+		for ($i = 0, $c = $this->num_field(); $i < $c; $i++)
 		{
-			$F		= new stdClass();
-			$F->name	= $field->name;
-			$F->type	= $field->type;
-			$F->max_length	= $field->max_length;
-			$F->primary_key = 0;
-			$F->default	= '';
+			$field = mssql_fetch_field($this->result_id, $i);
 
-			$retval[] = $F;
+			$retval[$i]		= new stdClass();
+			$retval[$i]->name	= $field->name;
+			$retval[$i]->type	= $field->type;
+			$retval[$i]->max_length	= $field->max_length;
 		}
 
 		return $retval;
@@ -131,11 +130,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return mssql_data_seek($this->result_id, $n);
 	}
@@ -161,7 +161,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/mssql/mssql_utility.php b/system/database/drivers/mssql/mssql_utility.php
index 69fcec5..76c7cb9 100644
--- a/system/database/drivers/mssql/mssql_utility.php
+++ b/system/database/drivers/mssql/mssql_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MS SQL Utility Class
@@ -34,19 +35,30 @@
  */
 class CI_DB_mssql_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
+
+	/**
+	 * OPTIMIZE TABLE statement
+	 *
+	 * @var	string
+	 */
 	protected $_optimize_table	= 'ALTER INDEX all ON %s REORGANIZE';
 
 	/**
-	 * MSSQL Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	bool
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php
index 29db904..5af4437 100644
--- a/system/database/drivers/mysql/mysql_driver.php
+++ b/system/database/drivers/mysql/mysql_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQL Database Adapter Class
@@ -40,28 +41,46 @@
  */
 class CI_DB_mysql_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'mysql';
 
-	// The character used for escaping
-	protected $_escape_char = '`';
-
-	// clause and character used for LIKE escape sequences - not used in MySQL
-	protected $_like_escape_str = '';
-	protected $_like_escape_chr = '\\';
-
-	protected $_random_keyword = ' RAND()'; // database specific random keyword
+	/**
+	 * Compression flag
+	 *
+	 * @var	bool
+	 */
+	public $compress = FALSE;
 
 	/**
+	 * DELETE hack flag
+	 *
 	 * Whether to use the MySQL "delete hack" which allows the number
 	 * of affected rows to be shown. Uses a preg_replace when enabled,
 	 * adding a bit more processing to all queries.
+	 *
+	 * @var	bool
 	 */
 	public $delete_hack = TRUE;
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * Constructor
+	 * Identifier escape character
 	 *
-	 * @param	array
+	 * @var	string
+	 */
+	protected $_escape_char = '`';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
 	 * @return	void
 	 */
 	public function __construct($params)
@@ -79,11 +98,21 @@
 	/**
 	 * Non-persistent database connection
 	 *
+	 * @param	bool	$persistent
 	 * @return	resource
 	 */
-	public function db_connect()
+	public function db_connect($persistent = FALSE)
 	{
-		return @mysql_connect($this->hostname, $this->username, $this->password, TRUE);
+		$client_flags = ($this->compress === FALSE) ? 0 : MYSQL_CLIENT_COMPRESS;
+
+		if ($this->encrypt === TRUE)
+		{
+			$client_flags = $client_flags | MYSQL_CLIENT_SSL;
+		}
+
+		return ($persistent === TRUE)
+			? @mysql_pconnect($this->hostname, $this->username, $this->password, $client_flags)
+			: @mysql_connect($this->hostname, $this->username, $this->password, TRUE, $client_flags);
 	}
 
 	// --------------------------------------------------------------------
@@ -95,7 +124,7 @@
 	 */
 	public function db_pconnect()
 	{
-		return @mysql_pconnect($this->hostname, $this->username, $this->password);
+		return $this->db_connect(TRUE);
 	}
 
 	// --------------------------------------------------------------------
@@ -121,7 +150,7 @@
 	/**
 	 * Select the database
 	 *
-	 * @param	string	database name
+	 * @param	string	$database
 	 * @return	bool
 	 */
 	public function db_select($database = '')
@@ -145,7 +174,7 @@
 	/**
 	 * Set client character set
 	 *
-	 * @param	string
+	 * @param	string	$charset
 	 * @return	bool
 	 */
 	protected function _db_set_charset($charset)
@@ -162,9 +191,21 @@
 	 */
 	public function version()
 	{
-		return isset($this->data_cache['version'])
-			? $this->data_cache['version']
-			: $this->data_cache['version'] = @mysql_get_server_info($this->conn_id);
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
+
+		if ( ! $this->conn_id OR ($version = @mysql_get_server_info($this->conn_id)) === FALSE)
+		{
+			return FALSE;
+		}
+
+		return $this->data_cache['version'] = $version;
 	}
 
 	// --------------------------------------------------------------------
@@ -172,7 +213,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	mixed
 	 */
 	protected function _execute($sql)
@@ -187,7 +228,7 @@
 	 *
 	 * If needed, each database adapter can prep the query string
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	string
 	 */
 	protected function _prep_query($sql)
@@ -207,6 +248,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -272,8 +314,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -281,12 +323,12 @@
 		if (is_array($str))
 		{
 			foreach ($str as $key => $val)
-	   		{
+			{
 				$str[$key] = $this->escape_str($val, $like);
-	   		}
+			}
 
-	   		return $str;
-	   	}
+			return $str;
+		}
 
 		$str = is_resource($this->conn_id) ? mysql_real_escape_string($str, $this->conn_id) : addslashes($str);
 
@@ -332,7 +374,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -354,7 +396,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -367,8 +409,8 @@
 	/**
 	 * Returns an object with field data
 	 *
-	 * @param	string	the table name
-	 * @return	object
+	 * @param	string	$table
+	 * @return	array
 	 */
 	public function field_data($table = '')
 	{
@@ -377,19 +419,24 @@
 			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
 		}
 
-		$query = $this->query('DESCRIBE '.$this->protect_identifiers($table, TRUE, NULL, FALSE));
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
 		$query = $query->result_object();
 
 		$retval = array();
 		for ($i = 0, $c = count($query); $i < $c; $i++)
 		{
-			preg_match('/([a-z]+)(\(\d+\))?/', $query[$i]->Type, $matches);
-
 			$retval[$i]			= new stdClass();
 			$retval[$i]->name		= $query[$i]->Field;
-			$retval[$i]->type		= empty($matches[1]) ? NULL : $matches[1];
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
 			$retval[$i]->default		= $query[$i]->Default;
-			$retval[$i]->max_length		= empty($matches[2]) ? NULL : preg_replace('/[^\d]/', '', $matches[2]);
 			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
 		}
 
@@ -418,12 +465,12 @@
 	 *
 	 * Generates a platform-specific batch update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -434,7 +481,7 @@
 			{
 				if ($field !== $index)
 				{
-					$final[$field][] =  'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
 				}
 			}
 		}
@@ -447,9 +494,29 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php
index 2ac75ba..f4394c6 100644
--- a/system/database/drivers/mysql/mysql_forge.php
+++ b/system/database/drivers/mysql/mysql_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQL Forge Class
@@ -34,165 +35,178 @@
  */
 class CI_DB_mysql_forge extends CI_DB_forge {
 
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_create_database	= 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
 
 	/**
-	 * Process Fields
+	 * CREATE TABLE keys flag
 	 *
-	 * @param	mixed	the fields
-	 * @return	string
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
 	 */
-	protected function _process_fields($fields)
-	{
-		$current_field_count = 0;
-		$sql = '';
+	protected $_create_table_keys	= TRUE;
 
-		foreach ($fields as $field => $attributes)
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT',
+		'SMALLINT',
+		'MEDIUMINT',
+		'INT',
+		'INTEGER',
+		'BIGINT',
+		'REAL',
+		'DOUBLE',
+		'DOUBLE PRECISION',
+		'FLOAT',
+		'DECIMAL',
+		'NUMERIC'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP')
 		{
-			// 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))
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
 			{
-				$sql .= "\n\t".$attributes;
+				$field[$i] = ($alter_type === 'ADD')
+						? "\n\tADD ".$field[$i]['_literal']
+						: "\n\tMODIFY ".$field[$i]['_literal'];
 			}
 			else
 			{
-				$attributes = array_change_key_case($attributes, CASE_UPPER);
-
-				$sql .= "\n\t".$this->db->escape_identifiers($field);
-
-				empty($attributes['NAME']) OR $sql .= ' '.$this->db->escape_identifiers($attributes['NAME']).' ';
-
-				if ( ! empty($attributes['TYPE']))
+				if ($alter_type === 'ADD')
 				{
-					$sql .=  ' '.$attributes['TYPE'];
-
-					if ( ! empty($attributes['CONSTRAINT']))
-					{
-						switch (strtolower($attributes['TYPE']))
-						{
-							case 'decimal':
-							case 'float':
-							case 'numeric':
-								$sql .= '('.implode(',', $attributes['CONSTRAINT']).')';
-								break;
-							case 'enum':
-							case 'set':
-								$sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")';
-								break;
-							default:
-								$sql .= '('.$attributes['CONSTRAINT'].')';
-						}
-					}
-				}
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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 .= ',';
-			}
-		}
-
-		return $sql;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Create Table
-	 *
-	 * @param	string	the table name
-	 * @param	mixed	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
-	 */
-	protected 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).' ('.$this->_process_fields($fields);
-
-		if (count($primary_keys) > 0)
-		{
-			$key_name = $this->db->escape_identifiers(implode('_', $primary_keys));
-			$sql .= ",\n\tPRIMARY KEY ".$key_name.' ('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				if (is_array($key))
-				{
-					$key_name = $this->db->escape_identifiers(implode('_', $key));
-					$key = $this->db->escape_identifiers($key);
+					$field[$i]['_literal'] = "\n\tADD ";
 				}
 				else
 				{
-					$key_name = $this->db->escape_identifiers($key);
-					$key = array($key_name);
+					$field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
 				}
 
-				$sql .= ",\n\tKEY ".$key_name.' ('.implode(', ', $key).')';
+				$field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]);
 			}
 		}
 
-		return $sql."\n) DEFAULT CHARACTER SET ".$this->db->char_set.' COLLATE '.$this->db->dbcollat.';';
+		return array($sql.implode(',', $field));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Process column
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column() and column_alter()
-	 *
-	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
-	 * @param	string	the column name
-	 * @param	array	fields
-	 * @param	string	the field after which we should add the new field
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _alter_table($alter_type, $table, $fields, $after_field = '')
+	protected function _process_column($field)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' ';
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
 
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
 		{
-			return $sql.$this->db->escape_identifiers($fields);
+			$extra_clause = ' FIRST';
 		}
 
-		return $sql.$this->_process_fields($fields)
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
 	}
 
 }
diff --git a/system/database/drivers/mysql/mysql_result.php b/system/database/drivers/mysql/mysql_result.php
index 7fbb654..293980e 100644
--- a/system/database/drivers/mysql/mysql_result.php
+++ b/system/database/drivers/mysql/mysql_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQL Result Class
@@ -38,9 +39,9 @@
 class CI_DB_mysql_result extends CI_DB_result {
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * @param	object
+	 * @param	object	&$driver_object
 	 * @return	void
 	 */
 	public function __construct(&$driver_object)
@@ -114,8 +115,7 @@
 			$retval[$i]->name		= mysql_field_name($this->result_id, $i);
 			$retval[$i]->type		= mysql_field_type($this->result_id, $i);
 			$retval[$i]->max_length		= mysql_field_len($this->result_id, $i);
-			$retval[$i]->primary_key	= (strpos(mysql_field_flags($this->result_id, $i), 'primary_key') === FALSE) ? 0 : 1;
-			$retval[$i]->default		= '';
+			$retval[$i]->primary_key	= (int) (strpos(mysql_field_flags($this->result_id, $i), 'primary_key') !== FALSE);
 		}
 
 		return $retval;
@@ -144,11 +144,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return $this->num_rows
 			? @mysql_data_seek($this->result_id, $n)
@@ -176,7 +177,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/mysql/mysql_utility.php b/system/database/drivers/mysql/mysql_utility.php
index f0bbc66..8aa0517 100644
--- a/system/database/drivers/mysql/mysql_utility.php
+++ b/system/database/drivers/mysql/mysql_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQL Utility Class
@@ -34,14 +35,33 @@
  */
 class CI_DB_mysql_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'SHOW DATABASES';
-	protected $_optimize_table	= 'OPTIMIZE TABLE %s';
-	protected $_repair_table	= 'REPAIR TABLE %s';
 
 	/**
-	 * MySQL Export
+	 * OPTIMIZE TABLE statement
 	 *
-	 * @param	array	Preferences
+	 * @var	string
+	 */
+	protected $_optimize_table	= 'OPTIMIZE TABLE %s';
+
+	/**
+	 * REPAIR TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_repair_table	= 'REPAIR TABLE %s';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Export
+	 *
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index be61aab..17f025f 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQLi Database Adapter Class
@@ -40,34 +41,60 @@
  */
 class CI_DB_mysqli_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'mysqli';
 
-	// The character used for escaping
-	protected $_escape_char = '`';
-
-	// clause and character used for LIKE escape sequences - not used in MySQL
-	protected $_like_escape_str = '';
-	protected $_like_escape_chr = '\\';
-
-	protected $_random_keyword = ' RAND()'; // database specific random keyword
+	/**
+	 * Compression flag
+	 *
+	 * @var	bool
+	 */
+	public $compress = FALSE;
 
 	/**
+	 * DELETE hack flag
+	 *
 	 * Whether to use the MySQL "delete hack" which allows the number
 	 * of affected rows to be shown. Uses a preg_replace when enabled,
 	 * adding a bit more processing to all queries.
+	 *
+	 * @var	bool
 	 */
 	public $delete_hack = TRUE;
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * Non-persistent database connection
+	 * Identifier escape character
 	 *
-	 * @return	object
+	 * @var	string
 	 */
-	public function db_connect()
+	protected $_escape_char = '`';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	object
+	 * @todo	SSL support
+	 */
+	public function db_connect($persistent = FALSE)
 	{
-		return empty($this->port)
-			? @new mysqli($this->hostname, $this->username, $this->password, $this->database)
-			: @new mysqli($this->hostname, $this->username, $this->password, $this->database, $this->port);
+		// Persistent connection support was added in PHP 5.3.0
+		$hostname = ($persistent === TRUE && is_php('5.3'))
+			? 'p:'.$this->hostname : $this->hostname;
+		$port = empty($this->port) ? NULL : $this->port;
+		$client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0;
+		$mysqli = mysqli_init();
+
+		return @$mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, NULL, $client_flags)
+			? $mysqli : FALSE;
 	}
 
 	// --------------------------------------------------------------------
@@ -79,15 +106,7 @@
 	 */
 	public function db_pconnect()
 	{
-		// Persistent connection support was added in PHP 5.3.0
-		if ( ! is_php('5.3'))
-		{
-			return $this->db_connect();
-		}
-
-		return empty($this->port)
-			? @new mysqli('p:'.$this->hostname, $this->username, $this->password, $this->database)
-			: @new mysqli('p:'.$this->hostname, $this->username, $this->password, $this->database, $this->port);
+		return $this->db_connect(TRUE);
 	}
 
 	// --------------------------------------------------------------------
@@ -113,7 +132,7 @@
 	/**
 	 * Select the database
 	 *
-	 * @param	string	database name
+	 * @param	string	$database
 	 * @return	bool
 	 */
 	public function db_select($database = '')
@@ -137,7 +156,7 @@
 	/**
 	 * Set client character set
 	 *
-	 * @param	string
+	 * @param	string	$charset
 	 * @return	bool
 	 */
 	protected function _db_set_charset($charset)
@@ -154,9 +173,16 @@
 	 */
 	public function version()
 	{
-		return isset($this->data_cache['version'])
-			? $this->data_cache['version']
-			: $this->data_cache['version'] = $this->conn_id->server_info;
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
+
+		return $this->data_cache['version'] = $this->conn_id->server_info;
 	}
 
 	// --------------------------------------------------------------------
@@ -164,7 +190,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	mixed
 	 */
 	protected function _execute($sql)
@@ -179,7 +205,7 @@
 	 *
 	 * If needed, each database adapter can prep the query string
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	string
 	 */
 	protected function _prep_query($sql)
@@ -199,6 +225,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -264,8 +291,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -324,7 +351,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -346,7 +373,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -359,8 +386,8 @@
 	/**
 	 * Returns an object with field data
 	 *
-	 * @param	string	the table name
-	 * @return	object
+	 * @param	string	$table
+	 * @return	array
 	 */
 	public function field_data($table = '')
 	{
@@ -369,19 +396,24 @@
 			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
 		}
 
-		$query = $this->query('DESCRIBE '.$this->protect_identifiers($table, TRUE, NULL, FALSE));
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
 		$query = $query->result_object();
 
 		$retval = array();
 		for ($i = 0, $c = count($query); $i < $c; $i++)
 		{
-			preg_match('/([a-z]+)(\(\d+\))?/', $query[$i]->Type, $matches);
-
 			$retval[$i]			= new stdClass();
 			$retval[$i]->name		= $query[$i]->Field;
-			$retval[$i]->type		= empty($matches[1]) ? NULL : $matches[1];
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
 			$retval[$i]->default		= $query[$i]->Default;
-			$retval[$i]->max_length		= empty($matches[2]) ? NULL : preg_replace('/[^\d]/', '', $matches[2]);
 			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
 		}
 
@@ -400,6 +432,14 @@
 	 */
 	public function error()
 	{
+		if ( ! empty($this->conn_id->connect_errno))
+		{
+			return array(
+				'code' => $this->conn_id->connect_errno,
+				'message' => is_php('5.2.9') ? $this->conn_id->connect_error : mysqli_connect_error()
+			);
+		}
+
 		return array('code' => $this->conn_id->errno, 'message' => $this->conn_id->error);
 	}
 
@@ -410,12 +450,12 @@
 	 *
 	 * Generates a platform-specific batch update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -426,7 +466,7 @@
 			{
 				if ($field !== $index)
 				{
-					$final[$field][] =  'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
 				}
 			}
 		}
@@ -439,11 +479,29 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		$where = ($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php
index b74c775..f29ea5a 100644
--- a/system/database/drivers/mysqli/mysqli_forge.php
+++ b/system/database/drivers/mysqli/mysqli_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQLi Forge Class
@@ -34,165 +35,178 @@
  */
 class CI_DB_mysqli_forge extends CI_DB_forge {
 
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_create_database	= 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
 
 	/**
-	 * Process Fields
+	 * CREATE TABLE keys flag
 	 *
-	 * @param	mixed	the fields
-	 * @return	string
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
 	 */
-	protected function _process_fields($fields)
-	{
-		$current_field_count = 0;
-		$sql = '';
+	protected $_create_table_keys	= TRUE;
 
-		foreach ($fields as $field => $attributes)
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT',
+		'SMALLINT',
+		'MEDIUMINT',
+		'INT',
+		'INTEGER',
+		'BIGINT',
+		'REAL',
+		'DOUBLE',
+		'DOUBLE PRECISION',
+		'FLOAT',
+		'DECIMAL',
+		'NUMERIC'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP')
 		{
-			// 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))
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
 			{
-				$sql .= "\n\t".$attributes;
+				$field[$i] = ($alter_type === 'ADD')
+						? "\n\tADD ".$field[$i]['_literal']
+						: "\n\tMODIFY ".$field[$i]['_literal'];
 			}
 			else
 			{
-				$attributes = array_change_key_case($attributes, CASE_UPPER);
-
-				$sql .= "\n\t".$this->db->escape_identifiers($field);
-
-				empty($attributes['NAME']) OR $sql .= ' '.$this->db->escape_identifiers($attributes['NAME']).' ';
-
-				if ( ! empty($attributes['TYPE']))
+				if ($alter_type === 'ADD')
 				{
-					$sql .= ' '.$attributes['TYPE'];
-
-					if ( ! empty($attributes['CONSTRAINT']))
-					{
-						switch (strtolower($attributes['TYPE']))
-						{
-							case 'decimal':
-							case 'float':
-							case 'numeric':
-								$sql .= '('.implode(',', $attributes['CONSTRAINT']).')';
-								break;
-							case 'enum':
-							case 'set':
-								$sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")';
-								break;
-							default:
-								$sql .= '('.$attributes['CONSTRAINT'].')';
-						}
-					}
-				}
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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 .= ',';
-			}
-		}
-
-		return $sql;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Create Table
-	 *
-	 * @param	string	the table name
-	 * @param	mixed	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
-	 */
-	protected 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).' ('.$this->_process_fields($fields);
-
-		if (count($primary_keys) > 0)
-		{
-			$key_name = $this->db->escape_identifiers(implode('_', $primary_keys));
-			$sql .= ",\n\tPRIMARY KEY ".$key_name.' ('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				if (is_array($key))
-				{
-					$key_name = $this->db->escape_identifiers(implode('_', $key));
-					$key = $this->db->escape_identifiers($key);
+					$field[$i]['_literal'] = "\n\tADD ";
 				}
 				else
 				{
-					$key_name = $this->db->escape_identifiers($key);
-					$key = array($key_name);
+					$field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
 				}
 
-				$sql .= ",\n\tKEY ".$key_name.' ('.implode(', ', $key).')';
+				$field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]);
 			}
 		}
 
-		return $sql."\n) DEFAULT CHARACTER SET ".$this->db->char_set.' COLLATE '.$this->db->dbcollat.';';
+		return array($sql.implode(',', $field));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Process column
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
-	 * @param	string	the column name
-	 * @param	array	fields
-	 * @param	string	the field after which we should add the new field
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _alter_table($alter_type, $table, $fields, $after_field = '')
+	protected function _process_column($field)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' ';
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
 
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
 		{
-			return $sql.$this->db->escape_identifiers($fields);
+			$extra_clause = ' FIRST';
 		}
 
-		return $sql.$this->_process_fields($fields)
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
 	}
 
 }
diff --git a/system/database/drivers/mysqli/mysqli_result.php b/system/database/drivers/mysqli/mysqli_result.php
index c1ec4da..ac0f1a8 100644
--- a/system/database/drivers/mysqli/mysqli_result.php
+++ b/system/database/drivers/mysqli/mysqli_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQLi Result Class
@@ -101,7 +102,7 @@
 			$retval[$i]->type		= $field_data[$i]->type;
 			$retval[$i]->max_length		= $field_data[$i]->max_length;
 			$retval[$i]->primary_key	= (int) ($field_data[$i]->flags & 2);
-			$retval[$i]->default		= '';
+			$retval[$i]->default		= $field_data[$i]->def;
 		}
 
 		return $retval;
@@ -130,11 +131,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return $this->result_id->data_seek($n);
 	}
@@ -160,7 +162,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/mysqli/mysqli_utility.php b/system/database/drivers/mysqli/mysqli_utility.php
index 5d2bdbc..345691e 100644
--- a/system/database/drivers/mysqli/mysqli_utility.php
+++ b/system/database/drivers/mysqli/mysqli_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * MySQLi Utility Class
@@ -34,14 +35,33 @@
  */
 class CI_DB_mysqli_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'SHOW DATABASES';
-	protected $_optimize_table	= 'OPTIMIZE TABLE %s';
-	protected $_repair_table	= 'REPAIR TABLE %s';
 
 	/**
-	 * MySQLi Export
+	 * OPTIMIZE TABLE statement
 	 *
-	 * @param	array	Preferences
+	 * @var	string
+	 */
+	protected $_optimize_table	= 'OPTIMIZE TABLE %s';
+
+	/**
+	 * REPAIR TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_repair_table	= 'REPAIR TABLE %s';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Export
+	 *
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php
index 691247f..706211a 100644
--- a/system/database/drivers/oci8/oci8_driver.php
+++ b/system/database/drivers/oci8/oci8_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * oci8 Database Adapter Class
@@ -49,36 +50,80 @@
  */
 class CI_DB_oci8_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'oci8';
 
-	// The character used for excaping
-	protected $_escape_char = '"';
-
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
+	/**
+	 * Statement ID
+	 *
+	 * @var	resource
+	 */
+	public $stmt_id;
 
 	/**
-	 * 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.
+	 * Cursor ID
+	 *
+	 * @var	resource
 	 */
-	protected $_count_string = 'SELECT COUNT(1) AS ';
-	protected $_random_keyword = ' ASC'; // not currently supported
-
-	protected $_reserved_identifiers = array('*', 'rownum');
-
-	// Set "auto commit" by default
-	public $commit_mode = OCI_COMMIT_ON_SUCCESS;
-
-	// need to track statement id and cursor id
-	public $stmt_id;
 	public $curs_id;
 
-	// if we use a limit, we will add a field that will
-	// throw off num_fields later
+	/**
+	 * Commit mode flag
+	 *
+	 * @var	int
+	 */
+	public $commit_mode = OCI_COMMIT_ON_SUCCESS;
+
+	/**
+	 * Limit used flag
+	 *
+	 * If we use LIMIT, we'll add a field that will
+	 * throw off num_fields later.
+	 *
+	 * @var	bool
+	 */
 	public $limit_used;
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * List of reserved identifiers
+	 *
+	 * Identifiers that must NOT be escaped.
+	 *
+	 * @var	string[]
+	 */
+	protected $_reserved_identifiers = array('*', 'rownum');
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('ASC', 'ASC'); // not currently supported
+
+	/**
+	 * COUNT string
+	 *
+	 * @used-by	CI_DB_driver::count_all()
+	 * @used-by	CI_DB_query_builder::count_all_results()
+	 *
+	 * @var	string
+	 */
+	protected $_count_string = 'SELECT COUNT(1) AS ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		parent::__construct($params);
@@ -195,9 +240,21 @@
 	 */
 	public function version()
 	{
-		return isset($this->data_cache['version'])
-			? $this->data_cache['version']
-			: $this->data_cache['version'] = oci_server_version($this->conn_id);
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
+
+		if ( ! $this->conn_id OR ($version = oci_server_version($this->conn_id)) === FALSE)
+		{
+			return FALSE;
+		}
+
+		return $this->data_cache['version'] = $version;
 	}
 
 	// --------------------------------------------------------------------
@@ -205,7 +262,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -224,7 +281,7 @@
 	/**
 	 * Generate a statement ID
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	void
 	 */
 	protected function _set_stmt_id($sql)
@@ -304,7 +361,7 @@
 	/**
 	 * Bind parameters
 	 *
-	 * @param	array
+	 * @param	array	$params
 	 * @return	void
 	 */
 	protected function _bind_params($params)
@@ -333,7 +390,7 @@
 	/**
 	 * Begin Transaction
 	 *
-	 * @param	bool
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -406,8 +463,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -467,7 +524,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -490,27 +547,83 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
 	{
-		return 'SELECT "COLUMN_NAME" FROM "all_tab_columns" WHERE "TABLE_NAME" = '.$this->escape($table);
+		if (strpos($table, '.') !== FALSE)
+		{
+			sscanf($table, '%[^.].%s', $owner, $table);
+		}
+		else
+		{
+			$owner = $this->username;
+		}
+
+		return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
+			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
+				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT * FROM '.$this->protect_identifiers($table).' WHERE rownum = 1';
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+		elseif (strpos($table, '.') !== FALSE)
+		{
+			sscanf($table, '%[^.].%s', $owner, $table);
+		}
+		else
+		{
+			$owner = $this->username;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
+			FROM ALL_TAB_COLUMNS
+			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
+				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+
+			$length = ($query[$i]->CHAR_LENGTH > 0)
+				? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
+			if ($length === NULL)
+			{
+				$length = $query[$i]->DATA_LENGTH;
+			}
+			$retval[$i]->max_length		= $length;
+
+			$default = $query[$i]->DATA_DEFAULT;
+			if ($default === NULL && $query[$i]->NULLABLE === 'N')
+			{
+				$default = '';
+			}
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -547,29 +660,13 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
-	 *
-	 * This function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _from_tables($tables)
-	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Insert_batch statement
+	 * Insert batch statement
 	 *
 	 * Generates a platform-specific insert string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the insert keys
-	 * @param 	array	the insert values
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param 	array	$values	INSERT values
 	 * @return	string
 	 */
 	protected function _insert_batch($table, $keys, $values)
@@ -592,10 +689,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
@@ -610,40 +707,35 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
+	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			$this->where('rownum <= ',$this->qb_limit, FALSE);
+			$this->qb_limit = FALSE;
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-		empty($limit) OR $conditions[] = 'rownum <= '.$limit;
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
 		$this->limit_used = TRUE;
-		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($offset + $limit + 1).')'
-			.($offset ? ' WHERE rnum >= '.($offset + 1): '');
+		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
+			.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1): '');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/oci8/oci8_forge.php b/system/database/drivers/oci8/oci8_forge.php
index 92e8c02..a0efa95 100644
--- a/system/database/drivers/oci8/oci8_forge.php
+++ b/system/database/drivers/oci8/oci8_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Oracle Forge Class
@@ -34,122 +35,96 @@
  */
 class CI_DB_oci8_forge extends CI_DB_forge {
 
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
 	protected $_create_database	= FALSE;
-	protected $_drop_database	= FALSE;
-	protected $_drop_table		= 'DROP TABLE %s';
 
 	/**
-	 * Create Table
+	 * DROP DATABASE statement
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	string
+	 * @var	string
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
-	{
-		$sql = 'CREATE TABLE ';
+	protected $_drop_database	= FALSE;
 
-		if ($if_not_exists === TRUE)
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP')
 		{
-			$sql .= 'IF NOT EXISTS ';
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+		elseif ($alter_type === 'CHANGE')
+		{
+			$alter_type = 'MODIFY';
 		}
 
-		$sql .= $this->db->escape_identifiers($table).' (';
-		$current_field_count = 0;
-
-		foreach ($fields as $field => $attributes)
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
 		{
-			// 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))
+			if ($field[$i]['_literal'] !== FALSE)
 			{
-				$sql .= "\n\t".$attributes;
+				$field[$i] = "\n\t".$field[$i]['_literal'];
 			}
 			else
 			{
-				$attributes = array_change_key_case($attributes, CASE_UPPER);
-
-				$sql .= "\n\t".$this->db->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				if (isset($attributes['UNSINGED']) && $attributes['UNSIGNED'] === TRUE)
+				$field[$i]['_literal'] = "\n\t".$this->_process_column($field[$i]);
+				if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name']))
 				{
-					$sql .= ' UNSIGNED';
+					$sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+						.' '.$this->db->escape_identifiers($field[$i]['new_name']);
 				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= (isset($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? '' : ' NOT NULL';
-
-				empty($attributes['CONSTRAINT']) OR ' CONSTRAINT '.$attributes['CONSTRAINT'];
-			}
-
-			// don't add a comma on the end of the last field
-			if (++$current_field_count < count($fields))
-			{
-				$sql .= ',';
 			}
 		}
 
-		if (count($primary_keys) > 0)
-		{
-			$sql .= ",\n\tCONSTRAINT ".$table.' PRIMARY KEY ('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
+		$sql .= ' '.$alter_type.' ';
+		$sql .= (count($field) === 1)
+				? $field[0]
+				: '('.implode(',', $field).')';
 
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tUNIQUE COLUMNS (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		// RENAME COLUMN must be executed after MODIFY
+		array_unshift($sqls, $sql);
+		return $sql;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Field attribute AUTO_INCREMENT
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _attr_auto_increment(&$attributes, &$field)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name);
-
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
-		{
-			return $sql;
-		}
-
-		return $sql.' '.$column_definition
-			.($default_value !== '' ? ' DEFAULT "'.$default_value.'"' : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
-
+		// Not supported - sequences and triggers must be used instead
 	}
 
 }
diff --git a/system/database/drivers/oci8/oci8_result.php b/system/database/drivers/oci8/oci8_result.php
index a2b600e..84d46f8 100644
--- a/system/database/drivers/oci8/oci8_result.php
+++ b/system/database/drivers/oci8/oci8_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * oci8 Result Class
@@ -37,15 +38,40 @@
  */
 class CI_DB_oci8_result extends CI_DB_result {
 
+	/**
+	 * Statement ID
+	 *
+	 * @var	resource
+	 */
 	public $stmt_id;
-	public $curs_id;
-	public $limit_used;
-	public $commit_mode;
 
 	/**
-	 * Constructor
+	 * Cursor ID
 	 *
-	 * @param	object
+	 * @var	resource
+	 */
+	public $curs_id;
+
+	/**
+	 * Limit used flag
+	 *
+	 * @var	bool
+	 */
+	public $limit_used;
+
+	/**
+	 * Commit mode flag
+	 *
+	 * @var	int
+	 */
+	public $commit_mode;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$driver_object
 	 * @return	void
 	 */
 	public function __construct(&$driver_object)
@@ -157,7 +183,7 @@
 	protected function _fetch_assoc()
 	{
 		$id = ($this->curs_id) ? $this->curs_id : $this->stmt_id;
-		return oci_fetch_assoc($id);
+		return @oci_fetch_assoc($id);
 	}
 
 	// --------------------------------------------------------------------
@@ -167,7 +193,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
@@ -190,63 +216,6 @@
 		return $class_name;
 	}
 
-	// --------------------------------------------------------------------
-
-	/**
-	 * 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.
-	 *
-	 * Oracle's PHP extension doesn't have an easy way of doing this
-	 * and the only workaround is to (re)execute the statement or cursor
-	 * in order to go to the first (zero) index of the result set.
-	 * Then, we would need to "dummy" fetch ($n - 1) rows to get to the
-	 * right one.
-	 *
-	 * This is as ridiculous as it sounds and it's the reason why every
-	 * other method that is fetching data tries to use an already "cached"
-	 * result set. Keeping this just in case it becomes needed at
-	 * some point in the future, but it will only work for resetting the
-	 * pointer to zero.
-	 *
-	 * @return	bool
-	 */
-	protected function _data_seek()
-	{
-		/* The PHP manual says that if OCI_NO_AUTO_COMMIT mode
-		 * is used, and oci_rollback() and/or oci_commit() are
-		 * not subsequently called - this will cause an unnecessary
-		 * rollback to be triggered at the end of the script execution.
-		 *
-		 * Therefore we'll try to avoid using that mode flag
-		 * if we're not currently in the middle of a transaction.
-		 */
-		if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
-		{
-			$result = @oci_execute($this->stmt_id, $this->commit_mode);
-		}
-		else
-		{
-			$result = @oci_execute($this->stmt_id);
-		}
-
-		if ($result && $this->curs_id)
-		{
-			if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
-			{
-				return @oci_execute($this->curs_id, $this->commit_mode);
-			}
-			else
-			{
-				return @oci_execute($this->curs_id);
-			}
-		}
-
-		return $result;
-	}
-
 }
 
 /* End of file oci8_result.php */
diff --git a/system/database/drivers/oci8/oci8_utility.php b/system/database/drivers/oci8/oci8_utility.php
index 0183eda..d15537f 100644
--- a/system/database/drivers/oci8/oci8_utility.php
+++ b/system/database/drivers/oci8/oci8_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Oracle Utility Class
@@ -34,18 +35,23 @@
  */
 class CI_DB_oci8_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'SELECT username FROM dba_users'; // Schemas are actual usernames
 
 	/**
-	 * Oracle Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php
index 8f0a474..29b2281 100644
--- a/system/database/drivers/odbc/odbc_driver.php
+++ b/system/database/drivers/odbc/odbc_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * ODBC Database Adapter Class
@@ -40,23 +41,57 @@
  */
 class CI_DB_odbc_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'odbc';
 
-	// the character used to excape - not necessary for ODBC
+	/**
+	 * Database schema
+	 *
+	 * @var	string
+	 */
+	public $schema = 'public';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Identifier escape character
+	 *
+	 * Must be empty for ODBC.
+	 *
+	 * @var	string
+	 */
 	protected $_escape_char = '';
 
-	// clause and character used for LIKE escape sequences
+	/**
+	 * ESCAPE statement string
+	 *
+	 * @var	string
+	 */
 	protected $_like_escape_str = " {escape '%s'} ";
-	protected $_like_escape_chr = '!';
 
-	protected $_random_keyword;
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RND()', 'RND(%d)');
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		parent::__construct($params);
 
-		$this->_random_keyword = ' RND('.time().')'; // database specific random keyword
-
 		// Legacy support for DSN in the hostname field
 		if (empty($this->dsn))
 		{
@@ -64,6 +99,8 @@
 		}
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Non-persistent database connection
 	 *
@@ -91,7 +128,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -104,6 +141,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -167,8 +205,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -217,7 +255,7 @@
 	 */
 	public function insert_id()
 	{
-		return ($this->db->db_debug) ? $this->db->display_error('db_unsuported_feature') : FALSE;
+		return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
 	}
 
 	// --------------------------------------------------------------------
@@ -227,17 +265,17 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
 	{
-		$sql = 'SHOW TABLES FROM '.$this->database;
+		$sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '".$this->schema."'";
 
 		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
 		{
-			//$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr);
-			return FALSE; // not currently supported
+			return $sql." AND table_name LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
 		}
 
 		return $sql;
@@ -250,7 +288,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -265,7 +303,7 @@
 	 *
 	 * Generates a platform-specific query so that the column data can be retrieved
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _field_data($table)
@@ -291,17 +329,19 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
+	 * Update statement
 	 *
-	 * This function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
+	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @param	array
+	 * @param	string	$table
+	 * @param	array	$values
 	 * @return	string
 	 */
-	protected function _from_tables($tables)
+	protected function _update($table, $values)
 	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -311,10 +351,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
@@ -325,6 +365,22 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Close DB Connection
 	 *
 	 * @return	void
diff --git a/system/database/drivers/odbc/odbc_forge.php b/system/database/drivers/odbc/odbc_forge.php
index b074c58..fb16ca5 100644
--- a/system/database/drivers/odbc/odbc_forge.php
+++ b/system/database/drivers/odbc/odbc_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * ODBC Forge Class
@@ -34,125 +35,39 @@
  */
 class CI_DB_odbc_forge extends CI_DB_forge {
 
-	protected $_drop_database	= 'DROP DATABASE %s';
-	protected $_drop_table		= 'DROP TABLE %s';
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
 
 	/**
-	 * Create Table
+	 * DROP TABLE IF statement
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @var	string
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
-	{
-		$sql = 'CREATE TABLE ';
+	protected $_drop_table_if	= FALSE;
 
-		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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				empty($attributes['CONSTRAINT']) OR $sql .= '('.$attributes['CONSTRAINT'].')';
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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)
-		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
-	}
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Field attribute AUTO_INCREMENT
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _attr_auto_increment(&$attributes, &$field)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name);
-
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
-		{
-			return $sql;
-		}
-
-		return $sql.' '.$column_definition
-			.($default_value != '' ? ' DEFAULT "'.$default_value.'"' : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field != '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+		// Not supported (in most databases at least)
 	}
 
 }
diff --git a/system/database/drivers/odbc/odbc_result.php b/system/database/drivers/odbc/odbc_result.php
index 48dc48d..2c50c25 100644
--- a/system/database/drivers/odbc/odbc_result.php
+++ b/system/database/drivers/odbc/odbc_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * ODBC Result Class
@@ -165,7 +166,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
@@ -198,11 +199,11 @@
 	 * Emulates the native odbc_fetch_array() function when
 	 * it is not available (odbc_fetch_array() requires unixODBC)
 	 *
-	 * @param	resource
-	 * @param	int
+	 * @param	resource	&$result
+	 * @param	int		$rownumber
 	 * @return	array
 	 */
-	function odbc_fetch_array(& $result, $rownumber = 1)
+	function odbc_fetch_array(&$result, $rownumber = 1)
 	{
 		$rs = array();
 		if ( ! odbc_fetch_into($result, $rs, $rownumber))
@@ -231,11 +232,11 @@
 	 * Emulates the native odbc_fetch_object() function when
 	 * it is not available.
 	 *
-	 * @param	resource
-	 * @param	int
+	 * @param	resource	&$result
+	 * @param	int		$rownumber
 	 * @return	object
 	 */
-	function odbc_fetch_object(& $result, $rownumber = 1)
+	function odbc_fetch_object(&$result, $rownumber = 1)
 	{
 		$rs = array();
 		if ( ! odbc_fetch_into($result, $rs, $rownumber))
diff --git a/system/database/drivers/odbc/odbc_utility.php b/system/database/drivers/odbc/odbc_utility.php
index 224d48d..908674a 100644
--- a/system/database/drivers/odbc/odbc_utility.php
+++ b/system/database/drivers/odbc/odbc_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * ODBC Utility Class
@@ -34,18 +35,16 @@
  */
 class CI_DB_odbc_utility extends CI_DB_utility {
 
-	protected $_list_databases	= FALSE;
-
 	/**
-	 * ODBC Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php
index 38a9fec..d2dfa8b 100644
--- a/system/database/drivers/pdo/pdo_driver.php
+++ b/system/database/drivers/pdo/pdo_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * PDO Database Adapter Class
@@ -40,21 +41,37 @@
  */
 class CI_DB_pdo_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'pdo';
 
-	// the character used to excape - not necessary for PDO
-	protected $_escape_char = '';
+	/**
+	 * Transaction enabled flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_enabled = FALSE;
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
-
-	protected $_random_keyword;
-
-	// need to track the pdo driver and options
-	public $pdodriver;
+	/**
+	 * PDO Options
+	 *
+	 * @var	array
+	 */
 	public $options = array();
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Validates the DSN string and/or detects the subdriver.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		parent::__construct($params);
@@ -63,172 +80,54 @@
 		{
 			// If there is a minimum valid dsn string pattern found, we're done
 			// This is for general PDO users, who tend to have a full DSN string.
-			$this->pdodriver = end($match);
+			$this->subdriver = $match[1];
+			return;
 		}
-		else
+		// Legacy support for DSN specified in the hostname field
+		elseif (preg_match('/([^;]+):/', $this->hostname, $match) && count($match) === 2)
 		{
-			// Try to build a complete DSN string from params
-			$this->_connect_string($params);
+			$this->dsn = $this->hostname;
+			$this->hostname = NULL;
+			$this->subdriver = $match[1];
+			return;
 		}
+		elseif (in_array($this->subdriver, array('mssql', 'sybase'), TRUE))
+		{
+			$this->subdriver = 'dblib';
+		}
+		elseif ($this->subdriver === '4D')
+		{
+			$this->subdriver = '4d';
+		}
+		elseif ( ! in_array($this->subdriver, array('4d', 'cubrid', 'dblib', 'firebird', 'ibm', 'informix', 'mysql', 'oci', 'odbc', 'sqlite', 'sqlsrv'), TRUE))
+		{
+			log_message('error', 'PDO: Invalid or non-existent subdriver');
 
-		// clause and character used for LIKE escape sequences
-		// this one depends on the driver being used
-		if ($this->pdodriver === 'mysql')
-		{
-			$this->_escape_char = '`';
-			$this->_like_escape_str = '';
-			$this->_like_escape_chr = '\\';
-		}
-		elseif ($this->pdodriver === 'odbc')
-		{
-			$this->_like_escape_str = " {escape '%s'} ";
-		}
-		elseif ( ! in_array($this->pdodriver, array('sqlsrv', 'mssql', 'dblib', 'sybase')))
-		{
-			$this->_escape_char = '"';
-		}
-
-		$this->trans_enabled = FALSE;
-		$this->_random_keyword = ' RND('.time().')'; // database specific random keyword
-	}
-
-	/**
-	 * Connection String
-	 *
-	 * @param	array
-	 * @return	void
-	 */
-	protected function _connect_string($params)
-	{
-		if (strpos($this->hostname, ':'))
-		{
-			// hostname generally would have this prototype
-			// $db['hostname'] = 'pdodriver:host(/Server(/DSN))=hostname(/DSN);';
-			// We need to get the prefix (pdodriver used by PDO).
-			$dsnarray = explode(':', $this->hostname);
-			$this->pdodriver = $dsnarray[0];
-
-			// End dsn with a semicolon for extra backward compability
-			// if database property was not empty.
-			if ( ! empty($this->database))
+			if ($this->db_debug)
 			{
-				$this->dsn .= rtrim($this->hostname, ';').';';
-			}
-		}
-		else
-		{
-			// Invalid DSN, display an error
-			if ( ! array_key_exists('pdodriver', $params))
-			{
-				show_error('Invalid DB Connection String for PDO');
-			}
-
-			// Assuming that the following DSN string format is used:
-			// $dsn = 'pdo://username:password@hostname:port/database?pdodriver=pgsql';
-			$this->dsn = $this->pdodriver.':';
-
-			// Add hostname to the DSN for databases that need it
-			if ( ! empty($this->hostname)
-				&& strpos($this->hostname, ':') === FALSE
-				&& in_array($this->pdodriver, array('informix', 'mysql', 'pgsql', 'sybase', 'mssql', 'dblib', 'cubrid')))
-			{
-			    $this->dsn .= 'host='.$this->hostname.';';
-			}
-
-			// Add a port to the DSN for databases that can use it
-			if ( ! empty($this->port) && in_array($this->pdodriver, array('informix', 'mysql', 'pgsql', 'ibm', 'cubrid')))
-			{
-			    $this->dsn .= 'port='.$this->port.';';
+				show_error('Invalid or non-existent PDO subdriver');
 			}
 		}
 
-		// Add the database name to the DSN, if needed
-	    if (stripos($this->dsn, 'dbname') === FALSE
-	       && in_array($this->pdodriver, array('4D', 'pgsql', 'mysql', 'firebird', 'sybase', 'mssql', 'dblib', 'cubrid')))
-	    {
-	        $this->dsn .= 'dbname='.$this->database.';';
-	    }
-	    elseif (stripos($this->dsn, 'database') === FALSE && in_array($this->pdodriver, array('ibm', 'sqlsrv')))
-	    {
-	    	if (stripos($this->dsn, 'dsn') === FALSE)
-	    	{
-		        $this->dsn .= 'database='.$this->database.';';
-	    	}
-	    }
-	    elseif ($this->pdodriver === 'sqlite' && $this->dsn === 'sqlite:')
-	    {
-	        if ($this->database !== ':memory')
-	        {
-	            if ( ! file_exists($this->database))
-	            {
-	                show_error('Invalid DB Connection string for PDO SQLite');
-	            }
-
-	            $this->dsn .= (strpos($this->database, DIRECTORY_SEPARATOR) !== 0) ? DIRECTORY_SEPARATOR : '';
-	        }
-
-	        $this->dsn .= $this->database;
-	    }
-
-	    // Add charset to the DSN, if needed
-	    if ( ! empty($this->char_set) && in_array($this->pdodriver, array('4D', 'mysql', 'sybase', 'mssql', 'dblib', 'oci')))
-	    {
-	        $this->dsn .= 'charset='.$this->char_set.';';
-	    }
+		$this->dsn = NULL;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Non-persistent database connection
+	 * Database connection
 	 *
+	 * @param	bool	$persistent
 	 * @return	object
 	 */
-	public function db_connect()
+	public function db_connect($persistent = FALSE)
 	{
-		return $this->_pdo_connect();
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Persistent database connection
-	 *
-	 * @return	object
-	 */
-	public function db_pconnect()
-	{
-		return $this->_pdo_connect(TRUE);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * PDO connection
-	 *
-	 * @param	bool
-	 * @return	object
-	 */
-	protected function _pdo_connect($persistent = FALSE)
-	{
-		$this->options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
-		$persistent === FALSE OR $this->options[PDO::ATTR_PERSISTENT] = TRUE;
-
-		/* Prior to PHP 5.3.6, even if the charset was supplied in the DSN
-		 * on connect - it was ignored. This is a work-around for the issue.
-		 *
-		 * Reference: http://www.php.net/manual/en/ref.pdo-mysql.connection.php
-		 */
-		if ($this->pdodriver === 'mysql' && ! is_php('5.3.6') && ! empty($this->char_set))
-		{
-			$this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$this->char_set
-					.( ! empty($this->db_collat) ? " COLLATE '".$this->dbcollat."'" : '');
-		}
+		$this->options[PDO::ATTR_PERSISTENT] = $persistent;
 
 		// Connecting...
 		try
 		{
-			return new PDO($this->dsn, $this->username, $this->password, $this->options);
+			return @new PDO($this->dsn, $this->username, $this->password, $this->options);
 		}
 		catch (PDOException $e)
 		{
@@ -244,6 +143,18 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Persistent database connection
+	 *
+	 * @return	object
+	 */
+	public function db_pconnect()
+	{
+		return $this->db_connect(TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Database version number
 	 *
 	 * @return	string
@@ -254,6 +165,10 @@
 		{
 			return $this->data_cache['version'];
 		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
 
 		// Not all subdrivers support the getAttribute() method
 		try
@@ -271,7 +186,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	SQL query
 	 * @return	mixed
 	 */
 	protected function _execute($sql)
@@ -284,6 +199,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -343,8 +259,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -363,7 +279,7 @@
 		$str = $this->conn_id->quote($str);
 
 		// If there are duplicated quotes, trim them away
-		if (strpos($str, "'") === 0)
+		if ($str[0] === "'")
 		{
 			$str = substr($str, 1, -1);
 		}
@@ -396,100 +312,27 @@
 	/**
 	 * Insert ID
 	 *
-	 * @param	string
+	 * @param	string	$name
 	 * @return	int
 	 */
 	public function insert_id($name = NULL)
 	{
-		if ($this->pdodriver === 'pgsql' && $name === NULL && $this->version() >= '8.1')
-		{
-			$query = $this->query('SELECT LASTVAL() AS ins_id');
-			$query = $query->row();
-			return $query->ins_id;
-		}
-
 		return $this->conn_id->lastInsertId($name);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Show table query
-	 *
-	 * Generates a platform-specific query string so that the table names can be fetched
-	 *
-	 * @param	bool
-	 * @return	string
-	 */
-	protected function _list_tables($prefix_limit = FALSE)
-	{
-		if ($this->pdodriver === 'pgsql')
-		{
-			// Analog function to show all tables in postgre
-			$sql = "SELECT * FROM information_schema.tables WHERE table_schema = 'public'";
-		}
-		elseif ($this->pdodriver === 'sqlite')
-		{
-			// Analog function to show all tables in sqlite
-			$sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
-		}
-		else
-		{
-			$sql = 'SHOW TABLES FROM '.$this->escape_identifiers($this->database);
-		}
-
-		if ($prefix_limit !== FALSE AND $this->dbprefix !== '')
-		{
-			return FALSE;
-		}
-
-		return $sql;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Show column query
-	 *
-	 * Generates a platform-specific query string so that the column names can be fetched
-	 *
-	 * @param	string	the table name
-	 * @return	string
-	 */
-	protected function _list_columns($table = '')
-	{
-		return 'SHOW COLUMNS FROM '.$this->escape_identifiers($table);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Field data query
 	 *
 	 * Generates a platform-specific query so that the column data can be retrieved
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _field_data($table)
 	{
-		if ($this->pdodriver === 'mysql' or $this->pdodriver === 'pgsql')
-		{
-			// Analog function for mysql and postgre
-			return 'SELECT * FROM '.$this->escape_identifiers($table).' LIMIT 1';
-		}
-		elseif ($this->pdodriver === 'oci')
-		{
-			// Analog function for oci
-			return 'SELECT * FROM '.$this->escape_identifiers($table).' WHERE ROWNUM <= 1';
-		}
-		elseif ($this->pdodriver === 'sqlite')
-		{
-			// Analog function for sqlite
-			return 'PRAGMA table_info('.$this->escape_identifiers($table).')';
-		}
-
-		return 'SELECT TOP 1 FROM '.$this->escape_identifiers($table);
+		return 'SELECT TOP 1 * FROM '.$this->protect_identifiers($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -528,16 +371,14 @@
 	 *
 	 * Generates a platform-specific batch update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
-		$ids   = array();
-		$where = ($where !== '' && count($where) >=1) ? implode(" ", $where).' AND ' : '';
-
+		$ids = array();
 		foreach ($values as $key => $val)
 		{
 			$ids[] = $val[$index];
@@ -546,14 +387,12 @@
 			{
 				if ($field !== $index)
 				{
-					$final[$field][] =  'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
 				}
 			}
 		}
 
-		$sql   = 'UPDATE '.$table.' SET ';
 		$cases = '';
-
 		foreach ($final as $k => $v)
 		{
 			$cases .= $k.' = CASE '."\n";
@@ -566,10 +405,9 @@
 			$cases .= 'ELSE '.$k.' END, ';
 		}
 
-		$sql .= substr($cases, 0, -2);
-		$sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
 
-		return $sql;
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
@@ -579,37 +417,15 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
 	{
-		return 'DELETE FROM '.$table;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Limit string
-	 *
-	 * Generates a platform-specific LIMIT clause
-	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
-	 * @return	string
-	 */
-	protected function _limit($sql, $limit, $offset)
-	{
-		if ($this->pdodriver === 'pgsql')
-		{
-			return $sql.' LIMIT '.$limit.($offset ? ' OFFSET '.$offset : '');
-		}
-
-		return $sql.' LIMIT '.($offset ? $offset.', ' : '').$limit;
+		return 'TRUNCATE TABLE '.$table;
 	}
 
 }
diff --git a/system/database/drivers/pdo/pdo_forge.php b/system/database/drivers/pdo/pdo_forge.php
index 02ceb74..130d366 100644
--- a/system/database/drivers/pdo/pdo_forge.php
+++ b/system/database/drivers/pdo/pdo_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * PDO Forge Class
@@ -34,134 +35,19 @@
  */
 class CI_DB_pdo_forge extends CI_DB_forge {
 
-	protected $_drop_database	= 'DROP DATABASE %s';
-	protected $_drop_table		= 'DROP TABLE %s';
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
 
 	/**
-	 * Create Table
+	 * DROP TABLE IF statement
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @var	string
 	 */
-	protected 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);
-				$numeric = array('SERIAL', 'INTEGER');
-
-				$sql .= "\n\t".$this->db->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				if ( ! empty($attributes['CONSTRAINT']))
-				{
-					// Exception for Postgre numeric which not too happy with constraint within those type
-					if ( ! ($this->db->pdodriver === 'pgsql' && in_array($attributes['TYPE'], $numeric)))
-					{
-						$sql .= '('.$attributes['CONSTRAINT'].')';
-					}
-				}
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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)
-		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Alter table query
-	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
-	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
-	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name);
-
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
-		{
-			return $sql;
-		}
-
-		return $sql .' '.$column_definition
-			.($default_value !== '' ? " DEFAULT '".$default_value."'" : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
-	}
+	protected $_drop_table_if	= FALSE;
 
 }
 
diff --git a/system/database/drivers/pdo/pdo_result.php b/system/database/drivers/pdo/pdo_result.php
index 4444069..25cf87b 100644
--- a/system/database/drivers/pdo/pdo_result.php
+++ b/system/database/drivers/pdo/pdo_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * PDO Result Class
@@ -108,61 +109,28 @@
 	 */
 	public function field_data()
 	{
-		$data = array();
-
 		try
 		{
-			if (strpos($this->result_id->queryString, 'PRAGMA') !== FALSE)
+			$retval = array();
+
+			for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 			{
-				foreach ($this->result_array() as $field)
-				{
-					preg_match('/([a-zA-Z]+)(\(\d+\))?/', $field['type'], $matches);
+				$field = $this->result_id->getColumnMeta($i);
 
-					$F		= new stdClass();
-					$F->name	= $field['name'];
-					$F->type	= ( ! empty($matches[1])) ? $matches[1] : NULL;
-					$F->default	= NULL;
-					$F->max_length	= ( ! empty($matches[2])) ? preg_replace('/[^\d]/', '', $matches[2]) : NULL;
-					$F->primary_key = (int) $field['pk'];
-					$F->pdo_type	= NULL;
-
-					$data[] = $F;
-				}
-			}
-			else
-			{
-				for($i = 0, $max = $this->num_fields(); $i < $max; $i++)
-				{
-					$field = $this->result_id->getColumnMeta($i);
-
-					$F		= new stdClass();
-					$F->name	= $field['name'];
-					$F->type	= $field['native_type'];
-					$F->default	= NULL;
-					$F->pdo_type	= $field['pdo_type'];
-
-					if ($field['precision'] < 0)
-					{
-						$F->max_length	= NULL;
-						$F->primary_key = 0;
-					}
-					else
-					{
-						$F->max_length	= ($field['len'] > 255) ? 0 : $field['len'];
-						$F->primary_key = (int) ( ! empty($field['flags']) && in_array('primary_key', $field['flags']));
-					}
-
-					$data[] = $F;
-				}
+				$retval[$i]			= new stdClass();
+				$retval[$i]->name		= $field['name'];
+				$retval[$i]->type		= $field['native_type'];
+				$retval[$i]->max_length		= ($field['len'] > 0) ? $field['len'] : NULL;
+				$retval[$i]->primary_key	= (int) ( ! empty($field['flags']) && in_array('primary_key', $field['flags'], TRUE));
 			}
 
-			return $data;
+			return $retval;
 		}
 		catch (Exception $e)
 		{
 			if ($this->db->db_debug)
 			{
-				return $this->db->display_error('db_unsuported_feature');
+				return $this->db->display_error('db_unsupported_feature');
 			}
 
 			return FALSE;
@@ -205,7 +173,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/pdo/pdo_utility.php b/system/database/drivers/pdo/pdo_utility.php
index 9308421..86f2b12 100644
--- a/system/database/drivers/pdo/pdo_utility.php
+++ b/system/database/drivers/pdo/pdo_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * PDO Utility Class
@@ -34,18 +35,16 @@
  */
 class CI_DB_pdo_utility extends CI_DB_utility {
 
-	protected $_list_databases = FALSE;
-
 	/**
-	 * PDO Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/pdo/subdrivers/index.html b/system/database/drivers/pdo/subdrivers/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/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/pdo/subdrivers/pdo_4d_driver.php b/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php
new file mode 100644
index 0000000..6ebd92a
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO 4D Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_4d_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = '4d';
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string[]
+	 */
+	protected $_escape_char = array('[', ']');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = '4D:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			empty($this->port) OR $this->dsn .= ';port='.$this->port;
+			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+		}
+		elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 3) === FALSE)
+		{
+			$this->dsn .= ';charset='.$this->char_set;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT '.$this->escape_identifiers('TABLE_NAME').' FROM '.$this->escape_identifiers('_USER_TABLES');
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			$sql .= ' WHERE '.$this->escape_identifiers('TABLE_NAME')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT '.$this->escape_identifiers('COLUMN_NAME').' FROM '.$this->escape_identifiers('_USER_COLUMNS')
+			.' WHERE '.$this->escape_identifiers('TABLE_NAME').' = '.$this->escape($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data query
+	 *
+	 * Generates a platform-specific query so that the column data can be retrieved
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _field_data($table)
+	{
+		return 'SELECT * FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE).' LIMIT 1';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+         */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
+	}
+
+}
+
+/* End of file pdo_4d_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_4d_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php b/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php
new file mode 100644
index 0000000..231ad53
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO 4D Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_4d_forge extends CI_DB_4d_forge {
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= 'CREATE SCHEMA %s';
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= 'DROP SCHEMA %s';
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= FALSE;
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'INT16'		=> 'INT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'INT64',
+		'INT32'		=> 'INT64'
+	);
+
+	/**
+	 * DEFAULT value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_default		= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
+		{
+			return parent::_alter_table($alter_table, $table, $field);
+		}
+
+		// No method of modifying columns is supported
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['null']
+			.$field['unique']
+			.$field['auto_increment'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			case 'BIGINT':
+				$attribites['TYPE'] = 'INT64';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNIQUE
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unique(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
+		{
+			$field['unique'] = ' UNIQUE';
+
+			// UNIQUE must be used with NOT NULL
+			$field['null'] = ' NOT NULL';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE)
+		{
+			if (stripos($field['type'], 'int') !== FALSE)
+			{
+				$field['auto_increment'] = ' AUTO_INCREMENT';
+			}
+			elseif (strcasecmp($field['type'], 'UUID') === 0)
+			{
+				$field['auto_increment'] = ' AUTO_GENERATE';
+			}
+		}
+	}
+
+}
+
+/* End of file pdo_4d_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_4d_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php
new file mode 100644
index 0000000..7e703e7
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php
@@ -0,0 +1,247 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO CUBRID Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_cubrid_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'cubrid';
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '`';
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM(%d)');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'cubrid:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			empty($this->port) OR $this->dsn .= ';port='.$this->port;
+			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SHOW TABLES';
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->Field;
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
+			$retval[$i]->default		= $query[$i]->Default;
+			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update_Batch statement
+	 *
+	 * Generates a platform-specific batch update string from the supplied data
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
+	 * @return	string
+	 */
+	protected function _update_batch($table, $values, $index)
+	{
+		$ids = array();
+		foreach ($values as $key => $val)
+		{
+			$ids[] = $val[$index];
+
+			foreach (array_keys($val) as $field)
+			{
+				if ($field !== $index)
+				{
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+				}
+			}
+		}
+
+		$cases = '';
+		foreach ($final as $k => $v)
+		{
+			$cases .= $k." = CASE \n"
+				.implode("\n", $v)."\n"
+				.'ELSE '.$k.' END), ';
+		}
+
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'TRUNCATE '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
+	}
+
+}
+
+/* End of file pdo_cubrid_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php
new file mode 100644
index 0000000..ebf4b15
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php
@@ -0,0 +1,219 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO CUBRID Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_cubrid_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= FALSE;
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= FALSE;
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= TRUE;
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SHORT'		=> 'INTEGER',
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'BIGINT'	=> 'NUMERIC',
+		'FLOAT'		=> 'DOUBLE',
+		'REAL'		=> 'DOUBLE'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				$sqls[] = $sql.' CHANGE '.$field[$i]['_literal'];
+			}
+			else
+			{
+				$alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE ';
+				$sqls[] = $sql.$alter_type.$this->_process_column($field[$i]);
+			}
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
+
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
+		{
+			$extra_clause = ' FIRST';
+		}
+
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
+	}
+
+}
+
+/* End of file pdo_cubrid_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php
new file mode 100644
index 0000000..cf049b6
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php
@@ -0,0 +1,329 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO DBLIB Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_dblib_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'dblib';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
+
+	/**
+	 * Quoted identifier flag
+	 *
+	 * Whether to use SQL-92 standard quoted identifier
+	 * (double quotes) or brackets for identifier escaping.
+	 *
+	 * @var	bool
+	 */
+	protected $_quoted_identifier;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = $params['subdriver'].':host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			if ( ! empty($this->port))
+			{
+				$this->dsn .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port;
+			}
+
+			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+			empty($this->appname) OR $this->dsn .= ';appname='.$this->appname;
+		}
+		else
+		{
+			if ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 6) === FALSE)
+			{
+				$this->dsn .= ';charset='.$this->char_set;
+			}
+
+			$this->subdriver = 'dblib';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	object
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		$this->conn_id = parent::db_connect($persistent);
+
+		if ( ! is_object($this->conn_id))
+		{
+			return $this->conn_id;
+		}
+
+		// Determine how identifiers are escaped
+		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+		$query = $query->row_array();
+		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
+		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+
+		return $this->conn_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		return 'SELECT '.$this->escape_identifiers('name')
+			.' FROM '.$this->escape_identifiers('sysobjects')
+			.' WHERE '.$this->escape_identifiers('type')." = 'U'";
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			$sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql.' ORDER BY '.$this->escape_identifiers('name');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT COLUMN_NAME
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+         */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
+
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		$limit = $this->qb_offset + $this->qb_limit;
+
+		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
+		// however an ORDER BY clause is required for it to work
+		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
+		{
+			$orderby = $this->_compile_order_by();
+
+			// We have to strip the ORDER BY clause
+			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
+
+			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
+			if (count($this->qb_select) === 0)
+			{
+				$select = '*'; // Inevitable
+			}
+			else
+			{
+				// Use only field names and their aliases, everything else is out of our scope.
+				$select = array();
+				$field_regexp = ($this->_quoted_identifier)
+					? '("[^\"]+")' : '(\[[^\]]+\])';
+				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
+				{
+					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
+						? $m[1] : $this->qb_select[$i];
+				}
+				$select = implode(', ', $select);
+			}
+
+			return 'SELECT '.$select." FROM (\n\n"
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				."\n\n) ".$this->escape_identifiers('CI_subquery')
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
+		}
+
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		// Multiple-value inserts are only supported as of SQL Server 2008
+		if (version_compare($this->version(), '10', '>='))
+		{
+			return parent::_insert_batch($table, $keys, $values);
+		}
+
+		return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+	}
+
+}
+
+/* End of file pdo_dblib_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php b/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php
new file mode 100644
index 0000000..236e1cc
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO DBLIB Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_dblib_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT'	=> 'SMALLINT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'BIGINT',
+		'REAL'		=> 'FLOAT'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			$sqls[] = $sql.$this->_process_column($field[$i]);
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' IDENTITY(1,1)';
+		}
+	}
+
+}
+
+/* End of file pdo_dblib_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
new file mode 100644
index 0000000..adac038
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
@@ -0,0 +1,260 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Firebird Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_firebird_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'firebird';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RAND()', 'RAND()');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'firebird:';
+
+			if ( ! empty($this->database))
+			{
+				$this->dsn .= 'dbname='.$this->database;
+			}
+			elseif ( ! empty($this->hostname))
+			{
+				$this->dsn .= 'dbname='.$this->hostname;
+			}
+
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+			empty($this->role) OR $this->dsn .= ';role='.$this->role;
+		}
+		elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 9) === FALSE)
+		{
+			$this->dsn .= ';charset='.$this->char_set;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "RDB$RELATION_NAME" FROM "RDB$RELATIONS" WHERE "RDB$RELATION_NAME" NOT LIKE \'RDB$%\' AND "RDB$RELATION_NAME" NOT LIKE \'MON$%\'';
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql.' AND "RDB$RELATION_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT "RDB$FIELD_NAME" FROM "RDB$RELATION_FIELDS" WHERE "RDB$RELATION_NAME" = '.$this->escape($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT "rfields"."RDB$FIELD_NAME" AS "name",
+				CASE "fields"."RDB$FIELD_TYPE"
+					WHEN 7 THEN \'SMALLINT\'
+					WHEN 8 THEN \'INTEGER\'
+					WHEN 9 THEN \'QUAD\'
+					WHEN 10 THEN \'FLOAT\'
+					WHEN 11 THEN \'DFLOAT\'
+					WHEN 12 THEN \'DATE\'
+					WHEN 13 THEN \'TIME\'
+					WHEN 14 THEN \'CHAR\'
+					WHEN 16 THEN \'INT64\'
+					WHEN 27 THEN \'DOUBLE\'
+					WHEN 35 THEN \'TIMESTAMP\'
+					WHEN 37 THEN \'VARCHAR\'
+					WHEN 40 THEN \'CSTRING\'
+					WHEN 261 THEN \'BLOB\'
+					ELSE NULL
+				END AS "type",
+				"fields"."RDB$FIELD_LENGTH" AS "max_length",
+				"rfields"."RDB$DEFAULT_VALUE" AS "default"
+			FROM "RDB$RELATION_FIELDS" "rfields"
+				JOIN "RDB$FIELDS" "fields" ON "rfields"."RDB$FIELD_SOURCE" = "fields"."RDB$FIELD_NAME"
+			WHERE "rfields"."RDB$RELATION_NAME" = '.$this->escape($table).'
+			ORDER BY "rfields"."RDB$FIELD_POSITION"';
+
+		return (($query = $this->query($sql)) !== FALSE)
+			? $query->result_object()
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+         */
+	protected function _truncate($table)
+	{
+		return 'DELETE FROM '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		// Limit clause depends on if Interbase or Firebird
+		if (stripos($this->version(), 'firebird') !== FALSE)
+		{
+			$select = 'FIRST '.$this->qb_limit
+				.($this->qb_offset > 0 ? ' SKIP '.$this->qb_offset : '');
+		}
+		else
+		{
+			$select = 'ROWS '
+				.($this->qb_offset > 0 ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
+		}
+
+		return preg_replace('`SELECT`i', 'SELECT '.$select, $sql);
+	}
+
+}
+
+/* End of file pdo_firebird_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
new file mode 100644
index 0000000..7b86de9
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Firebird Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_firebird_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SMALLINT'	=> 'INTEGER',
+		'INTEGER'	=> 'INT64',
+		'FLOAT'		=> 'DOUBLE PRECISION'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create database
+	 *
+	 * @param	string	$db_name
+	 * @return	string
+	 */
+	public function create_database($db_name)
+	{
+		// Firebird databases are flat files, so a path is required
+
+		// Hostname is needed for remote access
+		empty($this->db->hostname) OR $db_name = $this->hostname.':'.$db_name;
+
+		return parent::create_database('"'.$db_name.'"');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @param	string	$db_name	(ignored)
+	 * @return	bool
+	 */
+	public function drop_database($db_name = '')
+	{
+		if ( ! ibase_drop_db($this->conn_id))
+		{
+			return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE;
+		}
+		elseif ( ! empty($this->db->data_cache['db_names']))
+		{
+			$key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE);
+			if ($key !== FALSE)
+			{
+				unset($this->db->data_cache['db_names'][$key]);
+			}
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+ 	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				return FALSE;
+			}
+
+			if (isset($field[$i]['type']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TYPE '.$field[$i]['type'].$field[$i]['length'];
+			}
+
+			if ( ! empty($field[$i]['default']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' SET DEFAULT '.$field[$i]['default'];
+			}
+
+			if (isset($field[$i]['null']))
+			{
+				$sqls[] = 'UPDATE "RDB$RELATION_FIELDS" SET "RDB$NULL_FLAG" = '
+					.($field[$i]['null'] === TRUE ? 'NULL' : '1')
+					.' WHERE "RDB$FIELD_NAME" = '.$this->db->escape($field[$i]['name'])
+					.' AND "RDB$RELATION_NAME" = '.$this->db->escape($table);
+			}
+
+			if ( ! empty($field[$i]['new_name']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
+			}
+		}
+
+		return $sqls;
+ 	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type'].$field['length']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INT':
+				$attributes['TYPE'] = 'INTEGER';
+				return;
+			case 'BIGINT':
+				$attributes['TYPE'] = 'INT64';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported
+	}
+
+}
+
+/* End of file pdo_firebird_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php b/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php
new file mode 100644
index 0000000..2ef97cd
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO IBM DB2 Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_ibm_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'ibm';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'ibm:';
+
+			// Pre-defined DSN
+			if (empty($this->hostname) && empty($this->HOSTNAME) && empty($this->port) && empty($this->PORT))
+			{
+				if (isset($this->DSN))
+				{
+					$this->dsn .= 'DSN='.$this->DSN;
+				}
+				elseif ( ! empty($this->database))
+				{
+					$this->dsn .= 'DSN='.$this->database;
+				}
+
+				return;
+			}
+
+			$this->dsn .= 'DRIVER='.(isset($this->DRIVER) ? '{'.$this->DRIVER.'}' : '{IBM DB2 ODBC DRIVER}').';';
+
+			if (isset($this->DATABASE))
+			{
+				$this->dsn .= 'DATABASE='.$this->DATABASE.';';
+			}
+			elseif ( ! empty($this->database))
+			{
+				$this->dsn .= 'DATABASE='.$this->database.';';
+			}
+
+			if (isset($this->HOSTNAME))
+			{
+				$this->dsn .= 'HOSTNAME='.$this->HOSTNAME.';';
+			}
+			else
+			{
+				$this->dsn .= 'HOSTNAME='.(empty($this->hostname) ? '127.0.0.1;' : $this->hostname.';');
+			}
+
+			if (isset($this->PORT))
+			{
+				$this->dsn .= 'PORT='.$this->port.';';
+			}
+			elseif ( ! empty($this->port))
+			{
+				$this->dsn .= ';PORT='.$this->port.';';
+			}
+
+			$this->dsn .= 'PROTOCOL='.(isset($this->PROTOCOL) ? $this->PROTOCOL.';' : 'TCPIP;');
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "tabname" FROM "syscat"."tables"
+			WHERE "type" = \'T\' AND LOWER("tabschema") = '.$this->escape(strtolower($this->database));
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			$sql .= ' AND "tabname" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT "colname" FROM "syscat"."columns"
+			WHERE LOWER("tabschema") = '.$this->escape(strtolower($this->database)).'
+				AND LOWER("tabname") = '.$this->escape(strtolower($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		$sql = 'SELECT "colname" AS "name", "typename" AS "type", "default" AS "default", "length" AS "max_length",
+				CASE "keyseq" WHEN NULL THEN 0 ELSE 1 END AS "primary_key"
+			FROM "syscat"."columns"
+			WHERE LOWER("tabschema") = '.$this->escape(strtolower($this->database)).'
+				AND LOWER("tabname") = '.$this->escape(strtolower($table)).'
+			ORDER BY "colno"';
+
+		return (($query = $this->query($sql)) !== FALSE)
+			? $query->result_object()
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		$sql .= ' FETCH FIRST '.($this->qb_limit + $this->qb_offset).' ROWS ONLY';
+
+		return ($this->qb_offset)
+			? 'SELECT * FROM ('.$sql.') WHERE rownum > '.$this->qb_offset
+			: $sql;
+	}
+
+}
+
+/* End of file pdo_ibm_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php b/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php
new file mode 100644
index 0000000..f095c71
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO IBM DB2 Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_ibm_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * RENAME TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= 'RENAME TABLE %s TO %s';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT'
+	);
+
+	/**
+	 * DEFAULT value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_default		= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'CHANGE')
+		{
+			$alter_type = 'MODIFY';
+		}
+
+		return parent::_alter_table($alter_type, $table, $field);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNIQUE
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unique(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
+		{
+			$field['unique'] = ' UNIQUE';
+
+			// UNIQUE must be used with NOT NULL
+			$field['null'] = ' NOT NULL';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported
+	}
+
+}
+
+/* End of file pdo_ibm_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php b/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php
new file mode 100644
index 0000000..cfb474e
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Informix Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_informix_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'informix';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('ASC', 'ASC'); // Currently not supported
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'informix:';
+
+			// Pre-defined DSN
+			if (empty($this->hostname) && empty($this->host) && empty($this->port) && empty($this->service))
+			{
+				if (isset($this->DSN))
+				{
+					$this->dsn .= 'DSN='.$this->DSN;
+				}
+				elseif ( ! empty($this->database))
+				{
+					$this->dsn .= 'DSN='.$this->database;
+				}
+
+				return;
+			}
+
+			if (isset($this->host))
+			{
+				$this->dsn .= 'host='.$this->host;
+			}
+			else
+			{
+				$this->dsn .= 'host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+			}
+
+			if (isset($this->service))
+			{
+				$this->dsn .= '; service='.$this->service;
+			}
+			elseif ( ! empty($this->port))
+			{
+				$this->dsn .= '; service='.$this->port;
+			}
+
+			empty($this->database) OR $this->dsn .= '; database='.$this->database;
+			empty($this->server) OR $this->dsn .= '; server='.$this->server;
+
+			$this->dsn .= '; protocol='.(isset($this->protocol) ? $this->protocol : 'onsoctcp')
+				.'; EnableScrollableCursors=1';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "tabname" FROM "systables"
+			WHERE "tabid" > 99 AND "tabtype" = \'T\' AND LOWER("owner") = '.$this->escape(strtolower($this->username));
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			$sql .= ' AND "tabname" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		if (strpos($table, '.') !== FALSE)
+		{
+			sscanf($table, '%[^.].%s', $owner, $table);
+		}
+		else
+		{
+			$owner = $this->username;
+		}
+
+		return 'SELECT "colname" FROM "systables", "syscolumns"
+			WHERE "systables"."tabid" = "syscolumns"."tabid"
+				AND "systables"."tabtype" = \'T\'
+				AND LOWER("systables"."owner") = '.$this->escape(strtolower($owner)).'
+				AND LOWER("systables"."tabname") = '.$this->escape(strtolower($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		$sql = 'SELECT "syscolumns"."colname" AS "name",
+				CASE "syscolumns"."coltype"
+					WHEN 0 THEN \'CHAR\'
+					WHEN 1 THEN \'SMALLINT\'
+					WHEN 2 THEN \'INTEGER\'
+					WHEN 3 THEN \'FLOAT\'
+					WHEN 4 THEN \'SMALLFLOAT\'
+					WHEN 5 THEN \'DECIMAL\'
+					WHEN 6 THEN \'SERIAL\'
+					WHEN 7 THEN \'DATE\'
+					WHEN 8 THEN \'MONEY\'
+					WHEN 9 THEN \'NULL\'
+					WHEN 10 THEN \'DATETIME\'
+					WHEN 11 THEN \'BYTE\'
+					WHEN 12 THEN \'TEXT\'
+					WHEN 13 THEN \'VARCHAR\'
+					WHEN 14 THEN \'INTERVAL\'
+					WHEN 15 THEN \'NCHAR\'
+					WHEN 16 THEN \'NVARCHAR\'
+					WHEN 17 THEN \'INT8\'
+					WHEN 18 THEN \'SERIAL8\'
+					WHEN 19 THEN \'SET\'
+					WHEN 20 THEN \'MULTISET\'
+					WHEN 21 THEN \'LIST\'
+					WHEN 22 THEN \'Unnamed ROW\'
+					WHEN 40 THEN \'LVARCHAR\'
+					WHEN 41 THEN \'BLOB/CLOB/BOOLEAN\'
+					WHEN 4118 THEN \'Named ROW\'
+					ELSE "syscolumns"."coltype"
+				END AS "type",
+				"syscolumns"."collength" as "max_length",
+				CASE "sysdefaults"."type"
+					WHEN \'L\' THEN "sysdefaults"."default"
+					ELSE NULL
+				END AS "default"
+			FROM "syscolumns", "systables", "sysdefaults"
+			WHERE "syscolumns"."tabid" = "systables"."tabid"
+				AND "systables"."tabid" = "sysdefaults"."tabid"
+				AND "syscolumns"."colno" = "sysdefaults"."colno"
+				AND "systables"."tabtype" = \'T\'
+				AND LOWER("systables"."owner") = '.$this->escape(strtolower($this->username)).'
+				AND LOWER("systables"."tabname") = '.$this->escape(strtolower($table)).'
+			ORDER BY "syscolumns"."colno"';
+
+		return (($query = $this->query($sql)) !== FALSE)
+			? $query->result_object()
+			: FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'TRUNCATE TABLE ONLY '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	$SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		$select = 'SELECT '.($this->qb_offset ? 'SKIP '.$this->qb_offset : '').'FIRST '.$this->qb_limit.' ';
+		return preg_replace('/^(SELECT\s)/i', $select, $sql, 1);
+	}
+
+}
+
+/* End of file pdo_informix_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_informix_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php b/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php
new file mode 100644
index 0000000..8722dd2
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Informix Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_informix_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * RENAME TABLE statement
+	 *
+	 * @var	string
+	 */
+	protected $_rename_table	= 'RENAME TABLE %s TO %s';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'REAL'		=> 'DOUBLE PRECISION',
+		'SMALLFLOAT'	=> 'DOUBLE PRECISION'
+	);
+
+	/**
+	 * DEFAULT value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_default		= ', ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'CHANGE')
+		{
+			$alter_type = 'MODIFY';
+		}
+
+		return parent::_alter_table($alter_type, $table, $field);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'BYTE':
+			case 'TEXT':
+			case 'BLOB':
+			case 'CLOB':
+				$attributes['UNIQUE'] = FALSE;
+				if (isset($attributes['DEFAULT']))
+				{
+					unset($attributes['DEFAULT']);
+				}
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute UNIQUE
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_unique(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
+		{
+			$field['unique'] = ' UNIQUE CONSTRAINT '.$this->db->escape_identifiers($field['name']);
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported
+	}
+
+}
+
+/* End of file pdo_informix_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_informix_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
new file mode 100644
index 0000000..a293972
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
@@ -0,0 +1,283 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO MySQL Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_mysql_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'mysql';
+
+	/**
+	 * Compression flag
+	 *
+	 * @var	bool
+	 */
+	public $compress = FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Identifier escape character
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '`';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'mysql:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			empty($this->port) OR $this->dsn .= ';port='.$this->port;
+			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+		}
+		elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 6) === FALSE && is_php('5.3.6'))
+		{
+			$this->dsn .= ';charset='.$this->char_set;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	object
+	 * @todo	SSL support
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		/* Prior to PHP 5.3.6, even if the charset was supplied in the DSN
+		 * on connect - it was ignored. This is a work-around for the issue.
+		 *
+		 * Reference: http://www.php.net/manual/en/ref.pdo-mysql.connection.php
+		 */
+		if ( ! is_php('5.3.6') && ! empty($this->char_set))
+		{
+			$this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$this->char_set
+				.(empty($this->dbcollat) ? '' : ' COLLATE '.$this->dbcollat);
+		}
+
+		if ($this->compress === TRUE)
+		{
+			$this->options[PDO::MYSQL_ATTR_COMPRESS] = TRUE;
+		}
+
+		return parent::db_connect($persistent);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SHOW TABLES';
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->Field;
+
+			sscanf($query[$i]->Type, '%[a-z](%d)',
+				$retval[$i]->type,
+				$retval[$i]->max_length
+			);
+
+			$retval[$i]->default		= $query[$i]->Default;
+			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update_Batch statement
+	 *
+	 * Generates a platform-specific batch update string from the supplied data
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	UPDATE key
+	 * @return	string
+	 */
+	protected function _update_batch($table, $values, $index)
+	{
+		$ids = array();
+		foreach ($values as $key => $val)
+		{
+			$ids[] = $val[$index];
+
+			foreach (array_keys($val) as $field)
+			{
+				if ($field !== $index)
+				{
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+				}
+			}
+		}
+
+		$cases = '';
+		foreach ($final as $k => $v)
+		{
+			$cases .= $k." = CASE \n"
+				.implode("\n", $v)."\n"
+				.'ELSE '.$k.' END), ';
+		}
+
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'TRUNCATE '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * FROM tables
+	 *
+	 * Groups tables in FROM clauses if needed, so there is no confusion
+	 * about operator precedence.
+	 *
+	 * @return	string
+	 */
+	protected function _from_tables()
+	{
+		if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
+		{
+			return '('.implode(', ', $this->qb_from).')';
+		}
+
+		return implode(', ', $this->qb_from);
+	}
+
+}
+
+/* End of file pdo_mysql_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
new file mode 100644
index 0000000..5a6de6b
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO MySQL Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_mysql_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s';
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * CREATE TABLE keys flag
+	 *
+	 * Whether table keys are created from within the
+	 * CREATE TABLE statement.
+	 *
+	 * @var	bool
+	 */
+	protected $_create_table_keys	= TRUE;
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT',
+		'SMALLINT',
+		'MEDIUMINT',
+		'INT',
+		'INTEGER',
+		'BIGINT',
+		'REAL',
+		'DOUBLE',
+		'DOUBLE PRECISION',
+		'FLOAT',
+		'DECIMAL',
+		'NUMERIC'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP')
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				$field[$i] = ($alter_type === 'ADD')
+						? "\n\tADD ".$field[$i]['_literal']
+						: "\n\tMODIFY ".$field[$i]['_literal'];
+			}
+			else
+			{
+				if ($alter_type === 'ADD')
+				{
+					$field[$i]['_literal'] = "\n\tADD ";
+				}
+				else
+				{
+					$field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
+				}
+
+				$field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]);
+			}
+		}
+
+		return array($sql.implode(',', $field));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		$extra_clause = isset($field['after'])
+			? ' AFTER '.$this->db->escape_identifiers($field['after']) : '';
+
+		if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE)
+		{
+			$extra_clause = ' FIRST';
+		}
+
+		return $this->db->escape_identifiers($field['name'])
+			.(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name']))
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['null']
+			.$field['default']
+			.$field['auto_increment']
+			.$field['unique']
+			.$extra_clause;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process indexes
+	 *
+	 * @param	string	$table	(ignored)
+	 * @return	string
+	 */
+	protected function _process_indexes($table)
+	{
+		$sql = '';
+
+		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+		{
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
+			{
+				unset($this->keys[$i]);
+				continue;
+			}
+
+			is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]);
+
+			$sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i]))
+				.' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')';
+		}
+
+		$this->keys = array();
+
+		return $sql;
+	}
+
+}
+
+/* End of file pdo_mysql_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php b/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php
new file mode 100644
index 0000000..57df558
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Oracle Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_oci_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'oci';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List of reserved identifiers
+	 *
+	 * Identifiers that must NOT be escaped.
+	 *
+	 * @var	string[]
+	 */
+	protected $_reserved_identifiers = array('*', 'rownum');
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('ASC', 'ASC'); // Currently not supported
+
+	/**
+	 * COUNT string
+	 *
+	 * @used-by	CI_DB_driver::count_all()
+	 * @used-by	CI_DB_query_builder::count_all_results()
+	 *
+	 * @var	string
+	 */
+	protected $_count_string = 'SELECT COUNT(1) AS ';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'oci:dbname=';
+
+			// Oracle has a slightly different PDO DSN format (Easy Connect),
+			// which also supports pre-defined DSNs.
+			if (empty($this->hostname) && empty($this->port))
+			{
+				$this->dsn .= $this->database;
+			}
+			else
+			{
+				$this->dsn .= '//'.(empty($this->hostname) ? '127.0.0.1' : $this->hostname)
+					.(empty($this->port) ? '' : ':'.$this->port).'/';
+
+				empty($this->database) OR $this->dsn .= $this->database;
+			}
+
+			empty($this->char_set) OR $this->dsn .= ';charset='.$this->char_set;
+		}
+		elseif ( ! empty($this->char_set) && strpos($this->dsn, 'charset=', 4) === FALSE)
+		{
+			$this->dsn .= ';charset='.$this->char_set;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "TABLE_NAME" FROM "ALL_TABLES"';
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql.' WHERE "TABLE_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		if (strpos($table, '.') !== FALSE)
+		{
+			sscanf($table, '%[^.].%s', $owner, $table);
+		}
+		else
+		{
+			$owner = $this->username;
+		}
+
+		return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
+			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
+				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+		elseif (strpos($table, '.') !== FALSE)
+		{
+			sscanf($table, '%[^.].%s', $owner, $table);
+		}
+		else
+		{
+			$owner = $this->username;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
+			FROM ALL_TAB_COLUMNS
+			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
+				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+
+			$length = ($query[$i]->CHAR_LENGTH > 0)
+				? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
+			if ($length === NULL)
+			{
+				$length = $query[$i]->DATA_LENGTH;
+			}
+			$retval[$i]->max_length		= $length;
+
+			$default = $query[$i]->DATA_DEFAULT;
+			if ($default === NULL && $query[$i]->NULLABLE === 'N')
+			{
+				$default = '';
+			}
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return 	string
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		$keys = implode(', ', $keys);
+		$sql = "INSERT ALL\n";
+
+		for ($i = 0, $c = count($values); $i < $c; $i++)
+		{
+			$sql .= '	INTO '.$table.' ('.$keys.') VALUES '.$values[$i]."\n";
+		}
+
+		return $sql.'SELECT * FROM dual';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		if ($this->qb_limit)
+		{
+			$this->where('rownum <= ',$this->qb_limit, FALSE);
+			$this->qb_limit = FALSE;
+		}
+
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
+			.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1): '');
+	}
+
+}
+
+/* End of file pdo_oci_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_oci_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php b/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php
new file mode 100644
index 0000000..6d51a14
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO Oracle Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_oci_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_database	= FALSE;
+
+	/**
+	 * DROP DATABASE statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_database	= FALSE;
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP')
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+		elseif ($alter_type === 'CHANGE')
+		{
+			$alter_type = 'MODIFY';
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				$field[$i] = "\n\t".$field[$i]['_literal'];
+			}
+			else
+			{
+				$field[$i]['_literal'] = "\n\t".$this->_process_column($field[$i]);
+				if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name']))
+				{
+					$sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+						.' '.$this->db->escape_identifiers($field[$i]['new_name']);
+				}
+			}
+		}
+
+		$sql .= ' '.$alter_type.' ';
+		$sql .= (count($field) === 1)
+				? $field[0]
+				: '('.implode(',', $field).')';
+
+		// RENAME COLUMN must be executed after MODIFY
+		array_unshift($sqls, $sql);
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported - sequences and triggers must be used instead
+	}
+
+}
+
+/* End of file pdo_oci_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_oci_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
new file mode 100644
index 0000000..c940a7b
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
@@ -0,0 +1,258 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO ODBC Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_odbc_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'odbc';
+
+	/**
+	 * Database schema
+	 *
+	 * @var	string
+	 */
+	public $schema = 'public';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Identifier escape character
+	 *
+	 * Must be empty for ODBC.
+	 *
+	 * @var	string
+	 */
+	protected $_escape_char = '';
+
+	/**
+	 * ESCAPE statement string
+	 *
+	 * @var	string
+	 */
+	protected $_like_escape_str = " {escape '%s'} ";
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RND()', 'RND(%d)');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'odbc:';
+
+			// Pre-defined DSN
+			if (empty($this->hostname) && empty($this->HOSTNAME) && empty($this->port) && empty($this->PORT))
+			{
+				if (isset($this->DSN))
+				{
+					$this->dsn .= 'DSN='.$this->DSN;
+				}
+				elseif ( ! empty($this->database))
+				{
+					$this->dsn .= 'DSN='.$this->database;
+				}
+
+				return;
+			}
+
+			// If the DSN is not pre-configured - try to build an IBM DB2 connection string
+			$this->dsn .= 'DRIVER='.(isset($this->DRIVER) ? '{'.$this->DRIVER.'}' : '{IBM DB2 ODBC DRIVER}').';';
+
+			if (isset($this->DATABASE))
+			{
+				$this->dsn .= 'DATABASE='.$this->DATABASE.';';
+			}
+			elseif ( ! empty($this->database))
+			{
+				$this->dsn .= 'DATABASE='.$this->database.';';
+			}
+
+			if (isset($this->HOSTNAME))
+			{
+				$this->dsn .= 'HOSTNAME='.$this->HOSTNAME.';';
+			}
+			else
+			{
+				$this->dsn .= 'HOSTNAME='.(empty($this->hostname) ? '127.0.0.1;' : $this->hostname.';');
+			}
+
+			if (isset($this->PORT))
+			{
+				$this->dsn .= 'PORT='.$this->port.';';
+			}
+			elseif ( ! empty($this->port))
+			{
+				$this->dsn .= ';PORT='.$this->port.';';
+			}
+
+			$this->dsn .= 'PROTOCOL='.(isset($this->PROTOCOL) ? $this->PROTOCOL.';' : 'TCPIP;');
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '".$this->schema."'";
+
+		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
+		{
+			return $sql." AND table_name LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT column_name FROM information_schema.columns WHERE table_name = '.$this->escape($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'DELETE FROM '.$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$this->qb_limit.' ', $sql);
+	}
+
+}
+
+/* End of file pdo_odbc_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php
new file mode 100644
index 0000000..ab4d2bf
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO ODBC Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/database/
+ */
+class CI_DB_pdo_odbc_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		// Not supported (in most databases at least)
+	}
+
+}
+
+/* End of file pdo_odbc_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
new file mode 100644
index 0000000..2eb0606
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
@@ -0,0 +1,357 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO PostgreSQL Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_pgsql_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'pgsql';
+
+	/**
+	 * Database schema
+	 *
+	 * @var	string
+	 */
+	public $schema = 'public';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM()');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'pgsql:host='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			empty($this->port) OR $this->dsn .= ';port='.$this->port;
+			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	object
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		$this->conn_id = parent::db_connect($persistent);
+
+		if (is_object($this->conn_id) && ! empty($this->schema))
+		{
+			$this->simple_query('SET search_path TO '.$this->schema.',public');
+		}
+
+		return $this->conn_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * @param	string	$name
+	 * @return	int
+	 */
+	public function insert_id($name = NULL)
+	{
+		if ($name === NULL && version_compare($this->version(), '8.1', '>='))
+		{
+			$query = $this->query('SELECT LASTVAL() AS ins_id');
+			$query = $query->row();
+			return $query->ins_id;
+		}
+
+		return $this->conn_id->lastInsertId($name);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Smart" Escape String
+	 *
+	 * Escapes data based on type
+	 *
+	 * @param	string	$str
+	 * @return	mixed
+	 */
+	public function escape($str)
+	{
+		if (is_bool($str))
+		{
+			return ($str) ? 'TRUE' : 'FALSE';
+		}
+
+		return parent::escape($str);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY
+	 *
+	 * @param	string	$orderby
+	 * @param	string	$direction	ASC or DESC
+	 * @param	bool	$escape
+	 * @return	object
+	 */
+	public function order_by($orderby, $direction = '', $escape = NULL)
+	{
+		$direction = strtoupper(trim($direction));
+		if ($direction === 'RANDOM')
+		{
+			if ( ! is_float($orderby) && ctype_digit((string) $orderby))
+			{
+				$orderby = ($orderby > 1)
+					? (float) '0.'.$orderby
+					: (float) $orderby;
+			}
+
+			if (is_float($orderby))
+			{
+				$this->simple_query('SET SEED '.$orderby);
+			}
+
+			$orderby = $this->_random_keyword[0];
+			$direction = '';
+			$escape = FALSE;
+		}
+
+		return parent::order_by($orderby, $direction, $escape);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \''.$this->schema."'";
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql.' AND "table_name" LIKE \''
+				.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT "column_name"
+			FROM "information_schema"."columns"
+			WHERE LOWER("table_name") = '.$this->escape(strtolower($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default"
+			FROM "information_schema"."columns"
+			WHERE LOWER("table_name") = '.$this->escape(strtolower($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->column_name;
+			$retval[$i]->type		= $query[$i]->data_type;
+			$retval[$i]->max_length		= ($query[$i]->character_maximum_length > 0) ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision;
+			$retval[$i]->default		= $query[$i]->column_default;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update_Batch statement
+	 *
+	 * Generates a platform-specific batch update string from the supplied data
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
+	 * @return	string
+	 */
+	protected function _update_batch($table, $values, $index)
+	{
+		$ids = array();
+		foreach ($values as $key => $val)
+		{
+			$ids[] = $val[$index];
+
+			foreach (array_keys($val) as $field)
+			{
+				if ($field !== $index)
+				{
+					$final[$field][] = 'WHEN '.$val[$index].' THEN '.$val[$field];
+				}
+			}
+		}
+
+		$cases = '';
+		foreach ($final as $k => $v)
+		{
+			$cases .= $k.' = (CASE '.$index."\n"
+				.implode("\n", $v)."\n"
+				.'ELSE '.$k.' END), ';
+		}
+
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
+	}
+
+}
+
+/* End of file pdo_pgsql_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php
new file mode 100644
index 0000000..a24c898
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO PostgreSQL Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_pgsql_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'INT2'		=> 'INTEGER',
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INT4'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'INT8'		=> 'NUMERIC',
+		'BIGINT'	=> 'NUMERIC',
+		'REAL'		=> 'DOUBLE PRECISION',
+		'FLOAT'		=> 'DOUBLE PRECISION'
+	);
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		if (version_compare($this->db->version(), '9.0', '>'))
+		{
+			$this->create_table_if = 'CREATE TABLE IF NOT EXISTS';
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+ 	{
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				return FALSE;
+			}
+
+			if (version_compare($this->db->version(), '8', '>=') && isset($field[$i]['type']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TYPE '.$field[$i]['type'].$field[$i]['length'];
+			}
+
+			if ( ! empty($field[$i]['default']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' SET DEFAULT '.$field[$i]['default'];
+			}
+
+			if (isset($field[$i]['null']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.($field[$i]['null'] === TRUE ? ' DROP NOT NULL' : ' SET NOT NULL');
+			}
+
+			if ( ! empty($field[$i]['new_name']))
+			{
+				$sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
+			}
+		}
+
+		return $sqls;
+ 	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		// Reset field lenghts for data types that don't support it
+		if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== FALSE)
+		{
+			$attributes['CONSTRAINT'] = NULL;
+		}
+
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE)
+		{
+			$field['type'] = ($field['type'] === 'NUMERIC')
+						? 'BIGSERIAL'
+						: 'SERIAL';
+		}
+	}
+
+}
+
+/* End of file pdo_pgsql_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php
new file mode 100644
index 0000000..568044e
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO SQLite Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_sqlite_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'sqlite';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = ' RANDOM()';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'sqlite:';
+
+			if (empty($this->database) && empty($this->hostname))
+			{
+				$this->database = ':memory:';
+			}
+
+			$this->database = empty($this->database) ? $this->hostname : $this->database;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\'';
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			return $sql.' AND "NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		// Not supported
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$query = $query->result_array();
+		if (empty($query))
+		{
+			return FALSE;
+		}
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]['name'];
+			$retval[$i]->type		= $query[$i]['type'];
+			$retval[$i]->max_length		= NULL;
+			$retval[$i]->default		= $query[$i]['dflt_value'];
+			$retval[$i]->primary_key	= isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Replace statement
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return 	string
+	 */
+	protected function _replace($table, $keys, $values)
+	{
+		return 'INSERT OR '.parent::_replace($table, $keys, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 *
+	 * If the database does not support the TRUNCATE statement,
+	 * then this method maps to 'DELETE FROM table'
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return 'DELETE FROM '.$table;
+	}
+
+}
+
+/* End of file pdo_sqlite_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
new file mode 100644
index 0000000..da098df
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO SQLite Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_sqlite_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= 'CREATE TABLE IF NOT EXISTS';
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= 'DROP TABLE IF EXISTS';
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		if (version_compare($this->db->version(), '3.3', '<'))
+		{
+			$this->_create_table_if = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create database
+	 *
+	 * @param	string	$db_name	(ignored)
+	 * @return	bool
+	 */
+	public function create_database($db_name = '')
+	{
+		// In SQLite, a database is created when you connect to the database.
+		// We'll return TRUE so that an error isn't generated
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @param	string	$db_name	(ignored)
+	 * @return	bool
+	 */
+	public function drop_database($db_name = '')
+	{
+		// In SQLite, a database is dropped when we delete a file
+		if (@file_exists($this->db->database))
+		{
+			// We need to close the pseudo-connection first
+			$this->db->close();
+			if ( ! @unlink($this->db->database))
+			{
+				return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE;
+			}
+			elseif ( ! empty($this->db->data_cache['db_names']))
+			{
+				$key = array_search(strtolower($this->db->database), array_map('strtolower', $this->db->data_cache['db_names']), TRUE);
+				if ($key !== FALSE)
+				{
+					unset($this->db->data_cache['db_names'][$key]);
+				}
+			}
+
+			return TRUE;
+		}
+
+		return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if ($alter_type === 'DROP' OR $alter_type === 'CHANGE')
+		{
+			// drop_column():
+			//	BEGIN TRANSACTION;
+			//	CREATE TEMPORARY TABLE t1_backup(a,b);
+			//	INSERT INTO t1_backup SELECT a,b FROM t1;
+			//	DROP TABLE t1;
+			//	CREATE TABLE t1(a,b);
+			//	INSERT INTO t1 SELECT a,b FROM t1_backup;
+			//	DROP TABLE t1_backup;
+			//	COMMIT;
+
+			return FALSE;
+		}
+
+		return parent::_alter_table($alter_type, $table, $field);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process column
+	 *
+	 * @param	array	$field
+	 * @return	string
+	 */
+	protected function _process_column($field)
+	{
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type']
+			.$field['auto_increment']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'ENUM':
+			case 'SET':
+				$attributes['TYPE'] = 'TEXT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['type'] = 'INTEGER PRIMARY KEY';
+			$field['default'] = '';
+			$field['null'] = '';
+			$field['unique'] = '';
+			$field['auto_increment'] = ' AUTOINCREMENT';
+
+			$this->primary_keys = array();
+		}
+	}
+
+}
+
+/* End of file pdo_sqlite_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php
new file mode 100644
index 0000000..ec02526
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php
@@ -0,0 +1,363 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO SQLSRV Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the query builder
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_sqlsrv_driver extends CI_DB_pdo_driver {
+
+	/**
+	 * Sub-driver
+	 *
+	 * @var	string
+	 */
+	public $subdriver = 'sqlsrv';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
+
+	/**
+	 * Quoted identifier flag
+	 *
+	 * Whether to use SQL-92 standard quoted identifier
+	 * (double quotes) or brackets for identifier escaping.
+	 *
+	 * @var	bool
+	 */
+	protected $_quoted_identifier;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * Builds the DSN if not already set.
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
+	public function __construct($params)
+	{
+		parent::__construct($params);
+
+		if (empty($this->dsn))
+		{
+			$this->dsn = 'sqlsrv:Server='.(empty($this->hostname) ? '127.0.0.1' : $this->hostname);
+
+			empty($this->port) OR $this->dsn .= ','.$this->port;
+			empty($this->database) OR $this->dsn .= ';Database='.$this->database;
+
+			// Some custom options
+
+			if (isset($this->QuotedId))
+			{
+				$this->dsn .= ';QuotedId='.$this->QuotedId;
+				$this->_quoted_identifier = (bool) $this->QuotedId;
+			}
+
+			if (isset($this->ConnectionPooling))
+			{
+				$this->dsn .= ';ConnectionPooling='.$this->ConnectionPooling;
+			}
+
+			if ($this->encrypt === TRUE)
+			{
+				$this->dsn .= ';Encrypt=1';
+			}
+
+			if (isset($this->TraceOn))
+			{
+				$this->dsn .= ';TraceOn='.$this->TraceOn;
+			}
+
+			if (isset($this->TrustServerCertificate))
+			{
+				$this->dsn .= ';TrustServerCertificate='.$this->TrustServerCertificate;
+			}
+
+			empty($this->APP) OR $this->dsn .= ';APP='.$this->APP;
+			empty($this->Failover_Partner) OR $this->dsn .= ';Failover_Partner='.$this->Failover_Partner;
+			empty($this->LoginTimeout) OR $this->dsn .= ';LoginTimeout='.$this->LoginTimeout;
+			empty($this->MultipleActiveResultSets) OR $this->dsn .= ';MultipleActiveResultSets='.$this->MultipleActiveResultSets;
+			empty($this->TraceFile) OR $this->dsn .= ';TraceFile='.$this->TraceFile;
+			empty($this->WSID) OR $this->dsn .= ';WSID='.$this->WSID;
+		}
+		elseif (preg_match('/QuotedId=(0|1)/', $this->dsn, $match))
+		{
+			$this->_quoted_identifier = (bool) $match[1];
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$persistent
+	 * @return	object
+	 */
+	public function db_connect($persistent = FALSE)
+	{
+		if ( ! empty($this->char_set) && preg_match('/utf[^8]*8/i', $this->char_set))
+		{
+			$this->options[PDO::SQLSRV_ENCODING_UTF8] = 1;
+		}
+
+		$this->conn_id = parent::db_connect($persistent);
+
+		if ( ! is_object($this->conn_id) OR is_bool($this->_quoted_identifier))
+		{
+			return $this->conn_id;
+		}
+
+		// Determine how identifiers are escaped
+		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+		$query = $query->row_array();
+		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
+		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+
+		return $this->conn_id;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool	$prefix_limit
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		return 'SELECT '.$this->escape_identifiers('name')
+			.' FROM '.$this->escape_identifiers('sysobjects')
+			.' WHERE '.$this->escape_identifiers('type')." = 'U'";
+
+		if ($prefix_limit === TRUE && $this->dbprefix !== '')
+		{
+			$sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
+				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+		}
+
+		return $sql.' ORDER BY '.$this->escape_identifiers('name');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		return 'SELECT COLUMN_NAME
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Returns an object with field data
+	 *
+	 * @param	string	$table
+	 * @return	array
+	 */
+	public function field_data($table = '')
+	{
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @param	array	$values
+	 * @return	string
+	 */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	$table
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
+
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * LIMIT
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	$sql	SQL Query
+	 * @return	string
+	 */
+	protected function _limit($sql)
+	{
+		// As of SQL Server 2012 (11.0.*) OFFSET is supported
+		if (version_compare($this->version(), '11', '>='))
+		{
+			return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
+		}
+
+		$limit = $this->qb_offset + $this->qb_limit;
+
+		// An ORDER BY clause is required for ROW_NUMBER() to work
+		if ($this->qb_offset && ! empty($this->qb_orderby))
+		{
+			$orderby = $this->_compile_order_by();
+
+			// We have to strip the ORDER BY clause
+			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
+
+			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
+			if (count($this->qb_select) === 0)
+			{
+				$select = '*'; // Inevitable
+			}
+			else
+			{
+				// Use only field names and their aliases, everything else is out of our scope.
+				$select = array();
+				$field_regexp = ($this->_quoted_identifier)
+					? '("[^\"]+")' : '(\[[^\]]+\])';
+				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
+				{
+					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
+						? $m[1] : $this->qb_select[$i];
+				}
+				$select = implode(', ', $select);
+			}
+
+			return 'SELECT '.$select." FROM (\n\n"
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				."\n\n) ".$this->escape_identifiers('CI_subquery')
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
+		}
+
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		// Multiple-value inserts are only supported as of SQL Server 2008
+		if (version_compare($this->version(), '10', '>='))
+		{
+			return parent::_insert_batch($table, $keys, $values);
+		}
+
+		return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+	}
+
+}
+
+/* End of file pdo_sqlsrv_driver.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php
new file mode 100644
index 0000000..e4b669f
--- /dev/null
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * PDO SQLSRV Forge Class
+ *
+ * @category	Database
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_pdo_sqlsrv_forge extends CI_DB_pdo_forge {
+
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
+
+	/**
+	 * DROP TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_drop_table_if	= "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT'	=> 'SMALLINT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'BIGINT',
+		'REAL'		=> 'FLOAT'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
+	{
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
+
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			$sqls[] = $sql.$this->_process_column($field[$i]);
+		}
+
+		return $sqls;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' IDENTITY(1,1)';
+		}
+	}
+
+}
+
+/* End of file pdo_sqlsrv_forge.php */
+/* Location: ./system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php
index 20b7867..44cd0ce 100644
--- a/system/database/drivers/postgre/postgre_driver.php
+++ b/system/database/drivers/postgre/postgre_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Postgre Database Adapter Class
@@ -40,21 +41,37 @@
  */
 class CI_DB_postgre_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'postgre';
 
-	protected $_escape_char = '"';
+	/**
+	 * Database schema
+	 *
+	 * @var	string
+	 */
+	public $schema = 'public';
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
-
-	protected $_random_keyword = ' RANDOM()'; // database specific random keyword
+	// --------------------------------------------------------------------
 
 	/**
-	 * Constructor
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM()');
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
 	 *
 	 * Creates a DSN string to be used for db_connect() and db_pconnect()
 	 *
+	 * @param	array	$params
 	 * @return	void
 	 */
 	public function __construct($params)
@@ -114,13 +131,32 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Non-persistent database connection
+	 * Database connection
 	 *
+	 * @param	bool	$persistent
 	 * @return	resource
 	 */
-	public function db_connect()
+	public function db_connect($persistent = FALSE)
 	{
-		return @pg_connect($this->dsn);
+		if ($persistent === TRUE
+			&& ($this->conn_id = @pg_pconnect($this->dsn))
+			&& pg_connection_status($this->conn_id) === PGSQL_CONNECTION_BAD
+			&& pg_ping($this->conn_id) === FALSE
+		)
+		{
+			return FALSE;
+		}
+		else
+		{
+			$this->conn_id = @pg_connect($this->dsn);
+		}
+
+		if ($this->conn_id && ! empty($this->schema))
+		{
+			$this->simple_query('SET search_path TO '.$this->schema.',public');
+		}
+
+		return $this->conn_id;
 	}
 
 	// --------------------------------------------------------------------
@@ -132,7 +168,7 @@
 	 */
 	public function db_pconnect()
 	{
-		return @pg_pconnect($this->dsn);
+		return $this->db_connect(TRUE);
 	}
 
 	// --------------------------------------------------------------------
@@ -158,7 +194,7 @@
 	/**
 	 * Set client character set
 	 *
-	 * @param	string
+	 * @param	string	$charset
 	 * @return	bool
 	 */
 	protected function _db_set_charset($charset)
@@ -179,8 +215,12 @@
 		{
 			return $this->data_cache['version'];
 		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
 
-		if (($pg_version = pg_version($this->conn_id)) === FALSE)
+		if ( ! $this->conn_id OR ($pg_version = pg_version($this->conn_id)) === FALSE)
 		{
 			return FALSE;
 		}
@@ -200,7 +240,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -213,7 +253,7 @@
 	/**
 	 * Begin Transaction
 	 *
-	 * @param	bool
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -273,8 +313,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -308,9 +348,8 @@
 	 * "Smart" Escape String
 	 *
 	 * Escapes data based on type
-	 * Sets boolean and null types
 	 *
-	 * @param	string
+	 * @param	string	$str
 	 * @return	mixed
 	 */
 	public function escape($str)
@@ -388,12 +427,12 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
 	{
-		$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \'public\'';
+		$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \''.$this->schema."'";
 
 		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
 		{
@@ -408,31 +447,56 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Show column query
+	 * List column query
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
 	{
-		return 'SELECT "column_name" FROM "information_schema"."columns" WHERE "table_name" = '.$this->escape($table);
+		return 'SELECT "column_name"
+			FROM "information_schema"."columns"
+			WHERE LOWER("table_name") = '.$this->escape(strtolower($table));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT * FROM '.$table.' LIMIT 1';
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default"
+			FROM "information_schema"."columns"
+			WHERE LOWER("table_name") = '.$this->escape(strtolower($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->column_name;
+			$retval[$i]->type		= $query[$i]->data_type;
+			$retval[$i]->max_length		= ($query[$i]->character_maximum_length > 0) ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision;
+			$retval[$i]->default		= $query[$i]->column_default;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -453,17 +517,36 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
+	 * ORDER BY
 	 *
-	 * This function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
-	 *
-	 * @param	array
-	 * @return	string
+	 * @param	string	$orderby
+	 * @param	string	$direction	ASC or DESC
+	 * @param	bool	$escape
+	 * @return	object
 	 */
-	protected function _from_tables($tables)
+	public function order_by($orderby, $direction = '', $escape = NULL)
 	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
+		$direction = strtoupper(trim($direction));
+		if ($direction === 'RANDOM')
+		{
+			if ( ! is_float($orderby) && ctype_digit((string) $orderby))
+			{
+				$orderby = ($orderby > 1)
+					? (float) '0.'.$orderby
+					: (float) $orderby;
+			}
+
+			if (is_float($orderby))
+			{
+				$this->simple_query('SET SEED '.$orderby);
+			}
+
+			$orderby = $this->_random_keyword[0];
+			$direction = '';
+			$escape = FALSE;
+		}
+
+		return parent::order_by($orderby, $direction, $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -473,29 +556,15 @@
 	 *
 	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
+	 * @param	string	$table
+	 * @param	array	$values
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -505,12 +574,12 @@
 	 *
 	 * Generates a platform-specific batch update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	$table	Table name
+	 * @param	array	$values	Update data
+	 * @param	string	$index	WHERE key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -521,7 +590,7 @@
 			{
 				if ($field !== $index)
 				{
-					$final[$field][] =  'WHEN '.$val[$index].' THEN '.$val[$field];
+					$final[$field][] = 'WHEN '.$val[$index].' THEN '.$val[$field];
 				}
 			}
 		}
@@ -529,14 +598,14 @@
 		$cases = '';
 		foreach ($final as $k => $v)
 		{
-			$cases .= $k.' = (CASE '.$k."\n"
+			$cases .= $k.' = (CASE '.$index."\n"
 				.implode("\n", $v)."\n"
 				.'ELSE '.$k.' END), ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
@@ -546,108 +615,28 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
+	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.$limit.($offset ? ' OFFSET '.$offset : '');
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Where
-	 *
-	 * Called by where() or or_where()
-	 *
-	 * @param	mixed
-	 * @param	mixed
-	 * @param	string
-	 * @param	mixed
-	 * @return	object
-	 */
-	protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
-	{
-		if ( ! is_array($key))
-		{
-			$key = array($key => $value);
-		}
-
-		// If the escape value was not set will will base it on the global setting
-		is_bool($escape) OR $escape = $this->_protect_identifiers;
-
-		foreach ($key as $k => $v)
-		{
-			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
-				? $this->_group_get_type('')
-				: $this->_group_get_type($type);
-
-			if ($escape === TRUE)
-			{
-				$k = (($op = $this->_get_operator($k)) !== FALSE)
-					? $this->escape_identifiers(trim(substr($k, 0, strpos($k, $op)))).' '.strstr($k, $op)
-					: $this->escape_identifiers(trim($k));
-			}
-
-			if (is_null($v) && ! $this->_has_operator($k))
-			{
-				// value appears not to have been set, assign the test to IS NULL
-				$k .= ' IS NULL';
-			}
-
-			if ( ! is_null($v))
-			{
-				if ($escape === TRUE)
-				{
-					$v = ' '.$this->escape($v);
-				}
-				elseif (is_bool($v))
-				{
-					$v = ($v ? ' TRUE' : ' FALSE');
-				}
-
-				if ( ! $this->_has_operator($k))
-				{
-					$k .= ' = ';
-				}
-			}
-
-			$this->qb_where[] = $prefix.$k.$v;
-			if ($this->qb_caching === TRUE)
-			{
-				$this->qb_cache_where[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'where';
-			}
-
-		}
-
-		return $this;
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/postgre/postgre_forge.php b/system/database/drivers/postgre/postgre_forge.php
index c434e95..5e37838 100644
--- a/system/database/drivers/postgre/postgre_forge.php
+++ b/system/database/drivers/postgre/postgre_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Postgre Forge Class
@@ -34,185 +35,153 @@
  */
 class CI_DB_postgre_forge extends CI_DB_forge {
 
-	protected $_drop_table	= 'DROP TABLE IF EXISTS %s CASCADE';
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'INT2'		=> 'INTEGER',
+		'SMALLINT'	=> 'INTEGER',
+		'INT'		=> 'BIGINT',
+		'INT4'		=> 'BIGINT',
+		'INTEGER'	=> 'BIGINT',
+		'INT8'		=> 'NUMERIC',
+		'BIGINT'	=> 'NUMERIC',
+		'REAL'		=> 'DOUBLE PRECISION',
+		'FLOAT'		=> 'DOUBLE PRECISION'
+	);
 
 	/**
-	 * Process Fields
+	 * NULL value representation in CREATE/ALTER TABLE statements
 	 *
-	 * @param	mixed	the fields
-	 * @return	string
+	 * @var	string
 	 */
-	protected function _process_fields($fields, $primary_keys = array())
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
 	{
-		$sql = '';
-		$current_field_count = 0;
+		parent::__construct($db);
 
-		foreach ($fields as $field => $attributes)
+		if (version_compare($this->db->version(), '9.0', '>'))
 		{
-			// 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
-			{
-				$sql .= "\n\t".$this->db->escape_identifiers($field);
-
-				$attributes = array_change_key_case($attributes, CASE_UPPER);
-				$is_unsigned = ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE);
-
-				// Convert datatypes to be PostgreSQL-compatible
-				switch (strtoupper($attributes['TYPE']))
-				{
-					case 'TINYINT':
-						$attributes['TYPE'] = 'SMALLINT';
-						break;
-					case 'SMALLINT':
-						$attributes['TYPE'] = ($is_unsigned) ? 'INTEGER' : 'SMALLINT';
-						break;
-					case 'MEDIUMINT':
-						$attributes['TYPE'] = 'INTEGER';
-						break;
-					case 'INT':
-						$attributes['TYPE'] = ($is_unsigned) ? 'BIGINT' : 'INTEGER';
-						break;
-					case 'BIGINT':
-						$attributes['TYPE'] = ($is_unsigned) ? 'NUMERIC' : 'BIGINT';
-						break;
-					case 'DOUBLE':
-						$attributes['TYPE'] = 'DOUBLE PRECISION';
-						break;
-					case 'DATETIME':
-						$attributes['TYPE'] = 'TIMESTAMP';
-						break;
-					case 'LONGTEXT':
-						$attributes['TYPE'] = 'TEXT';
-						break;
-					case 'BLOB':
-						$attributes['TYPE'] = 'BYTEA';
-						break;
-					default:
-						break;
-				}
-
-				// If this is an auto-incrementing primary key, use the serial data type instead
-				$sql .= (in_array($field, $primary_keys) && ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE)
-					? ' SERIAL' : ' '.$attributes['TYPE'];
-
-				// Modified to prevent constraints with integer data types
-				if ( ! empty($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') === FALSE)
-				{
-					$sql .= '('.$attributes['CONSTRAINT'].')';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				// Added new attribute to create unique fields. Also works with MySQL
-				if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === TRUE)
-				{
-					$sql .= ' UNIQUE';
-				}
-			}
-
-			// don't add a comma on the end of the last field
-			if (++$current_field_count < count($fields))
-			{
-				$sql .= ',';
-			}
+			$this->create_table_if = 'CREATE TABLE IF NOT EXISTS';
 		}
-
-		return $sql;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Create Table
+	 * ALTER TABLE
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
-	{
-		$sql = 'CREATE TABLE ';
-
-		// PostgreSQL doesn't support IF NOT EXISTS syntax so we check if table exists manually
-		if ($if_not_exists === TRUE && $this->db->table_exists($table))
-		{
-			return TRUE;
-		}
-
-		$sql .= $this->db->escape_identifiers($table).' ('.$this->_process_fields($fields, $primary_keys);
-
-		if (count($primary_keys) > 0)
-		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		$sql .= "\n);";
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				foreach ($key as $field)
-				{
-					$sql .= "\nCREATE INDEX ".$this->db->escape_identifiers($table.'_'.str_replace(array('"', "'"), '', $field).'_index')
-						.' ON '.$this->db->escape_identifiers($table).' ('.$this->db->escape_identifiers($field).');';
-				}
-			}
-		}
-
-		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(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
-	 */
-	protected function _alter_table($alter_type, $table, $fields, $after_field = '')
+	protected function _alter_table($alter_type, $table, $field)
  	{
- 		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' ';
+		if (in_array($alter_type, array('DROP', 'ADD'), TRUE))
+		{
+			return parent::_alter_table($alter_type, $table, $field);
+		}
 
- 		// DROP has everything it needs now.
- 		if ($alter_type === 'DROP')
- 		{
- 			return $sql.$this->db->escape_identifiers($fields);
- 		}
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table);
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
+		{
+			if ($field[$i]['_literal'] !== FALSE)
+			{
+				return FALSE;
+			}
 
- 		return $sql.$this->_process_fields($fields)
-			.($after_field !== '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+			if (version_compare($this->db->version(), '8', '>=') && isset($field[$i]['type']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TYPE '.$field[$i]['type'].$field[$i]['length'];
+			}
+
+			if ( ! empty($field[$i]['default']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' SET DEFAULT '.$field[$i]['default'];
+			}
+
+			if (isset($field[$i]['null']))
+			{
+				$sqls[] = $sql.' ALTER COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.($field[$i]['null'] === TRUE ? ' DROP NOT NULL' : ' SET NOT NULL');
+			}
+
+			if ( ! empty($field[$i]['new_name']))
+			{
+				$sqls[] = $sql.' RENAME COLUMN '.$this->db->escape_identifiers($field[$i]['name'])
+					.' TO '.$this->db->escape_identifiers($field[$i]['new_name']);
+			}
+		}
+
+		return $sqls;
  	}
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		// Reset field lenghts for data types that don't support it
+		if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== FALSE)
+		{
+			$attributes['CONSTRAINT'] = NULL;
+		}
+
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'TINYINT':
+				$attributes['TYPE'] = 'SMALLINT';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE)
+		{
+			$field['type'] = ($field['type'] === 'NUMERIC')
+						? 'BIGSERIAL'
+						: 'SERIAL';
+		}
+	}
+
 }
 
 /* End of file postgre_forge.php */
diff --git a/system/database/drivers/postgre/postgre_result.php b/system/database/drivers/postgre/postgre_result.php
index eb9d647..fdaeaef 100644
--- a/system/database/drivers/postgre/postgre_result.php
+++ b/system/database/drivers/postgre/postgre_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Postgres Result Class
@@ -99,8 +100,6 @@
 			$retval[$i]->name		= pg_field_name($this->result_id, $i);
 			$retval[$i]->type		= pg_field_type($this->result_id, $i);
 			$retval[$i]->max_length		= pg_field_size($this->result_id, $i);
-			$retval[$i]->primary_key	= 0;
-			$retval[$i]->default		= '';
 		}
 
 		return $retval;
@@ -129,11 +128,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return pg_result_seek($this->result_id, $n);
 	}
@@ -159,7 +159,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/postgre/postgre_utility.php b/system/database/drivers/postgre/postgre_utility.php
index c95e6df..d3b2b9c 100644
--- a/system/database/drivers/postgre/postgre_utility.php
+++ b/system/database/drivers/postgre/postgre_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Postgre Utility Class
@@ -34,19 +35,32 @@
  */
 class CI_DB_postgre_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'SELECT datname FROM pg_database';
-	protected $_optimize_table	= 'REINDEX TABLE %s';
 
 	/**
-	 * Postgre Export
+	 * OPTIMIZE TABLE statement
 	 *
-	 * @param	array	Preferences
+	 * @var	string
+	 */
+	protected $_optimize_table	= 'REINDEX TABLE %s';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Export
+	 *
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 }
 
diff --git a/system/database/drivers/sqlite/sqlite_driver.php b/system/database/drivers/sqlite/sqlite_driver.php
index 19824db..8c706c2 100644
--- a/system/database/drivers/sqlite/sqlite_driver.php
+++ b/system/database/drivers/sqlite/sqlite_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite Database Adapter Class
@@ -40,16 +41,23 @@
  */
 class CI_DB_sqlite_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'sqlite';
 
-	// The character used to escape with - not needed for SQLite
-	protected $_escape_char = '"';
+	// --------------------------------------------------------------------
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM()');
 
-	protected $_random_keyword = ' Random()'; // database specific random keyword
+	// --------------------------------------------------------------------
 
 	/**
 	 * Non-persistent database connection
@@ -116,7 +124,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -131,6 +139,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -193,8 +202,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -253,7 +262,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -275,7 +284,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	bool
 	 */
 	protected function _list_columns($table = '')
@@ -287,16 +296,41 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT * FROM '.$this->escape_identifiers($table).' LIMIT 1';
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$query = $query->result_array();
+		if (empty($query))
+		{
+			return FALSE;
+		}
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]['name'];
+			$retval[$i]->type		= $query[$i]['type'];
+			$retval[$i]->max_length		= NULL;
+			$retval[$i]->default		= $query[$i]['dflt_value'];
+			$retval[$i]->primary_key	= isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -323,9 +357,9 @@
 	 *
 	 * Generates a platform-specific replace string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the insert keys
-	 * @param	array	the insert values
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
 	 * @return	string
 	 */
 	protected function _replace($table, $keys, $values)
@@ -340,10 +374,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this function maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
diff --git a/system/database/drivers/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php
index e02e327..6ad5835 100644
--- a/system/database/drivers/sqlite/sqlite_forge.php
+++ b/system/database/drivers/sqlite/sqlite_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite Forge Class
@@ -35,9 +36,32 @@
 class CI_DB_sqlite_forge extends CI_DB_forge {
 
 	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= FALSE;
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Create database
 	 *
-	 * @param	string	the database name
+	 * @param	string	$db_name	(ignored)
 	 * @return	bool
 	 */
 	public function create_database($db_name = '')
@@ -52,7 +76,7 @@
 	/**
 	 * Drop database
 	 *
-	 * @param	string	the database name (ignored)
+	 * @param	string	$db_name	(ignored)
 	 * @return	bool
 	 */
 	public function drop_database($db_name = '')
@@ -76,126 +100,95 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Create Table
+	 * ALTER TABLE
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @todo	implement drop_column(), modify_column()
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	protected function _alter_table($alter_type, $table, $field)
 	{
-		$sql = 'CREATE TABLE ';
-
-		// IF NOT EXISTS added to SQLite in 3.3.0
-		if ($if_not_exists === TRUE && version_compare($this->db->version(), '3.3.0', '>=') === TRUE)
+		if ($alter_type === 'DROP' OR $alter_type === 'CHANGE')
 		{
-			$sql .= 'IF NOT EXISTS ';
+			// drop_column():
+			//	BEGIN TRANSACTION;
+			//	CREATE TEMPORARY TABLE t1_backup(a,b);
+			//	INSERT INTO t1_backup SELECT a,b FROM t1;
+			//	DROP TABLE t1;
+			//	CREATE TABLE t1(a,b);
+			//	INSERT INTO t1 SELECT a,b FROM t1_backup;
+			//	DROP TABLE t1_backup;
+			//	COMMIT;
+
+			return FALSE;
 		}
 
-		$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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				empty($attributes['CONSTRAINT']) OR $sql .= '('.$attributes['CONSTRAINT'].')';
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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)
-		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		return parent::_alter_table($alter_type, $table, $field);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Process column
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _process_column($field)
 	{
-		/* SQLite only supports adding new columns and it does
-		 * NOT support the AFTER statement. Each new column will
-		 * be added as the last one in the table.
-		 */
-		if ($alter_type !== 'ADD COLUMN')
-		{
-			// Not supported
-			return FALSE;
-		}
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type']
+			.$field['auto_increment']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
+	}
 
-		return 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name)
-			.' '.$column_definition
-			.($default_value != '' ? " DEFAULT '".$default_value."'" : '')
-			// If NOT NULL is specified, the field must have a DEFAULT value other than NULL
-			.(($null !== NULL && $default_value !== 'NULL') ? ' NOT NULL' : ' NULL');
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'ENUM':
+			case 'SET':
+				$attributes['TYPE'] = 'TEXT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['type'] = 'INTEGER PRIMARY KEY';
+			$field['default'] = '';
+			$field['null'] = '';
+			$field['unique'] = '';
+			$field['auto_increment'] = ' AUTOINCREMENT';
+
+			$this->primary_keys = array();
+		}
 	}
 
 }
diff --git a/system/database/drivers/sqlite/sqlite_result.php b/system/database/drivers/sqlite/sqlite_result.php
index eef9787..889757d 100644
--- a/system/database/drivers/sqlite/sqlite_result.php
+++ b/system/database/drivers/sqlite/sqlite_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite Result Class
@@ -97,10 +98,8 @@
 		{
 			$retval[$i]			= new stdClass();
 			$retval[$i]->name		= sqlite_field_name($this->result_id, $i);
-			$retval[$i]->type		= 'varchar';
-			$retval[$i]->max_length		= 0;
-			$retval[$i]->primary_key	= 0;
-			$retval[$i]->default		= '';
+			$retval[$i]->type		= NULL;
+			$retval[$i]->max_length		= NULL;
 		}
 
 		return $retval;
@@ -113,11 +112,12 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return sqlite_seek($this->result_id, $n);
 	}
@@ -143,7 +143,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/sqlite/sqlite_utility.php b/system/database/drivers/sqlite/sqlite_utility.php
index 1bcb42d..222d027 100644
--- a/system/database/drivers/sqlite/sqlite_utility.php
+++ b/system/database/drivers/sqlite/sqlite_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite Utility Class
@@ -34,18 +35,16 @@
  */
 class CI_DB_sqlite_utility extends CI_DB_utility {
 
-	protected $_list_databases	= FALSE;
-
 	/**
-	 * SQLite Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/sqlite3/sqlite3_driver.php b/system/database/drivers/sqlite3/sqlite3_driver.php
index cc35d31..e37268a 100644
--- a/system/database/drivers/sqlite3/sqlite3_driver.php
+++ b/system/database/drivers/sqlite3/sqlite3_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite3 Database Adapter Class
@@ -41,16 +42,23 @@
  */
 class CI_DB_sqlite3_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'sqlite3';
 
-	// The character used for escaping
-	protected $_escape_char = '"';
+	// --------------------------------------------------------------------
 
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = ' ESCAPE \'%s\' ';
-	protected $_like_escape_chr = '!';
+	/**
+	 * ORDER BY random keyword
+	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('RANDOM()', 'RANDOM()');
 
-	protected $_random_keyword = ' RANDOM()';
+	// --------------------------------------------------------------------
 
 	/**
 	 * Non-persistent database connection
@@ -107,13 +115,12 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @todo	Implement use of SQLite3::querySingle(), if needed
+	 * @param	string	$sql
 	 * @return	mixed	SQLite3Result object or bool
 	 */
 	protected function _execute($sql)
 	{
-		// TODO: Implement use of SQLite3::querySingle(), if needed
-
 		return $this->is_write_type($sql)
 			? $this->conn_id->exec($sql)
 			: $this->conn_id->query($sql);
@@ -124,6 +131,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -183,8 +191,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -243,7 +251,7 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @param	bool
+	 * @param	bool	$prefix_limit
 	 * @return	string
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
@@ -261,7 +269,7 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
@@ -273,40 +281,56 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT * FROM '.$table.' LIMIT 0,1';
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protect_identifiers($table, TRUE, NULL, FALSE).')')) === FALSE)
+		{
+			return FALSE;
+		}
+
+		$query = $query->result_array();
+		if (empty($query))
+		{
+			return FALSE;
+		}
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]['name'];
+			$retval[$i]->type		= $query[$i]['type'];
+			$retval[$i]->max_length		= NULL;
+			$retval[$i]->default		= $query[$i]['dflt_value'];
+			$retval[$i]->primary_key	= isset($query[$i]['pk']) ? (int) $query[$i]['pk'] : 0;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * The error message string
+	 * Error
 	 *
-	 * @return	string
-	 */
-	protected function _error_message()
-	{
-		return $this->conn_id->lastErrorMsg();
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * The error message number
+	 * Returns an array containing code and message of the last
+	 * database error that has occured.
 	 *
-	 * @return	int
+	 * @return	array
 	 */
-	protected function _error_number()
+	public function error()
 	{
-		return $this->conn_id->lastErrorCode();
+		return array('code' => $this->conn_id->lastErrorCode(), 'message' => $this->conn_id->lastErrorMsg());
 	}
 
 	// --------------------------------------------------------------------
@@ -316,9 +340,9 @@
 	 *
 	 * Generates a platform-specific replace string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the insert keys
-	 * @param	array	the insert values
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
 	 * @return	string
 	 */
 	protected function _replace($table, $keys, $values)
@@ -333,10 +357,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command, then,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
diff --git a/system/database/drivers/sqlite3/sqlite3_forge.php b/system/database/drivers/sqlite3/sqlite3_forge.php
index 6a76ba9..e9b91e9 100644
--- a/system/database/drivers/sqlite3/sqlite3_forge.php
+++ b/system/database/drivers/sqlite3/sqlite3_forge.php
@@ -1,8 +1,8 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
- * An open source application development framework for PHP 5.1.6 or newer
+ * An open source application development framework for PHP 5.2.4 or newer
  *
  * NOTICE OF LICENSE
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite3 Forge Class
@@ -35,9 +36,43 @@
 class CI_DB_sqlite3_forge extends CI_DB_forge {
 
 	/**
+	 * UNSIGNED support
+	 *
+	 * @var	bool|array
+	 */
+	protected $_unsigned		= FALSE;
+
+	/**
+	 * NULL value representation in CREATE/ALTER TABLE statements
+	 *
+	 * @var	string
+	 */
+	protected $_null		= 'NULL';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @param	object	&$db	Database object
+	 * @return	void
+	 */
+	public function __construct(&$db)
+	{
+		parent::__construct($db);
+
+		if (version_compare($this->db->version(), '3.3', '<'))
+		{
+			$this->create_table_if = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Create database
 	 *
-	 * @param	string	the database name
+	 * @param	string	$db_name
 	 * @return	bool
 	 */
 	public function create_database($db_name = '')
@@ -52,7 +87,7 @@
 	/**
 	 * Drop database
 	 *
-	 * @param	string	the database name (ignored)
+	 * @param	string	$db_name	(ignored)
 	 * @return	bool
 	 */
 	public function drop_database($db_name = '')
@@ -84,125 +119,95 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Create Table
+	 * ALTER TABLE
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @todo	implement drop_column(), modify_column()
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	protected function _alter_table($alter_type, $table, $field)
 	{
-		$sql = 'CREATE TABLE ';
-
-		// IF NOT EXISTS added to SQLite in 3.3.0
-		if ($if_not_exists === TRUE && version_compare($this->db->version(), '3.3.0', '>=') === TRUE)
+		if ($alter_type === 'DROP' OR $alter_type === 'CHANGE')
 		{
-			$sql .= 'IF NOT EXISTS ';
+			// drop_column():
+			//	BEGIN TRANSACTION;
+			//	CREATE TEMPORARY TABLE t1_backup(a,b);
+			//	INSERT INTO t1_backup SELECT a,b FROM t1;
+			//	DROP TABLE t1;
+			//	CREATE TABLE t1(a,b);
+			//	INSERT INTO t1 SELECT a,b FROM t1_backup;
+			//	DROP TABLE t1_backup;
+			//	COMMIT;
+
+			return FALSE;
 		}
 
-		$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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				empty($attributes['CONSTRAINT']) OR $sql .= '('.$attributes['CONSTRAINT'].')';
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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)
-		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
-		}
-
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->db->escape_identifiers($key));
-
-				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		return parent::_alter_table($alter_type, $table, $field);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Process column
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
-	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
+	 * @param	array	$field
 	 * @return	string
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _process_column($field)
 	{
-		/* SQLite only supports adding new columns and it does
-		 * NOT support the AFTER statement. Each new column will
-		 * be added as the last one in the table.
-		 */
-		if ($alter_type !== 'ADD COLUMN')
-		{
-			// Not supported
-			return FALSE;
-		}
+		return $this->db->escape_identifiers($field['name'])
+			.' '.$field['type']
+			.$field['auto_increment']
+			.$field['null']
+			.$field['unique']
+			.$field['default'];
+	}
 
-		return 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name)
-			.' '.$column_definition
-			.($default_value !== '' ? ' DEFAULT '.$default_value : '')
-			// If NOT NULL is specified, the field must have a DEFAULT value other than NULL
-			.(($null !== NULL && $default_value !== 'NULL') ? ' NOT NULL' : ' NULL');
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute TYPE
+	 *
+	 * Performs a data type mapping between different databases.
+	 *
+	 * @param	array	&$attributes
+	 * @return	void
+	 */
+	protected function _attr_type(&$attributes)
+	{
+		switch (strtoupper($attributes['TYPE']))
+		{
+			case 'ENUM':
+			case 'SET':
+				$attributes['TYPE'] = 'TEXT';
+				return;
+			default: return;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['type'] = 'INTEGER PRIMARY KEY';
+			$field['default'] = '';
+			$field['null'] = '';
+			$field['unique'] = '';
+			$field['auto_increment'] = ' AUTOINCREMENT';
+
+			$this->primary_keys = array();
+		}
 	}
 
 }
diff --git a/system/database/drivers/sqlite3/sqlite3_result.php b/system/database/drivers/sqlite3/sqlite3_result.php
index 8e9b9c1..69c4200 100644
--- a/system/database/drivers/sqlite3/sqlite3_result.php
+++ b/system/database/drivers/sqlite3/sqlite3_result.php
@@ -1,8 +1,8 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
- * An open source application development framework for PHP 5.1.6 or newer
+ * An open source application development framework for PHP 5.2.4 or newer
  *
  * NOTICE OF LICENSE
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite3 Result Class
@@ -37,9 +38,6 @@
  */
 class CI_DB_sqlite3_result extends CI_DB_result {
 
-	// num_fields() might be called multiple times, so we'll use this one to cache it's result
-	protected $_num_fields;
-
 	/**
 	 * Number of fields in the result set
 	 *
@@ -47,9 +45,7 @@
 	 */
 	public function num_fields()
 	{
-		return ( ! is_int($this->_num_fields))
-			? $this->_num_fields = $this->result_id->numColumns()
-			: $this->_num_fields;
+		return $this->result_id->numColumns();
 	}
 
 	// --------------------------------------------------------------------
@@ -83,15 +79,24 @@
 	 */
 	public function field_data()
 	{
+		static $data_types = array(
+			SQLITE3_INTEGER	=> 'integer',
+			SQLITE3_FLOAT	=> 'float',
+			SQLITE3_TEXT	=> 'text',
+			SQLITE3_BLOB	=> 'blob',
+			SQLITE3_NULL	=> 'null'
+		);
+
 		$retval = array();
 		for ($i = 0, $c = $this->num_fields(); $i < $this->num_fields(); $i++)
 		{
 			$retval[$i]			= new stdClass();
 			$retval[$i]->name		= $this->result_id->columnName($i);
-			$retval[$i]->type		= 'varchar';
-			$retval[$i]->max_length		= 0;
-			$retval[$i]->primary_key	= 0;
-			$retval[$i]->default		= '';
+
+			$type = $this->result_id->columnType($i);
+			$retval[$i]->type		= isset($data_types[$type]) ? $data_types[$type] : $type;
+
+			$retval[$i]->max_length		= NULL;
 		}
 
 		return $retval;
@@ -134,7 +139,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
@@ -165,14 +170,15 @@
 	 *
 	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
+	 * @param	int	$n	(ignored)
 	 * @return	array
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		// Only resetting to the start of the result set is supported
-		return $this->result_id->reset();
+		return ($n > 0) ? FALSE : $this->result_id->reset();
 	}
 
 }
diff --git a/system/database/drivers/sqlite3/sqlite3_utility.php b/system/database/drivers/sqlite3/sqlite3_utility.php
index 965c838..c7736cc 100644
--- a/system/database/drivers/sqlite3/sqlite3_utility.php
+++ b/system/database/drivers/sqlite3/sqlite3_utility.php
@@ -1,8 +1,8 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
- * An open source application development framework for PHP 5.1.6 or newer
+ * An open source application development framework for PHP 5.2.4 or newer
  *
  * NOTICE OF LICENSE
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLite3 Utility Class
@@ -34,18 +35,16 @@
  */
 class CI_DB_sqlite3_utility extends CI_DB_utility {
 
-	protected $_list_databases	= FALSE;
-
 	/**
-	 * SQLite Export
+	 * Export
 	 *
-	 * @param	array	Preferences
+	 * @param	array	$params	Preferences
 	 * @return	mixed
 	 */
 	protected function _backup($params = array())
 	{
 		// Not supported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php
index 8bd18bd..ee9823e 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_driver.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.0.3
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLSRV Database Adapter Class
@@ -40,36 +41,52 @@
  */
 class CI_DB_sqlsrv_driver extends CI_DB {
 
+	/**
+	 * Database driver
+	 *
+	 * @var	string
+	 */
 	public $dbdriver = 'sqlsrv';
 
-	// The character used for escaping
-	protected $_escape_char = '"';
-
-	// clause and character used for LIKE escape sequences
-	protected $_like_escape_str = " ESCAPE '%s' ";
-	protected $_like_escape_chr = '!';
-
-	protected $_random_keyword = ' NEWID()';
-
-	// SQLSRV-specific properties
-	protected $_quoted_identifier = TRUE;
+	// --------------------------------------------------------------------
 
 	/**
-	 * Non-persistent database connection
+	 * ORDER BY random keyword
 	 *
+	 * @var	array
+	 */
+	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
+
+	/**
+	 * Quoted identifier flag
+	 *
+	 * Whether to use SQL-92 standard quoted identifier
+	 * (double quotes) or brackets for identifier escaping.
+	 *
+	 * @var	bool
+	 */
+	protected $_quoted_identifier = TRUE;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database connection
+	 *
+	 * @param	bool	$pooling
 	 * @return	resource
 	 */
 	public 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;
+		$charset = in_array(strtolower($this->char_set), array('utf-8', 'utf8'), TRUE)
+			? 'UTF-8' : SQLSRV_ENC_CHAR;
 
 		$connection = array(
 			'UID'			=> empty($this->username) ? '' : $this->username,
 			'PWD'			=> empty($this->password) ? '' : $this->password,
 			'Database'		=> $this->database,
-			'ConnectionPooling'	=> $pooling ? 1 : 0,
-			'CharacterSet'		=> $character_set,
+			'ConnectionPooling'	=> ($pooling === TRUE) ? 1 : 0,
+			'CharacterSet'		=> $charset,
+			'Encrypt'		=> ($this->encrypt === TRUE) ? 1 : 0,
 			'ReturnDatesAsStrings'	=> 1
 		);
 
@@ -108,7 +125,7 @@
 	/**
 	 * Select the database
 	 *
-	 * @param	string	database name
+	 * @param	string	$database
 	 * @return	bool
 	 */
 	public function db_select($database = '')
@@ -118,7 +135,7 @@
 			$database = $this->database;
 		}
 
-		if ($this->_execute('USE '.$database))
+		if ($this->_execute('USE '.$this->escape_identifiers($database)))
 		{
 			$this->database = $database;
 			return TRUE;
@@ -132,7 +149,7 @@
 	/**
 	 * Execute the query
 	 *
-	 * @param	string	an SQL query
+	 * @param	string	$sql	an SQL query
 	 * @return	resource
 	 */
 	protected function _execute($sql)
@@ -147,6 +164,7 @@
 	/**
 	 * Begin Transaction
 	 *
+	 * @param	bool	$test_mode
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -206,8 +224,8 @@
 	/**
 	 * Escape String
 	 *
-	 * @param	string
-	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @param	string	$str
+	 * @param	bool	$like	Whether or not the string will be used in a LIKE condition
 	 * @return	string
 	 */
 	public function escape_str($str, $like = FALSE)
@@ -225,7 +243,7 @@
 	 */
 	public function affected_rows()
 	{
-		return sqlrv_rows_affected($this->result_id);
+		return sqlsrv_rows_affected($this->result_id);
 	}
 
 	// --------------------------------------------------------------------
@@ -257,8 +275,12 @@
 		{
 			return $this->data_cache['version'];
 		}
+		elseif ( ! $this->conn_id)
+		{
+			$this->initialize();
+		}
 
-		if (($info = sqlsrv_server_info($this->conn_id)) === FALSE)
+		if ( ! $this->conn_id OR ($info = sqlsrv_server_info($this->conn_id)) === FALSE)
 		{
 			return FALSE;
 		}
@@ -274,7 +296,7 @@
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
 	 * @param	bool
-	 * @return	string
+	 * @return	string	$prefix_limit
 	 */
 	protected function _list_tables($prefix_limit = FALSE)
 	{
@@ -298,27 +320,52 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _list_columns($table = '')
 	{
-		return "SELECT * FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$table."'";
+		return 'SELECT COLUMN_NAME
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Field data query
+	 * Returns an object with field data
 	 *
-	 * Generates a platform-specific query so that the column data can be retrieved
-	 *
-	 * @param	string	the table name
-	 * @return	string
+	 * @param	string	$table
+	 * @return	array
 	 */
-	protected function _field_data($table)
+	public function field_data($table = '')
 	{
-		return 'SELECT TOP 1 * FROM '.$this->protect_identifiers($table);
+		if ($table === '')
+		{
+			return ($this->db_debug) ? $this->display_error('db_field_param_missing') : FALSE;
+		}
+
+		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
+			FROM INFORMATION_SCHEMA.Columns
+			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
+
+		if (($query = $this->query($sql)) === FALSE)
+		{
+			return FALSE;
+		}
+		$query = $query->result_object();
+
+		$retval = array();
+		for ($i = 0, $c = count($query); $i < $c; $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
+			$retval[$i]->type		= $query[$i]->DATA_TYPE;
+			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
+			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
+		}
+
+		return $retval;
 	}
 
 	// --------------------------------------------------------------------
@@ -362,49 +409,19 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * From Tables
-	 *
-	 * This function implicitly groups FROM tables so there is no confusion
-	 * about operator precedence in harmony with SQL standards
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _from_tables($tables)
-	{
-		return is_array($tables) ? implode(', ', $tables) : $tables;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Update statement
 	 *
 	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
+	 * @param	string	$table
+	 * @param	array	$values
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -414,10 +431,10 @@
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
 	 *
-	 * If the database does not support the truncate() command,
+	 * If the database does not support the TRUNCATE statement,
 	 * then this method maps to 'DELETE FROM table'
 	 *
-	 * @param	string	the table name
+	 * @param	string	$table
 	 * @return	string
 	 */
 	protected function _truncate($table)
@@ -432,60 +449,70 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
+	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Limit string
+	 * LIMIT
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @param	string	the sql query string
-	 * @param	int	the number of rows to limit the query to
-	 * @param	int	the offset value
+	 * @param	string	$sql	SQL Query
 	 * @return	string
 	 */
-	protected function _limit($sql, $limit, $offset)
+	protected function _limit($sql)
 	{
 		// As of SQL Server 2012 (11.0.*) OFFSET is supported
 		if (version_compare($this->version(), '11', '>='))
 		{
-			return $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY';
+			return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
 		}
 
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// An ORDER BY clause is required for ROW_NUMBER() to work
-		if ($offset && ! empty($this->qb_orderby))
+		if ($this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
-			$sql = trim(substr($sql, 0, strrpos($sql, 'ORDER BY '.$orderby)));
+			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
-			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
-				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
+			if (count($this->qb_select) === 0)
+			{
+				$select = '*'; // Inevitable
+			}
+			else
+			{
+				// Use only field names and their aliases, everything else is out of our scope.
+				$select = array();
+				$field_regexp = ($this->_quoted_identifier)
+					? '("[^\"]+")' : '(\[[^\]]+\])';
+				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
+				{
+					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
+						? $m[1] : $this->qb_select[$i];
+				}
+				$select = implode(', ', $select);
+			}
+
+			return 'SELECT '.$select." FROM (\n\n"
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				."\n\n) ".$this->escape_identifiers('CI_subquery')
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
@@ -494,6 +521,29 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Insert batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data.
+	 *
+	 * @param	string	$table	Table name
+	 * @param	array	$keys	INSERT keys
+	 * @param	array	$values	INSERT values
+	 * @return	string|bool
+	 */
+	protected function _insert_batch($table, $keys, $values)
+	{
+		// Multiple-value inserts are only supported as of SQL Server 2008
+		if (version_compare($this->version(), '10', '>='))
+		{
+			return parent::_insert_batch($table, $keys, $values);
+		}
+
+		return ($this->db->db_debug) ? $this->db->display_error('db_unsupported_feature') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Close DB Connection
 	 *
 	 * @return	void
diff --git a/system/database/drivers/sqlsrv/sqlsrv_forge.php b/system/database/drivers/sqlsrv/sqlsrv_forge.php
index ccdb369..da8f68c 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_forge.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_forge.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.0.3
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLSRV Forge Class
@@ -34,124 +35,99 @@
  */
 class CI_DB_sqlsrv_forge extends CI_DB_forge {
 
-	protected $_drop_table	= 'DROP TABLE %s';
+	/**
+	 * CREATE TABLE IF statement
+	 *
+	 * @var	string
+	 */
+	protected $_create_table_if	= "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
 
 	/**
-	 * Create Table
+	 * DROP TABLE IF statement
 	 *
-	 * @param	string	the table name
-	 * @param	array	the fields
-	 * @param	mixed	primary key(s)
-	 * @param	mixed	key(s)
-	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	string
+	 * @var	string
 	 */
-	protected function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	protected $_drop_table_if	= "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
+
+	/**
+	 * UNSIGNED support
+	 *
+	 * @var	array
+	 */
+	protected $_unsigned		= array(
+		'TINYINT'	=> 'SMALLINT',
+		'SMALLINT'	=> 'INT',
+		'INT'		=> 'BIGINT',
+		'REAL'		=> 'FLOAT'
+	);
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * ALTER TABLE
+	 *
+	 * @param	string	$alter_type	ALTER type
+	 * @param	string	$table		Table name
+	 * @param	mixed	$field		Column definition
+	 * @return	string|string[]
+	 */
+	protected function _alter_table($alter_type, $table, $field)
 	{
-		$sql = ($if_not_exists === TRUE)
-			? "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'".$table."') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"
-			: '';
-
-		$sql .= 'CREATE TABLE '.$this->db->escape_identifiers($table).' (';
-
-		$current_field_count = 0;
-		foreach ($fields as $field => $attributes)
+		if (in_array($alter_type, array('ADD', 'DROP'), TRUE))
 		{
-			// 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->escape_identifiers($field).' '.$attributes['TYPE'];
-
-				if (stripos($attributes['TYPE'], 'INT') === FALSE && ! empty($attributes['CONSTRAINT']))
-				{
-					$sql .= '('.$attributes['CONSTRAINT'].')';
-				}
-
-				if ( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE)
-				{
-					$sql .= ' UNSIGNED';
-				}
-
-				if (isset($attributes['DEFAULT']))
-				{
-					$sql .= " DEFAULT '".$attributes['DEFAULT']."'";
-				}
-
-				$sql .= ( ! empty($attributes['NULL']) && $attribues['NULL'] === TRUE)
-					? ' NULL' : ' NOT NULL';
-
-				if ( ! empty($attributes['AUTO_INCREMENT']) && $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 .= ',';
-			}
+			return parent::_alter_table($alter_type, $table, $field);
 		}
 
-		if (count($primary_keys) > 0)
+		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN ';
+		$sqls = array();
+		for ($i = 0, $c = count($field); $i < $c; $i++)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
+			$sqls[] = $sql.$this->_process_column($field[$i]);
 		}
 
-		if (is_array($keys) && count($keys) > 0)
-		{
-			foreach ($keys as $key)
-			{
-				$key = is_array($key)
-					? $this->db->escape_identifiers($key)
-					: array($this->escape_identifiers($key));
-
-				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
-			}
-		}
-
-		return $sql."\n)";
+		return $sqls;
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Alter table query
+	 * Field attribute TYPE
 	 *
-	 * Generates a platform-specific query so that a table can be altered
-	 * Called by add_column(), drop_column(), and column_alter(),
+	 * Performs a data type mapping between different databases.
 	 *
-	 * @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	bool	should 'NOT NULL' be added
-	 * @param	string	the field after which we should add the new field
-	 * @return	string
+	 * @param	array	&$attributes
+	 * @return	void
 	 */
-	protected function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	protected function _attr_type(&$attributes)
 	{
-		$sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' '.$alter_type.' '.$this->db->escape_identifiers($column_name);
-
-		// DROP has everything it needs now.
-		if ($alter_type === 'DROP')
+		switch (strtoupper($attributes['TYPE']))
 		{
-			return $sql;
+			case 'MEDIUMINT':
+				$attributes['TYPE'] = 'INTEGER';
+				$attributes['UNSIGNED'] = FALSE;
+				return;
+			case 'INTEGER':
+				$attributes['TYPE'] = 'INT';
+				return;
+			default: return;
 		}
+	}
 
-		return $sql.' '.$column_definition
-			.($default_value != '' ? ' DEFAULT "'.$default_value.'"' : '')
-			.($null === NULL ? ' NULL' : ' NOT NULL')
-			.($after_field != '' ? ' AFTER '.$this->db->escape_identifiers($after_field) : '');
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field attribute AUTO_INCREMENT
+	 *
+	 * @param	array	&$attributes
+	 * @param	array	&$field
+	 * @return	void
+	 */
+	protected function _attr_auto_increment(&$attributes, &$field)
+	{
+		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE)
+		{
+			$field['auto_increment'] = ' IDENTITY(1,1)';
+		}
 	}
 
 }
diff --git a/system/database/drivers/sqlsrv/sqlsrv_result.php b/system/database/drivers/sqlsrv/sqlsrv_result.php
index fb7a686..b084b84 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_result.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_result.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLSRV Result Class
@@ -93,16 +94,12 @@
 	public function field_data()
 	{
 		$retval = array();
-		foreach (sqlsrv_field_metadata($this->result_id) as $offset => $field)
+		foreach (sqlsrv_field_metadata($this->result_id) as $i => $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;
+			$retval[$i]		= new stdClass();
+			$retval[$i]->name	= $field['Name'];
+			$retval[$i]->type	= $field['Type'];
+			$retval[$i]->max_length	= $field['Size'];
 		}
 
 		return $retval;
@@ -145,7 +142,7 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @param	string
+	 * @param	string	$class_name
 	 * @return	object
 	 */
 	protected function _fetch_object($class_name = 'stdClass')
diff --git a/system/database/drivers/sqlsrv/sqlsrv_utility.php b/system/database/drivers/sqlsrv/sqlsrv_utility.php
index d518cc1..2bc7f9e 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_utility.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_utility.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 2.0.3
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * SQLSRV Utility Class
@@ -34,19 +35,32 @@
  */
 class CI_DB_sqlsrv_utility extends CI_DB_utility {
 
+	/**
+	 * List databases statement
+	 *
+	 * @var	string
+	 */
 	protected $_list_databases	= 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
-	protected $_optimize_table	= 'ALTER INDEX all ON %s REORGANIZE';
 
 	/**
-	 * SQLSRV Export
+	 * OPTIMIZE TABLE statement
 	 *
-	 * @param	array	Preferences
+	 * @var	string
+	 */
+	protected $_optimize_table	= 'ALTER INDEX all ON %s REORGANIZE';
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Export
+	 *
+	 * @param	array	$params	Preferences
 	 * @return	bool
 	 */
 	protected function _backup($params = array())
 	{
 		// Currently unsupported
-		return $this->db->display_error('db_unsuported_feature');
+		return $this->db->display_error('db_unsupported_feature');
 	}
 
 }
diff --git a/system/helpers/array_helper.php b/system/helpers/array_helper.php
index ed2fe3c..0e66e4b 100644
--- a/system/helpers/array_helper.php
+++ b/system/helpers/array_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Array Helpers
diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php
index 4676b2a..e9b167f 100644
--- a/system/helpers/captcha_helper.php
+++ b/system/helpers/captcha_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter CAPTCHA Helper
@@ -32,7 +33,7 @@
  * @subpackage	Helpers
  * @category	Helpers
  * @author		EllisLab Dev Team
- * @link		http://codeigniter.com/user_guide/helpers/xml_helper.html
+ * @link		http://codeigniter.com/user_guide/helpers/captcha_helper.html
  */
 
 // ------------------------------------------------------------------------
@@ -80,8 +81,7 @@
 		$current_dir = @opendir($img_path);
 		while ($filename = @readdir($current_dir))
 		{
-			if ($filename !== '.' && $filename !== '..' && $filename !== 'index.html'
-				&& (str_replace('.jpg', '', $filename) + $expiration) < $now)
+			if (substr($filename, -4) === '.jpg' && (str_replace('.jpg', '', $filename) + $expiration) < $now)
 			{
 				@unlink($img_path.$filename);
 			}
@@ -186,7 +186,6 @@
 			}
 		}
 
-
 		// Create the border
 		imagerectangle($im, 0, 0, $img_width - 1, $img_height - 1, $border_color);
 
diff --git a/system/helpers/cookie_helper.php b/system/helpers/cookie_helper.php
index f396c76..0284140 100644
--- a/system/helpers/cookie_helper.php
+++ b/system/helpers/cookie_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Cookie Helpers
diff --git a/system/helpers/date_helper.php b/system/helpers/date_helper.php
index a45b3d7..d54e019 100644
--- a/system/helpers/date_helper.php
+++ b/system/helpers/date_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Date Helpers
@@ -119,19 +120,16 @@
 	 *
 	 * As of PHP 5.2, the DateTime extension provides constants that
 	 * serve for the exact same purpose and are used with date().
-	 * Due to that, this function is DEPRECATED and should be removed
-	 * in CodeIgniter 3.1+.
 	 *
-	 * Here are two examples of how you should replace it:
+	 * @todo	Remove in version 3.1+.
+	 * @deprecated	3.0.0	Use PHP's native date() instead.
+	 * @link	http://www.php.net/manual/en/class.datetime.php#datetime.constants.types
 	 *
-	 *	date(DATE_RFC822, now()); // default
-	 *	date(DATE_W3C, $time); // a different format and time
+	 * @example	date(DATE_RFC822, now()); // default
+	 * @example	date(DATE_W3C, $time); // a different format and time
 	 *
-	 * Reference: http://www.php.net/manual/en/class.datetime.php#datetime.constants.types
-	 *
-	 * @deprecated
-	 * @param	string	the chosen format
-	 * @param	int	Unix timestamp
+	 * @param	string	$fmt = 'DATE_RFC822'	the chosen format
+	 * @param	int	$time = NULL		Unix timestamp
 	 * @return	string
 	 */
 	function standard_date($fmt = 'DATE_RFC822', $time = NULL)
@@ -362,8 +360,8 @@
 	/**
 	 * Converts a MySQL Timestamp to Unix
 	 *
-	 * @param	int	Unix timestamp
-	 * @return	int
+	 * @param	int	MySQL timestamp YYYY-MM-DD HH:MM:SS
+	 * @return	int	Unix timstamp
 	 */
 	function mysql_to_unix($time = '')
 	{
@@ -452,20 +450,13 @@
 			return FALSE;
 		}
 
-		$split = explode(' ', $datestr);
+		sscanf($datestr, '%d-%d-%d %s %s', $year, $month, $day, $time, $ampm);
+		sscanf($time, '%d:%d:%d', $hour, $min, $sec);
+		isset($sec) OR $sec = 0;
 
-		list($year, $month, $day) = explode('-', $split[0]);
-
-		$ex = explode(':', $split['1']);
-
-		$hour	= (int) $ex[0];
-		$min	= (int) $ex[1];
-		$sec	= ( ! empty($ex[2]) && preg_match('/[0-9]{1,2}/', $ex[2]))
-				? (int) $ex[2] : 0;
-
-		if (isset($split[2]))
+		if (isset($ampm))
 		{
-			$ampm = strtolower($split[2]);
+			$ampm = strtolower($ampm);
 
 			if ($ampm[0] === 'p' && $hour < 12)
 			{
@@ -575,22 +566,7 @@
 			$menu .= ' class="'.$class.'"';
 		}
 
-		// Generate a string from the attributes submitted, if any
-		if (is_array($attributes))
-		{
-			$atts = '';
-			foreach ($attributes as $key => $val)
-			{
-				$atts .= ' '.$key.'="'.$val.'"';
-			}
-			$attributes = $atts;
-		}
-		elseif (is_string($attributes) && strlen($attributes) > 0)
-		{
-			$attributes = ' '.$attributes;
-		}
-
-		$menu .= $attributes.">\n";
+		$menu .= _stringify_attributes($attributes).">\n";
 
 		foreach (timezones() as $key => $val)
 		{
@@ -672,5 +648,136 @@
 	}
 }
 
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('date_range'))
+{
+	/**
+	 * Date range
+	 *
+	 * Returns a list of dates within a specified period.
+	 *
+	 * @param	int	unix_start	UNIX timestamp of period start date
+	 * @param	int	unix_end|days	UNIX timestamp of period end date
+	 *					or interval in days.
+	 * @param	mixed	is_unix		Specifies whether the second parameter
+	 *					is a UNIX timestamp or a day interval
+	 *					 - TRUE or 'unix' for a timestamp
+	 *					 - FALSE or 'days' for an interval
+	 * @param	string  date_format	Output date format, same as in date()
+	 * @return	array
+	 */
+	function date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d')
+	{
+		if ($unix_start == '' OR $mixed == '' OR $format == '')
+		{
+			return FALSE;
+		}
+
+		$is_unix = ! ( ! $is_unix OR $is_unix === 'days');
+
+		// Validate input and try strtotime() on invalid timestamps/intervals, just in case
+		if ( ( ! ctype_digit((string) $unix_start) && ($unix_start = @strtotime($unix_time)) === FALSE)
+			OR ( ! ctype_digit((string) $mixed) && ($is_unix === FALSE OR ($mixed = @strtotime($mixed)) === FALSE))
+			OR ($is_unix === TRUE && $mixed < $unix_start))
+		{
+			return FALSE;
+		}
+
+		if ($is_unix && ($unix_start == $mixed OR date($format, $unix_start) === date($format, $mixed)))
+		{
+			return array($start_date);
+		}
+
+		$range = array();
+
+		/* NOTE: Even though the DateTime object has many useful features, it appears that
+		 *	 it doesn't always handle properly timezones, when timestamps are passed
+		 *	 directly to its constructor. Neither of the following gave proper results:
+		 *
+		 *		new DateTime('<timestamp>')
+		 *		new DateTime('<timestamp>', '<timezone>')
+		 *
+		 *	 --- available in PHP 5.3:
+		 *
+		 *		DateTime::createFromFormat('<format>', '<timestamp>')
+		 *		DateTime::createFromFormat('<format>', '<timestamp>', '<timezone')
+		 *
+		 *	 ... so we'll have to set the timestamp after the object is instantiated.
+		 *	 Furthermore, in PHP 5.3 we can use DateTime::setTimestamp() to do that and
+		 *	 given that we have UNIX timestamps - we should use it.
+		*/
+		$from = new DateTime();
+
+		if (is_php('5.3'))
+		{
+			$from->setTimestamp($unix_start);
+			if ($is_unix)
+			{
+				$arg = new DateTime();
+				$arg->setTimestamp($mixed);
+			}
+			else
+			{
+				$arg = (int) $mixed;
+			}
+
+			$period = new DatePeriod($from, new DateInterval('P1D'), $arg);
+			foreach ($period as $date)
+			{
+				$range[] = $date->format($format);
+			}
+
+			/* If a period end date was passed to the DatePeriod constructor, it might not
+			 * be in our results. Not sure if this is a bug or it's just possible because
+			 * the end date might actually be less than 24 hours away from the previously
+			 * generated DateTime object, but either way - we have to append it manually.
+			 */
+			if ( ! is_int($arg) && $range[count($range) - 1] !== $arg->format($format))
+			{
+				$range[] = $arg->format($format);
+			}
+
+			return $range;
+		}
+
+		$from->setDate(date('Y', $unix_start), date('n', $unix_start), date('j', $unix_start));
+		$from->setTime(date('G', $unix_start), date('i', $unix_start), date('s', $unix_start));
+		if ($is_unix)
+		{
+			$arg = new DateTime();
+			$arg->setDate(date('Y', $mixed), date('n', $mixed), date('j', $mixed));
+			$arg->setTime(date('G', $mixed), date('i', $mixed), date('s', $mixed));
+		}
+		else
+		{
+			$arg = (int) $mixed;
+		}
+		$range[] = $from->format($format);
+
+		if (is_int($arg)) // Day intervals
+		{
+			do
+			{
+				$from->modify('+1 day');
+				$range[] = $from->format($format);
+			}
+			while (--$arg > 0);
+		}
+		else // end date UNIX timestamp
+		{
+			for ($from->modify('+1 day'), $end_check = $arg->format('Ymd'); $from->format('Ymd') < $end_check; $from->modify('+1 day'))
+			{
+				$range[] = $from->format($format);
+			}
+
+			// Our loop only appended dates prior to our end date
+			$range[] = $arg->format($format);
+		}
+
+		return $range;
+	}
+}
+
 /* End of file date_helper.php */
 /* Location: ./system/helpers/date_helper.php */
\ No newline at end of file
diff --git a/system/helpers/directory_helper.php b/system/helpers/directory_helper.php
index e7d3b5e..49dcfc8 100644
--- a/system/helpers/directory_helper.php
+++ b/system/helpers/directory_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Directory Helpers
@@ -46,9 +47,10 @@
 	 * representation of it. Sub-folders contained with the
 	 * directory will be mapped as well.
 	 *
-	 * @param	string	path to source
-	 * @param	int	depth of directories to traverse (0 = fully recursive, 1 = current dir, etc)
-	 * @param	bool	whether to show hidden files
+	 * @param	string	$source_dir		Path to source
+	 * @param	int	$directory_depth	Depth of directories to traverse
+	 *						(0 = fully recursive, 1 = current dir, etc)
+	 * @param	bool	$hidden			Whether to show hidden files
 	 * @return	array
 	 */
 	function directory_map($source_dir, $directory_depth = 0, $hidden = FALSE)
@@ -62,14 +64,16 @@
 			while (FALSE !== ($file = readdir($fp)))
 			{
 				// Remove '.', '..', and hidden files [optional]
-				if ( ! trim($file, '.') OR ($hidden === FALSE && $file[0] === '.'))
+				if ($file === '.' OR $file === '..' OR ($hidden === FALSE && $file[0] === '.'))
 				{
 					continue;
 				}
 
+				@is_dir($source_dir.$file) AND $file .= DIRECTORY_SEPARATOR;
+
 				if (($directory_depth < 1 OR $new_depth > 0) && @is_dir($source_dir.$file))
 				{
-					$filedata[$file] = directory_map($source_dir.$file.DIRECTORY_SEPARATOR, $new_depth, $hidden);
+					$filedata[$file] = directory_map($source_dir.$file, $new_depth, $hidden);
 				}
 				else
 				{
diff --git a/system/helpers/download_helper.php b/system/helpers/download_helper.php
index 09c4de5..31652d5 100644
--- a/system/helpers/download_helper.php
+++ b/system/helpers/download_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Download Helpers
@@ -55,6 +56,23 @@
 		{
 			return FALSE;
 		}
+		elseif ($data === NULL)
+		{
+			if (@is_file($filename) && @file_exists($filename) && ($filesize = @filesize($filename)) !== FALSE)
+			{
+				$filepath = $filename;
+				$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
+				$filename = end($filename);
+			}
+			else
+			{
+				return FALSE;
+			}
+		}
+		else
+		{
+			$filesize = strlen($data);
+		}
 
 		// Set the default MIME type to send
 		$mime = 'application/octet-stream';
@@ -94,28 +112,46 @@
 			$filename = implode('.', $x);
 		}
 
+		if ($data === NULL && ($fp = @fopen($filepath, 'rb')) === FALSE)
+		{
+			return FALSE;
+		}
+
 		// Clean output buffer
-		ob_clean();
+		if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)
+		{
+			ob_clean();
+		}
 
 		// Generate the server headers
 		header('Content-Type: '.$mime);
 		header('Content-Disposition: attachment; filename="'.$filename.'"');
 		header('Expires: 0');
 		header('Content-Transfer-Encoding: binary');
-		header('Content-Length: '.strlen($data));
+		header('Content-Length: '.$filesize);
 
 		// Internet Explorer-specific headers
 		if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE)
 		{
-			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
-			header('Pragma: public');
-		}
-		else
-		{
-			header('Pragma: no-cache');
+			header('Cache-Control: no-cache, no-store, must-revalidate');
 		}
 
-		exit($data);
+		header('Pragma: no-cache');
+
+		// If we have raw data - just dump it
+		if ($data !== NULL)
+		{
+			exit($data);
+		}
+
+		// Flush 1MB chunks of data
+		while ( ! feof($fp) && ($data = fread($fp, 1048576)) !== FALSE)
+		{
+			echo $data;
+		}
+
+		fclose($fp);
+		exit;
 	}
 }
 
diff --git a/system/helpers/email_helper.php b/system/helpers/email_helper.php
index 2a63b36..dfb166a 100644
--- a/system/helpers/email_helper.php
+++ b/system/helpers/email_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Email Helpers
@@ -42,7 +43,8 @@
 	/**
 	 * Validate email address
 	 *
-	 * @param	string
+	 * @deprecated	3.0.0	Use PHP's filter_var() instead
+	 * @param	string	$email
 	 * @return	bool
 	 */
 	function valid_email($email)
@@ -58,12 +60,13 @@
 	/**
 	 * Send an email
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	string
+	 * @deprecated	3.0.0	Use PHP's mail() instead
+	 * @param	string	$recipient
+	 * @param	string	$subject
+	 * @param	string	$message
 	 * @return	bool
 	 */
-	function send_email($recipient, $subject = 'Test email', $message = 'Hello World')
+	function send_email($recipient, $subject, $message)
 	{
 		return mail($recipient, $subject, $message);
 	}
diff --git a/system/helpers/file_helper.php b/system/helpers/file_helper.php
index 7270ee3..aebb6e4 100644
--- a/system/helpers/file_helper.php
+++ b/system/helpers/file_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter File Helpers
@@ -32,7 +33,7 @@
  * @subpackage	Helpers
  * @category	Helpers
  * @author		EllisLab Dev Team
- * @link		http://codeigniter.com/user_guide/helpers/file_helpers.html
+ * @link		http://codeigniter.com/user_guide/helpers/file_helper.html
  */
 
 // ------------------------------------------------------------------------
@@ -44,12 +45,10 @@
 	 *
 	 * Opens the file specfied in the path and returns it as a string.
 	 *
-	 * This function is DEPRECATED and should be removed in
-	 * CodeIgniter 3.1+. Use file_get_contents() instead.
-	 *
-	 * @deprecated
-	 * @param	string	path to file
-	 * @return	string
+	 * @todo	Remove in version 3.1+.
+	 * @deprecated	3.0.0	It is now just an alias for PHP's native file_get_contents().
+	 * @param	string	$file	Path to file
+	 * @return	string	File contents
 	 */
 	function read_file($file)
 	{
@@ -67,12 +66,12 @@
 	 * Writes data to the file specified in the path.
 	 * Creates a new file if non-existent.
 	 *
-	 * @param	string	path to file
-	 * @param	string	file data
-	 * @param	int
+	 * @param	string	$path	File path
+	 * @param	string	$data	Data to write
+	 * @param	string	$mode	fopen() mode (default: 'wb')
 	 * @return	bool
 	 */
-	function write_file($path, $data, $mode = FOPEN_WRITE_CREATE_DESTRUCTIVE)
+	function write_file($path, $data, $mode = 'wb')
 	{
 		if ( ! $fp = @fopen($path, $mode))
 		{
@@ -100,16 +99,16 @@
 	 * If the second parameter is set to TRUE, any directories contained
 	 * within the supplied base directory will be nuked as well.
 	 *
-	 * @param	string	path to file
-	 * @param	bool	whether to delete any directories found in the path
-	 * @param	int
-	 * @param	bool	whether to skip deleting .htaccess and index page files
+	 * @param	string	$path		File path
+	 * @param	bool	$del_dir	Whether to delete any directories found in the path
+	 * @param	bool	$htdocs		Whether to skip deleting .htaccess and index page files
+	 * @param	int	$_level		Current directory depth level (default: 0; internal use only)
 	 * @return	bool
 	 */
-	function delete_files($path, $del_dir = FALSE, $level = 0, $htdocs = FALSE)
+	function delete_files($path, $del_dir = FALSE, $htdocs = FALSE, $_level = 0)
 	{
 		// Trim the trailing slash
-		$path = rtrim($path, DIRECTORY_SEPARATOR);
+		$path = rtrim($path, '/\\');
 
 		if ( ! $current_dir = @opendir($path))
 		{
@@ -122,9 +121,9 @@
 			{
 				if (is_dir($path.DIRECTORY_SEPARATOR.$filename) && $filename[0] !== '.')
 				{
-					delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $level + 1, $htdocs);
+					delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $htdocs, $_level + 1);
 				}
-				elseif ($htdocs === TRUE && ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
+				elseif ($htdocs !== TRUE OR ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
 				{
 					@unlink($path.DIRECTORY_SEPARATOR.$filename);
 				}
@@ -132,7 +131,7 @@
 		}
 		@closedir($current_dir);
 
-		if ($del_dir === TRUE && $level > 0)
+		if ($del_dir === TRUE && $_level > 0)
 		{
 			return @rmdir($path);
 		}
@@ -320,12 +319,12 @@
 	 * Note: this is NOT an accurate way of determining file mime types, and is here strictly as a convenience
 	 * It should NOT be trusted, and should certainly NOT be used for security
 	 *
-	 * @param	string	path to file
-	 * @return	mixed
+	 * @param	string	$filename	File name
+	 * @return	string
 	 */
-	function get_mime_by_extension($file)
+	function get_mime_by_extension($filename)
 	{
-		$extension = strtolower(substr(strrchr($file, '.'), 1));
+		$extension = strtolower(substr(strrchr($filename, '.'), 1));
 
 		static $mimes;
 
@@ -360,7 +359,7 @@
 	 * Takes a numeric value representing a file's permissions and returns
 	 * standard symbolic notation representing that value
 	 *
-	 * @param	int
+	 * @param	int	$perms	Permissions
 	 * @return	string
 	 */
 	function symbolic_permissions($perms)
@@ -427,7 +426,7 @@
 	 * Takes a numeric value representing a file's permissions and returns
 	 * a three character string representing the file's octal permissions
 	 *
-	 * @param	int
+	 * @param	int	$perms	Permissions
 	 * @return	string
 	 */
 	function octal_permissions($perms)
diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php
index 0c5d550..c7e6d49 100644
--- a/system/helpers/form_helper.php
+++ b/system/helpers/form_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -22,7 +22,9 @@
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 1.0
+ * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Form Helpers
@@ -124,9 +126,9 @@
 	 * Generates hidden fields. You can pass a simple key/value string or
 	 * an associative array with multiple values.
 	 *
-	 * @param	mixed
-	 * @param	string
-	 * @param	bool
+	 * @param	mixed	$name		Field name
+	 * @param	string	$value		Field value
+	 * @param	bool	$recursing
 	 * @return	string
 	 */
 	function form_hidden($name, $value = '', $recursing = FALSE)
@@ -149,7 +151,7 @@
 
 		if ( ! is_array($value))
 		{
-			$form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value, $name)."\" />\n";
+			$form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value)."\" />\n";
 		}
 		else
 		{
@@ -243,9 +245,9 @@
 	/**
 	 * Textarea field
 	 *
-	 * @param	mixed
-	 * @param	string
-	 * @param	string
+	 * @param	mixed	$data
+	 * @param	string	$value
+	 * @param	string	$extra
 	 * @return	string
 	 */
 	function form_textarea($data = '', $value = '', $extra = '')
@@ -263,7 +265,7 @@
 		}
 
 		$name = is_array($data) ? $data['name'] : $data;
-		return '<textarea '._parse_form_attributes($data, $defaults).$extra.'>'.form_prep($val, $name)."</textarea>\n";
+		return '<textarea '._parse_form_attributes($data, $defaults).$extra.'>'.form_prep($val, TRUE)."</textarea>\n";
 	}
 }
 
@@ -298,10 +300,10 @@
 	/**
 	 * Drop-down Menu
 	 *
-	 * @param	string
-	 * @param	array
-	 * @param	string
-	 * @param	string
+	 * @param	mixed	$name
+	 * @param	mixed	$options
+	 * @param	mixed	$selected
+	 * @param	mixed	$extra
 	 * @return	string
 	 */
 	function form_dropdown($name = '', $options = array(), $selected = array(), $extra = '')
@@ -316,10 +318,7 @@
 			return form_dropdown($name['name'], $name['options'], $name['selected'], $name['extra']);
 		}
 
-		if ( ! is_array($selected))
-		{
-			$selected = array($selected);
-		}
+		is_array($selected) OR $selected = array($selected);
 
 		// If no selected state was submitted we will attempt to set it automatically
 		if (count($selected) === 0 && isset($_POST[$name]))
@@ -340,21 +339,29 @@
 		{
 			$key = (string) $key;
 
-			if (is_array($val) && ! empty($val))
+			if (is_array($val))
 			{
+				if (empty($val))
+				{
+					continue;
+				}
+
 				$form .= '<optgroup label="'.$key."\">\n";
 
 				foreach ($val as $optgroup_key => $optgroup_val)
 				{
 					$sel = in_array($optgroup_key, $selected) ? ' selected="selected"' : '';
-					$form .= '<option value="'.$optgroup_key.'"'.$sel.'>'.(string) $optgroup_val."</option>\n";
+					$form .= '<option value="'.form_prep($optgroup_key).'"'.$sel.'>'
+						.(string) $optgroup_val."</option>\n";
 				}
 
 				$form .= "</optgroup>\n";
 			}
 			else
 			{
-				$form .= '<option value="'.$key.'"'.(in_array($key, $selected) ? ' selected="selected"' : '').'>'.(string) $val."</option>\n";
+				$form .= '<option value="'.form_prep($key).'"'
+					.(in_array($key, $selected) ? ' selected="selected"' : '').'>'
+					.(string) $val."</option>\n";
 			}
 		}
 
@@ -595,45 +602,28 @@
 	 *
 	 * Formats text so that it can be safely placed in a form field in the event it has HTML tags.
 	 *
-	 * @param	string
-	 * @param	string
-	 * @return	string
+	 * @param	string|string[]	$str		Value to escape
+	 * @param	bool		$is_textarea	Whether we're escaping for a textarea element
+	 * @return	string|string[]	Escaped values
 	 */
-	function form_prep($str = '', $field_name = '')
+	function form_prep($str = '', $is_textarea = FALSE)
 	{
-		static $prepped_fields = array();
-
-		// if the field name is an array we do this recursively
 		if (is_array($str))
 		{
-			foreach ($str as $key => $val)
+			foreach (array_keys($str) as $key)
 			{
-				$str[$key] = form_prep($val);
+				$str[$key] = form_prep($str[$key], $is_textarea);
 			}
 
 			return $str;
 		}
 
-		if ($str === '')
+		if ($is_textarea === TRUE)
 		{
-			return '';
+			return str_replace(array('<', '>'), array('&lt;', '&gt;'), stripslashes($str));
 		}
 
-		// we've already prepped a field with this name
-		// @todo need to figure out a way to namespace this so
-		// that we know the *exact* field and not just one with
-		// the same name
-		if (isset($prepped_fields[$field_name]))
-		{
-			return $str;
-		}
-
-		if ($field_name !== '')
-		{
-			$prepped_fields[$field_name] = $field_name;
-		}
-
-		return html_escape($str);
+		return str_replace(array("'", '"'), array('&#39;', '&quot;'), stripslashes($str));
 	}
 }
 
@@ -648,23 +638,21 @@
 	 * re-populate an input field or textarea. If Form Validation
 	 * is active it retrieves the info from the validation class
 	 *
-	 * @param	string
-	 * @param	string
-	 * @return	mixed
+	 * @param	string	$field		Field name
+	 * @param	string	$default	Default value
+	 * @param	bool	$is_textarea	Whether the field is a textarea element
+	 * @return	string
 	 */
-	function set_value($field = '', $default = '')
+	function set_value($field = '', $default = '', $is_textarea = FALSE)
 	{
 		if (FALSE === ($OBJ =& _get_validation_object()))
 		{
-			if ( ! isset($_POST[$field]))
-			{
-				return $default;
-			}
-
-			return form_prep($_POST[$field], $field);
+			return isset($_POST[$field])
+				? form_prep($_POST[$field], $is_textarea)
+				: form_prep($default, $is_textarea);
 		}
 
-		return form_prep($OBJ->set_value($field, $default), $field);
+		return form_prep($OBJ->set_value($field, $default), $is_textarea);
 	}
 }
 
@@ -826,7 +814,6 @@
 
 // ------------------------------------------------------------------------
 
-
 if ( ! function_exists('form_error'))
 {
 	/**
@@ -885,8 +872,8 @@
 	 *
 	 * Helper function used by some of the form helpers
 	 *
-	 * @param	array
-	 * @param	array
+	 * @param	array	$attributes	List of attributes
+	 * @param	array	$default	Default values
 	 * @return	string
 	 */
 	function _parse_form_attributes($attributes, $default)
@@ -914,7 +901,11 @@
 		{
 			if ($key === 'value')
 			{
-				$val = form_prep($val, $default['name']);
+				$val = form_prep($val);
+			}
+			elseif ($key === 'name' && ! strlen($default['name']))
+			{
+				continue;
 			}
 
 			$att .= $key.'="'.$val.'" ';
diff --git a/system/helpers/html_helper.php b/system/helpers/html_helper.php
index 68ce702..fa49f7d 100644
--- a/system/helpers/html_helper.php
+++ b/system/helpers/html_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter HTML Helpers
@@ -51,7 +52,7 @@
 	 */
 	function heading($data = '', $h = '1', $attributes = '')
 	{
-		return '<h'.$h.($attributes !== '' ? ' ' : '').$attributes.'>'.$data.'</h'.$h.'>';
+		return '<h'.$h._stringify_attributes($attributes).'>'.$data.'</h'.$h.'>';
 	}
 }
 
@@ -119,23 +120,8 @@
 		// Set the indentation based on the depth
 		$out = str_repeat(' ', $depth);
 
-		// Were any attributes submitted?  If so generate a string
-		if (is_array($attributes))
-		{
-			$atts = '';
-			foreach ($attributes as $key => $val)
-			{
-				$atts .= ' '.$key.'="'.$val.'"';
-			}
-			$attributes = $atts;
-		}
-		elseif (is_string($attributes) && strlen($attributes) > 0)
-		{
-			$attributes = ' '.$attributes;
-		}
-
 		// Write the opening list tag
-		$out .= '<'.$type.$attributes.">\n";
+		$out .= '<'.$type._stringify_attributes($attributes).">\n";
 
 		// Cycle through the list elements.  If an array is
 		// encountered we will recursively call _list()
@@ -171,12 +157,12 @@
 	/**
 	 * Generates HTML BR tags based on number supplied
 	 *
-	 * @param	int
+	 * @param	int	$count	Number of times to repeat the tag
 	 * @return	string
 	 */
-	function br($num = 1)
+	function br($count = 1)
 	{
-		return str_repeat('<br />', $num);
+		return str_repeat('<br />', $count);
 	}
 }
 
@@ -191,9 +177,10 @@
 	 *
 	 * @param	mixed
 	 * @param	bool
+	 * @param	mixed
 	 * @return	string
 	 */
-	function img($src = '', $index_page = FALSE)
+	function img($src = '', $index_page = FALSE, $attributes = '')
 	{
 		if ( ! is_array($src) )
 		{
@@ -229,7 +216,7 @@
 			}
 		}
 
-		return $img.'/>';
+		return $img._stringify_attributes($attributes).' />';
 	}
 }
 
@@ -242,9 +229,9 @@
 	 *
 	 * Generates a page document type declaration
 	 *
-	 * Valid options are xhtml-11, xhtml-strict, xhtml-trans, xhtml-frame,
-	 * html4-strict, html4-trans, and html4-frame. Values are saved in the
-	 * doctypes config file.
+	 * Examples of valid options: html5, xhtml-11, xhtml-strict, xhtml-trans,
+	 * xhtml-frame, html4-strict, html4-trans, and html4-frame.
+	 * All values are saved in the doctypes config file.
 	 *
 	 * @param	string	type	The doctype to be generated
 	 * @return	string
diff --git a/system/helpers/inflector_helper.php b/system/helpers/inflector_helper.php
index 647d840..68af820 100644
--- a/system/helpers/inflector_helper.php
+++ b/system/helpers/inflector_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Inflector Helpers
@@ -44,7 +45,7 @@
 	 *
 	 * Takes a plural word and makes it singular
 	 *
-	 * @param	string
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
 	function singular($str)
@@ -57,33 +58,33 @@
 		}
 
 		$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',
+			'/(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',
-			'/([^us])s$/'           => '\1',
+			'/([ti])a$/'		=> '\1um',
+			'/(p)eople$/'		=> '\1\2erson',
+			'/(m)en$/'		=> '\1an',
+			'/(s)tatuses$/'		=> '\1\2tatus',
+			'/(c)hildren$/'		=> '\1\2hild',
+			'/(n)ews$/'		=> '\1\2ews',
+			'/([^us])s$/'		=> '\1'
 		);
 
 		foreach ($singular_rules as $rule => $replacement)
@@ -108,11 +109,10 @@
 	 *
 	 * Takes a singular word and makes it plural
 	 *
-	 * @param	string
-	 * @param	bool
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
-	function plural($str, $force = FALSE)
+	function plural($str)
 	{
 		$result = strval($str);
 
@@ -165,7 +165,7 @@
 	 *
 	 * Takes multiple words separated by spaces or underscores and camelizes them
 	 *
-	 * @param	string
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
 	function camelize($str)
@@ -183,7 +183,7 @@
 	 *
 	 * Takes multiple words separated by spaces and underscores them
 	 *
-	 * @param	string
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
 	function underscore($str)
@@ -201,8 +201,8 @@
 	 *
 	 * Takes multiple words separated by the separator and changes them to spaces
 	 *
-	 * @param	string	$str
-	 * @param 	string	$separator
+	 * @param	string	$str		Input string
+	 * @param 	string	$separator	Input separator
 	 * @return	string
 	 */
 	function humanize($str, $separator = '_')
@@ -218,12 +218,12 @@
 	/**
 	 * Checks if the given word has a plural version.
 	 *
-	 * @param	string	the word to check
-	 * @return	bool	if the word is countable
+	 * @param	string	$word	Word to check
+	 * @return	bool
 	 */
 	function is_countable($word)
 	{
-		return ! in_array(strtolower(strval($word)),
+		return ! in_array(strtolower($word),
 					array(
 						'equipment', 'information', 'rice', 'money',
 						'species', 'series', 'fish', 'meta'
diff --git a/system/helpers/language_helper.php b/system/helpers/language_helper.php
index bd567ed..6d24a9c 100644
--- a/system/helpers/language_helper.php
+++ b/system/helpers/language_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Language Helpers
@@ -44,18 +45,19 @@
 	 *
 	 * Fetches a language variable and optionally outputs a form label
 	 *
-	 * @param	string	the language line
-	 * @param	string	the id of the form element
+	 * @param	string	$line		The language line
+	 * @param	string	$for		The "for" value (id of the form element)
+	 * @param	array	$attributes	Any additional HTML attributes
 	 * @return	string
 	 */
-	function lang($line, $id = '')
+	function lang($line, $for = '', $attributes = array())
 	{
 		$CI =& get_instance();
 		$line = $CI->lang->line($line);
 
-		if ($id !== '')
+		if ($for !== '')
 		{
-			$line = '<label for="'.$id.'">'.$line.'</label>';
+			$line = '<label for="'.$for.'"'._stringify_attributes($attributes).'>'.$line.'</label>';
 		}
 
 		return $line;
diff --git a/system/helpers/number_helper.php b/system/helpers/number_helper.php
index e49f2f7..b930965 100644
--- a/system/helpers/number_helper.php
+++ b/system/helpers/number_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Number Helpers
diff --git a/system/helpers/path_helper.php b/system/helpers/path_helper.php
index fec4a1a..5a798b1 100644
--- a/system/helpers/path_helper.php
+++ b/system/helpers/path_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Path Helpers
@@ -32,7 +33,7 @@
  * @subpackage	Helpers
  * @category	Helpers
  * @author		EllisLab Dev Team
- * @link		http://codeigniter.com/user_guide/helpers/xml_helper.html
+ * @link		http://codeigniter.com/user_guide/helpers/path_helper.html
  */
 
 // ------------------------------------------------------------------------
diff --git a/system/helpers/security_helper.php b/system/helpers/security_helper.php
index 0e8e9f9..898a49c 100644
--- a/system/helpers/security_helper.php
+++ b/system/helpers/security_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Security Helpers
@@ -77,12 +78,10 @@
 	/**
 	 * Hash encode a string
 	 *
-	 * This function is DEPRECATED and should be removed in
-	 * CodeIgniter 3.1+. Use hash() instead.
-	 *
-	 * @deprecated
-	 * @param	string
-	 * @param	string
+	 * @todo	Remove in version 3.1+.
+	 * @deprecated	3.0.0	Use PHP's native hash() instead.
+	 * @param	string	$str
+	 * @param	string	$type = 'sha1'
 	 * @return	string
 	 */
 	function do_hash($str, $type = 'sha1')
@@ -125,7 +124,7 @@
 	 */
 	function encode_php_tags($str)
 	{
-		return str_replace(array('<?', '?>'),  array('&lt;?', '?&gt;'), $str);
+		return str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $str);
 	}
 }
 
diff --git a/system/helpers/smiley_helper.php b/system/helpers/smiley_helper.php
index b6b2afc..7293999 100644
--- a/system/helpers/smiley_helper.php
+++ b/system/helpers/smiley_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Smiley Helpers
@@ -119,7 +120,6 @@
 
 // ------------------------------------------------------------------------
 
-
 if ( ! function_exists('get_clickable_smileys'))
 {
 	/**
@@ -130,10 +130,9 @@
 	 *
 	 * @param	string	the URL to the folder containing the smiley images
 	 * @param	array
-	 * @param	array
 	 * @return	array
 	 */
-	function get_clickable_smileys($image_url, $alias = '', $smileys = NULL)
+	function get_clickable_smileys($image_url, $alias = '')
 	{
 		// For backward compatibility with js_insert_smiley
 		if (is_array($alias))
@@ -142,7 +141,7 @@
 		}
 		elseif (FALSE === ($smileys = _get_smiley_array()))
 		{
-			return $smileys;
+			return FALSE;
 		}
 
 		// Add a trailing slash to the file path if needed
diff --git a/system/helpers/string_helper.php b/system/helpers/string_helper.php
index 4eee2a2..9fc2455 100644
--- a/system/helpers/string_helper.php
+++ b/system/helpers/string_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter String Helpers
@@ -50,6 +51,9 @@
 	 *
 	 * this/that/theother
 	 *
+	 * @todo	Remove in version 3.1+.
+	 * @deprecated	3.0.0	This is just an alias for PHP's native trim()
+	 *
 	 * @param	string
 	 * @return	string
 	 */
@@ -214,10 +218,10 @@
 						break;
 				}
 				return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len);
-			case 'unique':
+			case 'unique': // todo: remove in 3.1+
 			case 'md5':
 				return md5(uniqid(mt_rand()));
-			case 'encrypt':
+			case 'encrypt': // todo: remove in 3.1+
 			case 'sha1':
 				return sha1(uniqid(mt_rand(), TRUE));
 		}
@@ -276,8 +280,11 @@
 	/**
 	 * Repeater function
 	 *
-	 * @param	string
-	 * @param	int	number of repeats
+	 * @todo	Remove in version 3.1+.
+	 * @deprecated	3.0.0	This is just an alias for PHP's native str_repeat()
+	 *
+	 * @param	string	$data	String to repeat
+	 * @param	int	$num	Number of repeats
 	 * @return	string
 	 */
 	function repeater($data, $num = 1)
diff --git a/system/helpers/text_helper.php b/system/helpers/text_helper.php
index 8a1f01b..7052387 100644
--- a/system/helpers/text_helper.php
+++ b/system/helpers/text_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Text Helpers
@@ -89,7 +90,8 @@
 			return $str;
 		}
 
-		$str = preg_replace('/\s+/', ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str));
+		// a bit complicated, but faster than preg_replace with \s+
+		$str = preg_replace('/ {2,}/', ' ', str_replace(array("\r", "\n", "\t", "\x0B", "\x0C"), ' ', $str));
 
 		if (strlen($str) <= $n)
 		{
@@ -117,18 +119,15 @@
 	/**
 	 * High ASCII to Entities
 	 *
-	 * Converts High ascii text and MS Word special characters to character entities
+	 * Converts high ASCII text and MS Word special characters to character entities
 	 *
-	 * @param	string
+	 * @param	string	$str
 	 * @return	string
 	 */
 	function ascii_to_entities($str)
 	{
-		$count	= 1;
-		$out	= '';
-		$temp	= array();
-
-		for ($i = 0, $s = strlen($str); $i < $s; $i++)
+		$out = '';
+		for ($i = 0, $s = strlen($str), $count = 1, $temp = array(); $i < $s; $i++)
 		{
 			$ordinal = ord($str[$i]);
 
@@ -140,7 +139,7 @@
 				*/
 				if (count($temp) === 1)
 				{
-					$out  .= '&#'.array_shift($temp).';';
+					$out .= '&#'.array_shift($temp).';';
 					$count = 1;
 				}
 
@@ -389,19 +388,19 @@
 
 // ------------------------------------------------------------------------
 
-/**
- * Word Wrap
- *
- * Wraps text at the specified character. Maintains the integrity of words.
- * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor
- * will URLs.
- *
- * @param	string	the text string
- * @param	int	the number of characters to wrap at
- * @return	string
- */
 if ( ! function_exists('word_wrap'))
 {
+	/**
+	 * Word Wrap
+	 *
+	 * Wraps text at the specified character. Maintains the integrity of words.
+	 * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor
+	 * will URLs.
+	 *
+	 * @param	string	$str		the text string
+	 * @param	int	$charlim = 76	the number of characters to wrap at
+	 * @return	string
+	 */
 	function word_wrap($str, $charlim = 76)
 	{
 		// Set the character limit
diff --git a/system/helpers/typography_helper.php b/system/helpers/typography_helper.php
index af9d16a..48f1f11 100644
--- a/system/helpers/typography_helper.php
+++ b/system/helpers/typography_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Typography Helpers
@@ -60,16 +61,15 @@
 	/**
 	 * Auto Typography Wrapper Function
 	 *
-	 * @param	string
-	 * @param	bool	whether to allow javascript event handlers
-	 * @param	bool	whether to reduce multiple instances of double newlines to two
+	 * @param	string	$str
+	 * @param	bool	$reduce_linebreaks = FALSE	whether to reduce multiple instances of double newlines to two
 	 * @return	string
 	 */
-	function auto_typography($str, $strip_js_event_handlers = TRUE, $reduce_linebreaks = FALSE)
+	function auto_typography($str, $reduce_linebreaks = FALSE)
 	{
 		$CI =& get_instance();
 		$CI->load->library('typography');
-		return $CI->typography->auto_typography($str, $strip_js_event_handlers, $reduce_linebreaks);
+		return $CI->typography->auto_typography($str, $reduce_linebreaks);
 	}
 }
 
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index 39e6343..36ff0ff 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter URL Helpers
@@ -151,7 +152,7 @@
 
 		if ( ! is_array($uri))
 		{
-			$site_url = preg_match('!^\w+://! i', $uri) ? $uri : site_url($uri);
+			$site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri);
 		}
 		else
 		{
@@ -165,7 +166,7 @@
 
 		if ($attributes !== '')
 		{
-			$attributes = _parse_attributes($attributes);
+			$attributes = _stringify_attributes($attributes);
 		}
 
 		return '<a href="'.$site_url.'"'.$attributes.'>'.$title.'</a>';
@@ -190,7 +191,7 @@
 	function anchor_popup($uri = '', $title = '', $attributes = FALSE)
 	{
 		$title = (string) $title;
-		$site_url = preg_match('!^\w+://! i', $uri) ? $uri : site_url($uri);
+		$site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri);
 
 		if ($title === '')
 		{
@@ -221,10 +222,10 @@
 			unset($attributes[$key]);
 		}
 
-		$attributes = empty($attributes) ? '' : _parse_attributes($attributes);
+		$attributes = _stringify_attributes($attributes);
 
 		return '<a href="'.$site_url
-			.'" onclick="window.open(\''.$site_url."', '".$window_name."', '"._parse_attributes($atts, TRUE)."'); return false;\""
+			.'" onclick="window.open(\''.$site_url."', '".$window_name."', '"._stringify_attributes($atts, TRUE)."'); return false;\""
 			.$attributes.'>'.$title.'</a>';
 	}
 }
@@ -250,7 +251,7 @@
 			$title = $email;
 		}
 
-		return '<a href="mailto:'.$email.'"'._parse_attributes($attributes).'>'.$title.'</a>';
+		return '<a href="mailto:'.$email.'"'._stringify_attributes($attributes).'>'.$title.'</a>';
 	}
 }
 
@@ -292,7 +293,7 @@
 			{
 				foreach ($attributes as $key => $val)
 				{
-					$x[] =  ' '.$key.'="';
+					$x[] = ' '.$key.'="';
 					for ($i = 0, $l = strlen($val); $i < $l; $i++)
 					{
 						$x[] = '|'.ord($val[$i]);
@@ -388,40 +389,43 @@
 
 			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
 			{
-				if (preg_match('|\.$|', $matches[6][$i]))
+				if (preg_match('/(\.|\,)$/i', $matches[6][$i], $m))
 				{
-					$period = '.';
+					$punct = $m[1];
 					$matches[6][$i] = substr($matches[6][$i], 0, -1);
 				}
 				else
 				{
-					$period = '';
+					$punct = '';
 				}
 
 				$str = str_replace($matches[0][$i],
 							$matches[1][$i].'<a href="http'.$matches[4][$i].'://'
 								.$matches[5][$i].$matches[6][$i].'"'.$pop.'>http'
 								.$matches[4][$i].'://'.$matches[5][$i]
-								.$matches[6][$i].'</a>'.$period,
+								.$matches[6][$i].'</a>'.$punct,
 							$str);
 			}
 		}
 
-		if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]*)/i', $str, $matches))
+		if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]+)/i', $str, $matches))
 		{
 			for ($i = 0, $c = count($matches); $i < $c; $i++)
 			{
-				if (preg_match('|\.$|', $matches[3][$i]))
+				if (preg_match('/(\.|\,)$/i', $matches[3][$i], $m))
 				{
-					$period = '.';
+					$punct = $m[1];
 					$matches[3][$i] = substr($matches[3][$i], 0, -1);
 				}
 				else
 				{
-					$period = '';
+					$punct = '';
 				}
 
-				$str = str_replace($matches[0][$i], safe_mailto($matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]).$period, $str);
+				if (filter_var(($m = $matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]), FILTER_VALIDATE_EMAIL) !== FALSE)
+				{
+					$str = str_replace($matches[0][$i], safe_mailto($m).$punct, $str);
+				}
 			}
 		}
 
@@ -470,9 +474,11 @@
 	 * human-friendly URL string with a "separator" string
 	 * as the word separator.
 	 *
-	 * @param	string	the string
-	 * @param	string	the separator
-	 * @param	bool
+	 * @todo	Remove old 'dash' and 'underscore' usage in 3.1+.
+	 * @param	string	$str		Input string
+	 * @param	string	$separator	Word separator
+	 *			(usually '-' or '_')
+	 * @param	bool	$lowercase	Wether to transform the output string to lowercase
 	 * @return	string
 	 */
 	function url_title($str, $separator = '-', $lowercase = FALSE)
@@ -486,7 +492,7 @@
 			$separator = '_';
 		}
 
-		$q_separator = preg_quote($separator);
+		$q_separator = preg_quote($separator, '#');
 
 		$trans = array(
 				'&.+?;'			=> '',
@@ -521,20 +527,21 @@
 	 * For very fine grained control over headers, you could use the Output
 	 * Library's set_header() function.
 	 *
-	 * @param	string	the URL
-	 * @param	string	the method: location or refresh
-	 * @param	int
-	 * @return	string
+	 * @param	string	$uri	URL
+	 * @param	string	$method	Redirect method
+	 *			'auto', 'location' or 'refresh'
+	 * @param	int	$code	HTTP Response status code
+	 * @return	void
 	 */
 	function redirect($uri = '', $method = 'auto', $code = NULL)
 	{
-		if ( ! preg_match('#^https?://#i', $uri))
+		if ( ! preg_match('#^(\w+:)?//#i', $uri))
 		{
 			$uri = site_url($uri);
 		}
 
 		// IIS environment likely? Use 'refresh' for better compatibility
-		if (DIRECTORY_SEPARATOR !== '/' && $method === 'auto')
+		if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== FALSE)
 		{
 			$method = 'refresh';
 		}
@@ -560,47 +567,5 @@
 	}
 }
 
-// ------------------------------------------------------------------------
-
-if ( ! function_exists('_parse_attributes'))
-{
-	/**
-	 * Parse out the attributes
-	 *
-	 * Some of the functions use this
-	 *
-	 * @param	array
-	 * @param	bool
-	 * @return	string
-	 */
-	function _parse_attributes($attributes, $javascript = FALSE)
-	{
-		if (is_string($attributes))
-		{
-			return ($attributes !== '') ? ' '.$attributes : '';
-		}
-
-		$att = '';
-		foreach ($attributes as $key => $val)
-		{
-			if ($javascript === TRUE)
-			{
-				$att .= $key.'='.$val.',';
-			}
-			else
-			{
-				$att .= ' '.$key.'="'.$val.'"';
-			}
-		}
-
-		if ($javascript === TRUE && $att !== '')
-		{
-			return substr($att, 0, -1);
-		}
-
-		return $att;
-	}
-}
-
 /* End of file url_helper.php */
 /* Location: ./system/helpers/url_helper.php */
\ No newline at end of file
diff --git a/system/helpers/xml_helper.php b/system/helpers/xml_helper.php
index 1431777..c3dfdcd 100644
--- a/system/helpers/xml_helper.php
+++ b/system/helpers/xml_helper.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter XML Helpers
diff --git a/system/language/english/calendar_lang.php b/system/language/english/calendar_lang.php
index 48939d4..288eb91 100644
--- a/system/language/english/calendar_lang.php
+++ b/system/language/english/calendar_lang.php
@@ -24,53 +24,53 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['cal_su']			= "Su";
-$lang['cal_mo']			= "Mo";
-$lang['cal_tu']			= "Tu";
-$lang['cal_we']			= "We";
-$lang['cal_th']			= "Th";
-$lang['cal_fr']			= "Fr";
-$lang['cal_sa']			= "Sa";
-$lang['cal_sun']		= "Sun";
-$lang['cal_mon']		= "Mon";
-$lang['cal_tue']		= "Tue";
-$lang['cal_wed']		= "Wed";
-$lang['cal_thu']		= "Thu";
-$lang['cal_fri']		= "Fri";
-$lang['cal_sat']		= "Sat";
-$lang['cal_sunday']		= "Sunday";
-$lang['cal_monday']		= "Monday";
-$lang['cal_tuesday']	= "Tuesday";
-$lang['cal_wednesday']	= "Wednesday";
-$lang['cal_thursday']	= "Thursday";
-$lang['cal_friday']		= "Friday";
-$lang['cal_saturday']	= "Saturday";
-$lang['cal_jan']		= "Jan";
-$lang['cal_feb']		= "Feb";
-$lang['cal_mar']		= "Mar";
-$lang['cal_apr']		= "Apr";
-$lang['cal_may']		= "May";
-$lang['cal_jun']		= "Jun";
-$lang['cal_jul']		= "Jul";
-$lang['cal_aug']		= "Aug";
-$lang['cal_sep']		= "Sep";
-$lang['cal_oct']		= "Oct";
-$lang['cal_nov']		= "Nov";
-$lang['cal_dec']		= "Dec";
-$lang['cal_january']	= "January";
-$lang['cal_february']	= "February";
-$lang['cal_march']		= "March";
-$lang['cal_april']		= "April";
-$lang['cal_mayl']		= "May";
-$lang['cal_june']		= "June";
-$lang['cal_july']		= "July";
-$lang['cal_august']		= "August";
-$lang['cal_september']	= "September";
-$lang['cal_october']	= "October";
-$lang['cal_november']	= "November";
-$lang['cal_december']	= "December";
-
+$lang['cal_su']			= 'Su';
+$lang['cal_mo']			= 'Mo';
+$lang['cal_tu']			= 'Tu';
+$lang['cal_we']			= 'We';
+$lang['cal_th']			= 'Th';
+$lang['cal_fr']			= 'Fr';
+$lang['cal_sa']			= 'Sa';
+$lang['cal_sun']		= 'Sun';
+$lang['cal_mon']		= 'Mon';
+$lang['cal_tue']		= 'Tue';
+$lang['cal_wed']		= 'Wed';
+$lang['cal_thu']		= 'Thu';
+$lang['cal_fri']		= 'Fri';
+$lang['cal_sat']		= 'Sat';
+$lang['cal_sunday']		= 'Sunday';
+$lang['cal_monday']		= 'Monday';
+$lang['cal_tuesday']	= 'Tuesday';
+$lang['cal_wednesday']	= 'Wednesday';
+$lang['cal_thursday']	= 'Thursday';
+$lang['cal_friday']		= 'Friday';
+$lang['cal_saturday']	= 'Saturday';
+$lang['cal_jan']		= 'Jan';
+$lang['cal_feb']		= 'Feb';
+$lang['cal_mar']		= 'Mar';
+$lang['cal_apr']		= 'Apr';
+$lang['cal_may']		= 'May';
+$lang['cal_jun']		= 'Jun';
+$lang['cal_jul']		= 'Jul';
+$lang['cal_aug']		= 'Aug';
+$lang['cal_sep']		= 'Sep';
+$lang['cal_oct']		= 'Oct';
+$lang['cal_nov']		= 'Nov';
+$lang['cal_dec']		= 'Dec';
+$lang['cal_january']	= 'January';
+$lang['cal_february']	= 'February';
+$lang['cal_march']		= 'March';
+$lang['cal_april']		= 'April';
+$lang['cal_mayl']		= 'May';
+$lang['cal_june']		= 'June';
+$lang['cal_july']		= 'July';
+$lang['cal_august']		= 'August';
+$lang['cal_september']	= 'September';
+$lang['cal_october']	= 'October';
+$lang['cal_november']	= 'November';
+$lang['cal_december']	= 'December';
 
 /* End of file calendar_lang.php */
 /* Location: ./system/language/english/calendar_lang.php */
\ No newline at end of file
diff --git a/system/language/english/date_lang.php b/system/language/english/date_lang.php
index 38532b7..db1e9e0 100644
--- a/system/language/english/date_lang.php
+++ b/system/language/english/date_lang.php
@@ -24,21 +24,22 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['date_year'] = "Year";
-$lang['date_years'] = "Years";
-$lang['date_month'] = "Month";
-$lang['date_months'] = "Months";
-$lang['date_week'] = "Week";
-$lang['date_weeks'] = "Weeks";
-$lang['date_day'] = "Day";
-$lang['date_days'] = "Days";
-$lang['date_hour'] = "Hour";
-$lang['date_hours'] = "Hours";
-$lang['date_minute'] = "Minute";
-$lang['date_minutes'] = "Minutes";
-$lang['date_second'] = "Second";
-$lang['date_seconds'] = "Seconds";
+$lang['date_year'] = 'Year';
+$lang['date_years'] = 'Years';
+$lang['date_month'] = 'Month';
+$lang['date_months'] = 'Months';
+$lang['date_week'] = 'Week';
+$lang['date_weeks'] = 'Weeks';
+$lang['date_day'] = 'Day';
+$lang['date_days'] = 'Days';
+$lang['date_hour'] = 'Hour';
+$lang['date_hours'] = 'Hours';
+$lang['date_minute'] = 'Minute';
+$lang['date_minutes'] = 'Minutes';
+$lang['date_second'] = 'Second';
+$lang['date_seconds'] = 'Seconds';
 
 $lang['UM12']	= '(UTC -12:00) Baker/Howland Island';
 $lang['UM11']	= '(UTC -11:00) Niue';
@@ -81,6 +82,5 @@
 $lang['UP13']	= '(UTC +13:00) Samoa Time Zone, Phoenix Islands Time, Tonga';
 $lang['UP14']	= '(UTC +14:00) Line Islands';
 
-
 /* End of file date_lang.php */
 /* Location: ./system/language/english/date_lang.php */
\ No newline at end of file
diff --git a/system/language/english/db_lang.php b/system/language/english/db_lang.php
index 479cbb1..50ce6bb 100644
--- a/system/language/english/db_lang.php
+++ b/system/language/english/db_lang.php
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 $lang['db_invalid_connection_str'] = 'Unable to determine the database settings based on the connection string you submitted.';
 $lang['db_unable_to_connect'] = 'Unable to connect to your database server using the provided settings.';
@@ -40,8 +41,8 @@
 $lang['db_unsupported_function'] = 'This feature is not available for the database you are using.';
 $lang['db_transaction_failure'] = 'Transaction failure: Rollback performed.';
 $lang['db_unable_to_drop'] = 'Unable to drop the specified database.';
-$lang['db_unsuported_feature'] = 'Unsupported feature of the database platform you are using.';
-$lang['db_unsuported_compression'] = 'The file compression format you chose is not supported by your server.';
+$lang['db_unsupported_feature'] = 'Unsupported feature of the database platform you are using.';
+$lang['db_unsupported_compression'] = 'The file compression format you chose is not supported by your server.';
 $lang['db_filepath_error'] = 'Unable to write data to the file path you have submitted.';
 $lang['db_invalid_cache_path'] = 'The cache path you submitted is not valid or writable.';
 $lang['db_table_name_required'] = 'A table name is required for that operation.';
diff --git a/system/language/english/email_lang.php b/system/language/english/email_lang.php
index 95a16d1..646a496 100644
--- a/system/language/english/email_lang.php
+++ b/system/language/english/email_lang.php
@@ -24,26 +24,26 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['email_must_be_array'] = "The email validation method must be passed an array.";
-$lang['email_invalid_address'] = "Invalid email address: %s";
-$lang['email_attachment_missing'] = "Unable to locate the following email attachment: %s";
-$lang['email_attachment_unreadable'] = "Unable to open this attachment: %s";
-$lang['email_no_recipients'] = "You must include recipients: To, Cc, or Bcc";
-$lang['email_send_failure_phpmail'] = "Unable to send email using PHP mail().  Your server might not be configured to send mail using this method.";
-$lang['email_send_failure_sendmail'] = "Unable to send email using PHP Sendmail.  Your server might not be configured to send mail using this method.";
-$lang['email_send_failure_smtp'] = "Unable to send email using PHP SMTP.  Your server might not be configured to send mail using this method.";
-$lang['email_sent'] = "Your message has been successfully sent using the following protocol: %s";
-$lang['email_no_socket'] = "Unable to open a socket to Sendmail. Please check settings.";
-$lang['email_no_hostname'] = "You did not specify a SMTP hostname.";
-$lang['email_smtp_error'] = "The following SMTP error was encountered: %s";
-$lang['email_no_smtp_unpw'] = "Error: You must assign a SMTP username and password.";
-$lang['email_failed_smtp_login'] = "Failed to send AUTH LOGIN command. Error: %s";
-$lang['email_smtp_auth_un'] = "Failed to authenticate username. Error: %s";
-$lang['email_smtp_auth_pw'] = "Failed to authenticate password. Error: %s";
-$lang['email_smtp_data_failure'] = "Unable to send data: %s";
-$lang['email_exit_status'] = "Exit status code: %s";
-
+$lang['email_must_be_array'] = 'The email validation method must be passed an array.';
+$lang['email_invalid_address'] = 'Invalid email address: %s';
+$lang['email_attachment_missing'] = 'Unable to locate the following email attachment: %s';
+$lang['email_attachment_unreadable'] = 'Unable to open this attachment: %s';
+$lang['email_no_recipients'] = 'You must include recipients: To, Cc, or Bcc';
+$lang['email_send_failure_phpmail'] = 'Unable to send email using PHP mail(). Your server might not be configured to send mail using this method.';
+$lang['email_send_failure_sendmail'] = 'Unable to send email using PHP Sendmail. Your server might not be configured to send mail using this method.';
+$lang['email_send_failure_smtp'] = 'Unable to send email using PHP SMTP. Your server might not be configured to send mail using this method.';
+$lang['email_sent'] = 'Your message has been successfully sent using the following protocol: %s';
+$lang['email_no_socket'] = 'Unable to open a socket to Sendmail. Please check settings.';
+$lang['email_no_hostname'] = 'You did not specify a SMTP hostname.';
+$lang['email_smtp_error'] = 'The following SMTP error was encountered: %s';
+$lang['email_no_smtp_unpw'] = 'Error: You must assign a SMTP username and password.';
+$lang['email_failed_smtp_login'] = 'Failed to send AUTH LOGIN command. Error: %s';
+$lang['email_smtp_auth_un'] = 'Failed to authenticate username. Error: %s';
+$lang['email_smtp_auth_pw'] = 'Failed to authenticate password. Error: %s';
+$lang['email_smtp_data_failure'] = 'Unable to send data: %s';
+$lang['email_exit_status'] = 'Exit status code: %s';
 
 /* End of file email_lang.php */
 /* Location: ./system/language/english/email_lang.php */
\ No newline at end of file
diff --git a/system/language/english/form_validation_lang.php b/system/language/english/form_validation_lang.php
index eb4624e..a809f1f 100644
--- a/system/language/english/form_validation_lang.php
+++ b/system/language/english/form_validation_lang.php
@@ -24,33 +24,34 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['required']				= "The %s field is required.";
-$lang['isset']					= "The %s field must have a value.";
-$lang['valid_email']			= "The %s field must contain a valid email address.";
-$lang['valid_emails']			= "The %s field must contain all valid email addresses.";
-$lang['valid_url']				= "The %s field must contain a valid URL.";
-$lang['valid_ip']				= "The %s field must contain a valid IP.";
-$lang['min_length']				= "The %s field must be at least %s characters in length.";
-$lang['max_length']				= "The %s field cannot exceed %s characters in length.";
-$lang['exact_length']			= "The %s field must be exactly %s characters in length.";
-$lang['alpha']					= "The %s field may only contain alphabetical characters.";
-$lang['alpha_numeric']			= "The %s field may only contain alpha-numeric characters.";
-$lang['alpha_dash']				= "The %s field may only contain alpha-numeric characters, underscores, and dashes.";
-$lang['numeric']				= "The %s field must contain only numbers.";
-$lang['is_numeric']				= "The %s field must contain only numeric characters.";
-$lang['integer']				= "The %s field must contain an integer.";
-$lang['regex_match']			= "The %s field is not in the correct format.";
-$lang['matches']				= "The %s field does not match the %s field.";
-$lang['is_unique'] 				= "The %s field must contain a unique value.";
-$lang['is_natural']				= "The %s field must contain only positive numbers.";
-$lang['is_natural_no_zero']		= "The %s field must contain a number greater than zero.";
-$lang['decimal']				= "The %s field must contain a decimal number.";
-$lang['less_than']				= "The %s field must contain a number less than %s.";
-$lang['less_than_equal_to']		= "The %s field must contain a number less than or equal to %s.";
-$lang['greater_than']			= "The %s field must contain a number greater than %s.";
-$lang['greater_than_equal_to']	= "The %s field must contain a number greater than or equal to %s.";
-
+$lang['form_validation_required']		= 'The {field} field is required.';
+$lang['form_validation_isset']			= 'The {field} field must have a value.';
+$lang['form_validation_valid_email']		= 'The {field} field must contain a valid email address.';
+$lang['form_validation_valid_emails']		= 'The {field} field must contain all valid email addresses.';
+$lang['form_validation_valid_url']		= 'The {field} field must contain a valid URL.';
+$lang['form_validation_valid_ip']		= 'The {field} field must contain a valid IP.';
+$lang['form_validation_min_length']		= 'The {field} field must be at least {param} characters in length.';
+$lang['form_validation_max_length']		= 'The {field} field cannot exceed {param} characters in length.';
+$lang['form_validation_exact_length']		= 'The {field} field must be exactly {param} characters in length.';
+$lang['form_validation_alpha']			= 'The {field} field may only contain alphabetical characters.';
+$lang['form_validation_alpha_numeric']		= 'The {field} field may only contain alpha-numeric characters.';
+$lang['form_validation_alpha_dash']		= 'The {field} field may only contain alpha-numeric characters, underscores, and dashes.';
+$lang['form_validation_numeric']		= 'The {field} field must contain only numbers.';
+$lang['form_validation_is_numeric']		= 'The {field} field must contain only numeric characters.';
+$lang['form_validation_integer']		= 'The {field} field must contain an integer.';
+$lang['form_validation_regex_match']		= 'The {field} field is not in the correct format.';
+$lang['form_validation_matches']		= 'The {field} field does not match the {param} field.';
+$lang['form_validation_differs']		= 'The {field} field must differ from the {param} field.';
+$lang['form_validation_is_unique'] 		= 'The {field} field must contain a unique value.';
+$lang['form_validation_is_natural']		= 'The {field} field must only contain digits.';
+$lang['form_validation_is_natural_no_zero']	= 'The {field} field must only contain digits and must be greater than zero.';
+$lang['form_validation_decimal']		= 'The {field} field must contain a decimal number.';
+$lang['form_validation_less_than']		= 'The {field} field must contain a number less than {param}.';
+$lang['form_validation_less_than_equal_to']	= 'The {field} field must contain a number less than or equal to {param}.';
+$lang['form_validation_greater_than']		= 'The {field} field must contain a number greater than {param}.';
+$lang['form_validation_greater_than_equal_to']	= 'The {field} field must contain a number greater than or equal to {param}.';
 
 /* End of file form_validation_lang.php */
-/* Location: ./system/language/english/form_validation_lang.php */
+/* Location: ./system/language/english/form_validation_lang.php */
\ No newline at end of file
diff --git a/system/language/english/ftp_lang.php b/system/language/english/ftp_lang.php
index d00126b..090a88c 100644
--- a/system/language/english/ftp_lang.php
+++ b/system/language/english/ftp_lang.php
@@ -24,20 +24,20 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['ftp_no_connection']			= "Unable to locate a valid connection ID. Please make sure you are connected before peforming any file routines.";
-$lang['ftp_unable_to_connect']		= "Unable to connect to your FTP server using the supplied hostname.";
-$lang['ftp_unable_to_login']		= "Unable to login to your FTP server. Please check your username and password.";
-$lang['ftp_unable_to_makdir']		= "Unable to create the directory you have specified.";
-$lang['ftp_unable_to_changedir']	= "Unable to change directories.";
-$lang['ftp_unable_to_chmod']		= "Unable to set file permissions. Please check your path. Note: This feature is only available in PHP 5 or higher.";
-$lang['ftp_unable_to_upload']		= "Unable to upload the specified file. Please check your path.";
-$lang['ftp_unable_to_download']		= "Unable to download the specified file. Please check your path.";
-$lang['ftp_no_source_file']			= "Unable to locate the source file. Please check your path.";
-$lang['ftp_unable_to_rename']		= "Unable to rename the file.";
-$lang['ftp_unable_to_delete']		= "Unable to delete the file.";
-$lang['ftp_unable_to_move']			= "Unable to move the file. Please make sure the destination directory exists.";
-
+$lang['ftp_no_connection']			= 'Unable to locate a valid connection ID. Please make sure you are connected before peforming any file routines.';
+$lang['ftp_unable_to_connect']		= 'Unable to connect to your FTP server using the supplied hostname.';
+$lang['ftp_unable_to_login']		= 'Unable to login to your FTP server. Please check your username and password.';
+$lang['ftp_unable_to_makdir']		= 'Unable to create the directory you have specified.';
+$lang['ftp_unable_to_changedir']	= 'Unable to change directories.';
+$lang['ftp_unable_to_chmod']		= 'Unable to set file permissions. Please check your path. Note: This feature is only available in PHP 5 or higher.';
+$lang['ftp_unable_to_upload']		= 'Unable to upload the specified file. Please check your path.';
+$lang['ftp_unable_to_download']		= 'Unable to download the specified file. Please check your path.';
+$lang['ftp_no_source_file']			= 'Unable to locate the source file. Please check your path.';
+$lang['ftp_unable_to_rename']		= 'Unable to rename the file.';
+$lang['ftp_unable_to_delete']		= 'Unable to delete the file.';
+$lang['ftp_unable_to_move']			= 'Unable to move the file. Please make sure the destination directory exists.';
 
 /* End of file ftp_lang.php */
 /* Location: ./system/language/english/ftp_lang.php */
\ No newline at end of file
diff --git a/system/language/english/imglib_lang.php b/system/language/english/imglib_lang.php
index 67a36e1..296c4af 100644
--- a/system/language/english/imglib_lang.php
+++ b/system/language/english/imglib_lang.php
@@ -24,26 +24,26 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['imglib_source_image_required'] = "You must specify a source image in your preferences.";
-$lang['imglib_gd_required'] = "The GD image library is required for this feature.";
-$lang['imglib_gd_required_for_props'] = "Your server must support the GD image library in order to determine the image properties.";
-$lang['imglib_unsupported_imagecreate'] = "Your server does not support the GD function required to process this type of image.";
-$lang['imglib_gif_not_supported'] = "GIF images are often not supported due to licensing restrictions.  You may have to use JPG or PNG images instead.";
-$lang['imglib_jpg_not_supported'] = "JPG images are not supported.";
-$lang['imglib_png_not_supported'] = "PNG images are not supported.";
-$lang['imglib_jpg_or_png_required'] = "The image resize protocol specified in your preferences only works with JPEG or PNG image types.";
-$lang['imglib_copy_error'] = "An error was encountered while attempting to replace the file.  Please make sure your file directory is writable.";
-$lang['imglib_rotate_unsupported'] = "Image rotation does not appear to be supported by your server.";
-$lang['imglib_libpath_invalid'] = "The path to your image library is not correct.  Please set the correct path in your image preferences.";
-$lang['imglib_image_process_failed'] = "Image processing failed.  Please verify that your server supports the chosen protocol and that the path to your image library is correct.";
-$lang['imglib_rotation_angle_required'] = "An angle of rotation is required to rotate the image.";
-$lang['imglib_writing_failed_gif'] = "GIF image.";
-$lang['imglib_invalid_path'] = "The path to the image is not correct.";
-$lang['imglib_copy_failed'] = "The image copy routine failed.";
-$lang['imglib_missing_font'] = "Unable to find a font to use.";
-$lang['imglib_save_failed'] = "Unable to save the image.  Please make sure the image and file directory are writable.";
-
+$lang['imglib_source_image_required'] = 'You must specify a source image in your preferences.';
+$lang['imglib_gd_required'] = 'The GD image library is required for this feature.';
+$lang['imglib_gd_required_for_props'] = 'Your server must support the GD image library in order to determine the image properties.';
+$lang['imglib_unsupported_imagecreate'] = 'Your server does not support the GD function required to process this type of image.';
+$lang['imglib_gif_not_supported'] = 'GIF images are often not supported due to licensing restrictions. You may have to use JPG or PNG images instead.';
+$lang['imglib_jpg_not_supported'] = 'JPG images are not supported.';
+$lang['imglib_png_not_supported'] = 'PNG images are not supported.';
+$lang['imglib_jpg_or_png_required'] = 'The image resize protocol specified in your preferences only works with JPEG or PNG image types.';
+$lang['imglib_copy_error'] = 'An error was encountered while attempting to replace the file. Please make sure your file directory is writable.';
+$lang['imglib_rotate_unsupported'] = 'Image rotation does not appear to be supported by your server.';
+$lang['imglib_libpath_invalid'] = 'The path to your image library is not correct. Please set the correct path in your image preferences.';
+$lang['imglib_image_process_failed'] = 'Image processing failed. Please verify that your server supports the chosen protocol and that the path to your image library is correct.';
+$lang['imglib_rotation_angle_required'] = 'An angle of rotation is required to rotate the image.';
+$lang['imglib_writing_failed_gif'] = 'GIF image.';
+$lang['imglib_invalid_path'] = 'The path to the image is not correct.';
+$lang['imglib_copy_failed'] = 'The image copy routine failed.';
+$lang['imglib_missing_font'] = 'Unable to find a font to use.';
+$lang['imglib_save_failed'] = 'Unable to save the image. Please make sure the image and file directory are writable.';
 
 /* End of file imglib_lang.php */
 /* Location: ./system/language/english/imglib_lang.php */
\ No newline at end of file
diff --git a/system/language/english/migration_lang.php b/system/language/english/migration_lang.php
index af92066..33bd026 100644
--- a/system/language/english/migration_lang.php
+++ b/system/language/english/migration_lang.php
@@ -24,15 +24,16 @@
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['migration_none_found']			= "No migrations were found.";
-$lang['migration_not_found']			= "No migration could be found with the version number: %d.";
-$lang['migration_multiple_version']		= "This are multiple migrations with the same version number: %d.";
-$lang['migration_class_doesnt_exist']	= "The migration class \"%s\" could not be found.";
-$lang['migration_missing_up_method']	= "The migration class \"%s\" is missing an 'up' method.";
-$lang['migration_missing_down_method']	= "The migration class \"%s\" is missing a 'down' method.";
-$lang['migration_invalid_filename']		= "Migration \"%s\" has an invalid filename.";
-
+$lang['migration_none_found']		= 'No migrations were found.';
+$lang['migration_not_found']		= 'No migration could be found with the version number: %d.';
+$lang['migration_sequence_gap']		= 'There is a gap in the migration sequence near version number: %d.';
+$lang['migration_multiple_version']	= 'There are multiple migrations with the same version number: %d.';
+$lang['migration_class_doesnt_exist']	= 'The migration class "%s" could not be found.';
+$lang['migration_missing_up_method']	= 'The migration class "%s" is missing an "up" method.';
+$lang['migration_missing_down_method']	= 'The migration class "%s" is missing a "down" method.';
+$lang['migration_invalid_filename']	= 'Migration "%s" has an invalid filename.';
 
 /* End of file migration_lang.php */
 /* Location: ./system/language/english/migration_lang.php */
\ No newline at end of file
diff --git a/system/language/english/number_lang.php b/system/language/english/number_lang.php
index 429c647..019013a 100644
--- a/system/language/english/number_lang.php
+++ b/system/language/english/number_lang.php
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 $lang['terabyte_abbr'] = 'TB';
 $lang['gigabyte_abbr'] = 'GB';
diff --git a/system/language/english/profiler_lang.php b/system/language/english/profiler_lang.php
index 112527f..6ffcd93 100644
--- a/system/language/english/profiler_lang.php
+++ b/system/language/english/profiler_lang.php
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 $lang['profiler_database']		= 'DATABASE';
 $lang['profiler_controller_info'] = 'CLASS/METHOD';
diff --git a/system/language/english/unit_test_lang.php b/system/language/english/unit_test_lang.php
index 36e9aca..ed45261 100644
--- a/system/language/english/unit_test_lang.php
+++ b/system/language/english/unit_test_lang.php
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 $lang['ut_test_name']		= 'Test Name';
 $lang['ut_test_datatype']	= 'Test Datatype';
@@ -45,6 +46,5 @@
 $lang['ut_null']			= 'Null';
 $lang['ut_notes']			= 'Notes';
 
-
 /* End of file unit_test_lang.php */
 /* Location: ./system/language/english/unit_test_lang.php */
\ No newline at end of file
diff --git a/system/language/english/upload_lang.php b/system/language/english/upload_lang.php
index c3cb9c3..26a5c54 100644
--- a/system/language/english/upload_lang.php
+++ b/system/language/english/upload_lang.php
@@ -24,24 +24,24 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
-$lang['upload_userfile_not_set'] = "Unable to find a post variable called userfile.";
-$lang['upload_file_exceeds_limit'] = "The uploaded file exceeds the maximum allowed size in your PHP configuration file.";
-$lang['upload_file_exceeds_form_limit'] = "The uploaded file exceeds the maximum size allowed by the submission form.";
-$lang['upload_file_partial'] = "The file was only partially uploaded.";
-$lang['upload_no_temp_directory'] = "The temporary folder is missing.";
-$lang['upload_unable_to_write_file'] = "The file could not be written to disk.";
-$lang['upload_stopped_by_extension'] = "The file upload was stopped by extension.";
-$lang['upload_no_file_selected'] = "You did not select a file to upload.";
-$lang['upload_invalid_filetype'] = "The filetype you are attempting to upload is not allowed.";
-$lang['upload_invalid_filesize'] = "The file you are attempting to upload is larger than the permitted size.";
-$lang['upload_invalid_dimensions'] = "The image you are attempting to upload exceeds the maximum height or width.";
-$lang['upload_destination_error'] = "A problem was encountered while attempting to move the uploaded file to the final destination.";
-$lang['upload_no_filepath'] = "The upload path does not appear to be valid.";
-$lang['upload_no_file_types'] = "You have not specified any allowed file types.";
-$lang['upload_bad_filename'] = "The file name you submitted already exists on the server.";
-$lang['upload_not_writable'] = "The upload destination folder does not appear to be writable.";
-
+$lang['upload_userfile_not_set'] = 'Unable to find a post variable called userfile.';
+$lang['upload_file_exceeds_limit'] = 'The uploaded file exceeds the maximum allowed size in your PHP configuration file.';
+$lang['upload_file_exceeds_form_limit'] = 'The uploaded file exceeds the maximum size allowed by the submission form.';
+$lang['upload_file_partial'] = 'The file was only partially uploaded.';
+$lang['upload_no_temp_directory'] = 'The temporary folder is missing.';
+$lang['upload_unable_to_write_file'] = 'The file could not be written to disk.';
+$lang['upload_stopped_by_extension'] = 'The file upload was stopped by extension.';
+$lang['upload_no_file_selected'] = 'You did not select a file to upload.';
+$lang['upload_invalid_filetype'] = 'The filetype you are attempting to upload is not allowed.';
+$lang['upload_invalid_filesize'] = 'The file you are attempting to upload is larger than the permitted size.';
+$lang['upload_invalid_dimensions'] = 'The image you are attempting to upload doesn\'t fit into the allowed dimensions.';
+$lang['upload_destination_error'] = 'A problem was encountered while attempting to move the uploaded file to the final destination.';
+$lang['upload_no_filepath'] = 'The upload path does not appear to be valid.';
+$lang['upload_no_file_types'] = 'You have not specified any allowed file types.';
+$lang['upload_bad_filename'] = 'The file name you submitted already exists on the server.';
+$lang['upload_not_writable'] = 'The upload destination folder does not appear to be writable.';
 
 /* End of file upload_lang.php */
 /* Location: ./system/language/english/upload_lang.php */
\ No newline at end of file
diff --git a/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php
index 4395cf4..48bd958 100644
--- a/system/libraries/Cache/Cache.php
+++ b/system/libraries/Cache/Cache.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Caching Class
@@ -41,13 +42,13 @@
 	 *
 	 * @var array
 	 */
-	protected $valid_drivers 	= array(
-		'cache_apc',
-		'cache_dummy',
-		'cache_file',
-		'cache_memcached',
-		'cache_redis',
-		'cache_wincache'
+	protected $valid_drivers = array(
+		'apc',
+		'dummy',
+		'file',
+		'memcached',
+		'redis',
+		'wincache'
 	);
 
 	/**
@@ -67,16 +68,23 @@
 	/**
 	 * Fallback driver
 	 *
-	 * @param string
+	 * @var string
 	 */
 	protected $_backup_driver = 'dummy';
 
 	/**
+	 * Cache key prefix
+	 *
+	 * @var	string
+	 */
+	public $key_prefix = '';
+
+	/**
 	 * Constructor
 	 *
 	 * Initialize class properties based on the configuration array.
 	 *
-	 * @param	array
+	 * @param	array	$config = array()
 	 * @return	void
 	 */
 	public function __construct($config = array())
@@ -96,12 +104,11 @@
 			}
 		}
 
-		if (isset($config['backup']))
+		isset($config['key_prefix']) && $this->key_prefix = $config['key_prefix'];
+
+		if (isset($config['backup']) && in_array('cache_'.$config['backup'], $this->valid_drivers))
 		{
-			if (in_array('cache_'.$config['backup'], $this->valid_drivers))
-			{
-				$this->_backup_driver = $config['backup'];
-			}
+			$this->_backup_driver = $config['backup'];
 		}
 
 		// If the specified adapter isn't available, check the backup.
@@ -129,12 +136,12 @@
 	 * Look for a value in the cache. If it exists, return the data
 	 * if not, return FALSE
 	 *
-	 * @param	string
-	 * @return	mixed	value that is stored/FALSE on failure
+	 * @param	string	$id
+	 * @return	mixed	value matching $id or FALSE on failure
 	 */
 	public function get($id)
 	{
-		return $this->{$this->_adapter}->get($id);
+		return $this->{$this->_adapter}->get($this->key_prefix.$id);
 	}
 
 	// ------------------------------------------------------------------------
@@ -142,14 +149,14 @@
 	/**
 	 * Cache Save
 	 *
-	 * @param	string	Unique Key
-	 * @param	mixed	Data to store
-	 * @param	int	Length of time (in seconds) to cache the data
-	 * @return	bool	true on success/false on failure
+	 * @param	string	$id		Cache ID
+	 * @param	mixed	$data		Data to store
+	 * @param	int	$ttl = 60	Cache TTL (in seconds)
+	 * @return	bool	TRUE on success, FALSE on failure
 	 */
 	public function save($id, $data, $ttl = 60)
 	{
-		return $this->{$this->_adapter}->save($id, $data, $ttl);
+		return $this->{$this->_adapter}->save($this->key_prefix.$id, $data, $ttl);
 	}
 
 	// ------------------------------------------------------------------------
@@ -157,12 +164,12 @@
 	/**
 	 * Delete from Cache
 	 *
-	 * @param	mixed	unique identifier of the item in the cache
-	 * @return	bool	true on success/false on failure
+	 * @param	string	$id	Cache ID
+	 * @return	bool	TRUE on success, FALSE on failure
 	 */
 	public function delete($id)
 	{
-		return $this->{$this->_adapter}->delete($id);
+		return $this->{$this->_adapter}->delete($this->key_prefix.$id);
 	}
 
 	// ------------------------------------------------------------------------
@@ -170,7 +177,7 @@
 	/**
 	 * Clean the cache
 	 *
-	 * @return	bool	false on failure/true on success
+	 * @return	bool	TRUE on success, FALSE on failure
 	 */
 	public function clean()
 	{
@@ -182,8 +189,8 @@
 	/**
 	 * Cache Info
 	 *
-	 * @param	string	user/filehits
-	 * @return	mixed	array on success, false on failure
+	 * @param	string	$type = 'user'	user/filehits
+	 * @return	mixed	array containing cache info on success OR FALSE on failure
 	 */
 	public function cache_info($type = 'user')
 	{
@@ -195,12 +202,12 @@
 	/**
 	 * Get Cache Metadata
 	 *
-	 * @param	mixed	key to get cache metadata on
-	 * @return	mixed	return value from child method
+	 * @param	string	$id	key to get cache metadata on
+	 * @return	mixed	cache item metadata
 	 */
 	public function get_metadata($id)
 	{
-		return $this->{$this->_adapter}->get_metadata($id);
+		return $this->{$this->_adapter}->get_metadata($this->key_prefix.$id);
 	}
 
 	// ------------------------------------------------------------------------
@@ -208,7 +215,7 @@
 	/**
 	 * Is the requested driver supported in this environment?
 	 *
-	 * @param	string	The driver to test.
+	 * @param	string	$driver	The driver to test
 	 * @return	array
 	 */
 	public function is_supported($driver)
diff --git a/system/libraries/Cache/drivers/Cache_apc.php b/system/libraries/Cache/drivers/Cache_apc.php
index c85034f..a9ad6ed 100644
--- a/system/libraries/Cache/drivers/Cache_apc.php
+++ b/system/libraries/Cache/drivers/Cache_apc.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter APC Caching Class
@@ -47,9 +48,11 @@
 	 */
 	public function get($id)
 	{
-		$data = apc_fetch($id);
+		$success = FALSE;
+		$data = apc_fetch($id, $success);
 
-		return is_array($data) ? $data[0] : FALSE;
+		return ($success === TRUE && is_array($data))
+			? unserialize($data[0]) : FALSE;
 	}
 
 	// ------------------------------------------------------------------------
@@ -66,7 +69,7 @@
 	public function save($id, $data, $ttl = 60)
 	{
 		$ttl = (int) $ttl;
-		return apc_store($id, array($data, time(), $ttl), $ttl);
+		return apc_store($id, array(serialize($data), time(), $ttl), $ttl);
 	}
 
 	// ------------------------------------------------------------------------
@@ -117,9 +120,10 @@
 	 */
 	public function get_metadata($id)
 	{
-		$stored = apc_fetch($id);
+		$success = FALSE;
+		$stored = apc_fetch($id, $success);
 
-		if (count($stored) !== 3)
+		if ($success === FALSE OR count($stored) !== 3)
 		{
 			return FALSE;
 		}
@@ -129,7 +133,7 @@
 		return array(
 			'expire'	=> $time + $ttl,
 			'mtime'		=> $time,
-			'data'		=> $data
+			'data'		=> unserialize($data)
 		);
 	}
 
diff --git a/system/libraries/Cache/drivers/Cache_dummy.php b/system/libraries/Cache/drivers/Cache_dummy.php
index 3f2b4b9..a3bdc3c 100644
--- a/system/libraries/Cache/drivers/Cache_dummy.php
+++ b/system/libraries/Cache/drivers/Cache_dummy.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Dummy Caching Class
diff --git a/system/libraries/Cache/drivers/Cache_file.php b/system/libraries/Cache/drivers/Cache_file.php
index 37d77c2..9fd0533 100644
--- a/system/libraries/Cache/drivers/Cache_file.php
+++ b/system/libraries/Cache/drivers/Cache_file.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter File Caching Class
@@ -73,7 +74,7 @@
 
 		$data = unserialize(file_get_contents($this->_cache_path.$id));
 
-		if ($data['ttl'] > 0 && time() >  $data['time'] + $data['ttl'])
+		if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl'])
 		{
 			unlink($this->_cache_path.$id);
 			return FALSE;
diff --git a/system/libraries/Cache/drivers/Cache_memcached.php b/system/libraries/Cache/drivers/Cache_memcached.php
index bf90f61..fae0023 100644
--- a/system/libraries/Cache/drivers/Cache_memcached.php
+++ b/system/libraries/Cache/drivers/Cache_memcached.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 2.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Memcached Caching Class
diff --git a/system/libraries/Cache/drivers/Cache_redis.php b/system/libraries/Cache/drivers/Cache_redis.php
index e4a26b5..6003334 100644
--- a/system/libraries/Cache/drivers/Cache_redis.php
+++ b/system/libraries/Cache/drivers/Cache_redis.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Redis Caching Class
diff --git a/system/libraries/Cache/drivers/Cache_wincache.php b/system/libraries/Cache/drivers/Cache_wincache.php
index 74048d5..9154f3f 100644
--- a/system/libraries/Cache/drivers/Cache_wincache.php
+++ b/system/libraries/Cache/drivers/Cache_wincache.php
@@ -1,8 +1,8 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
- * An open source application development framework for PHP 5.1.6 or newer
+ * An open source application development framework for PHP 5.2.4 or newer
  *
  * NOTICE OF LICENSE
  *
@@ -18,12 +18,13 @@
  *
  * @package		CodeIgniter
  * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2006 - 2012 EllisLab, Inc.
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
  * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  * @link		http://codeigniter.com
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Wincache Caching Class
@@ -126,10 +127,10 @@
 			$hitcount = $stored['ucache_entries'][1]['hitcount'];
 
 			return array(
-				'expire'    => $ttl - $age,
-				'hitcount'  => $hitcount,
-				'age'       => $age,
-				'ttl'       => $ttl
+				'expire'	=> $ttl - $age,
+				'hitcount'	=> $hitcount,
+				'age'		=> $age,
+				'ttl'		=> $ttl
 			);
 		}
 
diff --git a/system/libraries/Cache/drivers/index.html b/system/libraries/Cache/drivers/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/libraries/Cache/drivers/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/libraries/Cache/index.html b/system/libraries/Cache/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/libraries/Cache/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/libraries/Calendar.php b/system/libraries/Calendar.php
index a49f171..ec2b7bc 100644
--- a/system/libraries/Calendar.php
+++ b/system/libraries/Calendar.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Calendar Class
@@ -95,11 +96,13 @@
 	public $next_prev_url		= '';
 
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
-	 * Loads the calendar language file and sets the default time reference
+	 * Loads the calendar language file and sets the default time reference.
 	 *
-	 * @param	array
+	 * @uses	CI_Lang::$is_loaded
+	 *
+	 * @param	array	$config	Calendar options
 	 * @return	void
 	 */
 	public function __construct($config = array())
@@ -157,7 +160,7 @@
 		// Set and validate the supplied month/year
 		if (empty($year))
 		{
-			$year  = date('Y', $this->local_time);
+			$year = date('Y', $this->local_time);
 		}
 		elseif (strlen($year) === 1)
 		{
@@ -216,8 +219,8 @@
 		// "previous" month link
 		if ($this->show_next_prev === TRUE)
 		{
-			// Add a trailing slash to the  URL if needed
-			$this->next_prev_url = preg_replace('/(.+?)\/*$/', '\\1/',  $this->next_prev_url);
+			// Add a trailing slash to the URL if needed
+			$this->next_prev_url = preg_replace('/(.+?)\/*$/', '\\1/', $this->next_prev_url);
 
 			$adjusted_date = $this->adjust_date($month - 1, $year);
 			$out .= str_replace('{previous_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->temp['heading_previous_cell'])."\n";
@@ -441,7 +444,7 @@
 	 */
 	public function default_template()
 	{
-		return  array(
+		return array(
 			'table_open'				=> '<table border="0" cellpadding="4" cellspacing="0">',
 			'heading_row_start'			=> '<tr>',
 			'heading_previous_cell'		=> '<th><a href="{previous_url}">&lt;&lt;</a></th>',
@@ -487,7 +490,7 @@
 
 		$today = array('cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today');
 
-		foreach (array('table_open', 'table_close', 'heading_row_start', 'heading_previous_cell', 'heading_title_cell', 'heading_next_cell', 'heading_row_end', 'week_row_start', 'week_day_cell', 'week_row_end', 'cal_row_start', 'cal_cell_start', 'cal_cell_content', 'cal_cell_no_content',  'cal_cell_blank', 'cal_cell_end', 'cal_row_end', 'cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today') as $val)
+		foreach (array('table_open', 'table_close', 'heading_row_start', 'heading_previous_cell', 'heading_title_cell', 'heading_next_cell', 'heading_row_end', 'week_row_start', 'week_day_cell', 'week_row_end', 'cal_row_start', 'cal_cell_start', 'cal_cell_content', 'cal_cell_no_content', 'cal_cell_blank', 'cal_cell_end', 'cal_row_end', 'cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today') as $val)
 		{
 			if (preg_match('/\{'.$val.'\}(.*?)\{\/'.$val.'\}/si', $this->template, $match))
 			{
diff --git a/system/libraries/Cart.php b/system/libraries/Cart.php
index c442f88..79c2b8c 100644
--- a/system/libraries/Cart.php
+++ b/system/libraries/Cart.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Shopping Cart Class
@@ -98,7 +99,7 @@
 
 		// Grab the shopping cart array from the session table
 		$this->_cart_contents = $this->CI->session->userdata('cart_contents');
-		if ($this->_cart_contents === FALSE)
+		if ($this->_cart_contents === NULL)
 		{
 			// No cart exists so we'll set some base values
 			$this->_cart_contents = array('cart_total' => 0, 'total_items' => 0);
@@ -193,7 +194,7 @@
 		$items['qty'] = (float) $items['qty'];
 
 		// If the quantity is zero or blank there's nothing for us to do
-		if ( ! is_numeric($items['qty']) OR $items['qty'] == 0)
+		if ($items['qty'] == 0)
 		{
 			return FALSE;
 		}
@@ -224,15 +225,6 @@
 		// Prep the price. Remove leading zeros and anything that isn't a number or decimal point.
 		$items['price'] = (float) $items['price'];
 
-		// Is the price a valid number?
-		if ( ! is_numeric($items['price']))
-		{
-			log_message('error', 'An invalid price was submitted for product ID: '.$items['id']);
-			return FALSE;
-		}
-
-		// --------------------------------------------------------------------
-
 		// We now need to create a unique identifier for the item being inserted into the cart.
 		// Every time something is added to the cart it is stored in the master cart array.
 		// Each row in the cart array, however, must have a unique index that identifies not only
@@ -350,12 +342,6 @@
 		// Prep the quantity
 		$items['qty'] = (float) $items['qty'];
 
-		// Is the quantity a number?
-		if ( ! is_numeric($items['qty']))
-		{
-			return FALSE;
-		}
-
 		// Is the quantity zero?  If so we will remove the item from the cart.
 		// If the quantity is greater than zero we are updating
 		if ($items['qty'] == 0)
@@ -480,17 +466,34 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Get cart item
+	 *
+	 * Returns the details of a specific item in the cart
+	 *
+	 * @param	string	$row_id
+	 * @return	array
+	 */
+	public function get_item($row_id)
+	{
+		return (in_array($row_id, array('total_items', 'cart_total'), TRUE) OR ! isset($this->_cart_contents[$row_id]))
+			? FALSE
+			: $this->_cart_contents[$row_id];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Has options
 	 *
 	 * Returns TRUE if the rowid passed to this function correlates to an item
 	 * that has options associated with it.
 	 *
-	 * @param	mixed
+	 * @param	string	$row_id = ''
 	 * @return	bool
 	 */
-	public function has_options($rowid = '')
+	public function has_options($row_id = '')
 	{
-		return (isset($this->_cart_contents[$rowid]['options']) && count($this->_cart_contents[$rowid]['options']) !== 0);
+		return (isset($this->_cart_contents[$row_id]['options']) && count($this->_cart_contents[$row_id]['options']) !== 0);
 	}
 
 	// --------------------------------------------------------------------
@@ -500,12 +503,12 @@
 	 *
 	 * Returns the an array of options, for a particular product row ID
 	 *
-	 * @param	int
+	 * @param	string	$row_id = ''
 	 * @return	array
 	 */
-	public function product_options($rowid = '')
+	public function product_options($row_id = '')
 	{
-		return isset($this->_cart_contents[$rowid]['options']) ? $this->_cart_contents[$rowid]['options'] : array();
+		return isset($this->_cart_contents[$row_id]['options']) ? $this->_cart_contents[$row_id]['options'] : array();
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php
index d67ee25..8323e8f 100644
--- a/system/libraries/Driver.php
+++ b/system/libraries/Driver.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Driver Library Class
@@ -54,64 +55,128 @@
 	protected $lib_name;
 
 	/**
+	 * Get magic method
+	 *
 	 * The first time a child is used it won't exist, so we instantiate it
 	 * subsequents calls will go straight to the proper child.
 	 *
-	 * @param	mixed	$child
-	 * @return	mixed
+	 * @param	string	Child class name
+	 * @return	object	Child class
 	 */
 	public function __get($child)
 	{
+		// Try to load the driver
+		return $this->load_driver($child);
+	}
+
+	/**
+	 * Load driver
+	 *
+	 * Separate load_driver call to support explicit driver load by library or user
+	 *
+	 * @param	string	Driver name (w/o parent prefix)
+	 * @return	object	Child class
+	 */
+	public function load_driver($child)
+	{
+		// Get CodeIgniter instance and subclass prefix
+		$CI = get_instance();
+		$prefix = (string) $CI->config->item('subclass_prefix');
+
 		if ( ! isset($this->lib_name))
 		{
-			$this->lib_name = get_class($this);
+			// Get library name without any prefix
+			$this->lib_name = str_replace(array('CI_', $prefix), '', get_class($this));
 		}
 
-		// The class will be prefixed with the parent lib
-		$child_class = $this->lib_name.'_'.$child;
+		// The child will be prefixed with the parent lib
+		$child_name = $this->lib_name.'_'.$child;
 
-		// Remove the CI_ prefix and lowercase
-		$lib_name = ucfirst(strtolower(str_replace('CI_', '', $this->lib_name)));
-		$driver_name = strtolower(str_replace('CI_', '', $child_class));
-
-		if (in_array($driver_name, array_map('strtolower', $this->valid_drivers)))
+		// See if requested child is a valid driver
+		if ( ! in_array($child, $this->valid_drivers))
 		{
-			// check and see if the driver is in a separate file
-			if ( ! class_exists($child_class))
+			// The requested driver isn't valid!
+			$msg = 'Invalid driver requested: '.$child_name;
+			log_message('error', $msg);
+			show_error($msg);
+		}
+
+		// Get package paths and filename case variations to search
+		$paths = $CI->load->get_package_paths(TRUE);
+
+		// Is there an extension?
+		$class_name = $prefix.$child_name;
+		$found = class_exists($class_name);
+		if ( ! $found)
+		{
+			// Check for subclass file
+			foreach ($paths as $path)
 			{
-				// check application path first
-				foreach (get_instance()->load->get_package_paths(TRUE) as $path)
+				// Does the file exist?
+				$file = $path.'libraries/'.$this->lib_name.'/drivers/'.$prefix.$child_name.'.php';
+				if (file_exists($file))
 				{
-					// loves me some nesting!
-					foreach (array(ucfirst($driver_name), $driver_name) as $class)
+					// Yes - require base class from BASEPATH
+					$basepath = BASEPATH.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
+					if ( ! file_exists($basepath))
 					{
-						$filepath = $path.'libraries/'.$lib_name.'/drivers/'.$class.'.php';
-
-						if (file_exists($filepath))
-						{
-							include_once $filepath;
-							break 2;
-						}
+						$msg = 'Unable to load the requested class: CI_'.$child_name;
+						log_message('error', $msg);
+						show_error($msg);
 					}
-				}
 
-				// it's a valid driver, but the file simply can't be found
-				if ( ! class_exists($child_class))
-				{
-					log_message('error', 'Unable to load the requested driver: '.$child_class);
-					show_error('Unable to load the requested driver: '.$child_class);
+					// Include both sources and mark found
+					include($basepath);
+					include($file);
+					$found = TRUE;
+					break;
 				}
 			}
-
-			$obj = new $child_class;
-			$obj->decorate($this);
-			$this->$child = $obj;
-			return $this->$child;
 		}
 
-		// The requested driver isn't valid!
-		log_message('error', 'Invalid driver requested: '.$child_class);
-		show_error('Invalid driver requested: '.$child_class);
+		// Do we need to search for the class?
+		if ( ! $found)
+		{
+			// Use standard class name
+			$class_name = 'CI_'.$child_name;
+			$found = class_exists($class_name);
+			if ( ! $found)
+			{
+				// Check package paths
+				foreach ($paths as $path)
+				{
+					// Does the file exist?
+					$file = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
+					if (file_exists($file))
+					{
+						// Include source
+						include($file);
+						break;
+					}
+				}
+			}
+		}
+
+		// Did we finally find the class?
+		if ( ! class_exists($class_name))
+		{
+			if (class_exists($child_name))
+			{
+				$class_name = $child_name;
+			}
+			else
+			{
+				$msg = 'Unable to load the requested driver: '.$class_name;
+				log_message('error', $msg);
+				show_error($msg);
+			}
+		}
+
+		// Instantiate, decorate and add child
+		$obj = new $class_name();
+		$obj->decorate($this);
+		$this->$child = $obj;
+		return $this->$child;
 	}
 
 }
@@ -156,7 +221,8 @@
 	/**
 	 * Array of methods and properties for the parent class(es)
 	 *
-	 * @var array
+	 * @static
+	 * @var	array
 	 */
 	protected static $_reflections = array();
 
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index fdb9be4..5d8fc8a 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Email Class
@@ -38,66 +39,352 @@
  */
 class CI_Email {
 
+	/**
+	 * Used as the User-Agent and X-Mailer headers' value.
+	 *
+	 * @var	string
+	 */
 	public $useragent	= 'CodeIgniter';
-	public $mailpath	= '/usr/sbin/sendmail';	// Sendmail path
-	public $protocol	= 'mail';		// mail/sendmail/smtp
-	public $smtp_host	= '';			// SMTP Server. Example: mail.earthlink.net
-	public $smtp_user	= '';			// SMTP Username
-	public $smtp_pass	= '';			// SMTP Password
-	public $smtp_port	= 25;			// SMTP Port
-	public $smtp_timeout	= 5;			// SMTP Timeout in seconds
-	public $smtp_crypto	= '';			// SMTP Encryption. Can be null, tls or ssl.
-	public $wordwrap	= TRUE;			// TRUE/FALSE  Turns word-wrap on/off
-	public $wrapchars	= 76;			// Number of characters to wrap at.
-	public $mailtype	= 'text';		// text/html  Defines email formatting
-	public $charset		= 'utf-8';		// Default char set: iso-8859-1 or us-ascii
-	public $multipart	= 'mixed';		// "mixed" (in the body) or "related" (separate)
-	public $alt_message	= '';			// Alternative message for HTML emails
-	public $validate	= FALSE;		// TRUE/FALSE.  Enables email validation
-	public $priority	= 3;			// Default priority (1 - 5)
-	public $newline		= "\n";			// Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822)
-	public $crlf		= "\n";			// The RFC 2045 compliant CRLF for quoted-printable is "\r\n".  Apparently some servers,
-									// even on the receiving end think they need to muck with CRLFs, so using "\n", while
-									// distasteful, is the only thing that seems to work for all environments.
-	public $dsn		= FALSE;		// Delivery Status Notification
-	public $send_multipart	= TRUE;		// TRUE/FALSE - Yahoo does not like multipart alternative, so this is an override.  Set to FALSE for Yahoo.
-	public $bcc_batch_mode	= FALSE;	// TRUE/FALSE - Turns on/off Bcc batch feature
-	public $bcc_batch_size	= 200;		// If bcc_batch_mode = TRUE, sets max number of Bccs in each batch
 
+	/**
+	 * Path to the Sendmail binary.
+	 *
+	 * @var	string
+	 */
+	public $mailpath	= '/usr/sbin/sendmail';	// Sendmail path
+
+	/**
+	 * Which method to use for sending e-mails.
+	 *
+	 * @var	string	'mail', 'sendmail' or 'smtp'
+	 */
+	public $protocol	= 'mail';		// mail/sendmail/smtp
+
+	/**
+	 * STMP Server host
+	 *
+	 * @var	string
+	 */
+	public $smtp_host	= '';
+
+	/**
+	 * SMTP Username
+	 *
+	 * @var	string
+	 */
+	public $smtp_user	= '';
+
+	/**
+	 * SMTP Password
+	 *
+	 * @var	string
+	 */
+	public $smtp_pass	= '';
+
+	/**
+	 * SMTP Server port
+	 *
+	 * @var	int
+	 */
+	public $smtp_port	= 25;
+
+	/**
+	 * SMTP connection timeout in seconds
+	 *
+	 * @var	int
+	 */
+	public $smtp_timeout	= 5;
+
+	/**
+	 * SMTP Encryption
+	 *
+	 * @var	string	NULL, 'tls' or 'ssl'
+	 */
+	public $smtp_crypto	= NULL;
+
+	/**
+	 * Whether to apply word-wrapping to the message body.
+	 *
+	 * @var	bool
+	 */
+	public $wordwrap	= TRUE;
+
+	/**
+	 * Number of characters to wrap at.
+	 *
+	 * @see	CI_Email::$wordwrap
+	 * @var	int
+	 */
+	public $wrapchars	= 76;
+
+	/**
+	 * Message format.
+	 *
+	 * @var	string	'text' or 'html'
+	 */
+	public $mailtype	= 'text';
+
+	/**
+	 * Character set (default: utf-8)
+	 *
+	 * @var	string
+	 */
+	public $charset		= 'utf-8';
+
+	/**
+	 * Multipart message
+	 *
+	 * @var	string	'mixed' (in the body) or 'related' (separate)
+	 */
+	public $multipart	= 'mixed';		// "mixed" (in the body) or "related" (separate)
+
+	/**
+	 * Alternative message (for HTML messages only)
+	 *
+	 * @var	string
+	 */
+	public $alt_message	= '';
+
+	/**
+	 * Whether to validate e-mail addresses.
+	 *
+	 * @var	bool
+	 */
+	public $validate	= FALSE;
+
+	/**
+	 * X-Priority header value.
+	 *
+	 * @var	int	1-5
+	 */
+	public $priority	= 3;			// Default priority (1 - 5)
+
+	/**
+	 * Newline character sequence.
+	 * Use "\r\n" to comply with RFC 822.
+	 *
+	 * @link	http://www.ietf.org/rfc/rfc822.txt
+	 * @var	string	"\r\n" or "\n"
+	 */
+	public $newline		= "\n";			// Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822)
+
+	/**
+	 * CRLF character sequence
+	 *
+	 * RFC 2045 specifies that for 'quoted-printable' encoding,
+	 * "\r\n" must be used. However, it appears that some servers
+	 * (even on the receiving end) don't handle it properly and
+	 * switching to "\n", while improper, is the only solution
+	 * that seems to work for all environments.
+	 *
+	 * @link	http://www.ietf.org/rfc/rfc822.txt
+	 * @var	string
+	 */
+	public $crlf		= "\n";
+
+	/**
+	 * Whether to use Delivery Status Notification.
+	 *
+	 * @var	bool
+	 */
+	public $dsn		= FALSE;
+
+	/**
+	 * Whether to send multipart alternatives.
+	 * Yahoo! doesn't seem to like these.
+	 *
+	 * @var	bool
+	 */
+	public $send_multipart	= TRUE;
+
+	/**
+	 * Whether to send messages to BCC recipients in batches.
+	 *
+	 * @var	bool
+	 */
+	public $bcc_batch_mode	= FALSE;
+
+	/**
+	 * BCC Batch max number size.
+	 *
+	 * @see	CI_Email::$bcc_batch_mode
+	 * @var	int
+	 */
+	public $bcc_batch_size	= 200;
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Whether PHP is running in safe mode. Initialized by the class constructor.
+	 *
+	 * @var	bool
+	 */
 	protected $_safe_mode		= FALSE;
+
+	/**
+	 * Subject header
+	 *
+	 * @var	string
+	 */
 	protected $_subject		= '';
+
+	/**
+	 * Message body
+	 *
+	 * @var	string
+	 */
 	protected $_body		= '';
+
+	/**
+	 * Final message body to be sent.
+	 *
+	 * @var	string
+	 */
 	protected $_finalbody		= '';
+
+	/**
+	 * multipart/alternative boundary
+	 *
+	 * @var	string
+	 */
 	protected $_alt_boundary	= '';
+
+	/**
+	 * Attachment boundary
+	 *
+	 * @var	string
+	 */
 	protected $_atc_boundary	= '';
+
+	/**
+	 * Final headers to send
+	 *
+	 * @var	string
+	 */
 	protected $_header_str		= '';
+
+	/**
+	 * SMTP Connection socket placeholder
+	 *
+	 * @var	resource
+	 */
 	protected $_smtp_connect	= '';
+
+	/**
+	 * Mail encoding
+	 *
+	 * @var	string	'8bit' or '7bit'
+	 */
 	protected $_encoding		= '8bit';
-	protected $_IP			= FALSE;
+
+	/**
+	 * Whether to perform SMTP authentication
+	 *
+	 * @var	bool
+	 */
 	protected $_smtp_auth		= FALSE;
+
+	/**
+	 * Whether to send a Reply-To header
+	 *
+	 * @var	bool
+	 */
 	protected $_replyto_flag	= FALSE;
+
+	/**
+	 * Debug messages
+	 *
+	 * @see	CI_Email::print_debugger()
+	 * @var	string
+	 */
 	protected $_debug_msg		= array();
+
+	/**
+	 * Recipients
+	 *
+	 * @var	string[]
+	 */
 	protected $_recipients		= array();
+
+	/**
+	 * CC Recipients
+	 *
+	 * @var	string[]
+	 */
 	protected $_cc_array		= array();
+
+	/**
+	 * BCC Recipients
+	 *
+	 * @var	string[]
+	 */
 	protected $_bcc_array		= array();
+
+	/**
+	 * Message headers
+	 *
+	 * @var	string[]
+	 */
 	protected $_headers		= array();
-	protected $_attach_name		= array();
-	protected $_attach_type		= array();
-	protected $_attach_disp		= array();
+
+	/**
+	 * Attachment data
+	 *
+	 * @var	array
+	 */
+	protected $_attachments		= array();
+
+	/**
+	 * Valid $protocol values
+	 *
+	 * @see	CI_Email::$protocol
+	 * @var	string[]
+	 */
 	protected $_protocols		= array('mail', 'sendmail', 'smtp');
-	protected $_base_charsets	= array('us-ascii', 'iso-2022-');	// 7-bit charsets (excluding language suffix)
+
+	/**
+	 * Base charsets
+	 *
+	 * Character sets valid for 7-bit encoding,
+	 * excluding language suffix.
+	 *
+	 * @var	string[]
+	 */
+	protected $_base_charsets	= array('us-ascii', 'iso-2022-');
+
+	/**
+	 * Bit depths
+	 *
+	 * Valid mail encodings
+	 *
+	 * @see	CI_Email::$_encoding
+	 * @var	string[]
+	 */
 	protected $_bit_depths		= array('7bit', '8bit');
+
+	/**
+	 * $priority translations
+	 *
+	 * Actual values to send with the X-Priority header
+	 *
+	 * @var	string[]
+	 */
 	protected $_priorities		= array('1 (Highest)', '2 (High)', '3 (Normal)', '4 (Low)', '5 (Lowest)');
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor - Sets Email Preferences
 	 *
 	 * The constructor can be passed an array of config values
 	 *
+	 * @param	array	$config = array()
 	 * @return	void
 	 */
 	public function __construct($config = array())
 	{
+		$this->charset = config_item('charset');
+
 		if (count($config) > 0)
 		{
 			$this->initialize($config);
@@ -108,6 +395,8 @@
 			$this->_safe_mode = (bool) @ini_get('safe_mode');
 		}
 
+		$this->charset = strtoupper($this->charset);
+
 		log_message('debug', 'Email Class Initialized');
 	}
 
@@ -171,9 +460,7 @@
 
 		if ($clear_attachments !== FALSE)
 		{
-			$this->_attach_name = array();
-			$this->_attach_type = array();
-			$this->_attach_disp = array();
+			$this->_attachments = array();
 		}
 
 		return $this;
@@ -184,11 +471,12 @@
 	/**
 	 * Set FROM
 	 *
-	 * @param	string
-	 * @param	string
+	 * @param	string	$from
+	 * @param	string	$name
+	 * @param	string	$return_path = NULL	Return-Path
 	 * @return	object
 	 */
-	public function from($from, $name = '')
+	public function from($from, $name = '', $return_path = NULL)
 	{
 		if (preg_match('/\<(.*)\>/', $from, $match))
 		{
@@ -198,6 +486,10 @@
 		if ($this->validate)
 		{
 			$this->validate_email($this->_str_to_array($from));
+			if ($return_path)
+			{
+				$this->validate_email($this->_str_to_array($return_path));
+			}
 		}
 
 		// prepare the display name
@@ -211,12 +503,14 @@
 			}
 			else
 			{
-				$name = $this->_prep_q_encoding($name, TRUE);
+				$name = $this->_prep_q_encoding($name);
 			}
 		}
 
 		$this->set_header('From', $name.' <'.$from.'>');
-		$this->set_header('Return-Path', '<'.$from.'>');
+
+		isset($return_path) OR $return_path = $from;
+		$this->set_header('Return-Path', '<'.$return_path.'>');
 
 		return $this;
 	}
@@ -281,16 +575,7 @@
 			$this->set_header('To', implode(', ', $to));
 		}
 
-		switch ($this->_get_protocol())
-		{
-			case 'smtp':
-				$this->_recipients = $to;
-			break;
-			case 'sendmail':
-			case 'mail':
-				$this->_recipients = implode(', ', $to);
-			break;
-		}
+		$this->_recipients = $to;
 
 		return $this;
 	}
@@ -404,14 +689,20 @@
 	/**
 	 * Assign file attachments
 	 *
-	 * @param	string
+	 * @param	string	$filename
+	 * @param	string	$disposition = 'attachment'
+	 * @param	string	$newname = NULL
+	 * @param	string	$mime = ''
 	 * @return	object
 	 */
 	public function attach($filename, $disposition = '', $newname = NULL, $mime = '')
 	{
-		$this->_attach_name[] = array($filename, $newname);
-		$this->_attach_disp[] = empty($disposition) ? 'attachment' : $disposition; // Can also be 'inline'  Not sure if it matters
-		$this->_attach_type[] = $mime;
+		$this->_attachments[] = array(
+			'name'		=> array($filename, $newname),
+			'disposition'	=> empty($disposition) ? 'attachment' : $disposition,  // Can also be 'inline'  Not sure if it matters
+			'type' 		=> $mime
+		);
+
 		return $this;
 	}
 
@@ -629,9 +920,9 @@
 	{
 		if ($this->mailtype === 'html')
 		{
-			return (count($this->_attach_name) === 0) ? 'html' : 'html-attach';
+			return (count($this->_attachments) === 0) ? 'html' : 'html-attach';
 		}
-		elseif	($this->mailtype === 'text' && count($this->_attach_name) > 0)
+		elseif	($this->mailtype === 'text' && count($this->_attachments) > 0)
 		{
 			return 'plain-attach';
 		}
@@ -741,8 +1032,8 @@
 	/**
 	 * Build alternative plain text message
 	 *
-	 * This public function provides the raw message for use
-	 * in plain-text headers of HTML-formatted emails.
+	 * Provides the raw message for use in plain-text headers of
+	 * HTML-formatted emails.
 	 * If the user hasn't specified his own alternative message
 	 * it creates one by stripping the HTML
 	 *
@@ -750,9 +1041,11 @@
 	 */
 	protected function _get_alt_message()
 	{
-		if ($this->alt_message !== '')
+		if ( ! empty($this->alt_message))
 		{
-			return $this->word_wrap($this->alt_message, '76');
+			return ($this->wordwrap)
+				? $this->word_wrap($this->alt_message, 76)
+				: $this->alt_message;
 		}
 
 		$body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body;
@@ -763,7 +1056,12 @@
 			$body = str_replace(str_repeat("\n", $i), "\n\n", $body);
 		}
 
-		return $this->word_wrap($body, 76);
+		// Reduce multiple spaces
+		$body = preg_replace('| +|', ' ', $body);
+
+		return ($this->wordwrap)
+			? $this->word_wrap($body, 76)
+			: $body;
 	}
 
 	// --------------------------------------------------------------------
@@ -772,26 +1070,26 @@
 	 * Word Wrap
 	 *
 	 * @param	string
-	 * @param	int
+	 * @param	int	line-length limit
 	 * @return	string
 	 */
-	public function word_wrap($str, $charlim = '')
+	public function word_wrap($str, $charlim = NULL)
 	{
-		// Se the character limit
-		if ($charlim === '')
+		// Set the character limit, if not already present
+		if (empty($charlim))
 		{
-			$charlim = ($this->wrapchars === '') ? 76 : $this->wrapchars;
+			$charlim = empty($this->wrapchars) ? 76 : $this->wrapchars;
 		}
 
-		// Reduce multiple spaces
-		$str = preg_replace('| +|', ' ', $str);
-
 		// Standardize newlines
 		if (strpos($str, "\r") !== FALSE)
 		{
 			$str = str_replace(array("\r\n", "\r"), "\n", $str);
 		}
 
+		// Reduce multiple spaces at end of line
+		$str = preg_replace('| +\n|', "\n", $str);
+
 		// If the current word is surrounded by {unwrap} tags we'll
 		// strip the entire chunk and replace it with a marker.
 		$unwrap = array();
@@ -971,7 +1269,6 @@
 
 				$this->_finalbody = $body.$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline;
 
-
 				if ($this->_get_protocol() === 'mail')
 				{
 					$this->_header_str .= $hdr;
@@ -981,7 +1278,6 @@
 					$this->_finalbody = $hdr.$this->_finalbody;
 				}
 
-
 				if ($this->send_multipart !== FALSE)
 				{
 					$this->_finalbody .= '--'.$this->_alt_boundary.'--';
@@ -1036,14 +1332,15 @@
 		}
 
 		$attachment = array();
-		for ($i = 0, $c = count($this->_attach_name), $z = 0; $i < $c; $i++)
+		for ($i = 0, $c = count($this->_attachments), $z = 0; $i < $c; $i++)
 		{
-			$filename = $this->_attach_name[$i][0];
-			$basename = is_null($this->_attach_name[$i][1]) ? basename($filename) : $this->_attach_name[$i][1];
-			$ctype = $this->_attach_type[$i];
+			$filename = $this->_attachments[$i]['name'][0];
+			$basename = is_null($this->_attachments[$i]['name'][1])
+				? basename($filename) : $this->_attachments[$i]['name'][1];
+			$ctype = $this->_attachments[$i]['type'];
 			$file_content = '';
 
-			if ($this->_attach_type[$i] === '')
+			if ($ctype === '')
 			{
 				if ( ! file_exists($filename))
 				{
@@ -1065,13 +1362,13 @@
 			}
 			else
 			{
-				$file_content =& $this->_attach_content[$i];
+				$file_content =& $this->_attachments[$i]['name'][0];
 			}
 
 			$attachment[$z++] = '--'.$this->_atc_boundary.$this->newline
 				.'Content-type: '.$ctype.'; '
 				.'name="'.$basename.'"'.$this->newline
-				.'Content-Disposition: '.$this->_attach_disp[$i].';'.$this->newline
+				.'Content-Disposition: '.$this->_attachments[$i]['disposition'].';'.$this->newline
 				.'Content-Transfer-Encoding: base64'.$this->newline;
 
 			$attachment[$z++] = chunk_split(base64_encode($file_content));
@@ -1091,17 +1388,28 @@
 	 * Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt
 	 *
 	 * @param	string
-	 * @param	int
 	 * @return	string
 	 */
-	protected function _prep_quoted_printable($str, $charlim = '')
+	protected function _prep_quoted_printable($str)
 	{
-		// Set the character limit
-		// Don't allow over 76, as that will make servers and MUAs barf
-		// all over quoted-printable data
-		if ($charlim === '' OR $charlim > 76)
+		// We are intentionally wrapping so mail servers will encode characters
+		// properly and MUAs will behave, so {unwrap} must go!
+		$str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
+
+		// RFC 2045 specifies CRLF as "\r\n".
+		// However, many developers choose to override that and violate
+		// the RFC rules due to (apparently) a bug in MS Exchange,
+		// which only works with "\n".
+		if ($this->crlf === "\r\n")
 		{
-			$charlim = 76;
+			if (is_php('5.3'))
+			{
+				return quoted_printable_encode($str);
+			}
+			elseif (function_exists('imap_8bit'))
+			{
+				return imap_8bit($str);
+			}
 		}
 
 		// Reduce multiple spaces & remove nulls
@@ -1113,10 +1421,6 @@
 			$str = str_replace(array("\r\n", "\r"), "\n", $str);
 		}
 
-		// We are intentionally wrapping so mail servers will encode characters
-		// properly and MUAs will behave, so {unwrap} must go!
-		$str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
-
 		$escape = '=';
 		$output = '';
 
@@ -1146,7 +1450,7 @@
 
 				// If we're at the character limit, add the line to the output,
 				// reset our temp variable, and keep on chuggin'
-				if ((strlen($temp) + strlen($char)) >= $charlim)
+				if ((strlen($temp) + strlen($char)) >= 76)
 				{
 					$output .= $temp.$escape.$this->crlf;
 					$temp = '';
@@ -1169,66 +1473,75 @@
 	/**
 	 * Prep Q Encoding
 	 *
-	 * Performs "Q Encoding" on a string for use in email headers.  It's related
-	 * but not identical to quoted-printable, so it has its own method
+	 * Performs "Q Encoding" on a string for use in email headers.
+	 * It's related but not identical to quoted-printable, so it has its
+	 * own method.
 	 *
 	 * @param	string
-	 * @param	bool	set to TRUE for processing From: headers
 	 * @return	string
 	 */
-	protected function _prep_q_encoding($str, $from = FALSE)
+	protected function _prep_q_encoding($str)
 	{
-		$str = str_replace(array("\r", "\n"), array('', ''), $str);
+		$str = str_replace(array("\r", "\n"), '', $str);
 
-		// Line length must not exceed 76 characters, so we adjust for
-		// a space, 7 extra characters =??Q??=, and the charset that we will add to each line
-		$limit = 75 - 7 - strlen($this->charset);
-
-		// these special characters must be converted too
-		$convert = array('_', '=', '?');
-
-		if ($from === TRUE)
+		if ($this->charset === 'UTF-8')
 		{
-			$convert[] = ',';
-			$convert[] = ';';
+			if (MB_ENABLED === TRUE)
+			{
+				return mb_encode_mimeheader($str, $this->charset, 'Q', $this->crlf);
+			}
+			elseif (extension_loaded('iconv'))
+			{
+				$output = @iconv_mime_encode('', $str,
+					array(
+						'scheme' => 'Q',
+						'line-length' => 76,
+						'input-charset' => $this->charset,
+						'output-charset' => $this->charset,
+						'line-break-chars' => $this->crlf
+					)
+				);
+
+				// There are reports that iconv_mime_encode() might fail and return FALSE
+				if ($output !== FALSE)
+				{
+					// iconv_mime_encode() will always put a header field name.
+					// We've passed it an empty one, but it still prepends our
+					// encoded string with ': ', so we need to strip it.
+					return substr($output, 2);
+				}
+
+				$chars = iconv_strlen($str, 'UTF-8');
+			}
 		}
 
-		$output = '';
-		$temp = '';
+		// We might already have this set for UTF-8
+		isset($chars) OR $chars = strlen($str);
 
-		for ($i = 0, $length = strlen($str); $i < $length; $i++)
+		$output = '=?'.$this->charset.'?Q?';
+		for ($i = 0, $length = strlen($output), $iconv = extension_loaded('iconv'); $i < $chars; $i++)
 		{
-			// Grab the next character
-			$char = $str[$i];
-			$ascii = ord($char);
+			$chr = ($this->charset === 'UTF-8' && $iconv === TRUE)
+				? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2))
+				: '='.strtoupper(bin2hex($str[$i]));
 
-			// convert ALL non-printable ASCII characters and our specials
-			if ($ascii < 32 OR $ascii > 126 OR in_array($char, $convert))
+			// RFC 2045 sets a limit of 76 characters per line.
+			// We'll append ?= to the end of each line though.
+			if ($length + ($l = strlen($chr)) > 74)
 			{
-				$char = '='.dechex($ascii);
+				$output .= '?='.$this->crlf // EOL
+					.' =?'.$this->charset.'?Q?'.$chr; // New line
+				$length = 6 + strlen($this->charset) + $l; // Reset the length for the new line
 			}
-
-			// handle regular spaces a bit more compactly than =20
-			if ($ascii === 32)
+			else
 			{
-				$char = '_';
+				$output .= $chr;
+				$length += $l;
 			}
-
-			// If we're at the character limit, add the line to the output,
-			// reset our temp variable, and keep on chuggin'
-			if ((strlen($temp) + strlen($char)) >= $limit)
-			{
-				$output .= $temp.$this->crlf;
-				$temp = '';
-			}
-
-			// Add the character to our temporary line
-			$temp .= $char;
 		}
 
-		// wrap each line with the shebang, charset, and transfer encoding
-		// the preceding space on successive lines is required for header "folding"
-		return trim(preg_replace('/^(.*)$/m', ' =?'.$this->charset.'?Q?$1?=', $output.$temp));
+		// End the header
+		return $output.'?=';
 	}
 
 	// --------------------------------------------------------------------
@@ -1236,9 +1549,10 @@
 	/**
 	 * Send Email
 	 *
+	 * @param	bool	$auto_clear = TRUE
 	 * @return	bool
 	 */
-	public function send()
+	public function send($auto_clear = TRUE)
 	{
 		if ($this->_replyto_flag === FALSE)
 		{
@@ -1257,11 +1571,25 @@
 
 		if ($this->bcc_batch_mode && count($this->_bcc_array) > $this->bcc_batch_size)
 		{
-			return $this->batch_bcc_send();
+			$result = $this->batch_bcc_send();
+
+			if ($result && $auto_clear)
+			{
+				$this->clear();
+			}
+
+			return $result;
 		}
 
 		$this->_build_message();
-		return $this->_spool_email();
+		$result = $this->_spool_email();
+
+		if ($result && $auto_clear)
+		{
+			$this->clear();
+		}
+
+		return $result;
 	}
 
 	// --------------------------------------------------------------------
@@ -1334,6 +1662,7 @@
 	/**
 	 * Strip line-breaks via callback
 	 *
+	 * @param	string	$matches
 	 * @return	string
 	 */
 	protected function _remove_nl_callback($matches)
@@ -1377,6 +1706,11 @@
 	 */
 	protected function _send_with_mail()
 	{
+		if (is_array($this->_recipients))
+		{
+			$this->_recipients = implode(', ', $this->_recipients);
+		}
+
 		if ($this->_safe_mode === TRUE)
 		{
 			return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str);
@@ -1385,7 +1719,7 @@
 		{
 			// most documentation of sendmail using the "-f" flag lacks a space after it, however
 			// we've encountered servers that seem to require it to be in place.
-			return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, '-f '.$this->clean_email($this->_headers['From']));
+			return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, '-f '.$this->clean_email($this->_headers['Return-Path']));
 		}
 	}
 
@@ -1398,11 +1732,14 @@
 	 */
 	protected function _send_with_sendmail()
 	{
-		$fp = @popen($this->mailpath.' -oi -f '.$this->clean_email($this->_headers['From']).' -t', 'w');
-
-		if ($fp === FALSE OR $fp === NULL)
+		// is popen() enabled?
+		if ( ! function_usable('popen')
+			OR FALSE === ($fp = @popen(
+						$this->mailpath.' -oi -f '.$this->clean_email($this->_headers['From'])
+							.' -t -r '.$this->clean_email($this->_headers['Return-Path'])
+						, 'w'))
+		) // server probably has popen disabled, so nothing we can do to get a verbose error.
 		{
-			// server probably has popen disabled, so nothing we can do to get a verbose error.
 			return FALSE;
 		}
 
@@ -1496,7 +1833,6 @@
 	/**
 	 * SMTP Connect
 	 *
-	 * @param	string
 	 * @return	string
 	 */
 	protected function _smtp_connect()
@@ -1599,7 +1935,7 @@
 
 		$this->_debug_msg[] = '<pre>'.$cmd.': '.$reply.'</pre>';
 
-		if ( (int) substr($reply, 0, 3) !== $resp)
+		if ((int) substr($reply, 0, 3) !== $resp)
 		{
 			$this->_set_error_message('lang:email_smtp_error', $reply);
 			return FALSE;
@@ -1616,7 +1952,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 *  SMTP Authenticate
+	 * SMTP Authenticate
 	 *
 	 * @return	bool
 	 */
@@ -1671,11 +2007,12 @@
 	/**
 	 * Send SMTP data
 	 *
+	 * @param	string	$data
 	 * @return	bool
 	 */
 	protected function _send_data($data)
 	{
-		if ( ! fwrite($this->_smtp_connect, $data . $this->newline))
+		if ( ! fwrite($this->_smtp_connect, $data.$this->newline))
 		{
 			$this->_set_error_message('lang:email_smtp_data_failure', $data);
 			return FALSE;
@@ -1723,52 +2060,13 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Get IP
-	 *
-	 * @return	string
-	 */
-	protected function _get_ip()
-	{
-		if ($this->_IP !== FALSE)
-		{
-			return $this->_IP;
-		}
-
-		$cip = ( ! empty($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : FALSE;
-		$rip = ( ! empty($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : FALSE;
-		if ($cip) $this->_IP = $cip;
-		elseif ($rip) $this->_IP = $rip;
-		else
-		{
-			$fip = ( ! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : FALSE;
-			if ($fip)
-			{
-				$this->_IP = $fip;
-			}
-		}
-
-		if (strpos($this->_IP, ',') !== FALSE)
-		{
-			$x = explode(',', $this->_IP);
-			$this->_IP = end($x);
-		}
-
-		if ( ! preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->_IP))
-		{
-			$this->_IP = '0.0.0.0';
-		}
-
-		return $this->_IP;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Get Debug Message
 	 *
+	 * @param	array	$include	List of raw data chunks to include in the output
+	 *					Valid options are: 'headers', 'subject', 'body'
 	 * @return	string
 	 */
-	public function print_debugger()
+	public function print_debugger($include = array('headers', 'subject', 'body'))
 	{
 		$msg = '';
 
@@ -1780,7 +2078,26 @@
 			}
 		}
 
-		return $msg.'<pre>'.$this->_header_str."\n".htmlspecialchars($this->_subject)."\n".htmlspecialchars($this->_finalbody).'</pre>';
+		// Determine which parts of our raw data needs to be printed
+		$raw_data = '';
+		is_array($include) OR $include = array($include);
+
+		if (in_array('headers', $include, TRUE))
+		{
+			$raw_data = $this->_header_str."\n";
+		}
+
+		if (in_array('subject', $include, TRUE))
+		{
+			$raw_data .= htmlspecialchars($this->_subject)."\n";
+		}
+
+		if (in_array('body', $include, TRUE))
+		{
+			$raw_data .= htmlspecialchars($this->_finalbody);
+		}
+
+		return $msg.($raw_data === '' ? '' : '<pre>'.$raw_data.'</pre>');
 	}
 
 	// --------------------------------------------------------------------
@@ -1788,7 +2105,8 @@
 	/**
 	 * Set Message
 	 *
-	 * @param	string
+	 * @param	string	$msg
+	 * @param	string	$val = ''
 	 * @return	void
 	 */
 	protected function _set_error_message($msg, $val = '')
@@ -1796,7 +2114,7 @@
 		$CI =& get_instance();
 		$CI->lang->load('email');
 
-		if (substr($msg, 0, 5) !== 'lang:' OR FALSE === ($line = $CI->lang->line(substr($msg, 5))))
+		if (sscanf($msg, 'lang:%s', $line) !== 1 OR FALSE === ($line = $CI->lang->line($line)))
 		{
 			$this->_debug_msg[] = str_replace('%s', $val, $msg).'<br />';
 		}
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
index 8ffd93a..cdb0a64 100644
--- a/system/libraries/Encrypt.php
+++ b/system/libraries/Encrypt.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Encryption Class
@@ -165,7 +166,7 @@
 	 */
 	public function decode($string, $key = '')
 	{
-		if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string))
+		if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string) OR base64_encode(base64_decode($string)) !== $string)
 		{
 			return FALSE;
 		}
@@ -484,7 +485,7 @@
 	 */
 	public function set_hash($type = 'sha1')
 	{
-		$this->_hash_type = ($type !== 'sha1' && $type !== 'md5') ? 'sha1' : $type;
+		$this->_hash_type = in_array($type, hash_algos()) ? $type : 'sha1';
 	}
 
 	// --------------------------------------------------------------------
@@ -497,7 +498,7 @@
 	 */
 	public function hash($str)
 	{
-		return ($this->_hash_type === 'sha1') ? sha1($str) : md5($str);
+		return hash($this->_hash_type, $str);
 	}
 
 }
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index 3536241..6853425 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Form Validation Class
@@ -104,7 +105,7 @@
 	 *
 	 * @var array
 	 */
-	protected $validation_data	= array();
+	public $validation_data	= array();
 
 	/**
 	 * Initialize Form_Validation class
@@ -134,12 +135,6 @@
 		// Automatically load the form helper
 		$this->CI->load->helper('form');
 
-		// Set the character encoding in MB.
-		if (MB_ENABLED === TRUE)
-		{
-			mb_internal_encoding($this->CI->config->item('charset'));
-		}
-
 		log_message('debug', 'Form Validation Class Initialized');
 	}
 
@@ -204,12 +199,10 @@
 
 		// Is the field name an array? If it is an array, we break it apart
 		// into its components so that we can fetch the corresponding POST data later
+		$indexes = array();
 		if (preg_match_all('/\[(.*?)\]/', $field, $matches))
 		{
-			// Note: Due to a bug in current() that affects some versions
-			// of PHP we can not pass function call directly into it
-			$x = explode('[', $field);
-			$indexes[] = current($x);
+			sscanf($field, '%[^[][', $indexes[0]);
 
 			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
 			{
@@ -223,7 +216,6 @@
 		}
 		else
 		{
-			$indexes	= array();
 			$is_array	= FALSE;
 		}
 
@@ -445,11 +437,10 @@
 		// Load the language file containing error messages
 		$this->CI->lang->load('form_validation');
 
-		// Cycle through the rules for each field, match the
-		// corresponding $_POST item and test for errors
+		// Cycle through the rules for each field and match the corresponding $validation_data item
 		foreach ($this->_field_data as $field => $row)
 		{
-			// Fetch the data from the corresponding $_POST or validation array and cache it in the _field_data array.
+			// Fetch the data from the validation_data array item and cache it in the _field_data array.
 			// Depending on whether the field name is an array or a string will determine where we get it from.
 			if ($row['is_array'] === TRUE)
 			{
@@ -459,6 +450,18 @@
 			{
 				$this->_field_data[$field]['postdata'] = $validation_array[$field];
 			}
+		}
+
+		// Execute validation rules
+		// Note: A second foreach (for now) is required in order to avoid false-positives
+		//	 for rules like 'matches', which correlate to other validation fields.
+		foreach ($this->_field_data as $field => $row)
+		{
+			// Don't try to validate if we have no rules set
+			if (empty($row['rules']))
+			{
+				continue;
+			}
 
 			$this->_execute($row, explode('|', $row['rules']), $this->_field_data[$field]['postdata']);
 		}
@@ -493,7 +496,8 @@
 			return isset($array[$keys[$i]]) ? $this->_reduce_array($array[$keys[$i]], $keys, ($i+1)) : NULL;
 		}
 
-		return $array;
+		// NULL must be returned for empty fields
+		return ($array === '') ? NULL : $array;
 	}
 
 	// --------------------------------------------------------------------
@@ -605,13 +609,15 @@
 				{
 					$line = $this->_error_messages[$type];
 				}
-				elseif (FALSE === ($line = $this->CI->lang->line($type)))
+				elseif (FALSE === ($line = $this->CI->lang->line('form_validation_'.$type))
+					// DEPRECATED support for non-prefixed keys
+					&& FALSE === ($line = $this->CI->lang->line($type, FALSE)))
 				{
 					$line = 'The field was not set';
 				}
 
 				// Build the error message
-				$message = sprintf($line, $this->_translate_fieldname($row['label']));
+				$message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']));
 
 				// Save the error message
 				$this->_field_data[$row['field']]['error'] = $message;
@@ -669,8 +675,8 @@
 			$param = FALSE;
 			if (preg_match('/(.*?)\[(.*)\]/', $rule, $match))
 			{
-				$rule	= $match[1];
-				$param	= $match[2];
+				$rule = $match[1];
+				$param = $match[2];
 			}
 
 			// Call the function that corresponds to the rule
@@ -745,7 +751,9 @@
 			{
 				if ( ! isset($this->_error_messages[$rule]))
 				{
-					if (FALSE === ($line = $this->CI->lang->line($rule)))
+					if (FALSE === ($line = $this->CI->lang->line('form_validation_'.$rule))
+						// DEPRECATED support for non-prefixed keys
+						&& FALSE === ($line = $this->CI->lang->line($rule, FALSE)))
 					{
 						$line = 'Unable to access an error message corresponding to your field name.';
 					}
@@ -763,7 +771,7 @@
 				}
 
 				// Build the error message
-				$message = sprintf($line, $this->_translate_fieldname($row['label']), $param);
+				$message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param);
 
 				// Save the error message
 				$this->_field_data[$row['field']]['error'] = $message;
@@ -790,13 +798,12 @@
 	{
 		// Do we need to translate the field name?
 		// We look for the prefix lang: to determine this
-		if (strpos($fieldname, 'lang:') === 0)
+		if (sscanf($fieldname, 'lang:%s', $line) === 1)
 		{
-			// Grab the variable
-			$line = substr($fieldname, 5);
-
 			// Were we able to translate the field name?  If not we use $line
-			if (FALSE === ($fieldname = $this->CI->lang->line($line)))
+			if (FALSE === ($fieldname = $this->CI->lang->line('form_validation_'.$line))
+				// DEPRECATED support for non-prefixed keys
+				&& FALSE === ($fieldname = $this->CI->lang->line($line, FALSE)))
 			{
 				return $line;
 			}
@@ -808,6 +815,27 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Build an error message using the field and param.
+	 *
+	 * @param	string	The error message line
+	 * @param	string	A field's human name
+	 * @param	mixed	A rule's optional parameter
+	 * @return	string
+	 */
+	protected function _build_error_msg($line, $field = '', $param = '')
+	{
+		// Check for %s in the string for legacy support.
+		if (strpos($line, '%s') !== FALSE)
+		{
+			return sprintf($line, $field, $param);
+		}
+		
+		return str_replace(array('{field}', '{param}'), array($field, $param), $line);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Get the value from a form
 	 *
 	 * Permits you to repopulate a form field with the value it was submitted
@@ -957,15 +985,29 @@
 	/**
 	 * Match one field to another
 	 *
-	 * @param	string
-	 * @param	string	field
+	 * @param	string	$str	string to compare against
+	 * @param	string	$field
 	 * @return	bool
 	 */
 	public function matches($str, $field)
 	{
-		$validation_array = empty($this->validation_data) ? $_POST : $this->validation_data;
+		return isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])
+			? ($str === $this->_field_data[$field]['postdata'])
+			: FALSE;
+	}
 
-		return isset($validation_array[$field]) ? ($str === $validation_array[$field]) : FALSE;
+	// --------------------------------------------------------------------
+
+	/**
+	 * Differs from another field
+	 *
+	 * @param	string
+	 * @param	string	field
+	 * @return	bool
+	 */
+	public function differs($str, $field)
+	{
+		return ! (isset($this->_field_data[$field]) && $this->_field_data[$field]['postdata'] === $str);
 	}
 
 	// --------------------------------------------------------------------
@@ -982,7 +1024,7 @@
 	 */
 	public function is_unique($str, $field)
 	{
-		list($table, $field) = explode('.', $field);
+		sscanf($field, '%[^.].%[^.]', $table, $field);
 		if (isset($this->CI->db))
 		{
 			$query = $this->CI->db->limit(1)->get_where($table, array($field => $str));
@@ -1069,6 +1111,48 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Valid URL
+	 *
+	 * @param	string	$str
+	 * @return	bool
+	 */
+	public function valid_url($str)
+	{
+		if (empty($str))
+		{
+			return FALSE;
+		}
+		elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches))
+		{
+			if (empty($matches[2]))
+			{
+				return FALSE;
+			}
+			elseif ( ! in_array($matches[1], array('http', 'https'), TRUE))
+			{
+				return FALSE;
+			}
+
+			$str = $matches[2];
+		}
+
+		$str = 'http://'.$str;
+
+		// There's a bug affecting PHP 5.2.13, 5.3.2 that considers the
+		// underscore to be a valid hostname character instead of a dash.
+		// Reference: https://bugs.php.net/bug.php?id=51192
+		if (version_compare(PHP_VERSION, '5.2.13', '==') === 0 OR version_compare(PHP_VERSION, '5.3.2', '==') === 0)
+		{
+			sscanf($str, 'http://%[^/]', $host);
+			$str = substr_replace($str, strtr($host, array('_' => '-', '-' => '_')), 7, strlen($host));
+		}
+
+		return (filter_var($str, FILTER_VALIDATE_URL) !== FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Valid Email
 	 *
 	 * @param	string
@@ -1309,6 +1393,11 @@
 	 */
 	public function prep_for_form($data = '')
 	{
+		if ($this->_safe_form_data === FALSE OR empty($data))
+		{
+			return $data;
+		}
+
 		if (is_array($data))
 		{
 			foreach ($data as $key => $val)
@@ -1319,11 +1408,6 @@
 			return $data;
 		}
 
-		if ($this->_safe_form_data === FALSE OR $data === '')
-		{
-			return $data;
-		}
-
 		return str_replace(array("'", '"', '<', '>'), array('&#39;', '&quot;', '&lt;', '&gt;'), stripslashes($data));
 	}
 
@@ -1386,7 +1470,7 @@
 	 */
 	public function encode_php_tags($str)
 	{
-		return str_replace(array('<?', '?>'),  array('&lt;?', '?&gt;'), $str);
+		return str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $str);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Ftp.php b/system/libraries/Ftp.php
index 76f5e15..7f58fee 100644
--- a/system/libraries/Ftp.php
+++ b/system/libraries/Ftp.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * FTP Class
@@ -36,14 +37,65 @@
  */
 class CI_FTP {
 
+	/**
+	 * FTP Server hostname
+	 *
+	 * @var	string
+	 */
 	public $hostname	= '';
+
+	/**
+	 * FTP Username
+	 *
+	 * @var	string
+	 */
 	public $username	= '';
+
+	/**
+	 * FTP Password
+	 *
+	 * @var	string
+	 */
 	public $password	= '';
+
+	/**
+	 * FTP Server port
+	 *
+	 * @var	int
+	 */
 	public $port		= 21;
+
+	/**
+	 * Passive mode flag
+	 *
+	 * @var	bool
+	 */
 	public $passive		= TRUE;
+
+	/**
+	 * Debug flag
+	 *
+	 * Specifies whether to display error messages.
+	 *
+	 * @var	bool
+	 */
 	public $debug		= FALSE;
+
+	/**
+	 * Connection
+	 *
+	 * @var	resource
+	 */
 	public $conn_id		= FALSE;
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	array	$config
+	 * @return	void
+	 */
 	public function __construct($config = array())
 	{
 		if (count($config) > 0)
@@ -59,7 +111,7 @@
 	/**
 	 * Initialize preferences
 	 *
-	 * @param	array
+	 * @param	array	$config
 	 * @return	void
 	 */
 	public function initialize($config = array())
@@ -81,7 +133,7 @@
 	/**
 	 * FTP Connect
 	 *
-	 * @param	array	 the connection values
+	 * @param	array	 $config	Connection values
 	 * @return	bool
 	 */
 	public function connect($config = array())
@@ -161,8 +213,8 @@
 	 * so we do it by trying to change to a particular directory.
 	 * Internally, this parameter is only used by the "mirror" function below.
 	 *
-	 * @param	string
-	 * @param	bool
+	 * @param	string	$path
+	 * @param	bool	$supress_debug
 	 * @return	bool
 	 */
 	public function changedir($path = '', $supress_debug = FALSE)
@@ -191,8 +243,8 @@
 	/**
 	 * Create a directory
 	 *
-	 * @param	string
-	 * @param	int
+	 * @param	string	$path
+	 * @param	int	$permissions
 	 * @return	bool
 	 */
 	public function mkdir($path = '', $permissions = NULL)
@@ -227,10 +279,10 @@
 	/**
 	 * Upload a file to the server
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	string
-	 * @param	int
+	 * @param	string	$locpath
+	 * @param	string	$rempath
+	 * @param	string	$mode
+	 * @param	int	$permissions
 	 * @return	bool
 	 */
 	public function upload($locpath, $rempath, $mode = 'auto', $permissions = NULL)
@@ -281,9 +333,9 @@
 	/**
 	 * Download a file from a remote server to the local server
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	string
+	 * @param	string	$rempath
+	 * @param	string	$locpath
+	 * @param	string	$mode
 	 * @return	bool
 	 */
 	public function download($rempath, $locpath, $mode = 'auto')
@@ -322,9 +374,9 @@
 	/**
 	 * Rename (or move) a file
 	 *
-	 * @param	string
-	 * @param	string
-	 * @param	bool
+	 * @param	string	$old_file
+	 * @param	string	$new_file
+	 * @param	bool	$move
 	 * @return	bool
 	 */
 	public function rename($old_file, $new_file, $move = FALSE)
@@ -353,8 +405,8 @@
 	/**
 	 * Move a file
 	 *
-	 * @param	string
-	 * @param	string
+	 * @param	string	$old_file
+	 * @param	string	$new_file
 	 * @return	bool
 	 */
 	public function move($old_file, $new_file)
@@ -367,7 +419,7 @@
 	/**
 	 * Rename (or move) a file
 	 *
-	 * @param	string
+	 * @param	string	$filepath
 	 * @return	bool
 	 */
 	public function delete_file($filepath)
@@ -397,7 +449,7 @@
 	 * Delete a folder and recursively delete everything (including sub-folders)
 	 * containted within it.
 	 *
-	 * @param	string
+	 * @param	string	$filepath
 	 * @return	bool
 	 */
 	public function delete_dir($filepath)
@@ -408,7 +460,7 @@
 		}
 
 		// Add a trailing slash to the file path if needed
-		$filepath = preg_replace('/(.+?)\/*$/', '\\1/',  $filepath);
+		$filepath = preg_replace('/(.+?)\/*$/', '\\1/', $filepath);
 
 		$list = $this->list_files($filepath);
 
@@ -444,8 +496,8 @@
 	/**
 	 * Set file permissions
 	 *
-	 * @param	string	the file path
-	 * @param	int	the permissions
+	 * @param	string	$path	File path
+	 * @param	int	$perm	Permissions
 	 * @return	bool
 	 */
 	public function chmod($path, $perm)
@@ -474,6 +526,7 @@
 	/**
 	 * FTP List files in the specified directory
 	 *
+	 * @param	string	$path
 	 * @return	array
 	 */
 	public function list_files($path = '.')
@@ -496,8 +549,8 @@
 	 * Whatever the directory structure of the original file path will be
 	 * recreated on the server.
 	 *
-	 * @param	string	path to source with trailing slash
-	 * @param	string	path to destination - include the base folder with trailing slash
+	 * @param	string	$locpath	Path to source with trailing slash
+	 * @param	string	$rempath	Path to destination - include the base folder with trailing slash
 	 * @return	bool
 	 */
 	public function mirror($locpath, $rempath)
@@ -543,7 +596,7 @@
 	/**
 	 * Extract the file extension
 	 *
-	 * @param	string
+	 * @param	string	$filename
 	 * @return	string
 	 */
 	protected function _getext($filename)
@@ -562,7 +615,7 @@
 	/**
 	 * Set the upload type
 	 *
-	 * @param	string
+	 * @param	string	$ext	Filename extension
 	 * @return	string
 	 */
 	protected function _settype($ext)
@@ -583,7 +636,6 @@
 					'xml'
 				);
 
-
 		return in_array($ext, $text_types) ? 'ascii' : 'binary';
 	}
 
@@ -609,7 +661,7 @@
 	/**
 	 * Display error message
 	 *
-	 * @param	string
+	 * @param	string	$line
 	 * @return	void
 	 */
 	protected function _error($line)
diff --git a/system/libraries/Image_lib.php b/system/libraries/Image_lib.php
index 899b995..46a9c12 100644
--- a/system/libraries/Image_lib.php
+++ b/system/libraries/Image_lib.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Image Manipulation class
@@ -604,7 +605,7 @@
 		// Set the quality
 		$this->quality = trim(str_replace('%', '', $this->quality));
 
-		if ($this->quality === '' OR $this->quality === 0 OR ! preg_match('/^[0-9]+$/', $this->quality))
+		if ($this->quality === '' OR $this->quality === 0 OR ! ctype_digit($this->quality))
 		{
 			$this->quality = 90;
 		}
@@ -866,7 +867,11 @@
 		}
 
 		$retval = 1;
-		@exec($cmd, $output, $retval);
+		// exec() might be disabled
+		if (function_usable('exec'))
+		{
+			@exec($cmd, $output, $retval);
+		}
 
 		// Did it work?
 		if ($retval > 0)
@@ -946,7 +951,11 @@
 		$cmd = $this->library_path.$cmd_in.' '.$this->full_src_path.' | '.$cmd_inner.' | '.$cmd_out.' > '.$this->dest_folder.'netpbm.tmp';
 
 		$retval = 1;
-		@exec($cmd, $output, $retval);
+		// exec() might be disabled
+		if (function_usable('exec'))
+		{
+			@exec($cmd, $output, $retval);
+		}
 
 		// Did it work?
 		if ($retval > 0)
@@ -958,7 +967,7 @@
 		// With NetPBM we have to create a temporary image.
 		// If you try manipulating the original it fails so
 		// we have to rename the temp file.
-		copy ($this->dest_folder.'netpbm.tmp', $this->full_dst_path);
+		copy($this->dest_folder.'netpbm.tmp', $this->full_dst_path);
 		unlink($this->dest_folder.'netpbm.tmp');
 		@chmod($this->full_dst_path, FILE_WRITE_MODE);
 
@@ -1268,8 +1277,8 @@
 		if ($this->wm_use_drop_shadow === FALSE)
 			$this->wm_shadow_distance = 0;
 
-		$this->wm_vrt_alignment = strtoupper(substr($this->wm_vrt_alignment, 0, 1));
-		$this->wm_hor_alignment = strtoupper(substr($this->wm_hor_alignment, 0, 1));
+		$this->wm_vrt_alignment = strtoupper($this->wm_vrt_alignment[0]);
+		$this->wm_hor_alignment = strtoupper($this->wm_hor_alignment[0]);
 
 		// Set verticle alignment
 		if ($this->wm_vrt_alignment === 'M')
@@ -1320,6 +1329,13 @@
 				imagestring($src_img, $this->wm_font_size, $x_shad, $y_shad, $this->wm_text, $drp_color);
 				imagestring($src_img, $this->wm_font_size, $x_axis, $y_axis, $this->wm_text, $txt_color);
 			}
+
+			// We can preserve transparency for PNG images
+			if ($this->image_type === 3)
+			{
+				imagealphablending($src_img, FALSE);
+				imagesavealpha($src_img, TRUE);
+			}
 		}
 
 		// Output the final image
@@ -1357,7 +1373,6 @@
 		if ($image_type === '')
 			$image_type = $this->image_type;
 
-
 		switch ($image_type)
 		{
 			case	 1 :
@@ -1502,13 +1517,13 @@
 	public function image_reproportion()
 	{
 		if (($this->width === 0 && $this->height === 0) OR $this->orig_width === 0 OR $this->orig_height === 0
-			OR ( ! preg_match('/^[0-9]+$/', $this->width) && ! preg_match('/^[0-9]+$/', $this->height))
-			OR ! preg_match('/^[0-9]+$/', $this->orig_width) OR ! preg_match('/^[0-9]+$/', $this->orig_height))
+			OR ( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height))
+			OR ! ctype_digit((string) $this->orig_width) OR ! ctype_digit((string) $this->orig_height))
 		{
 			return;
 		}
 
-		// Sanitize so we don't call preg_match() anymore
+		// Sanitize
 		$this->width = (int) $this->width;
 		$this->height = (int) $this->height;
 
diff --git a/system/libraries/Javascript.php b/system/libraries/Javascript.php
index 5c8b092..420b623 100644
--- a/system/libraries/Javascript.php
+++ b/system/libraries/Javascript.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Javascript Class
@@ -36,8 +37,21 @@
  */
 class CI_Javascript {
 
+	/**
+	 * JavaScript location
+	 *
+	 * @var	string
+	 */
 	protected $_javascript_location = 'js';
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params = array())
 	{
 		$defaults = array('js_library_driver' => 'jquery', 'autoload' => TRUE);
@@ -312,8 +326,7 @@
 	 *
 	 * Outputs a javascript library mouseup event
 	 *
-	 * @param	string	The element to attach the event to
-	 * @param	string	The code to execute
+	 * @param	string	$js	Code to execute
 	 * @return	string
 	 */
 	public function ready($js)
@@ -394,9 +407,10 @@
 	 *
 	 * Outputs a javascript library animate event
 	 *
-	 * @param	string	- element
-	 * @param	string	- One of 'slow', 'normal', 'fast', or time in milliseconds
-	 * @param	string	- Javascript callback function
+	 * @param	string	$element = 'this'
+	 * @param	array	$params = array()
+	 * @param	mixed	$speed			'slow', 'normal', 'fast', or time in milliseconds
+	 * @param	string	$extra
 	 * @return	string
 	 */
 	public function animate($element = 'this', $params = array(), $speed = '', $extra = '')
@@ -546,10 +560,11 @@
 	 *
 	 * Outputs a javascript library toggle class event
 	 *
-	 * @param	string	- element
+	 * @param	string	$element = 'this'
+	 * @param	string	$class = ''
 	 * @return	string
 	 */
-	public function toggleClass($element = 'this', $class='')
+	public function toggleClass($element = 'this', $class = '')
 	{
 		return $this->js->_toggleClass($element, $class);
 	}
@@ -571,7 +586,6 @@
 		return $this->js->_show($element, $speed, $callback);
 	}
 
-
 	// --------------------------------------------------------------------
 
 	/**
@@ -579,7 +593,8 @@
 	 *
 	 * gather together all script needing to be output
 	 *
-	 * @param	string	The element to attach the event to
+	 * @param	string	$view_var
+	 * @param	bool	$script_tags
 	 * @return	string
 	 */
 	public function compile($view_var = 'script_foot', $script_tags = TRUE)
@@ -587,6 +602,8 @@
 		$this->js->_compile($view_var, $script_tags);
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Clear Compile
 	 *
@@ -606,7 +623,8 @@
 	 *
 	 * Outputs a <script> tag with the source as an external js file
 	 *
-	 * @param	string	The element to attach the event to
+	 * @param	string	$external_file
+	 * @param	bool	$relative
 	 * @return	string
 	 */
 	public function external($external_file = '', $relative = FALSE)
@@ -799,7 +817,8 @@
 	 *
 	 * Ensures a standard json value and escapes values
 	 *
-	 * @param	mixed
+	 * @param	mixed	$result
+	 * @param	bool	$is_key = FALSE
 	 * @return	string
 	 */
 	protected function _prep_args($result, $is_key = FALSE)
diff --git a/system/libraries/Migration.php b/system/libraries/Migration.php
index c786703..e591ab6 100644
--- a/system/libraries/Migration.php
+++ b/system/libraries/Migration.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 3.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Migration Class
@@ -47,6 +48,13 @@
 	protected $_migration_enabled = FALSE;
 
 	/**
+	 * Migration numbering type
+	 *
+	 * @var	bool
+	 */
+	protected $_migration_type = 'sequential';
+
+	/**
 	 * Path to migration classes
 	 *
 	 * @var string
@@ -70,11 +78,18 @@
 	/**
 	 * Whether to automatically run migrations
 	 *
-	 * @var bool
+	 * @var	bool
 	 */
 	protected $_migration_auto_latest = FALSE;
 
 	/**
+	 * Migration basename regex
+	 *
+	 * @var bool
+	 */
+	protected $_migration_regex = NULL;
+
+	/**
 	 * Error message
 	 *
 	 * @var string
@@ -84,7 +99,7 @@
 	/**
 	 * Initialize Migration Class
 	 *
-	 * @param	array
+	 * @param	array	$config
 	 * @return	void
 	 */
 	public function __construct($config = array())
@@ -126,11 +141,22 @@
 			show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
 		}
 
+		// Migration basename regex
+		$this->_migration_regex = ($this->_migration_type === 'timestamp')
+			? '/^\d{14}_(\w+)$/'
+			: '/^\d{3}_(\w+)$/';
+
+		// Make sure a valid migration numbering type was set.
+		if ( ! in_array($this->_migration_type, array('sequential', 'timestamp')))
+		{
+			show_error('An invalid migration numbering type was specified: '.$this->_migration_type);
+		}
+
 		// If the migrations table is missing, make it
 		if ( ! $this->db->table_exists($this->_migration_table))
 		{
 			$this->dbforge->add_field(array(
-				'version' => array('type' => 'INT', 'constraint' => 3),
+				'version' => array('type' => 'BIGINT', 'constraint' => 20),
 			));
 
 			$this->dbforge->create_table($this->_migration_table, TRUE);
@@ -153,118 +179,88 @@
 	 * Calls each migration step required to get to the schema version of
 	 * choice
 	 *
-	 * @param	int	Target schema version
+	 * @param	int	$target_version	Target schema version
 	 * @return	mixed	TRUE if already latest, FALSE if failed, int if upgraded
 	 */
 	public function version($target_version)
 	{
-		$start = $current_version = $this->_get_version();
-		$stop = $target_version;
+		$current_version = (int) $this->_get_version();
+		$target_version = (int) $target_version;
+
+		$migrations = $this->find_migrations();
+
+		if ($target_version > 0 && ! isset($migrations[$target_version]))
+		{
+			$this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version);
+			return FALSE;
+		}
 
 		if ($target_version > $current_version)
 		{
 			// Moving Up
-			++$start;
-			++$stop;
-			$step = 1;
+			$method = 'up';
 		}
 		else
 		{
-			// Moving Down
-			$step = -1;
+			// Moving Down, apply in reverse order
+			$method = 'down';
+			krsort($migrations);
 		}
 
-		$method = $step === 1 ? 'up' : 'down';
-		$migrations = array();
-
-		// We now prepare to actually DO the migrations
-		// But first let's make sure that everything is the way it should be
-		for ($i = $start; $i != $stop; $i += $step)
+		if (empty($migrations))
 		{
-			$f = glob(sprintf($this->_migration_path.'%03d_*.php', $i));
+			return TRUE;
+		}
 
-			// Only one migration per step is permitted
-			if (count($f) > 1)
+		$previous = FALSE;
+
+		// Validate all available migrations, and run the ones within our target range
+		foreach ($migrations as $number => $file)
+		{
+			// Check for sequence gaps
+			if ($this->_migration_type === 'sequential' && $previous !== FALSE && abs($number - $previous) > 1)
 			{
-				$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
+				$this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number);
 				return FALSE;
 			}
 
-			// Migration step not found
-			if (count($f) === 0)
-			{
-				// If trying to migrate up to a version greater than the last
-				// existing one, migrate to the last one.
-				if ($step === 1)
-				{
-					break;
-				}
+			include $file;
+			$class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php'))));
 
-				// If trying to migrate down but we're missing a step,
-				// something must definitely be wrong.
-				$this->_error_string = sprintf($this->lang->line('migration_not_found'), $i);
+			// Validate the migration file structure
+			if ( ! class_exists($class))
+			{
+				$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
 				return FALSE;
 			}
 
-			$file = basename($f[0]);
-			$name = basename($f[0], '.php');
+			$previous = $number;
 
-			// Filename validations
-			if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
+			// Run migrations that are inside the target range
+			if (
+				($method === 'up'   && $number > $current_version && $number <= $target_version) OR
+				($method === 'down' && $number <= $current_version && $number > $target_version)
+			)
 			{
-				$match[1] = strtolower($match[1]);
-
-				// Cannot repeat a migration at different steps
-				if (in_array($match[1], $migrations))
-				{
-					$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
-					return FALSE;
-				}
-
-				include $f[0];
-				$class = 'Migration_'.ucfirst($match[1]);
-
-				if ( ! class_exists($class))
-				{
-					$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
-					return FALSE;
-				}
-
-				if ( ! is_callable(array($class, $method)))
+				$instance = new $class();
+				if ( ! is_callable(array($instance, $method)))
 				{
 					$this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
 					return FALSE;
 				}
 
-				$migrations[] = $match[1];
-			}
-			else
-			{
-				$this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
-				return FALSE;
+				log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number);
+				call_user_func(array($instance, $method));
+				$current_version = $number;
+				$this->_update_version($current_version);
 			}
 		}
 
-		log_message('debug', 'Current migration: '.$current_version);
-
-		$version = $i + ($step === 1 ? -1 : 0);
-
-		// If there is nothing to do so quit
-		if ($migrations === array())
+		// This is necessary when moving down, since the the last migration applied
+		// will be the down() method for the next migration up from the target
+		if ($current_version <> $target_version)
 		{
-			return TRUE;
-		}
-
-		log_message('debug', 'Migrating from '.$method.' to version '.$version);
-
-		// Loop through the migrations
-		foreach ($migrations AS $migration)
-		{
-			// Run the migration class
-			$class = 'Migration_'.ucfirst(strtolower($migration));
-			call_user_func(array(new $class, $method));
-
-			$current_version += $step;
+			$current_version = $target_version;
 			$this->_update_version($current_version);
 		}
 
@@ -278,21 +274,23 @@
 	/**
 	 * Set's the schema to the latest migration
 	 *
-	 * @return	mixed	true if already latest, false if failed, int if upgraded
+	 * @return	mixed	TRUE if already latest, FALSE if failed, int if upgraded
 	 */
 	public function latest()
 	{
-		if ( ! $migrations = $this->find_migrations())
+		$migrations = $this->find_migrations();
+
+		if (empty($migrations))
 		{
 			$this->_error_string = $this->lang->line('migration_none_found');
-			return false;
+			return FALSE;
 		}
 
 		$last_migration = basename(end($migrations));
 
 		// Calculate the last migration step from existing migration
 		// filenames and procceed to the standard version migration
-		return $this->version((int) $last_migration);
+		return $this->version($this->_get_migration_number($last_migration));
 	}
 
 	// --------------------------------------------------------------------
@@ -300,7 +298,7 @@
 	/**
 	 * Set's the schema to the migration version set in config
 	 *
-	 * @return	mixed	true if already current, false if failed, int if upgraded
+	 * @return	mixed	TRUE if already current, FALSE if failed, int if upgraded
 	 */
 	public function current()
 	{
@@ -326,22 +324,62 @@
 	 *
 	 * @return	array	list of migration file paths sorted by version
 	 */
-	protected function find_migrations()
+	public function find_migrations()
 	{
-		// Load all *_*.php files in the migrations path
-		$files = glob($this->_migration_path.'*_*.php');
+		$migrations = array();
 
-		for ($i = 0, $c = count($files); $i < $c; $i++)
+		// Load all *_*.php files in the migrations path
+		foreach (glob($this->_migration_path.'*_*.php') as $file)
 		{
-			// Mark wrongly formatted files as false for later filtering
-			if ( ! preg_match('/^\d{3}_(\w+)$/', basename($files[$i], '.php')))
+			$name = basename($file, '.php');
+
+			// Filter out non-migration files
+			if (preg_match($this->_migration_regex, $name))
 			{
-				$files[$i] = FALSE;
+				$number = $this->_get_migration_number($name);
+
+				// There cannot be duplicate migration numbers
+				if (isset($migrations[$number]))
+				{
+					$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $number);
+					show_error($this->_error_string);
+				}
+
+				$migrations[$number] = $file;
 			}
 		}
 
-		sort($files);
-		return $files;
+		ksort($migrations);
+		return $migrations;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Extracts the migration number from a filename
+	 *
+	 * @param	string	$migration
+	 * @return	int	Numeric portion of a migration filename
+	 */
+	protected function _get_migration_number($migration)
+	{
+		return sscanf($migration, '%d', $number)
+			? $number : 0;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Extracts the migration class name from a filename
+	 *
+	 * @param	string	$migration
+	 * @return	string	text portion of a migration filename
+	 */
+	protected function _get_migration_name($migration)
+	{
+		$parts = explode('_', $migration);
+		array_shift($parts);
+		return implode('_', $parts);
 	}
 
 	// --------------------------------------------------------------------
@@ -362,13 +400,13 @@
 	/**
 	 * Stores the current schema version
 	 *
-	 * @param	int	Migration reached
+	 * @param	int	$migration	Migration reached
 	 * @return	void	Outputs a report of the migration
 	 */
-	protected function _update_version($migrations)
+	protected function _update_version($migration)
 	{
 		return $this->db->update($this->_migration_table, array(
-			'version' => $migrations
+			'version' => $migration
 		));
 	}
 
@@ -377,7 +415,7 @@
 	/**
 	 * Enable the use of CI super-global
 	 *
-	 * @param	$var
+	 * @param	string	$var
 	 * @return	mixed
 	 */
 	public function __get($var)
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
index 75745dd..66b3417 100644
--- a/system/libraries/Pagination.php
+++ b/system/libraries/Pagination.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Pagination Class
@@ -36,45 +37,269 @@
  */
 class CI_Pagination {
 
-	protected $base_url				= ''; // The page we are linking to
-	protected $prefix				= ''; // A custom prefix added to the path.
-	protected $suffix				= ''; // A custom suffix added to the path.
-	protected $total_rows			= 0; // Total number of items (database results)
-	protected $per_page				= 10; // Max number of items you want shown per page
-	protected $num_links			= 2; // Number of "digit" links to show before/after the currently viewed page
-	protected $cur_page				= 0; // The current page being viewed
-	protected $use_page_numbers		= FALSE; // Use page number for segment instead of offset
-	protected $first_link			= '&lsaquo; First';
-	protected $next_link			= '&gt;';
-	protected $prev_link			= '&lt;';
-	protected $last_link			= 'Last &rsaquo;';
-	protected $uri_segment			= 3;
-	protected $full_tag_open		= '';
-	protected $full_tag_close		= '';
-	protected $first_tag_open		= '';
-	protected $first_tag_close		= '&nbsp;';
-	protected $last_tag_open		= '&nbsp;';
-	protected $last_tag_close		= '';
-	protected $first_url			= ''; // Alternative URL for the First Page.
-	protected $cur_tag_open			= '&nbsp;<strong>';
-	protected $cur_tag_close		= '</strong>';
-	protected $next_tag_open		= '&nbsp;';
-	protected $next_tag_close		= '&nbsp;';
-	protected $prev_tag_open		= '&nbsp;';
-	protected $prev_tag_close		= '';
-	protected $num_tag_open			= '&nbsp;';
-	protected $num_tag_close		= '';
+	/**
+	 * Base URL
+	 *
+	 * The page that we're linking to
+	 *
+	 * @var	string
+	 */
+	protected $base_url		= '';
+
+	/**
+	 * Prefix
+	 *
+	 * @var	string
+	 */
+	protected $prefix		= '';
+
+	/**
+	 * Suffix
+	 *
+	 * @var	string
+	 */
+	protected $suffix		= '';
+
+	/**
+	 * Total number of items
+	 *
+	 * @var	int
+	 */
+	protected $total_rows		= 0;
+
+	/**
+	 * Items per page
+	 *
+	 * @var	int
+	 */
+	protected $per_page		= 10;
+
+	/**
+	 * Number of links to show
+	 *
+	 * Relates to "digit" type links shown before/after
+	 * the currently viewed page.
+	 *
+	 * @var	int
+	 */
+	protected $num_links		= 2;
+
+	/**
+	 * Current page
+	 *
+	 * @var	int
+	 */
+	protected $cur_page		= 0;
+
+	/**
+	 * Use page numbers flag
+	 *
+	 * Whether to use actual page numbers instead of an offset
+	 *
+	 * @var	bool
+	 */
+	protected $use_page_numbers	= FALSE;
+
+	/**
+	 * First link
+	 *
+	 * @var	string
+	 */
+	protected $first_link		= '&lsaquo; First';
+
+	/**
+	 * Next link
+	 *
+	 * @var	string
+	 */
+	protected $next_link		= '&gt;';
+
+	/**
+	 * Previous link
+	 *
+	 * @var	string
+	 */
+	protected $prev_link		= '&lt;';
+
+	/**
+	 * Last link
+	 *
+	 * @var	string
+	 */
+	protected $last_link		= 'Last &rsaquo;';
+
+	/**
+	 * URI Segment
+	 *
+	 * @var	int
+	 */
+	protected $uri_segment		= 3;
+
+	/**
+	 * Full tag open
+	 *
+	 * @var	string
+	 */
+	protected $full_tag_open	= '';
+
+	/**
+	 * Full tag close
+	 *
+	 * @var	string
+	 */
+	protected $full_tag_close	= '';
+
+	/**
+	 * First tag open
+	 *
+	 * @var	string
+	 */
+	protected $first_tag_open	= '';
+
+	/**
+	 * First tag close
+	 *
+	 * @var	string
+	 */
+	protected $first_tag_close	= '';
+
+	/**
+	 * Last tag open
+	 *
+	 * @var	string
+	 */
+	protected $last_tag_open	= '';
+
+	/**
+	 * Last tag close
+	 *
+	 * @var	string
+	 */
+	protected $last_tag_close	= '';
+
+	/**
+	 * First URL
+	 *
+	 * An alternative URL for the first page
+	 *
+	 * @var	string
+	 */
+	protected $first_url		= '';
+
+	/**
+	 * Current tag open
+	 *
+	 * @var	string
+	 */
+	protected $cur_tag_open		= '<strong>';
+
+	/**
+	 * Current tag close
+	 *
+	 * @var	string
+	 */
+	protected $cur_tag_close	= '</strong>';
+
+	/**
+	 * Next tag open
+	 *
+	 * @var	string
+	 */
+	protected $next_tag_open	= '';
+
+	/**
+	 * Next tag close
+	 *
+	 * @var	string
+	 */
+	protected $next_tag_close	= '';
+
+	/**
+	 * Previous tag open
+	 *
+	 * @var	string
+	 */
+	protected $prev_tag_open	= '';
+
+	/**
+	 * Previous tag close
+	 *
+	 * @var	string
+	 */
+	protected $prev_tag_close	= '';
+
+	/**
+	 * Number tag open
+	 *
+	 * @var	string
+	 */
+	protected $num_tag_open		= '';
+
+	/**
+	 * Number tag close
+	 *
+	 * @var	string
+	 */
+	protected $num_tag_close	= '';
+
+	/**
+	 * Page query string flag
+	 *
+	 * @var	bool
+	 */
 	protected $page_query_string	= FALSE;
-	protected $query_string_segment 	= 'per_page';
-	protected $display_pages		= TRUE;
-	protected $_attributes			= '';
-	protected $_link_types			= array();
+
+	/**
+	 * Query string segment
+	 *
+	 * @var	string
+	 */
+	protected $query_string_segment = 'per_page';
+
+	/**
+	 * Display pages flag
+	 *
+	 * @var	bool
+	 */
+	protected $display_pages	= TRUE;
+
+	/**
+	 * Attributes
+	 *
+	 * @var	string
+	 */
+	protected $_attributes		= '';
+
+	/**
+	 * Link types
+	 *
+	 * "rel" attribute
+	 *
+	 * @see	CI_Pagination::_attr_rel()
+	 * @var	array
+	 */
+	protected $_link_types		= array();
+
+	/**
+	 * Reuse query string flag
+	 *
+	 * @var	bool
+	 */
 	protected $reuse_query_string   = FALSE;
 
 	/**
+	 * Data page attribute
+	 *
+	 * @var	string
+	 */
+	protected $data_page_attr	= 'data-ci-pagination-page';
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Constructor
 	 *
-	 * @param	array	initialization parameters
+	 * @param	array	$params	Initialization parameters
 	 * @return	void
 	 */
 	public function __construct($params = array())
@@ -88,7 +313,7 @@
 	/**
 	 * Initialize Preferences
 	 *
-	 * @param	array	initialization parameters
+	 * @param	array	$params	Initialization parameters
 	 * @return	void
 	 */
 	public function initialize($params = array())
@@ -156,7 +381,7 @@
 		// See if we are using a prefix or suffix on links
 		if ($this->prefix !== '' OR $this->suffix !== '')
 		{
-			$this->cur_page = (int) str_replace(array($this->prefix, $this->suffix), '', $CI->uri->segment($this->uri_segment));
+			$this->cur_page = (int) str_replace(array($this->prefix, $this->suffix), '', $CI->uri->rsegment($this->uri_segment));
 		}
 
 		if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE)
@@ -168,7 +393,7 @@
 		}
 		elseif ( ! $this->cur_page && $CI->uri->segment($this->uri_segment) !== $base_page)
 		{
-			$this->cur_page = (int) $CI->uri->segment($this->uri_segment);
+			$this->cur_page = (int) $CI->uri->rsegment($this->uri_segment);
 		}
 
 		// Set current page to 1 if it's not valid or if using page numbers instead of offset
@@ -202,7 +427,7 @@
 
 		if ( ! $this->use_page_numbers)
 		{
-			$this->cur_page = floor(($this->cur_page/$this->per_page) + 1);
+			$this->cur_page = (int) floor(($this->cur_page/$this->per_page) + 1);
 		}
 
 		// Calculate the start and end numbers. These determine
@@ -214,7 +439,8 @@
 		// string. If post, add a trailing slash to the base URL if needed
 		if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE)
 		{
-			$this->base_url = rtrim($this->base_url).'&amp;'.$this->query_string_segment.'=';
+			$segment = (strpos($this->base_url, '?')) ? '&amp;' : '?';
+			$this->base_url = rtrim($this->base_url).$segment.$this->query_string_segment.'=';
 		}
 		else
 		{
@@ -230,22 +456,30 @@
 		if ($this->reuse_query_string === TRUE)
 		{
 			$get = $CI->input->get();
-			
+
 			// Unset the controll, method, old-school routing options
 			unset($get['c'], $get['m'], $get[$this->query_string_segment]);
 
-			// Put everything else onto the end
-			$query_string = (strpos($this->base_url, '&amp;') !== FALSE ? '&amp;' : '?') . http_build_query($get, '', '&amp;');
+			if ( ! empty($get))
+			{
+				// Put everything else onto the end
+				$query_string = (strpos($this->base_url, '?') !== FALSE ? '&amp;' : '?')
+						.http_build_query($get, '', '&amp;');
 
-			// Add this after the suffix to put it into more links easily
-			$this->suffix .= $query_string;
+				// Add this after the suffix to put it into more links easily
+				$this->suffix .= $query_string;
+			}
 		}
 
 		// Render the "First" link
 		if ($this->first_link !== FALSE && $this->cur_page > ($this->num_links + 1))
 		{
 			$first_url = ($this->first_url === '') ? $this->base_url : $this->first_url;
-			$output .= $this->first_tag_open.'<a href="'.$first_url.'"'.$this->_attributes.$this->_attr_rel('start').'>'
+
+			// Take the general parameters, and squeeze this pagination-page attr in there for JS fw's
+			$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, 1);
+
+			$output .= $this->first_tag_open.'<a href="'.$first_url.'"'.$attributes.$this->_attr_rel('start').'>'
 				.$this->first_link.'</a>'.$this->first_tag_close;
 		}
 
@@ -254,15 +488,18 @@
 		{
 			$i = ($this->use_page_numbers) ? $uri_page_number - 1 : $uri_page_number - $this->per_page;
 
+			// Take the general parameters, and squeeze this pagination-page attr in there for JS fw's
+			$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+
 			if ($i === $base_page && $this->first_url !== '')
 			{
-				$output .= $this->prev_tag_open.'<a href="'.$this->first_url.$query_string.'"'.$this->_attributes.$this->_attr_rel('prev').'>'
+				$output .= $this->prev_tag_open.'<a href="'.$this->first_url.$query_string.'"'.$attributes.$this->_attr_rel('prev').'>'
 					.$this->prev_link.'</a>'.$this->prev_tag_close;
 			}
 			else
 			{
 				$append = ($i === $base_page) ? $query_string : $this->prefix.$i.$this->suffix;
-				$output .= $this->prev_tag_open.'<a href="'.$this->base_url.$append.'"'.$this->_attributes.$this->_attr_rel('prev').'>'
+				$output .= $this->prev_tag_open.'<a href="'.$this->base_url.$append.'"'.$attributes.$this->_attr_rel('prev').'>'
 					.$this->prev_link.'</a>'.$this->prev_tag_close;
 			}
 
@@ -275,6 +512,10 @@
 			for ($loop = $start -1; $loop <= $end; $loop++)
 			{
 				$i = ($this->use_page_numbers) ? $loop : ($loop * $this->per_page) - $this->per_page;
+
+				// Take the general parameters, and squeeze this pagination-page attr in there for JS fw's
+				$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+
 				if ($i >= $base_page)
 				{
 					if ($this->cur_page === $loop)
@@ -286,13 +527,13 @@
 						$n = ($i === $base_page) ? '' : $i;
 						if ($n === '' && ! empty($this->first_url))
 						{
-							$output .= $this->num_tag_open.'<a href="'.$this->first_url.$query_string.'"'.$this->_attributes.$this->_attr_rel('start').'>'
+							$output .= $this->num_tag_open.'<a href="'.$this->first_url.$query_string.'"'.$attributes.$this->_attr_rel('start').'>'
 								.$loop.'</a>'.$this->num_tag_close;
 						}
 						else
 						{
 							$append = ($n === '') ? $query_string : $this->prefix.$n.$this->suffix;
-							$output .= $this->num_tag_open.'<a href="'.$this->base_url.$append.'"'.$this->_attributes.$this->_attr_rel('start').'>'
+							$output .= $this->num_tag_open.'<a href="'.$this->base_url.$append.'"'.$attributes.$this->_attr_rel('start').'>'
 								.$loop.'</a>'.$this->num_tag_close;
 						}
 					}
@@ -305,7 +546,10 @@
 		{
 			$i = ($this->use_page_numbers) ? $this->cur_page + 1 : $this->cur_page * $this->per_page;
 
-			$output .= $this->next_tag_open.'<a href="'.$this->base_url.$this->prefix.$i.$this->suffix.'"'.$this->_attributes
+			// Take the general parameters, and squeeze this pagination-page attr in there for JS fw's
+			$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+
+			$output .= $this->next_tag_open.'<a href="'.$this->base_url.$this->prefix.$i.$this->suffix.'"'.$attributes
 				.$this->_attr_rel('next').'>'.$this->next_link.'</a>'.$this->next_tag_close;
 		}
 
@@ -314,7 +558,10 @@
 		{
 			$i = ($this->use_page_numbers) ? $num_pages : ($num_pages * $this->per_page) - $this->per_page;
 
-			$output .= $this->last_tag_open.'<a href="'.$this->base_url.$this->prefix.$i.$this->suffix.'"'.$this->_attributes.'>'
+			// Take the general parameters, and squeeze this pagination-page attr in there for JS fw's
+			$attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, (int) $i);
+
+			$output .= $this->last_tag_open.'<a href="'.$this->base_url.$this->prefix.$i.$this->suffix.'"'.$attributes.'>'
 				.$this->last_link.'</a>'.$this->last_tag_close;
 		}
 
@@ -331,7 +578,7 @@
 	/**
 	 * Parse attributes
 	 *
-	 * @param	array
+	 * @param	array	$attributes
 	 * @return	void
 	 */
 	protected function _parse_attributes($attributes)
@@ -355,7 +602,7 @@
 	 * Add "rel" attribute
 	 *
 	 * @link	http://www.w3.org/TR/html5/links.html#linkTypes
-	 * @param	string
+	 * @param	string	$type
 	 * @return	string
 	 */
 	protected function _attr_rel($type)
diff --git a/system/libraries/Parser.php b/system/libraries/Parser.php
index b64c782..faa94b7 100644
--- a/system/libraries/Parser.php
+++ b/system/libraries/Parser.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Parser Class
@@ -57,6 +58,20 @@
 	 */
 	protected $CI;
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Class constructor
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->CI =& get_instance();
+	}
+
+	// --------------------------------------------------------------------
+
 	/**
 	 * Parse a template
 	 *
@@ -70,7 +85,6 @@
 	 */
 	public function parse($template, $data, $return = FALSE)
 	{
-		$this->CI =& get_instance();
 		$template = $this->CI->load->view($template, $data, TRUE);
 
 		return $this->_parse($template, $data, $return);
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
index 1e961f6..1c97e34 100644
--- a/system/libraries/Profiler.php
+++ b/system/libraries/Profiler.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * CodeIgniter Profiler Class
@@ -74,12 +75,14 @@
 	 */
 	protected $CI;
 
+	// --------------------------------------------------------------------
+
 	/**
-	 * Constructor
+	 * Class constructor
 	 *
 	 * Initialize Profiler
 	 *
-	 * @param array $config
+	 * @param	array	$config	Parameters
 	 */
 	public function __construct($config = array())
 	{
@@ -111,7 +114,7 @@
 	 *
 	 * Sets the private _compile_* properties to enable/disable Profiler sections
 	 *
-	 * @param	mixed
+	 * @param	mixed	$config
 	 * @return	void
 	 */
 	public function set_sections($config)
@@ -190,11 +193,24 @@
 		$dbs = array();
 
 		// Let's determine which databases are currently connected to
-		foreach (get_object_vars($this->CI) as $CI_object)
+		foreach (get_object_vars($this->CI) as $name => $cobject)
 		{
-			if (is_object($CI_object) && is_subclass_of(get_class($CI_object), 'CI_DB'))
+			if (is_object($cobject))
 			{
-				$dbs[] = $CI_object;
+				if ($cobject instanceof CI_DB)
+				{
+					$dbs[get_class($this->CI).':$'.$name] = $cobject;
+				}
+				elseif ($cobject instanceof CI_Model)
+				{
+					foreach (get_object_vars($cobject) as $mname => $mobject)
+					{
+						if ($mobject instanceof CI_DB)
+						{
+							$dbs[get_class($cobject).':$'.$mname] = $mobject;
+						}
+					}
+				}
 			}
 		}
 
@@ -219,7 +235,7 @@
 		$output  = "\n\n";
 		$count = 0;
 
-		foreach ($dbs as $db)
+		foreach ($dbs as $name => $db)
 		{
 			$hide_queries = (count($db->queries) > $this->_query_toggle_count) ? ' display:none' : '';
 
@@ -233,7 +249,7 @@
 			$output .= '<fieldset style="border:1px solid #0000FF;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">'
 				."\n"
 				.'<legend style="color:#0000FF;">&nbsp;&nbsp;'.$this->CI->lang->line('profiler_database')
-				.':&nbsp; '.$db->database.'&nbsp;&nbsp;&nbsp;'.$this->CI->lang->line('profiler_queries')
+				.':&nbsp; '.$db->database.' ('.$name.')&nbsp;&nbsp;&nbsp;'.$this->CI->lang->line('profiler_queries')
 				.': '.count($db->queries).'&nbsp;&nbsp;'.$show_hide_js."</legend>\n\n\n"
 				.'<table style="width:100%;'.$hide_queries.'" id="ci_profiler_queries_db_'.$count."\">\n";
 
diff --git a/system/libraries/Session.php b/system/libraries/Session.php
deleted file mode 100644
index af38dc3..0000000
--- a/system/libraries/Session.php
+++ /dev/null
@@ -1,955 +0,0 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
-/**
- * CodeIgniter
- *
- * An open source application development framework for PHP 5.2.4 or newer
- *
- * NOTICE OF LICENSE
- *
- * Licensed under the Open Software License version 3.0
- *
- * This source file is subject to the Open Software License (OSL 3.0) that is
- * bundled with this package in the files license.txt / license.rst.  It is
- * also available through the world wide web at this URL:
- * http://opensource.org/licenses/OSL-3.0
- * If you did not receive a copy of the license and are unable to obtain it
- * through the world wide web, please send an email to
- * licensing@ellislab.com so we can send you a copy immediately.
- *
- * @package		CodeIgniter
- * @author		EllisLab Dev Team
- * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
- * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
- * @link		http://codeigniter.com
- * @since		Version 1.0
- * @filesource
- */
-
-/**
- * Session Class
- *
- * @package		CodeIgniter
- * @subpackage	Libraries
- * @category	Sessions
- * @author		EllisLab Dev Team
- * @link		http://codeigniter.com/user_guide/libraries/sessions.html
- */
-class CI_Session {
-
-	/**
-	 * Whether to encrypt the session cookie
-	 *
-	 * @var bool
-	 */
-	public $sess_encrypt_cookie		= FALSE;
-
-	/**
-	 * Whether to use to the database for session storage
-	 *
-	 * @var bool
-	 */
-	public $sess_use_database		= FALSE;
-
-	/**
-	 * Name of the database table in which to store sessions
-	 *
-	 * @var string
-	 */
-	public $sess_table_name			= '';
-
-	/**
-	 * Length of time (in seconds) for sessions to expire
-	 *
-	 * @var int
-	 */
-	public $sess_expiration			= 7200;
-
-	/**
-	 * Whether to kill session on close of browser window
-	 *
-	 * @var bool
-	 */
-	public $sess_expire_on_close		= FALSE;
-
-	/**
-	 * Whether to match session on ip address
-	 *
-	 * @var bool
-	 */
-	public $sess_match_ip			= FALSE;
-
-	/**
-	 * Whether to match session on user-agent
-	 *
-	 * @var bool
-	 */
-	public $sess_match_useragent		= TRUE;
-
-	/**
-	 * Name of session cookie
-	 *
-	 * @var string
-	 */
-	public $sess_cookie_name		= 'ci_session';
-
-	/**
-	 * Session cookie prefix
-	 *
-	 * @var string
-	 */
-	public $cookie_prefix			= '';
-
-	/**
-	 * Session cookie path
-	 *
-	 * @var string
-	 */
-	public $cookie_path			= '';
-
-	/**
-	 * Session cookie domain
-	 *
-	 * @var string
-	 */
-	public $cookie_domain			= '';
-
-	/**
-	 * Whether to set the cookie only on HTTPS connections
-	 *
-	 * @var bool
-	 */
-	public $cookie_secure			= FALSE;
-
-	/**
-	 * Whether cookie should be allowed only to be sent by the server
-	 *
-	 * @var bool
-	 */
-	public $cookie_httponly 		= FALSE;
-
-	/**
-	 * Interval at which to update session
-	 *
-	 * @var int
-	 */
-	public $sess_time_to_update		= 300;
-
-	/**
-	 * Key with which to encrypt the session cookie
-	 *
-	 * @var string
-	 */
-	public $encryption_key			= '';
-
-	/**
-	 * String to indicate flash data cookies
-	 *
-	 * @var string
-	 */
-	public $flashdata_key			= 'flash';
-
-	/**
-	 * Timezone to use for the current time
-	 *
-	 * @var string
-	 */
-	public $time_reference			= 'local';
-
-
-	/**
-	 * Session data
-	 *
-	 * @var array
-	 */
-	public $userdata			= array();
-
-	/**
-	 * Reference to CodeIgniter instance
-	 *
-	 * @var object
-	 */
-	public $CI;
-
-	/**
-	 * Current time
-	 *
-	 * @var int
-	 */
-	public $now;
-
-	/**
-	 * Session Constructor
-	 *
-	 * The constructor runs the session routines automatically
-	 * whenever the class is instantiated.
-	 *
-	 * @param	array
-	 * @return	void
-	 */
-	public function __construct($params = array())
-	{
-		log_message('debug', 'Session Class Initialized');
-
-		// Set the super object to a local variable for use throughout the class
-		$this->CI =& get_instance();
-
-		// Set all the session preferences, which can either be set
-		// manually via the $params array above or via the config file
-		foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
-		{
-			$this->$key = isset($params[$key]) ? $params[$key] : $this->CI->config->item($key);
-		}
-
-		if ($this->encryption_key === '')
-		{
-			show_error('In order to use the Session class you are required to set an encryption key in your config file.');
-		}
-
-		// Load the string helper so we can use the strip_slashes() function
-		$this->CI->load->helper('string');
-
-		// Do we need encryption? If so, load the encryption class
-		if ($this->sess_encrypt_cookie === TRUE)
-		{
-			$this->CI->load->library('encrypt');
-		}
-
-		// Are we using a database? If so, load it
-		if ($this->sess_use_database === TRUE && $this->sess_table_name !== '')
-		{
-			$this->CI->load->database();
-		}
-
-		// Set the "now" time. Can either be GMT or server time, based on the
-		// config prefs. We use this to set the "last activity" time
-		$this->now = $this->_get_time();
-
-		// Set the session length. If the session expiration is
-		// set to zero we'll set the expiration two years from now.
-		if ($this->sess_expiration === 0)
-		{
-			$this->sess_expiration = (60*60*24*365*2);
-		}
-
-		// Set the cookie name
-		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
-
-		// Run the Session routine. If a session doesn't exist we'll
-		// create a new one. If it does, we'll update it.
-		if ( ! $this->sess_read())
-		{
-			$this->sess_create();
-		}
-		else
-		{
-			$this->sess_update();
-		}
-
-		// Delete 'old' flashdata (from last request)
-		$this->_flashdata_sweep();
-
-		// Mark all new flashdata as old (data will be deleted before next request)
-		$this->_flashdata_mark();
-
-		// Delete expired sessions if necessary
-		$this->_sess_gc();
-
-		log_message('debug', 'Session routines successfully run');
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Fetch the current session data if it exists
-	 *
-	 * @return	bool
-	 */
-	public function sess_read()
-	{
-		// Fetch the cookie
-		$session = $this->CI->input->cookie($this->sess_cookie_name);
-
-		// No cookie?  Goodbye cruel world!...
-		if ($session === NULL)
-		{
-			log_message('debug', 'A session cookie was not found.');
-			return FALSE;
-		}
-
-		// Decrypt the cookie data
-		if ($this->sess_encrypt_cookie === TRUE)
-		{
-			$session = $this->CI->encrypt->decode($session);
-		}
-		else
-		{
-			// encryption was not used, so we need to check the md5 hash
-			$hash	 = substr($session, strlen($session)-32); // get last 32 chars
-			$session = substr($session, 0, strlen($session)-32);
-
-			// Does the md5 hash match? This is to prevent manipulation of session data in userspace
-			if ($hash !==  md5($session.$this->encryption_key))
-			{
-				log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
-				$this->sess_destroy();
-				return FALSE;
-			}
-		}
-
-		// Unserialize the session array
-		$session = $this->_unserialize($session);
-
-		// Is the session data we unserialized an array with the correct format?
-		if ( ! is_array($session) OR ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], $session['last_activity']))
-		{
-			$this->sess_destroy();
-			return FALSE;
-		}
-
-		// Is the session current?
-		if (($session['last_activity'] + $this->sess_expiration) < $this->now)
-		{
-			$this->sess_destroy();
-			return FALSE;
-		}
-
-		// Does the IP match?
-		if ($this->sess_match_ip === TRUE && $session['ip_address'] !== $this->CI->input->ip_address())
-		{
-			$this->sess_destroy();
-			return FALSE;
-		}
-
-		// Does the User Agent Match?
-		if ($this->sess_match_useragent === TRUE && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120)))
-		{
-			$this->sess_destroy();
-			return FALSE;
-		}
-
-		// Is there a corresponding session in the DB?
-		if ($this->sess_use_database === TRUE)
-		{
-			$this->CI->db->where('session_id', $session['session_id']);
-
-			if ($this->sess_match_ip === TRUE)
-			{
-				$this->CI->db->where('ip_address', $session['ip_address']);
-			}
-
-			if ($this->sess_match_useragent === TRUE)
-			{
-				$this->CI->db->where('user_agent', $session['user_agent']);
-			}
-
-			$query = $this->CI->db->limit(1)->get($this->sess_table_name);
-
-			// No result? Kill it!
-			if ($query->num_rows() === 0)
-			{
-				$this->sess_destroy();
-				return FALSE;
-			}
-
-			// Is there custom data?  If so, add it to the main session array
-			$row = $query->row();
-			if ( ! empty($row->user_data))
-			{
-				$custom_data = $this->_unserialize($row->user_data);
-
-				if (is_array($custom_data))
-				{
-					foreach ($custom_data as $key => $val)
-					{
-						$session[$key] = $val;
-					}
-				}
-			}
-		}
-
-		// Session is valid!
-		$this->userdata = $session;
-		unset($session);
-
-		return TRUE;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Write the session data
-	 *
-	 * @return	void
-	 */
-	public function sess_write()
-	{
-		// Are we saving custom data to the DB?  If not, all we do is update the cookie
-		if ($this->sess_use_database === FALSE)
-		{
-			$this->_set_cookie();
-			return;
-		}
-
-		// set the custom userdata, the session data we will set in a second
-		$custom_userdata = $this->userdata;
-		$cookie_userdata = array();
-
-		// Before continuing, we need to determine if there is any custom data to deal with.
-		// Let's determine this by removing the default indexes to see if there's anything left in the array
-		// and set the session data while we're at it
-		foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
-		{
-			unset($custom_userdata[$val]);
-			$cookie_userdata[$val] = $this->userdata[$val];
-		}
-
-		// Did we find any custom data? If not, we turn the empty array into a string
-		// since there's no reason to serialize and store an empty array in the DB
-		if (count($custom_userdata) === 0)
-		{
-			$custom_userdata = '';
-		}
-		else
-		{
-			// Serialize the custom data array so we can store it
-			$custom_userdata = $this->_serialize($custom_userdata);
-		}
-
-		// Run the update query
-		$this->CI->db->where('session_id', $this->userdata['session_id']);
-		$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
-
-		// Write the cookie. Notice that we manually pass the cookie data array to the
-		// _set_cookie() function. Normally that function will store $this->userdata, but
-		// in this case that array contains custom data, which we do not want in the cookie.
-		$this->_set_cookie($cookie_userdata);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Create a new session
-	 *
-	 * @return	void
-	 */
-	public function sess_create()
-	{
-		$sessid = '';
-		do
-		{
-			$sessid .= mt_rand(0, mt_getrandmax());
-		}
-		while (strlen($sessid) < 32);
-
-		// To make the session ID even more secure we'll combine it with the user's IP
-		$sessid .= $this->CI->input->ip_address();
-
-		$this->userdata = array(
-					'session_id'	=> md5(uniqid($sessid, TRUE)),
-					'ip_address'	=> $this->CI->input->ip_address(),
-					'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
-					'last_activity'	=> $this->now,
-					'user_data'	=> ''
-				);
-
-		// Save the data to the DB if needed
-		if ($this->sess_use_database === TRUE)
-		{
-			$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
-		}
-
-		// Write the cookie
-		$this->_set_cookie();
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Update an existing session
-	 *
-	 * @return	void
-	 */
-	public function sess_update()
-	{
-		// We only update the session every five minutes by default
-		if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
-		{
-			return;
-		}
-
-		// _set_cookie() will handle this for us if we aren't using database sessions
-		// by pushing all userdata to the cookie.
-		$cookie_data = NULL;
-
-		/* Changing the session ID during an AJAX call causes problems,
-		 * so we'll only update our last_activity
-		 */
-		if ($this->CI->input->is_ajax_request())
-		{
-			$this->userdata['last_activity'] = $this->now;
-
-			// Update the session ID and last_activity field in the DB if needed
-			if ($this->sess_use_database === TRUE)
-			{
-				// set cookie explicitly to only have our session data
-				$cookie_data = array();
-				foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
-				{
-					$cookie_data[$val] = $this->userdata[$val];
-				}
-
-				$this->CI->db->query($this->CI->db->update_string($this->sess_table_name,
-											array('last_activity' => $this->userdata['last_activity']),
-											array('session_id' => $this->userdata['session_id'])));
-			}
-
-			return $this->_set_cookie($cookie_data);
-		}
-
-		// Save the old session id so we know which record to
-		// update in the database if we need it
-		$old_sessid = $this->userdata['session_id'];
-		$new_sessid = '';
-		do
-		{
-			$new_sessid .= mt_rand(0, mt_getrandmax());
-		}
-		while (strlen($new_sessid) < 32);
-
-		// To make the session ID even more secure we'll combine it with the user's IP
-		$new_sessid .= $this->CI->input->ip_address();
-
-		// Turn it into a hash and update the session data array
-		$this->userdata['session_id'] = $new_sessid = md5(uniqid($new_sessid, TRUE));
-		$this->userdata['last_activity'] = $this->now;
-
-		// Update the session ID and last_activity field in the DB if needed
-		if ($this->sess_use_database === TRUE)
-		{
-			// set cookie explicitly to only have our session data
-			$cookie_data = array();
-			foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
-			{
-				$cookie_data[$val] = $this->userdata[$val];
-			}
-
-			$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
-		}
-
-		// Write the cookie
-		$this->_set_cookie($cookie_data);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Destroy the current session
-	 *
-	 * @return	void
-	 */
-	public function sess_destroy()
-	{
-		// Kill the session DB row
-		if ($this->sess_use_database === TRUE && isset($this->userdata['session_id']))
-		{
-			$this->CI->db->where('session_id', $this->userdata['session_id']);
-			$this->CI->db->delete($this->sess_table_name);
-		}
-
-		// Kill the cookie
-		setcookie(
-				$this->sess_cookie_name,
-				addslashes(serialize(array())),
-				($this->now - 31500000),
-				$this->cookie_path,
-				$this->cookie_domain,
-				0
-			);
-
-		// Kill session data
-		$this->userdata = array();
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Fetch a specific item from the session array
-	 *
-	 * @param	string
-	 * @return	string
-	 */
-	public function userdata($item)
-	{
-		return isset($this->userdata[$item]) ? $this->userdata[$item] : NULL;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Fetch all session data
-	 *
-	 * @return	array
-	 */
-	public function all_userdata()
-	{
-		return $this->userdata;
-	}
-
-	// --------------------------------------------------------------------------
-
-	/**
-	 * Fetch all flashdata
-	 *
-	 * @return	array
-	 */
-	public function all_flashdata()
-	{
-		$out = array();
-
-		// loop through all userdata
-		foreach ($this->all_userdata() as $key => $val)
-		{
-			// if it contains flashdata, add it
-			if (strpos($key, 'flash:old:') !== FALSE)
-			{
-				$out[$key] = $val;
-			}
-		}
-		return $out;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Add or change data in the "userdata" array
-	 *
-	 * @param	mixed
-	 * @param	string
-	 * @return	void
-	 */
-	public function set_userdata($newdata = array(), $newval = '')
-	{
-		if (is_string($newdata))
-		{
-			$newdata = array($newdata => $newval);
-		}
-
-		if (count($newdata) > 0)
-		{
-			foreach ($newdata as $key => $val)
-			{
-				$this->userdata[$key] = $val;
-			}
-		}
-
-		$this->sess_write();
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Delete a session variable from the "userdata" array
-	 *
-	 * @param	array
-	 * @return	void
-	 */
-	public function unset_userdata($newdata = array())
-	{
-		if (is_string($newdata))
-		{
-			$newdata = array($newdata => '');
-		}
-
-		if (count($newdata) > 0)
-		{
-			foreach ($newdata as $key => $val)
-			{
-				unset($this->userdata[$key]);
-			}
-		}
-
-		$this->sess_write();
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Add or change flashdata, only available
-	 * until the next request
-	 *
-	 * @param	mixed
-	 * @param	string
-	 * @return	void
-	 */
-	public function set_flashdata($newdata = array(), $newval = '')
-	{
-		if (is_string($newdata))
-		{
-			$newdata = array($newdata => $newval);
-		}
-
-		if (count($newdata) > 0)
-		{
-			foreach ($newdata as $key => $val)
-			{
-				$this->set_userdata($this->flashdata_key.':new:'.$key, $val);
-			}
-		}
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Keeps existing flashdata available to next request.
-	 *
-	 * @param	string
-	 * @return	void
-	 */
-	public function keep_flashdata($key)
-	{
-		// 'old' flashdata gets removed. Here we mark all
-		// flashdata as 'new' to preserve it from _flashdata_sweep()
-		// Note the function will return NULL if the $key
-		// provided cannot be found
-		$value = $this->userdata($this->flashdata_key.':old:'.$key);
-
-		$this->set_userdata($this->flashdata_key.':new:'.$key, $value);
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Fetch a specific flashdata item from the session array
-	 *
-	 * @param	string
-	 * @return	string
-	 */
-	public function flashdata($key)
-	{
-		return $this->userdata($this->flashdata_key.':old:'.$key);
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Identifies flashdata as 'old' for removal
-	 * when _flashdata_sweep() runs.
-	 *
-	 * @return	void
-	 */
-	protected function _flashdata_mark()
-	{
-		$userdata = $this->all_userdata();
-		foreach ($userdata as $name => $value)
-		{
-			$parts = explode(':new:', $name);
-			if (is_array($parts) && count($parts) === 2)
-			{
-				$this->set_userdata($this->flashdata_key.':old:'.$parts[1], $value);
-				$this->unset_userdata($name);
-			}
-		}
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Removes all flashdata marked as 'old'
-	 *
-	 * @return	void
-	 */
-	protected function _flashdata_sweep()
-	{
-		$userdata = $this->all_userdata();
-		foreach ($userdata as $key => $value)
-		{
-			if (strpos($key, ':old:'))
-			{
-				$this->unset_userdata($key);
-			}
-		}
-
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Get the "now" time
-	 *
-	 * @return	string
-	 */
-	protected function _get_time()
-	{
-		if ($this->time_reference === 'local' OR $this->time_reference === date_default_timezone_get())
-		{
-			return time();
-		}
-
-		$datetime = new DateTime('now', new DateTimeZone($this->time_reference));
-		sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second);
-
-		return mktime($hour, $minute, $second, $month, $day, $year);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Write the session cookie
-	 *
-	 * @param	mixed
-	 * @return	void
-	 */
-	protected function _set_cookie($cookie_data = NULL)
-	{
-		if (is_null($cookie_data))
-		{
-			$cookie_data = $this->userdata;
-		}
-
-		// Serialize the userdata for the cookie
-		$cookie_data = $this->_serialize($cookie_data);
-
-		if ($this->sess_encrypt_cookie === TRUE)
-		{
-			$cookie_data = $this->CI->encrypt->encode($cookie_data);
-		}
-		else
-		{
-			// if encryption is not used, we provide an md5 hash to prevent userside tampering
-			$cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
-		}
-
-		$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
-
-		// Set the cookie
-		setcookie(
-			$this->sess_cookie_name,
-			$cookie_data,
-			$expire,
-			$this->cookie_path,
-			$this->cookie_domain,
-			$this->cookie_secure,
-			$this->cookie_httponly
-		);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Serialize an array
-	 *
-	 * This function first converts any slashes found in the array to a temporary
-	 * marker, so when it gets unserialized the slashes will be preserved
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _serialize($data)
-	{
-		if (is_array($data))
-		{
-			array_walk_recursive($data, array(&$this, '_escape_slashes'));
-		}
-		elseif (is_string($data))
-		{
-			$data = str_replace('\\', '{{slash}}', $data);
-		}
-		return serialize($data);
-	}
-
-	/**
-	 * Escape slashes
-	 *
-	 * This function converts any slashes found into a temporary marker
-	 *
-	 * @param	string
-	 * @param	string
-	 * @return	void
-	 */
-	protected function _escape_slashes(&$val, $key)
-	{
-		if (is_string($val))
-		{
-			$val = str_replace('\\', '{{slash}}', $val);
-		}
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Unserialize
-	 *
-	 * This function unserializes a data string, then converts any
-	 * temporary slash markers back to actual slashes
-	 *
-	 * @param	array
-	 * @return	string
-	 */
-	protected function _unserialize($data)
-	{
-		$data = @unserialize(strip_slashes(trim($data)));
-
-		if (is_array($data))
-		{
-			array_walk_recursive($data, array(&$this, '_unescape_slashes'));
-			return $data;
-		}
-
-		return is_string($data) ? str_replace('{{slash}}', '\\', $data) : $data;
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Unescape slashes
-	 *
-	 * This function converts any slash markers back into actual slashes
-	 *
-	 * @param	string
-	 * @param	string
-	 * @return	void
-	 */
-	protected function _unescape_slashes(&$val, $key)
-	{
-		if (is_string($val))
-		{
-	 		$val= str_replace('{{slash}}', '\\', $val);
-		}
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Garbage collection
-	 *
-	 * This deletes expired session rows from database
-	 * if the probability percentage is met
-	 *
-	 * @return	void
-	 */
-	protected function _sess_gc()
-	{
-		if ($this->sess_use_database !== TRUE)
-		{
-			return;
-		}
-
-		$probability = ini_get('session.gc_probability');
-		$divisor = ini_get('session.gc_divisor');
-
-		srand(time());
-		if ((mt_rand(0, $divisor) / $divisor) < $probability)
-		{
-			$expire = $this->now - $this->sess_expiration;
-
-			$this->CI->db->where('last_activity < '.$expire);
-			$this->CI->db->delete($this->sess_table_name);
-
-			log_message('debug', 'Session garbage collection performed.');
-		}
-	}
-
-}
-
-/* End of file Session.php */
-/* Location: ./system/libraries/Session.php */
\ No newline at end of file
diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php
new file mode 100644
index 0000000..85a4835
--- /dev/null
+++ b/system/libraries/Session/Session.php
@@ -0,0 +1,753 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 2.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CodeIgniter Session Class
+ *
+ * The user interface defined by EllisLabs, now with puggable drivers to manage different storage mechanisms.
+ * By default, the cookie session driver will load, but the 'sess_driver' config/param item (see above) can be
+ * used to specify the 'native' driver, or any other you might create.
+ * Once loaded, this driver setup is a drop-in replacement for the former CI_Session library, taking its place as the
+ * 'session' member of the global controller framework (e.g.: $CI->session or $this->session).
+ * In keeping with the CI_Driver methodology, multiple drivers may be loaded, although this might be a bit confusing.
+ * The CI_Session library class keeps track of the most recently loaded driver as "current" to call for driver methods.
+ * Ideally, one driver is loaded and all calls go directly through the main library interface. However, any methods
+ * called through the specific driver will switch the "current" driver to itself before invoking the library method
+ * (which will then call back into the driver for low-level operations). So, alternation between two drivers can be
+ * achieved by specifying which driver to use for each call (e.g.: $this->session->native->set_userdata('foo', 'bar');
+ * $this->session->cookie->userdata('foo'); $this->session->native->unset_userdata('foo');). Notice in the previous
+ * example that the _native_ userdata value 'foo' would be set to 'bar', which would NOT be returned by the call for
+ * the _cookie_ userdata 'foo', nor would the _cookie_ value be unset by the call to unset the _native_ 'foo' value.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Sessions
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/libraries/sessions.html
+ */
+class CI_Session extends CI_Driver_Library {
+
+	/**
+	 * Initialization parameters
+	 *
+	 * @var	array
+	 */
+	public $params = array();
+
+	/**
+	 * Current driver in use
+	 *
+	 * @var	string
+	 */
+	protected $current = NULL;
+
+	/**
+	 * User data
+	 *
+	 * @var	array
+	 */
+	protected $userdata = array();
+
+	// ------------------------------------------------------------------------
+
+	const FLASHDATA_KEY = 'flash';
+	const FLASHDATA_NEW = ':new:';
+	const FLASHDATA_OLD = ':old:';
+	const FLASHDATA_EXP = ':exp:';
+	const EXPIRATION_KEY = '__expirations';
+	const TEMP_EXP_DEF = 300;
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * CI_Session constructor
+	 *
+	 * The constructor loads the configured driver ('sess_driver' in config.php or as a parameter), running
+	 * routines in its constructor, and manages flashdata aging.
+	 *
+	 * @param	array	Configuration parameters
+	 * @return	void
+	 */
+	public function __construct(array $params = array())
+	{
+		$CI =& get_instance();
+
+		// No sessions under CLI
+		if ($CI->input->is_cli_request())
+		{
+			return;
+		}
+
+		log_message('debug', 'CI_Session Class Initialized');
+
+		// Get valid drivers list
+		$this->valid_drivers = array(
+			'native',
+			'cookie'
+		);
+		$key = 'sess_valid_drivers';
+		$drivers = isset($params[$key]) ? $params[$key] : $CI->config->item($key);
+		if ($drivers)
+		{
+			// Add driver names to valid list
+			foreach ((array) $drivers as $driver)
+			{
+				if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
+				{
+					$this->valid_drivers[] = $driver;
+				}
+			}
+		}
+
+		// Get driver to load
+		$key = 'sess_driver';
+		$driver = isset($params[$key]) ? $params[$key] : $CI->config->item($key);
+		if ( ! $driver)
+		{
+			$driver = 'cookie';
+		}
+
+		if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
+		{
+			$this->valid_drivers[] = $driver;
+		}
+
+		// Save a copy of parameters in case drivers need access
+		$this->params = $params;
+
+		// Load driver and get array reference
+		$this->load_driver($driver);
+
+		// Delete 'old' flashdata (from last request)
+		$this->_flashdata_sweep();
+
+		// Mark all new flashdata as old (data will be deleted before next request)
+		$this->_flashdata_mark();
+
+		// Delete expired tempdata
+		$this->_tempdata_sweep();
+
+		log_message('debug', 'CI_Session routines successfully run');
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Loads session storage driver
+	 *
+	 * @param	string	Driver classname
+	 * @return	object	Loaded driver object
+	 */
+	public function load_driver($driver)
+	{
+		// Save reference to most recently loaded driver as library default and sync userdata
+		$this->current = parent::load_driver($driver);
+		$this->userdata =& $this->current->get_userdata();
+		return $this->current;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Select default session storage driver
+	 *
+	 * @param	string	Driver name
+	 * @return	void
+	 */
+	public function select_driver($driver)
+	{
+		// Validate driver name
+		$prefix = (string) get_instance()->config->item('subclass_prefix');
+		$child = strtolower(str_replace(array('CI_', $prefix, $this->lib_name.'_'), '', $driver));
+		if (in_array($child, array_map('strtolower', $this->valid_drivers)))
+		{
+			// See if driver is loaded
+			if (isset($this->$child))
+			{
+				// See if driver is already current
+				if ($this->$child !== $this->current)
+				{
+					// Make driver current and sync userdata
+					$this->current = $this->$child;
+					$this->userdata =& $this->current->get_userdata();
+				}
+			}
+			else
+			{
+				// Load new driver
+				$this->load_driver($child);
+			}
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Destroy the current session
+	 *
+	 * @return	void
+	 */
+	public function sess_destroy()
+	{
+		// Just call destroy on driver
+		$this->current->sess_destroy();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Regenerate the current session
+	 *
+	 * @param	bool	Destroy session data flag (default: false)
+	 * @return	void
+	 */
+	public function sess_regenerate($destroy = FALSE)
+	{
+		// Call regenerate on driver and resync userdata
+		$this->current->sess_regenerate($destroy);
+		$this->userdata =& $this->current->get_userdata();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch a specific item from the session array
+	 *
+	 * @param	string	Item key
+	 * @return	string	Item value or NULL if not found
+	 */
+	public function userdata($item)
+	{
+		return isset($this->userdata[$item]) ? $this->userdata[$item] : NULL;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch all session data
+	 *
+	 * @return	array	User data array
+	 */
+	public function all_userdata()
+	{
+		return isset($this->userdata) ? $this->userdata : NULL;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch all flashdata
+	 *
+	 * @return	array	Flash data array
+	 */
+	public function all_flashdata()
+	{
+		$out = array();
+
+		// loop through all userdata
+		foreach ($this->all_userdata() as $key => $val)
+		{
+			// if it contains flashdata, add it
+			if (strpos($key, self::FLASHDATA_KEY.self::FLASHDATA_OLD) !== FALSE)
+			{
+				$key = str_replace(self::FLASHDATA_KEY.self::FLASHDATA_OLD, '', $key);
+				$out[$key] = $val;
+			}
+		}
+		return $out;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Add or change data in the "userdata" array
+	 *
+	 * @param	mixed	Item name or array of items
+	 * @param	string	Item value or empty string
+	 * @return	void
+	 */
+	public function set_userdata($newdata = array(), $newval = '')
+	{
+		// Wrap params as array if singular
+		if (is_string($newdata))
+		{
+			$newdata = array($newdata => $newval);
+		}
+
+		// Set each name/value pair
+		if (count($newdata) > 0)
+		{
+			foreach ($newdata as $key => $val)
+			{
+				$this->userdata[$key] = $val;
+			}
+		}
+
+		// Tell driver data changed
+		$this->current->sess_save();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Delete a session variable from the "userdata" array
+	 *
+	 * @param	mixed	Item name or array of item names
+	 * @return	void
+	 */
+	public function unset_userdata($newdata = array())
+	{
+		// Wrap single name as array
+		if (is_string($newdata))
+		{
+			$newdata = array($newdata => '');
+		}
+
+		// Unset each item name
+		if (count($newdata) > 0)
+		{
+			foreach (array_keys($newdata) as $key)
+			{
+				unset($this->userdata[$key]);
+			}
+		}
+
+		// Tell driver data changed
+		$this->current->sess_save();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Determine if an item exists
+	 *
+	 * @param	string	Item name
+	 * @return	bool
+	 */
+	public function has_userdata($item)
+	{
+		return isset($this->userdata[$item]);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Add or change flashdata, only available until the next request
+	 *
+	 * @param	mixed	Item name or array of items
+	 * @param	string	Item value or empty string
+	 * @return	void
+	 */
+	public function set_flashdata($newdata = array(), $newval = '')
+	{
+		// Wrap item as array if singular
+		if (is_string($newdata))
+		{
+			$newdata = array($newdata => $newval);
+		}
+
+		// Prepend each key name and set value
+		if (count($newdata) > 0)
+		{
+			foreach ($newdata as $key => $val)
+			{
+				$flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
+				$this->set_userdata($flashdata_key, $val);
+			}
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Keeps existing flashdata available to next request.
+	 *
+	 * @param	mixed	Item key(s)
+	 * @return	void
+	 */
+	public function keep_flashdata($key)
+	{
+
+		if (is_array($key))
+		{
+			foreach ($key as $k)
+			{
+				$this->keep_flashdata($k);
+			}
+
+			return;
+		}
+
+		// 'old' flashdata gets removed. Here we mark all flashdata as 'new' to preserve it from _flashdata_sweep()
+		// Note the function will return NULL if the $key provided cannot be found
+		$old_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
+		$value = $this->userdata($old_flashdata_key);
+
+		$new_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
+		$this->set_userdata($new_flashdata_key, $value);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch a specific flashdata item from the session array
+	 *
+	 * @param	string	Item key
+	 * @return	string
+	 */
+	public function flashdata($key)
+	{
+		// Prepend key and retrieve value
+		$flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
+		return $this->userdata($flashdata_key);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Add or change tempdata, only available until expiration
+	 *
+	 * @param	mixed	Item name or array of items
+	 * @param	string	Item value or empty string
+	 * @param	int	Item lifetime in seconds or 0 for default
+	 * @return	void
+	 */
+	public function set_tempdata($newdata = array(), $newval = '', $expire = 0)
+	{
+		// Set expiration time
+		$expire = time() + ($expire ? $expire : self::TEMP_EXP_DEF);
+
+		// Wrap item as array if singular
+		if (is_string($newdata))
+		{
+			$newdata = array($newdata => $newval);
+		}
+
+		// Get or create expiration list
+		$expirations = $this->userdata(self::EXPIRATION_KEY);
+		if ( ! $expirations)
+		{
+			$expirations = array();
+		}
+
+		// Prepend each key name and set value
+		if (count($newdata) > 0)
+		{
+			foreach ($newdata as $key => $val)
+			{
+				$tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
+				$expirations[$tempdata_key] = $expire;
+				$this->set_userdata($tempdata_key, $val);
+			}
+		}
+
+		// Update expiration list
+		$this->set_userdata(self::EXPIRATION_KEY, $expirations);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Delete a temporary session variable from the "userdata" array
+	 *
+	 * @param	mixed	Item name or array of item names
+	 * @return	void
+	 */
+	public function unset_tempdata($newdata = array())
+	{
+		// Get expirations list
+		$expirations = $this->userdata(self::EXPIRATION_KEY);
+		if (empty($expirations))
+		{
+			// Nothing to do
+			return;
+		}
+
+		// Wrap single name as array
+		if (is_string($newdata))
+		{
+			$newdata = array($newdata => '');
+		}
+
+		// Prepend each item name and unset
+		if (count($newdata) > 0)
+		{
+			foreach (array_keys($newdata) as $key)
+			{
+				$tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
+				unset($expirations[$tempdata_key]);
+				$this->unset_userdata($tempdata_key);
+			}
+		}
+
+		// Update expiration list
+		$this->set_userdata(self::EXPIRATION_KEY, $expirations);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch a specific tempdata item from the session array
+	 *
+	 * @param	string	Item key
+	 * @return	string
+	 */
+	public function tempdata($key)
+	{
+		// Prepend key and return value
+		$tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
+		return $this->userdata($tempdata_key);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Identifies flashdata as 'old' for removal
+	 * when _flashdata_sweep() runs.
+	 *
+	 * @return	void
+	 */
+	protected function _flashdata_mark()
+	{
+		foreach ($this->all_userdata() as $name => $value)
+		{
+			$parts = explode(self::FLASHDATA_NEW, $name);
+			if (count($parts) === 2)
+			{
+				$new_name = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$parts[1];
+				$this->set_userdata($new_name, $value);
+				$this->unset_userdata($name);
+			}
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Removes all flashdata marked as 'old'
+	 *
+	 * @return	void
+	 */
+	protected function _flashdata_sweep()
+	{
+		$userdata = $this->all_userdata();
+		foreach (array_keys($userdata) as $key)
+		{
+			if (strpos($key, self::FLASHDATA_OLD))
+			{
+				$this->unset_userdata($key);
+			}
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Removes all expired tempdata
+	 *
+	 * @return	void
+	 */
+	protected function _tempdata_sweep()
+	{
+		// Get expirations list
+		$expirations = $this->userdata(self::EXPIRATION_KEY);
+		if (empty($expirations))
+		{
+			// Nothing to do
+			return;
+		}
+
+		// Unset expired elements
+		$now = time();
+		$userdata = $this->all_userdata();
+		foreach (array_keys($userdata) as $key)
+		{
+			if (strpos($key, self::FLASHDATA_EXP) && $expirations[$key] < $now)
+			{
+				unset($expirations[$key]);
+				$this->unset_userdata($key);
+			}
+		}
+
+		// Update expiration list
+		$this->set_userdata(self::EXPIRATION_KEY, $expirations);
+	}
+
+}
+
+// ------------------------------------------------------------------------
+
+/**
+ * CI_Session_driver Class
+ *
+ * Extend this class to make a new CI_Session driver.
+ * A CI_Session driver basically manages an array of name/value pairs with some sort of storage mechanism.
+ * To make a new driver, derive from (extend) CI_Session_driver. Overload the initialize method and read or create
+ * session data. Then implement a save handler to write changed data to storage (sess_save), a destroy handler
+ * to remove deleted data (sess_destroy), and an access handler to expose the data (get_userdata).
+ * Put your driver in the libraries/Session/drivers folder anywhere in the loader paths. This includes the
+ * application directory, the system directory, or any path you add with $CI->load->add_package_path().
+ * Your driver must be named CI_Session_<name>, and your filename must be Session_<name>.php,
+ * preferably also capitalized. (e.g.: CI_Session_foo in libraries/Session/drivers/Session_foo.php)
+ * Then specify the driver by setting 'sess_driver' in your config file or as a parameter when loading the CI_Session
+ * object. (e.g.: $config['sess_driver'] = 'foo'; OR $CI->load->driver('session', array('sess_driver' => 'foo')); )
+ * Already provided are the Native driver, which manages the native PHP $_SESSION array, and
+ * the Cookie driver, which manages the data in a browser cookie, with optional extra storage in a database table.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Sessions
+ * @author		EllisLab Dev Team
+ */
+abstract class CI_Session_driver extends CI_Driver {
+
+	/**
+	 * CI Singleton
+	 *
+	 * @see	get_instance()
+	 * @var	object
+	 */
+	protected $CI;
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * Gets the CI singleton, so that individual drivers
+	 * don't have to do it separately.
+	 *
+	 * @return	void
+	 */
+	public function __construct()
+	{
+		$this->CI =& get_instance();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Decorate
+	 *
+	 * Decorates the child with the parent driver lib's methods and properties
+	 *
+	 * @param	object	Parent library object
+	 * @return	void
+	 */
+	public function decorate($parent)
+	{
+		// Call base class decorate first
+		parent::decorate($parent);
+
+		// Call initialize method now that driver has access to $this->_parent
+		$this->initialize();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * __call magic method
+	 *
+	 * Handles access to the parent driver library's methods
+	 *
+	 * @param	string	Library method name
+	 * @param	array	Method arguments (default: none)
+	 * @return	mixed
+	 */
+	public function __call($method, $args = array())
+	{
+		// Make sure the parent library uses this driver
+		$this->_parent->select_driver(get_class($this));
+		return parent::__call($method, $args);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Initialize driver
+	 *
+	 * @return	void
+	 */
+	protected function initialize()
+	{
+		// Overload this method to implement initialization
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Save the session data
+	 *
+	 * Data in the array has changed - perform any storage synchronization
+	 * necessary. The child class MUST implement this abstract method!
+	 *
+	 * @return	void
+	 */
+	abstract public function sess_save();
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Destroy the current session
+	 *
+	 * Clean up storage for this session - it has been terminated.
+	 * The child class MUST implement this abstract method!
+	 *
+	 * @return	void
+	 */
+	abstract public function sess_destroy();
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Regenerate the current session
+	 *
+	 * Regenerate the session ID.
+	 * The child class MUST implement this abstract method!
+	 *
+	 * @param	bool	Destroy session data flag (default: false)
+	 * @return	void
+	 */
+	abstract public function sess_regenerate($destroy = FALSE);
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Get a reference to user data array
+	 *
+	 * Give array access to the main CI_Session object.
+	 * The child class MUST implement this abstract method!
+	 *
+	 * @return	array	Reference to userdata
+	 */
+	abstract public function &get_userdata();
+
+}
+
+/* End of file Session.php */
+/* Location: ./system/libraries/Session/Session.php */
\ No newline at end of file
diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php
new file mode 100644
index 0000000..9392a4d
--- /dev/null
+++ b/system/libraries/Session/drivers/Session_cookie.php
@@ -0,0 +1,845 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Cookie-based session management driver
+ *
+ * This is the classic CI_Session functionality, as written by EllisLab, abstracted out to a driver.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Sessions
+ * @author		EllisLab Dev Team
+ * @link		http://codeigniter.com/user_guide/libraries/sessions.html
+ */
+class CI_Session_cookie extends CI_Session_driver {
+
+	/**
+	 * Whether to encrypt the session cookie
+	 *
+	 * @var bool
+	 */
+	public $sess_encrypt_cookie		= FALSE;
+
+	/**
+	 * Whether to use to the database for session storage
+	 *
+	 * @var bool
+	 */
+	public $sess_use_database		= FALSE;
+
+	/**
+	 * Name of the database table in which to store sessions
+	 *
+	 * @var string
+	 */
+	public $sess_table_name			= '';
+
+	/**
+	 * Length of time (in seconds) for sessions to expire
+	 *
+	 * @var int
+	 */
+	public $sess_expiration			= 7200;
+
+	/**
+	 * Whether to kill session on close of browser window
+	 *
+	 * @var bool
+	 */
+	public $sess_expire_on_close	= FALSE;
+
+	/**
+	 * Whether to match session on ip address
+	 *
+	 * @var bool
+	 */
+	public $sess_match_ip			= FALSE;
+
+	/**
+	 * Whether to match session on user-agent
+	 *
+	 * @var bool
+	 */
+	public $sess_match_useragent	= TRUE;
+
+	/**
+	 * Name of session cookie
+	 *
+	 * @var string
+	 */
+	public $sess_cookie_name		= 'ci_session';
+
+	/**
+	 * Session cookie prefix
+	 *
+	 * @var string
+	 */
+	public $cookie_prefix			= '';
+
+	/**
+	 * Session cookie path
+	 *
+	 * @var string
+	 */
+	public $cookie_path				= '';
+
+	/**
+	 * Session cookie domain
+	 *
+	 * @var string
+	 */
+	public $cookie_domain			= '';
+
+	/**
+	 * Whether to set the cookie only on HTTPS connections
+	 *
+	 * @var bool
+	 */
+	public $cookie_secure			= FALSE;
+
+	/**
+	 * Whether cookie should be allowed only to be sent by the server
+	 *
+	 * @var bool
+	 */
+	public $cookie_httponly 		= FALSE;
+
+	/**
+	 * Interval at which to update session
+	 *
+	 * @var int
+	 */
+	public $sess_time_to_update		= 300;
+
+	/**
+	 * Key with which to encrypt the session cookie
+	 *
+	 * @var string
+	 */
+	public $encryption_key			= '';
+
+	/**
+	 * Timezone to use for the current time
+	 *
+	 * @var string
+	 */
+	public $time_reference			= 'local';
+
+	/**
+	 * Session data
+	 *
+	 * @var array
+	 */
+	public $userdata				= array();
+
+	/**
+	 * Current time
+	 *
+	 * @var int
+	 */
+	public $now;
+
+	/**
+	 * Default userdata keys
+	 *
+	 * @var	array
+	 */
+	protected $defaults = array(
+		'session_id' => NULL,
+		'ip_address' => NULL,
+		'user_agent' => NULL,
+		'last_activity' => NULL
+	);
+
+	/**
+	 * Data needs DB update flag
+	 *
+	 * @var	bool
+	 */
+	protected $data_dirty = FALSE;
+
+	/**
+	 * Initialize session driver object
+	 *
+	 * @return	void
+	 */
+	protected function initialize()
+	{
+		// Set all the session preferences, which can either be set
+		// manually via the $params array or via the config file
+		$prefs = array(
+			'sess_encrypt_cookie',
+			'sess_use_database',
+			'sess_table_name',
+			'sess_expiration',
+			'sess_expire_on_close',
+			'sess_match_ip',
+			'sess_match_useragent',
+			'sess_cookie_name',
+			'cookie_path',
+			'cookie_domain',
+			'cookie_secure',
+			'cookie_httponly',
+			'sess_time_to_update',
+			'time_reference',
+			'cookie_prefix',
+			'encryption_key'
+		);
+
+		foreach ($prefs as $key)
+		{
+			$this->$key = isset($this->_parent->params[$key])
+				? $this->_parent->params[$key]
+				: $this->CI->config->item($key);
+		}
+
+		if (empty($this->encryption_key))
+		{
+			show_error('In order to use the Cookie Session driver you are required to set an encryption key in your config file.');
+		}
+
+		// Do we need encryption? If so, load the encryption class
+		if ($this->sess_encrypt_cookie === TRUE)
+		{
+			$this->CI->load->library('encrypt');
+		}
+
+		// Check for database
+		if ($this->sess_use_database === TRUE && $this->sess_table_name !== '')
+		{
+			// Load database driver
+			$this->CI->load->database();
+
+			// Register shutdown function
+			register_shutdown_function(array($this, '_update_db'));
+		}
+
+		// Set the "now" time. Can either be GMT or server time, based on the config prefs.
+		// We use this to set the "last activity" time
+		$this->now = $this->_get_time();
+
+		// Set the session length. If the session expiration is
+		// set to zero we'll set the expiration two years from now.
+		if ($this->sess_expiration === 0)
+		{
+			$this->sess_expiration = (60*60*24*365*2);
+		}
+
+		// Set the cookie name
+		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
+
+		// Run the Session routine. If a session doesn't exist we'll
+		// create a new one. If it does, we'll update it.
+		if ( ! $this->_sess_read())
+		{
+			$this->_sess_create();
+		}
+		else
+		{
+			$this->_sess_update();
+		}
+
+		// Delete expired sessions if necessary
+		$this->_sess_gc();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Write the session data
+	 *
+	 * @return	void
+	 */
+	public function sess_save()
+	{
+		// Check for database
+		if ($this->sess_use_database === TRUE)
+		{
+			// Mark custom data as dirty so we know to update the DB
+			$this->data_dirty = TRUE;
+		}
+
+		// Write the cookie
+		$this->_set_cookie();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Destroy the current session
+	 *
+	 * @return	void
+	 */
+	public function sess_destroy()
+	{
+		// Kill the session DB row
+		if ($this->sess_use_database === TRUE && isset($this->userdata['session_id']))
+		{
+			$this->CI->db->delete($this->sess_table_name, array('session_id' => $this->userdata['session_id']));
+			$this->data_dirty = FALSE;
+		}
+
+		// Kill the cookie
+		$this->_setcookie($this->sess_cookie_name, '', ($this->now - 31500000),
+			$this->cookie_path, $this->cookie_domain, 0);
+
+		// Kill session data
+		$this->userdata = array();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Regenerate the current session
+	 *
+	 * Regenerate the session id
+	 *
+	 * @param	bool	Destroy session data flag (default: false)
+	 * @return	void
+	 */
+	public function sess_regenerate($destroy = FALSE)
+	{
+		// Check destroy flag
+		if ($destroy)
+		{
+			// Destroy old session and create new one
+			$this->sess_destroy();
+			$this->_sess_create();
+		}
+		else
+		{
+			// Just force an update to recreate the id
+			$this->_sess_update(TRUE);
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Get a reference to user data array
+	 *
+	 * @return	array	Reference to userdata
+	 */
+	public function &get_userdata()
+	{
+		return $this->userdata;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Fetch the current session data if it exists
+	 *
+	 * @return	bool
+	 */
+	protected function _sess_read()
+	{
+		// Fetch the cookie
+		$session = $this->CI->input->cookie($this->sess_cookie_name);
+
+		// No cookie? Goodbye cruel world!...
+		if ($session === NULL)
+		{
+			log_message('debug', 'A session cookie was not found.');
+			return FALSE;
+		}
+
+		$len = strlen($session) - 40;
+
+		if ($len < 0)
+		{
+			log_message('debug', 'The session cookie was not signed.');
+			return FALSE;
+		}
+
+		// Check cookie authentication
+		$hmac	 = substr($session, $len);
+		$session = substr($session, 0, $len);
+
+		if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key))
+		{
+			log_message('error', 'The session cookie data did not match what was expected.');
+			$this->sess_destroy();
+			return FALSE;
+		}
+
+		// Check for encryption
+		if ($this->sess_encrypt_cookie === TRUE)
+		{
+			// Decrypt the cookie data
+			$session = $this->CI->encrypt->decode($session);
+		}
+
+		// Unserialize the session array
+		$session = $this->_unserialize($session);
+
+		// Is the session data we unserialized an array with the correct format?
+		if ( ! is_array($session) OR ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], $session['last_activity']))
+		{
+			$this->sess_destroy();
+			return FALSE;
+		}
+
+		// Is the session current?
+		if (($session['last_activity'] + $this->sess_expiration) < $this->now OR $session['last_activity'] > $this->now)
+		{
+			$this->sess_destroy();
+			return FALSE;
+		}
+
+		// Does the IP match?
+		if ($this->sess_match_ip === TRUE && $session['ip_address'] !== $this->CI->input->ip_address())
+		{
+			$this->sess_destroy();
+			return FALSE;
+		}
+
+		// Does the User Agent Match?
+		if ($this->sess_match_useragent === TRUE &&
+			trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120)))
+		{
+			$this->sess_destroy();
+			return FALSE;
+		}
+
+		// Is there a corresponding session in the DB?
+		if ($this->sess_use_database === TRUE)
+		{
+			$this->CI->db->where('session_id', $session['session_id']);
+
+			if ($this->sess_match_ip === TRUE)
+			{
+				$this->CI->db->where('ip_address', $session['ip_address']);
+			}
+
+			if ($this->sess_match_useragent === TRUE)
+			{
+				$this->CI->db->where('user_agent', $session['user_agent']);
+			}
+
+			// Is caching in effect? Turn it off
+			$db_cache = $this->CI->db->cache_on;
+			$this->CI->db->cache_off();
+
+			$query = $this->CI->db->limit(1)->get($this->sess_table_name);
+
+			// Was caching in effect?
+			if ($db_cache)
+			{
+				// Turn it back on
+				$this->CI->db->cache_on();
+			}
+
+			// No result? Kill it!
+			if (empty($query) OR $query->num_rows() === 0)
+			{
+				$this->sess_destroy();
+				return FALSE;
+			}
+
+			// Is there custom data? If so, add it to the main session array
+			$row = $query->row();
+			if ( ! empty($row->user_data))
+			{
+				$custom_data = $this->_unserialize($row->user_data);
+
+				if (is_array($custom_data))
+				{
+					$session = $session + $custom_data;
+				}
+			}
+		}
+
+		// Session is valid!
+		$this->userdata = $session;
+		return TRUE;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Create a new session
+	 *
+	 * @return	void
+	 */
+	protected function _sess_create()
+	{
+		// Initialize userdata
+		$this->userdata = array(
+			'session_id'	=> $this->_make_sess_id(),
+			'ip_address'	=> $this->CI->input->ip_address(),
+			'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
+			'last_activity'	=> $this->now,
+		);
+
+		// Check for database
+		if ($this->sess_use_database === TRUE)
+		{
+			// Add empty user_data field and save the data to the DB
+			$this->CI->db->set('user_data', '')->insert($this->sess_table_name, $this->userdata);
+		}
+
+		// Write the cookie
+		$this->_set_cookie();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Update an existing session
+	 *
+	 * @param	bool	Force update flag (default: false)
+	 * @return	void
+	 */
+	protected function _sess_update($force = FALSE)
+	{
+		// We only update the session every five minutes by default (unless forced)
+		if ( ! $force && ($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
+		{
+			return;
+		}
+
+		// Update last activity to now
+		$this->userdata['last_activity'] = $this->now;
+
+		// Save the old session id so we know which DB record to update
+		$old_sessid = $this->userdata['session_id'];
+
+		// Changing the session ID during an AJAX call causes problems
+		if ( ! $this->CI->input->is_ajax_request())
+		{
+			// Get new id
+			$this->userdata['session_id'] = $this->_make_sess_id();
+		}
+
+		// Check for database
+		if ($this->sess_use_database === TRUE)
+		{
+			$this->CI->db->where('session_id', $old_sessid);
+
+			if ($this->sess_match_ip === TRUE)
+			{
+				$this->CI->db->where('ip_address', $this->CI->input->ip_address());
+			}
+
+			if ($this->sess_match_useragent === TRUE)
+			{
+				$this->CI->db->where('user_agent', trim(substr($this->CI->input->user_agent(), 0, 120)));
+			}
+
+			// Update the session ID and last_activity field in the DB
+			$this->CI->db->update($this->sess_table_name,
+				array(
+					'last_activity' => $this->now,
+					'session_id' => $this->userdata['session_id']
+				)
+			);
+		}
+
+		// Write the cookie
+		$this->_set_cookie();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Update database with current data
+	 *
+	 * This gets called from the shutdown function and also
+	 * registered with PHP to run at the end of the request
+	 * so it's guaranteed to update even when a fatal error
+	 * occurs. The first call makes the update and clears the
+	 * dirty flag so it won't happen twice.
+	 *
+	 * @return	void
+	 */
+	public function _update_db()
+	{
+		// Check for database and dirty flag and unsaved
+		if ($this->sess_use_database === TRUE && $this->data_dirty === TRUE)
+		{
+			// Set up activity and data fields to be set
+			// If we don't find custom data, user_data will remain an empty string
+			$set = array(
+				'last_activity' => $this->userdata['last_activity'],
+				'user_data' => ''
+			);
+
+			// Get the custom userdata, leaving out the defaults
+			// (which get stored in the cookie)
+			$userdata = array_diff_key($this->userdata, $this->defaults);
+
+			// Did we find any custom data?
+			if ( ! empty($userdata))
+			{
+				// Serialize the custom data array so we can store it
+				$set['user_data'] = $this->_serialize($userdata);
+			}
+
+			// Run the update query
+			// Any time we change the session id, it gets updated immediately,
+			// so our where clause below is always safe
+			$this->CI->db->where('session_id', $this->userdata['session_id']);
+
+			if ($this->sess_match_ip === TRUE)
+			{
+				$this->CI->db->where('ip_address', $this->CI->input->ip_address());
+			}
+
+			if ($this->sess_match_useragent === TRUE)
+			{
+				$this->CI->db->where('user_agent', trim(substr($this->CI->input->user_agent(), 0, 120)));
+			}
+
+			$this->CI->db->update($this->sess_table_name, $set);
+
+			// Clear dirty flag to prevent double updates
+			$this->data_dirty = FALSE;
+
+			log_message('debug', 'CI_Session Data Saved To DB');
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Generate a new session id
+	 *
+	 * @return	string	Hashed session id
+	 */
+	protected function _make_sess_id()
+	{
+		$new_sessid = '';
+		do
+		{
+			$new_sessid .= mt_rand(0, mt_getrandmax());
+		}
+		while (strlen($new_sessid) < 32);
+
+		// To make the session ID even more secure we'll combine it with the user's IP
+		$new_sessid .= $this->CI->input->ip_address();
+
+		// Turn it into a hash and return
+		return md5(uniqid($new_sessid, TRUE));
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Get the "now" time
+	 *
+	 * @return	int	 Time
+	 */
+	protected function _get_time()
+	{
+		if ($this->time_reference === 'local' OR $this->time_reference === date_default_timezone_get())
+		{
+			return time();
+		}
+
+		$datetime = new DateTime('now', new DateTimeZone($this->time_reference));
+		sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second);
+
+		return mktime($hour, $minute, $second, $month, $day, $year);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Write the session cookie
+	 *
+	 * @return	void
+	 */
+	protected function _set_cookie()
+	{
+		// Get userdata (only defaults if database)
+		$cookie_data = ($this->sess_use_database === TRUE)
+				? array_intersect_key($this->userdata, $this->defaults)
+				: $this->userdata;
+
+		// Serialize the userdata for the cookie
+		$cookie_data = $this->_serialize($cookie_data);
+
+		if ($this->sess_encrypt_cookie === TRUE)
+		{
+			$cookie_data = $this->CI->encrypt->encode($cookie_data);
+		}
+
+		// Require message authentication
+		$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);
+
+		$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
+
+		// Set the cookie
+		$this->_setcookie($this->sess_cookie_name, $cookie_data, $expire, $this->cookie_path, $this->cookie_domain,
+			$this->cookie_secure, $this->cookie_httponly);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Set a cookie with the system
+	 *
+	 * This abstraction of the setcookie call allows overriding for unit testing
+	 *
+	 * @param	string	Cookie name
+	 * @param	string	Cookie value
+	 * @param	int	Expiration time
+	 * @param	string	Cookie path
+	 * @param	string	Cookie domain
+	 * @param	bool	Secure connection flag
+	 * @param	bool	HTTP protocol only flag
+	 * @return	void
+	 */
+	protected function _setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = FALSE, $httponly = FALSE)
+	{
+		setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Serialize an array
+	 *
+	 * This function first converts any slashes found in the array to a temporary
+	 * marker, so when it gets unserialized the slashes will be preserved
+	 *
+	 * @param	mixed	Data to serialize
+	 * @return	string	Serialized data
+	 */
+	protected function _serialize($data)
+	{
+		if (is_array($data))
+		{
+			array_walk_recursive($data, array(&$this, '_escape_slashes'));
+		}
+		elseif (is_string($data))
+		{
+			$data = str_replace('\\', '{{slash}}', $data);
+		}
+
+		return serialize($data);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Escape slashes
+	 *
+	 * This function converts any slashes found into a temporary marker
+	 *
+	 * @param	string	Value
+	 * @param	string	Key
+	 * @return	void
+	 */
+	protected function _escape_slashes(&$val, $key)
+	{
+		if (is_string($val))
+		{
+			$val = str_replace('\\', '{{slash}}', $val);
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Unserialize
+	 *
+	 * This function unserializes a data string, then converts any
+	 * temporary slash markers back to actual slashes
+	 *
+	 * @param	mixed	Data to unserialize
+	 * @return	mixed	Unserialized data
+	 */
+	protected function _unserialize($data)
+	{
+		$data = @unserialize(trim($data));
+
+		if (is_array($data))
+		{
+			array_walk_recursive($data, array(&$this, '_unescape_slashes'));
+			return $data;
+		}
+
+		return is_string($data) ? str_replace('{{slash}}', '\\', $data) : $data;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Unescape slashes
+	 *
+	 * This function converts any slash markers back into actual slashes
+	 *
+	 * @param	string	Value
+	 * @param	string	Key
+	 * @return	void
+	 */
+	protected function _unescape_slashes(&$val, $key)
+	{
+		if (is_string($val))
+		{
+	 		$val= str_replace('{{slash}}', '\\', $val);
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Garbage collection
+	 *
+	 * This deletes expired session rows from database
+	 * if the probability percentage is met
+	 *
+	 * @return	void
+	 */
+	protected function _sess_gc()
+	{
+		if ($this->sess_use_database !== TRUE)
+		{
+			return;
+		}
+
+		$probability = ini_get('session.gc_probability');
+		$divisor = ini_get('session.gc_divisor');
+
+		srand(time());
+		if ((mt_rand(0, $divisor) / $divisor) < $probability)
+		{
+			$expire = $this->now - $this->sess_expiration;
+			$this->CI->db->delete($this->sess_table_name, 'last_activity < '.$expire);
+
+			log_message('debug', 'Session garbage collection performed.');
+		}
+	}
+
+}
+
+/* End of file Session_cookie.php */
+/* Location: ./system/libraries/Session/drivers/Session_cookie.php */
\ No newline at end of file
diff --git a/system/libraries/Session/drivers/Session_native.php b/system/libraries/Session/drivers/Session_native.php
new file mode 100644
index 0000000..3e700ad
--- /dev/null
+++ b/system/libraries/Session/drivers/Session_native.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * Native PHP session management driver
+ *
+ * This is the driver that uses the native PHP $_SESSION array through the Session driver library.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Sessions
+ * @author		EllisLab Dev Team
+ */
+class CI_Session_native extends CI_Session_driver {
+
+	/**
+	 * Initialize session driver object
+	 *
+	 * @return	void
+	 */
+	protected function initialize()
+	{
+		// Get config parameters
+		$config = array();
+		$prefs = array(
+			'sess_cookie_name',
+			'sess_expire_on_close',
+			'sess_expiration',
+			'sess_match_ip',
+			'sess_match_useragent',
+			'sess_time_to_update',
+			'cookie_prefix',
+			'cookie_path',
+			'cookie_domain',
+			'cookie_secure',
+			'cookie_httponly'
+		);
+
+		foreach ($prefs as $key)
+		{
+			$config[$key] = isset($this->_parent->params[$key])
+				? $this->_parent->params[$key]
+				: $this->CI->config->item($key);
+		}
+
+		// Set session name, if specified
+		if ($config['sess_cookie_name'])
+		{
+			// Differentiate name from cookie driver with '_id' suffix
+			$name = $config['sess_cookie_name'].'_id';
+			if ($config['cookie_prefix'])
+			{
+				// Prepend cookie prefix
+				$name = $config['cookie_prefix'].$name;
+			}
+			session_name($name);
+		}
+
+		// Set expiration, path, and domain
+		$expire = 7200;
+		$path = '/';
+		$domain = '';
+		$secure = (bool) $config['cookie_secure'];
+		$http_only = (bool) $config['cookie_httponly'];
+
+		if ($config['sess_expiration'] !== FALSE)
+		{
+			// Default to 2 years if expiration is "0"
+			$expire = ($config['sess_expiration'] == 0) ? (60*60*24*365*2) : $config['sess_expiration'];
+		}
+
+		if ($config['cookie_path'])
+		{
+			// Use specified path
+			$path = $config['cookie_path'];
+		}
+
+		if ($config['cookie_domain'])
+		{
+			// Use specified domain
+			$domain = $config['cookie_domain'];
+		}
+
+		session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain, $secure, $http_only);
+
+		// Start session
+		session_start();
+
+		// Check session expiration, ip, and agent
+		$now = time();
+		$destroy = FALSE;
+		if (isset($_SESSION['last_activity']) && (($_SESSION['last_activity'] + $expire) < $now OR $_SESSION['last_activity'] > $now))
+		{
+			// Expired - destroy
+			$destroy = TRUE;
+		}
+		elseif ($config['sess_match_ip'] === TRUE && isset($_SESSION['ip_address'])
+			&& $_SESSION['ip_address'] !== $this->CI->input->ip_address())
+		{
+			// IP doesn't match - destroy
+			$destroy = TRUE;
+		}
+		elseif ($config['sess_match_useragent'] === TRUE && isset($_SESSION['user_agent'])
+			&& $_SESSION['user_agent'] !== trim(substr($this->CI->input->user_agent(), 0, 50)))
+		{
+			// Agent doesn't match - destroy
+			$destroy = TRUE;
+		}
+
+		// Destroy expired or invalid session
+		if ($destroy)
+		{
+			// Clear old session and start new
+			$this->sess_destroy();
+			session_start();
+		}
+
+		// Check for update time
+		if ($config['sess_time_to_update'] && isset($_SESSION['last_activity'])
+			&& ($_SESSION['last_activity'] + $config['sess_time_to_update']) < $now)
+		{
+			// Changing the session ID amidst a series of AJAX calls causes problems
+			if( ! $this->CI->input->is_ajax_request())
+			{
+				// Regenerate ID, but don't destroy session
+				$this->sess_regenerate(FALSE);
+			}
+		}
+
+		// Set activity time
+		$_SESSION['last_activity'] = $now;
+
+		// Set matching values as required
+		if ($config['sess_match_ip'] === TRUE && ! isset($_SESSION['ip_address']))
+		{
+			// Store user IP address
+			$_SESSION['ip_address'] = $this->CI->input->ip_address();
+		}
+
+		if ($config['sess_match_useragent'] === TRUE && ! isset($_SESSION['user_agent']))
+		{
+			// Store user agent string
+			$_SESSION['user_agent'] = trim(substr($this->CI->input->user_agent(), 0, 50));
+		}
+
+		// Make session ID available
+		$_SESSION['session_id'] = session_id();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Save the session data
+	 *
+	 * @return	void
+	 */
+	public function sess_save()
+	{
+		// Nothing to do - changes to $_SESSION are automatically saved
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Destroy the current session
+	 *
+	 * @return	void
+	 */
+	public function sess_destroy()
+	{
+		// Cleanup session
+		$_SESSION = array();
+		$name = session_name();
+		if (isset($_COOKIE[$name]))
+		{
+			// Clear session cookie
+			$params = session_get_cookie_params();
+			setcookie($name, '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+			unset($_COOKIE[$name]);
+		}
+		session_destroy();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Regenerate the current session
+	 *
+	 * Regenerate the session id
+	 *
+	 * @param	bool	Destroy session data flag (default: FALSE)
+	 * @return	void
+	 */
+	public function sess_regenerate($destroy = FALSE)
+	{
+		// Just regenerate id, passing destroy flag
+		session_regenerate_id($destroy);
+		$_SESSION['session_id'] = session_id();
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Get a reference to user data array
+	 *
+	 * @return	array	Reference to userdata
+	 */
+	public function &get_userdata()
+	{
+		// Just return reference to $_SESSION
+		return $_SESSION;
+	}
+
+}
+
+/* End of file Session_native.php */
+/* Location: ./system/libraries/Session/drivers/Session_native.php */
\ No newline at end of file
diff --git a/system/libraries/Session/drivers/index.html b/system/libraries/Session/drivers/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/libraries/Session/drivers/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/libraries/Session/index.html b/system/libraries/Session/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/libraries/Session/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/libraries/Table.php b/system/libraries/Table.php
index 0f8404d..3d53b1c 100644
--- a/system/libraries/Table.php
+++ b/system/libraries/Table.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.3.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * HTML Table Generating Class
@@ -526,7 +527,7 @@
 	 */
 	protected function _default_template()
 	{
-		return  array(
+		return array(
 				'table_open'		=> '<table border="0" cellpadding="4" cellspacing="0">',
 
 				'thead_open'		=> '<thead>',
diff --git a/system/libraries/Trackback.php b/system/libraries/Trackback.php
index 9a680dc..d303503 100644
--- a/system/libraries/Trackback.php
+++ b/system/libraries/Trackback.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Trackback Class
@@ -38,13 +39,51 @@
  */
 class CI_Trackback {
 
-	public $time_format	= 'local';
+	/**
+	 * Character set
+	 *
+	 * @var	string
+	 */
 	public $charset		= 'UTF-8';
-	public $data			= array('url' => '', 'title' => '', 'excerpt' => '', 'blog_name' => '', 'charset' => '');
-	public $convert_ascii	= TRUE;
-	public $response		= '';
-	public $error_msg		= array();
 
+	/**
+	 * Trackback data
+	 *
+	 * @var	array
+	 */
+	public $data		= array('url' => '', 'title' => '', 'excerpt' => '', 'blog_name' => '', 'charset' => '');
+
+	/**
+	 * Convert ASCII flag
+	 *
+	 * Whether to convert high-ASCII and MS Word
+	 * characters to HTML entities.
+	 *
+	 * @var	bool
+	 */
+	public $convert_ascii	= TRUE;
+
+	/**
+	 * Response
+	 *
+	 * @var	string
+	 */
+	public $response	= '';
+
+	/**
+	 * Error messages list
+	 *
+	 * @var	string[]
+	 */
+	public $error_msg	= array();
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @return	void
+	 */
 	public function __construct()
 	{
 		log_message('debug', 'Trackback Class Initialized');
@@ -355,7 +394,7 @@
 			}
 		}
 
-		return preg_match('/^[0-9]+$/', $tb_id) ? $tb_id : FALSE;
+		return ctype_digit((string) $tb_id) ? $tb_id : FALSE;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Typography.php b/system/libraries/Typography.php
index a50934f..d83bf51 100644
--- a/system/libraries/Typography.php
+++ b/system/libraries/Typography.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Typography Class
diff --git a/system/libraries/Unit_test.php b/system/libraries/Unit_test.php
index 70ad8dc..05c7eef 100644
--- a/system/libraries/Unit_test.php
+++ b/system/libraries/Unit_test.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.3.1
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Unit Testing Class
@@ -38,13 +39,57 @@
  */
 class CI_Unit_test {
 
-	public $active					= TRUE;
-	public $results				= array();
-	public $strict					= FALSE;
-	protected $_template				= NULL;
-	protected $_template_rows			= NULL;
+	/**
+	 * Active flag
+	 *
+	 * @var	bool
+	 */
+	public $active			= TRUE;
+
+	/**
+	 * Test results
+	 *
+	 * @var	array
+	 */
+	public $results			= array();
+
+	/**
+	 * Strict comparison flag
+	 *
+	 * Whether to use === or == when comparing
+	 *
+	 * @var	bool
+	 */
+	public $strict			= FALSE;
+
+	/**
+	 * Template
+	 *
+	 * @var	string
+	 */
+	protected $_template		= NULL;
+
+	/**
+	 * Template rows
+	 *
+	 * @var	string
+	 */
+	protected $_template_rows	= NULL;
+
+	/**
+	 * List of visible test items
+	 *
+	 * @var	array
+	 */
 	protected $_test_items_visible	= array();
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @return	void
+	 */
 	public function __construct()
 	{
 		// These are the default items visible when a test is run.
@@ -86,9 +131,10 @@
 	 *
 	 * Runs the supplied tests
 	 *
-	 * @param	mixed
-	 * @param	mixed
-	 * @param	string
+	 * @param	mixed	$test
+	 * @param	mixed	$expected
+	 * @param	string	$test_name
+	 * @param	string	$notes
 	 * @return	string
 	 */
 	public function run($test, $expected = TRUE, $test_name = 'undefined', $notes = '')
@@ -106,13 +152,13 @@
 		}
 		else
 		{
-			$result = ($this->strict === TRUE) ? ($test === $expected) : ($test === $expected);
+			$result = ($this->strict === TRUE) ? ($test === $expected) : ($test == $expected);
 			$extype = gettype($expected);
 		}
 
 		$back = $this->_backtrace();
 
-		$report[] = array (
+		$report = array (
 							'test_name'			=> $test_name,
 							'test_datatype'		=> gettype($test),
 							'res_datatype'		=> $extype,
@@ -124,7 +170,7 @@
 
 		$this->results[] = $report;
 
-		return $this->report($this->result($report));
+		return $this->report($this->result(array($report)));
 	}
 
 	// --------------------------------------------------------------------
@@ -134,6 +180,7 @@
 	 *
 	 * Displays a table with the test data
 	 *
+	 * @param	array	 $result
 	 * @return	string
 	 */
 	public function report($result = array())
@@ -213,6 +260,7 @@
 	 *
 	 * Returns the raw result data
 	 *
+	 * @param	array	$results
 	 * @return	array
 	 */
 	public function result($results = array())
@@ -236,25 +284,11 @@
 					continue;
 				}
 
-				if (is_array($val))
+				if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val), FALSE)))
 				{
-					foreach ($val as $k => $v)
-					{
-						if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$v))))
-						{
-							$v = $line;
-						}
-						$temp[$CI->lang->line('ut_'.$k)] = $v;
-					}
+					$val = $line;
 				}
-				else
-				{
-					if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val))))
-					{
-						$val = $line;
-					}
-					$temp[$CI->lang->line('ut_'.$key)] = $val;
-				}
+				$temp[$CI->lang->line('ut_'.$key, FALSE)] = $val;
 			}
 
 			$retval[] = $temp;
@@ -340,18 +374,26 @@
 }
 
 /**
- * Helper functions to test boolean true/false
+ * Helper function to test boolean TRUE
  *
+ * @param	mixed	$test
  * @return	bool
  */
 function is_true($test)
 {
 	return ($test === TRUE);
 }
+
+/**
+ * Helper function to test boolean FALSE
+ *
+ * @param	mixed	$test
+ * @return	bool
+ */
 function is_false($test)
 {
 	return ($test === FALSE);
 }
 
 /* End of file Unit_test.php */
-/* Location: ./system/libraries/Unit_test.php */
\ No newline at end of file
+/* Location: ./system/libraries/Unit_test.php */
diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php
index d381440..4f65c9e 100644
--- a/system/libraries/Upload.php
+++ b/system/libraries/Upload.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * File Uploading Class
@@ -36,40 +37,224 @@
  */
 class CI_Upload {
 
+	/**
+	 * Maximum file size
+	 *
+	 * @var	int
+	 */
 	public $max_size		= 0;
+
+	/**
+	 * Maximum image width
+	 *
+	 * @var	int
+	 */
 	public $max_width		= 0;
+
+	/**
+	 * Maximum image height
+	 *
+	 * @var	int
+	 */
 	public $max_height		= 0;
+
+	/**
+	 * Minimum image width
+	 *
+	 * @var	int
+	 */
+	public $min_width		= 0;
+
+	/**
+	 * Minimum image height
+	 *
+	 * @var	int
+	 */
+	public $min_height		= 0;
+
+	/**
+	 * Maximum filename length
+	 *
+	 * @var	int
+	 */
 	public $max_filename		= 0;
+
+	/**
+	 * Maximum duplicate filename increment ID
+	 *
+	 * @var	int
+	 */
 	public $max_filename_increment 	= 100;
+
+	/**
+	 * Allowed file types
+	 *
+	 * @var	string
+	 */
 	public $allowed_types		= '';
+
+	/**
+	 * Temporary filename
+	 *
+	 * @var	string
+	 */
 	public $file_temp		= '';
+
+	/**
+	 * Filename
+	 *
+	 * @var	string
+	 */
 	public $file_name		= '';
+
+	/**
+	 * Original filename
+	 *
+	 * @var	string
+	 */
 	public $orig_name		= '';
+
+	/**
+	 * File type
+	 *
+	 * @var	string
+	 */
 	public $file_type		= '';
-	public $file_size		= '';
+
+	/**
+	 * File size
+	 *
+	 * @var	int
+	 */
+	public $file_size		= NULL;
+
+	/**
+	 * Filename extension
+	 *
+	 * @var	string
+	 */
 	public $file_ext		= '';
+
+	/**
+	 * Upload path
+	 *
+	 * @var	string
+	 */
 	public $upload_path		= '';
+
+	/**
+	 * Overwrite flag
+	 *
+	 * @var	bool
+	 */
 	public $overwrite		= FALSE;
+
+	/**
+	 * Obfuscate filename flag
+	 *
+	 * @var	bool
+	 */
 	public $encrypt_name		= FALSE;
+
+	/**
+	 * Is image flag
+	 *
+	 * @var	bool
+	 */
 	public $is_image		= FALSE;
-	public $image_width		= '';
-	public $image_height		= '';
+
+	/**
+	 * Image width
+	 *
+	 * @var	int
+	 */
+	public $image_width		= NULL;
+
+	/**
+	 * Image height
+	 *
+	 * @var	int
+	 */
+	public $image_height		= NULL;
+
+	/**
+	 * Image type
+	 *
+	 * @var	string
+	 */
 	public $image_type		= '';
+
+	/**
+	 * Image size string
+	 *
+	 * @var	string
+	 */
 	public $image_size_str		= '';
+
+	/**
+	 * Error messages list
+	 *
+	 * @var	array
+	 */
 	public $error_msg		= array();
+
+	/**
+	 * MIME types list
+	 *
+	 * @var	array
+	 */
 	public $mimes			= array();
+
+	/**
+	 * Remove spaces flag
+	 *
+	 * @var	bool
+	 */
 	public $remove_spaces		= TRUE;
+
+	/**
+	 * MIME detection flag
+	 *
+	 * @var	bool
+	 */
 	public $detect_mime		= TRUE;
+
+	/**
+	 * XSS filter flag
+	 *
+	 * @var	bool
+	 */
 	public $xss_clean		= FALSE;
+
+	/**
+	 * Temporary filename prefix
+	 *
+	 * @var	string
+	 */
 	public $temp_prefix		= 'temp_file_';
+
+	/**
+	 * Filename sent by the client
+	 *
+	 * @var	bool
+	 */
 	public $client_name		= '';
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Filename override
+	 *
+	 * @var	string
+	 */
 	protected $_file_name_override	= '';
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	array
+	 * @param	array	$props
 	 * @return	void
 	 */
 	public function __construct($props = array())
@@ -89,7 +274,7 @@
 	/**
 	 * Initialize preferences
 	 *
-	 * @param	array
+	 * @param	array	$config
 	 * @return	void
 	 */
 	public function initialize($config = array())
@@ -98,6 +283,8 @@
 					'max_size'			=> 0,
 					'max_width'			=> 0,
 					'max_height'			=> 0,
+					'min_width'			=> 0,
+					'min_height'			=> 0,
 					'max_filename'			=> 0,
 					'max_filename_increment'	=> 100,
 					'allowed_types'			=> '',
@@ -105,14 +292,14 @@
 					'file_name'			=> '',
 					'orig_name'			=> '',
 					'file_type'			=> '',
-					'file_size'			=> '',
+					'file_size'			=> NULL,
 					'file_ext'			=> '',
 					'upload_path'			=> '',
 					'overwrite'			=> FALSE,
 					'encrypt_name'			=> FALSE,
 					'is_image'			=> FALSE,
-					'image_width'			=> '',
-					'image_height'			=> '',
+					'image_width'			=> NULL,
+					'image_height'			=> NULL,
 					'image_type'			=> '',
 					'image_size_str'		=> '',
 					'error_msg'			=> array(),
@@ -123,7 +310,6 @@
 					'client_name'			=> ''
 				);
 
-
 		foreach ($defaults as $key => $val)
 		{
 			if (isset($config[$key]))
@@ -154,6 +340,7 @@
 	/**
 	 * Perform the file upload
 	 *
+	 * @param	string	$field
 	 * @return	bool
 	 */
 	public function do_upload($field = 'userfile')
@@ -355,7 +542,7 @@
 	 * Returns an associative array containing all of the information
 	 * related to the upload, allowing the developer easy access in one array.
 	 *
-	 * @param	string
+	 * @param	string	$index
 	 * @return	mixed
 	 */
 	public function data($index = NULL)
@@ -390,7 +577,7 @@
 	/**
 	 * Set Upload Path
 	 *
-	 * @param	string
+	 * @param	string	$path
 	 * @return	void
 	 */
 	public function set_upload_path($path)
@@ -408,8 +595,8 @@
 	 * existence of a file with the same name. If found, it will append a
 	 * number to the end of the filename to avoid overwriting a pre-existing file.
 	 *
-	 * @param	string
-	 * @param	string
+	 * @param	string	$path
+	 * @param	string	$filename
 	 * @return	string
 	 */
 	public function set_filename($path, $filename)
@@ -453,7 +640,7 @@
 	/**
 	 * Set Maximum File Size
 	 *
-	 * @param	int
+	 * @param	int	$n
 	 * @return	void
 	 */
 	public function set_max_filesize($n)
@@ -466,7 +653,7 @@
 	/**
 	 * Set Maximum File Name Length
 	 *
-	 * @param	int
+	 * @param	int	$n
 	 * @return	void
 	 */
 	public function set_max_filename($n)
@@ -479,7 +666,7 @@
 	/**
 	 * Set Maximum Image Width
 	 *
-	 * @param	int
+	 * @param	int	$n
 	 * @return	void
 	 */
 	public function set_max_width($n)
@@ -492,7 +679,7 @@
 	/**
 	 * Set Maximum Image Height
 	 *
-	 * @param	int
+	 * @param	int	$n
 	 * @return	void
 	 */
 	public function set_max_height($n)
@@ -503,9 +690,35 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Set minimum image width
+	 *
+	 * @param	int	$n
+	 * @return	void
+	 */
+	public function set_min_width($n)
+	{
+		$this->min_width = ((int) $n < 0) ? 0 : (int) $n;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set minimum image height
+	 *
+	 * @param	int	$n
+	 * @return	void
+	 */
+	public function set_min_height($n)
+	{
+		$this->min_height = ((int) $n < 0) ? 0 : (int) $n;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Set Allowed File Types
 	 *
-	 * @param	string
+	 * @param	string	$types
 	 * @return	void
 	 */
 	public function set_allowed_types($types)
@@ -525,7 +738,7 @@
 	 *
 	 * Uses GD to determine the width/height/type of image
 	 *
-	 * @param	string
+	 * @param	string	$path
 	 * @return	void
 	 */
 	public function set_image_properties($path = '')
@@ -557,7 +770,7 @@
 	 * Enables the XSS flag so that the file that was uploaded
 	 * will be run through the XSS filter.
 	 *
-	 * @param	bool
+	 * @param	bool	$flag
 	 * @return	void
 	 */
 	public function set_xss_clean($flag = FALSE)
@@ -599,7 +812,7 @@
 	/**
 	 * Verify that the filetype is allowed
 	 *
-	 * @param	bool
+	 * @param	bool	$ignore_mime
 	 * @return	bool
 	 */
 	public function is_allowed_filetype($ignore_mime = FALSE)
@@ -688,6 +901,16 @@
 			{
 				return FALSE;
 			}
+
+			if ($this->min_width > 0 && $D[0] < $this->min_width)
+			{
+				return FALSE;
+			}
+
+			if ($this->min_height > 0 && $D[1] < $this->min_height)
+			{
+				return FALSE;
+			}
 		}
 
 		return TRUE;
@@ -736,7 +959,7 @@
 	/**
 	 * Extract the file extension
 	 *
-	 * @param	string
+	 * @param	string	$filename
 	 * @return	string
 	 */
 	public function get_extension($filename)
@@ -750,7 +973,7 @@
 	/**
 	 * Clean the file name for security
 	 *
-	 * @param	string
+	 * @param	string	$filename
 	 * @return	string
 	 */
 	public function clean_file_name($filename)
@@ -790,7 +1013,8 @@
 	/**
 	 * Limit the File Name Length
 	 *
-	 * @param	string
+	 * @param	string	$filename
+	 * @param	int	$length
 	 * @return	string
 	 */
 	public function limit_filename_length($filename, $length)
@@ -883,7 +1107,7 @@
 	/**
 	 * Set an error message
 	 *
-	 * @param	string
+	 * @param	string	$msg
 	 * @return	void
 	 */
 	public function set_error($msg)
@@ -913,8 +1137,8 @@
 	/**
 	 * Display the error message
 	 *
-	 * @param	string
-	 * @param	string
+	 * @param	string	$open
+	 * @param	string	$close
 	 * @return	string
 	 */
 	public function display_errors($open = '<p>', $close = '</p>')
@@ -930,7 +1154,7 @@
 	 * This is a list of mime types. We use it to validate
 	 * the "allowed types" set by the developer
 	 *
-	 * @param	string
+	 * @param	string	$mime
 	 * @return	string
 	 */
 	public function mimes_types($mime)
@@ -943,10 +1167,12 @@
 	/**
 	 * Prep Filename
 	 *
-	 * Prevents possible script execution from Apache's handling of files multiple extensions
-	 * http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext
+	 * Prevents possible script execution from Apache's handling
+	 * of files' multiple extensions.
 	 *
-	 * @param	string
+	 * @link	http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext
+	 *
+	 * @param	string	$filename
 	 * @return	string
 	 */
 	protected function _prep_filename($filename)
@@ -983,7 +1209,7 @@
 	 * Detects the (actual) MIME type of the uploaded file, if possible.
 	 * The input array is expected to be $_FILES[$field]
 	 *
-	 * @param	array
+	 * @param	array	$file
 	 * @return	void
 	 */
 	protected function _file_mime_type($file)
@@ -1033,7 +1259,7 @@
 				? 'file --brief --mime '.escapeshellarg($file['tmp_name']).' 2>&1'
 				: 'file --brief --mime '.$file['tmp_name'].' 2>&1';
 
-			if (function_exists('exec'))
+			if (function_usable('exec'))
 			{
 				/* This might look confusing, as $mime is being populated with all of the output when set in the second parameter.
 				 * However, we only neeed the last line, which is the actual return value of exec(), and as such - it overwrites
@@ -1048,7 +1274,7 @@
 				}
 			}
 
-			if ( (bool) @ini_get('safe_mode') === FALSE && function_exists('shell_exec'))
+			if ( (bool) @ini_get('safe_mode') === FALSE && function_usable('shell_exec'))
 			{
 				$mime = @shell_exec($cmd);
 				if (strlen($mime) > 0)
@@ -1062,7 +1288,7 @@
 				}
 			}
 
-			if (function_exists('popen'))
+			if (function_usable('popen'))
 			{
 				$proc = @popen($cmd, 'r');
 				if (is_resource($proc))
diff --git a/system/libraries/User_agent.php b/system/libraries/User_agent.php
index ff596f0..659371a 100644
--- a/system/libraries/User_agent.php
+++ b/system/libraries/User_agent.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * User Agent Class
@@ -309,6 +310,7 @@
 				{
 					$this->is_robot = TRUE;
 					$this->robot = $val;
+					$this->_set_mobile();
 					return TRUE;
 				}
 			}
@@ -466,7 +468,13 @@
 	 */
 	public function is_referral()
 	{
-		return ! empty($_SERVER['HTTP_REFERER']);
+		if (empty($_SERVER['HTTP_REFERER']))
+		{
+			return FALSE;
+		}
+
+		$referer = parse_url($_SERVER['HTTP_REFERER']);
+		return ! (empty($referer['host']) && strpos(config_item('base_url'), $referer['host']) !== FALSE);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Xmlrpc.php b/system/libraries/Xmlrpc.php
index cbb91c4..5290899 100644
--- a/system/libraries/Xmlrpc.php
+++ b/system/libraries/Xmlrpc.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,14 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+if ( ! function_exists('xml_parser_create'))
+{
+	show_error('Your PHP installation does not support XML');
+}
+
+// ------------------------------------------------------------------------
 
 /**
  * XML-RPC request handler class
@@ -34,56 +42,212 @@
  * @author		EllisLab Dev Team
  * @link		http://codeigniter.com/user_guide/libraries/xmlrpc.html
  */
-
-if ( ! function_exists('xml_parser_create'))
-{
-	show_error('Your PHP installation does not support XML');
-}
-
-// ------------------------------------------------------------------------
-
 class CI_Xmlrpc {
 
-	public $debug		= FALSE;	// Debugging on or off
+	/**
+	 * Debug flag
+	 *
+	 * @var	bool
+	 */
+	public $debug		= FALSE;
+
+	/**
+	 * I4 data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcI4	= 'i4';
+
+	/**
+	 * Integer data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcInt	= 'int';
+
+	/**
+	 * Boolean data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcBoolean	= 'boolean';
+
+	/**
+	 * Double data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcDouble	= 'double';
+
+	/**
+	 * String data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcString	= 'string';
+
+	/**
+	 * DateTime format
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcDateTime	= 'dateTime.iso8601';
+
+	/**
+	 * Base64 data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcBase64	= 'base64';
+
+	/**
+	 * Array data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcArray	= 'array';
+
+	/**
+	 * Struct data type
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcStruct	= 'struct';
 
+	/**
+	 * Data types list
+	 *
+	 * @var	array
+	 */
 	public $xmlrpcTypes	= array();
+
+	/**
+	 * Valid parents list
+	 *
+	 * @var	array
+	 */
 	public $valid_parents	= array();
-	public $xmlrpcerr		= array(); // Response numbers
-	public $xmlrpcstr		= array(); // Response strings
 
+	/**
+	 * Response error numbers list
+	 *
+	 * @var	array
+	 */
+	public $xmlrpcerr		= array();
+
+	/**
+	 * Response error messages list
+	 *
+	 * @var	string[]
+	 */
+	public $xmlrpcstr		= array();
+
+	/**
+	 * Encoding charset
+	 *
+	 * @var	string
+	 */
 	public $xmlrpc_defencoding	= 'UTF-8';
+
+	/**
+	 * XML-RPC client name
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcName		= 'XML-RPC for CodeIgniter';
+
+	/**
+	 * XML-RPC version
+	 *
+	 * @var	string
+	 */
 	public $xmlrpcVersion		= '1.1';
-	public $xmlrpcerruser		= 800; // Start of user errors
-	public $xmlrpcerrxml		= 100; // Start of XML Parse errors
-	public $xmlrpc_backslash	= ''; // formulate backslashes for escaping regexp
 
+	/**
+	 * Start of user errors
+	 *
+	 * @var	int
+	 */
+	public $xmlrpcerruser		= 800;
+
+	/**
+	 * Start of XML parse errors
+	 *
+	 * @var	int
+	 */
+	public $xmlrpcerrxml		= 100;
+
+	/**
+	 * Backslash replacement value
+	 *
+	 * @var	string
+	 */
+	public $xmlrpc_backslash	= '';
+
+	/**
+	 * XML-RPC Client object
+	 *
+	 * @var	object
+	 */
 	public $client;
-	public $method;
-	public $data;
-	public $message			= '';
-	public $error			= '';	// Error string for request
-	public $result;
-	public $response		= array();  // Response from remote server
 
+	/**
+	 * XML-RPC Method name
+	 *
+	 * @var	string
+	 */
+	public $method;
+
+	/**
+	 * XML-RPC Data
+	 *
+	 * @var	array
+	 */
+	public $data;
+
+	/**
+	 * XML-RPC Message
+	 *
+	 * @var	string
+	 */
+	public $message			= '';
+
+	/**
+	 * Request error message
+	 *
+	 * @var	string
+	 */
+	public $error			= '';
+
+	/**
+	 * XML-RPC result object
+	 *
+	 * @var	object
+	 */
+	public $result;
+
+	/**
+	 * XML-RPC Reponse
+	 *
+	 * @var	array
+	 */
+	public $response		= array(); // Response from remote server
+
+	/**
+	 * XSS Filter flag
+	 *
+	 * @var	bool
+	 */
 	public $xss_clean		= TRUE;
 
+	// --------------------------------------------------------------------
 
 	/**
 	 * Constructor
 	 *
 	 * Initializes property default values
 	 *
-	 * @param	array
+	 * @param	array	$config
 	 * @return	void
 	 */
 	public function __construct($config = array())
@@ -101,7 +265,7 @@
 			$this->xmlrpcBase64		=> '1',
 			$this->xmlrpcArray		=> '2',
 			$this->xmlrpcStruct		=> '3'
-			);
+		);
 
 		// Array of Valid Parents for Various XML-RPC elements
 		$this->valid_parents = array('BOOLEAN' => array('VALUE'),
@@ -121,14 +285,13 @@
 			'DATA'				=> array('ARRAY'),
 			'FAULT'			=> array('METHODRESPONSE'),
 			'VALUE'			=> array('MEMBER', 'DATA', 'PARAM', 'FAULT')
-		 );
-
+		);
 
 		// XML-RPC Responses
 		$this->xmlrpcerr['unknown_method'] = '1';
 		$this->xmlrpcstr['unknown_method'] = 'This is not a known method for this XML-RPC Server';
 		$this->xmlrpcerr['invalid_return'] = '2';
-		$this->xmlrpcstr['invalid_return'] = 'The XML data received was either invalid or not in the correct form for XML-RPC.  Turn on debugging to examine the XML data further.';
+		$this->xmlrpcstr['invalid_return'] = 'The XML data received was either invalid or not in the correct form for XML-RPC. Turn on debugging to examine the XML data further.';
 		$this->xmlrpcerr['incorrect_params'] = '3';
 		$this->xmlrpcstr['incorrect_params'] = 'Incorrect parameters were passed to method';
 		$this->xmlrpcerr['introspect_unknown'] = '4';
@@ -136,7 +299,7 @@
 		$this->xmlrpcerr['http_error'] = '5';
 		$this->xmlrpcstr['http_error'] = "Did not receive a '200 OK' response from remote server.";
 		$this->xmlrpcerr['no_data'] = '6';
-		$this->xmlrpcstr['no_data'] ='No data received from server.';
+		$this->xmlrpcstr['no_data'] = 'No data received from server.';
 
 		$this->initialize($config);
 
@@ -148,7 +311,7 @@
 	/**
 	 * Initialize
 	 *
-	 * @param	array
+	 * @param	array	$config
 	 * @return	void
 	 */
 	public function initialize($config = array())
@@ -170,8 +333,10 @@
 	/**
 	 * Parse server URL
 	 *
-	 * @param	string	url
-	 * @param	int	port
+	 * @param	string	$url
+	 * @param	int	$port
+	 * @param	string	$proxy
+	 * @param	int	$proxy_port
 	 * @return	void
 	 */
 	public function server($url, $port = 80, $proxy = FALSE, $proxy_port = 8080)
@@ -198,7 +363,7 @@
 	/**
 	 * Set Timeout
 	 *
-	 * @param	int	seconds
+	 * @param	int	$seconds
 	 * @return	void
 	 */
 	public function timeout($seconds = 5)
@@ -214,7 +379,7 @@
 	/**
 	 * Set Methods
 	 *
-	 * @param	string	method name
+	 * @param	string	$function	Method name
 	 * @return	void
 	 */
 	public function method($function)
@@ -227,7 +392,7 @@
 	/**
 	 * Take Array of Data and Create Objects
 	 *
-	 * @param	array
+	 * @param	array	$incoming
 	 * @return	void
 	 */
 	public function request($incoming)
@@ -251,7 +416,7 @@
 	/**
 	 * Set Debug
 	 *
-	 * @param	bool
+	 * @param	bool	$flag
 	 * @return	void
 	 */
 	public function set_debug($flag = TRUE)
@@ -264,7 +429,7 @@
 	/**
 	 * Values Parsing
 	 *
-	 * @param	mixed
+	 * @param	mixed	$value
 	 * @return	object
 	 */
 	public function values_parsing($value)
@@ -347,8 +512,8 @@
 	/**
 	 * Sends an Error Message for Server Request
 	 *
-	 * @param	int
-	 * @param	string
+	 * @param	int	$number
+	 * @param	string	$message
 	 * @return	object
 	 */
 	public function send_error_message($number, $message)
@@ -361,7 +526,7 @@
 	/**
 	 * Send Response for Server Request
 	 *
-	 * @param	array
+	 * @param	array	$response
 	 * @return	object
 	 */
 	public function send_response($response)
@@ -382,22 +547,79 @@
  */
 class XML_RPC_Client extends CI_Xmlrpc
 {
+	/**
+	 * Path
+	 *
+	 * @var	string
+	 */
 	public $path			= '';
+
+	/**
+	 * Server hostname
+	 *
+	 * @var	string
+	 */
 	public $server			= '';
+
+	/**
+	 * Server port
+	 *
+	 * @var	int
+	 */
 	public $port			= 80;
+
+	/**
+	 * Proxy hostname
+	 *
+	 * @var	string
+	 */
 	public $proxy			= FALSE;
+
+	/**
+	 * Proxy port
+	 *
+	 * @var	int
+	 */
 	public $proxy_port		= 8080;
+
+	/**
+	 * Error number
+	 *
+	 * @var	string
+	 */
 	public $errno			= '';
+
+	/**
+	 * Error message
+	 *
+	 * @var	string
+	 */
 	public $errstring		= '';
+
+	/**
+	 * Timeout in seconds
+	 *
+	 * @var	int
+	 */
 	public $timeout		= 5;
+
+	/**
+	 * No Multicall flag
+	 *
+	 * @var	bool
+	 */
 	public $no_multicall	= FALSE;
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	string
-	 * @param	object
-	 * @param	int
+	 * @param	string	$path
+	 * @param	object	$server
+	 * @param	int	$port
+	 * @param	string	$proxy
+	 * @param	int	$proxy_port
 	 * @return	void
 	 */
 	public function __construct($path, $server, $port = 80, $proxy = FALSE, $proxy_port = 8080)
@@ -416,7 +638,7 @@
 	/**
 	 * Send message
 	 *
-	 * @param	mixed
+	 * @param	mixed	$msg
 	 * @return	object
 	 */
 	public function send($msg)
@@ -435,7 +657,7 @@
 	/**
 	 * Send payload
 	 *
-	 * @param	object
+	 * @param	object	$msg
 	 * @return	object
 	 */
 	public function sendPayload($msg)
@@ -495,18 +717,50 @@
  */
 class XML_RPC_Response
 {
+
+	/**
+	 * Value
+	 *
+	 * @var	mixed
+	 */
 	public $val		= 0;
+
+	/**
+	 * Error number
+	 *
+	 * @var	int
+	 */
 	public $errno		= 0;
+
+	/**
+	 * Error message
+	 *
+	 * @var	string
+	 */
 	public $errstr		= '';
+
+	/**
+	 * Headers list
+	 *
+	 * @var	array
+	 */
 	public $headers		= array();
+
+	/**
+	 * XSS Filter flag
+	 *
+	 * @var	bool
+	 */
 	public $xss_clean	= TRUE;
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	mixed
-	 * @param	int
-	 * @param	string
+	 * @param	mixed	$val
+	 * @param	int	$code
+	 * @param	string	$fstr
 	 * @return	void
 	 */
 	public function __construct($val, $code = 0, $fstr = '')
@@ -691,7 +945,7 @@
 	 */
 	public function iso8601_decode($time, $utc = FALSE)
 	{
-		// return a time in the localtime, or UTC
+		// Return a time in the localtime, or UTC
 		$t = 0;
 		if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $time, $regs))
 		{
@@ -712,16 +966,42 @@
  */
 class XML_RPC_Message extends CI_Xmlrpc
 {
+
+	/**
+	 * Payload
+	 *
+	 * @var	string
+	 */
 	public $payload;
+
+	/**
+	 * Method name
+	 *
+	 * @var	string
+	 */
 	public $method_name;
+
+	/**
+	 * Parameter list
+	 *
+	 * @var	array
+	 */
 	public $params		= array();
+
+	/**
+	 * XH?
+	 *
+	 * @var	array
+	 */
 	public $xh		= array();
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	string	method name
-	 * @param	array
+	 * @param	string	$method
+	 * @param	array	$pars
 	 * @return	void
 	 */
 	public function __construct($method, $pars = FALSE)
@@ -800,7 +1080,7 @@
 		}
 
 		//-------------------------------------
-		//  Create and Set Up XML Parser
+		// Create and Set Up XML Parser
 		//-------------------------------------
 
 		$parser = xml_parser_create($this->xmlrpc_defencoding);
@@ -838,7 +1118,7 @@
 			$errstr = sprintf('XML error: %s at line %d',
 						xml_error_string(xml_get_error_code($parser)),
 						xml_get_current_line_number($parser));
-			//error_log($errstr);
+
 			$r = new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return']);
 			xml_parser_free($parser);
 			return $r;
@@ -949,7 +1229,7 @@
 		elseif ( ! in_array($this->xh[$the_parser]['stack'][0], $this->valid_parents[$name], TRUE))
 		{
 			$this->xh[$the_parser]['isf'] = 2;
-			$this->xh[$the_parser]['isf_reason'] = 'XML-RPC element $name cannot be child of '.$this->xh[$the_parser]['stack'][0];
+			$this->xh[$the_parser]['isf_reason'] = 'XML-RPC element '.$name.' cannot be child of '.$this->xh[$the_parser]['stack'][0];
 			return;
 		}
 
@@ -987,7 +1267,7 @@
 				{
 					//two data elements inside a value: an error occurred!
 					$this->xh[$the_parser]['isf'] = 2;
-					$this->xh[$the_parser]['isf_reason'] = "'Twas a ".$name.' element following a '
+					$this->xh[$the_parser]['isf_reason'] = 'There is a '.$name.' element following a '
 										.$this->xh[$the_parser]['vt'].' element inside a single value';
 					return;
 				}
@@ -1099,7 +1379,7 @@
 				break;
 			case 'VALUE':
 				// This if() detects if no scalar was inside <VALUE></VALUE>
-				if ($this->xh[$the_parser]['vt']=='value')
+				if ($this->xh[$the_parser]['vt'] == 'value')
 				{
 					$this->xh[$the_parser]['value']	= $this->xh[$the_parser]['ac'];
 					$this->xh[$the_parser]['vt']	= $this->xmlrpcString;
@@ -1299,14 +1579,27 @@
  */
 class XML_RPC_Values extends CI_Xmlrpc
 {
+	/**
+	 * Value data
+	 *
+	 * @var	array
+	 */
 	public $me	= array();
+
+	/**
+	 * Value type
+	 *
+	 * @var	int
+	 */
 	public $mytype	= 0;
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Constructor
 	 *
-	 * @param	mixed
-	 * @param	string
+	 * @param	mixed	$val
+	 * @param	string	$type
 	 * @return	void
 	 */
 	public function __construct($val = -1, $type = '')
@@ -1317,15 +1610,15 @@
 		{
 			$type = $type === '' ? 'string' : $type;
 
-			if ($this->xmlrpcTypes[$type] === 1)
+			if ($this->xmlrpcTypes[$type] == 1)
 			{
-				$this->addScalar($val,$type);
+				$this->addScalar($val, $type);
 			}
-			elseif ($this->xmlrpcTypes[$type] === 2)
+			elseif ($this->xmlrpcTypes[$type] == 2)
 			{
 				$this->addArray($val);
 			}
-			elseif ($this->xmlrpcTypes[$type] === 3)
+			elseif ($this->xmlrpcTypes[$type] == 3)
 			{
 				$this->addStruct($val);
 			}
@@ -1351,7 +1644,7 @@
 			return 0;
 		}
 
-		if ($typeof !== 1)
+		if ($typeof != 1)
 		{
 			echo '<strong>XML_RPC_Values</strong>: not a scalar type (${typeof})<br />';
 			return 0;
@@ -1359,7 +1652,7 @@
 
 		if ($type === $this->xmlrpcBoolean)
 		{
-			$val = (int) (strcasecmp($val,'true') === 0 OR $val === 1 OR ($val === TRUE && strcasecmp($val, 'false')));
+			$val = (int) (strcasecmp($val, 'true') === 0 OR $val === 1 OR ($val === TRUE && strcasecmp($val, 'false')));
 		}
 
 		if ($this->mytype === 2)
diff --git a/system/libraries/Xmlrpcs.php b/system/libraries/Xmlrpcs.php
index e81f2ca..d6c3416 100644
--- a/system/libraries/Xmlrpcs.php
+++ b/system/libraries/Xmlrpcs.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 if ( ! function_exists('xml_parser_create'))
 {
@@ -46,10 +47,10 @@
  * @author		EllisLab Dev Team
  * @link		http://codeigniter.com/user_guide/libraries/xmlrpc.html
  */
-class CI_Xmlrpcs extends CI_Xmlrpc
-{
+class CI_Xmlrpcs extends CI_Xmlrpc {
+
 	/**
-	 * array of methods mapped to function names and signatures
+	 * Array of methods mapped to function names and signatures
 	 *
 	 * @var array
 	 */
@@ -186,9 +187,9 @@
 	public function add_to_map($methodname, $function, $sig, $doc)
 	{
 		$this->methods[$methodname] = array(
-			'function'  => $function,
-			'signature' => $sig,
-			'docstring' => $doc
+			'function'	=> $function,
+			'signature'	=> $sig,
+			'docstring'	=> $doc
 		);
 	}
 
@@ -230,21 +231,21 @@
 						);
 
 		xml_set_object($parser, $parser_object);
-		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
+		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, TRUE);
 		xml_set_element_handler($parser, 'open_tag', 'closing_tag');
 		xml_set_character_data_handler($parser, 'character_data');
 		//xml_set_default_handler($parser, 'default_handler');
 
 		//-------------------------------------
-		//  PARSE + PROCESS XML DATA
+		// PARSE + PROCESS XML DATA
 		//-------------------------------------
 
 		if ( ! xml_parse($parser, $data, 1))
 		{
-			// return XML error as a faultCode
+			// Return XML error as a faultCode
 			$r = new XML_RPC_Response(0,
-			$this->xmlrpcerrxml + xml_get_error_code($parser),
-			sprintf('XML error: %s at line %d',
+				$this->xmlrpcerrxml + xml_get_error_code($parser),
+				sprintf('XML error: %s at line %d',
 				xml_error_string(xml_get_error_code($parser)),
 				xml_get_current_line_number($parser)));
 			xml_parser_free($parser);
@@ -258,7 +259,7 @@
 			xml_parser_free($parser);
 
 			$m = new XML_RPC_Message($parser_object->xh[$parser]['method']);
-			$plist='';
+			$plist = '';
 
 			for ($i = 0, $c = count($parser_object->xh[$parser]['params']); $i < $c; $i++)
 			{
@@ -279,7 +280,7 @@
 		}
 
 		//-------------------------------------
-		//  SET DEBUGGING MESSAGE
+		// SET DEBUGGING MESSAGE
 		//-------------------------------------
 
 		if ($this->debug === TRUE)
@@ -311,7 +312,7 @@
 		}
 
 		//-------------------------------------
-		//  Valid Method
+		// Valid Method
 		//-------------------------------------
 
 		if ( ! isset($this->methods[$methName]['function']))
@@ -320,7 +321,7 @@
 		}
 
 		//-------------------------------------
-		//  Check for Method (and Object)
+		// Check for Method (and Object)
 		//-------------------------------------
 
 		$method_parts = explode('.', $this->methods[$methName]['function']);
@@ -341,7 +342,7 @@
 		}
 
 		//-------------------------------------
-		//  Checking Methods Signature
+		// Checking Methods Signature
 		//-------------------------------------
 
 		if (isset($this->methods[$methName]['signature']))
@@ -374,7 +375,7 @@
 		}
 
 		//-------------------------------------
-		//  Calls the Function
+		// Calls the Function
 		//-------------------------------------
 
 		if ($objectCall === TRUE)
@@ -393,7 +394,6 @@
 				else
 				{
 					return $this->object->$method_parts[1]($m);
-					//return call_user_func(array(&$method_parts['0'],$method_parts['1']), $m);
 				}
 			}
 		}
@@ -406,7 +406,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Server Function:  List Methods
+	 * Server Function: List Methods
 	 *
 	 * @param	mixed
 	 * @return	object
@@ -433,7 +433,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Server Function:  Return Signature for Method
+	 * Server Function: Return Signature for Method
 	 *
 	 * @param	mixed
 	 * @return	object
@@ -467,13 +467,13 @@
 			return new XML_RPC_Response(new XML_RPC_Values('undef', 'string'));
 		}
 
-		return new XML_RPC_Response(0,$this->xmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']);
+		return new XML_RPC_Response(0, $this->xmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Server Function:  Doc String for Method
+	 * Server Function: Doc String for Method
 	 *
 	 * @param	mixed
 	 * @return	object
@@ -498,7 +498,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Server Function:  Multi-call
+	 * Server Function: Multi-call
 	 *
 	 * @param	mixed
 	 * @return	object
@@ -515,10 +515,8 @@
 
 		foreach ($calls as $value)
 		{
-			//$attempt = $this->_execute(new XML_RPC_Message($value[0], $value[1]));
-
 			$m = new XML_RPC_Message($value[0]);
-			$plist='';
+			$plist = '';
 
 			for ($i = 0, $c = count($value[1]); $i < $c; $i++)
 			{
@@ -541,14 +539,14 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 *  Multi-call Function:  Error Handling
+	 * Multi-call Function: Error Handling
 	 *
 	 * @param	mixed
 	 * @return	object
 	 */
 	public function multicall_error($err)
 	{
-		$str  = is_string($err) ? $this->xmlrpcstr["multicall_${err}"] : $err->faultString();
+		$str = is_string($err) ? $this->xmlrpcstr["multicall_${err}"] : $err->faultString();
 		$code = is_string($err) ? $this->xmlrpcerr["multicall_${err}"] : $err->faultCode();
 
 		$struct['faultCode'] = new XML_RPC_Values($code, 'int');
@@ -560,7 +558,7 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 *  Multi-call Function:  Processes method
+	 * Multi-call Function: Processes method
 	 *
 	 * @param	mixed
 	 * @return	object
@@ -576,7 +574,7 @@
 			return $this->multicall_error('nomethod');
 		}
 
-		list($scalar_type,$scalar_value)=each($methName->me);
+		list($scalar_type, $scalar_value) = each($methName->me);
 		$scalar_type = $scalar_type === $this->xmlrpcI4 ? $this->xmlrpcInt : $scalar_type;
 
 		if ($methName->kindOf() !== 'scalar' OR $scalar_type !== 'string')
@@ -596,7 +594,7 @@
 			return $this->multicall_error('notarray');
 		}
 
-		list($a,$b) = each($params->me);
+		list($a, $b) = each($params->me);
 
 		$msg = new XML_RPC_Message($scalar_value);
 		for ($i = 0, $numParams = count($b); $i < $numParams; $i++)
diff --git a/system/libraries/Zip.php b/system/libraries/Zip.php
index 5c4c257..5eb33d2 100644
--- a/system/libraries/Zip.php
+++ b/system/libraries/Zip.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Zip Compression Class
@@ -132,7 +133,7 @@
 	protected function _get_mod_time($dir)
 	{
 		// filemtime() may return false, but raises an error for non-existing files
-		$date = file_exists($dir) ? filemtime($dir) : getdate($this->now);
+		$date = file_exists($dir) ? @filemtime($dir) : getdate($this->now);
 
 		return array(
 				'file_mtime' => ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2,
diff --git a/system/libraries/javascript/Jquery.php b/system/libraries/javascript/Jquery.php
index 44c16b5..2bf4795 100644
--- a/system/libraries/javascript/Jquery.php
+++ b/system/libraries/javascript/Jquery.php
@@ -1,4 +1,4 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php
 /**
  * CodeIgniter
  *
@@ -24,6 +24,7 @@
  * @since		Version 1.0
  * @filesource
  */
+defined('BASEPATH') OR exit('No direct script access allowed');
 
 /**
  * Jquery Class
@@ -34,17 +35,65 @@
  * @author		EllisLab Dev Team
  * @link		http://codeigniter.com/user_guide/libraries/javascript.html
  */
-
 class CI_Jquery extends CI_Javascript {
 
+	/**
+	 * JavaScript directory location
+	 *
+	 * @var	string
+	 */
 	protected $_javascript_folder = 'js';
+
+	/**
+	 * JQuery code for load
+	 *
+	 * @var	array
+	 */
 	public $jquery_code_for_load = array();
+
+	/**
+	 * JQuery code for compile
+	 *
+	 * @var	array
+	 */
 	public $jquery_code_for_compile = array();
+
+	/**
+	 * JQuery corner active flag
+	 *
+	 * @var	bool
+	 */
 	public $jquery_corner_active = FALSE;
+
+	/**
+	 * JQuery table sorter active flag
+	 *
+	 * @var	bool
+	 */
 	public $jquery_table_sorter_active = FALSE;
+
+	/**
+	 * JQuery table sorder pager active
+	 *
+	 * @var	bool
+	 */
 	public $jquery_table_sorter_pager_active = FALSE;
+
+	/**
+	 * JQuery AJAX image
+	 *
+	 * @var	string
+	 */
 	public $jquery_ajax_img = '';
 
+	// --------------------------------------------------------------------
+
+	/**
+	 * Constructor
+	 *
+	 * @param	array	$params
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		$this->CI =& get_instance();
@@ -101,15 +150,12 @@
 	 *
 	 * @param	string	The element to attach the event to
 	 * @param	string	The code to execute
-	 * @param	boolean	whether or not to return false
+	 * @param	bool	whether or not to return false
 	 * @return	string
 	 */
 	protected function _click($element = 'this', $js = '', $ret_false = TRUE)
 	{
-		if ( ! is_array($js))
-		{
-			$js = array($js);
-		}
+		is_array($js) OR $js = array($js);
 
 		if ($ret_false)
 		{
@@ -307,11 +353,10 @@
 	 *
 	 * Outputs script directly
 	 *
-	 * @param	string	The element to attach the event to
-	 * @param	string	The code to execute
-	 * @return	string
+	 * @param	array	$array_js = array()
+	 * @return	void
 	 */
-	protected function _output($array_js = '')
+	protected function _output($array_js = array())
 	{
 		if ( ! is_array($array_js))
 		{
@@ -381,10 +426,11 @@
 	 *
 	 * Outputs a jQuery addClass event
 	 *
-	 * @param	string	- element
+	 * @param	string	$element
+	 * @param	string	$class
 	 * @return	string
 	 */
-	protected function _addClass($element = 'this', $class='')
+	protected function _addClass($element = 'this', $class = '')
 	{
 		$element = $this->_prep_element($element);
 		return '$('.$element.').addClass("'.$class.'");';
@@ -397,9 +443,10 @@
 	 *
 	 * Outputs a jQuery animate event
 	 *
-	 * @param	string	- element
-	 * @param	string	- One of 'slow', 'normal', 'fast', or time in milliseconds
-	 * @param	string	- Javascript callback function
+	 * @param	string	$element
+	 * @param	array	$params
+	 * @param	string	$speed	'slow', 'normal', 'fast', or time in milliseconds
+	 * @param	string	$extra
 	 * @return	string
 	 */
 	protected function _animate($element = 'this', $params = array(), $speed = '', $extra = '')
@@ -511,10 +558,11 @@
 	 *
 	 * Outputs a jQuery remove class event
 	 *
-	 * @param	string	- element
+	 * @param	string	$element
+	 * @param	string	$class
 	 * @return	string
 	 */
-	protected function _removeClass($element = 'this', $class='')
+	protected function _removeClass($element = 'this', $class = '')
 	{
 		$element = $this->_prep_element($element);
 		return '$('.$element.').removeClass("'.$class.'");';
@@ -618,10 +666,11 @@
 	 *
 	 * Outputs a jQuery toggle class event
 	 *
-	 * @param	string	- element
+	 * @param	string	$element
+	 * @param	string	$class
 	 * @return	string
 	 */
-	protected function _toggleClass($element = 'this', $class='')
+	protected function _toggleClass($element = 'this', $class = '')
 	{
 		$element = $this->_prep_element($element);
 		return '$('.$element.').toggleClass("'.$class.'");';
@@ -695,7 +744,6 @@
 		return $updater."\t\t$($container).load('$controller'$request_options);";
 	}
 
-
 	// --------------------------------------------------------------------
 	// Pre-written handy stuff
 	// --------------------------------------------------------------------
@@ -703,14 +751,15 @@
 	/**
 	 * Zebra tables
 	 *
-	 * @param	string	table name
-	 * @param	string	plugin location
+	 * @param	string	$class
+	 * @param	string	$odd
+	 * @param	string	$hover
 	 * @return	string
 	 */
 	protected function _zebraTables($class = '', $odd = 'odd', $hover = '')
 	{
 		$class = ($class !== '') ? '.'.$class : '';
-		$zebra  = "\t\$(\"table{$class} tbody tr:nth-child(even)\").addClass(\"{$odd}\");";
+		$zebra = "\t\$(\"table{$class} tbody tr:nth-child(even)\").addClass(\"{$odd}\");";
 
 		$this->jquery_code_for_compile[] = $zebra;
 
@@ -729,9 +778,9 @@
 	/**
 	 * Corner Plugin
 	 *
-	 * http://www.malsup.com/jquery/corner/
-	 *
-	 * @param	string	target
+	 * @link	http://www.malsup.com/jquery/corner/
+	 * @param	string	$element
+	 * @param	string	$corner_style
 	 * @return	string
 	 */
 	public function corner($element = '', $corner_style = '')
@@ -750,10 +799,12 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * modal window
+	 * Modal window
 	 *
 	 * Load a thickbox modal window
 	 *
+	 * @param	string	$src
+	 * @param	bool	$relative
 	 * @return	void
 	 */
 	public function modal($src, $relative = FALSE)
@@ -768,6 +819,8 @@
 	 *
 	 * Load an Effect library
 	 *
+	 * @param	string	$src
+	 * @param	bool	$relative
 	 * @return	void
 	 */
 	public function effect($src, $relative = FALSE)
@@ -782,6 +835,8 @@
 	 *
 	 * Load a plugin library
 	 *
+	 * @param	string	$src
+	 * @param	bool	$relative
 	 * @return	void
 	 */
 	public function plugin($src, $relative = FALSE)
@@ -796,12 +851,15 @@
 	 *
 	 * Load a user interface library
 	 *
+	 * @param	string	$src
+	 * @param	bool	$relative
 	 * @return	void
 	 */
 	public function ui($src, $relative = FALSE)
 	{
 		$this->jquery_code_for_load[] = $this->external($src, $relative);
 	}
+
 	// --------------------------------------------------------------------
 
 	/**
@@ -809,11 +867,12 @@
 	 *
 	 * Creates a jQuery sortable
 	 *
-	 * @return	void
+	 * @param	string	$element
+	 * @param	array	$options
+	 * @return	string
 	 */
 	public function sortable($element, $options = array())
 	{
-
 		if (count($options) > 0)
 		{
 			$sort_options = array();
@@ -880,7 +939,9 @@
 	 * As events are specified, they are stored in an array
 	 * This funciton compiles them all for output on a page
 	 *
-	 * @return	string
+	 * @param	string	$view_var
+	 * @param	bool	$script_tags
+	 * @return	void
 	 */
 	protected function _compile($view_var = 'script_foot', $script_tags = TRUE)
 	{
@@ -902,7 +963,6 @@
 		$output = ($script_tags === FALSE) ? $script : $this->inline($script);
 
 		$this->CI->load->vars(array($view_var => $output));
-
 	}
 
 	// --------------------------------------------------------------------
@@ -926,14 +986,12 @@
 	 *
 	 * A wrapper for writing document.ready()
 	 *
-	 * @return	string
+	 * @param	array	$js
+	 * @return	void
 	 */
 	protected function _document_ready($js)
 	{
-		if ( ! is_array($js))
-		{
-			$js = array($js);
-		}
+		is_array($js) OR $js = array($js);
 
 		foreach ($js as $script)
 		{
@@ -948,7 +1006,8 @@
 	 *
 	 * Outputs the script tag that loads the jquery.js file into an HTML document
 	 *
-	 * @param	string
+	 * @param	string	$library_src
+	 * @param	bool	$relative
 	 * @return	string
 	 */
 	public function script($library_src = '', $relative = FALSE)
diff --git a/system/libraries/javascript/index.html b/system/libraries/javascript/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/libraries/javascript/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/tests/Bootstrap.php b/tests/Bootstrap.php
index 5216038..8ce80b3 100644
--- a/tests/Bootstrap.php
+++ b/tests/Bootstrap.php
@@ -8,9 +8,7 @@
 
 // Path constants
 defined('PROJECT_BASE') OR define('PROJECT_BASE', realpath($dir.'/../').'/');
-defined('BASEPATH') OR define('BASEPATH', PROJECT_BASE.'system/');
-defined('APPPATH') OR define('APPPATH', PROJECT_BASE.'application/');
-defined('VIEWPATH') OR define('VIEWPATH', PROJECT_BASE.'');
+defined('SYSTEM_PATH') OR define('SYSTEM_PATH', PROJECT_BASE.'system/');
 
 // Get vfsStream either via PEAR or composer
 foreach (explode(PATH_SEPARATOR, get_include_path()) as $path)
@@ -30,8 +28,17 @@
 	class_alias('org\bovigo\vfs\vfsStreamWrapper', 'vfsStreamWrapper');
 }
 
+// Define CI path constants to VFS (filesystem setup in CI_TestCase::setUp)
+defined('BASEPATH') OR define('BASEPATH', vfsStream::url('system/'));
+defined('APPPATH') OR define('APPPATH', vfsStream::url('application/'));
+defined('VIEWPATH') OR define('VIEWPATH', APPPATH.'views/');
+
+// Set localhost "remote" IP
+isset($_SERVER['REMOTE_ADDR']) OR $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+
 // Prep our test environment
 include_once $dir.'/mocks/core/common.php';
+include_once SYSTEM_PATH.'core/Common.php';
 include_once $dir.'/mocks/autoloader.php';
 spl_autoload_register('autoload');
 
diff --git a/tests/README.md b/tests/README.md
index d600951..a5f89a2 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -64,6 +64,30 @@
 3. Application Test	- bootstrapping for application/tests [not started]
 4. Package Test		- bootstrapping for <package>/tests [not started]
 
+### Test Environment:
+
+The test/Bootstrap.php file establishes global constants such as BASEPATH,
+APPPATH, and VIEWPATH, initializing them to point to VFS locations. The
+test case class employs vfsStream to make a clean virtual filesystem with
+the necessary paths for every individual test.
+
+Within each test case, VFS directory objects are available to use as arguments
+to the VFS convenience functions (see below):
+
+- ci_vfs_root: VFS filesystem root
+- ci_app_root: Application directory
+- ci_base_root: System directory
+- ci_view_root: Views directory
+
+Classes being instantiated for testing are read from the actual filesystem
+by the unit test autoloader, as are mockups created in tests/mocks. If you
+need access to the real system directory, the SYSTEM_PATH constant always
+points to it.
+
+Any other resources which need to be read from the path constants must be
+created or cloned within your test. Functions for doing so are outlined
+below.
+
 ### CI_TestCase Documentation
 
 Test cases should extend CI_TestCase. This internally extends
@@ -78,8 +102,14 @@
 
     $this->ci_set_config($key, $val)
 
-Set the global config variables. If key is an array, it will
-replace the entire config array. They are _not_ merged.
+Set the global config variables in a mock Config object. If key is an array,
+it will replace the entire config array. They are _not_ merged. If called
+without any parameters, it will create the mock object but not set any values.
+The mock Config object also provides rudimentary item() and load() stubs for
+delivering configured values to classes being tested and handling config load
+calls, respectively. The load() stub does _not_ actually load any files, it
+only records the filename provided. Check the config->loaded array to verify
+calls made.
 
     $this->ci_instance($obj)
 
@@ -103,11 +133,48 @@
     $cfg = new $cfg; // instantiates config and overwrites the CFG global
 
 	$this->ci_set_core_class($name, $obj)
-	
+
 An alternative way to set one of the core globals.
 
+	$this->ci_vfs_mkdir($name, $root)
+
+Creates a new directory in the test VFS. Pass a directory object to be the
+parent directory or none to create a root-level directory. Returns the new
+directory object.
+
+	$this->ci_vfs_create($file, $content, $root, $path)
+
+Creates a new VFS file. '.php' is automatically appended to the filename if
+it has no extension. Pass a directory object as the root, and an optional path
+to recurse and/or create for containing the file. Path may be a string (such
+as 'models/subdir') or an array (e.g. - array('models', 'subdir') ). Existing
+directories in the VFS root will be recursed until a new directory is
+identified - all others in the path will be created, so you can mix-and-match
+old and new directories. If $file is an array (key = name, value = content),
+multiple files will be created in the same path.
+
+	$this->ci_vfs_clone($path)
+
+Clones an existing file from the real filesystem to exist in the same path of
+the VFS. Path must be relative to the project root (i.e. - starting with
+'system' or 'application').
+
+	$this->ci_vfs_path($path, $base)
+
+Creates a VFS file path string suitable for use with PHP file operations. Path
+may be absolute from the VFS root, or relative to a base path. It is often
+useful to use APPPATH or BASEPATH as the base.
+
+	$this->helper($name)
+
+Loads a helper from the real filesystem.
+
+	$this->lang($name)
+
+Loads a language file from the real filesystem and returns the $lang array.
+
 	$this->ci_get_config()  __internal__
-	
+
 Returns the global config array. Internal as you shouldn't need to
 call this (you're setting it, after all). Used internally to make
 CI's get_config() work.
@@ -155,4 +222,4 @@
 
 Needs to be able to handle packages
 that are used multiple times within the application (i.e. EE/Pyro modules)
-as well as packages that are used by multiple applications (library distributions)
+as well as packages that are used by multiple applications (library distributions)
\ No newline at end of file
diff --git a/tests/codeigniter/core/Common_test.php b/tests/codeigniter/core/Common_test.php
index f9bf6c2..999b49c 100644
--- a/tests/codeigniter/core/Common_test.php
+++ b/tests/codeigniter/core/Common_test.php
@@ -2,12 +2,51 @@
 
 class Common_test extends CI_TestCase {
 
-	// ------------------------------------------------------------------------
-
 	public function test_is_php()
 	{
 		$this->assertEquals(TRUE, is_php('1.2.0'));
 		$this->assertEquals(FALSE, is_php('9999.9.9'));
 	}
 
+	// ------------------------------------------------------------------------
+
+	public function test_stringify_attributes()
+	{
+		$this->assertEquals(' class="foo" id="bar"', _stringify_attributes(array('class' => 'foo', 'id' => 'bar')));
+
+		$atts = new stdClass;
+		$atts->class = 'foo';
+		$atts->id = 'bar';
+		$this->assertEquals(' class="foo" id="bar"', _stringify_attributes($atts));
+
+		$atts = new stdClass;
+		$this->assertEquals('', _stringify_attributes($atts));
+
+		$this->assertEquals(' class="foo" id="bar"', _stringify_attributes('class="foo" id="bar"'));
+
+		$this->assertEquals('', _stringify_attributes(array()));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function test_stringify_js_attributes()
+	{
+		$this->assertEquals('width=800,height=600', _stringify_attributes(array('width' => '800', 'height' => '600'), TRUE));
+
+		$atts = new stdClass;
+		$atts->width = 800;
+		$atts->height = 600;
+		$this->assertEquals('width=800,height=600', _stringify_attributes($atts, TRUE));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function test_html_escape()
+	{
+		$this->assertEquals(
+			html_escape('Here is a string containing "quoted" text.'),
+			'Here is a string containing &quot;quoted&quot; text.'
+		);
+	}
+
 }
\ No newline at end of file
diff --git a/tests/codeigniter/core/Config_test.php b/tests/codeigniter/core/Config_test.php
index 30cb90a..be426d0 100644
--- a/tests/codeigniter/core/Config_test.php
+++ b/tests/codeigniter/core/Config_test.php
@@ -7,11 +7,12 @@
 		$cls =& $this->ci_core_class('cfg');
 
 		// set predictable config values
-		$this->ci_set_config(array(
+		$this->cfg = array(
 			'index_page'		=> 'index.php',
-			'base_url'			=> 'http://example.com/',
+			'base_url'		=> 'http://example.com/',
 			'subclass_prefix'	=> 'MY_'
-		));
+		);
+		$this->ci_set_config($this->cfg);
 
 		$this->config = new $cls;
 	}
@@ -20,7 +21,7 @@
 
 	public function test_item()
 	{
-		$this->assertEquals('http://example.com/', $this->config->item('base_url'));
+		$this->assertEquals($this->cfg['base_url'], $this->config->item('base_url'));
 
 		// Bad Config value
 		$this->assertFalse($this->config->item('no_good_item'));
@@ -37,7 +38,6 @@
 		$this->assertFalse($this->config->item('not_yet_set'));
 
 		$this->config->set_item('not_yet_set', 'is set');
-
 		$this->assertEquals('is set', $this->config->item('not_yet_set'));
 	}
 
@@ -48,36 +48,101 @@
 		// Bad Config value
 		$this->assertFalse($this->config->slash_item('no_good_item'));
 
-		$this->assertEquals('http://example.com/', $this->config->slash_item('base_url'));
+		$this->assertEquals($this->cfg['base_url'], $this->config->slash_item('base_url'));
+		$this->assertEquals($this->cfg['subclass_prefix'].'/', $this->config->slash_item('subclass_prefix'));
+	}
 
-		$this->assertEquals('MY_/', $this->config->slash_item('subclass_prefix'));
+	// --------------------------------------------------------------------
+
+	public function test_base_url()
+	{
+		// Test regular base URL
+		$base_url = $this->cfg['base_url'];
+		$this->assertEquals($base_url, $this->config->base_url());
+
+		// Test with URI
+		$uri = 'test';
+		$this->assertEquals($base_url.$uri, $this->config->base_url($uri));
+
+		// Clear base_url
+		$this->ci_set_config('base_url', '');
+
+		// Rerun constructor
+		$cls =& $this->ci_core_class('cfg');
+		$this->config = new $cls;
+
+		// Test default base
+		$this->assertEquals('http://localhost/', $this->config->base_url());
+
+		// Capture server vars
+		$old_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : NULL;
+		$old_script = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : NULL;
+		$old_https = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : NULL;
+
+		// Setup server vars for detection
+		$host = 'test.com';
+		$path = '/path/';
+		$script = 'base_test.php';
+		$_SERVER['HTTP_HOST'] = $host;
+		$_SERVER['SCRIPT_NAME'] = $path.$script;
+
+		// Rerun constructor
+		$this->config = new $cls;
+
+		// Test plain detected
+		$this->assertEquals('http://'.$host.$path, $this->config->base_url());
+
+		// Rerun constructor
+		$_SERVER['HTTPS'] = 'on';
+		$this->config = new $cls;
+
+		// Test secure detected
+		$this->assertEquals('https://'.$host.$path, $this->config->base_url());
+
+		// Restore server vars
+		if ($old_host === NULL) unset($_SERVER['HTTP_HOST']);
+		else $_SERVER['HTTP_HOST'] = $old_host;
+		if ($old_script === NULL) unset($_SERVER['SCRIPT_NAME']);
+		else $_SERVER['SCRIPT_NAME'] = $old_script;
+		if ($old_https === NULL) unset($_SERVER['HTTPS']);
+		else $_SERVER['HTTPS'] = $old_https;
 	}
 
 	// --------------------------------------------------------------------
 
 	public function test_site_url()
 	{
-		$this->assertEquals('http://example.com/index.php', $this->config->site_url());
+		$base_url = $this->cfg['base_url'];
+		$index_page = $this->cfg['index_page'];
+		$this->assertEquals($base_url.$index_page, $this->config->site_url());
 
-		$base_url = $this->config->item('base_url');
-
+		$old_base = $this->config->item('base_url');
 		$this->config->set_item('base_url', '');
 
 		$q_string = $this->config->item('enable_query_strings');
-
 		$this->config->set_item('enable_query_strings', FALSE);
 
-		$this->assertEquals('index.php/test', $this->config->site_url('test'));
-		$this->assertEquals('index.php/test/1', $this->config->site_url(array('test', '1')));
+		$uri = 'test';
+		$uri2 = '1';
+		$this->assertEquals($index_page.'/'.$uri, $this->config->site_url($uri));
+		$this->assertEquals($index_page.'/'.$uri.'/'.$uri2, $this->config->site_url(array($uri, $uri2)));
 
+		$suffix = 'ing';
+		$this->config->set_item('url_suffix', $suffix);
+
+		$arg = 'pass';
+		$this->assertEquals($index_page.'/'.$uri.$suffix, $this->config->site_url($uri));
+		$this->assertEquals($index_page.'/'.$uri.$suffix.'?'.$arg, $this->config->site_url($uri.'?'.$arg));
+
+		$this->config->set_item('url_suffix', FALSE);
 		$this->config->set_item('enable_query_strings', TRUE);
 
-		$this->assertEquals('index.php?test', $this->config->site_url('test'));
-		$this->assertEquals('index.php?0=test&1=1', $this->config->site_url(array('test', '1')));
+		$this->assertEquals($index_page.'?'.$uri, $this->config->site_url($uri));
+		$this->assertEquals($index_page.'?0='.$uri.'&1='.$uri2, $this->config->site_url(array($uri, $uri2)));
 
-		$this->config->set_item('base_url', $base_url);
+		$this->config->set_item('base_url', $old_base);
 
-		$this->assertEquals('http://example.com/index.php?test', $this->config->site_url('test'));
+		$this->assertEquals($base_url.$index_page.'?'.$uri, $this->config->site_url($uri));
 
 		// back to home base
 		$this->config->set_item('enable_query_strings', $q_string);
@@ -87,7 +152,82 @@
 
 	public function test_system_url()
 	{
-		$this->assertEquals('http://example.com/system/', $this->config->system_url());
+		$this->assertEquals($this->cfg['base_url'].'system/', $this->config->system_url());
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_load()
+	{
+		// Test regular load
+		$file = 'test.php';
+		$key = 'testconfig';
+		$val = 'my_value';
+		$cfg = array($key => $val);
+		$this->ci_vfs_create($file, '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config');
+		$this->assertTrue($this->config->load($file));
+		$this->assertEquals($val, $this->config->item($key));
+
+		// Test reload - value should not change
+		$val2 = 'new_value';
+		$cfg = array($key => $val2);
+		$this->ci_vfs_create($file, '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config');
+		$this->assertTrue($this->config->load($file));
+		$this->assertEquals($val, $this->config->item($key));
+
+		// Test section load
+		$file = 'secttest';
+		$cfg = array(
+			'one' => 'prime',
+			'two' => 2,
+			'three' => true
+		);
+		$this->ci_vfs_create($file.'.php', '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config');
+		$this->assertTrue($this->config->load($file, TRUE));
+		$this->assertEquals($cfg, $this->config->item($file));
+
+		// Test section merge
+		$cfg2 = array(
+			'three' => 'tres',
+			'number' => 42,
+			'letter' => 'Z'
+		);
+		$pkg_dir = 'package';
+		$this->ci_vfs_create($file.'.php', '<?php $config = '.var_export($cfg2, TRUE).';', $this->ci_app_root,
+			array($pkg_dir, 'config'));
+		array_push($this->config->_config_paths, $this->ci_vfs_path($pkg_dir.'/', APPPATH));
+		$this->assertTrue($this->config->load($file, TRUE));
+		$this->assertEquals(array_merge($cfg, $cfg2), $this->config->item($file));
+		array_pop($this->config->_config_paths);
+
+		// Test graceful fail of invalid file
+		$file = 'badfile';
+		$this->ci_vfs_create($file, '', $this->ci_app_root, 'config');
+		$this->assertFalse($this->config->load($file, FALSE, TRUE));
+
+		// Test regular fail of invalid file
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Your '.$this->ci_vfs_path('config/'.$file.'.php', APPPATH).
+				' file does not appear to contain a valid configuration array.'
+		);
+		$this->assertNull($this->config->load($file));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_load_nonexistent()
+	{
+		// Test graceful fail of nonexistent file
+		$this->assertFalse($this->config->load('not_config_file', FALSE, TRUE));
+
+		// Test regular fail
+		$file = 'absentia';
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: The configuration file '.$file.'.php does not exist.'
+		);
+		$this->assertNull($this->config->load($file));
 	}
 
 }
\ No newline at end of file
diff --git a/tests/codeigniter/core/Input_test.php b/tests/codeigniter/core/Input_test.php
index fe87388..ca1c6df 100644
--- a/tests/codeigniter/core/Input_test.php
+++ b/tests/codeigniter/core/Input_test.php
@@ -22,18 +22,7 @@
 
 	public function test_get_not_exists()
 	{
-		$this->assertEmpty($this->input->get());
-		$this->assertEmpty($this->input->get('foo'));
-
-		$this->assertTrue( ! $this->input->get());
-		$this->assertTrue( ! $this->input->get('foo'));
-
-		// Test we're getting empty results
-		$this->assertTrue($this->input->get() === NULL);
-		$this->assertTrue($this->input->get('foo') === NULL);
-
-		// Test new 3.0 behaviour for non existant results (used to be FALSE)
-		$this->assertTrue($this->input->get() === NULL);
+		$this->assertTrue($this->input->get() === array());
 		$this->assertTrue($this->input->get('foo') === NULL);
 	}
 
@@ -64,16 +53,7 @@
 
 	public function test_post_not_exists()
 	{
-		$this->assertEmpty($this->input->post());
-		$this->assertEmpty($this->input->post('foo'));
-
-		$this->assertTrue( ! $this->input->post());
-		$this->assertTrue( ! $this->input->post('foo'));
-
-		$this->assertTrue($this->input->post() === NULL);
-		$this->assertTrue($this->input->post('foo') === NULL);
-
-		$this->assertTrue($this->input->post() === NULL);
+		$this->assertTrue($this->input->post() === array());
 		$this->assertTrue($this->input->post('foo') === NULL);
 	}
 
diff --git a/tests/codeigniter/core/Lang_test.php b/tests/codeigniter/core/Lang_test.php
index a410dab..3364362 100644
--- a/tests/codeigniter/core/Lang_test.php
+++ b/tests/codeigniter/core/Lang_test.php
@@ -17,6 +17,7 @@
 
 	public function test_load()
 	{
+		$this->ci_vfs_clone('system/language/english/profiler_lang.php');
 		$this->assertTrue($this->lang->load('profiler', 'english'));
 		$this->assertEquals('URI STRING', $this->lang->line('profiler_uri_string'));
 	}
@@ -25,6 +26,7 @@
 
 	public function test_load_with_unspecified_language()
 	{
+		$this->ci_vfs_clone('system/language/english/profiler_lang.php');
 		$this->assertTrue($this->lang->load('profiler'));
 		$this->assertEquals('URI STRING', $this->lang->line('profiler_uri_string'));
 	}
diff --git a/tests/codeigniter/core/Loader_test.php b/tests/codeigniter/core/Loader_test.php
index fdea962..ecc5ca9 100644
--- a/tests/codeigniter/core/Loader_test.php
+++ b/tests/codeigniter/core/Loader_test.php
@@ -7,61 +7,225 @@
 	public function set_up()
 	{
 		// Instantiate a new loader
-		$this->load = new Mock_Core_Loader();
+		$loader = $this->ci_core_class('loader');
+		$this->load = new $loader();
 
-		// mock up a ci instance
-		$this->ci_obj = new stdClass;
+		// Get CI instance
+		$this->ci_obj = $this->ci_instance();
 
-		// Fix get_instance()
-		$this->ci_instance($this->ci_obj);
+		// Set subclass prefix
+		$this->prefix = 'MY_';
+		$this->ci_set_config('subclass_prefix', $this->prefix);
 	}
 
 	// --------------------------------------------------------------------
 
 	public function test_library()
 	{
-		$this->_setup_config_mock();
+		// Create library in VFS
+		$lib = 'unit_test_lib';
+		$class = 'CI_'.ucfirst($lib);
+		$this->ci_vfs_create($lib, '<?php class '.$class.' { }', $this->ci_base_root, 'libraries');
+
+		// Test is_loaded fail
+		$this->assertFalse($this->load->is_loaded($lib));
 
 		// Test loading as an array.
-		$this->assertNull($this->load->library(array('table')));
-		$this->assertTrue(class_exists('CI_Table'), 'Table class exists');
-		$this->assertAttributeInstanceOf('CI_Table', 'table', $this->ci_obj);
+		$this->assertNull($this->load->library(array($lib)));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $lib, $this->ci_obj);
 
 		// Test no lib given
-		$this->assertEquals(FALSE, $this->load->library());
+		$this->assertNull($this->load->library());
 
 		// Test a string given to params
-		$this->assertEquals(NULL, $this->load->library('table', ' '));
+		$this->assertNull($this->load->library($lib, ' '));
+
+		// Create library w/o class
+		$lib = 'bad_test_lib';
+		$this->ci_vfs_create($lib, '', $this->ci_base_root, 'libraries');
+
+		// Test non-existent class
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Non-existent class: '.$lib
+		);
+		$this->assertNull($this->load->library($lib));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_library_extension()
+	{
+		// Create library and extension in VFS
+		$name = 'ext_test_lib';
+		$lib = ucfirst($name);
+		$class = 'CI_'.$lib;
+		$ext = $this->prefix.$lib;
+		$this->ci_vfs_create($lib, '<?php class '.$class.' { }', $this->ci_base_root, 'libraries');
+		$this->ci_vfs_create($ext, '<?php class '.$ext.' extends '.$class.' { }', $this->ci_app_root, 'libraries');
+
+		// Test loading with extension
+		$this->assertNull($this->load->library($lib));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertTrue(class_exists($ext), $ext.' does not exist');
+		$this->assertAttributeInstanceOf($class, $name, $this->ci_obj);
+		$this->assertAttributeInstanceOf($ext, $name, $this->ci_obj);
+
+		// Test reloading with object name
+		$obj = 'exttest';
+		$this->assertNull($this->load->library($lib, NULL, $obj));
+		$this->assertAttributeInstanceOf($class, $obj, $this->ci_obj);
+		$this->assertAttributeInstanceOf($ext, $obj, $this->ci_obj);
+
+		// Test reloading
+		unset($this->ci_obj->$name);
+		$this->assertNull($this->load->library($lib));
+		$this->assertObjectNotHasAttribute($name, $this->ci_obj);
+
+		// Create baseless library
+		$name = 'ext_baseless_lib';
+		$lib = ucfirst($name);
+		$class = $this->prefix.$lib;
+		$this->ci_vfs_create($class, '<?php class '.$class.' { }', $this->ci_app_root, 'libraries');
+
+		// Test missing base class
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Unable to load the requested class: '.$lib
+		);
+		$this->assertNull($this->load->library($lib));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_library_config()
+	{
+		// Create library in VFS
+		$lib = 'unit_test_config_lib';
+		$class = 'CI_'.ucfirst($lib);
+		$content = '<?php class '.$class.' { public function __construct($params) { $this->config = $params; } }';
+		$this->ci_vfs_create($lib, $content, $this->ci_base_root, 'libraries');
+
+		// Create config file
+		$cfg = array(
+			'foo' => 'bar',
+			'bar' => 'baz',
+			'baz' => false
+		);
+		$this->ci_vfs_create($lib, '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config');
+
+		// Test object name and config
+		$obj = 'testy';
+		$this->assertNull($this->load->library($lib, NULL, $obj));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $obj, $this->ci_obj);
+		$this->assertEquals($cfg, $this->ci_obj->$obj->config);
+
+		// Test is_loaded
+		$this->assertEquals($obj, $this->load->is_loaded($lib));
 	}
 
 	// --------------------------------------------------------------------
 
 	public function test_load_library_in_application_dir()
 	{
-		$this->_setup_config_mock();
+		// Create library in VFS
+		$lib = 'super_test_library';
+		$class = ucfirst($lib);
+		$this->ci_vfs_create($lib, '<?php class '.$class.' { }', $this->ci_app_root, 'libraries');
 
-		$content = '<?php class Super_test_library {} ';
-
-		$model = vfsStream::newFile('Super_test_library.php')->withContent($content)->at($this->load->libs_dir);
-		$this->assertNull($this->load->library('super_test_library'));
+		// Load library
+		$this->assertNull($this->load->library($lib));
 
 		// Was the model class instantiated.
-		$this->assertTrue(class_exists('Super_test_library'));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $lib, $this->ci_obj);
 	}
 
 	// --------------------------------------------------------------------
 
-	private function _setup_config_mock()
+	public function test_driver()
 	{
-		// Mock up a config object until we
-		// figure out how to test the library configs
-		$config = $this->getMock('CI_Config', NULL, array(), '', FALSE);
-		$config->expects($this->any())
-			   ->method('load')
-			   ->will($this->returnValue(TRUE));
+		// Create driver in VFS
+		$driver = 'unit_test_driver';
+		$dir = ucfirst($driver);
+		$class = 'CI_'.$dir;
+		$content = '<?php class '.$class.' { } ';
+		$this->ci_vfs_create($driver, $content, $this->ci_base_root, 'libraries/'.$dir);
 
-		// Add the mock to our stdClass
-		$this->ci_instance_var('config', $config);
+		// Test loading as an array.
+		$this->assertNull($this->load->driver(array($driver)));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $driver, $this->ci_obj);
+
+		// Test loading as a library with a name
+		$obj = 'testdrive';
+		$this->assertNull($this->load->library($driver, NULL, $obj));
+		$this->assertAttributeInstanceOf($class, $obj, $this->ci_obj);
+
+		// Test no driver given
+		$this->assertFalse($this->load->driver());
+
+		// Test a string given to params
+		$this->assertNull($this->load->driver($driver, ' '));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_models()
+	{
+		$this->ci_set_core_class('model', 'CI_Model');
+
+		// Create model in VFS
+		$model = 'unit_test_model';
+		$class = ucfirst($model);
+		$content = '<?php class '.$class.' extends CI_Model {} ';
+		$this->ci_vfs_create($model, $content, $this->ci_app_root, 'models');
+
+		// Load model
+		$this->assertNull($this->load->model($model));
+
+		// Was the model class instantiated.
+		$this->assertTrue(class_exists($class));
+
+		// Test no model given
+		$this->assertNull($this->load->model(''));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_model_subdir()
+	{
+		// Make sure base class is loaded - we'll test _ci_include later
+		$this->ci_core_class('model');
+
+		// Create modelin VFS
+		$model = 'test_sub_model';
+		$base = 'CI_Model';
+		$class = ucfirst($model);
+		$subdir = 'cars';
+		$this->ci_vfs_create($model, '<?php class '.$class.' extends '.$base.' { }', $this->ci_app_root,
+			array('models', $subdir));
+
+		// Load model
+		$name = 'testors';
+		$this->assertNull($this->load->model($subdir.'/'.$model, $name));
+
+		// Was the model class instantiated?
+		$this->assertTrue(class_exists($class));
+		$this->assertObjectHasAttribute($name, $this->ci_obj);
+		$this->assertAttributeInstanceOf($base, $name, $this->ci_obj);
+		$this->assertAttributeInstanceOf($class, $name, $this->ci_obj);
+
+		// Test name conflict
+		$obj = 'conflict';
+		$this->ci_obj->$obj = new StdClass();
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: The model name you are loading is the name of a resource that is already being used: '.$obj
+		);
+		$this->load->model('not_real', $obj);
 	}
 
 	// --------------------------------------------------------------------
@@ -78,58 +242,38 @@
 
 	// --------------------------------------------------------------------
 
-	/**
-	 * @coverts CI_Loader::model
-	 */
-	public function test_models()
-	{
-		$this->ci_set_core_class('model', 'CI_Model');
-
-		$content = '<?php class Unit_test_model extends CI_Model {} ';
-
-		$model = vfsStream::newFile('unit_test_model.php')->withContent($content)->at($this->load->models_dir);
-
-		$this->assertNull($this->load->model('unit_test_model'));
-
-		// Was the model class instantiated.
-		$this->assertTrue(class_exists('Unit_test_model'));
-
-		// Test no model given
-		$this->assertNull($this->load->model(''));
-	}
-
-	// --------------------------------------------------------------------
-
 	// public function testDatabase()
 	// {
-	// 	$this->assertEquals(NULL, $this->load->database());
-	// 	$this->assertEquals(NULL, $this->load->dbutil());
+	// 	$this->assertNull($this->load->database());
+	// 	$this->assertNull($this->load->dbutil());
 	// }
 
 	// --------------------------------------------------------------------
 
-	/**
-	 * @coverts CI_Loader::view
-	 */
 	public function test_load_view()
 	{
-		$this->ci_set_core_class('output', 'CI_Output');
+		// Create view in VFS
+		$view = 'unit_test_view';
+		$var = 'hello';
+		$value = 'World!';
+		$content = 'This is my test page.  ';
+		$this->ci_vfs_create($view, $content.'<?php echo $'.$var.';', $this->ci_app_root, 'views');
 
-		$content = 'This is my test page.  <?php echo $hello; ?>';
-		$view = vfsStream::newFile('unit_test_view.php')->withContent($content)->at($this->load->views_dir);
+		// Test returning view
+		$out = $this->load->view($view, array($var => $value), TRUE);
+		$this->assertEquals($content.$value, $out);
 
-		// Use the optional return parameter in this test, so the view is not
-		// run through the output class.
-		$this->assertEquals('This is my test page.  World!',
-		$this->load->view('unit_test_view', array('hello' => "World!"), TRUE));
+		// Mock output class
+		$output = $this->getMock('CI_Output', array('append_output'));
+		$output->expects($this->once())->method('append_output')->with($content.$value);
+		$this->ci_instance_var('output', $output);
 
+		// Test view output
+		$this->assertNull($this->load->view($view, array($var => $value)));
 	}
 
 	// --------------------------------------------------------------------
 
-	/**
-	 * @coverts CI_Loader::view
-	 */
 	public function test_non_existent_view()
 	{
 		$this->setExpectedException(
@@ -144,14 +288,17 @@
 
 	public function test_file()
 	{
+		// Create view in VFS
+		$dir = 'views';
+		$file = 'ci_test_mock_file';
 		$content = 'Here is a test file, which we will load now.';
-		$file = vfsStream::newFile('ci_test_mock_file.php')->withContent($content)->at($this->load->views_dir);
+		$this->ci_vfs_create($file, $content, $this->ci_app_root, $dir);
 
 		// Just like load->view(), take the output class out of the mix here.
-		$load = $this->load->file(vfsStream::url('application').'/views/ci_test_mock_file.php', TRUE);
+		$out = $this->load->file(APPPATH.$dir.'/'.$file.'.php', TRUE);
+		$this->assertEquals($content, $out);
 
-		$this->assertEquals($content, $load);
-
+		// Test non-existent file
 		$this->setExpectedException(
 			'RuntimeException',
 			'CI Error: Unable to load the requested file: ci_test_file_not_exists'
@@ -164,21 +311,56 @@
 
 	public function test_vars()
 	{
-		$this->assertNull($this->load->vars(array('foo' => 'bar')));
-		$this->assertNull($this->load->vars('foo', 'bar'));
+		$key1 = 'foo';
+		$val1 = 'bar';
+		$key2 = 'boo';
+		$val2 = 'hoo';
+		$this->assertNull($this->load->vars(array($key1 => $val1)));
+		$this->assertNull($this->load->vars($key2, $val2));
+		$this->assertEquals($val1, $this->load->get_var($key1));
+		$this->assertEquals(array($key1 => $val1, $key2 => $val2), $this->load->get_vars());
 	}
 
 	// --------------------------------------------------------------------
 
 	public function test_helper()
 	{
-		$this->assertEquals(NULL, $this->load->helper('array'));
+		// Create helper in VFS
+		$helper = 'test';
+		$func = '_my_helper_test_func';
+		$content = '<?php function '.$func.'() { return true; } ';
+		$this->ci_vfs_create($helper.'_helper', $content, $this->ci_base_root, 'helpers');
 
+		// Create helper extension
+		$exfunc = '_my_extension_func';
+		$content = '<?php function '.$exfunc.'() { return true; } ';
+		$this->ci_vfs_create($this->prefix.$helper.'_helper', $content, $this->ci_app_root, 'helpers');
+
+		// Load helper
+		$this->assertNull($this->load->helper($helper));
+		$this->assertTrue(function_exists($func), $func.' does not exist');
+		$this->assertTrue(function_exists($exfunc), $exfunc.' does not exist');
+
+		// Create baseless extension
+		$ext = 'bad_ext';
+		$this->ci_vfs_create($this->prefix.$ext.'_helper', '', $this->ci_app_root, 'helpers');
+
+		// Test bad extension
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Unable to load the requested file: helpers/'.$ext.'_helper.php'
+		);
+		$this->load->helper($ext);
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_non_existent_helper()
+	{
 		$this->setExpectedException(
 			'RuntimeException',
 			'CI Error: Unable to load the requested file: helpers/bad_helper.php'
 		);
-
 		$this->load->helper('bad');
 	}
 
@@ -186,36 +368,153 @@
 
 	public function test_loading_multiple_helpers()
 	{
-		$this->assertEquals(NULL, $this->load->helpers(array('file', 'array', 'string')));
+		// Create helpers in VFS
+		$helpers = array();
+		$funcs = array();
+		$files = array();
+		for ($i = 1; $i <= 3; ++$i) {
+			$helper = 'test'.$i;
+			$helpers[] = $helper;
+			$func = '_my_helper_test_func'.$i;
+			$funcs[] = $func;
+			$files[$helper.'_helper'] = '<?php function '.$func.'() { return true; } ';
+		}
+		$this->ci_vfs_create($files, NULL, $this->ci_base_root, 'helpers');
+
+		// Load helpers
+		$this->assertNull($this->load->helpers($helpers));
+
+		// Verify helper existence
+		foreach ($funcs as $func) {
+			$this->assertTrue(function_exists($func), $func.' does not exist');
+		}
 	}
 
 	// --------------------------------------------------------------------
 
-	// public function testLanguage()
-	// {
-	// 	$this->assertEquals(NULL, $this->load->language('test'));
-	// }
+	public function test_language()
+	{
+		// Mock lang class and test load call
+		$file = 'test';
+		$lang = $this->getMock('CI_Lang', array('load'));
+		$lang->expects($this->once())->method('load')->with($file);
+		$this->ci_instance_var('lang', $lang);
+		$this->assertNull($this->load->language($file));
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_packages()
+	{
+		// Create model in VFS package path
+		$dir = 'third-party';
+		$lib = 'unit_test_package';
+		$class = 'CI_'.ucfirst($lib);
+		$this->ci_vfs_create($lib, '<?php class '.$class.' { }', $this->ci_app_root, array($dir, 'libraries'));
+
+		// Get paths
+		$paths = $this->load->get_package_paths(TRUE);
+
+		// Add path and verify
+		$path = APPPATH.$dir.'/';
+		$this->assertNull($this->load->add_package_path($path));
+		$this->assertContains($path, $this->load->get_package_paths(TRUE));
+
+		// Test successful load
+		$this->assertNull($this->load->library($lib));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+
+		// Add another path
+		$path2 = APPPATH.'another/';
+		$this->assertNull($this->load->add_package_path($path2));
+		$this->assertContains($path2, $this->load->get_package_paths(TRUE));
+
+		// Remove last path
+		$this->assertNull($this->load->remove_package_path());
+		$this->assertNotContains($path2, $this->load->get_package_paths(TRUE));
+
+		// Remove path and verify restored paths
+		$this->assertNull($this->load->remove_package_path($path));
+		$this->assertEquals($paths, $this->load->get_package_paths(TRUE));
+
+		// Test failed load without path
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Unable to load the requested class: '.$lib
+		);
+		$this->load->library($lib);
+	}
 
 	// --------------------------------------------------------------------
 
 	public function test_load_config()
 	{
-		$this->_setup_config_mock();
-		$this->assertNull($this->load->config('config', FALSE));
+		$cfg = 'someconfig';
+		$this->assertTrue($this->load->config($cfg, FALSE));
+		$this->assertContains($cfg, $this->ci_obj->config->loaded);
 	}
 
 	// --------------------------------------------------------------------
 
-	public function test_load_bad_config()
+	public function test_initialize()
 	{
-		$this->_setup_config_mock();
+		// Create helper in VFS
+		$helper = 'autohelp';
+		$hlp_func = '_autohelp_test_func';
+		$content = '<?php function '.$hlp_func.'() { return true; }';
+		$this->ci_vfs_create($helper.'_helper', $content, $this->ci_app_root, 'helpers');
 
-		$this->setExpectedException(
-			'RuntimeException',
-			'CI Error: The configuration file foobar.php does not exist.'
+		// Create library in VFS
+		$lib = 'autolib';
+		$lib_class = 'CI_'.ucfirst($lib);
+		$this->ci_vfs_create($lib, '<?php class '.$lib_class.' { }', $this->ci_base_root, 'libraries');
+
+		// Create driver in VFS
+		$drv = 'autodrv';
+		$subdir = ucfirst($drv);
+		$drv_class = 'CI_'.$subdir;
+		$this->ci_vfs_create($drv, '<?php class '.$drv_class.' { }', $this->ci_base_root, array('libraries', $subdir));
+
+		// Create model in VFS package path
+		$dir = 'testdir';
+		$path = APPPATH.$dir.'/';
+		$model = 'automod';
+		$mod_class = ucfirst($model);
+		$this->ci_vfs_create($model, '<?php class '.$mod_class.' { }', $this->ci_app_root, array($dir, 'models'));
+
+		// Create autoloader config
+		$cfg = array(
+			'packages' => array($path),
+			'helper' => array($helper),
+			'libraries' => array($lib),
+			'drivers' => array($drv),
+			'model' => array($model),
+			'config' => array('config1', 'config2')
 		);
+		$this->ci_vfs_create('autoload', '<?php $autoload = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config');
 
-		$this->load->config('foobar', FALSE);
+		$this->load->initialize();
+
+		// Verify path
+		$this->assertContains($path, $this->load->get_package_paths());
+
+		// Verify helper
+		$this->assertTrue(function_exists($hlp_func), $hlp_func.' does not exist');
+
+		// Verify library
+		$this->assertTrue(class_exists($lib_class), $lib_class.' does not exist');
+		$this->assertAttributeInstanceOf($lib_class, $lib, $this->ci_obj);
+
+		// Verify driver
+		$this->assertTrue(class_exists($drv_class), $drv_class.' does not exist');
+		$this->assertAttributeInstanceOf($drv_class, $drv, $this->ci_obj);
+
+		// Verify model
+		$this->assertTrue(class_exists($mod_class), $mod_class.' does not exist');
+		$this->assertAttributeInstanceOf($mod_class, $model, $this->ci_obj);
+
+		// Verify config calls
+		$this->assertEquals($cfg['config'], $this->ci_obj->config->loaded);
 	}
 
 }
\ No newline at end of file
diff --git a/tests/codeigniter/core/Output_test.php b/tests/codeigniter/core/Output_test.php
new file mode 100644
index 0000000..d825240
--- /dev/null
+++ b/tests/codeigniter/core/Output_test.php
@@ -0,0 +1,37 @@
+<?php
+
+class Output_test extends CI_TestCase {
+
+	public $output;
+
+	public function set_up()
+	{
+		$this->ci_set_config('charset', 'UTF-8');
+		$output = $this->ci_core_class('output');
+		$this->output = new $output();
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_get_content_type()
+	{
+		$this->assertEquals('text/html', $this->output->get_content_type());
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_get_header()
+	{
+		$this->assertNull($this->output->get_header('Non-Existent-Header'));
+
+		// TODO: Find a way to test header() values as well. Currently,
+		//	 PHPUnit prevents this by not using output buffering.
+
+		$this->output->set_content_type('text/plain', 'WINDOWS-1251');
+		$this->assertEquals(
+			'text/plain; charset=windows-1251',		// Character set is converted to lowercase
+			$this->output->get_header('content-type')	// Case-insensitive comparison
+		);
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/core/URI_test.php b/tests/codeigniter/core/URI_test.php
index 60ed1a4..e2deabe 100644
--- a/tests/codeigniter/core/URI_test.php
+++ b/tests/codeigniter/core/URI_test.php
@@ -40,13 +40,13 @@
 			'/index.php?/controller/method/?var=foo' => 'controller/method'
 		);
 
-		foreach($requests as $request => $expected)
+		foreach ($requests as $request => $expected)
 		{
 			$_SERVER['SCRIPT_NAME'] = '/index.php';
 			$_SERVER['REQUEST_URI'] = $request;
 
 			$this->uri->_fetch_uri_string();
-			$this->assertEquals($expected, $this->uri->uri_string );
+			$this->assertEquals($expected, $this->uri->uri_string);
 		}
 
 		// Test a subfolder
@@ -60,10 +60,10 @@
 		unset($_SERVER['REQUEST_URI']);
 
 		// life to path info
-		$_SERVER['PATH_INFO'] = $a = '/controller/method/';
+		$_SERVER['PATH_INFO'] = '/controller/method/';
 
 		$this->uri->_fetch_uri_string();
-		$this->assertEquals($a, $this->uri->uri_string);
+		$this->assertEquals('controller/method', $this->uri->uri_string);
 
 		// death to path info
 		// At this point your server must be seriously drunk
@@ -72,7 +72,7 @@
 		$_SERVER['QUERY_STRING'] = '/controller/method/';
 
 		$this->uri->_fetch_uri_string();
-		$this->assertEquals($a, $this->uri->uri_string);
+		$this->assertEquals('controller/method', $this->uri->uri_string);
 
 		// At this point your server is a labotomy victim
 		unset($_SERVER['QUERY_STRING']);
@@ -80,7 +80,7 @@
 		$_GET['/controller/method/'] = '';
 
 		$this->uri->_fetch_uri_string();
-		$this->assertEquals($a, $this->uri->uri_string);
+		$this->assertEquals('controller/method', $this->uri->uri_string);
 
 		// Test coverage implies that these will work
 		// uri_protocol: REQUEST_URI
diff --git a/tests/codeigniter/database/DB_driver_test.php b/tests/codeigniter/database/DB_driver_test.php
index 9e16e29..c04c42b 100644
--- a/tests/codeigniter/database/DB_driver_test.php
+++ b/tests/codeigniter/database/DB_driver_test.php
@@ -5,7 +5,7 @@
 	public function test_initialize()
 	{
 		$config = Mock_Database_DB::config(DB_DRIVER);
-		$driver_name = current(explode('/', DB_DRIVER));
+		sscanf(DB_DRIVER, '%[^/]/', $driver_name);
 		$driver = $this->$driver_name($config[DB_DRIVER]);
 
 		$this->assertTrue($driver->initialize());
@@ -21,6 +21,11 @@
 		return new Mock_Database_Drivers_Mysql($config);
 	}
 
+	protected function mysqli($config)
+	{
+		return new Mock_Database_Drivers_Mysqli($config);
+	}
+
 	protected function sqlite($config)
 	{
 		return new Mock_Database_Drivers_Sqlite($config);
diff --git a/tests/codeigniter/database/query_builder/escape_test.php b/tests/codeigniter/database/query_builder/escape_test.php
index c6380dd..27e678f 100644
--- a/tests/codeigniter/database/query_builder/escape_test.php
+++ b/tests/codeigniter/database/query_builder/escape_test.php
@@ -27,7 +27,7 @@
 
 		if (strpos(DB_DRIVER, 'mysql') !== FALSE)
 		{
-			$sql = "SELECT `value` FROM `misc` WHERE `key` LIKE '$string%' ESCAPE '';";
+			$sql = "SELECT `value` FROM `misc` WHERE `key` LIKE '$string%' ESCAPE '!';";
 		}
 		else
 		{
@@ -52,7 +52,7 @@
 
 		if (strpos(DB_DRIVER, 'mysql') !== FALSE)
 		{
-			$sql = "SELECT `value` FROM `misc` WHERE `key` LIKE '$string%' ESCAPE '';";
+			$sql = "SELECT `value` FROM `misc` WHERE `key` LIKE '$string%' ESCAPE '!';";
 		}
 		else
 		{
diff --git a/tests/codeigniter/database/query_builder/insert_test.php b/tests/codeigniter/database/query_builder/insert_test.php
index a9aafb6..30c0556 100644
--- a/tests/codeigniter/database/query_builder/insert_test.php
+++ b/tests/codeigniter/database/query_builder/insert_test.php
@@ -52,7 +52,7 @@
 		// Do insert batch except for sqlite driver
 		if (strpos(DB_DRIVER, 'sqlite') === FALSE)
 		{
-			$this->assertTrue($this->db->insert_batch('job', $job_datas));
+			$this->assertEquals(2, $this->db->insert_batch('job', $job_datas));
 
 			$job_2 = $this->db->where('id', 2)->get('job')->row();
 			$job_3 = $this->db->where('id', 3)->get('job')->row();
diff --git a/tests/codeigniter/database/query_builder/like_test.php b/tests/codeigniter/database/query_builder/like_test.php
index 5f3e522..2736fbe 100644
--- a/tests/codeigniter/database/query_builder/like_test.php
+++ b/tests/codeigniter/database/query_builder/like_test.php
@@ -87,4 +87,20 @@
 		$this->assertEquals('Musician', $jobs[2]['name']);
 	}
 
+	// ------------------------------------------------------------------------
+
+	/**
+	 * GitHub issue #273
+	 *
+	 * @see ./mocks/schema/skeleton.php
+	 */
+	public function test_like_spaces_and_tabs()
+	{
+		$spaces = $this->db->like('value', '   ')->get('misc')->result_array();
+		$tabs = $this->db->like('value', "\t")->get('misc')->result_array();
+
+		$this->assertEquals(1, count($spaces));
+		$this->assertEquals(1, count($tabs));
+	}
+
 }
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/captcha_helper_test.php b/tests/codeigniter/helpers/captcha_helper_test.php
new file mode 100644
index 0000000..fc86305
--- /dev/null
+++ b/tests/codeigniter/helpers/captcha_helper_test.php
@@ -0,0 +1,10 @@
+<?php
+
+class Captcha_helper_test extends CI_TestCase {
+
+	public function test_create_captcha()
+	{
+		$this->markTestSkipped('Cant easily test');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/cookie_helper_test.php b/tests/codeigniter/helpers/cookie_helper_test.php
new file mode 100644
index 0000000..fba68f2
--- /dev/null
+++ b/tests/codeigniter/helpers/cookie_helper_test.php
@@ -0,0 +1,59 @@
+<?php
+
+class Cookie_helper_test extends CI_TestCase {
+
+	public function set_up()
+	{
+		$this->helper('cookie');
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_set_cookie()
+	{
+		/*$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new $input_cls);
+
+		$this->assertTrue(set_cookie(
+			'my_cookie',
+			'foobar'
+		));*/
+
+		$this->markTestSkipped('Need to find a way to overcome a headers already set exception');
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_get_cookie()
+	{
+		$_COOKIE['foo'] = 'bar';
+
+		$security = new Mock_Core_Security();
+		$utf8 = new Mock_Core_Utf8();
+		$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new Mock_Core_Input($security, $utf8));
+
+		$this->assertEquals('bar', get_cookie('foo', FALSE));
+		$this->assertEquals('bar', get_cookie('foo', TRUE));
+
+		$_COOKIE['bar'] = "Hello, i try to <script>alert('Hack');</script> your site";
+
+		$this->assertEquals("Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site", get_cookie('bar', TRUE));
+		$this->assertEquals("Hello, i try to <script>alert('Hack');</script> your site", get_cookie('bar', FALSE));
+	}
+
+	// ------------------------------------------------------------------------
+
+	function test_delete_cookie()
+	{
+		/*$input_cls = $this->ci_core_class('input');
+		$this->ci_instance_var('input', new $input_cls);
+
+		$this->assertTrue(delete_cookie(
+			'my_cookie'
+		));*/
+
+		$this->markTestSkipped('Need to find a way to overcome a headers already set exception');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/date_helper_test.php b/tests/codeigniter/helpers/date_helper_test.php
index 1b79b94..0f16e6c 100644
--- a/tests/codeigniter/helpers/date_helper_test.php
+++ b/tests/codeigniter/helpers/date_helper_test.php
@@ -5,7 +5,6 @@
 	public function set_up()
 	{
 		$this->helper('date');
-
 		$this->time = time();
 	}
 
@@ -168,6 +167,8 @@
 
 	public function test_timespan()
 	{
+		$this->ci_vfs_clone('system/language/english/date_lang.php');
+
 		$loader_cls = $this->ci_core_class('load');
 		$this->ci_instance_var('load', new $loader_cls);
 
@@ -290,6 +291,29 @@
 		$this->assertEquals(0, timezones('non_existant'));
 	}
 
+	// ------------------------------------------------------------------------
+
+	public function test_date_range()
+	{
+		$dates = array(
+			'29-01-2012', '30-01-2012', '31-01-2012',
+			'01-02-2012', '02-02-2012', '03-02-2012',
+			'04-02-2012', '05-02-2012', '06-02-2012',
+			'07-02-2012', '08-02-2012', '09-02-2012',
+			'10-02-2012', '11-02-2012', '12-02-2012',
+			'13-02-2012', '14-02-2012', '15-02-2012',
+			'16-02-2012', '17-02-2012', '18-02-2012',
+			'19-02-2012', '20-02-2012', '21-02-2012',
+			'22-02-2012', '23-02-2012', '24-02-2012',
+			'25-02-2012', '26-02-2012', '27-02-2012',
+			'28-02-2012', '29-02-2012', '01-03-2012'
+		);
+
+		$this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), mktime(12, 0, 0, 3, 1, 2012), TRUE, 'd-m-Y'));
+		array_pop($dates);
+		$this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), 31, FALSE, 'd-m-Y'));
+	}
+
 }
 
 /* End of file date_helper_test.php */
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/directory_helper_test.php b/tests/codeigniter/helpers/directory_helper_test.php
index 176ff1d..41370e6 100644
--- a/tests/codeigniter/helpers/directory_helper_test.php
+++ b/tests/codeigniter/helpers/directory_helper_test.php
@@ -19,6 +19,7 @@
 				'benchmark.html' => '',
 				'database' => array('active_record.html' => '', 'binds.html' => ''),
 				'email.html' => '',
+				'0' => '',
 				'.hiddenfile.txt' => ''
 			)
 		);
@@ -27,22 +28,23 @@
 
 		// test default recursive behavior
 		$expected = array(
-			'libraries' => array(
+			'libraries/' => array(
 				'benchmark.html',
-				'database' => array('active_record.html', 'binds.html'),
-				'email.html'
+				'database/' => array('active_record.html', 'binds.html'),
+				'email.html',
+				'0'
 			)
 		);
 
 		$this->assertEquals($expected, directory_map(vfsStream::url('testDir')));
 
 		// test detection of hidden files
-		$expected['libraries'][] = '.hiddenfile.txt';
+		$expected['libraries/'][] = '.hiddenfile.txt';
 
 		$this->assertEquals($expected, directory_map(vfsStream::url('testDir'), FALSE, TRUE));
 
 		// test recursion depth behavior
-		$this->assertEquals(array('libraries'), directory_map(vfsStream::url('testDir'), 1));
+		$this->assertEquals(array('libraries/'), directory_map(vfsStream::url('testDir'), 1));
 	}
 
 }
diff --git a/tests/codeigniter/helpers/download_helper_test.php b/tests/codeigniter/helpers/download_helper_test.php
new file mode 100644
index 0000000..d2b42e4
--- /dev/null
+++ b/tests/codeigniter/helpers/download_helper_test.php
@@ -0,0 +1,10 @@
+<?php
+
+class Download_helper_test extends CI_TestCase {
+
+	public function test_force_download()
+	{
+		$this->markTestSkipped('Cant easily test');
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/file_helper_test.php b/tests/codeigniter/helpers/file_helper_test.php
index 9b03da9..3a6c73a 100644
--- a/tests/codeigniter/helpers/file_helper_test.php
+++ b/tests/codeigniter/helpers/file_helper_test.php
@@ -148,6 +148,4 @@
 	//
 	// }
 
-	// --------------------------------------------------------------------
-
 }
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/form_helper_test.php b/tests/codeigniter/helpers/form_helper_test.php
index 1a30ed9..8916527 100644
--- a/tests/codeigniter/helpers/form_helper_test.php
+++ b/tests/codeigniter/helpers/form_helper_test.php
@@ -1,10 +1,14 @@
 <?php
 
-require BASEPATH . 'core/Common.php';
-require BASEPATH . 'helpers/form_helper.php';
-
 class Form_helper_test extends CI_TestCase
 {
+	public function set_up()
+	{
+		$this->helper('form');
+	}
+
+	// ------------------------------------------------------------------------
+
 	public function test_form_hidden()
 	{
 		$expected = <<<EOH
@@ -16,6 +20,8 @@
 		$this->assertEquals($expected, form_hidden('username', 'johndoe'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_input()
 	{
 		$expected = <<<EOH
@@ -35,6 +41,8 @@
 		$this->assertEquals($expected, form_input($data));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_password()
 	{
 		$expected = <<<EOH
@@ -45,6 +53,8 @@
 		$this->assertEquals($expected, form_password('password'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_upload()
 	{
 		$expected = <<<EOH
@@ -55,6 +65,8 @@
 		$this->assertEquals($expected, form_upload('attachment'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_textarea()
 	{
 		$expected = <<<EOH
@@ -65,6 +77,8 @@
 		$this->assertEquals($expected, form_textarea('notes', 'Notes'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_dropdown()
 	{
 		$expected = <<<EOH
@@ -128,6 +142,8 @@
 		$this->assertEquals($expected, form_dropdown('cars', $options, array('volvo', 'audi')));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_multiselect()
 	{
 		$expected = <<<EOH
@@ -150,6 +166,8 @@
 		$this->assertEquals($expected, form_multiselect('shirts[]', $options, array('med', 'large')));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_fieldset()
 	{
 		$expected = <<<EOH
@@ -161,6 +179,8 @@
 		$this->assertEquals($expected, form_fieldset('Address Information'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_fieldset_close()
 	{
 		$expected = <<<EOH
@@ -170,6 +190,8 @@
 		$this->assertEquals($expected, form_fieldset_close('</div></div>'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_checkbox()
 	{
 		$expected = <<<EOH
@@ -180,6 +202,8 @@
 		$this->assertEquals($expected, form_checkbox('newsletter', 'accept', TRUE));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_radio()
 	{
 		$expected = <<<EOH
@@ -190,6 +214,8 @@
 		$this->assertEquals($expected, form_radio('newsletter', 'accept', TRUE));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_submit()
 	{
 		$expected = <<<EOH
@@ -200,6 +226,8 @@
 		$this->assertEquals($expected, form_submit('mysubmit', 'Submit Post!'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_label()
 	{
 		$expected = <<<EOH
@@ -209,6 +237,8 @@
 		$this->assertEquals($expected, form_label('What is your Name', 'username'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_reset()
 	{
 		$expected = <<<EOH
@@ -219,6 +249,8 @@
 		$this->assertEquals($expected, form_reset('myreset', 'Reset'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_button()
 	{
 		$expected = <<<EOH
@@ -229,6 +261,8 @@
 		$this->assertEquals($expected, form_button('name', 'content'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_close()
 	{
 		$expected = <<<EOH
@@ -238,11 +272,19 @@
 		$this->assertEquals($expected, form_close('</div></div>'));
 	}
 
+	// ------------------------------------------------------------------------
+
 	public function test_form_prep()
 	{
-		$expected = 'Here is a string containing &quot;quoted&quot; text.';
+		$this->assertEquals(
+			'Here is a string containing &quot;quoted&quot; text.',
+			form_prep('Here is a string containing "quoted" text.')
+		);
 
-		$this->assertEquals($expected, form_prep('Here is a string containing "quoted" text.'));
+		$this->assertEquals(
+			'Here is a string containing a &lt;tag&gt;.',
+			form_prep('Here is a string containing a <tag>.', TRUE)
+		);
 	}
 
 }
diff --git a/tests/codeigniter/helpers/html_helper_test.php b/tests/codeigniter/helpers/html_helper_test.php
index 9a7bb48..d66ad89 100644
--- a/tests/codeigniter/helpers/html_helper_test.php
+++ b/tests/codeigniter/helpers/html_helper_test.php
@@ -22,6 +22,22 @@
 		$this->assertEquals('<h2 class="bar">foobar</h2>', heading('foobar', 2, 'class="bar"'));
 	}
 
+	public function test_heading_array_attributes()
+	{
+		// Test array of attributes
+		$this->assertEquals('<h2 class="bar" id="foo">foobar</h2>', heading('foobar', 2, array('class' => 'bar', 'id' => 'foo')));
+	}
+
+	public function test_heading_object_attributes()
+	{
+		// Test array of attributes
+		$this->assertEquals('<h2 class="bar" id="foo">foobar</h2>', heading('foobar', 2, array('class' => 'bar', 'id' => 'foo')));
+		$test = new stdClass;
+		$test->class = "bar";
+		$test->id = "foo";
+		$this->assertEquals('<h2 class="bar" id="foo">foobar</h2>', heading('foobar', 2, $test));
+	}
+
 	// ------------------------------------------------------------------------
 
 	public function test_Ul()
diff --git a/tests/codeigniter/helpers/language_helper_test.php b/tests/codeigniter/helpers/language_helper_test.php
new file mode 100644
index 0000000..176da68
--- /dev/null
+++ b/tests/codeigniter/helpers/language_helper_test.php
@@ -0,0 +1,16 @@
+<?php
+
+class Language_helper_test extends CI_TestCase {
+
+	public function test_lang()
+	{
+		$this->helper('language');
+		$lang = $this->getMock('CI_Lang', array('line'));
+		$lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
+		$this->ci_instance_var('lang', $lang);
+
+		$this->assertFalse(lang(1));
+		$this->assertEquals('<label for="foo" class="bar"></label>', lang(1, 'foo', array('class' => 'bar')));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/number_helper_test.php b/tests/codeigniter/helpers/number_helper_test.php
index ef6aae1..817db2c 100644
--- a/tests/codeigniter/helpers/number_helper_test.php
+++ b/tests/codeigniter/helpers/number_helper_test.php
@@ -11,31 +11,18 @@
 
 		// Mock away load, too much going on in there,
 		// we'll just check for the expected parameter
-
 		$lang = $this->getMock($lang_cls, array('load'));
 		$lang->expects($this->once())
 			 ->method('load')
 			 ->with($this->equalTo('number'));
 
 		// Assign the proper language array
-
-		$lang->language = $this->_get_lang('number');
+		$lang->language = $this->lang('number');
 
 		// We don't have a controller, so just create
 		// a cheap class to act as our super object.
 		// Make sure it has a lang attribute.
-
-		$obj = new stdClass;
-		$obj->lang = $lang;
-		$this->ci_instance($obj);
-	}
-
-	// Quick helper to actually grab the language
-	// file. Consider moving this to ci_testcase?
-	public function _get_lang($name)
-	{
-		require BASEPATH.'language/english/'.$name.'_lang.php';
-		return $lang;
+		$this->ci_instance_var('lang', $lang);
 	}
 
 	public function test_byte_format()
diff --git a/tests/codeigniter/helpers/path_helper_test.php b/tests/codeigniter/helpers/path_helper_test.php
index 0faf6f3..d25c3ed 100644
--- a/tests/codeigniter/helpers/path_helper_test.php
+++ b/tests/codeigniter/helpers/path_helper_test.php
@@ -26,6 +26,7 @@
 
 		set_realpath('/path/to/nowhere', TRUE);
 	}
+
 }
 
 /* End of file path_helper_test.php */
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/security_helper_test.php b/tests/codeigniter/helpers/security_helper_test.php
new file mode 100644
index 0000000..effd3ec
--- /dev/null
+++ b/tests/codeigniter/helpers/security_helper_test.php
@@ -0,0 +1,64 @@
+<?php
+
+class Security_helper_tests extends CI_TestCase {
+
+	function setUp()
+	{
+		$this->helper('security');
+		$obj = new stdClass;
+		$obj->security = new Mock_Core_Security();
+		$this->ci_instance($obj);
+	}
+
+	function test_xss_clean()
+	{
+		$this->assertEquals('foo', xss_clean('foo'));
+
+		$this->assertEquals("Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site", xss_clean("Hello, i try to <script>alert('Hack');</script> your site"));
+	}
+
+	function test_sanitize_filename()
+	{
+		$this->assertEquals('hello.doc', sanitize_filename('hello.doc'));
+
+		$filename = './<!--foo-->';
+		$this->assertEquals('foo', sanitize_filename($filename));
+	}
+
+	function test_do_hash()
+	{
+		$md5 = md5('foo');
+		$sha1 = sha1('foo');
+
+		$algos = hash_algos();
+		$algo_results = array();
+		foreach ($algos as $k => $v)
+		{
+			$algo_results[$v] = hash($v, 'foo');
+		}
+
+		$this->assertEquals($sha1, do_hash('foo'));
+		$this->assertEquals($sha1, do_hash('foo', 'sha1'));
+		$this->assertEquals($md5, do_hash('foo', 'md5'));
+		$this->assertEquals($md5, do_hash('foo', 'foobar'));
+
+		// Test each algorithm available to PHP
+		foreach ($algo_results as $algo => $result)
+		{
+			$this->assertEquals($result, do_hash('foo', $algo));
+		}
+	}
+
+	function test_strip_image_tags()
+	{
+		$this->assertEquals('http://example.com/spacer.gif', strip_image_tags('http://example.com/spacer.gif'));
+
+		$this->assertEquals('http://example.com/spacer.gif', strip_image_tags('<img src="http://example.com/spacer.gif" alt="Who needs CSS when you have a spacer.gif?" />'));
+	}
+
+	function test_encode_php_tags()
+	{
+		$this->assertEquals('&lt;? echo $foo; ?&gt;', encode_php_tags('<? echo $foo; ?>'));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/helpers/text_helper_test.php b/tests/codeigniter/helpers/text_helper_test.php
index f131469..d75d262 100644
--- a/tests/codeigniter/helpers/text_helper_test.php
+++ b/tests/codeigniter/helpers/text_helper_test.php
@@ -64,6 +64,7 @@
 
 	public function test_convert_accented_characters()
 	{
+		$this->ci_vfs_clone('application/config/foreign_chars.php');
 		$this->assertEquals('AAAeEEEIIOOEUUUeY', convert_accented_characters('ÀÂÄÈÊËÎÏÔŒÙÛÜŸ'));
 		$this->assertEquals('a e i o u n ue', convert_accented_characters('á é í ó ú ñ ü'));
 	}
diff --git a/tests/codeigniter/helpers/url_helper_test.php b/tests/codeigniter/helpers/url_helper_test.php
index c81c5f1..5fc3642 100644
--- a/tests/codeigniter/helpers/url_helper_test.php
+++ b/tests/codeigniter/helpers/url_helper_test.php
@@ -51,6 +51,8 @@
 			'www.codeigniter.com test' => '<a href="http://www.codeigniter.com">http://www.codeigniter.com</a> test',
 			'This is my noreply@codeigniter.com test' => 'This is my noreply@codeigniter.com test',
 			'<br />www.google.com' => '<br /><a href="http://www.google.com">http://www.google.com</a>',
+			'Download CodeIgniter at www.codeigniter.com. Period test.' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>. Period test.',
+			'Download CodeIgniter at www.codeigniter.com, comma test' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>, comma test'
 		);
 
 		foreach ($strings as $in => $out)
diff --git a/tests/codeigniter/libraries/Calendar_test.php b/tests/codeigniter/libraries/Calendar_test.php
new file mode 100644
index 0000000..952e8a8
--- /dev/null
+++ b/tests/codeigniter/libraries/Calendar_test.php
@@ -0,0 +1,201 @@
+<?php
+
+class Calendar_test extends CI_TestCase {
+
+	function set_up()
+	{
+		$lang = $this->getMock('CI_Lang', array('load', 'line'));
+		$lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
+		$this->ci_instance_var('lang', $lang);
+		$this->calendar = new CI_Calendar();
+	}
+
+	function test_initialize()
+	{
+		$this->calendar->initialize(array(
+			'month_type'	=>	'short',
+			'start_day'	=>	'monday'
+		));
+		$this->assertEquals('short', $this->calendar->month_type);
+		$this->assertEquals('monday', $this->calendar->start_day);
+	}
+
+	function test_generate()
+	{
+		$no_events = '<table border="0" cellpadding="4" cellspacing="0">
+
+<tr>
+<th colspan="7">September&nbsp;2011</th>
+
+</tr>
+
+<tr>
+<td>Su</td><td>Mo</td><td>Tu</td><td>We</td><td>Th</td><td>Fr</td><td>Sa</td>
+</tr>
+
+<tr>
+<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>1</td><td>2</td><td>3</td>
+</tr>
+
+<tr>
+<td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td>
+</tr>
+
+<tr>
+<td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td>
+</tr>
+
+<tr>
+<td>18</td><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td>
+</tr>
+
+<tr>
+<td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>&nbsp;</td>
+</tr>
+
+</table>';
+
+		$this->assertEquals($no_events, $this->calendar->generate(2011, 9));
+
+		$data = array(
+			3  => 'http://example.com/news/article/2006/03/',
+			7  => 'http://example.com/news/article/2006/07/',
+			13 => 'http://example.com/news/article/2006/13/',
+			26 => 'http://example.com/news/article/2006/26/'
+		);
+
+		$events = '<table border="0" cellpadding="4" cellspacing="0">
+
+<tr>
+<th colspan="7">September&nbsp;2011</th>
+
+</tr>
+
+<tr>
+<td>Su</td><td>Mo</td><td>Tu</td><td>We</td><td>Th</td><td>Fr</td><td>Sa</td>
+</tr>
+
+<tr>
+<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>1</td><td>2</td><td><a href="http://example.com/news/article/2006/03/">3</a></td>
+</tr>
+
+<tr>
+<td>4</td><td>5</td><td>6</td><td><a href="http://example.com/news/article/2006/07/">7</a></td><td>8</td><td>9</td><td>10</td>
+</tr>
+
+<tr>
+<td>11</td><td>12</td><td><a href="http://example.com/news/article/2006/13/">13</a></td><td>14</td><td>15</td><td>16</td><td>17</td>
+</tr>
+
+<tr>
+<td>18</td><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td>
+</tr>
+
+<tr>
+<td>25</td><td><a href="http://example.com/news/article/2006/26/">26</a></td><td>27</td><td>28</td><td>29</td><td>30</td><td>&nbsp;</td>
+</tr>
+
+</table>';
+
+		$this->assertEquals($events, $this->calendar->generate(2011, 9, $data));
+	}
+
+	function test_get_month_name()
+	{
+		$this->calendar->month_type = NULL;
+		$this->assertEquals('January', $this->calendar->get_month_name('01'));
+
+		$this->calendar->month_type = 'short';
+		$this->assertEquals('Jan', $this->calendar->get_month_name('01'));
+	}
+
+	function test_get_day_names()
+	{
+		$this->assertEquals(array(
+			'Sunday',
+			'Monday',
+			'Tuesday',
+			'Wednesday',
+			'Thursday',
+			'Friday',
+			'Saturday'
+		), $this->calendar->get_day_names('long'));
+
+		$this->assertEquals(array(
+			'Sun',
+			'Mon',
+			'Tue',
+			'Wed',
+			'Thu',
+			'Fri',
+			'Sat'
+		), $this->calendar->get_day_names('short'));
+
+		$this->calendar->day_type = NULL;
+
+		$this->assertEquals(array(
+			'Su',
+			'Mo',
+			'Tu',
+			'We',
+			'Th',
+			'Fr',
+			'Sa'
+		), $this->calendar->get_day_names());
+	}
+
+	function test_adjust_date()
+	{
+		$this->assertEquals(array('month' => 8, 'year' => 2012), $this->calendar->adjust_date(8, 2012));
+		$this->assertEquals(array('month' => 1, 'year' => 2013), $this->calendar->adjust_date(13, 2012));
+	}
+
+	function test_get_total_days()
+	{
+		$this->assertEquals(0, $this->calendar->get_total_days(13, 2012));
+
+		$this->assertEquals(31, $this->calendar->get_total_days(1, 2012));
+		$this->assertEquals(28, $this->calendar->get_total_days(2, 2011));
+		$this->assertEquals(29, $this->calendar->get_total_days(2, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(3, 2012));
+		$this->assertEquals(30, $this->calendar->get_total_days(4, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(5, 2012));
+		$this->assertEquals(30, $this->calendar->get_total_days(6, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(7, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(8, 2012));
+		$this->assertEquals(30, $this->calendar->get_total_days(9, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(10, 2012));
+		$this->assertEquals(30, $this->calendar->get_total_days(11, 2012));
+		$this->assertEquals(31, $this->calendar->get_total_days(12, 2012));
+	}
+
+	function test_default_template()
+	{
+		$array = array(
+			'table_open'				=> '<table border="0" cellpadding="4" cellspacing="0">',
+			'heading_row_start'			=> '<tr>',
+			'heading_previous_cell'		=> '<th><a href="{previous_url}">&lt;&lt;</a></th>',
+			'heading_title_cell'		=> '<th colspan="{colspan}">{heading}</th>',
+			'heading_next_cell'			=> '<th><a href="{next_url}">&gt;&gt;</a></th>',
+			'heading_row_end'			=> '</tr>',
+			'week_row_start'			=> '<tr>',
+			'week_day_cell'				=> '<td>{week_day}</td>',
+			'week_row_end'				=> '</tr>',
+			'cal_row_start'				=> '<tr>',
+			'cal_cell_start'			=> '<td>',
+			'cal_cell_start_today'		=> '<td>',
+			'cal_cell_content'			=> '<a href="{content}">{day}</a>',
+			'cal_cell_content_today'	=> '<a href="{content}"><strong>{day}</strong></a>',
+			'cal_cell_no_content'		=> '{day}',
+			'cal_cell_no_content_today'	=> '<strong>{day}</strong>',
+			'cal_cell_blank'			=> '&nbsp;',
+			'cal_cell_end'				=> '</td>',
+			'cal_cell_end_today'		=> '</td>',
+			'cal_row_end'				=> '</tr>',
+			'table_close'				=> '</table>'
+		);
+
+		$this->assertEquals($array, $this->calendar->default_template());
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/libraries/Driver_test.php b/tests/codeigniter/libraries/Driver_test.php
new file mode 100644
index 0000000..d98e8ab
--- /dev/null
+++ b/tests/codeigniter/libraries/Driver_test.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * Driver library base class unit test
+ */
+class Driver_test extends CI_TestCase {
+	/**
+	 * Set up test framework
+	 */
+	public function set_up()
+	{
+		// Set our subclass prefix
+		$this->subclass = 'Mock_Libraries_';
+		$this->ci_set_config('subclass_prefix', $this->subclass);
+
+        // Mock Loader->get_package_paths
+        $paths = 'get_package_paths';
+        $ldr = $this->getMock('CI_Loader', array($paths));
+        $ldr->expects($this->any())->method($paths)->will($this->returnValue(array(APPPATH, BASEPATH)));
+        $this->ci_instance_var('load', $ldr);
+
+		// Create mock driver library
+		$this->name = 'Driver';
+		$this->lib = new Mock_Libraries_Driver();
+	}
+
+	/**
+	 * Test driver child loading
+	 */
+	public function test_load_driver()
+	{
+		// Create driver file
+		$driver = 'basic';
+		$file = $this->name.'_'.$driver;
+		$class = 'CI_'.$file;
+		$prop = 'called';
+		$content = '<?php class '.$class.' extends CI_Driver { public $'.$prop.' = FALSE; '.
+			'public function decorate($parent) { $this->'.$prop.' = TRUE; } }';
+		$this->ci_vfs_create($file, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers'));
+
+		// Make driver valid
+		$this->lib->driver_list($driver);
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Did lib name get set?
+		$this->assertEquals($this->name, $this->lib->get_name());
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Was decorate called?
+		$this->assertObjectHasAttribute($prop, $this->lib->$driver);
+		$this->assertTrue($this->lib->$driver->$prop);
+
+		// Do we get an error for an invalid driver?
+		$driver = 'unlisted';
+		$this->setExpectedException('RuntimeException', 'CI Error: Invalid driver requested: '.$this->name.'_'.$driver);
+		$this->lib->load_driver($driver);
+	}
+
+	/**
+	 * Test loading lowercase from app path
+	 */
+	public function test_load_app_driver()
+	{
+		// Create driver file
+		$driver = 'lowpack';
+		$file = $this->name.'_'.$driver;
+		$class = 'CI_'.$file;
+		$content = '<?php class '.$class.' extends CI_Driver {  }';
+		$this->ci_vfs_create($file, $content, $this->ci_app_root,
+			array('libraries', $this->name, 'drivers'));
+
+		// Make valid list
+		$nodriver = 'absent';
+		$this->lib->driver_list(array($driver, $nodriver));
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Do we get an error for a non-existent driver?
+		$this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested driver: CI_'.
+			$this->name.'_'.$nodriver);
+		$this->lib->load_driver($nodriver);
+	}
+
+	/**
+	 * Test loading driver extension
+	 */
+	public function test_load_driver_ext()
+	{
+		// Create base file
+		$driver = 'extend';
+		$base = $this->name.'_'.$driver;
+		$baseclass = 'CI_'.$base;
+		$content = '<?php class '.$baseclass.' extends CI_Driver {  }';
+		$this->ci_vfs_create($base, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers'));
+
+		// Create driver file
+		$class = $this->subclass.$base;
+		$content = '<?php class '.$class.' extends '.$baseclass.' {  }';
+		$this->ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers'));
+
+		// Make valid list
+		$this->lib->driver_list($driver);
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf($baseclass, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Create driver extension without base
+		$driver = 'baseless';
+		$base = $this->name.'_'.$driver;
+		$class = $this->subclass.$base;
+		$content = '<?php class '.$class.' extends CI_Driver {  }';
+		$this->ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers'));
+		$this->lib->driver_list($driver);
+
+		// Do we get an error when base class isn't found?
+		$this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested class: CI_'.$base);
+		$this->lib->load_driver($driver);
+	}
+
+	/**
+	 * Test decorating driver with parent attributes
+	 */
+	public function test_decorate()
+	{
+		// Create parent with a method and property to access
+		$pclass = 'Test_Parent';
+		$prop = 'parent_prop';
+		$value = 'initial';
+		$method = 'parent_func';
+		$return = 'func return';
+		$code = 'class '.$pclass.' { public $'.$prop.' = \''.$value.'\'; '.
+			'public function '.$method.'() { return \''.$return.'\'; } }';
+		eval($code);
+		$parent = new $pclass();
+
+		// Create child driver to decorate
+		$class = 'Test_Driver';
+		eval('class '.$class.' extends CI_Driver {  }');
+		$child = new $class();
+
+		// Decorate child
+		$child->decorate($parent);
+
+		// Do we get the initial parent property value?
+		$this->assertEquals($value, $child->$prop);
+
+		// Can we change the parent property?
+		$newval = 'changed';
+		$child->$prop = $newval;
+		$this->assertEquals($newval, $parent->$prop);
+
+		// Do we get back the updated value?
+		$this->assertEquals($newval, $child->$prop);
+
+		// Can we call the parent method?
+		$this->assertEquals($return, $child->$method());
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/libraries/Encrypt_test.php b/tests/codeigniter/libraries/Encrypt_test.php
index 153a25e..a08db8e 100644
--- a/tests/codeigniter/libraries/Encrypt_test.php
+++ b/tests/codeigniter/libraries/Encrypt_test.php
@@ -4,14 +4,12 @@
 
 	public function set_up()
 	{
-		$obj = new stdClass;
-		$obj->encrypt = new Mock_Libraries_Encrypt();
-
-		$this->ci_instance($obj);
-		$this->encrypt = $obj->encrypt;
+		$this->encrypt = new Mock_Libraries_Encrypt();
+		$this->ci_instance_var('encrypt', $this->encrypt);
 
 		$this->ci_set_config('encryption_key', "Encryptin'glike@boss!");
 		$this->msg = 'My secret message';
+		$this->mcrypt = extension_loaded('mcrypt');
 	}
 
 	// --------------------------------------------------------------------
@@ -42,14 +40,25 @@
 
 	public function test_default_cipher()
 	{
+		if ( ! $this->mcrypt)
+		{
+			$this->markTestSkipped('MCrypt not available');
+			return;
+		}
+
 		$this->assertEquals('rijndael-256', $this->encrypt->get_cipher());
 	}
 
 	// --------------------------------------------------------------------
 
-
 	public function test_set_cipher()
 	{
+		if ( ! $this->mcrypt)
+		{
+			$this->markTestSkipped('MCrypt not available');
+			return;
+		}
+
 		$this->encrypt->set_cipher(MCRYPT_BLOWFISH);
 		$this->assertEquals('blowfish', $this->encrypt->get_cipher());
 	}
@@ -58,6 +67,12 @@
 
 	public function test_default_mode()
 	{
+		if ( ! $this->mcrypt)
+		{
+			$this->markTestSkipped('MCrypt not available');
+			return;
+		}
+
 		$this->assertEquals('cbc', $this->encrypt->get_mode());
 	}
 
@@ -65,6 +80,12 @@
 
 	public function test_set_mode()
 	{
+		if ( ! $this->mcrypt)
+		{
+			$this->markTestSkipped('MCrypt not available');
+			return;
+		}
+
 		$this->encrypt->set_mode(MCRYPT_MODE_CFB);
 		$this->assertEquals('cfb', $this->encrypt->get_mode());
 	}
diff --git a/tests/codeigniter/libraries/Parser_test.php b/tests/codeigniter/libraries/Parser_test.php
index b68f44a..394c226 100644
--- a/tests/codeigniter/libraries/Parser_test.php
+++ b/tests/codeigniter/libraries/Parser_test.php
@@ -4,12 +4,8 @@
 
 	public function set_up()
 	{
-		$obj = new stdClass;
-		$obj->parser = new Mock_Libraries_Parser();
-
-		$this->ci_instance($obj);
-
-		$this->parser = $obj->parser;
+		$this->parser = new Mock_Libraries_Parser();
+		$this->ci_instance_var('parser', $this->parser);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/tests/codeigniter/libraries/Session_test.php b/tests/codeigniter/libraries/Session_test.php
new file mode 100644
index 0000000..6edda99
--- /dev/null
+++ b/tests/codeigniter/libraries/Session_test.php
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * Session driver library unit test
+ */
+class Session_test extends CI_TestCase {
+
+	protected $settings = array(
+		'use_cookies' => 0,
+		'use_only_cookies' => 0,
+		'cache_limiter' => false
+	);
+	protected $setting_vals = array();
+	protected $cookie_vals;
+	protected $session;
+
+	/**
+	 * Set up test framework
+	 */
+	public function set_up()
+	{
+		// Override settings
+		foreach ($this->settings as $name => $value) {
+			$this->setting_vals[$name] = ini_get('session.'.$name);
+			ini_set('session.'.$name, $value);
+		}
+
+		// Start with clean environment
+		$this->cookie_vals = $_COOKIE;
+		$_COOKIE = array();
+
+		// Set subclass prefix to match our mock
+		$this->ci_set_config('subclass_prefix', 'Mock_Libraries_');
+
+		// Establish necessary support classes
+		$ci = $this->ci_instance();
+		$ldr = $this->ci_core_class('load');
+		$ci->load = new $ldr();
+		$ci->input = new Mock_Core_Input(NULL, NULL);
+
+		// Make sure string helper is available
+		$this->ci_vfs_clone('system/helpers/string_helper.php');
+
+		// Attach session instance locally
+		$config = array(
+			'sess_encrypt_cookie' => FALSE,
+			'sess_use_database' => FALSE,
+			'sess_table_name' => '',
+			'sess_expiration' => 7200,
+			'sess_expire_on_close' => FALSE,
+			'sess_match_ip' => FALSE,
+			'sess_match_useragent' => TRUE,
+			'sess_cookie_name' => 'ci_session',
+			'cookie_path' => '',
+			'cookie_domain' => '',
+			'cookie_secure' => FALSE,
+			'cookie_httponly' => FALSE,
+			'sess_time_to_update' => 300,
+			'time_reference' => 'local',
+			'cookie_prefix' => '',
+			'encryption_key' => 'foobar'
+		);
+		$this->session = new Mock_Libraries_Session($config);
+	}
+
+	/**
+	 * Tear down test framework
+	 */
+	public function tear_down()
+	{
+		// Restore environment
+		if (session_id()) session_destroy();
+		$_SESSION = array();
+		$_COOKIE = $this->cookie_vals;
+
+		// Restore settings
+		foreach ($this->settings as $name => $value) {
+			ini_set('session.'.$name, $this->setting_vals[$name]);
+		}
+	}
+
+	/**
+	 * Test set_userdata() function
+	 */
+	public function test_set_userdata()
+	{
+		// Set userdata values for each driver
+		$key1 = 'test1';
+		$ckey2 = 'test2';
+		$nkey2 = 'test3';
+		$cmsg1 = 'Some test data';
+		$cmsg2 = 42;
+		$nmsg1 = 'Other test data';
+		$nmsg2 = true;
+		$this->session->cookie->set_userdata($key1, $cmsg1);
+		$this->session->set_userdata($ckey2, $cmsg2);
+		$this->session->native->set_userdata($key1, $nmsg1);
+		$this->session->set_userdata($nkey2, $nmsg2);
+
+		// Verify independent messages
+		$this->assertEquals($cmsg1, $this->session->cookie->userdata($key1));
+		$this->assertEquals($nmsg1, $this->session->native->userdata($key1));
+
+		// Verify pre-selected driver sets
+		$this->assertEquals($cmsg2, $this->session->cookie->userdata($ckey2));
+		$this->assertEquals($nmsg2, $this->session->native->userdata($nkey2));
+
+		// Verify no crossover
+		$this->assertNull($this->session->cookie->userdata($nkey2));
+		$this->assertNull($this->session->native->userdata($ckey2));
+	}
+
+	/**
+	 * Test the has_userdata() function
+	 */
+	public function test_has_userdata()
+	{
+		// Set a userdata value for each driver
+		$key = 'hastest';
+		$cmsg = 'My test data';
+		$this->session->cookie->set_userdata($key, $cmsg);
+		$nmsg = 'Your test data';
+		$this->session->native->set_userdata($key, $nmsg);
+
+		// Verify values exist
+		$this->assertTrue($this->session->cookie->has_userdata($key));
+		$this->assertTrue($this->session->native->has_userdata($key));
+
+		// Verify non-existent values
+		$nokey = 'hasnot';
+		$this->assertFalse($this->session->cookie->has_userdata($nokey));
+		$this->assertFalse($this->session->native->has_userdata($nokey));
+	}
+
+	/**
+	 * Test the all_userdata() function
+	 */
+	public function test_all_userdata()
+	{
+		// Set a specific series of data for each driver
+		$cdata = array(
+			'one' => 'first',
+			'two' => 'second',
+			'three' => 'third',
+			'foo' => 'bar',
+			'bar' => 'baz'
+		);
+		$ndata = array(
+			'one' => 'gold',
+			'two' => 'silver',
+			'three' => 'bronze',
+			'foo' => 'baz',
+			'bar' => 'foo'
+		);
+		$this->session->cookie->set_userdata($cdata);
+		$this->session->native->set_userdata($ndata);
+
+		// Make sure all values are present
+		$call = $this->session->cookie->all_userdata();
+		foreach ($cdata as $key => $value) {
+			$this->assertEquals($value, $call[$key]);
+		}
+		$nall = $this->session->native->all_userdata();
+		foreach ($ndata as $key => $value) {
+			$this->assertEquals($value, $nall[$key]);
+		}
+	}
+
+	/**
+	 * Test the unset_userdata() function
+	 */
+	public function test_unset_userdata()
+	{
+		// Set a userdata message for each driver
+		$key = 'untest';
+		$cmsg = 'Other test data';
+		$this->session->cookie->set_userdata($key, $cmsg);
+		$nmsg = 'Sundry test data';
+		$this->session->native->set_userdata($key, $nmsg);
+
+		// Verify independent messages
+		$this->assertEquals($this->session->cookie->userdata($key), $cmsg);
+		$this->assertEquals($this->session->native->userdata($key), $nmsg);
+
+		// Unset them and verify absence
+		$this->session->cookie->unset_userdata($key);
+		$this->session->native->unset_userdata($key);
+		$this->assertNull($this->session->cookie->userdata($key));
+		$this->assertNull($this->session->native->userdata($key));
+	}
+
+	/**
+	 * Test the flashdata() functions
+	 */
+	public function test_flashdata()
+	{
+		// Set flashdata message for each driver
+		$key = 'fltest';
+		$cmsg = 'Some flash data';
+		$this->session->cookie->set_flashdata($key, $cmsg);
+		$nmsg = 'Other flash data';
+		$this->session->native->set_flashdata($key, $nmsg);
+
+		// Simulate page reload
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+
+		// Verify independent messages
+		$this->assertEquals($cmsg, $this->session->cookie->flashdata($key));
+		$this->assertEquals($nmsg, $this->session->native->flashdata($key));
+
+		// Simulate next page reload
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+
+		// Verify absence of messages
+		$this->assertNull($this->session->cookie->flashdata($key));
+		$this->assertNull($this->session->native->flashdata($key));
+	}
+
+	/**
+	 * Test the keep_flashdata() function
+	 */
+	public function test_keep_flashdata()
+	{
+		// Set flashdata message for each driver
+		$key = 'kfltest';
+		$cmsg = 'My flash data';
+		$this->session->cookie->set_flashdata($key, $cmsg);
+		$nmsg = 'Your flash data';
+		$this->session->native->set_flashdata($key, $nmsg);
+
+		// Simulate page reload and verify independent messages
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cmsg, $this->session->cookie->flashdata($key));
+		$this->assertEquals($nmsg, $this->session->native->flashdata($key));
+
+		// Keep messages
+		$this->session->cookie->keep_flashdata($key);
+		$this->session->native->keep_flashdata($key);
+
+		// Simulate next page reload and verify message persistence
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cmsg, $this->session->cookie->flashdata($key));
+		$this->assertEquals($nmsg, $this->session->native->flashdata($key));
+
+		// Simulate next page reload and verify absence of messages
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertNull($this->session->cookie->flashdata($key));
+		$this->assertNull($this->session->native->flashdata($key));
+	}
+
+	public function test_keep_flashdata_with_array()
+	{
+		// Set flashdata array for each driver
+		$cdata = array(
+			'one' => 'first',
+			'two' => 'second',
+			'three' => 'third',
+			'foo' => 'bar',
+			'bar' => 'baz'
+		);
+		$ndata = array(
+			'one' => 'gold',
+			'two' => 'silver',
+			'three' => 'bronze',
+			'foo' => 'baz',
+			'bar' => 'foo'
+		);
+		$kdata = array(
+			'one',
+			'two',
+			'three',
+			'foo',
+			'bar'
+		);
+		$this->session->cookie->set_flashdata($cdata);
+		$this->session->native->set_flashdata($ndata);
+
+		// Simulate page reload and verify independent messages
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cdata, $this->session->cookie->all_flashdata());
+		$this->assertEquals($ndata, $this->session->native->all_flashdata());
+
+		// Keep messages
+		$this->session->cookie->keep_flashdata($kdata);
+		$this->session->native->keep_flashdata($kdata);
+
+		// Simulate next page reload and verify message persistence
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cdata, $this->session->cookie->all_flashdata());
+		$this->assertEquals($ndata, $this->session->native->all_flashdata());
+
+		// Simulate next page reload and verify absence of messages
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEmpty($this->session->cookie->all_flashdata());
+		$this->assertEmpty($this->session->native->all_flashdata());
+	}
+
+	/**
+	 * Test the all_flashdata() function
+	 */
+	public function test_all_flashdata()
+	{
+		// Set a specific series of data for each driver
+		$cdata = array(
+			'one' => 'first',
+			'two' => 'second',
+			'three' => 'third',
+			'foo' => 'bar',
+			'bar' => 'baz'
+		);
+		$ndata = array(
+			'one' => 'gold',
+			'two' => 'silver',
+			'three' => 'bronze',
+			'foo' => 'baz',
+			'bar' => 'foo'
+		);
+		$this->session->cookie->set_flashdata($cdata);
+		$this->session->native->set_flashdata($ndata);
+
+		// Simulate page reload and make sure all values are present
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cdata, $this->session->cookie->all_flashdata());
+		$this->assertEquals($ndata, $this->session->native->all_flashdata());
+	}
+
+	/**
+	 * Test the tempdata() functions
+	 */
+	public function test_set_tempdata()
+	{
+		// Set tempdata message for each driver - 1 second timeout
+		$key = 'tmptest';
+		$cmsg = 'Some temp data';
+		$this->session->cookie->set_tempdata($key, $cmsg, 1);
+		$nmsg = 'Other temp data';
+		$this->session->native->set_tempdata($key, $nmsg, 1);
+
+		// Simulate page reload and verify independent messages
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertEquals($cmsg, $this->session->cookie->tempdata($key));
+		$this->assertEquals($nmsg, $this->session->native->tempdata($key));
+
+		// Wait 2 seconds, simulate page reload and verify message absence
+		sleep(2);
+		$this->session->cookie->reload();
+		$this->session->native->reload();
+		$this->assertNull($this->session->cookie->tempdata($key));
+		$this->assertNull($this->session->native->tempdata($key));
+	}
+
+	/**
+	 * Test the unset_tempdata() function
+	 */
+	public function test_unset_tempdata()
+	{
+		// Set tempdata message for each driver - 1 second timeout
+		$key = 'utmptest';
+		$cmsg = 'My temp data';
+		$this->session->cookie->set_tempdata($key, $cmsg, 1);
+		$nmsg = 'Your temp data';
+		$this->session->native->set_tempdata($key, $nmsg, 1);
+
+		// Verify independent messages
+		$this->assertEquals($cmsg, $this->session->cookie->tempdata($key));
+		$this->assertEquals($nmsg, $this->session->native->tempdata($key));
+
+		// Unset data and verify message absence
+		$this->session->cookie->unset_tempdata($key);
+		$this->session->native->unset_tempdata($key);
+		$this->assertNull($this->session->cookie->tempdata($key));
+		$this->assertNull($this->session->native->tempdata($key));
+	}
+
+	/**
+	 * Test the sess_regenerate() function
+	 */
+	public function test_sess_regenerate()
+	{
+		// Get current session id, regenerate, and compare
+		// Cookie driver
+		$oldid = $this->session->cookie->userdata('session_id');
+		$this->session->cookie->sess_regenerate();
+		$newid = $this->session->cookie->userdata('session_id');
+		$this->assertNotEquals($oldid, $newid);
+
+		// Native driver - bug #55267 (https://bugs.php.net/bug.php?id=55267) prevents testing this
+		// $oldid = session_id();
+		// $this->session->native->sess_regenerate();
+		// $oldid = session_id();
+		// $this->assertNotEquals($oldid, $newid);
+	}
+
+	/**
+	 * Test the sess_destroy() function
+	 */
+	public function test_sess_destroy()
+	{
+		// Set a userdata message, destroy session, and verify absence
+		$key = 'dsttest';
+		$msg = 'More test data';
+
+		// Cookie driver
+		$this->session->cookie->set_userdata($key, $msg);
+		$this->assertEquals($msg, $this->session->cookie->userdata($key));
+		$this->session->cookie->sess_destroy();
+		$this->assertNull($this->session->cookie->userdata($key));
+
+		// Native driver
+		$this->session->native->set_userdata($key, $msg);
+		$this->assertEquals($msg, $this->session->native->userdata($key));
+		$this->session->native->sess_destroy();
+		$this->assertNull($this->session->native->userdata($key));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/libraries/Table_test.php b/tests/codeigniter/libraries/Table_test.php
index edfc83d..ce04b6a 100644
--- a/tests/codeigniter/libraries/Table_test.php
+++ b/tests/codeigniter/libraries/Table_test.php
@@ -4,12 +4,8 @@
 
 	public function set_up()
 	{
-		$obj = new stdClass;
-		$obj->table = new Mock_Libraries_Table();
-
-		$this->ci_instance($obj);
-
-		$this->table = $obj->table;
+		$this->table = new Mock_Libraries_Table();
+		$this->ci_instance_var('table', $this->table);
 	}
 
 	// Setter Methods
diff --git a/tests/codeigniter/libraries/Typography_test.php b/tests/codeigniter/libraries/Typography_test.php
index eb6dacb..5dba062 100644
--- a/tests/codeigniter/libraries/Typography_test.php
+++ b/tests/codeigniter/libraries/Typography_test.php
@@ -4,12 +4,8 @@
 
 	public function set_up()
 	{
-		$obj = new stdClass;
-		$obj->type = new Mock_Libraries_Typography();
-
-		$this->ci_instance($obj);
-
-		$this->type = $obj->type;
+		$this->type = new Mock_Libraries_Typography();
+		$this->ci_instance('type', $this->type);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php
new file mode 100644
index 0000000..1bd8f14
--- /dev/null
+++ b/tests/codeigniter/libraries/Upload_test.php
@@ -0,0 +1,274 @@
+<?php
+
+class Upload_test extends CI_TestCase {
+
+	function set_up()
+	{
+		$ci = $this->ci_instance();
+		$ci->upload = new Mock_Libraries_Upload();
+		$ci->security = new Mock_Core_Security();
+		$ci->lang = $this->getMock('CI_Lang', array('load', 'line'));
+		$ci->lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
+		$this->upload = $ci->upload;
+	}
+
+	function test_do_upload()
+	{
+		$this->markTestSkipped('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream');
+	}
+
+	function test_data()
+	{
+		$data = array(
+				'file_name'		=> 'hello.txt',
+				'file_type'		=> 'text/plain',
+				'file_path'		=> '/tmp/',
+				'full_path'		=> '/tmp/hello.txt',
+				'raw_name'		=> 'hello',
+				'orig_name'		=> 'hello.txt',
+				'client_name'		=> '',
+				'file_ext'		=> '.txt',
+				'file_size'		=> 100,
+				'is_image'		=> FALSE,
+				'image_width'		=> '',
+				'image_height'		=> '',
+				'image_type'		=> '',
+				'image_size_str'	=> ''
+			);
+
+		$this->upload->set_upload_path('/tmp/');
+
+		foreach ($data as $k => $v)
+		{
+			$this->upload->{$k}	= $v;
+		}
+
+		$this->assertEquals('hello.txt', $this->upload->data('file_name'));
+		$this->assertEquals($data, $this->upload->data());
+	}
+
+	function test_set_upload_path()
+	{
+		$this->upload->set_upload_path('/tmp/');
+		$this->assertEquals('/tmp/', $this->upload->upload_path);
+
+		$this->upload->set_upload_path('/tmp');
+		$this->assertEquals('/tmp/', $this->upload->upload_path);
+	}
+
+	function test_set_filename()
+	{
+		$dir = 'uploads';
+		$isnew = 'helloworld.txt';
+		$exists = 'hello-world.txt';
+		$this->ci_vfs_create($exists, 'Hello world.', $this->ci_app_root, $dir);
+		$path = $this->ci_vfs_path($dir.'/', APPPATH);
+		$this->upload->file_ext = '.txt';
+
+		$this->assertEquals($isnew, $this->upload->set_filename($path, $isnew));
+		$this->assertEquals('hello-world1.txt', $this->upload->set_filename($path, $exists));
+	}
+
+	function test_set_max_filesize()
+	{
+		$this->upload->set_max_filesize(100);
+		$this->assertEquals(100, $this->upload->max_size);
+	}
+
+	function test_set_max_filename()
+	{
+		$this->upload->set_max_filename(100);
+		$this->assertEquals(100, $this->upload->max_filename);
+	}
+
+	function test_set_max_width()
+	{
+		$this->upload->set_max_width(100);
+		$this->assertEquals(100, $this->upload->max_width);
+	}
+
+	function test_set_max_height()
+	{
+		$this->upload->set_max_height(100);
+		$this->assertEquals(100, $this->upload->max_height);
+	}
+
+	function test_set_allowed_types()
+	{
+		$this->upload->set_allowed_types('*');
+		$this->assertEquals('*', $this->upload->allowed_types);
+
+		$this->upload->set_allowed_types('foo|bar');
+		$this->assertEquals(array('foo', 'bar'), $this->upload->allowed_types);
+	}
+
+	function test_set_image_properties()
+	{
+		$this->upload->file_type = 'image/gif';
+		$this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif');
+
+		$props = array(
+			'image_width'	=>	170,
+			'image_height'	=>	73,
+			'image_type'	=>	'gif',
+			'image_size_str'	=>	'width="170" height="73"'
+		);
+
+		$this->upload->set_image_properties($this->upload->file_temp);
+
+		$this->assertEquals($props['image_width'], $this->upload->image_width);
+		$this->assertEquals($props['image_height'], $this->upload->image_height);
+		$this->assertEquals($props['image_type'], $this->upload->image_type);
+		$this->assertEquals($props['image_size_str'], $this->upload->image_size_str);
+	}
+
+	function test_set_xss_clean()
+	{
+		$this->upload->set_xss_clean(TRUE);
+		$this->assertTrue($this->upload->xss_clean);
+
+		$this->upload->set_xss_clean(FALSE);
+		$this->assertFalse($this->upload->xss_clean);
+	}
+
+	function test_is_image()
+	{
+		$this->upload->file_type = 'image/x-png';
+		$this->assertTrue($this->upload->is_image());
+
+		$this->upload->file_type = 'text/plain';
+		$this->assertFalse($this->upload->is_image());
+	}
+
+	function test_is_allowed_filetype()
+	{
+		$this->upload->allowed_types = array('html', 'gif');
+
+		$this->upload->file_ext = '.txt';
+		$this->upload->file_type = 'text/plain';
+		$this->assertFalse($this->upload->is_allowed_filetype(FALSE));
+		$this->assertFalse($this->upload->is_allowed_filetype(TRUE));
+
+		$this->upload->file_ext = '.html';
+		$this->upload->file_type = 'text/html';
+		$this->assertTrue($this->upload->is_allowed_filetype(FALSE));
+		$this->assertTrue($this->upload->is_allowed_filetype(TRUE));
+
+		$this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif');
+		$this->upload->file_ext = '.gif';
+		$this->upload->file_type = 'image/gif';
+		$this->assertTrue($this->upload->is_allowed_filetype());
+	}
+
+	function test_is_allowed_filesize()
+	{
+		$this->upload->max_size = 100;
+		$this->upload->file_size = 99;
+
+		$this->assertTrue($this->upload->is_allowed_filesize());
+
+		$this->upload->file_size = 101;
+		$this->assertFalse($this->upload->is_allowed_filesize());
+	}
+
+	function test_is_allowed_dimensions()
+	{
+		$this->upload->file_type = 'text/plain';
+		$this->assertTrue($this->upload->is_allowed_dimensions());
+
+		$this->upload->file_type = 'image/gif';
+		$this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif');
+
+		$this->upload->max_width = 10;
+		$this->assertFalse($this->upload->is_allowed_dimensions());
+
+		$this->upload->max_width = 170;
+		$this->upload->max_height = 10;
+		$this->assertFalse($this->upload->is_allowed_dimensions());
+
+		$this->upload->max_height = 73;
+		$this->assertTrue($this->upload->is_allowed_dimensions());
+	}
+
+	function test_validate_upload_path()
+	{
+		$this->upload->upload_path = '';
+		$this->assertFalse($this->upload->validate_upload_path());
+
+		$dir = 'uploads';
+		$this->ci_vfs_mkdir($dir);
+		$this->upload->upload_path = $this->ci_vfs_path($dir);
+		$this->assertTrue($this->upload->validate_upload_path());
+	}
+
+	function test_get_extension()
+	{
+		$this->assertEquals('.txt', $this->upload->get_extension('hello.txt'));
+		$this->assertEquals('.htaccess', $this->upload->get_extension('.htaccess'));
+		$this->assertEquals('', $this->upload->get_extension('hello'));
+	}
+
+	function test_clean_file_name()
+	{
+		$this->assertEquals('hello.txt', $this->upload->clean_file_name('hello.txt'));
+		$this->assertEquals('hello.txt', $this->upload->clean_file_name('%253chell>o.txt'));
+	}
+
+	function test_limit_filename_length()
+	{
+		$this->assertEquals('hello.txt', $this->upload->limit_filename_length('hello.txt', 10));
+		$this->assertEquals('hello.txt', $this->upload->limit_filename_length('hello-world.txt', 9));
+	}
+
+	function test_do_xss_clean()
+	{
+		$dir = 'uploads';
+		$file1 = 'file1.txt';
+		$file2 = 'file2.txt';
+		$file3 = 'file3.txt';
+		$this->ci_vfs_create($file1, 'The billy goat was waiting for them.', $this->ci_vfs_root, $dir);
+		$this->ci_vfs_create($file2, '', $this->ci_vfs_root, $dir);
+		$this->ci_vfs_create($file3, '<script type="text/javascript">alert("Boo! said the billy goat")</script>', $this->ci_vfs_root, $dir);
+
+		$this->upload->file_temp = $this->ci_vfs_path($file1, $dir);
+		$this->assertTrue($this->upload->do_xss_clean());
+
+		$this->upload->file_temp = $this->ci_vfs_path($file2, $dir);
+		$this->assertFalse($this->upload->do_xss_clean());
+
+		$this->upload->file_temp = $this->ci_vfs_path($file3, $dir);
+		$this->assertFalse($this->upload->do_xss_clean());
+
+		$this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif');
+		$this->assertTrue($this->upload->do_xss_clean());
+	}
+
+	function test_set_error()
+	{
+		$errors = array(
+			'An error!'
+		);
+
+		$another = 'Another error!';
+
+		$this->upload->set_error($errors);
+		$this->assertEquals($errors, $this->upload->error_msg);
+
+		$errors[] = $another;
+		$this->upload->set_error($another);
+		$this->assertEquals($errors, $this->upload->error_msg);
+	}
+
+	function test_display_errors()
+	{
+		$this->upload->error_msg[] = 'Error test';
+		$this->assertEquals('<p>Error test</p>', $this->upload->display_errors());
+	}
+
+	function test_mimes_types()
+	{
+		$this->assertEquals('text/plain', $this->upload->mimes_types('txt'));
+		$this->assertFalse($this->upload->mimes_types('foobar'));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/codeigniter/libraries/Useragent_test.php b/tests/codeigniter/libraries/Useragent_test.php
index 89383f8..e372655 100644
--- a/tests/codeigniter/libraries/Useragent_test.php
+++ b/tests/codeigniter/libraries/Useragent_test.php
@@ -10,12 +10,11 @@
 		// set a baseline user agent
 		$_SERVER['HTTP_USER_AGENT'] = $this->_user_agent;
 
-		$obj = new stdClass;
-		$obj->agent = new Mock_Libraries_UserAgent();
+		$this->ci_vfs_clone('application/config/user_agents.php');
 
-		$this->ci_instance($obj);
+		$this->agent = new Mock_Libraries_UserAgent();
 
-		$this->agent = $obj->agent;
+		$this->ci_instance_var('agent', $this->agent);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php
index be1c222..3d216da 100644
--- a/tests/mocks/autoloader.php
+++ b/tests/mocks/autoloader.php
@@ -16,7 +16,7 @@
 	$ci_core = array(
 		'Benchmark', 'Config', 'Controller',
 		'Exceptions', 'Hooks', 'Input',
-		'Lang', 'Loader', 'Model',
+		'Lang', 'Loader', 'Log', 'Model',
 		'Output', 'Router', 'Security',
 		'URI', 'Utf8',
 	);
@@ -25,42 +25,58 @@
 		'Calendar', 'Cart', 'Driver_Library',
 		'Email', 'Encrypt', 'Form_validation',
 		'Ftp', 'Image_lib', 'Javascript',
-		'Log', 'Migration', 'Pagination',
-		'Parser', 'Profiler', 'Session',
-		'Table', 'Trackback', 'Typography',
-		'Unit_test', 'Upload', 'User_agent',
-		'Xmlrpc', 'Zip',
+		'Migration', 'Pagination', 'Parser',
+		'Profiler', 'Table', 'Trackback',
+	   	'Typography', 'Unit_test', 'Upload',
+	   	'User_agent', 'Xmlrpc', 'Zip'
+	);
+
+	$ci_drivers = array(
+		'Session',
 	);
 
 	if (strpos($class, 'Mock_') === 0)
 	{
-		$class = str_replace(array('Mock_', '_'), array('', DIRECTORY_SEPARATOR), $class);
-		$class = strtolower($class);
+		$class = strtolower(str_replace(array('Mock_', '_'), array('', DIRECTORY_SEPARATOR), $class));
 	}
 	elseif (strpos($class, 'CI_') === 0)
 	{
-		$fragments = explode('_', $class, 2);
-		$subclass = next($fragments);
+		$subclass = substr($class, 3);
 
 		if (in_array($subclass, $ci_core))
 		{
-			$dir = BASEPATH.'core'.DIRECTORY_SEPARATOR;
+			$dir = SYSTEM_PATH.'core'.DIRECTORY_SEPARATOR;
 			$class = $subclass;
 		}
 		elseif (in_array($subclass, $ci_libraries))
 		{
-			$dir = BASEPATH.'libraries'.DIRECTORY_SEPARATOR;
+			$dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR;
 			$class = ($subclass === 'Driver_Library') ? 'Driver' : $subclass;
 		}
+		elseif (in_array($subclass, $ci_drivers))
+		{
+			$dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR.$subclass.DIRECTORY_SEPARATOR;
+			$class = $subclass;
+		}
+		elseif (in_array(($parent = strtok($subclass, '_')), $ci_drivers)) {
+			$dir = SYSTEM_PATH.'libraries'.DIRECTORY_SEPARATOR.$parent.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR;
+			$class = $subclass;
+		}
+		elseif (preg_match('/^CI_DB_(.+)_(.+)_(driver|forge|result|utility)$/', $class, $m) && count($m) === 4)
+		{
+			$driver_path = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR;
+			$dir = $driver_path.$m[1].DIRECTORY_SEPARATOR.'subdrivers'.DIRECTORY_SEPARATOR;
+			$file = $dir.$m[1].'_'.$m[2].'_'.$m[3].'.php';
+		}
 		elseif (preg_match('/^CI_DB_(.+)_(driver|forge|result|utility)$/', $class, $m) && count($m) === 3)
 		{
-			$driver_path = BASEPATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR;
+			$driver_path = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR.'drivers'.DIRECTORY_SEPARATOR;
 			$dir = $driver_path.$m[1].DIRECTORY_SEPARATOR;
 			$file = $dir.$m[1].'_'.$m[2].'.php';
 		}
 		elseif (strpos($class, 'CI_DB') === 0)
 		{
-			$dir = BASEPATH.'database'.DIRECTORY_SEPARATOR;
+			$dir = SYSTEM_PATH.'database'.DIRECTORY_SEPARATOR;
 			$file = $dir.str_replace(array('CI_DB','active_record'), array('DB', 'active_rec'), $subclass).'.php';
 		}
 		else
@@ -69,7 +85,7 @@
 		}
 	}
 
-	$file = (isset($file)) ? $file : $dir.$class.'.php';
+	$file = isset($file) ? $file : $dir.$class.'.php';
 
 	if ( ! file_exists($file))
 	{
diff --git a/tests/mocks/ci_testcase.php b/tests/mocks/ci_testcase.php
index eda9e1b..f164929 100644
--- a/tests/mocks/ci_testcase.php
+++ b/tests/mocks/ci_testcase.php
@@ -2,7 +2,9 @@
 
 class CI_TestCase extends PHPUnit_Framework_TestCase {
 
-	protected $ci_config;
+	public $ci_vfs_root;
+	public $ci_app_root;
+	public $ci_base_root;
 	protected $ci_instance;
 	protected static $ci_test_instance;
 
@@ -25,13 +27,19 @@
 	public function __construct()
 	{
 		parent::__construct();
-		$this->ci_config = array();
+		$this->ci_instance = new StdClass();
 	}
 
 	// --------------------------------------------------------------------
 
 	public function setUp()
 	{
+		// Setup VFS with base directories
+		$this->ci_vfs_root = vfsStream::setup();
+		$this->ci_app_root = vfsStream::newDirectory('application')->at($this->ci_vfs_root);
+		$this->ci_base_root = vfsStream::newDirectory('system')->at($this->ci_vfs_root);
+		$this->ci_view_root = vfsStream::newDirectory('views')->at($this->ci_app_root);
+
 		if (method_exists($this, 'set_up'))
 		{
 			$this->set_up();
@@ -57,15 +65,27 @@
 
 	// --------------------------------------------------------------------
 
-	public function ci_set_config($key, $val = '')
+	public function ci_set_config($key = '', $val = '')
 	{
+		// Add test config
+		if ( ! isset($this->ci_instance->config))
+		{
+			$this->ci_instance->config = new CI_TestConfig();
+		}
+
+		// Empty key means just do setup above
+		if ($key === '')
+		{
+			return;
+		}
+
 		if (is_array($key))
 		{
-			$this->ci_config = $key;
+			$this->ci_instance->config->config = $key;
 		}
 		else
 		{
-			$this->ci_config[$key] = $val;
+			$this->ci_instance->config->config[$key] = $val;
 		}
 	}
 
@@ -73,7 +93,7 @@
 
 	public function ci_get_config()
 	{
-		return $this->ci_config;
+		return isset($this->ci_instance->config) ? $this->ci_instance->config->config : array();
 	}
 
 	// --------------------------------------------------------------------
@@ -132,7 +152,7 @@
 
 		if ( ! class_exists('CI_'.$class_name))
 		{
-			require_once BASEPATH.'core/'.$class_name.'.php';
+			require_once SYSTEM_PATH.'core/'.$class_name.'.php';
 		}
 
 		$GLOBALS[strtoupper($global_name)] = 'CI_'.$class_name;
@@ -148,6 +168,165 @@
 		$orig = $obj;
 	}
 
+	/**
+	 * Create VFS directory
+	 *
+	 * @param	string	Directory name
+	 * @param	object	Optional root to create in
+	 * @return	object	New directory object
+	 */
+	public function ci_vfs_mkdir($name, $root = NULL)
+	{
+		// Check for root
+		if ( ! $root)
+		{
+			$root = $this->ci_vfs_root;
+		}
+
+		// Return new directory object
+		return vfsStream::newDirectory($name)->at($root);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create VFS content
+	 *
+	 * @param	string	File name
+	 * @param	string	File content
+	 * @param	object	VFS directory object
+	 * @param	mixed	Optional subdirectory path or array of subs
+	 * @return	void
+	 */
+	public function ci_vfs_create($file, $content = '', $root = NULL, $path = NULL)
+	{
+		// Check for array
+		if (is_array($file))
+		{
+			foreach ($file as $name => $content)
+			{
+				$this->ci_vfs_create($name, $content, $root, $path);
+			}
+			return;
+		}
+
+		// Assert .php extension if none given
+		if (pathinfo($file, PATHINFO_EXTENSION) == '')
+		{
+			$file .= '.php';
+		}
+
+		// Build content
+		$tree = array($file => $content);
+
+		// Check for path
+		$subs = array();
+		if ($path)
+		{
+			// Explode if not array
+			$subs = is_array($path) ? $path : explode('/', trim($path, '/'));
+		}
+
+		// Check for root
+		if ( ! $root)
+		{
+			// Use base VFS root
+			$root = $this->ci_vfs_root;
+		}
+
+		// Handle subdirectories
+		while (($dir = array_shift($subs)))
+		{
+			// See if subdir exists under current root
+			$dir_root = $root->getChild($dir);
+			if ($dir_root)
+			{
+			   	// Yes - recurse into subdir
+				$root = $dir_root;
+			}
+			else
+			{
+				// No - put subdirectory back and quit
+				array_unshift($subs, $dir);
+				break;
+			}
+		}
+
+		// Create any remaining subdirectories
+		if ($subs)
+		{
+			foreach (array_reverse($subs) as $dir)
+			{
+				// Wrap content in subdirectory for creation
+				$tree = array($dir => $tree);
+			}
+		}
+
+		// Create tree
+		vfsStream::create($tree, $root);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Clone a real file into VFS
+	 *
+	 * @param	string	Path from base directory
+	 * @return	bool	TRUE on success, otherwise FALSE
+	 */
+	public function ci_vfs_clone($path)
+	{
+		// Check for array
+		if (is_array($path))
+		{
+			foreach ($path as $file)
+			{
+				$this->ci_vfs_clone($file);
+			}
+			return;
+		}
+
+		// Get real file contents
+		$content = file_get_contents(PROJECT_BASE.$path);
+		if ($content === FALSE)
+		{
+			// Couldn't find file to clone
+			return FALSE;
+		}
+
+		$this->ci_vfs_create(basename($path), $content, NULL, dirname($path));
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Helper to get a VFS URL path
+	 *
+	 * @param	string	Path
+	 * @param	string	Optional base path
+	 * @return	string	Path URL
+	 */
+	public function ci_vfs_path($path, $base = '')
+	{
+		// Check for base path
+		if ($base)
+		{
+			// Prepend to path
+			$path = rtrim($base, '/').'/'.ltrim($path, '/');
+
+			// Is it already in URL form?
+			if (strpos($path, '://') !== FALSE)
+			{
+				// Done - return path
+				return $path;
+			}
+		}
+
+		// Trim leading slash and return URL
+		return vfsStream::url(ltrim($path, '/'));
+	}
+
 	// --------------------------------------------------------------------
 	// Internals
 	// --------------------------------------------------------------------
@@ -171,7 +350,15 @@
 
 	public function helper($name)
 	{
-		require_once(BASEPATH.'helpers/'.$name.'_helper.php');
+		require_once(SYSTEM_PATH.'helpers/'.$name.'_helper.php');
+	}
+
+	// --------------------------------------------------------------------
+
+	public function lang($name)
+	{
+		require(SYSTEM_PATH.'language/english/'.$name.'_lang.php');
+		return $lang;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/tests/mocks/ci_testconfig.php b/tests/mocks/ci_testconfig.php
new file mode 100644
index 0000000..f80adc5
--- /dev/null
+++ b/tests/mocks/ci_testconfig.php
@@ -0,0 +1,20 @@
+<?php
+
+class CI_TestConfig {
+
+	public $config = array();
+	public $_config_paths = array(APPPATH);
+	public $loaded = array();
+
+	public function item($key)
+	{
+		return isset($this->config[$key]) ? $this->config[$key] : FALSE;
+	}
+
+	public function load($file, $arg2 = FALSE, $arg3 = FALSE)
+	{
+		$this->loaded[] = $file;
+		return TRUE;
+	}
+
+}
\ No newline at end of file
diff --git a/tests/mocks/core/common.php b/tests/mocks/core/common.php
index a655ee1..24d645a 100644
--- a/tests/mocks/core/common.php
+++ b/tests/mocks/core/common.php
@@ -39,6 +39,30 @@
 	}
 }
 
+if ( ! function_exists('get_mimes'))
+{
+	/**
+	 * Returns the MIME types array from config/mimes.php
+	 *
+	 * @return	array
+	 */
+	function &get_mimes()
+	{
+		static $_mimes = array();
+
+		if (empty($_mimes))
+		{
+			$path = realpath(PROJECT_BASE.'application/config/mimes.php');
+			if (is_file($path))
+			{
+				$_mimes = include($path);
+			}
+		}
+
+		return $_mimes;
+	}
+}
+
 // --------------------------------------------------------------------
 
 if ( ! function_exists('load_class'))
@@ -124,7 +148,6 @@
 	}
 }
 
-
 // We assume a few things about our environment ...
 // --------------------------------------------------------------------
 
@@ -146,9 +169,10 @@
 
 if ( ! function_exists('is_loaded'))
 {
-	function is_loaded()
+	function &is_loaded()
 	{
-		throw new Exception('Bad Isolation: mock up environment');
+		$loaded = array();
+		return $loaded;
 	}
 }
 
diff --git a/tests/mocks/core/input.php b/tests/mocks/core/input.php
index 2a4aa49..0d18738 100644
--- a/tests/mocks/core/input.php
+++ b/tests/mocks/core/input.php
@@ -28,4 +28,14 @@
 		return parent::_fetch_from_array($array, $index, $xss_clean);
 	}
 
+	/**
+	 * Lie about being a CLI request
+	 *
+	 * We take advantage of this in libraries/Session_test
+	 */
+	public function is_cli_request()
+	{
+		return FALSE;
+	}
+
 }
\ No newline at end of file
diff --git a/tests/mocks/core/loader.php b/tests/mocks/core/loader.php
deleted file mode 100644
index 53d88d5..0000000
--- a/tests/mocks/core/loader.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-class Mock_Core_Loader extends CI_Loader {
-
-	/**
-	 * Since we use paths to load up models, views, etc, we need the ability to
-	 * mock up the file system so when core tests are run, we aren't mucking
-	 * in the application directory.  this will give finer grained control over
-	 * these tests.  So yeah, while this looks odd, I need to overwrite protected
-	 * class vars in the loader.  So here we go...
-	 *
-	 * @covers CI_Loader::__construct()
-	 */
-	public function __construct()
-	{
-		vfsStreamWrapper::register();
-		vfsStreamWrapper::setRoot(new vfsStreamDirectory('application'));
-
-		$this->models_dir 	= vfsStream::newDirectory('models')->at(vfsStreamWrapper::getRoot());
-		$this->libs_dir 	= vfsStream::newDirectory('libraries')->at(vfsStreamWrapper::getRoot());
-		$this->helpers_dir 	= vfsStream::newDirectory('helpers')->at(vfsStreamWrapper::getRoot());
-		$this->views_dir 	= vfsStream::newDirectory('views')->at(vfsStreamWrapper::getRoot());
-
-		$this->_ci_ob_level  		= ob_get_level();
-		$this->_ci_library_paths	= array(vfsStream::url('application').'/', BASEPATH);
-		$this->_ci_helper_paths 	= array(vfsStream::url('application').'/', BASEPATH);
-		$this->_ci_model_paths 		= array(vfsStream::url('application').'/');
-		$this->_ci_view_paths 		= array(vfsStream::url('application').'/views/' => TRUE);
-	}
-
-}
\ No newline at end of file
diff --git a/tests/mocks/database/config/mysqli.php b/tests/mocks/database/config/mysqli.php
new file mode 100644
index 0000000..5dd08ab
--- /dev/null
+++ b/tests/mocks/database/config/mysqli.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+
+	// Typical Database configuration
+	'mysqli' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'travis',
+		'password' => '',
+		'database' => 'ci_test',
+		'dbdriver' => 'mysqli'
+	),
+
+	// Database configuration with failover
+	'mysqli_failover' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'not_travis',
+		'password' => 'wrong password',
+		'database' => 'not_ci_test',
+		'dbdriver' => 'mysqli',
+		'failover' => array(
+			array(
+				'dsn' => '',
+				'hostname' => 'localhost',
+				'username' => 'travis',
+				'password' => '',
+				'database' => 'ci_test',
+				'dbdriver' => 'mysqli',
+			)
+		)
+	)
+);
\ No newline at end of file
diff --git a/tests/mocks/database/config/pdo/mysql.php b/tests/mocks/database/config/pdo/mysql.php
index fefe0d6..96608f7 100644
--- a/tests/mocks/database/config/pdo/mysql.php
+++ b/tests/mocks/database/config/pdo/mysql.php
@@ -4,13 +4,13 @@
 
 	// Typical Database configuration
 	'pdo/mysql' => array(
-		'dsn' => '',
+		'dsn' => 'mysql:host=localhost;dbname=ci_test',
 		'hostname' => 'localhost',
 		'username' => 'travis',
 		'password' => '',
 		'database' => 'ci_test',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'mysql'
+		'subdriver' => 'mysql'
 	),
 
 	// Database configuration with failover
@@ -21,16 +21,16 @@
 		'password' => 'wrong password',
 		'database' => 'not_ci_test',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'mysql',
+		'subdriver' => 'mysql',
 		'failover' => array(
 			array(
-				'dsn' => '',
+				'dsn' => 'mysql:host=localhost;dbname=ci_test',
 				'hostname' => 'localhost',
 				'username' => 'travis',
 				'password' => '',
 				'database' => 'ci_test',
 				'dbdriver' => 'pdo',
-				'pdodriver' => 'mysql'
+				'subdriver' => 'mysql'
 			)
 		)
 	)
diff --git a/tests/mocks/database/config/pdo/pgsql.php b/tests/mocks/database/config/pdo/pgsql.php
index ddd638c..e55e3ea 100644
--- a/tests/mocks/database/config/pdo/pgsql.php
+++ b/tests/mocks/database/config/pdo/pgsql.php
@@ -10,7 +10,7 @@
 		'password' => '',
 		'database' => 'ci_test',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'pgsql'
+		'subdriver' => 'pgsql'
 	),
 
 	// Database configuration with failover
@@ -21,7 +21,7 @@
 		'password' => 'wrong password',
 		'database' => 'not_ci_test',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'pgsql',
+		'subdriver' => 'pgsql',
 		'failover' => array(
 			array(
 				'dsn' => 'pgsql:host=localhost;port=5432;dbname=ci_test;',
@@ -30,7 +30,7 @@
 				'password' => '',
 				'database' => 'ci_test',
 				'dbdriver' => 'pdo',
-				'pdodriver' => 'pgsql'
+				'subdriver' => 'pgsql'
 			)
 		)
 	)
diff --git a/tests/mocks/database/config/pdo/sqlite.php b/tests/mocks/database/config/pdo/sqlite.php
index 3646184..1bf56b3 100644
--- a/tests/mocks/database/config/pdo/sqlite.php
+++ b/tests/mocks/database/config/pdo/sqlite.php
@@ -10,7 +10,7 @@
 		'password' => 'sqlite',
 		'database' => 'sqlite',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'sqlite'
+		'subdriver' => 'sqlite'
 	),
 
 	// Database configuration with failover
@@ -21,7 +21,7 @@
 		'password' => 'sqlite',
 		'database' => 'sqlite',
 		'dbdriver' => 'pdo',
-		'pdodriver' => 'sqlite',
+		'subdriver' => 'sqlite',
 		'failover' => array(
 			array(
 				'dsn' => 'sqlite:/'.realpath(__DIR__.'/../..').'/ci_test.sqlite',
@@ -30,7 +30,7 @@
 				'password' => 'sqlite',
 				'database' => 'sqlite',
 				'dbdriver' => 'pdo',
-				'pdodriver' => 'sqlite'
+				'subdriver' => 'sqlite'
 			)
 		)
 	)
diff --git a/tests/mocks/database/db.php b/tests/mocks/database/db.php
index 30504bb..7e0030e 100644
--- a/tests/mocks/database/db.php
+++ b/tests/mocks/database/db.php
@@ -8,6 +8,16 @@
 	private $config = array();
 
 	/**
+	 * @var string DB driver name
+	 */
+	private static $dbdriver = '';
+
+	/**
+	 * @var string DB sub-driver name
+	 */
+	private static $subdriver = '';
+
+	/**
 	 * Prepare database configuration skeleton
 	 *
 	 * @param  array 	DB configuration to set
@@ -31,6 +41,12 @@
 			throw new InvalidArgumentException('Group '.$group.' not exists');
 		}
 
+		self::$dbdriver = $this->config[$group]['dbdriver'];
+		if (isset($this->config[$group]['subdriver']))
+		{
+			self::$subdriver = $this->config[$group]['subdriver'];
+		}
+
 		$params = array(
 			'dbprefix' => '',
 			'pconnect' => FALSE,
@@ -45,17 +61,17 @@
 		);
 
 		$config = array_merge($this->config[$group], $params);
-		$dsnstring = ( ! empty($config['dsn'])) ? $config['dsn'] : FALSE;
-		$pdodriver = ( ! empty($config['pdodriver'])) ? $config['pdodriver'] : FALSE;
-		$failover = ( ! empty($config['failover'])) ? $config['failover'] : FALSE;
+		$dsnstring = empty($config['dsn']) ? FALSE : $config['dsn'];
+		$subdriver = empty($config['subdriver']) ? FALSE: $config['subdriver'];
+		$failover = empty($config['failover']) ? FALSE : $config['failover'];
 
 		$dsn = $config['dbdriver'].'://'.$config['username'].':'.$config['password']
-			       .'@'.$config['hostname'].'/'.$config['database'];
+					.'@'.$config['hostname'].'/'.$config['database'];
 
 		// Build the parameter
 		$other_params = array_slice($config, 6);
 		if ($dsnstring) $other_params['dsn'] = $dsnstring;
-		if ($pdodriver) $other_params['pdodriver'] = $pdodriver;
+		if ($subdriver) $other_params['subdriver'] = $subdriver;
 		if ($failover) $other_params['failover'] = $failover;
 
 		return $dsn.'?'.http_build_query($other_params);
@@ -83,7 +99,32 @@
 	 */
 	public static function DB($group, $query_builder = FALSE)
 	{
-		include_once(BASEPATH.'database/DB.php');
+		// Create dummy driver and builder files to "load" - the mocks have
+		// already triggered autoloading of the real files
+		$case = CI_TestCase::instance();
+		$driver = self::$dbdriver;
+		$subdriver = self::$subdriver;
+		$case->ci_vfs_create(array(
+			'DB_driver.php' => '',
+			'DB_forge.php' => '',
+			'DB_query_builder.php' => ''
+		), '', $case->ci_base_root, 'database');
+		if (file_exists(SYSTEM_PATH.'database/drivers/'.$driver.'/'.$driver.'_driver.php'))
+		{
+			$case->ci_vfs_create(array(
+				$driver.'_driver.php' => '',
+				$driver.'_forge.php' => ''
+			), '', $case->ci_base_root, 'database/drivers/'.$driver);
+		}
+		if ($subdriver)
+		{
+			$case->ci_vfs_create(array(
+				$driver.'_'.$subdriver.'_driver.php' => '',
+				$driver.'_'.$subdriver.'_forge.php' => ''
+			), '', $case->ci_base_root, 'database/drivers/'.$driver.'/subdrivers');
+		}
+
+		include_once(SYSTEM_PATH.'database/DB.php');
 
 		try
 		{
diff --git a/tests/mocks/database/db/driver.php b/tests/mocks/database/db/driver.php
index 65ac2c4..2cf54b9 100644
--- a/tests/mocks/database/db/driver.php
+++ b/tests/mocks/database/db/driver.php
@@ -34,6 +34,7 @@
 
 		return call_user_func_array(array($this->ci_db_driver, $method), $arguments);
 	}
+
 }
 
 class CI_DB extends Mock_Database_DB_QueryBuilder {}
\ No newline at end of file
diff --git a/tests/mocks/database/drivers/mysqli.php b/tests/mocks/database/drivers/mysqli.php
new file mode 100644
index 0000000..73c35b6
--- /dev/null
+++ b/tests/mocks/database/drivers/mysqli.php
@@ -0,0 +1,17 @@
+<?php
+
+class Mock_Database_Drivers_Mysqli extends Mock_Database_DB_Driver {
+
+	/**
+	 * Instantiate the database driver
+	 *
+	 * @param	string	DB Driver class name
+	 * @param	array	DB configuration to set
+	 * @return	void
+	 */
+	public function __construct($config = array())
+	{
+		parent::__construct('CI_DB_mysqli_driver', $config);
+	}
+
+}
\ No newline at end of file
diff --git a/tests/mocks/database/schema/skeleton.php b/tests/mocks/database/schema/skeleton.php
index 18e1ddd..e5c5360 100644
--- a/tests/mocks/database/schema/skeleton.php
+++ b/tests/mocks/database/schema/skeleton.php
@@ -30,7 +30,7 @@
 
 			CI_TestCase::instance()->ci_instance_var('db', $db);
 
-			$loader = new Mock_Core_Loader();
+			$loader = new CI_Loader();
 			$loader->dbforge();
 			$forge = CI_TestCase::instance()->ci_instance_var('dbforge');
 
@@ -69,7 +69,7 @@
 			)
 		));
 		static::$forge->add_key('id', TRUE);
-		static::$forge->create_table('user', (strpos(static::$driver, 'pgsql') === FALSE));
+		static::$forge->create_table('user', TRUE);
 
 		// Job Table
 		static::$forge->add_field(array(
@@ -86,7 +86,7 @@
 			)
 		));
 		static::$forge->add_key('id', TRUE);
-		static::$forge->create_table('job', (strpos(static::$driver, 'pgsql') === FALSE));
+		static::$forge->create_table('job', TRUE);
 
 		// Misc Table
 		static::$forge->add_field(array(
@@ -103,7 +103,7 @@
 			)
 		));
 		static::$forge->add_key('id', TRUE);
-		static::$forge->create_table('misc', (strpos(static::$driver, 'pgsql') === FALSE));
+		static::$forge->create_table('misc', TRUE);
 	}
 
 	/**
@@ -129,7 +129,8 @@
 			),
 			'misc' => array(
 				array('id' => 1, 'key' => '\\xxxfoo456', 'value' => 'Entry with \\xxx'),
-				array('id' => 2, 'key' => '\\%foo456', 'value' => 'Entry with \\%')
+				array('id' => 2, 'key' => '\\%foo456', 'value' => 'Entry with \\%'),
+				array('id' => 3, 'key' => 'spaces and tabs', 'value' => ' One  two   three	tab')
 			)
 		);
 
diff --git a/tests/mocks/libraries/driver.php b/tests/mocks/libraries/driver.php
new file mode 100644
index 0000000..6331943
--- /dev/null
+++ b/tests/mocks/libraries/driver.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Mock library to subclass Driver for testing
+ */
+class Mock_Libraries_Driver extends CI_Driver_Library {
+	/**
+	 * Set valid drivers list
+	 */
+	public function driver_list($drivers = NULL)
+	{
+		if (empty($drivers))
+		{
+			return $this->valid_drivers;
+		}
+
+		$this->valid_drivers = (array) $drivers;
+	}
+
+	/**
+	 * Get library name
+	 */
+	public function get_name()
+	{
+		return $this->lib_name;
+	}
+}
\ No newline at end of file
diff --git a/tests/mocks/libraries/session.php b/tests/mocks/libraries/session.php
new file mode 100644
index 0000000..562033b
--- /dev/null
+++ b/tests/mocks/libraries/session.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Mock library to add testing features to Session driver library
+ */
+class Mock_Libraries_Session extends CI_Session {
+	/**
+	 * Simulate new page load
+	 */
+	public function reload()
+	{
+		$this->_flashdata_sweep();
+		$this->_flashdata_mark();
+		$this->_tempdata_sweep();
+	}
+}
+
+/**
+ * Mock cookie driver to overload cookie setting
+ */
+class Mock_Libraries_Session_cookie extends CI_Session_cookie {
+	/**
+	 * Overload _setcookie to manage $_COOKIE values, since actual cookies can't be set in unit testing
+	 */
+	protected function _setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = FALSE, $httponly = FALSE)
+	{
+		if (empty($value) OR $expire <= time())
+		{
+			unset($_COOKIE[$name]);
+		}
+		else
+		{
+			$_COOKIE[$name] = $value;
+		}
+	}
+}
\ No newline at end of file
diff --git a/tests/mocks/libraries/upload.php b/tests/mocks/libraries/upload.php
new file mode 100644
index 0000000..988723e
--- /dev/null
+++ b/tests/mocks/libraries/upload.php
@@ -0,0 +1,3 @@
+<?php
+
+class Mock_Libraries_Upload extends CI_Upload {}
\ No newline at end of file
diff --git a/tests/mocks/uploads/ci_logo.gif b/tests/mocks/uploads/ci_logo.gif
new file mode 100644
index 0000000..073ec14
--- /dev/null
+++ b/tests/mocks/uploads/ci_logo.gif
Binary files differ
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 56cb884..96c3af9 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit 
+<phpunit
 	bootstrap="./Bootstrap.php"
 	colors="true"
 	convertNoticesToExceptions="true"
@@ -16,10 +16,11 @@
 			<directory suffix="test.php">./codeigniter/libraries</directory>
 		</testsuite>
 	</testsuites>
-	<filters>
+	<filter>
 		<blacklist>
 			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
 			<directory suffix=".php">PHP_LIBDIR</directory>
+			<directory suffix=".php">../vendor</directory>
 		</blacklist>
-	</filters>
+	</filter>
 </phpunit>
\ No newline at end of file
diff --git a/tests/travis/mysql.phpunit.xml b/tests/travis/mysql.phpunit.xml
index 38c8eba..06d4a01 100644
--- a/tests/travis/mysql.phpunit.xml
+++ b/tests/travis/mysql.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../system</directory>
         </whitelist>
 	</filter>
diff --git a/tests/travis/mysqli.phpunit.xml b/tests/travis/mysqli.phpunit.xml
new file mode 100644
index 0000000..1364f8b
--- /dev/null
+++ b/tests/travis/mysqli.phpunit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit 
+	bootstrap="../Bootstrap.php"
+	colors="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	stopOnError="false"
+	stopOnFailure="false"
+	stopOnIncomplete="false"
+	stopOnSkipped="false">
+	<php>
+        <const name="DB_DRIVER" value="mysqli"/>
+    </php>
+	<testsuites>
+		<testsuite name="CodeIgniter Core Test Suite">
+			<directory suffix="test.php">../codeigniter</directory>
+		</testsuite>
+	</testsuites>
+	<filter>
+        <whitelist addUncoveredFilesFromWhitelist="false">
+            <directory suffix=".php">../../system</directory>
+        </whitelist>
+	</filter>
+</phpunit>
\ No newline at end of file
diff --git a/tests/travis/pdo/mysql.phpunit.xml b/tests/travis/pdo/mysql.phpunit.xml
index c3113a6..7121edc 100644
--- a/tests/travis/pdo/mysql.phpunit.xml
+++ b/tests/travis/pdo/mysql.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../../system</directory>
         </whitelist>
 	</filter>
diff --git a/tests/travis/pdo/pgsql.phpunit.xml b/tests/travis/pdo/pgsql.phpunit.xml
index 2320255..df3ff98 100644
--- a/tests/travis/pdo/pgsql.phpunit.xml
+++ b/tests/travis/pdo/pgsql.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../../system</directory>
         </whitelist>
 	</filter>
diff --git a/tests/travis/pdo/sqlite.phpunit.xml b/tests/travis/pdo/sqlite.phpunit.xml
index 3d12567..7d867f6 100644
--- a/tests/travis/pdo/sqlite.phpunit.xml
+++ b/tests/travis/pdo/sqlite.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../../system</directory>
         </whitelist>
 	</filter>
diff --git a/tests/travis/pgsql.phpunit.xml b/tests/travis/pgsql.phpunit.xml
index 51e433d..bfddbf6 100644
--- a/tests/travis/pgsql.phpunit.xml
+++ b/tests/travis/pgsql.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../system</directory>
         </whitelist>
 	</filter>
diff --git a/tests/travis/sqlite.phpunit.xml b/tests/travis/sqlite.phpunit.xml
index 7011657..75c946a 100644
--- a/tests/travis/sqlite.phpunit.xml
+++ b/tests/travis/sqlite.phpunit.xml
@@ -18,7 +18,7 @@
 		</testsuite>
 	</testsuites>
 	<filter>
-        <whitelist addUncoveredFilesFromWhitelist="true">
+        <whitelist addUncoveredFilesFromWhitelist="false">
             <directory suffix=".php">../../system</directory>
         </whitelist>
 	</filter>
diff --git a/user_guide_src/source/_themes/eldocs/layout.html b/user_guide_src/source/_themes/eldocs/layout.html
index 01db07c..51d61b8 100644
--- a/user_guide_src/source/_themes/eldocs/layout.html
+++ b/user_guide_src/source/_themes/eldocs/layout.html
@@ -91,13 +91,7 @@
 		</div><!-- /#brand -->
 
 		<div id="header">
-			<form method="get" action="http://www.google.com/search">
-				<fieldset>
-					<input type="text" name="q" id="q" value="">
-					<input type="hidden" name="as_sitesearch" id="as_sitesearch" value="{{ project_domain }}/user_guide/" />
-					<input class="grades" type="submit" value="search">
-				</fieldset>
-			</form>
+            {%- include "searchbox.html" %}
 			<ul>
 				{%- block rootrellink %}
 				<li><a href="{{ pathto(master_doc) }}">User Guide Home</a>{%- if pagename != 'index' %}&nbsp;&nbsp;{{ reldelim1 }}{%- endif %}</li>
@@ -113,8 +107,10 @@
 			</ul>
 		</div><!-- /#header -->
 
-		<div class="section" id="content">
+		<div class="section body" id="content">
+			{%- block body %}
 			{{ body }}
+			{%- endblock %}
 		</div>
 	{%- endblock %}
 
@@ -125,8 +121,8 @@
 	{%- block footer %}			
 		<div id="footer">
 			<p class="top"><a href="#header" title="Return to top">Return to top</a></p>
-			<p><a href="{{ project_url }}">{{ project }}</a> &ndash; Copyright &copy; {{ copyright }}</a></p>
+			<p><a href="http://{{ project_domain }}/">{{ project }}</a> &ndash; Copyright &copy; {{ copyright }}</a></p>
 		</div><!-- /#footer -->
 	{%- endblock %}
 	</body>
-</html>
+</html>
\ No newline at end of file
diff --git a/user_guide_src/source/_themes/eldocs/searchbox.html b/user_guide_src/source/_themes/eldocs/searchbox.html
new file mode 100644
index 0000000..039590b
--- /dev/null
+++ b/user_guide_src/source/_themes/eldocs/searchbox.html
@@ -0,0 +1,21 @@
+<!--
+	--------------------------------
+	Original Google search box block
+	--------------------------------
+	
+<form method="get" action="http://www.google.com/search">
+	<fieldset>
+		<input type="text" name="q" id="q" value="">
+		<input type="hidden" name="as_sitesearch" id="as_sitesearch" value="{{ project_domain }}/user_guide/" />
+		<input class="grades" type="submit" value="search">
+	</fieldset>
+</form>
+-->
+
+
+<form class="search" action="{{ pathto('search') }}" method="get">
+  <input type="text" name="q" id="q" value="" />
+  <input type="submit" value="search" />
+  <input type="hidden" name="check_keywords" value="yes" />
+  <input type="hidden" name="area" value="default" />
+</form>
diff --git a/user_guide_src/source/_themes/eldocs/static/asset/css/common.css b/user_guide_src/source/_themes/eldocs/static/asset/css/common.css
index 6cabda0..0a63871 100644
--- a/user_guide_src/source/_themes/eldocs/static/asset/css/common.css
+++ b/user_guide_src/source/_themes/eldocs/static/asset/css/common.css
@@ -148,6 +148,8 @@
 
 .top{ float: right; }
 
+.highlight{ overflow-x: auto; }
+
 .admonition,
 .highlight-ee,
 .highlight-ci,
@@ -166,6 +168,8 @@
 	padding: 10px 10px 8px; 
 }
 
+.highlight-ci{ background-color: #FEFEFE; border-color: #E5E5E5; }
+
 	.admonition p{ margin: 0; }
 	
 	.codeblock{ margin: 10px 0; }
@@ -181,6 +185,8 @@
 }
 
 .admonition-title:after{ content: ':  '; }
+
+.highlighted{ background-color: #FFF09B; }
 	
 #table-contents{
 	bottom: 0;
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 75229f9..a3d2905 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -22,33 +22,40 @@
    -  ``$_SERVER['CI_ENV']`` can now be set to control the ``ENVIRONMENT`` constant.
    -  Added an optional backtrace to php-error template.
    -  Added Android to the list of user agents.
-   -  Added Windows 7, Android, Blackberry and iOS to the list of user platforms.
+   -  Added Windows 7, Windows 8, Android, Blackberry, iOS and PlayStation 3 to the list of user platforms.
    -  Added Fennec (Firefox for mobile) to the list of mobile user agents.
    -  Ability to log certain error types, not all under a threshold.
    -  Added support for pem, p10, p12, p7a, p7c, p7m, p7r, p7s, crt, crl, der, kdb, rsa, cer, sst, csr Certs to mimes.php.
-   -  Added support for pgp and gpg to mimes.php.
+   -  Added support for pgp, gpg, zsh and cdr files to mimes.php.
    -  Added support for 3gp, 3g2, mp4, wmv, f4v, vlc Video files to mimes.php.
-   -  Added support for m4a, aac, m4u, xspf, au, ac3, flac, ogg Audio files to mimes.php.
+   -  Added support for m4a, aac, m4u, xspf, au, ac3, flac, ogg, wma Audio files to mimes.php.
    -  Added support for kmz and kml (Google Earth) files to mimes.php.
    -  Added support for ics Calendar files to mimes.php.
-   -  Added support for rar archives to mimes.php.
+   -  Added support for rar, jar and 7zip archives to mimes.php.
    -  Updated support for xml ('application/xml') and xsl ('application/xml', 'text/xsl') files in mimes.php.
    -  Updated support for doc files in mimes.php.
+   -  Updated support for docx files in mimes.php.
    -  Updated support for php files in mimes.php.
    -  Updated support for zip files in mimes.php.
+   -  Updated support for csv files in mimes.php.
    -  Added some more doctypes.
-   -  Added Romanian and Greek characters in foreign_characters.php.
+   -  Added Romanian, Greek, Vietnamese and Cyrilic characters in *application/config/foreign_characters.php*.
    -  Changed logger to only chmod when file is first created.
    -  Removed previously deprecated SHA1 Library.
-   -  Removed previously deprecated use of ``$autoload['core']`` in application/config/autoload.php.
+   -  Removed previously deprecated use of ``$autoload['core']`` in *application/config/autoload.php*.
       Only entries in ``$autoload['libraries']`` are auto-loaded now.
    -  Removed previously deprecated EXT constant.
    -  Updated all classes to be written in PHP 5 style, with visibility declarations and no ``var`` usage for properties.
-   -  Moved error templates to "application/views/errors"
+   -  Moved error templates to *application/views/errors/*.
+   -  Moved the Log class to *application/core/*
    -  Global config files are loaded first, then environment ones. Environment config keys overwrite base ones, allowing to only set the keys we want changed per environment.
    -  Changed detection of ``$view_folder`` so that if it's not found in the current path, it will now also be searched for under the application folder.
    -  Path constants BASEPATH, APPPATH and VIEWPATH are now (internally) defined as absolute paths.
-   -  Updated email validation methods to use filter_var() instead of PCRE.
+   -  Updated email validation methods to use ``filter_var()`` instead of PCRE.
+   -  Changed environment defaults to report all errors in *development* and only fatal ones in *testing*, *production* but only display them in *development*.
+   -  Updated *ip_address* database field lengths from 16 to 45 for supporting IPv6 address on :doc:`Trackback Library <libraries/trackback>` and :doc:`Captcha Helper <helpers/captcha_helper>`.
+   -  Removed *cheatsheets* and *quick_reference* PDFs from the documentation.
+   -  Added availability checks where usage of dangerous functions like ``eval()`` and ``exec()`` is required.
 
 -  Helpers
 
@@ -56,150 +63,262 @@
 	 - ``now()`` now works with all timezone strings supported by PHP.
 	 - Added an optional third parameter to ``timespan()`` that constrains the number of time units displayed.
 	 - Added an optional parameter to ``timezone_menu()`` that allows more attributes to be added to the generated select tag.
-	 - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants <http://bg2.php.net/manual/en/class.datetime.php#datetime.constants.types>`_.
+	 - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants <http://www.php.net/manual/en/class.datetime.php#datetime.constants.types>`_.
+	 - Added function ``date_range()`` that generates a list of dates between a specified period.
    -  ``create_captcha()`` accepts additional colors parameter, allowing for color customization.
    -  :doc:`URL Helper <helpers/url_helper>` changes include:
-	 - ``url_title()`` will now trim extra dashes from beginning and end.
-	 - ``anchor_popup()`` will now fill the "href" attribute with the URL and its JS code will return false instead.
-	 - Added JS window name support to ``anchor_popup()`` function.
-	 - Added support (auto-detection) for HTTP/1.1 response code 303 in ``redirect()``.
+	 - Deprecated *separator* options **dash** and **underscore** for function :php:func:`url_title()` (they are only aliases for '-' and '_' respectively).
+	 - :php:func:`url_title()` will now trim extra dashes from beginning and end.
+	 - :php:func:`anchor_popup()` will now fill the *href* attribute with the URL and its JS code will return FALSE instead.
+	 - Added JS window name support to the :php:func:`anchor_popup()` function.
+	 - Added support (auto-detection) for HTTP/1.1 response code 303 in :php:func:`redirect()`.
+	 - Changed :php:func:`redirect()` to only choose the **refresh** method only on IIS servers, instead of all servers on Windows (when **auto** is used).
+	 - Changed :php:func:`anchor()`, :php:func:`anchor_popup()`, and :php:func:`redirect()` to support protocol-relative URLs (e.g. *//ellislab.com/codeigniter*).
    -  Added XHTML Basic 1.1 doctype to :doc:`HTML Helper <helpers/html_helper>`.
-   -  Changed ``humanize()`` to include a second param for the separator.
-   -  Refactored ``plural()`` and ``singular()`` to avoid double pluralization and support more words.
-   -  Added an optional third parameter to ``force_download()`` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default).
-   -  Added a work-around in ``force_download()`` for a bug Android <= 2.1, where the filename extension needs to be in uppercase.
-   -  ``form_dropdown()`` will now also take an array for unity with other form helpers.
-   -  ``do_hash()`` now uses PHP's native ``hash()`` function (supporting more algorithms) and is deprecated.
-   -  Removed previously deprecated helper function ``js_insert_smiley()`` from smiley helper.
+   -  :doc:`Inflector Helper <helpers/inflector_helper>` changes include:
+	 - Changed :php:func:`humanize()` to allow passing an input separator as its second parameter.
+	 - Refactored :php:func:`plural()` and :php:func:`singular()` to avoid double pluralization and support more words.
+   -  :doc:`Download Helper <helpers/download_helper>` changes include:
+	 - Added an optional third parameter to :php:func:`force_download()` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default).
+	 - Added a work-around in :php:func:`force_download()` for a bug Android <= 2.1, where the filename extension needs to be in uppercase.
+	 - Added support for reading from an existing file path by passing NULL as the second parameter to :php:func:`force_download()` (useful for large files and/or safely transmitting binary data).
+   -  :doc:`Form Helper <helpers/form_helper>` changes include:
+	 - :php:func:`form_dropdown()` will now also take an array for unity with other form helpers.
+	 - :php:func:`form_prep()`'s second argument now only accepts a boolean value, which determines whether the value is escaped for a <textarea> or a regular <input> element.
+   -  :doc:`Security Helper <helpers/security_helper>` changes include:
+	 - :php:func:`do_hash()` now uses PHP's native ``hash()`` function (supporting more algorithms) and is deprecated.
+	 - :php:func:`strip_image_tags()` is now an alias for the same method in the :doc:`Security Library <libraries/security>`.
+   -  Removed previously deprecated helper function ``js_insert_smiley()`` from :doc:`Smiley Helper <helpers/smiley_helper>`.
    -  :doc:`File Helper <helpers/file_helper>` changes include:
-	 - ``set_realpath()`` can now also handle file paths as opposed to just directories.
-	 - Added an optional paramater to ``delete_files()`` to enable it to skip deleting files such as .htaccess and index.html.
-	 - ``read_file()`` is now a deprecated alias of ``file_get_contents()``.
-   -  :doc:`Security Helper <helpers/security_helper>` function ``strip_image_tags()`` is now an alias for the same method in the :doc:`Security Library <libraries/security>`.
+	 - :php:func:`set_realpath()` can now also handle file paths as opposed to just directories.
+	 - Added an optional paramater to :php:func:`delete_files()` to enable it to skip deleting files such as *.htaccess* and *index.html*.
+	 - Deprecated function ``read_file()`` - it's just an alias for PHP's native ``file_get_contents()``.
+   -  :doc:`String Helper <helpers/string_helper>` changes include:
+	 - Deprecated function ``repeater()`` - it's just an alias for PHP's native ``str_repeat()``.
+	 - Deprecated function ``trim_slashes()`` - it's just an alias for PHP's native ``trim()`` (with a slash as its second argument).
+	 - Deprecated randomization type options **unique** and **encrypt** for funcion :php:func:`random_string()` (they are only aliases for **md5** and **sha1** respectively).
+   -  :doc:`Directory Helper <helpers/directory_helper>` :php:func:`directory_map()` will now append ``DIRECTORY_SEPARATOR`` to directory names in the returned array.
+   -  :doc:`Language Helper <helpers/language_helper>` :php:func:`lang()` now accepts an optional list of additional HTML attributes.
+   -  Deprecated the :doc:`Email Helper <helpers/email_helper>` as its ``valid_email()``, ``send_email()`` functions are now only aliases for PHP native functions ``filter_var()`` and ``mail()`` respectively.
 
 -  Database
 
+   -  Added **dsn** configuration setting for drivers that support DSN strings (PDO, PostgreSQL, Oracle, ODBC, CUBRID).
+   -  Added **schema** configuration setting (defaults to *public*) for drivers that might need it (currently used by PostgreSQL and ODBC).
+   -  Added subdrivers support (currently only used by PDO).
+   -  Added an optional database name parameter to ``db_select()``.
+   -  Removed ``protect_identifiers()`` and renamed internal method ``_protect_identifiers()`` to it instead - it was just an alias.
+   -  Renamed internal method ``_escape_identifiers()`` to ``escape_identifiers()``.
+   -  Updated ``escape_identifiers()`` to accept an array of fields as well as strings.
+   -  MySQL and MySQLi drivers now require at least MySQL version 5.1.
+   -  ``db_set_charset()`` now only requires one parameter (collation was only needed due to legacy support for MySQL versions prior to 5.1).
+   -  Replaced the ``_error_message()`` and ``_error_number()`` methods with ``error()``, which returns an array containing the last database error code and message.
+   -  Improved ``version()`` implementation so that drivers that have a native function to get the version number don't have to be defined in the core ``DB_driver`` class.
+   -  Added capability for packages to hold *config/database.php* config files.
+   -  Added MySQL client compression support.
+   -  Added encrypted connections support (for *mysql*, *sqlsrv* and PDO with *sqlsrv*).
+   -  Removed :doc:`Loader Class <libraries/loader>` from Database error tracing to better find the likely culprit.
+   -  Added support for SQLite3 database driver.
+   -  Added Interbase/Firebird database support via the *ibase* driver.
+   -  Added ODBC support for ``create_database()``, ``drop_database()`` and ``drop_table()`` in :doc:`Database Forge <database/forge>`.
    -  :doc:`Query Builder <database/query_builder>` changes include:
 	 - Renamed the Active Record class to Query Builder to remove confusion with the Active Record design pattern.
-	 - Added the ability to insert objects with insert_batch().
-	 - Added new methods that return the SQL string of queries without executing them: get_compiled_select(), get_compiled_insert(), get_compiled_update(), get_compiled_delete().
-	 - Added an optional parameter that allows to disable escaping (useful for custom fields) for methods join(), order_by(), where_in(), or_where_in(), where_not_in(), or_where_not_in().
-	 - Added support for join() with multiple conditions.
-	 - Added support for USING in join().
-	 - Changed limit() to ignore NULL values instead of always casting to integer.
-	 - Changed offset() to ignore empty values instead of always casting to integer.
+	 - Added the ability to insert objects with ``insert_batch()``.
+	 - Added new methods that return the SQL string of queries without executing them: ``get_compiled_select()``, ``get_compiled_insert()``, ``get_compiled_update()``, ``get_compiled_delete()``.
+	 - Added an optional parameter that allows to disable escaping (useful for custom fields) for methods ``join()``, ``order_by()``, ``where_in()``, ``or_where_in()``, ``where_not_in()``, ``or_where_not_in()``, ``insert()``, ``insert_batch()``.
+	 - Added support for ``join()`` with multiple conditions.
+	 - Added support for *USING* in ``join()``.
+	 - Added seed values support for random ordering with ``order_by(seed, 'RANDOM')``.
+	 - Changed ``limit()`` to ignore NULL values instead of always casting to integer.
+	 - Changed ``offset()`` to ignore empty values instead of always casting to integer.
+	 - Methods ``insert_batch()`` and ``update_batch()`` now return an integer representing the number of rows affected by them.
+   -  :doc:`Database Results <database/results>` changes include:
+	 - Added a constructor to the ``DB_result`` class and moved all driver-specific properties and logic out of the base ``DB_driver`` class to allow better abstraction.
+	 - Added method ``unbuffered_row()`` for fetching a row without prefetching the whole result (consume less memory).
+	 - Renamed former method ``_data_seek()`` to ``data_seek()`` and made it public.
    -  Improved support for the MySQLi driver, including:
 	 - OOP style of the PHP extension is now used, instead of the procedural aliases.
 	 - Server version checking is now done via ``mysqli::$server_info`` instead of running an SQL query.
 	 - Added persistent connections support for PHP >= 5.3.
-	 - Added support for backup() in :doc:`Database Utilities <database/utilities>`.
-   -  Added 'dsn' configuration setting for drivers that support DSN strings (PDO, PostgreSQL, Oracle, ODBC, CUBRID).
-   -  Improved PDO database support.
-   -  Added Interbase/Firebird database support via the 'ibase' driver.
-   -  Added an optional database name parameter to db_select().
-   -  Replaced the _error_message() and _error_number() methods with error(), that returns an array containing the last database error code and message.
-   -  Improved version() implementation so that drivers that have a native function to get the version number don't have to be defined in the core DB_driver class.
+	 - Added support for ``backup()`` in :doc:`Database Utilities <database/utilities>`.
+   -  Improved support of the PDO driver, including:
+	 - Added support for ``create_database()``, ``drop_database()`` and ``drop_table()`` in :doc:`Database Forge <database/forge>`.
+	 - Added support for ``list_fields()`` in :doc:`Database Results <database/results>`.
+	 - Subdrivers are now isolated from each other instead of being in one large class.
    -  Improved support of the PostgreSQL driver, including:
-	 - pg_version() is now used to get the database version number, when possible.
-	 - Added db_set_charset() support.
-	 - Added support for optimize_table() in :doc:`Database Utilities <database/utilities>` (rebuilds table indexes).
-	 - Added boolean data type support in escape().
-	 - Added update_batch() support.
-	 - Removed limit() and order_by() support for UPDATE and DELETE queries in as PostgreSQL does not support those features.
-   -  Added a constructor to the DB_result class and moved all driver-specific properties and logic out of the base DB_driver class to allow better abstraction.
-   -  Removed protect_identifiers() and renamed internal method _protect_identifiers() to it instead - it was just an alias.
-   -  Renamed internal method _escape_identifiers() to escape_identifiers().
-   -  Updated escape_identifiers() to accept an array of fields as well as strings.
-   -  MySQL and MySQLi drivers now require at least MySQL version 5.1.
-   -  db_set_charset() now only requires one parameter (collation was only needed due to legacy support for MySQL versions prior to 5.1).
-   -  Added support for SQLite3 database driver.
+	 - ``pg_version()`` is now used to get the database version number, when possible.
+	 - Added ``db_set_charset()`` support.
+	 - Added support for ``optimize_table()`` in :doc:`Database Utilities <database/utilities>` (rebuilds table indexes).
+	 - Added boolean data type support in ``escape()``.
+	 - Added ``update_batch()`` support.
+	 - Removed ``limit()`` and ``order_by()`` support for *UPDATE* and *DELETE* queries as PostgreSQL does not support those features.
+	 - Added a work-around for dead persistent connections to be re-created after a database restart.
+	 - Changed ``db_connect()`` to include the (new) **schema** value into Postgre's **search_path** session variable.
    -  Improved support of the CUBRID driver, including:
 	 - Added DSN string support.
 	 - Added persistent connections support.
-	 - Improved list_databases() in :doc:`Database Utility <database/utilities>` (until now only the currently used database was returned).
+	 - Improved ``list_databases()`` in :doc:`Database Utility <database/utilities>` (until now only the currently used database was returned).
    -  Improved support of the MSSQL and SQLSRV drivers, including:
 	 - Added random ordering support.
-	 - Added support for optimize_table() in :doc:`Database Utility <database/utilities>`.
-	 - Added escaping with QUOTE_IDENTIFIER setting detection.
+	 - Added support for ``optimize_table()`` in :doc:`Database Utility <database/utilities>`.
+	 - Added escaping with *QUOTE_IDENTIFIER* setting detection.
 	 - Added port handling support for UNIX-based systems (MSSQL driver).
-	 - Added OFFSET support for SQL Server 2005 and above.
+	 - Added *OFFSET* support for SQL Server 2005 and above.
+	 - Added ``db_set_charset()`` support (MSSQL driver).
    -  Improved support of the Oracle (OCI8) driver, including:
 	 - Added DSN string support (Easy Connect and TNS).
-	 - Added support for drop_table() in :doc:`Database Forge <database/forge>`.
-	 - Added support for list_databases() in :doc:`Database Utilities <database/utilities>`.
+	 - Added support for ``drop_table()`` in :doc:`Database Forge <database/forge>`.
+	 - Added support for ``list_databases()`` in :doc:`Database Utilities <database/utilities>`.
 	 - Generally improved for speed and cleaned up all of its components.
-	 - num_rows() is now only called explicitly by the developer and no longer re-executes statements.
+	 - ``num_rows()`` is now only called explicitly by the developer and no longer re-executes statements.
    -  Improved support of the SQLite driver, including:
-	 - Added support for replace() in :doc:`Query Builder <database/query_builder>`.
-	 - Added support for drop_table() in :doc:`Database Forge <database/forge>`.
-   -  Added ODBC support for create_database(), drop_database() and drop_table() in :doc:`Database Forge <database/forge>`.
-   -  Added PDO support for create_database(), drop_database and drop_table() in :doc:`Database Forge <database/forge>`.
-   -  Added unbuffered_row() method for getting a row without prefetching whole result (consume less memory).
-   -  Added PDO support for ``list_fields()`` in :doc:`Database Results <database/results>`.
-   -  Added capability for packages to hold database.php config files 
+	 - Added support for ``replace()`` in :doc:`Query Builder <database/query_builder>`.
+	 - Added support for ``drop_table()`` in :doc:`Database Forge <database/forge>`.
+   -  :doc:`Database Forge <database/forge>` changes include:
+	 - Added an optional second parameter to ``drop_table()`` that allows adding the **IF EXISTS** condition, which is no longer the default.
+	 - Added support for passing a custom database object to the loader.
+	 - Deprecated ``add_column()``'s third method. *AFTER* clause should now be added to the field definition array instead.
+	 - Added support for usage of the *FIRST* clause in ``add_column()`` for MySQL and CUBRID.
+	 - Overall improved support for all of the drivers.
+   -  :doc:`Database Utility <database/utilities>` chages include:
+	 - Added support for passing a custom database object to the loader.
+	 - Modified the class to no longer extend :doc:`Database Forge <database/forge>`, which has been a deprecated behavior for awhile.
+	 - Overall improved support for all of the drivers.
 
 -  Libraries
 
-   -  CI_Session now respects php.ini's session.gc_probability and session.gc_divisor
-   -  Added max_filename_increment config setting for Upload library.
-   -  CI_Loader::_ci_autoloader() is now a protected method.
-   -  Added custom filename to Email::attach() as $this->email->attach($filename, $disposition, $newname).
-   -  Added possibility to send attachment as buffer string in Email::attach() as $this->email->attach($buffer, $disposition, $newname, $mime).
+   -  :doc:`Session Library <libraries/sessions>` changes include:
+	 -  Library changed to :doc:`Driver <general/drivers>` with classic Cookie driver as default.
+	 -  Added Native PHP Session driver to work with ``$_SESSION``.
+	 -  Custom drivers can be added anywhere in package paths and be loaded with the library.
+	 -  Drivers interchangeable on the fly.
+	 -  New **tempdata** feature allows setting user data items with an expiration time.
+	 -  Added default ``$config['sess_driver']`` and ``$config['sess_valid_drivers']`` items to *config.php* file.
+	 -  Cookie driver now respects php.ini's *session.gc_probability* and *session.gc_divisor* settings.
+	 -  Cookie driver now uses HMAC authentication instead of the simple md5 checksum.
+	 -  The Cookie driver now also checks authentication on encrypted session data.
+	 -  Changed the Cookie driver to select only one row when using database sessions.
+	 -  Cookie driver now only writes to database at end of request when using database.
+	 -  Cookie driver now uses PHP functions for faster array manipulation when using database.
+	 -  Added ``all_flashdata()`` method to session class. Returns an associative array of only flashdata.
+	 -  Added ``has_userdata()`` method to verify existence of userdata item.
+	 -  Added ``tempdata()``, ``set_tempdata()``, and ``unset_tempdata()`` methods for manipulating tempdata.
+	 -  ``keep_flashdata()`` now accepts an array of keys.
+   -  :doc:`File Uploading Library <libraries/file_uploading>` changes include:
+	 -  Added **max_filename_increment** config setting.
+	 -  Added an **index** parameter to the ``data()`` method.
+	 -  Added the **min_width** and **min_height** options for images.
    -  :doc:`Cart library <libraries/cart>` changes include:
-	 -  It now auto-increments quantity's instead of just resetting it, this is the default behaviour of large e-commerce sites.
-	 -  Product Name strictness can be disabled via the Cart Library by switching "$product_name_safe".
-	 -  Added function remove() to remove a cart item, updating with quantity of 0 seemed like a hack but has remained to retain compatibility.
+	 -  ``insert()`` now auto-increments quantity for an item when inserted twice instead of resetting it, this is the default behaviour of large e-commerce sites.
+	 -  *Product Name* strictness can be disabled by switching the ``$product_name_safe`` property to FALSE.
+	 -  Added method ``remove()`` to remove a cart item, updating with quantity of 0 seemed like a hack but has remained to retain compatibility.
+	 -  Added method ``get_item()`` to enable retrieving data for a single cart item.
    -  :doc:`Image Manipulation library <libraries/image_lib>` changes include:
-	 -  The initialize() method now only sets existing class properties.
-	 -  Added support for 3-length hex color values for wm_font_color and wm_shadow_color properties, as well as validation for them.
-	 -  Class properties wm_font_color, wm_shadow_color and wm_use_drop_shadow are now protected, to avoid breaking the text_watermark() method if they are set manually after initialization.
-	 -  If property maintain_ratio is set to TRUE, image_reproportion() now doesn't need both width and height to be specified.
-	 -  Property maintain_ratio is now taken into account when resizing images using ImageMagick library
-   -  Removed SHA1 function in the :doc:`Encryption Library <libraries/encryption>`.
-   -  Added $config['csrf_regeneration'] to the CSRF protection in the :doc:`Security library <libraries/security>`, which makes token regeneration optional.
-   -  Added $config['csrf_exclude_uris'] to the CSRF protection in the :doc:`Security library <libraries/security>`, which allows you list URIs which will not have the CSRF validation functions run.
+	 -  The ``initialize()`` method now only sets existing class properties.
+	 -  Added support for 3-length hex color values for *wm_font_color* and *wm_shadow_color* properties, as well as validation for them.
+	 -  Class properties *wm_font_color*, *wm_shadow_color* and *wm_use_drop_shadow* are now protected, to avoid breaking the ``text_watermark()`` method if they are set manually after initialization.
+	 -  If property *maintain_ratio* is set to TRUE, ``image_reproportion()`` now doesn't need both width and height to be specified.
+	 -  Property *maintain_ratio* is now taken into account when resizing images using ImageMagick library.
+	 -  Added support for maintaining transparency for PNG images in method ``text_watermark()``.
    -  :doc:`Form Validation library <libraries/form_validation>` changes include:
-	 -  Added method error_array() to return all error messages as an array.
-	 -  Added method set_data() to set an alternative data array to be validated instead of the default $_POST.
-	 -  Added method reset_validation(), which resets internal validation variables in case of multiple validation routines.
-	 -  Added support for setting error delimiters in the config file via $config['error_prefix'] and $config['error_suffix'].
-	 -  _execute() now considers input data to be invalid if a specified rule is not found.
-	 -  Removed method is_numeric() as it exists as a native PHP function and _execute() will find and use that (the 'is_numeric' rule itself is deprecated since 1.6.1).
+	 -  Added method ``error_array()`` to return all error messages as an array.
+	 -  Added method ``set_data()`` to set an alternative data array to be validated instead of the default ``$_POST``.
+	 -  Added method ``reset_validation()`` which resets internal validation variables in case of multiple validation routines.
+	 -  Added support for setting error delimiters in the config file via ``$config['error_prefix']`` and ``$config['error_suffix']``.
+	 -  Internal method ``_execute()`` now considers input data to be invalid if a specified rule is not found.
+	 -  Removed method ``is_numeric()`` as it exists as a native PHP function and ``_execute()`` will find and use that (the **is_numeric** rule itself is deprecated since 1.6.1).
 	 -  Native PHP functions used as rules can now accept an additional parameter, other than the data itself.
-	 -  Updated set_rules() to accept an array of rules as well as a string.
-   -  Changed the :doc:`Session Library <libraries/sessions>` to select only one row when using database sessions.
-   -  Added all_flashdata() method to session class. Returns an associative array of only flashdata.
-   -  Allowed for setting table class defaults in a config file.
-   -  Added a Wincache driver to the :doc:`Caching Library <libraries/caching>`.
-   -  Added a Redis driver to the :doc:`Caching Library <libraries/caching>`.
-   -  Added dsn (delivery status notification) option to the :doc:`Email Library <libraries/email>`.
-   -  Renamed method _set_header() to set_header() and made it public to enable adding custom headers in the :doc:`Email Library <libraries/email>`.
-   -  Added an "index" parameter to the data() method in the :doc:`Upload Library <libraries/file_uploading>`.
+	 -  Updated method ``set_rules()`` to accept an array of rules as well as a string.
+	 -  Fields that have empty rules set no longer run through validation (and therefore are not considered erroneous).
+	 -  Added rule **differs* to check if the value of a field differs from the value of another field.
+	 -  Added rule **valid_url**.
+	 -  Added support for named parameters in error messages.
+	 -  :doc:`Language <libraries/language>` line keys must now be prefixed with **form_validation_**.
+   -  Added support for setting :doc:`Table <libraries/table>` class defaults in a config file.
+   -  :doc:`Caching Library <libraries/caching>` changes include:
+	 -  Added Wincache driver.
+	 -  Added Redis driver.
+	 -  Added a *key_prefix* option for cache IDs.
+   -  :doc:`Email library <libraries/email>` changes include:
+	 -  Added custom filename to ``Email::attach()`` as ``$this->email->attach($filename, $disposition, $newname)``.
+	 -  Added possibility to send attachment as buffer string in ``Email::attach()`` as ``$this->email->attach($buffer, $disposition, $newname, $mime)``.
+	 -  Added dsn (delivery status notification) option.
+	 -  Renamed method _set_header() to set_header() and made it public to enable adding custom headers in the :doc:`Email Library <libraries/email>`.
+	 -  Successfully sent emails will automatically clear the parameters.
+	 -  Added a *return_path* parameter to the ``from()`` method.
+	 -  Removed the second parameter (character limit) from internal method ``_prep_quoted_printable()`` as it is never used.
+	 -  Internal method ``_prep_quoted_printable()`` will now utilize the native ``quoted_printable_encode()``, ``imap_8bit()`` functions (if available) when CRLF is set to "\r\n".
+	 -  Default charset now relies on the global ``$config['charset']`` setting.
+	 -  Removed unused protected method ``_get_ip()`` (:doc:`Input Library <libraries/input>`'s ``ip_address()`` should be used anyway).
+	 -  Internal method ``_prep_q_encoding()`` now utilizes PHP's *mbstring* and *iconv* extensions (when available) and no longer has a second (``$from``) argument.
+	 -  Added an optional parameter to ``print_debugger()`` to allow specifying which parts of the message should be printed ('headers', 'subject', 'body').
    -  :doc:`Pagination Library <libraries/pagination>` changes include:
 	 -  Added support for the anchor "rel" attribute.
 	 -  Added support for setting custom attributes.
 	 -  Deprecated usage of the "anchor_class" setting (use the new "attributes" setting instead).
 	 -  Added $config['reuse_query_string'] to allow automatic repopulation of query string arguments, combined with normal URI segments.
+   -  Removed the default ``&nbsp;`` from a number of the configuration variables.
    -  Added the ability to use a proxy with the :doc:`XML-RPC Library <libraries/xmlrpc>`.
+   -  :doc:`Encryption Library <libraries/encryption>` changes include:
+	 -  Added support for hashing algorithms other than SHA1 and MD5.
+	 -  Removed previously deprecated ``sha1()`` method.
+   -  :doc:`Profiler Library <general/profiling>` now also displays database object names.
+   -  :doc:`Migration Library <libraries/migration>` changes include:
+	 -  Added support for timestamp-based migrations (enabled by default).
+	 -  Added ``$config['migration_type']`` to allow switching between *sequential* and *timestamp* migrations.
+   -  :doc:`User Agent Library <libraries/user_agent>` will now check if robots are pretending to be mobile clients (helps with e.g. Google indexing mobile website versions).
 
 -  Core
 
-   -  Changed private methods in the :doc:`URI Library <libraries/uri>` to protected so MY_URI can override them.
-   -  Removed CI_CORE boolean constant from CodeIgniter.php (no longer Reactor and Core versions).
-   -  Added method get_vars() to the :doc:`Loader Library <libraries/loader>` to retrieve all variables loaded with $this->load->vars().
-   -  is_loaded() function from system/core/Commons.php now returns a reference.
-   -  $config['rewrite_short_tags'] now has no effect when using PHP 5.4 as *<?=* will always be available.
-   -  Added method() to the :doc:`Input Library <libraries/input>` to retrieve $_SERVER['REQUEST_METHOD'].
-   -  Modified valid_ip() to use PHP's filter_var() in the :doc:`Input Library <libraries/input>`.
-   -  Added support for HTTP-Only cookies with new config option ``cookie_httponly`` (default FALSE).
-   -  Renamed method _call_hook() to call_hook() in the :doc:`Hooks Library <general/hooks>`.
-   -  Added get_content_type() method to the :doc:`Output Library <libraries/output>`.
-   -  Added get_mimes() function to system/core/Commons.php to return the config/mimes.php array.
-   -  Added a second argument to set_content_type() in the :doc:`Output Library <libraries/output>` that allows setting the document charset as well.
-   -  $config['time_reference'] now supports all timezone strings supported by PHP.
-   -  Added support for HTTP code 303 ("See Other") in set_status_header().
-   -  Changed :doc:`Config Library <libraries/config>` method site_url() to accept an array as well.
-   -  Added method ``strip_image_tags()`` to the :doc:`Security Library <libraries/security>`.
+   -  :doc:`URI Library <libraries/uri>` changes include:
+	 -  Changed private methods to protected so that MY_URI can override them.
+	 -  Renamed internal method ``_parse_cli_args()`` to ``_parse_argv()``.
+	 -  Renamed internal method ``_detect_uri()`` to ``_parse_request_uri()``.
+	 -  Changed ``_parse_request_uri()`` to accept absolute URIs for compatibility with HTTP/1.1 as per `RFC2616 <http://www.ietf.org/rfc/rfc2616.txt>`.
+	 -  Added protected method ``_parse_query_string()`` to URI paths in the the **QUERY_STRING** value, like ``_parse_request_uri()`` does.
+	 -  Changed ``_fetch_uri_string()`` to try the **PATH_INFO** variable first when auto-detecting.
+   -  Removed ``CI_CORE`` boolean constant from *CodeIgniter.php* (no longer Reactor and Core versions).
+   -  :doc:`Loader Library <libraries/loader>` changes include:
+	 -  Added method ``get_vars()`` to the Loader to retrieve all variables loaded with ``$this->load->vars()``.
+	 -  ``_ci_autoloader()`` is now a protected method.
+	 -  Added autoloading of drivers with ``$autoload['drivers']``.
+	 -  ``$config['rewrite_short_tags']`` now has no effect when using PHP 5.4 as ``<?=`` will always be available.
+	 -  Changed method ``config()`` to return whatever ``CI_Config::load()`` returns instead of always being void.
+   -  :doc:`Input Library <libraries/input>` changes include:
+	 -  Added ``method()`` to retrieve ``$_SERVER['REQUEST_METHOD']``.
+	 -  Added support for arrays and network addresses (e.g. 192.168.1.1/24) for use with the *proxy_ips* setting.
+	 -  Added method ``input_stream()`` to aid in using **php://input** stream data such as one passed via PUT, DELETE and PATCH requests.
+	 -  Changed method ``valid_ip()`` to use PHP's native ``filter_var()`` function.
+	 -  Changed internal method ``_sanitize_globals()`` to skip enforcing reversal of *register_globals* in PHP 5.4+, where this functionality no longer exists.
+	 -  Changed methods ``get()``, ``post()``, ``get_post()``, ``cookie()``, ``server()``, ``user_agent()`` to return NULL instead of FALSE when no value is found.
+   -  :doc:`Common functions <general/common_functions>` changes include:
+	 -  Added function :php:func:`get_mimes()` to return the *application/config/mimes.php* array.
+	 -  Added support for HTTP code 303 ("See Other") in :php:func:`set_status_header()`.
+	 -  Removed redundant conditional to determine HTTP server protocol in :php:func:`set_status_header()`.
+	 -  Changed ``_exception_handler()`` to respect php.ini *display_errors* setting.
+	 -  Added function :php:func:`is_https()` to check if a secure connection is used.
+	 -  Added function :php:func:`function_usable()` to check if a function exists and is not disabled by `Suhosin <http://www.hardened-php.net/suhosin/>`.
+   -  Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE).
+   -  Renamed method ``_call_hook()`` to ``call_hook()`` in the :doc:`Hooks Library <general/hooks>`.
+   -  :doc:`Output Library <libraries/output>` changes include:
+	 -  Added a second argument to method ``set_content_type()`` that allows setting the document charset as well.
+	 -  Added methods ``get_content_type()`` and ``get_header()``.
+	 -  Added method ``delete_cache()``.
+   -  ``$config['time_reference']`` now supports all timezone strings supported by PHP.
+   -  :doc:`Config Library <libraries/config>` changes include:
+	 -  Changed ``site_url()`` method  to accept an array as well.
+	 -  Removed internal method ``_assign_to_config()`` and moved it's implementation in *CodeIgniter.php* instead.
+   -  :doc:`Security Library <libraries/security>` changes include:
+	 -  Added method ``strip_image_tags()``.
+	 -  Added ``$config['csrf_regeneration']``, which makes token regeneration optional.
+	 -  Added ``$config['csrf_exclude_uris']``, which allows you list URIs which will not have the CSRF validation methods run.
+   -  :doc:`URI Routing <general/routing>` changes include:
+	 -  Added possibility to route requests using callbacks.
+	 -  Added possibility to use dashes in the controller and method URI segments (translated to underscores).
+   -  :doc:`Language Library <libraries/language>` changes include:
+	 -  Changed method ``load()`` to filter the language name with ``ctype_digit()``.
+	 -  Added an optional second parameter to method ``line()`` to disable error login for line keys that were not found.
+	 -  Language files are now loaded in a cascading style with the one in **system/** always loaded and overriden afterwards, if another one is found.
 
 Bug fixes for 3.0
 ------------------
@@ -208,9 +327,9 @@
 -  Fixed a bug (#181) where a mis-spelling was in the form validation language file.
 -  Fixed a bug (#159, #163) that mishandled Query Builder nested transactions because _trans_depth was not getting incremented.
 -  Fixed a bug (#737, #75) - :doc:`Pagination <libraries/pagination>` anchor class was not set properly when using initialize method.
--  Fixed a bug (#419) - auto_link() now recognizes URLs that come after a word boundary.
--  Fixed a bug (#724) - is_unique in form validation now checks that you are connected to a database.
--  Fixed a bug (#647) - _get_mod_time() in Zip library no longer generates stat failed errors.
+-  Fixed a bug (#419) - ``auto_link()`` now recognizes URLs that come after a word boundary.
+-  Fixed a bug (#724) - :doc:`Form Validation Library <libraries/form_validation>` rule **is_unique** didn't check if a database connection exists.
+-  Fixed a bug (#647) - :doc:`Zip Library <libraries/zip>` internal method ``_get_mod_time()`` didn't suppress possible "stat failed" errors generated by ``filemtime()``.
 -  Fixed a bug (#608) - Fixes an issue with the Image_lib class not clearing properties completely.
 -  Fixed a bug (#157, #174) - the Image_lib clear() function now resets all variables to their default values.
 -  Fixed a bug where using $this->dbforge->create_table() with PostgreSQL database could lead to fetching whole table.
@@ -234,23 +353,20 @@
 -  Fixed a bug (#129) - ODBC's num_rows() returned -1 in some cases, due to not all subdrivers supporting the odbc_num_rows() function.
 -  Fixed a bug (#153) - E_NOTICE being generated by getimagesize() in the :doc:`File Uploading Library <libraries/file_uploading>`.
 -  Fixed a bug (#611) - SQLSRV's error handling methods used to issue warnings when there's no actual error.
--  Fixed a bug (#1036) - is_write_type() method in the :doc:`Database Library <database/index>` didn't return TRUE for RENAME queries.
+-  Fixed a bug (#1036) - ``is_write_type()`` method in the :doc:`Database Library <database/index>` didn't return TRUE for RENAME queries.
 -  Fixed a bug in PDO's _version() method where it used to return the client version as opposed to the server one.
 -  Fixed a bug in PDO's insert_id() method where it could've failed if it's used with Postgre versions prior to 8.1.
 -  Fixed a bug in CUBRID's affected_rows() method where a connection resource was passed to cubrid_affected_rows() instead of a result.
 -  Fixed a bug (#638) - db_set_charset() ignored its arguments and always used the configured charset instead.
 -  Fixed a bug (#413) - Oracle's error handling methods used to only return connection-related errors.
--  Fixed a bug (#804) - Profiler library was trying to handle objects as strings in some cases, resulting in warnings being issued by htmlspecialchars().
 -  Fixed a bug (#1101) - MySQL/MySQLi result method field_data() was implemented as if it was handling a DESCRIBE result instead of the actual result set.
 -  Fixed a bug in Oracle's :doc:`Database Forge Class <database/forge>` method _create_table() where it failed with AUTO_INCREMENT as it's not supported.
 -  Fixed a bug (#1080) - When using the SMTP protocol, the :doc:`Email Library <libraries/email>` send() method was returning TRUE even if the connection/authentication against the server failed.
--  Fixed a bug (#499) - a CSRF cookie was created even with CSRF protection being disabled.
 -  Fixed a bug (#306) - ODBC's insert_id() method was calling non-existent function odbc_insert_id(), which resulted in a fatal error.
 -  Fixed a bug in Oracle's DB_result class where the cursor id passed to it was always NULL.
 -  Fixed a bug (#64) - Regular expression in DB_query_builder.php failed to handle queries containing SQL bracket delimiters in the join condition.
 -  Fixed a bug in the :doc:`Session Library <libraries/sessions>` where a PHP E_NOTICE error was triggered by _unserialize() due to results from databases such as MSSQL and Oracle being space-padded on the right.
 -  Fixed a bug (#501) - set_rules() to check if the request method is not 'POST' before aborting, instead of depending on count($_POST) in the :doc:`Form Validation Library <libraries/form_validation>`.
--  Fixed a bug (#940) - csrf_verify() used to set the CSRF cookie while processing a POST request with no actual POST data, which resulted in validating a request that should be considered invalid.
 -  Fixed a bug (#136) - PostgreSQL, MySQL and MySQLi's escape_str() method didn't properly escape LIKE wild characters.
 -  Fixed a bug in the library loader where some PHP versions wouldn't execute the class constructor.
 -  Fixed a bug (#88) - An unexisting property was used for configuration of the Memcache cache driver.
@@ -269,7 +385,6 @@
 -  Fixed a bug (#1265) - Database connections were always closed, regardless of the 'pconnect' option value.
 -  Fixed a bug (#128) - :doc:`Language Library <libraries/language>` did not correctly keep track of loaded language files.
 -  Fixed a bug (#1242) - Added Windows path compatibility to function read_dir of ZIP library.
--  Fixed a bug (#1314) - sess_destroy() did not destroy userdata.
 -  Fixed a bug (#1349) - get_extension() in the :doc:`File Uploading Library <libraries/file_uploading>` returned the original filename when it didn't have an actual extension.
 -  Fixed a bug (#1273) - E_NOTICE being generated by :doc:`Query Builder <database/query_builder>`'s set_update_batch() method.
 -  Fixed a bug (#44, #110) - :doc:`Upload library <libraries/file_uploading>`'s clean_file_name() method didn't clear '!' and '#' characters.
@@ -283,14 +398,13 @@
 -  Fixed a bug (#666) - :doc:`Output library <libraries/output>`'s set_content_type() method didn't set the document charset.
 -  Fixed a bug (#784, #861) - :doc:`Database Forge <database/forge>` method ``create_table()`` used to accept constraints for MSSQL/SQLSRV integer-type columns.
 -  Fixed a bug (#706) - SQLSRV/MSSSQL didn't escape field names.
--  Fixed a bug (#1452) - protect_identifiers() didn't properly detect identifiers with spaces in their names.
--  Fixed a bug where protect_identifiers() ignored it's extra arguments when the value passed to it is an array.
--  Fixed a bug where _has_operator() didn't detect BETWEEN.
--  Fixed a bug in :doc:`Query Builder <database/query_builder>`'s join() method where it failed with identifiers containing dashes.
+-  Fixed a bug (#1452) - ``protect_identifiers()`` didn't properly detect identifiers with spaces in their names.
+-  Fixed a bug where ``protect_identifiers()`` ignored it's extra arguments when the value passed to it is an array.
+-  Fixed a bug where ``_has_operator()`` didn't detect BETWEEN.
+-  Fixed a bug in :doc:`Query Builder <database/query_builder>`'s ``join()`` method where it failed with identifiers containing dashes.
 -  Fixed a bug (#1264) - :doc:`Database Forge <database/forge>` and :doc:`Database Utilities <database/utilities>` didn't update/reset the databases and tables list cache when a table or a database is created, dropped or renamed.
--  Fixed a bug (#7) - :doc:`Query Builder <database/query_builder>`'s join() method only escaped one set of conditions.
+-  Fixed a bug (#7) - :doc:`Query Builder <database/query_builder>`'s ``join()`` method only escaped one set of conditions.
 -  Fixed a bug (#1321) - Core Exceptions class couldn't find the errors/ folder in some cases.
--  Fixed a bug in the File-based :doc:`Cache Library <libraries/caching>` driver's get_metadata() method where a non-existent array key was accessed for the TTL value.
 -  Fixed a bug (#1202) - :doc:`Encryption Library <libraries/encryption>` encode_from_legacy() didn't set back the encrypt mode on failure.
 -  Fixed a bug (#145) - compile_binds() failed when the bind marker was present in a literal string within the query.
 -  Fixed a bug in protect_identifiers() where if passed along with the field names, operators got escaped as well.
@@ -299,15 +413,83 @@
 -  Fixed a bug (#520) - :doc:`Date Helper <helpers/date_helper>` function nice_date() failed when the optional second parameter is not passed.
 -  Fixed a bug (#167) - ``$config['permitted_uri_chars']`` didn't affect URL-encoded characters.
 -  Fixed a bug (#318) - :doc:`Profiling <general/profiling>` setting *query_toggle_count* was not settable as described in the manual.
--  Fixed a bug (#938) - :doc:`Config Library <libraries/config>` method site_url() added a question mark to the URL string when query strings are enabled even if it already existed.
--  Fixed a bug (#999) - :doc:`Config Library <libraries/config>` method site_url() always appended ``$config['url_suffix']`` to the end of the URL string, regardless of wether a query string exists in it.
--  Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function anchor_popup() ignored the attributes argument if it is not an array.
+-  Fixed a bug (#938) - :doc:`Config Library <libraries/config>` method ``site_url()`` added a question mark to the URL string when query strings are enabled even if it already existed.
+-  Fixed a bug (#999) - :doc:`Config Library <libraries/config>` method ``site_url()`` always appended ``$config['url_suffix']`` to the end of the URL string, regardless of whether a query string exists in it.
+-  Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function ``anchor_popup()`` ignored the attributes argument if it is not an array.
 -  Fixed a bug (#1328) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly check the type of the form fields before processing them.
 -  Fixed a bug (#79) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly validate array fields that use associative keys or have custom indexes.
 -  Fixed a bug (#427) - :doc:`Form Validation Library <libraries/form_validation>` method ``strip_image_tags()`` was an alias to a non-existent method.
 -  Fixed a bug (#1545) - :doc:`Query Builder <database/query_builder>` method ``limit()`` wasn't executed properly under Oracle.
 -  Fixed a bug (#1551) - :doc:`Date Helper <helpers/date_helper>` function ``standard_date()`` didn't properly format *W3C* and *ATOM* standard dates.
 -  Fixed a bug in :doc:`Query Builder <database/query_builder>` method join() where literal values were escaped as if they were fields.
+-  Fixed a bug (#135) - PHP Error logging was impossible without the errors being displayed.
+-  Fixed a bug (#1613) - :doc:`Form Helper <helpers/form_helper>` functions ``form_multiselect()``, ``form_dropdown()`` didn't properly handle empty array option groups.
+-  Fixed a bug (#1605) - :doc:`Pagination Library <libraries/pagination>` produced incorrect *previous* and *next* link values.
+-  Fixed a bug in SQLSRV's ``affected_rows()`` method where an erroneous function name was used.
+-  Fixed a bug (#1000) - Change syntax of ``$view_file`` to ``$_ci_view_file`` to prevent being overwritten by application.
+-  Fixed a bug (#1757) - :doc:`Directory Helper <helpers/directory_helper>` function ``directory_map()`` was skipping files and directories named *0*.
+-  Fixed a bug (#1789) - :doc:`Database Library <database/index>` method ``escape_str()`` escaped quote characters in LIKE conditions twice under MySQL.
+-  Fixed a bug (#395) - :doc:`Unit Testing Library <libraries/unit_testing>` method ``result()`` didn't properly check array result columns when called from ``report()``.
+-  Fixed a bug (#1692) - :doc:`Database Library <database/index>` method ``display_error()`` didn't properly trace the possible error source on Windows systems.
+-  Fixed a bug (#1745) - ``is_write_type()`` method in the :doc:`Database Library <database/index>` didn't return TRUE for LOAD queries.
+-  Fixed a bug (#1765) - :doc:`Database Library <database/index>` didn't properly detect connection errors for MySQLi.
+-  Fixed a bug (#1257) - :doc:`Query Builder <database/query_builder>` used to (unnecessarily) group FROM clause contents, which breaks certain queries and is invalid for some databases.
+-  Fixed a bug (#1709) - :doc:`Email <libraries/email>` headers were broken when using long email subjects and \r\n as CRLF.
+-  Fixed a bug where ``MB_ENABLED`` was only declared if ``UTF8_ENABLED`` was set to TRUE.
+-  Fixed a bug where the :doc:`Session Library <libraries/sessions>` accepted cookies with *last_activity* values being in the future.
+-  Fixed a bug (#1897) - :doc:`Email Library <libraries/email>` triggered PHP E_WARNING errors when *mail* protocol used and ``to()`` is never called.
+-  Fixed a bug (#1409) - :doc:`Email Library <libraries/email>` didn't properly handle multibyte characters when applying Q-encoding to headers.
+-  Fixed a bug where :doc:`Email Library <libraries/email>` didn't honor it's *wordwrap* setting while handling alternative messages.
+-  Fixed a bug (#1476, #1909) - :doc:`Pagination Library <libraries/pagination>` didn't take into account actual routing when determining the current page.
+-  Fixed a bug (#1766) - :doc:`Query Builder <database/query_builder>` didn't always take into account the *dbprefix* setting.
+-  Fixed a bug (#779) - :doc:`URI Class <libraries/uri>` didn't always trim slashes from the *uri_string* as shown in the documentation.
+-  Fixed a bug (#134) - :doc:`Database Caching <database/caching>` method ``delete_cache()`` didn't work in some cases due to *cachedir* not being initialized properly.
+-  Fixed a bug (#191) - :doc:`Loader Library <libraries/loader>` ignored attempts for (re)loading databases to ``get_instance()->db`` even when the old database connection is dead.
+-  Fixed a bug (#1255) - :doc:`User Agent Library <libraries/user_agent>` method ``is_referral()`` only checked if ``$_SERVER['HTTP_REFERER']`` exists.
+-  Fixed a bug (#1146) - :doc:`Download Helper <helpers/download_helper>` function ``force_download()`` incorrectly sent *Cache-Control* directives *pre-check* and *post-check* to Internet Explorer.
+-  Fixed a bug (#1811) - :doc:`URI Library <libraries/uri>` didn't properly cache segments for ``uri_to_assoc()`` and ``ruri_to_assoc()``.
+-  Fixed a bug (#1506) - :doc:`Form Helpers <helpers/form_helper>` set empty *name* attributes.
+-  Fixed a bug (#59) - :doc:`Query Builder <database/query_builder>` method ``count_all_results()`` ignored the DISTINCT clause.
+-  Fixed a bug (#1624) - :doc:`Form Validation Library <libraries/form_validation>` rule **matches** didn't property handle array field names.
+-  Fixed a bug (#1630) - :doc:`Form Helper <helpers/form_helper>` function ``set_value()`` didn't escape HTML entities.
+-  Fixed a bug (#142) - :doc:`Form Helper <helpers/form_helper>` function ``form_dropdown()`` didn't escape HTML entities in option values.
+-  Fixed a bug (#50) - :doc:`Session Library <libraries/sessions>` unnecessarily stripped slashed from serialized data, making it impossible to read objects in a namespace.
+-  Fixed a bug (#658) - :doc:`Routing <general/routing>` wildcard **:any** didn't work as advertised and matched multiple URI segments instead of all characters within a single segment.
+-  Fixed a bug (#1938) - :doc:`Email Library <libraries/email>` removed multiple spaces inside a pre-formatted plain text message.
+-  Fixed a bug (#388, #705) - :doc:`URI Library <libraries/uri>` didn't apply URL-decoding to URI segments that it got from **REQUEST_URI** and/or **QUERY_STRING**.
+-  Fixed a bug (#122) - :doc:`URI Library <libraries/uri>` method ``ruri_string()`` didn't include a directory if one is used.
+-  Fixed a bug - :doc:`Routing Library <general/routing>` didn't properly handle *default_controller* in a subdirectory when a method is also specified.
+-  Fixed a bug (#953) - :doc:`post_controller_constructor hook <general/hooks>` wasn't called with a *404_override*.
+-  Fixed a bug (#1220) - :doc:`Profiler Library <general/profiling>` didn't display information for database objects that are instantiated inside models.
+-  Fixed a bug (#1978) - :doc:`Directory Helper <helpers/directory_helper>` function :php:func:`directory_map()`'s return array didn't make a distinction between directories and file indexes when a directory with a numeric name is present.
+-  Fixed a bug (#777) - :doc:`Loader Library <libraries/loader>` didn't look for helper extensions in added package paths.
+-  Fixed a bug (#18) - :doc:`APC Cache <libraries/caching>` driver didn't (un)serialize data, resulting in failure to store objects.
+-  Fixed a bug (#188) - :doc:`Unit Testing Library <libraries/unit_testing>` filled up logs with error messages for non-existing language keys.
+-  Fixed a bug (#113) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly handle empty fields that were specified as an array.
+-  Fixed a bug (#2061) - :doc:`Routing Class <general/routing>` didn't properly sanitize directory, controller and function triggers with **enable_query_strings** set to TRUE.
+
+Version 2.1.3
+=============
+
+Release Date: October 8, 2012
+
+-  Core
+   - :doc:`Common function <general/common_functions>` ``is_loaded()`` now returns a reference.
+
+Bug fixes for 2.1.3
+-------------------
+
+-  Fixed a bug (#1543) - File-based :doc:`Caching <libraries/caching>` method ``get_metadata()`` used a non-existent array key to look for the TTL value.
+-  Fixed a bug (#1314) - :doc:`Session Library <libraries/sessions>` method ``sess_destroy()`` didn't destroy the userdata array.
+-  Fixed a bug (#804) - :doc:`Profiler library <general/profiling>` was trying to handle objects as strings in some cases, resulting in *E_WARNING* messages being issued by ``htmlspecialchars()``.
+-  Fixed a bug (#1699) - :doc:`Migration Library <libraries/migration>` ignored the ``$config['migration_path']`` setting.
+-  Fixed a bug (#227) - :doc:`Input Library <libraries/input>` allowed unconditional spoofing of HTTP clients' IP addresses through the *HTTP_CLIENT_IP* header.
+-  Fixed a bug (#907) - :doc:`Input Library <libraries/input>` ignored *HTTP_X_CLUSTER_CLIENT_IP* and *HTTP_X_CLIENT_IP* headers when checking for proxies.
+-  Fixed a bug (#940) - ``csrf_verify()`` used to set the CSRF cookie while processing a POST request with no actual POST data, which resulted in validating a request that should be considered invalid.
+-  Fixed a bug (#499) - :doc:`Security Library <libraries/security>` where a CSRF cookie was created even if ``$config['csrf_protection']`` is set tot FALSE.
+-  Fixed a bug (#1715) - :doc:`Input Library <libraries/input>` triggered ``csrf_verify()`` on CLI requests.
+-  Fixed a bug (#751) - :doc:`Query Builder <database/query_builder>` didn't properly handle cached field escaping overrides.
+-  Fixed a bug (#2004) - :doc:`Query Builder <database/query_builder>` didn't properly merge cached calls with non-cache ones.
 
 Version 2.1.2
 =============
@@ -2237,7 +2419,7 @@
 -  Moved the list of "allowed URI characters" out of the Router class
    and into the config file.
 -  Moved the MIME type array out of the Upload class and into its own
-   file in the applications/config/ folder.
+   file in the application/config/ folder.
 -  Updated the Upload class to allow the upload field name to be set
    when calling :doc:`do_upload() <./libraries/file_uploading>`.
 -  Updated the :doc:`Config Library <./libraries/config>` to be able to
diff --git a/user_guide_src/source/conf.py b/user_guide_src/source/conf.py
index e972a38..f68405b 100644
--- a/user_guide_src/source/conf.py
+++ b/user_guide_src/source/conf.py
@@ -167,6 +167,7 @@
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'CodeIgniterdoc'
 
+html_copy_source = False
 
 # -- Options for LaTeX output --------------------------------------------------
 
diff --git a/user_guide_src/source/contributing/index.rst b/user_guide_src/source/contributing/index.rst
index 2ede0e6..0771a41 100644
--- a/user_guide_src/source/contributing/index.rst
+++ b/user_guide_src/source/contributing/index.rst
@@ -102,4 +102,4 @@
 
 By signing your work in this manner, you certify to a "Developer's Certificate 
 or Origin". The current version of this certificate is in the :doc:`/DCO` file
-in the root of this documentation.
+in the root of this documentation.
\ No newline at end of file
diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst
index c17de60..34cefff 100644
--- a/user_guide_src/source/database/configuration.rst
+++ b/user_guide_src/source/database/configuration.rst
@@ -28,6 +28,8 @@
 		'dbcollat' => 'utf8_general_ci',
 		'swap_pre' => '',
 		'autoinit' => TRUE,
+		'encrypt' => FALSE,
+		'compress' => FALSE,
 		'stricton' => FALSE,
 		'failover' => array()
 	);
@@ -69,6 +71,8 @@
 				'dbcollat' => 'utf8_general_ci',
 				'swap_pre' => '',
 				'autoinit' => TRUE,
+				'encrypt' => FALSE,
+				'compress' => FALSE,
 				'stricton' => FALSE
 			),
 			array(
@@ -86,6 +90,8 @@
 				'dbcollat' => 'utf8_general_ci',
 				'swap_pre' => '',
 				'autoinit' => TRUE,
+				'encrypt' => FALSE,
+				'compress' => FALSE,
 				'stricton' => FALSE
 			)
 		);
@@ -115,6 +121,8 @@
 		'dbcollat' => 'utf8_general_ci',
 		'swap_pre' => '',
 		'autoinit' => TRUE,
+		'compress' => FALSE,
+		'encrypt' => FALSE,
 		'stricton' => FALSE,
 		'failover' => array()
 	);
@@ -174,11 +182,14 @@
 			customizable by the end user.
 **autoinit**		Whether or not to automatically connect to the database when the library loads. If set to false,
 			the connection will take place prior to executing the first query.
+**schema**		The database schema, defaults to 'public'. Used by PostgreSQL and ODBC drivers.
+**encrypt**		Whether or not to use an encrypted connection.
+**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.
 **port**		The database port number. To use this value you have to add a line to the database config array.
 			::
-			
+
 				$db['default']['port'] = 5432;
 ======================  ==================================================================================================
 
diff --git a/user_guide_src/source/database/forge.rst b/user_guide_src/source/database/forge.rst
index bf17e29..ca904ed 100644
--- a/user_guide_src/source/database/forge.rst
+++ b/user_guide_src/source/database/forge.rst
@@ -2,7 +2,7 @@
 Database Forge Class
 ####################
 
-The Database Forge Class contains functions that help you manage your
+The Database Forge Class contains methods that help you manage your
 database.
 
 .. contents:: Table of Contents
@@ -18,13 +18,25 @@
 
 	$this->load->dbforge()
 
-Once initialized you will access the functions using the $this->dbforge
+You can also pass another database object to the DB Forge loader, in case
+the database you want to manage isn't the default one::
+
+	$this->myforge = $this->load->dbforge($this->other_db, TRUE);
+
+In the above example, we're passing a custom database object as the first
+parameter and then tell it to return the dbforge object, instead of
+assigning it directly to ``$this->dbforge``.
+
+.. note:: Both of the parameters can be used individually, just pass an empty
+	value as the first one if you wish to skip it.
+
+Once initialized you will access the methods using the ``$this->dbforge``
 object::
 
-	$this->dbforge->some_function()
+	$this->dbforge->some_method();
 
 $this->dbforge->create_database('db_name')
-============================================
+==========================================
 
 Permits you to create the database specified in the first parameter.
 Returns TRUE/FALSE based on success or failure::
@@ -108,13 +120,13 @@
 
 
 After the fields have been defined, they can be added using
-$this->dbforge->add_field($fields); followed by a call to the
-create_table() function.
+``$this->dbforge->add_field($fields);`` followed by a call to the
+``create_table()`` method.
 
 $this->dbforge->add_field()
-----------------------------
+---------------------------
 
-The add fields function will accept the above array.
+The add fields method will accept the above array.
 
 Passing strings as fields
 -------------------------
@@ -193,13 +205,15 @@
 Dropping a table
 ================
 
-Executes a DROP TABLE sql
+Execute a DROP TABLE statement and optionally add an IF EXISTS clause.
 
 ::
 
+	// Produces: DROP TABLE table_name
 	$this->dbforge->drop_table('table_name');
-	// gives DROP TABLE IF EXISTS  table_name
 
+	// Produces: DROP TABLE IF EXISTS table_name
+	$this->dbforge->drop_table('table_name');
 
 Renaming a table
 ================
@@ -217,9 +231,9 @@
 ****************
 
 $this->dbforge->add_column()
-=============================
+============================
 
-The add_column() function is used to modify an existing table. It
+The ``add_column()`` method is used to modify an existing table. It
 accepts the same field array as above, and can be used for an unlimited
 number of additional fields.
 
@@ -229,18 +243,25 @@
 		'preferences' => array('type' => 'TEXT')
 	);
 	$this->dbforge->add_column('table_name', $fields); 
-	// gives ALTER TABLE table_name ADD preferences TEXT
+	// Executes: ALTER TABLE table_name ADD preferences TEXT
 
-An optional third parameter can be used to specify which existing column
-to add the new column after.
+If you are using MySQL or CUBIRD, then you can take advantage of their
+AFTER and FIRST clauses to position the new column.
 
-::
+Examples::
 
-	$this->dbforge->add_column('table_name', $fields, 'after_field');
+	// Will place the new column after the `another_field` column:
+	$fields = array(
+		'preferences' => array('type' => 'TEXT', 'after' => 'another_field')
+	);
 
+	// Will place the new column at the start of the table definition:
+	$fields = array(
+		'preferences' => array('type' => 'TEXT', 'first' => TRUE)
+	);
 
 $this->dbforge->drop_column()
-==============================
+=============================
 
 Used to remove a column from a table.
 
@@ -250,9 +271,9 @@
 
 
 $this->dbforge->modify_column()
-================================
+===============================
 
-The usage of this function is identical to add_column(), except it
+The usage of this method is identical to ``add_column()``, except it
 alters an existing column rather than adding a new one. In order to
 change the name you can add a "name" key into the field defining array.
 
diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst
index b86a0c8..65609c1 100644
--- a/user_guide_src/source/database/query_builder.rst
+++ b/user_guide_src/source/database/query_builder.rst
@@ -345,23 +345,24 @@
 $this->db->like()
 =================
 
-This function enables you to generate **LIKE** clauses, useful for doing
+This method enables you to generate **LIKE** clauses, useful for doing
 searches.
 
-.. note:: All values passed to this function are escaped automatically.
+.. note:: All values passed to this method are escaped automatically.
 
 #. **Simple key/value method:**
 
 	::
 
-		$this->db->like('title', 'match');     // Produces: WHERE title LIKE '%match%'
+		$this->db->like('title', 'match');
+		// Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
 
-	If you use multiple function calls they will be chained together with
+	If you use multiple method calls they will be chained together with
 	AND between them::
 
 		$this->db->like('title', 'match');
 		$this->db->like('body', 'match');
-		// WHERE title LIKE '%match%' AND  body LIKE '%match%
+		// WHERE `title` LIKE '%match%' ESCAPE '!' AND  `body` LIKE '%match% ESCAPE '!'
 
 	If you want to control where the wildcard (%) is placed, you can use
 	an optional third argument. Your options are 'before', 'after' and
@@ -369,9 +370,9 @@
 
 	::
 
-		$this->db->like('title', 'match', 'before');	// Produces: WHERE title LIKE '%match'
-		$this->db->like('title', 'match', 'after');		// Produces: WHERE title LIKE 'match%'
-		$this->db->like('title', 'match', 'both');		// Produces: WHERE title LIKE '%match%'
+		$this->db->like('title', 'match', 'before');	// Produces: WHERE `title` LIKE '%match' ESCAPE '!'
+		$this->db->like('title', 'match', 'after');	// Produces: WHERE `title` LIKE 'match%' ESCAPE '!'
+		$this->db->like('title', 'match', 'both');	// Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
 
 #. **Associative array method:**
 
@@ -379,37 +380,37 @@
 
 		$array = array('title' => $match, 'page1' => $match, 'page2' => $match);
 		$this->db->like($array);
-		// WHERE title LIKE '%match%' AND  page1 LIKE '%match%' AND  page2 LIKE '%match%'
+		// WHERE `title` LIKE '%match%' ESCAPE '!' AND  `page1` LIKE '%match%' ESCAPE '!' AND  `page2` LIKE '%match%' ESCAPE '!'
 
 
 $this->db->or_like()
 ====================
 
-This function is identical to the one above, except that multiple
+This method is identical to the one above, except that multiple
 instances are joined by OR::
 
 	$this->db->like('title', 'match'); $this->db->or_like('body', $match);
-	// WHERE title LIKE '%match%' OR  body LIKE '%match%'
+	// WHERE `title` LIKE '%match%' ESCAPE '!' OR  `body` LIKE '%match%' ESCAPE '!'
 
-.. note:: or_like() was formerly known as orlike(), which has been removed.
+.. note:: ``or_like()`` was formerly known as ``orlike()``, which has been removed.
 
 $this->db->not_like()
 =====================
 
-This function is identical to **like()**, except that it generates NOT
-LIKE statements::
+This method is identical to ``like()``, except that it generates
+NOT LIKE statements::
 
-	$this->db->not_like('title', 'match');  // WHERE title NOT LIKE '%match%
+	$this->db->not_like('title', 'match');	// WHERE `title` NOT LIKE '%match% ESCAPE '!'
 
 $this->db->or_not_like()
 ========================
 
-This function is identical to **not_like()**, except that multiple
+This method is identical to ``not_like()``, except that multiple
 instances are joined by OR::
 
 	$this->db->like('title', 'match');
 	$this->db->or_not_like('body', 'match');
-	// WHERE title  LIKE '%match% OR body NOT LIKE '%match%'
+	// WHERE `title` LIKE '%match% OR  `body` NOT LIKE '%match%' ESCAPE '!'
 
 $this->db->group_by()
 =====================
@@ -469,31 +470,47 @@
 $this->db->order_by()
 =====================
 
-Lets you set an ORDER BY clause. The first parameter contains the name
-of the column you would like to order by. The second parameter lets you
-set the direction of the result. Options are asc or desc, or random.
+Lets you set an ORDER BY clause.
+
+The first parameter contains the name of the column you would like to order by.
+
+The second parameter lets you set the direction of the result.
+Options are **ASC**, **DESC** AND **RANDOM**.
 
 ::
 
-	$this->db->order_by("title", "desc");  // Produces: ORDER BY title DESC
+	$this->db->order_by('title', 'DESC');
+	// Produces: ORDER BY `title` DESC
 
 You can also pass your own string in the first parameter::
 
-	$this->db->order_by('title desc, name asc');  // Produces: ORDER BY title DESC, name ASC
+	$this->db->order_by('title DESC, name ASC');
+	// Produces: ORDER BY `title` DESC, `name` ASC
 
 Or multiple function calls can be made if you need multiple fields.
 
 ::
 
-	$this->db->order_by("title", "desc");
-	$this->db->order_by("name", "asc"); // Produces: ORDER BY title DESC, name ASC
+	$this->db->order_by('title', 'DESC');
+	$this->db->order_by('name', 'ASC');
+	// Produces: ORDER BY `title` DESC, `name` ASC
 
+If you choose the **RANDOM** direction option, then the first parameters will
+be ignored, unless you specify a numeric seed value.
+
+::
+
+	$this->db->order_by('title', 'RANDOM');
+	// Produces: ORDER BY RAND()
+
+	$this->db->order_by(42, 'RANDOM');
+	// Produces: ORDER BY RAND(42)
 
 .. note:: order_by() was formerly known as orderby(), which has been
 	removed.
 
-.. note:: random ordering is not currently supported in Oracle or MSSQL
-	drivers. These will default to 'ASC'.
+.. note:: Random ordering is not currently supported in Oracle and
+	will default to ASC instead.
 
 $this->db->limit()
 ==================
@@ -681,6 +698,35 @@
 
 .. note:: All values are escaped automatically producing safer queries.
 
+$this->db->replace()
+====================
+
+This method executes a REPLACE statement, which is basically the SQL
+standard for (optional) DELETE + INSERT, using *PRIMARY* and *UNIQUE*
+keys as the determining factor.
+In our case, it will save you from the need to implement complex
+logics with different combinations of  ``select()``, ``update()``,
+``delete()`` and ``insert()`` calls.
+
+Example::
+
+	$data = array(
+		'title' => 'My title',
+		'name'  => 'My Name',
+		'date'  => 'My date'
+	);
+
+	$this->db->replace('table', $data);
+
+	// Executes: REPLACE INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date')
+
+In the above example, if we assume that the *title* field is our primary
+key, then if a row containing 'My title' as the *title* value, that row
+will be deleted with our new row data replacing it.
+
+Usage of the ``set()`` method is also allowed and all fields are
+automatically escaped, just like with ``insert()``.
+
 $this->db->set()
 ================
 
@@ -740,7 +786,6 @@
 	$this->db->set($object);
 	$this->db->insert('mytable');
 
-
 *************
 Updating Data
 *************
@@ -792,6 +837,7 @@
 You may also use the $this->db->set() function described above when
 performing updates.
 
+
 $this->db->update_batch()
 =========================
 
@@ -830,6 +876,10 @@
 
 .. note:: All values are escaped automatically producing safer queries.
 
+.. note:: ``affected_rows()`` won't give you proper results with this method,
+	due to the very nature of how it works. Instead, ``update_batch()``
+	returns the number of rows affected.
+
 $this->db->get_compiled_update()
 ================================
 
@@ -1005,4 +1055,4 @@
 	$data = $this->db->get()->result_array();
 
 	// Would execute and return an array of results of the following query:
-	// SELECT field1, field1 from mytable where field3 = 5;
+	// SELECT field1, field1 from mytable where field3 = 5;
\ No newline at end of file
diff --git a/user_guide_src/source/database/results.rst b/user_guide_src/source/database/results.rst
index d032f73..e0a87a8 100644
--- a/user_guide_src/source/database/results.rst
+++ b/user_guide_src/source/database/results.rst
@@ -99,7 +99,7 @@
 	echo $row->reverse_name(); // or methods defined on the 'User' class
 
 row_array()
-============
+===========
 
 Identical to the above row() function, except it returns an array.
 Example::
@@ -138,12 +138,12 @@
 
 .. note:: all the functions above will load the whole result into memory (prefetching) use unbuffered_row() for processing large result sets.
 
-unbuffered_row($type)
-=====================
+unbuffered_row()
+================
 
-This function returns a single result row without prefetching the whole result in memory as row() does.
-If your query has more than one row, it returns the current row and moves the internal data pointer ahead. 
-The result is returned as $type could be 'object' (default) or 'array' that will return an associative array.
+This method returns a single result row without prefetching the whole
+result in memory as ``row()`` does. If your query has more than one row,
+it returns the current row and moves the internal data pointer ahead. 
 
 ::
 
@@ -156,12 +156,19 @@
 		echo $row->body;
 	}
 
+You can optionally pass 'object' (default) or 'array' in order to specify
+the returned value's type::
+
+	$query->unbuffered_row();		// object
+	$query->unbuffered_row('object');	// object
+	$query->unbuffered_row('array');	// associative array
+
 ***********************
 Result Helper Functions
 ***********************
 
 $query->num_rows()
-===================
+==================
 
 The number of rows returned by the query. Note: In this example, $query
 is the variable that the query result object is assigned to::
@@ -177,7 +184,7 @@
 	resulting array in order to achieve the same functionality.
 	
 $query->num_fields()
-=====================
+====================
 
 The number of FIELDS (columns) returned by the query. Make sure to call
 the function using your query result object::
@@ -187,7 +194,7 @@
 	echo $query->num_fields();
 
 $query->free_result()
-======================
+=====================
 
 It frees the memory associated with the result and deletes the result
 resource ID. Normally PHP frees its memory automatically at the end of
@@ -209,3 +216,21 @@
 	$row = $query2->row();
 	echo $row->name;
 	$query2->free_result(); // The $query2 result object will no longer be available
+
+data_seek()
+===========
+
+This method sets the internal pointer for the next result row to be
+fetched. It is only useful in combination with ``unbuffered_row()``.
+
+It accepts a positive integer value, which defaults to 0 and returns
+TRUE on success or FALSE on failure.
+
+::
+
+	$query = $this->db->query('SELECT `field_name` FROM `table_name`');
+	$query->data_seek(5); // Skip the first 5 rows
+	$row = $query->unbuffered_row();
+
+.. note:: Not all database drivers support this feature and will return FALSE.
+	Most notably - you won't be able to use it with PDO.
\ No newline at end of file
diff --git a/user_guide_src/source/database/utilities.rst b/user_guide_src/source/database/utilities.rst
index 4e83929..06ecb2d 100644
--- a/user_guide_src/source/database/utilities.rst
+++ b/user_guide_src/source/database/utilities.rst
@@ -2,7 +2,7 @@
 Database Utility Class
 ######################
 
-The Database Utility Class contains functions that help you manage your
+The Database Utility Class contains methods that help you manage your
 database.
 
 .. contents:: Table of Contents
@@ -22,12 +22,24 @@
 
 	$this->load->dbutil()
 
-Once initialized you will access the functions using the $this->dbutil
+You can also pass another database object to the DB Utility loader, in case
+the database you want to manage isn't the default one::
+
+	$this->myutil = $this->load->dbutil($this->other_db, TRUE);
+
+In the above example, we're passing a custom database object as the first
+parameter and then tell it to return the dbutil object, instead of
+assigning it directly to ``$this->dbutil``.
+
+.. note:: Both of the parameters can be used individually, just pass an empty
+	value as the first one if you wish to skip it.
+
+Once initialized you will access the methods using the ``$this->dbutil``
 object::
 
-	$this->dbutil->some_function()
+	$this->dbutil->some_method()
 
-$this->dbutil->list_databases()
+$this->dbutil->list_databases();
 ================================
 
 Returns an array of database names::
@@ -40,7 +52,7 @@
 	}
 
 $this->dbutil->database_exists();
-==================================
+=================================
 
 Sometimes it's helpful to know whether a particular database exists.
 Returns a boolean TRUE/FALSE. Usage example::
@@ -50,13 +62,11 @@
 		// some code...
 	}
 
-Note: Replace *database_name* with the name of the table you are
-looking for. This function is case sensitive.
+.. note:: Replace *database_name* with the name of the table you are
+	looking for. This method is case sensitive.
 
 $this->dbutil->optimize_table('table_name');
-==============================================
-
-.. note:: This features is only available for MySQL/MySQLi databases.
+============================================
 
 Permits you to optimize a table using the table name specified in the
 first parameter. Returns TRUE/FALSE based on success or failure::
@@ -66,12 +76,11 @@
 		echo 'Success!';
 	}
 
-.. note:: Not all database platforms support table optimization.
+.. note:: Not all database platforms support table optimization. It is
+	mostly for use with MySQL.
 
 $this->dbutil->repair_table('table_name');
-============================================
-
-.. note:: This features is only available for MySQL/MySQLi databases.
+==========================================
 
 Permits you to repair a table using the table name specified in the
 first parameter. Returns TRUE/FALSE based on success or failure::
@@ -86,8 +95,6 @@
 $this->dbutil->optimize_database();
 ====================================
 
-.. note:: This features is only available for MySQL/MySQLi databases.
-
 Permits you to optimize the database your DB class is currently
 connected to. Returns an array containing the DB status messages or
 FALSE on failure.
@@ -101,13 +108,14 @@
 		print_r($result);
 	}
 
-.. note:: Not all database platforms support table optimization.
+.. note:: Not all database platforms support table optimization. It
+	it is mostly for use with MySQL.
 
-$this->dbutil->csv_from_result($db_result)
-=============================================
+$this->dbutil->csv_from_result($db_result);
+===========================================
 
 Permits you to generate a CSV file from a query result. The first
-parameter of the function must contain the result object from your
+parameter of the method must contain the result object from your
 query. Example::
 
 	$this->load->dbutil();
@@ -127,12 +135,12 @@
 
 	echo $this->dbutil->csv_from_result($query, $delimiter, $newline, $enclosure);
 
-.. important:: This function will NOT write the CSV file for you. It
+.. important:: This method will NOT write the CSV file for you. It
 	simply creates the CSV layout. If you need to write the file
 	use the :doc:`File Helper <../helpers/file_helper>`.
 
-$this->dbutil->xml_from_result($db_result)
-=============================================
+$this->dbutil->xml_from_result($db_result);
+===========================================
 
 Permits you to generate an XML file from a query result. The first
 parameter expects a query result object, the second may contain an
@@ -151,17 +159,17 @@
 	
 	echo $this->dbutil->xml_from_result($query, $config);
 
-.. important:: This function will NOT write the XML file for you. It
+.. important:: This method will NOT write the XML file for you. It
 	simply creates the XML layout. If you need to write the file
 	use the :doc:`File Helper <../helpers/file_helper>`.
 
-$this->dbutil->backup()
-=======================
+$this->dbutil->backup();
+========================
 
 Permits you to backup your full database or individual tables. The
 backup data can be compressed in either Zip or Gzip format.
 
-.. note:: This features is only available for MySQL and Interbase/Firebird databases.
+.. note:: This feature is only available for MySQL and Interbase/Firebird databases.
 
 .. note:: For Interbase/Firebird databases, the backup file name is the only parameter.
 	
@@ -196,16 +204,16 @@
 --------------------------
 
 Backup preferences are set by submitting an array of values to the first
-parameter of the backup function. Example::
+parameter of the ``backup()`` method. Example::
 
 	$prefs = array(
-		'tables'		=> array('table1', 'table2'),	// Array of tables to backup.
-		'ignore'		=> array(),						// List of tables to omit from the backup
-		'format'		=> 'txt',						// gzip, zip, txt
-		'filename'		=> 'mybackup.sql',				// File name - NEEDED ONLY WITH ZIP FILES
-		'add_drop'		=> TRUE,						// Whether to add DROP TABLE statements to backup file
-		'add_insert'	=> TRUE,						// Whether to add INSERT data to backup file
-		'newline'		=> "\n"							// Newline character used in backup file
+		'tables'	=> array('table1', 'table2'),	// Array of tables to backup.
+		'ignore'	=> array(),			// List of tables to omit from the backup
+		'format'	=> 'txt',			// gzip, zip, txt
+		'filename'	=> 'mybackup.sql',		// File name - NEEDED ONLY WITH ZIP FILES
+		'add_drop'	=> TRUE,			// Whether to add DROP TABLE statements to backup file
+		'add_insert'	=> TRUE,			// Whether to add INSERT data to backup file
+		'newline'	=> "\n"				// Newline character used in backup file
 	);
 	
 	$this->dbutil->backup($prefs);
diff --git a/user_guide_src/source/general/alternative_php.rst b/user_guide_src/source/general/alternative_php.rst
index 4dc857a..418d2e6 100644
--- a/user_guide_src/source/general/alternative_php.rst
+++ b/user_guide_src/source/general/alternative_php.rst
@@ -17,12 +17,12 @@
 	work on your server it might be that "short tags" are disabled in your
 	PHP ini file. CodeIgniter will optionally rewrite short tags on-the-fly,
 	allowing you to use that syntax even if your server doesn't support it.
-	This feature can be enabled in your config/config.php file.
+	This feature can be enabled in your *config/config.php* file.
 
 Please note that if you do use this feature, if PHP errors are
 encountered in your **view files**, the error message and line number
 will not be accurately shown. Instead, all errors will be shown as
-eval() errors.
+``eval()`` errors.
 
 Alternative Echos
 =================
@@ -39,13 +39,13 @@
 ==============================
 
 Controls structures, like if, for, foreach, and while can be written in
-a simplified format as well. Here is an example using foreach::
+a simplified format as well. Here is an example using ``foreach``::
 
 	<ul>
 
 	<?php foreach ($todo as $item): ?>
 
-	<li><?=$item?></li>
+		<li><?=$item?></li>
 
 	<?php endforeach; ?>
 
@@ -60,17 +60,16 @@
 
 Here is another example, using ``if``/``elseif``/``else``. Notice the colons::
 
-	<?php if ($username == 'sally'): ?>
+	<?php if ($username === 'sally'): ?>
 
-	   <h3>Hi Sally</h3>
+		<h3>Hi Sally</h3>
 
-	<?php elseif ($username == 'joe'): ?>
+	<?php elseif ($username === 'joe'): ?>
 
-	   <h3>Hi Joe</h3>
+		<h3>Hi Joe</h3>
 
 	<?php else: ?>
 
-	   <h3>Hi unknown user</h3>
+		<h3>Hi unknown user</h3>
 
-	<?php endif; ?>
-
+	<?php endif; ?>
\ No newline at end of file
diff --git a/user_guide_src/source/general/ancillary_classes.rst b/user_guide_src/source/general/ancillary_classes.rst
index f7c8701..a4befc7 100644
--- a/user_guide_src/source/general/ancillary_classes.rst
+++ b/user_guide_src/source/general/ancillary_classes.rst
@@ -7,31 +7,35 @@
 resources. This is easily possible as you'll see.
 
 get_instance()
-===============
+==============
 
-**Any class that you instantiate within your controller functions can
+.. php:function:: get_instance()
+
+	:returns:	object of class CI_Controller
+
+**Any class that you instantiate within your controller methods can
 access CodeIgniter's native resources** simply by using the
-get_instance() function. This function returns the main CodeIgniter
-object.
+``get_instance()`` function. This function returns the main
+CodeIgniter object.
 
-Normally, to call any of the available CodeIgniter functions requires
-you to use the $this construct::
+Normally, to call any of the available CodeIgniter methods requires
+you to use the ``$this`` construct::
 
 	$this->load->helper('url');
 	$this->load->library('session');
 	$this->config->item('base_url');
 	// etc.
 
-$this, however, only works within your controllers, your models, or your
-views. If you would like to use CodeIgniter's classes from within your
-own custom classes you can do so as follows:
+``$this``, however, only works within your controllers, your models,
+or your views. If you would like to use CodeIgniter's classes from
+within your own custom classes you can do so as follows:
 
 First, assign the CodeIgniter object to a variable::
 
 	$CI =& get_instance();
 
 Once you've assigned the object to a variable, you'll use that variable
-*instead* of $this::
+*instead* of ``$this``::
 
 	$CI =& get_instance();
 
@@ -40,10 +44,45 @@
 	$CI->config->item('base_url');
 	// etc.
 
-.. note:: You'll notice that the above get_instance() function is being
+.. note:: You'll notice that the above get_instance() ``function`` is being
 	passed by reference::
 
 		$CI =& get_instance();
 	
 	This is very important. Assigning by reference allows you to use the
 	original CodeIgniter object rather than creating a copy of it.
+
+Furthermore, if you'll be using ``get_intance()`` inside anoter class,
+then it would be better if you assign it to a property. This way, you
+won't need to call ``get_instance()`` in every single method.
+
+Example::
+
+class Example {
+
+	protected $CI;
+
+	// We'll use a constructor, as you can't directly call a function
+	// from a property definition.
+	public function __construct()
+	{
+		// Assign the CodeIgniter super-object
+		$this->CI =& get_instance();
+	}
+
+	public function foo()
+	{
+		$this->CI->load->helper('url');
+		redirect();
+	}
+
+	public function bar()
+	{
+		$this->CI->config_item('base_url');
+	}
+
+}
+
+In the above example, both methods ``foo()`` and ``bar()`` will work
+after you instantiate the Example class, without the need to call
+``get_instance()`` in each of them.
\ No newline at end of file
diff --git a/user_guide_src/source/general/autoloader.rst b/user_guide_src/source/general/autoloader.rst
index 259a498..e5cec72 100644
--- a/user_guide_src/source/general/autoloader.rst
+++ b/user_guide_src/source/general/autoloader.rst
@@ -9,15 +9,15 @@
 
 The following items can be loaded automatically:
 
--  Core classes found in the "libraries" folder
--  Helper files found in the "helpers" folder
--  Custom config files found in the "config" folder
--  Language files found in the "system/language" folder
--  Models found in the "models" folder
+-  Classes found in the *libraries/* directory
+-  Helper files found in the *helpers/* directory
+-  Custom config files found in the *config/* directory
+-  Language files found in the *system/language/* directory
+-  Models found in the *models/* folder
 
-To autoload resources, open the application/config/autoload.php file and
-add the item you want loaded to the autoload array. You'll find
-instructions in that file corresponding to each type of item.
+To autoload resources, open the *application/config/autoload.php*
+file and add the item you want loaded to the autoload array. You'll
+find instructions in that file corresponding to each type of item.
 
 .. note:: Do not include the file extension (.php) when adding items to
-	the autoload array.
+	the autoload array.
\ No newline at end of file
diff --git a/user_guide_src/source/general/caching.rst b/user_guide_src/source/general/caching.rst
index bf6ed50..48385d6 100644
--- a/user_guide_src/source/general/caching.rst
+++ b/user_guide_src/source/general/caching.rst
@@ -23,36 +23,46 @@
 expired, it will be deleted and refreshed before being sent to the
 browser.
 
-Note: The Benchmark tag is not cached so you can still view your page
-load speed when caching is enabled.
+.. note: The Benchmark tag is not cached so you can still view your page
+	load speed when caching is enabled.
 
 Enabling Caching
 ================
 
 To enable caching, put the following tag in any of your controller
-functions::
+methods::
 
-	$this->output->cache(n);
+	$this->output->cache($n);
 
-Where n is the number of **minutes** you wish the page to remain cached
-between refreshes.
+Where ``$n`` is the number of **minutes** you wish the page to remain
+cached between refreshes.
 
-The above tag can go anywhere within a function. It is not affected by
+The above tag can go anywhere within a method. It is not affected by
 the order that it appears, so place it wherever it seems most logical to
 you. Once the tag is in place, your pages will begin being cached.
 
 .. important:: Because of the way CodeIgniter stores content for output,
-	caching will only work if you are generating display for your controller
-	with a :doc:`view <./views>`.
+	caching will only work if you are generating display for your
+	controller with a :doc:`view <./views>`.
 
 .. note:: Before the cache files can be written you must set the file
-	permissions on your application/cache folder such that it is writable.
+	permissions on your *application/cache/* directory such that
+	it is writable.
 
 Deleting Caches
 ===============
 
 If you no longer wish to cache a file you can remove the caching tag and
-it will no longer be refreshed when it expires. Note: Removing the tag
-will not delete the cache immediately. It will have to expire normally.
-If you need to remove it earlier you will need to manually delete it
-from your cache folder.
+it will no longer be refreshed when it expires.
+
+.. note:: Removing the tag will not delete the cache immediately. It will
+	have to expire normally.
+
+If you need to manually delete the cache, you can use the ``delete_cache()``
+method::
+
+	// Deletes cache for the currently requested URI
+	$this->output->delete_cache();
+
+	// Deletes cache for /foo/bar
+	$this->output->delete_cache('/foo/bar');
\ No newline at end of file
diff --git a/user_guide_src/source/general/cli.rst b/user_guide_src/source/general/cli.rst
index 7dc1ca3..998d2a9 100644
--- a/user_guide_src/source/general/cli.rst
+++ b/user_guide_src/source/general/cli.rst
@@ -21,7 +21,7 @@
 There are many reasons for running CodeIgniter from the command-line,
 but they are not always obvious.
 
--  Run your cron-jobs without needing to use wget or curl
+-  Run your cron-jobs without needing to use *wget* or *curl*
 -  Make your cron-jobs inaccessible from being loaded in the URL by
    checking for ``$this->input->is_cli_request()``
 -  Make interactive "tasks" that can do things like set permissions,
@@ -44,15 +44,14 @@
 			echo "Hello {$to}!".PHP_EOL;
 		}
 	}
-	?>
 
-Then save the file to your application/controllers/ folder.
+Then save the file to your *application/controllers/* folder.
 
 Now normally you would visit the your site using a URL similar to this::
 
 	example.com/index.php/tools/message/to
 
-Instead, we are going to open Terminal in Mac/Lunix or go to Run > "cmd"
+Instead, we are going to open Terminal in Mac/Linux or go to Run > "cmd"
 in Windows and navigate to our CodeIgniter project.
 
 .. code-block:: bash
@@ -60,19 +59,20 @@
 	$ cd /path/to/project;
 	$ php index.php tools message
 
-If you did it right, you should see Hello World!.
+If you did it right, you should see *Hello World!* printed.
 
 .. code-block:: bash
 
 	$ php index.php tools message "John Smith"
 
 Here we are passing it a argument in the same way that URL parameters
-work. "John Smith" is passed as a argument and output is: Hello John
-Smith!.
+work. "John Smith" is passed as a argument and output is::
+
+	Hello John Smith!
 
 That's it!
 ==========
 
 That, in a nutshell, is all there is to know about controllers on the
 command line. Remember that this is just a normal controller, so routing
-and _remap works fine.
+and ``_remap()`` works fine.
\ No newline at end of file
diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst
index 99126f9..7917d32 100644
--- a/user_guide_src/source/general/common_functions.rst
+++ b/user_guide_src/source/general/common_functions.rst
@@ -6,58 +6,118 @@
 defined, and are available to you at any point. These do not require
 loading any libraries or helpers.
 
-is_php('version_number')
-==========================
+is_php()
+========
 
-is_php() determines of the PHP version being used is greater than the
-supplied version_number.
+.. php:function:: is_php($version = '5.3.0')
 
-::
+	:param	string	$version: Version number
+	:returns:	bool
 
-	if (is_php('5.3.0'))
+Determines of the PHP version being used is greater than the
+supplied version number.
+
+Example::
+
+	if (is_php('5.3'))
 	{
-	    $str = quoted_printable_encode($str);
+		$str = quoted_printable_encode($str);
 	}
 
 Returns boolean TRUE if the installed version of PHP is equal to or
 greater than the supplied version number. Returns FALSE if the installed
 version of PHP is lower than the supplied version number.
 
-is_really_writable('path/to/file')
-====================================
+is_really_writable()
+====================
 
-is_writable() returns TRUE on Windows servers when you really can't
+.. php:function:: is_really_writeable($file)
+
+	:param	string	$file: File path
+	:returns:	bool
+
+``is_writable()`` returns TRUE on Windows servers when you really can't
 write to the file as the OS reports to PHP as FALSE only if the
-read-only attribute is marked. This function determines if a file is
-actually writable by attempting to write to it first. Generally only
-recommended on platforms where this information may be unreliable.
+read-only attribute is marked.
 
-::
+This function determines if a file is actually writable by attempting
+to write to it first. Generally only recommended on platforms where
+this information may be unreliable.
+
+Example::
 
 	if (is_really_writable('file.txt'))
 	{
-	    echo "I could write to this if I wanted to";
+		echo "I could write to this if I wanted to";
 	}
 	else
 	{
-	    echo "File is not writable";
+		echo "File is not writable";
 	}
 
-config_item('item_key')
-=========================
+config_item()
+=============
 
-The :doc:`Config library <../libraries/config>` is the preferred way of
-accessing configuration information, however config_item() can be used
-to retrieve single keys. See Config library documentation for more
-information.
+.. php:function:: config_item($key)
 
-show_error('message'), show_404('page'), log_message('level', 'message')
-========================================================================
+	:param	string	$key: Config item key
+	:returns:	mixed
 
-These are each outlined on the :doc:`Error Handling <errors>` page.
+The :doc:`Config Library <../libraries/config>` is the preferred way of
+accessing configuration information, however ``config_item()`` can be used
+to retrieve single keys. See :doc:`Config Library <../libraries/config>`
+documentation for more information.
 
-set_status_header(code, 'text');
-================================
+.. important:: This function only returns values set in your configuration
+	files. It does not take into account config values that are
+	dynamically set at runtime.
+
+show_error()
+============
+
+.. php:function:: show_error($message, $status_code, $heading = 'An Error Was Encountered')
+
+	:param	mixed	$message: Error message
+	:param	int	$status_code: HTTP Response status code
+	:param	string	$heading: Error page heading
+	:returns:	void
+
+This function calls ``CI_Exception::show_error()``. For more info,
+please see the :doc:`Error Handling <errors>` documentation.
+
+show_404()
+==========
+
+.. php:function:: show_404($page = '', $log_error = TRUE)
+
+	:param	string	$page: URI string
+	:param	bool	$log_error: Whether to log the error
+	:returns:	void
+
+This function calls ``CI_Exception::show_404()``. For more info,
+please see the :doc:`Error Handling <errors>` documentation.
+
+log_message()
+=============
+
+.. php:function:: log_message($level = 'error', $message, $php_error = FALSE)
+
+	:param	string	$level: Log level
+	:param	string	$message: Message to log
+	:param	bool	$php_error: Whether we're loggin a native PHP error message
+	:returns:	void
+
+This function is an alias for ``CI_Log::write_log()``. For more info,
+please see the :doc:`Error Handling <errors>` documentation.
+
+set_status_header()
+===============================
+
+.. php:function:: set_status_header($code, $text = '')
+
+	:param	int	$code: HTTP Reponse status code
+	:param	string	$text: A custom message to set with the status code
+	:returns:	void
 
 Permits you to manually set a server status header. Example::
 
@@ -67,20 +127,71 @@
 `See here <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>`_ for
 a full list of headers.
 
-remove_invisible_characters($str)
-===================================
+remove_invisible_characters()
+=============================
 
-This function prevents inserting null characters between ascii
+.. php:function:: remove_invisible_characters($str, $url_encoded = TRUE)
+
+	:param	string	$str: Input string
+	:param	bool	$url_encoded: Whether to remove URL-encoded characters as well
+	:returns:	string
+
+This function prevents inserting NULL characters between ASCII
 characters, like Java\\0script.
 
-html_escape($mixed)
-====================
+Example::
 
-This function provides short cut for htmlspecialchars() function. It
-accepts string and array. To prevent Cross Site Scripting (XSS), it is
-very useful.
+	remove_invisible_characters('Java\\0script');
+	// Returns: 'Javascript'
 
-get_mimes()
+html_escape()
 =============
 
-This function returns the MIMEs array from config/mimes.php.
\ No newline at end of file
+.. php:function:: html_escape($var)
+
+	:param	mixed	$var: Variable to escape
+			(string or array)
+	:returns:	mixed
+
+This function acts as an alias for PHP's native ``htmlspecialchars()``
+function, with the advantage of being able to accept an array of strings.
+
+It is useful in preventing Cross Site Scripting (XSS).
+
+get_mimes()
+===========
+
+.. php:function:: get_mimes()
+
+	:returns:	array
+
+This function returns a *reference* to the MIMEs array from
+*application/config/mimes.php*.
+
+is_https()
+==========
+
+.. php:function:: is_https()
+
+	:returns:	bool
+
+Returns TRUE if a secure (HTTPS) connection is used and FALSE
+in any other case (including non-HTTP requests).
+
+function_usable()
+=================
+
+.. php:function:: function_usable($function_name)
+
+	:param	string	$function_name: Function name
+	:returns:	bool
+
+Returns TRUE if a function exists and is usable, FALSE otherwise.
+
+This function runs a ``function_exists()`` check and if the
+`Suhosin extension <http://www.hardened-php.net/suhosin/>` is loaded,
+checks if it doesn't disable the function being checked.
+
+It is useful if you want to check for the availability of functions
+such as ``eval()`` and ``exec()``, which are dangerous and might be
+disabled on servers with highly restrictive security policies.
\ No newline at end of file
diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst
index 6e50794..729b084 100644
--- a/user_guide_src/source/general/controllers.rst
+++ b/user_guide_src/source/general/controllers.rst
@@ -38,50 +38,49 @@
 			echo 'Hello World!';
 		}
 	}
-	?>
 
-Then save the file to your application/controllers/ folder.
+Then save the file to your *application/controllers/* directory.
 
 Now visit the your site using a URL similar to this::
 
 	example.com/index.php/blog/
 
-If you did it right, you should see Hello World!.
+If you did it right, you should see:
 
-Note: Class names must start with an uppercase letter. In other words,
-this is valid::
+	Hello World!
+
+.. important:: Class names must start with an uppercase letter.
+
+This is valid::
 
 	<?php
 	class Blog extends CI_Controller {
 
 	}
-	?>
 	
-
 This is **not** valid::
 
 	<?php
 	class blog extends CI_Controller {
 
 	}
-	?>
 
 Also, always make sure your controller extends the parent controller
-class so that it can inherit all its functions.
+class so that it can inherit all its methods.
 
-Functions
-=========
+Methods
+=======
 
-In the above example the function name is index(). The "index" function
+In the above example the method name is ``index()``. The "index" method
 is always loaded by default if the **second segment** of the URI is
 empty. Another way to show your "Hello World" message would be this::
 
 	example.com/index.php/blog/index/
 
-**The second segment of the URI determines which function in the
+**The second segment of the URI determines which method in the
 controller gets called.**
 
-Let's try it. Add a new function to your controller::
+Let's try it. Add a new method to your controller::
 
 	<?php
 	class Blog extends CI_Controller {
@@ -96,39 +95,37 @@
 			echo 'Look at this!';
 		}
 	}
-	?>
 
-Now load the following URL to see the comment function::
+Now load the following URL to see the comment method::
 
 	example.com/index.php/blog/comments/
 
 You should see your new message.
 
-Passing URI Segments to your Functions
-======================================
+Passing URI Segments to your methods
+====================================
 
 If your URI contains more then two segments they will be passed to your
-function as parameters.
+method as parameters.
 
 For example, lets say you have a URI like this::
 
 	example.com/index.php/products/shoes/sandals/123
 
-Your function will be passed URI segments 3 and 4 ("sandals" and "123")::
+Your method will be passed URI segments 3 and 4 ("sandals" and "123")::
 
 	<?php
 	class Products extends CI_Controller {
 
-	    public function shoes($sandals, $id)
-	    {
-	        echo $sandals;
-	        echo $id;
-	    }
+		public function shoes($sandals, $id)
+		{
+			echo $sandals;
+			echo $id;
+		}
 	}
-	?>
 
 .. important:: If you are using the :doc:`URI Routing <routing>`
-	feature, the segments passed to your function will be the re-routed
+	feature, the segments passed to your method will be the re-routed
 	ones.
 
 Defining a Default Controller
@@ -139,59 +136,59 @@
 To specify a default controller, open your **application/config/routes.php**
 file and set this variable::
 
-	$route['default_controller'] = 'Blog';
+	$route['default_controller'] = 'blog';
 
 Where Blog is the name of the controller class you want used. If you now
 load your main index.php file without specifying any URI segments you'll
 see your Hello World message by default.
 
-Remapping Function Calls
-========================
+Remapping Method Calls
+======================
 
 As noted above, the second segment of the URI typically determines which
-function in the controller gets called. CodeIgniter permits you to
-override this behavior through the use of the _remap() function::
+method in the controller gets called. CodeIgniter permits you to override
+this behavior through the use of the ``_remap()`` method::
 
 	public function _remap()
 	{
-	    // Some code here...
+		// Some code here...
 	}
 
-.. important:: If your controller contains a function named _remap(),
+.. important:: If your controller contains a method named _remap(),
 	it will **always** get called regardless of what your URI contains. It
-	overrides the normal behavior in which the URI determines which function
-	is called, allowing you to define your own function routing rules.
+	overrides the normal behavior in which the URI determines which method
+	is called, allowing you to define your own method routing rules.
 
-The overridden function call (typically the second segment of the URI)
-will be passed as a parameter to the _remap() function::
+The overridden method call (typically the second segment of the URI) will
+be passed as a parameter to the ``_remap()`` method::
 
 	public function _remap($method)
 	{
-	    if ($method == 'some_method')
-	    {
-	        $this->$method();
-	    }
-	    else
-	    {
-	        $this->default_method();
-	    }
+		if ($method === 'some_method')
+		{
+			$this->$method();
+		}
+		else
+		{
+			$this->default_method();
+		}
 	}
 
-Any extra segments after the method name are passed into _remap() as an
+Any extra segments after the method name are passed into ``_remap()`` as an
 optional second parameter. This array can be used in combination with
-PHP's `call_user_func_array <http://php.net/call_user_func_array>`_
+PHP's `call_user_func_array() <http://php.net/call_user_func_array>`_
 to emulate CodeIgniter's default behavior.
 
-::
+Example::
 
 	public function _remap($method, $params = array())
 	{
-	    $method = 'process_'.$method;
-	    if (method_exists($this, $method))
-	    {
-	        return call_user_func_array(array($this, $method), $params);
-	    }
-	    show_404();
+		$method = 'process_'.$method;
+		if (method_exists($this, $method))
+		{
+			return call_user_func_array(array($this, $method), $params);
+		}
+		show_404();
 	}
 
 Processing Output
@@ -199,69 +196,75 @@
 
 CodeIgniter has an output class that takes care of sending your final
 rendered data to the web browser automatically. More information on this
-can be found in the :doc:`Views <views>` and :doc:`Output class <../libraries/output>` pages. In some cases, however, you
-might want to post-process the finalized data in some way and send it to
-the browser yourself. CodeIgniter permits you to add a function named
-_output() to your controller that will receive the finalized output
-data.
+can be found in the :doc:`Views <views>` and :doc:`Output Class
+<../libraries/output>` pages. In some cases, however, you might want to
+post-process the finalized data in some way and send it to the browser
+yourself. CodeIgniter permits you to add a method named ``_output()``
+to your controller that will receive the finalized output data.
 
-.. important:: If your controller contains a function named _output(),
-	it will **always** be called by the output class instead of echoing the
-	finalized data directly. The first parameter of the function will
-	contain the finalized output.
+.. important:: If your controller contains a method named ``_output()``,
+	it will **always** be called by the output class instead of
+	echoing the finalized data directly. The first parameter of the
+	method will contain the finalized output.
 
 Here is an example::
 
 	public function _output($output)
 	{
-	    echo $output;
+		echo $output;
 	}
 
-.. note:: Please note that your _output() function will receive the data in its
-	finalized state. Benchmark and memory usage data will be rendered, cache
-	files written (if you have caching enabled), and headers will be sent
-	(if you use that :doc:`feature <../libraries/output>`) before it is
-	handed off to the _output() function.
-	To have your controller's output cached properly, its _output() method
-	can use::
+.. note:: Please note that your ``_output()`` method will receive the
+	data in its finalized state. Benchmark and memory usage data
+	will be rendered, cache files written (if you have caching
+	enabled), and headers will be sent (if you use that
+	:doc:`feature <../libraries/output>`) before it is handed off
+	to the ``_output()`` method.
+	To have your controller's output cached properly, its
+	``_output()`` method can use::
 
 		if ($this->output->cache_expiration > 0)
 		{
-		    $this->output->_write_cache($output);
+			$this->output->_write_cache($output);
 		}
 
-	If you are using this feature the page execution timer and memory usage
-	stats might not be perfectly accurate since they will not take into
-	acccount any further processing you do. For an alternate way to control
-	output *before* any of the final processing is done, please see the
-	available methods in the :doc:`Output Class <../libraries/output>`.
+	If you are using this feature the page execution timer and
+	memory usage stats might not be perfectly accurate since they
+	will not take into account any further processing you do.
+	For an alternate way to control output *before* any of the
+	final processing is done, please see the available methods
+	in the :doc:`Output Library <../libraries/output>`.
 
-Private Functions
-=================
+Private methods
+===============
 
-In some cases you may want certain functions hidden from public access.
-To make a function private, simply add an underscore as the name prefix
-and it will not be served via a URL request. For example, if you were to
-have a function like this::
+In some cases you may want certain methods hidden from public access.
+In order to achieve this, simply declare the method as being private
+or protected and it will not be served via a URL request. For example,
+if you were to have a method like this::
 
 	private function _utility()
 	{
-	  // some code
+		// some code
 	}
 
 Trying to access it via the URL, like this, will not work::
 
 	example.com/index.php/blog/_utility/
 
-Organizing Your Controllers into Sub-folders
-============================================
+.. note:: Prefixing method names with an underscore will also prevent
+	them from being called. This is a legacy feature that is left
+	for backwards-compatibility.
+
+Organizing Your Controllers into Sub-directories
+================================================
 
 If you are building a large application you might find it convenient to
-organize your controllers into sub-folders. CodeIgniter permits you to
-do this.
+organize your controllers into sub-directories. CodeIgniter permits you
+to do this.
 
-Simply create folders within your application/controllers directory and
-place your controller classes within them.
+Simply create folders within your *application/controllers/* directory
+and place your controller classes within them.
 
 .. note:: When using this feature the first segment of your URI must
 	specify the folder. For example, lets say you have a controller located
@@ -273,9 +276,9 @@
 
 		example.com/index.php/products/shoes/show/123
 
-Each of your sub-folders may contain a default controller which will be
+Each of your sub-directories may contain a default controller which will be
 called if the URL contains only the sub-folder. Simply name your default
-controller as specified in your application/config/routes.php file
+controller as specified in your *application/config/routes.php* file.
 
 CodeIgniter also permits you to remap your URIs using its :doc:`URI
 Routing <routing>` feature.
@@ -292,33 +295,38 @@
 be overriding the one in the parent controller class so we need to
 manually call it.
 
-::
+Example::
 
 	<?php
 	class Blog extends CI_Controller {
 
-	       public function __construct()
-	       {
-	            parent::__construct();
-	            // Your own constructor code
-	       }
+		public function __construct()
+		{
+			parent::__construct();
+			// Your own constructor code
+		}
 	}
-	?>
 
 Constructors are useful if you need to set some default values, or run a
 default process when your class is instantiated. Constructors can't
 return a value, but they can do some default work.
 
-Reserved Function Names
-=======================
+Reserved method names
+=====================
 
 Since your controller classes will extend the main application
-controller you must be careful not to name your functions identically to
+controller you must be careful not to name your methods identically to
 the ones used by that class, otherwise your local functions will
 override them. See :doc:`Reserved Names <reserved_names>` for a full
 list.
 
+.. important:: You should also never have a method named identically
+	to its class name. If you do, and there is no ``__construct()``
+	method in the same class, then your e.g. ``Index::index()``
+	method will be executed as a class constructor! This is a PHP4
+	backwards-compatibility feature.
+
 That's it!
 ==========
 
-That, in a nutshell, is all there is to know about controllers.
+That, in a nutshell, is all there is to know about controllers.
\ No newline at end of file
diff --git a/user_guide_src/source/general/core_classes.rst b/user_guide_src/source/general/core_classes.rst
index 4aa6693..ce57aee 100644
--- a/user_guide_src/source/general/core_classes.rst
+++ b/user_guide_src/source/general/core_classes.rst
@@ -39,9 +39,9 @@
 ======================
 
 To use one of your own system classes instead of a default one simply
-place your version inside your local application/core directory::
+place your version inside your local *application/core/* directory::
 
-	application/core/some-class.php
+	application/core/some_class.php
 
 If this directory does not exist you can create it.
 
@@ -59,7 +59,7 @@
 ====================
 
 If all you need to do is add some functionality to an existing library -
-perhaps add a function or two - then it's overkill to replace the entire
+perhaps add a method or two - then it's overkill to replace the entire
 library with your version. In this case it's better to simply extend the
 class. Extending a class is nearly identical to replacing a class with a
 couple exceptions:
@@ -75,19 +75,19 @@
 
 	}
 
-Note: If you need to use a constructor in your class make sure you
+.. note:: If you need to use a constructor in your class make sure you
 extend the parent constructor::
 
 	class MY_Input extends CI_Input {
 
-	    function __construct()
-	    {
-	        parent::__construct();
-	    }
+		public function __construct()
+		{
+			parent::__construct();
+		}
 	}
 
 **Tip:** Any functions in your class that are named identically to the
-functions in the parent class will be used instead of the native ones
+methods in the parent class will be used instead of the native ones
 (this is known as "method overriding"). This allows you to substantially
 alter the CodeIgniter core.
 
@@ -98,24 +98,24 @@
 
 	class Welcome extends MY_Controller {
 
-	    function __construct()
-	    {
-	        parent::__construct();
-	    }
+		public function __construct()
+		{
+			parent::__construct();
+		}
 
-	    function index()
-	    {
-	        $this->load->view('welcome_message');
-	    }
+		public function index()
+		{
+			$this->load->view('welcome_message');
+		}
 	}
 
 Setting Your Own Prefix
 -----------------------
 
 To set your own sub-class prefix, open your
-application/config/config.php file and look for this item::
+*application/config/config.php* file and look for this item::
 
 	$config['subclass_prefix'] = 'MY_';
 
-Please note that all native CodeIgniter libraries are prefixed with CI\_
-so DO NOT use that as your prefix.
+Please note that all native CodeIgniter libraries are prefixed
+with CI\_ so DO NOT use that as your prefix.
\ No newline at end of file
diff --git a/user_guide_src/source/general/creating_drivers.rst b/user_guide_src/source/general/creating_drivers.rst
index 0e22e4f..cf4ea5d 100644
--- a/user_guide_src/source/general/creating_drivers.rst
+++ b/user_guide_src/source/general/creating_drivers.rst
@@ -16,5 +16,6 @@
       -  Driver_name_subclass_2.php
       -  Driver_name_subclass_3.php
 
-**NOTE:** In order to maintain compatibility on case-sensitive file
-systems, the Driver_name directory must be ucfirst()
+.. note:: In order to maintain compatibility on case-sensitive
+	file systems, the Driver_name directory must be
+	named in the format returned by ``ucfirst()``.
\ No newline at end of file
diff --git a/user_guide_src/source/general/creating_libraries.rst b/user_guide_src/source/general/creating_libraries.rst
index 673fbd4..8bafd45 100644
--- a/user_guide_src/source/general/creating_libraries.rst
+++ b/user_guide_src/source/general/creating_libraries.rst
@@ -12,7 +12,7 @@
 As an added bonus, CodeIgniter permits your libraries to extend native
 classes if you simply need to add some functionality to an existing
 library. Or you can even replace native libraries just by placing
-identically named versions in your application/libraries folder.
+identically named versions in your *application/libraries* directory.
 
 In summary:
 
@@ -28,8 +28,8 @@
 Storage
 =======
 
-Your library classes should be placed within your application/libraries
-folder, as this is where CodeIgniter will look for them when they are
+Your library classes should be placed within your *application/libraries*
+directory, as this is where CodeIgniter will look for them when they are
 initialized.
 
 Naming Conventions
@@ -49,9 +49,9 @@
 
 	class Someclass {
 
-	    public function some_function()
-	    {
-	    }
+		public function some_method()
+		{
+		}
 	}
 
 	/* End of file Someclass.php */
@@ -59,7 +59,7 @@
 Using Your Class
 ================
 
-From within any of your :doc:`Controller <controllers>` functions you
+From within any of your :doc:`Controller <controllers>` methods you
 can initialize your class using the standard::
 
 	$this->load->library('someclass');
@@ -70,12 +70,12 @@
 
 Once loaded you can access your class using the lower case version::
 
-	$this->someclass->some_function();  // Object instances will always be lower case
+	$this->someclass->some_method();  // Object instances will always be lower case
 
 Passing Parameters When Initializing Your Class
 ===============================================
 
-In the library loading function you can dynamically pass data as an
+In the library loading method you can dynamically pass data as an
 array via the second parameter and it will be passed to your class
 constructor::
 
@@ -86,21 +86,19 @@
 If you use this feature you must set up your class constructor to expect
 data::
 
-	<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+	<?php defined('BASEPATH') OR exit('No direct script access allowed');
 
 	class Someclass {
 
-	    public function __construct($params)
-	    {
-	        // Do something with $params
-	    }
+		public function __construct($params)
+		{
+			// Do something with $params
+		}
 	}
 
-	?>
-
 You can also pass parameters stored in a config file. Simply create a
 config file named identically to the class file name and store it in
-your application/config/ folder. Note that if you dynamically pass
+your *application/config/* directory. Note that if you dynamically pass
 parameters as described above, the config file option will not be
 available.
 
@@ -108,18 +106,18 @@
 ===================================================
 
 To access CodeIgniter's native resources within your library use the
-get_instance() function. This function returns the CodeIgniter super
+``get_instance()`` method. This method returns the CodeIgniter super
 object.
 
-Normally from within your controller functions you will call any of the
-available CodeIgniter functions using the $this construct::
+Normally from within your controller methods you will call any of the
+available CodeIgniter methods using the ``$this`` construct::
 
 	$this->load->helper('url');
 	$this->load->library('session');
 	$this->config->item('base_url');
 	// etc.
 
-$this, however, only works directly within your controllers, your
+``$this``, however, only works directly within your controllers, your
 models, or your views. If you would like to use CodeIgniter's classes
 from within your own custom classes you can do so as follows:
 
@@ -128,7 +126,7 @@
 	$CI =& get_instance();
 
 Once you've assigned the object to a variable, you'll use that variable
-*instead* of $this::
+*instead* of ``$this``::
 
 	$CI =& get_instance();
 
@@ -137,7 +135,7 @@
 	$CI->config->item('base_url');
 	// etc.
 
-.. note:: You'll notice that the above get_instance() function is being
+.. note:: You'll notice that the above ``get_instance()`` function is being
 	passed by reference::
 	
 		$CI =& get_instance();
@@ -145,6 +143,36 @@
 	This is very important. Assigning by reference allows you to use the
 	original CodeIgniter object rather than creating a copy of it.
 
+However, since a library is a class, it would be better if you
+take full advantage of the OOP principles. So, in order to
+be able to use the CodeIgniter super-object in all of the class
+methods, you're encouraged to assign it to a property instead::
+
+class Example_library {
+
+	protected $CI;
+
+	// We'll use a constructor, as you can't directly call a function
+	// from a property definition.
+	public function __construct()
+	{
+		// Assign the CodeIgniter super-object
+		$this->CI =& get_instance();
+	}
+
+	public function foo()
+	{
+		$this->CI->load->helper('url');
+		redirect();
+	}
+
+	public function bar()
+	{
+		echo $this->CI->config_item('base_url');
+	}
+
+}
+
 Replacing Native Libraries with Your Versions
 =============================================
 
@@ -152,8 +180,8 @@
 cause CodeIgniter to use it instead of the native one. To use this
 feature you must name the file and the class declaration exactly the
 same as the native library. For example, to replace the native Email
-library you'll create a file named application/libraries/Email.php, and
-declare your class with::
+library you'll create a file named *application/libraries/Email.php*,
+and declare your class with::
 
 	class CI_Email {
 	
@@ -161,7 +189,7 @@
 
 Note that most native classes are prefixed with CI\_.
 
-To load your library you'll see the standard loading function::
+To load your library you'll see the standard loading method::
 
 	$this->load->library('email');
 
@@ -172,7 +200,7 @@
 ==========================
 
 If all you need to do is add some functionality to an existing library -
-perhaps add a function or two - then it's overkill to replace the entire
+perhaps add a method or two - then it's overkill to replace the entire
 library with your version. In this case it's better to simply extend the
 class. Extending a class is nearly identical to replacing a class with a
 couple exceptions:
@@ -182,7 +210,7 @@
    item is configurable. See below.).
 
 For example, to extend the native Email class you'll create a file named
-application/libraries/MY_Email.php, and declare your class with::
+*application/libraries/MY_Email.php*, and declare your class with::
 
 	class MY_Email extends CI_Email {
 
@@ -200,8 +228,7 @@
 
 	}
 
-.. note::
-	Not all of the libraries have the same (or any) parameters
+.. note:: Not all of the libraries have the same (or any) parameters
 	in their constructor. Take a look at the library that you're
 	extending first to see how it should be implemented.
 
@@ -218,15 +245,15 @@
 the class you are extending. In the case of the email class all calls
 will use::
 
-	$this->email->some_function();
+	$this->email->some_method();
 
 Setting Your Own Prefix
 -----------------------
 
 To set your own sub-class prefix, open your
-application/config/config.php file and look for this item::
+*application/config/config.php* file and look for this item::
 
 	$config['subclass_prefix'] = 'MY_';
 
 Please note that all native CodeIgniter libraries are prefixed with CI\_
-so DO NOT use that as your prefix.
+so DO NOT use that as your prefix.
\ No newline at end of file
diff --git a/user_guide_src/source/general/credits.rst b/user_guide_src/source/general/credits.rst
index 2c74598..03ee83d 100644
--- a/user_guide_src/source/general/credits.rst
+++ b/user_guide_src/source/general/credits.rst
@@ -16,4 +16,4 @@
 
 A hat tip goes to Ruby on Rails for inspiring us to create a PHP
 framework, and for bringing frameworks into the general consciousness of
-the web community.
+the web community.
\ No newline at end of file
diff --git a/user_guide_src/source/general/drivers.rst b/user_guide_src/source/general/drivers.rst
index e2ded62..b64b0e7 100644
--- a/user_guide_src/source/general/drivers.rst
+++ b/user_guide_src/source/general/drivers.rst
@@ -8,18 +8,18 @@
 in your :doc:`controllers <controllers>` for libraries that benefit
 from or require being broken down into discrete classes.
 
-Drivers are found in the system/libraries folder, in their own folder
-which is identically named to the parent library class. Also inside that
-folder is a subfolder named drivers, which contains all of the possible
-child class files.
+Drivers are found in the *system/libraries/* directory, in their own
+sub-directory which is identically named to the parent library class.
+Also inside that directory is a subdirectory named drivers, which
+contains all of the possible child class files.
 
 To use a driver you will initialize it within a controller using the
-following initialization function::
+following initialization method::
 
-	$this->load->driver('class name'); 
+	$this->load->driver('class_name');
 
 Where class name is the name of the driver class you want to invoke. For
-example, to load a driver named "Some Parent" you would do this::
+example, to load a driver named "Some_parent" you would do this::
 
 	$this->load->driver('some_parent');
 
@@ -37,4 +37,4 @@
 =========================
 
 Please read the section of the user guide that discusses how to :doc:`create
-your own drivers <creating_drivers>`.
+your own drivers <creating_drivers>`.
\ No newline at end of file
diff --git a/user_guide_src/source/general/environments.rst b/user_guide_src/source/general/environments.rst
index fa1b096..d74ebb8 100644
--- a/user_guide_src/source/general/environments.rst
+++ b/user_guide_src/source/general/environments.rst
@@ -11,8 +11,8 @@
 The ENVIRONMENT Constant
 ========================
 
-By default, CodeIgniter comes with the environment constant set to use 
-the value provided in ``$_SERVER['CI_ENV']``, otherwise defaults to 
+By default, CodeIgniter comes with the environment constant set to use
+the value provided in ``$_SERVER['CI_ENV']``, otherwise defaults to
 'development'. At the top of index.php, you will see::
 
 	define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'development');
@@ -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 `Config
-Class <../libraries/config.html#environments>`_ documentation.
+Class <../libraries/config.html#environments>`_ documentation.
\ No newline at end of file
diff --git a/user_guide_src/source/general/errors.rst b/user_guide_src/source/general/errors.rst
index 91b5914..8c941aa 100644
--- a/user_guide_src/source/general/errors.rst
+++ b/user_guide_src/source/general/errors.rst
@@ -20,47 +20,69 @@
 
 The following functions let you generate errors:
 
-show_error('message' [, int $status_code= 500 ] )
-===================================================
+show_error()
+============
+
+.. php:function:: show_error($message, $status_code, $heading = 'An Error Was Encountered')
+
+	:param	mixed	$message: Error message
+	:param	int	$status_code: HTTP Response status code
+	:param	string	$heading: Error page heading
+	:returns:	void
 
 This function will display the error message supplied to it using the
-following error template:
+following error template::
 
-application/errors/error_general.php
+	application/errors/error_general.php
 
-The optional parameter $status_code determines what HTTP status code
-should be sent with the error.
+The optional parameter ``$status_code`` determines what HTTP status
+code should be sent with the error.
 
-show_404('page' [, 'log_error'])
-==================================
+show_404()
+==========
+
+.. php:function:: show_404($page = '', $log_error = TRUE)
+
+	:param	string	$page: URI string
+	:param	bool	$log_error: Whether to log the error
+	:returns:	void
 
 This function will display the 404 error message supplied to it using
-the following error template:
+the following error template::
 
-application/errors/error_404.php
+	application/errors/error_404.php
 
 The function expects the string passed to it to be the file path to the
 page that isn't found. Note that CodeIgniter automatically shows 404
 messages if controllers are not found.
 
-CodeIgniter automatically logs any show_404() calls. Setting the
+CodeIgniter automatically logs any ``show_404()`` calls. Setting the
 optional second parameter to FALSE will skip logging.
 
-log_message('level', 'message')
-================================
+log_message()
+=============
+
+.. php:function:: log_message($level = 'error', $message, $php_error = FALSE)
+
+	:param	string	$level: Log level
+	:param	string	$message: Message to log
+	:param	bool	$php_error: Whether we're loggin a native PHP error message
+	:returns:	void
 
 This function lets you write messages to your log files. You must supply
 one of three "levels" in the first parameter, indicating what type of
 message it is (debug, error, info), with the message itself in the
-second parameter. Example::
+second parameter.
 
-	if ($some_var == "")
+Example::
+
+	if ($some_var == '')
 	{
-	    log_message('error', 'Some variable did not contain a value.');
+		log_message('error', 'Some variable did not contain a value.');
 	}
 	else
 	{
-	    log_message('debug', 'Some variable was correctly set');
+		log_message('debug', 'Some variable was correctly set');
 	}
 
 	log_message('info', 'The purpose of some variable is to provide some value.');
@@ -77,8 +99,8 @@
    natively generate any info messages but you may want to in your
    application.
 
-.. note:: In order for the log file to actually be written, the "logs"
-	folder must be writable. In addition, you must set the "threshold" for
-	logging in application/config/config.php. You might, for example, only
-	want error messages to be logged, and not the other two types. If you
-	set it to zero logging will be disabled.
+.. note:: In order for the log file to actually be written, the *logs*
+	directory must be writable. In addition, you must set the "threshold"
+	for logging in *application/config/config.php*. You might, for example,
+	only want error messages to be logged, and not the other two types.
+	If you set it to zero logging will be disabled.
\ No newline at end of file
diff --git a/user_guide_src/source/general/helpers.rst b/user_guide_src/source/general/helpers.rst
index 3a98311..d171aa8 100644
--- a/user_guide_src/source/general/helpers.rst
+++ b/user_guide_src/source/general/helpers.rst
@@ -23,12 +23,12 @@
 **application/helpers directory**. CodeIgniter will look first in your
 **application/helpers directory**. If the directory does not exist or the
 specified helper is not located there CI will instead look in your
-global system/helpers folder.
+global *system/helpers/* directory.
 
 Loading a Helper
 ================
 
-Loading a helper file is quite simple using the following function::
+Loading a helper file is quite simple using the following method::
 
 	$this->load->helper('name');
 
@@ -40,15 +40,15 @@
 
 	$this->load->helper('url');
 
-A helper can be loaded anywhere within your controller functions (or
+A helper can be loaded anywhere within your controller methods (or
 even within your View files, although that's not a good practice), as
 long as you load it before you use it. You can load your helpers in your
 controller constructor so that they become available automatically in
 any function, or you can load a helper in a specific function that needs
 it.
 
-Note: The Helper loading function above does not return a value, so
-don't try to assign it to a variable. Just use it as shown.
+.. note:: The Helper loading method above does not return a value, so
+	don't try to assign it to a variable. Just use it as shown.
 
 Loading Multiple Helpers
 ========================
@@ -56,14 +56,16 @@
 If you need to load more than one helper you can specify them in an
 array, like this::
 
-	$this->load->helper( array('helper1', 'helper2', 'helper3') );
+	$this->load->helper(
+		array('helper1', 'helper2', 'helper3')
+	);
 
 Auto-loading Helpers
 ====================
 
 If you find that you need a particular helper globally throughout your
 application, you can tell CodeIgniter to auto-load it during system
-initialization. This is done by opening the **application/config/autoload.php** 
+initialization. This is done by opening the **application/config/autoload.php**
 file and adding the helper to the autoload array.
 
 Using a Helper
@@ -72,13 +74,13 @@
 Once you've loaded the Helper File containing the function you intend to
 use, you'll call it the way you would a standard PHP function.
 
-For example, to create a link using the anchor() function in one of your
-view files you would do this::
+For example, to create a link using the ``anchor()`` function in one of
+your view files you would do this::
 
 	<?php echo anchor('blog/comments', 'Click Here');?>
 
 Where "Click Here" is the name of the link, and "blog/comments" is the
-URI to the controller/function you wish to link to.
+URI to the controller/method you wish to link to.
 
 "Extending" Helpers
 ===================
@@ -91,11 +93,11 @@
 perhaps add a function or two, or change how a particular helper
 function operates - then it's overkill to replace the entire helper with
 your version. In this case it's better to simply "extend" the Helper.
-The term "extend" is used loosely since Helper functions are procedural
-and discrete and cannot be extended in the traditional programmatic
-sense. Under the hood, this gives you the ability to add to the
-functions a Helper provides, or to modify how the native Helper
-functions operate.
+
+.. note:: The term "extend" is used loosely since Helper functions are
+	procedural and discrete and cannot be extended in the traditional
+	programmatic sense. Under the hood, this gives you the ability to
+	add to or or to replace the functions a Helper provides.
 
 For example, to extend the native **Array Helper** you'll create a file
 named **application/helpers/MY_array_helper.php**, and add or override
@@ -104,31 +106,31 @@
 	// any_in_array() is not in the Array Helper, so it defines a new function
 	function any_in_array($needle, $haystack)
 	{
-	    $needle = (is_array($needle)) ? $needle : array($needle);
+		$needle = is_array($needle) ? $needle : array($needle);
 
-	    foreach ($needle as $item)
-	    {
-	        if (in_array($item, $haystack))
-	        {
-	            return TRUE;
-	        }
+		foreach ($needle as $item)
+		{
+			if (in_array($item, $haystack))
+			{
+				return TRUE;
+			}
 	        }
 
-	    return FALSE;
+		return FALSE;
 	}
 
 	// random_element() is included in Array Helper, so it overrides the native function
 	function random_element($array)
 	{
-	    shuffle($array);
-	    return array_pop($array);
+		shuffle($array);
+		return array_pop($array);
 	}
 
 Setting Your Own Prefix
 -----------------------
 
 The filename prefix for "extending" Helpers is the same used to extend
-libraries and Core classes. To set your own prefix, open your
+libraries and core classes. To set your own prefix, open your
 **application/config/config.php** file and look for this item::
 
 	$config['subclass_prefix'] = 'MY_';
@@ -140,4 +142,4 @@
 =========
 
 In the Table of Contents you'll find a list of all the available Helper
-Files. Browse each one to see what they do.
+Files. Browse each one to see what they do.
\ No newline at end of file
diff --git a/user_guide_src/source/general/hooks.rst b/user_guide_src/source/general/hooks.rst
index 65696f6..fc5da5b 100644
--- a/user_guide_src/source/general/hooks.rst
+++ b/user_guide_src/source/general/hooks.rst
@@ -16,25 +16,26 @@
 ==============
 
 The hooks feature can be globally enabled/disabled by setting the
-following item in the application/config/config.php file::
+following item in the **application/config/config.php** file::
 
 	$config['enable_hooks'] = TRUE;
 
 Defining a Hook
 ===============
 
-Hooks are defined in application/config/hooks.php file. Each hook is
-specified as an array with this prototype::
+Hooks are defined in **application/config/hooks.php** file.
+Each hook is specified as an array with this prototype::
 
 	$hook['pre_controller'] = array(
-	                                'class'    => 'MyClass',
-	                                'function' => 'Myfunction',
-	                                'filename' => 'Myclass.php',
-	                                'filepath' => 'hooks',
-	                                'params'   => array('beer', 'wine', 'snacks')
-	                                );
+		'class'    => 'MyClass',
+		'function' => 'Myfunction',
+		'filename' => 'Myclass.php',
+		'filepath' => 'hooks',
+		'params'   => array('beer', 'wine', 'snacks')
+	);
 
 **Notes:**
+
 The array index correlates to the name of the particular hook point you
 want to use. In the above example the hook point is pre_controller. A
 list of hook points is found below. The following items should be
@@ -42,14 +43,15 @@
 
 -  **class** The name of the class you wish to invoke. If you prefer to
    use a procedural function instead of a class, leave this item blank.
--  **function** The function name you wish to call.
+-  **function** The function (or method) name you wish to call.
 -  **filename** The file name containing your class/function.
--  **filepath** The name of the directory containing your script. Note:
-   Your script must be located in a directory INSIDE your application
-   folder, so the file path is relative to that folder. For example, if
-   your script is located in application/hooks, you will simply use
-   hooks as your filepath. If your script is located in
-   application/hooks/utilities you will use hooks/utilities as your
+-  **filepath** The name of the directory containing your script.
+   Note:
+   Your script must be located in a directory INSIDE your *application/*
+   directory, so the file path is relative to that directory. For example,
+   if your script is located in *application/hooks/*, you will simply use
+   'hooks' as your filepath. If your script is located in
+   *application/hooks/utilities/* you will use 'hooks/utilities' as your
    filepath. No trailing slash.
 -  **params** Any parameters you wish to pass to your script. This item
    is optional.
@@ -61,20 +63,20 @@
 make your array declaration multi-dimensional, like this::
 
 	$hook['pre_controller'][] = array(
-	                                'class'    => 'MyClass',
-	                                'function' => 'Myfunction',
-	                                'filename' => 'Myclass.php',
-	                                'filepath' => 'hooks',
-	                                'params'   => array('beer', 'wine', 'snacks')
-	                                );
+		'class'    => 'MyClass',
+		'function' => 'MyMethod',
+		'filename' => 'Myclass.php',
+		'filepath' => 'hooks',
+		'params'   => array('beer', 'wine', 'snacks')
+	);
 
 	$hook['pre_controller'][] = array(
-	                                'class'    => 'MyOtherClass',
-	                                'function' => 'MyOtherfunction',
-	                                'filename' => 'Myotherclass.php',
-	                                'filepath' => 'hooks',
-	                                'params'   => array('red', 'yellow', 'blue')
-	                                );
+		'class'    => 'MyOtherClass',
+		'function' => 'MyOtherMethod',
+		'filename' => 'Myotherclass.php',
+		'filepath' => 'hooks',
+		'params'   => array('red', 'yellow', 'blue')
+	);
 
 Notice the brackets after each array index::
 
@@ -101,18 +103,17 @@
 -  **post_controller**
    Called immediately after your controller is fully executed.
 -  **display_override**
-   Overrides the _display() function, used to send the finalized page
+   Overrides the ``_display()`` method, used to send the finalized page
    to the web browser at the end of system execution. This permits you
    to use your own display methodology. Note that you will need to
-   reference the CI superobject with $this->CI =& get_instance() and
+   reference the CI superobject with ``$this->CI =& get_instance()`` and
    then the finalized data will be available by calling
-   $this->CI->output->get_output()
+   ``$this->CI->output->get_output()``.
 -  **cache_override**
-   Enables you to call your own function instead of the
-   _display_cache() function in the output class. This permits you to
-   use your own cache display mechanism.
+   Enables you to call your own method instead of the ``_display_cache()``
+   method in the :doc:`Output Library <../libraries/output>`. This permits
+   you to use your own cache display mechanism.
 -  **post_system**
    Called after the final rendered page is sent to the browser, at the
    end of system execution after the finalized data is sent to the
-   browser.
-
+   browser.
\ No newline at end of file
diff --git a/user_guide_src/source/general/index.rst b/user_guide_src/source/general/index.rst
index 2162b81..2bc684a 100644
--- a/user_guide_src/source/general/index.rst
+++ b/user_guide_src/source/general/index.rst
@@ -29,4 +29,4 @@
 	environments
 	alternative_php
 	security
-	PHP Style Guide <styleguide>
+	PHP Style Guide <styleguide>
\ No newline at end of file
diff --git a/user_guide_src/source/general/libraries.rst b/user_guide_src/source/general/libraries.rst
index 954a5b8..6e1c8b6 100644
--- a/user_guide_src/source/general/libraries.rst
+++ b/user_guide_src/source/general/libraries.rst
@@ -2,30 +2,31 @@
 Using CodeIgniter Libraries
 ###########################
 
-All of the available libraries are located in your system/libraries
-folder. In most cases, to use one of these classes involves initializing
+All of the available libraries are located in your *system/libraries/*
+directory. In most cases, to use one of these classes involves initializing
 it within a :doc:`controller <controllers>` using the following
-initialization function::
+initialization method::
 
-	$this->load->library('class name'); 
+	$this->load->library('class_name');
 
-Where class name is the name of the class you want to invoke. For
-example, to load the form validation class you would do this::
+Where 'class_name' is the name of the class you want to invoke. For
+example, to load the :doc:`Form Validation Library
+<../libraries/form_validation>` you would do this::
 
-	$this->load->library('form_validation'); 
+	$this->load->library('form_validation');
 
 Once initialized you can use it as indicated in the user guide page
 corresponding to that class.
 
 Additionally, multiple libraries can be loaded at the same time by
-passing an array of libraries to the load function.
+passing an array of libraries to the load method.
 
-::
+Example::
 
 	$this->load->library(array('email', 'table'));
 
 Creating Your Own Libraries
 ===========================
 
-Please read the section of the user guide that discusses how to :doc:`create
-your own libraries <creating_libraries>`
+Please read the section of the user guide that discusses how to
+:doc:`create your own libraries <creating_libraries>`.
diff --git a/user_guide_src/source/general/managing_apps.rst b/user_guide_src/source/general/managing_apps.rst
index 9964813..afb1aba 100644
--- a/user_guide_src/source/general/managing_apps.rst
+++ b/user_guide_src/source/general/managing_apps.rst
@@ -3,41 +3,39 @@
 ##########################
 
 By default it is assumed that you only intend to use CodeIgniter to
-manage one application, which you will build in your application/
+manage one application, which you will build in your *application/*
 directory. It is possible, however, to have multiple sets of
 applications that share a single CodeIgniter installation, or even to
-rename or relocate your application folder.
+rename or relocate your application directory.
 
-Renaming the Application Folder
-===============================
-
-If you would like to rename your application folder you may do so as
-long as you open your main index.php file and set its name using the
-$application_folder variable::
-
-	$application_folder = "application";
-
-Relocating your Application Folder
+Renaming the Application Directory
 ==================================
 
-It is possible to move your application folder to a different location
-on your server than your system folder. To do so open your main
-index.php and set a *full server path* in the $application_folder
-variable.
+If you would like to rename your application directory you may do so
+as long as you open your main index.php file and set its name using
+the ``$application_folder`` variable::
 
-::
+	$application_folder = 'application';
 
-	$application_folder = "/Path/to/your/application";
+Relocating your Application Directory
+=====================================
+
+It is possible to move your application directory to a different
+location on your server than your system directory. To do so open
+your main index.php and set a *full server path* in the
+``$application_folder`` variable::
+
+	$application_folder = '/path/to/your/application';
 
 Running Multiple Applications with one CodeIgniter Installation
 ===============================================================
 
 If you would like to share a common CodeIgniter installation to manage
 several different applications simply put all of the directories located
-inside your application folder into their own sub-folder.
+inside your application directory into their own sub-directory.
 
-For example, let's say you want to create two applications, "foo" and
-"bar". You could structure your application folders like this::
+For example, let's say you want to create two applications, named "foo"
+and "bar". You could structure your application directories like this::
 
 	applications/foo/
 	applications/foo/config/
@@ -55,11 +53,11 @@
 	applications/bar/views/
 
 To select a particular application for use requires that you open your
-main index.php file and set the $application_folder variable. For
+main index.php file and set the ``$application_folder`` variable. For
 example, to select the "foo" application for use you would do this::
 
-	$application_folder = "applications/foo";
+	$application_folder = 'applications/foo';
 
 .. note:: Each of your applications will need its own index.php file
 	which calls the desired application. The index.php file can be named
-	anything you want.
+	anything you want.
\ No newline at end of file
diff --git a/user_guide_src/source/general/models.rst b/user_guide_src/source/general/models.rst
index 4e52a96..a028a95 100644
--- a/user_guide_src/source/general/models.rst
+++ b/user_guide_src/source/general/models.rst
@@ -18,55 +18,56 @@
 
 	class Blog_model extends CI_Model {
 
-	    public $title   = '';
-	    public $content = '';
-	    public $date    = '';
+		public $title;
+		public $content;
+		public $date;
 
-	    function __construct()
-	    {
-	        // Call the Model constructor
-	        parent::__construct();
-	    }
+		public function __construct()
+		{
+			// Call the CI_Model constructor
+			parent::__construct();
+		}
 
-	    function get_last_ten_entries()
-	    {
-	        $query = $this->db->get('entries', 10);
-	        return $query->result();
-	    }
+		public function get_last_ten_entries()
+		{
+			$query = $this->db->get('entries', 10);
+			return $query->result();
+		}
 
-	    function insert_entry()
-	    {
-	        $this->title   = $_POST['title']; // please read the below note
-	        $this->content = $_POST['content'];
-	        $this->date    = time();
+		public function insert_entry()
+		{
+			$this->title	= $_POST['title']; // please read the below note
+			$this->content	= $_POST['content'];
+			$this->date	= time();
 
-	        $this->db->insert('entries', $this);
-	    }
+			$this->db->insert('entries', $this);
+		}
 
-	    function update_entry()
-	    {
-	        $this->title   = $_POST['title'];
-	        $this->content = $_POST['content'];
-	        $this->date    = time();
+		public function update_entry()
+		{
+			$this->title	= $_POST['title'];
+			$this->content	= $_POST['content'];
+			$this->date	= time();
 
-	        $this->db->update('entries', $this, array('id' => $_POST['id']));
-	    }
+			$this->db->update('entries', $this, array('id' => $_POST['id']));
+		}
 
 	}
 
-.. note:: The functions in the above example use the :doc:`Active
-	Record <../database/query_builder>` database functions.
+.. note:: The methods in the above example use the :doc:`Query Builder
+	<../database/query_builder>` database methods.
 
-.. note:: For the sake of simplicity in this example we're using $_POST
+.. note:: For the sake of simplicity in this example we're using ``$_POST``
 	directly. This is generally bad practice, and a more common approach
-	would be to use the :doc:`Input Class <../libraries/input>`
-	$this->input->post('title')
+	would be to use the :doc:`Input Library <../libraries/input>`
+	``$this->input->post('title')``.
 
 Anatomy of a Model
 ==================
 
-Model classes are stored in your **application/models/ folder**. They can be
-nested within sub-folders if you want this type of organization.
+Model classes are stored in your **application/models/** directory.
+They can be nested within sub-directories if you want this type of
+organization.
 
 The basic prototype for a model class is this::
 
@@ -103,14 +104,14 @@
 ===============
 
 Your models will typically be loaded and called from within your
-:doc:`controller <controllers>` functions. To load a model you will use
+:doc:`controller <controllers>` methods. To load a model you will use
 the following method::
 
 	$this->load->model('model_name');
 
-If your model is located in a sub-folder, include the relative path from
-your models folder. For example, if you have a model located at
-application/models/blog/queries.php you'll load it using::
+If your model is located in a sub-directory, include the relative path
+from your models directory. For example, if you have a model located at
+*application/models/blog/queries.php* you'll load it using::
 
 	$this->load->model('blog/queries');
 
@@ -141,7 +142,6 @@
 
 			$this->load->view('blog', $data);
 		}
-
 	}
 	
 
@@ -163,10 +163,9 @@
 -  You can connect using the standard database methods :doc:`described
    here <../database/connecting>`, either from within your
    Controller class or your Model class.
--  You can tell the model loading function to auto-connect by passing
-   TRUE (boolean) via the third parameter, and connectivity settings, as
-   defined in your database config file will be used:
-   ::
+-  You can tell the model loading method to auto-connect by passing
+   TRUE (boolean) via the third parameter, and connectivity settings,
+   as defined in your database config file will be used::
 
 	$this->load->model('model_name', '', TRUE);
 
diff --git a/user_guide_src/source/general/profiling.rst b/user_guide_src/source/general/profiling.rst
index 4376352..6dbd0be 100644
--- a/user_guide_src/source/general/profiling.rst
+++ b/user_guide_src/source/general/profiling.rst
@@ -3,7 +3,7 @@
 ##########################
 
 The Profiler Class will display benchmark results, queries you have run,
-and $_POST data at the bottom of your pages. This information can be
+and ``$_POST`` data at the bottom of your pages. This information can be
 useful during development in order to help with debugging and
 optimization.
 
@@ -11,14 +11,14 @@
 ======================
 
 .. important:: This class does NOT need to be initialized. It is loaded
-	automatically by the :doc:`Output Class <../libraries/output>` if
-	profiling is enabled as shown below.
+	automatically by the :doc:`Output Library <../libraries/output>`
+	if profiling is enabled as shown below.
 
 Enabling the Profiler
 =====================
 
-To enable the profiler place the following function anywhere within your
-:doc:`Controller <controllers>` functions::
+To enable the profiler place the following line anywhere within your
+:doc:`Controller <controllers>` methods::
 
 	$this->output->enable_profiler(TRUE);
 
@@ -35,8 +35,8 @@
 In order for the Profiler to compile and display your benchmark data you
 must name your mark points using specific syntax.
 
-Please read the information on setting Benchmark points in :doc:`Benchmark
-Class <../libraries/benchmark>` page.
+Please read the information on setting Benchmark points in the
+:doc:`Benchmark Library <../libraries/benchmark>` page.
 
 Enabling and Disabling Profiler Sections
 ========================================
@@ -44,21 +44,21 @@
 Each section of Profiler data can be enabled or disabled by setting a
 corresponding config variable to TRUE or FALSE. This can be done one of
 two ways. First, you can set application wide defaults with the
-application/config/profiler.php config file.
+*application/config/profiler.php* config file.
 
-::
+Example::
 
 	$config['config']          = FALSE;
 	$config['queries']         = FALSE;
 
 In your controllers, you can override the defaults and config file
-values by calling the set_profiler_sections() method of the :doc:`Output
-class <../libraries/output>`::
+values by calling the ``set_profiler_sections()`` method of the
+:doc:`Output Library <../libraries/output>`::
 
 	$sections = array(
-	    'config'  => TRUE,
-	    'queries' => TRUE
-	    );
+		'config'  => TRUE,
+		'queries' => TRUE
+	);
 
 	$this->output->set_profiler_sections($sections);
 
diff --git a/user_guide_src/source/general/quick_reference.rst b/user_guide_src/source/general/quick_reference.rst
deleted file mode 100644
index b9108a5..0000000
--- a/user_guide_src/source/general/quick_reference.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-#####################
-Quick Reference Chart
-#####################
-
-For a PDF version of this chart, `click
-here <http://codeigniter.com/download_files/ci_quick_ref.pdf>`_.
-
-.. figure:: ../images/ci_quick_ref.png
-   :align: center
-   :alt: 
-
diff --git a/user_guide_src/source/general/requirements.rst b/user_guide_src/source/general/requirements.rst
index d9edfba..1049236 100644
--- a/user_guide_src/source/general/requirements.rst
+++ b/user_guide_src/source/general/requirements.rst
@@ -3,7 +3,13 @@
 ###################
 
 -  `PHP <http://www.php.net/>`_ version 5.2.4 or newer.
--  A Database is required for most web application programming. Current
-   supported databases are MySQL (5.1+), MySQLi, Oracle, PostgreSQL,
-   MS SQL, SQLSRV (SQL Server 2005+), SQLite, SQLite3, CUBRID, Interbase,
-   ODBC and PDO.
+-  A Database is required for most web application programming.
+   Currently supported databases are:
+   -  MySQL (5.1+) via the *mysql* (deprecated), *mysqli* and *pdo* drivers
+   -  Oracle via the *oci8* and *pdo* drivers
+   -  PostgreSQL via the *postgre* and *pdo* drivers
+   -  MS SQL via the *mssql*, *sqlsrv* (version 2005 and above only) and *pdo* drivers
+   -  SQLite via the *sqlite* (version 2), *sqlite3* (version 3) and *pdo* drivers
+   -  CUBRID via the *cubrid* and *pdo* drivers
+   -  Interbase/Firebird via the *ibase* and *pdo* drivers
+   -  ODBC via the *odbc* and *pdo* drivers (you should know that ODBC is actually an abstraction layer)
\ No newline at end of file
diff --git a/user_guide_src/source/general/reserved_names.rst b/user_guide_src/source/general/reserved_names.rst
index 5ce7fc2..d912923 100644
--- a/user_guide_src/source/general/reserved_names.rst
+++ b/user_guide_src/source/general/reserved_names.rst
@@ -2,16 +2,17 @@
 Reserved Names
 ##############
 
-In order to help out, CodeIgniter uses a series of functions and names
-in its operation. Because of this, some names cannot be used by a
-developer. Following is a list of reserved names that cannot be used.
+In order to help out, CodeIgniter uses a series of function, method,
+class and variable names in its operation. Because of this, some names
+cannot be used by a developer. Following is a list of reserved names
+that cannot be used.
 
 Controller names
 ----------------
 
 Since your controller classes will extend the main application
-controller you must be careful not to name your functions identically to
-the ones used by that class, otherwise your local functions will
+controller you must be careful not to name your methods identically to
+the ones used by that class, otherwise your local methods will
 override them. The following is a list of reserved names. Do not name
 your controller any of these:
 
@@ -24,32 +25,35 @@
 Functions
 ---------
 
--  is_really_writable()
--  load_class()
--  get_config()
--  config_item()
--  show_error()
--  show_404()
--  log_message()
--  _exception_handler()
--  get_instance()
+-  :php:func:`is_really_writable()`
+-  ``load_class()``
+-  ``get_config()``
+-  :php:func:`config_item()`
+-  :php:func:`show_error()`
+-  :php:func:`show_404()`
+-  :php:func:`log_message()`
+-  :php:func:`get_mimes()`
+-  :php:func:`html_escape()`
+-  :php:func:`get_instance()`
+-  ``_exception_handler()``
+-  ``_stringify_attributes()``
 
 Variables
 ---------
 
--  $config
--  $mimes
--  $lang
+-  ``$config``
+-  ``$db``
+-  ``$lang``
 
 Constants
 ---------
 
 -  ENVIRONMENT
--  EXT
 -  FCPATH
 -  SELF
 -  BASEPATH
 -  APPPATH
+-  VIEWPATH
 -  CI_VERSION
 -  FILE_READ_MODE
 -  FILE_WRITE_MODE
@@ -62,5 +66,4 @@
 -  FOPEN_WRITE_CREATE
 -  FOPEN_READ_WRITE_CREATE
 -  FOPEN_WRITE_CREATE_STRICT
--  FOPEN_READ_WRITE_CREATE_STRICT
-
+-  FOPEN_READ_WRITE_CREATE_STRICT
\ No newline at end of file
diff --git a/user_guide_src/source/general/routing.rst b/user_guide_src/source/general/routing.rst
index 45950fc..2a03320 100644
--- a/user_guide_src/source/general/routing.rst
+++ b/user_guide_src/source/general/routing.rst
@@ -9,34 +9,34 @@
 	example.com/class/function/id/
 
 In some instances, however, you may want to remap this relationship so
-that a different class/function can be called instead of the one
+that a different class/method can be called instead of the one
 corresponding to the URL.
 
-For example, lets say you want your URLs to have this prototype:
+For example, lets say you want your URLs to have this prototype::
 
-example.com/product/1/
-example.com/product/2/
-example.com/product/3/
-example.com/product/4/
+	example.com/product/1/
+	example.com/product/2/
+	example.com/product/3/
+	example.com/product/4/
 
-Normally the second segment of the URL is reserved for the function
-name, but in the example above it instead has a product ID. To overcome
-this, CodeIgniter allows you to remap the URI handler.
+Normally the second segment of the URL is reserved for the method
+name, but in the example above it instead has a product ID. To
+overcome this, CodeIgniter allows you to remap the URI handler.
 
 Setting your own routing rules
 ==============================
 
-Routing rules are defined in your application/config/routes.php file. In
-it you'll see an array called $route that permits you to specify your
-own routing criteria. Routes can either be specified using wildcards or
-Regular Expressions
+Routing rules are defined in your *application/config/routes.php* file.
+In it you'll see an array called ``$route`` that permits you to specify
+your own routing criteria. Routes can either be specified using wildcards
+or Regular Expressions.
 
 Wildcards
 =========
 
 A typical wildcard route might look something like this::
 
-	$route['product/:num'] = "catalog/product_lookup";
+	$route['product/:num'] = 'catalog/product_lookup';
 
 In a route, the array key contains the URI to be matched, while the
 array value contains the destination it should be re-routed to. In the
@@ -47,31 +47,40 @@
 You can match literal values or you can use two wildcard types:
 
 **(:num)** will match a segment containing only numbers.
- **(:any)** will match a segment containing any character.
+**(:any)** will match a segment containing any character (except for '/', which is the segment delimiter).
+
+.. note:: Wildcards are actually aliases for regular expressions, with
+	**:any** being translated to **[^/]+** and **:num** to **[0-9]+**,
+	respectively.
 
 .. note:: Routes will run in the order they are defined. Higher routes
 	will always take precedence over lower ones.
 
+.. note:: Route rules are not filters! Setting a rule of e.g.
+	'foo/bar/(:num)' will not prevent controller *Foo* and method
+	*bar* to be called with a non-numeric value if that is a valid
+	route.
+
 Examples
 ========
 
 Here are a few routing examples::
 
-	$route['journals'] = "blogs";
+	$route['journals'] = 'blogs';
 
 A URL containing the word "journals" in the first segment will be
 remapped to the "blogs" class.
 
 ::
 
-	$route['blog/joe'] = "blogs/users/34";
+	$route['blog/joe'] = 'blogs/users/34';
 
 A URL containing the segments blog/joe will be remapped to the "blogs"
 class and the "users" method. The ID will be set to "34".
 
 ::
 
-	$route['product/(:any)'] = "catalog/product_lookup";
+	$route['product/(:any)'] = 'catalog/product_lookup';
 
 A URL with "product" as the first segment, and anything in the second
 will be remapped to the "catalog" class and the "product_lookup"
@@ -79,12 +88,12 @@
 
 ::
 
-	$route['product/(:num)'] = "catalog/product_lookup_by_id/$1";
+	$route['product/(:num)'] = 'catalog/product_lookup_by_id/$1';
 
 A URL with "product" as the first segment, and a number in the second
 will be remapped to the "catalog" class and the
 "product_lookup_by_id" method passing in the match as a variable to
-the function.
+the method.
 
 .. important:: Do not use leading/trailing slashes.
 
@@ -99,12 +108,39 @@
 
 A typical RegEx route might look something like this::
 
-	$route['products/([a-z]+)/(\d+)'] = "$1/id_$2";
+	$route['products/([a-z]+)/(\d+)'] = '$1/id_$2';
 
 In the above example, a URI similar to products/shirts/123 would instead
-call the shirts controller class and the id_123 function.
+call the "shirts" controller class and the "id_123" method.
 
-You can also mix and match wildcards with regular expressions.
+With regular expressions, you can also catch a segment containing a
+forward slash ('/'), which would usually represent the delimiter between
+multiple segments.
+For example, if a user accesses a password protected area of your web
+application and you wish to be able to redirect them back to the same
+page after they log in, you may find this example useful::
+
+	$route['login/(.+)'] = 'auth/login/$1';
+
+That will call the "auth" controller class and its ``login()`` method,
+passing everything contained in the URI after *login/* as a parameter.
+
+For those of you who don't know regular expressions and want to learn
+more about them, `regular-expressions.info <http://www.regular-expressions.info/>`
+might be a good starting point.
+
+..note:: You can also mix and match wildcards with regular expressions.
+
+Callbacks
+=========
+
+If you are using PHP >= 5.3 you can use callbacks in place of the normal
+routing rules to process the back-references. Example::
+
+	$route['products/([a-z]+)/edit/(\d+)'] = function ($product_type, $id)
+	{
+		return 'catalog/product_edit/' . strtolower($product_type) . '/' . $id;
+	};
 
 Reserved Routes
 ===============
@@ -125,9 +161,9 @@
 
 This route indicates which controller class should be loaded if the
 requested controller is not found. It will override the default 404
-error page. It won't affect to the show_404() function, which will
-continue loading the default error_404.php file at
-application/errors/error_404.php.
+error page. It won't affect to the ``show_404()`` function, which will
+continue loading the default *error_404.php* file at
+*application/errors/error_404.php*.
 
 .. important:: The reserved routes must come before any wildcard or
-	regular expression routes.
+	regular expression routes.
\ No newline at end of file
diff --git a/user_guide_src/source/general/security.rst b/user_guide_src/source/general/security.rst
index 4d7a213..984ca84 100644
--- a/user_guide_src/source/general/security.rst
+++ b/user_guide_src/source/general/security.rst
@@ -13,38 +13,40 @@
 malicious data can be passed to your application. URIs may only contain
 the following:
 
--  Alpha-numeric text
+-  Alpha-numeric text (latin characters only)
 -  Tilde: ~
 -  Period: .
 -  Colon: :
 -  Underscore: \_
 -  Dash: -
+-  Pipe: |
 
 Register_globals
 =================
 
 During system initialization all global variables are unset, except
-those found in the $_GET, $_POST, and $_COOKIE arrays. The unsetting
-routine is effectively the same as register_globals = off.
+those found in the ``$_GET``, ``$_POST``, and ``$_COOKIE`` arrays.
+The unsetting routine is effectively the same as
+*register_globals = off*.
 
-error_reporting
-================
+display_errors
+==============
 
-In production environments, it is typically desirable to disable PHP's
-error reporting by setting the internal error_reporting flag to a value
+In production environments, it is typically desirable to "disable" PHP's
+error reporting by setting the internal *display_errors* flag to a value
 of 0. This disables native PHP errors from being rendered as output,
 which may potentially contain sensitive information.
 
 Setting CodeIgniter's **ENVIRONMENT** constant in index.php to a value of
 **\'production\'** will turn off these errors. In development mode, it is
 recommended that a value of 'development' is used. More information
-about differentiating between environments can be found on the :doc:`Handling
-Environments <environments>` page.
+about differentiating between environments can be found on the
+:doc:`Handling Environments <environments>` page.
 
 magic_quotes_runtime
-======================
+====================
 
-The magic_quotes_runtime directive is turned off during system
+The *magic_quotes_runtime* directive is turned off during system
 initialization so that you don't have to remove slashes when retrieving
 data from your database.
 
@@ -68,7 +70,7 @@
 =============
 
 CodeIgniter comes with a Cross Site Scripting filter. This filter
-looks for commonly used techniques to embed malicious Javascript into
+looks for commonly used techniques to embed malicious JavaScript into
 your data, or other types of code that attempt to hijack cookies or
 do other malicious things. The XSS Filter is described
 :doc:`here <../libraries/security>`.
@@ -76,15 +78,32 @@
 Validate the data
 =================
 
-CodeIgniter has a :doc:`Form Validation
-Class <../libraries/form_validation>` that assists you in
+CodeIgniter has a :doc:`Form Validation Library
+<../libraries/form_validation>` that assists you in
 validating, filtering, and prepping your data.
 
 Escape all data before database insertion
 =========================================
 
 Never insert information into your database without escaping it.
-Please see the section that discusses
-:doc:`queries <../database/queries>` for more information.
+Please see the section that discusses :doc:`database queries
+<../database/queries>` for more information.
 
+Hide your files
+===============
 
+Another good security practice is to only leave your *index.php*
+and "assets" (e.g. .js, css and image files) under your server's
+*webroot* directory (most commonly named "htdocs/"). These are
+the only files that you would need to be accessible from the web.
+
+Allowing your visitors to see anything else would potentially
+allow them to access sensitive data, execute scripts, etc.
+
+If you're not allowed to do that, you can try using a .htaccess
+file to restrict access to those resources.
+
+CodeIgniter will have an index.html file in all of its
+directories in an attempt to hide some of this data, but have
+it in mind that this is not enough to prevent a serious
+attacker.
\ No newline at end of file
diff --git a/user_guide_src/source/general/styleguide.rst b/user_guide_src/source/general/styleguide.rst
index 925954c..99bc056 100644
--- a/user_guide_src/source/general/styleguide.rst
+++ b/user_guide_src/source/general/styleguide.rst
@@ -52,7 +52,7 @@
 cause unwanted output, PHP errors, or if the latter are suppressed,
 blank pages. For this reason, all PHP files should **OMIT** the closing
 PHP tag, and instead use a comment block to mark the end of file and
-it's location relative to the application root. This allows you to still
+its location relative to the application root. This allows you to still
 identify a file as being complete and not truncated.
 
 **INCORRECT**::
@@ -168,11 +168,11 @@
 	/**
 	 * Encodes string for use in XML
 	 *
-	 * @param	string
+	 * @param	string	$str	Input string
 	 * @return	string
 	 */
 	function xml_encode($str)
-	
+
 ::
 
 	/**
@@ -180,9 +180,7 @@
 	 *
 	 * @var	array
 	 */
-	public $data
-	
-	
+	public $data = array();
 
 Use single line comments within code, leaving a blank line between large
 comment blocks and code.
@@ -308,8 +306,8 @@
 	}
 
 
-See also information regarding
-`typecasting <http://us3.php.net/manual/en/language.types.type-juggling.php#language.types.typecasting>`_,
+See also information regarding `typecasting
+<http://php.net/manual/en/language.types.type-juggling.php#language.types.typecasting>`_,
 which can be quite useful. Typecasting has a slightly different effect
 which may be desirable. When casting a variable as a string, for
 instance, NULL and boolean FALSE variables become empty strings, 0 (and
@@ -338,7 +336,6 @@
 inability for CodeIgniter to send proper headers. In the examples below,
 select the text with your mouse to reveal the incorrect whitespace.
 
-
 Compatibility
 =============
 
@@ -559,16 +556,16 @@
 
 ::
 
-	convert_text()		// public method
-	_convert_text()		// private method
+	public function convert_text()
+	private function _convert_text()
 
 PHP Errors
 ==========
 
 Code must run error free and not rely on warnings and notices to be
 hidden to meet this requirement. For instance, never access a variable
-that you did not set yourself (such as $_POST array keys) without first
-checking to see that it isset().
+that you did not set yourself (such as ``$_POST`` array keys) without first
+checking to see that it ``isset()``.
 
 Make sure that while developing your add-on, error reporting is enabled
 for ALL users, and that display_errors is enabled in the PHP
@@ -579,22 +576,22 @@
 		exit "Enabled";
 	}
 
-On some servers where display_errors is disabled, and you do not have
+On some servers where *display_errors* is disabled, and you do not have
 the ability to change this in the php.ini, you can often enable it with::
 
 	ini_set('display_errors', 1);
 
-**NOTE:** Setting the
-`display_errors <http://us.php.net/manual/en/ref.errorfunc.php#ini.display-errors>`_
-setting with ini_set() at runtime is not identical to having it enabled
-in the PHP environment. Namely, it will not have any effect if the
-script has fatal errors
+.. note:: Setting the `display_errors
+	<http://php.net/manual/en/ref.errorfunc.php#ini.display-errors>`_
+	setting with ``ini_set()`` at runtime is not identical to having
+	it enabled in the PHP environment. Namely, it will not have any
+	effect if the script has fatal errors.
 
 Short Open Tags
 ===============
 
 Always use full PHP opening tags, in case a server does not have
-short_open_tag enabled.
+*short_open_tag* enabled.
 
 **INCORRECT**::
 
@@ -606,6 +603,8 @@
 
 	<?php echo $foo; ?>
 
+.. note:: PHP 5.4 will always have the **<?=** tag available.
+
 One Statement Per Line
 ======================
 
@@ -645,7 +644,7 @@
 SQL Queries
 ===========
 
-MySQL keywords are always capitalized: SELECT, INSERT, UPDATE, WHERE,
+SQL keywords are always capitalized: SELECT, INSERT, UPDATE, WHERE,
 AS, JOIN, ON, IN, etc.
 
 Break up long queries into multiple lines for legibility, preferably
@@ -674,5 +673,4 @@
 prevent PHP errors with mistaken calls and provides common fallback
 values which can save a few lines of code. Example::
 
-	function foo($bar = '', $baz = FALSE)
-
+	function foo($bar = '', $baz = FALSE)
\ No newline at end of file
diff --git a/user_guide_src/source/general/urls.rst b/user_guide_src/source/general/urls.rst
index 6b390b5..d678e4a 100644
--- a/user_guide_src/source/general/urls.rst
+++ b/user_guide_src/source/general/urls.rst
@@ -20,7 +20,6 @@
 
 	example.com/class/function/ID
 
-
 #. The first segment represents the controller **class** that should be
    invoked.
 #. The second segment represents the class **function**, or method, that
@@ -28,9 +27,28 @@
 #. The third, and any additional segments, represent the ID and any
    variables that will be passed to the controller.
 
-The :doc:`URI Class <../libraries/uri>` and the :doc:`URL Helper <../helpers/url_helper>` contain functions that make it
-easy to work with your URI data. In addition, your URLs can be remapped
-using the :doc:`URI Routing <routing>` feature for more flexibility.
+The :doc:`URI Library <../libraries/uri>` and the :doc:`URL Helper
+<../helpers/url_helper>` contain functions that make it easy to work
+with your URI data. In addition, your URLs can be remapped using the
+:doc:`URI Routing <routing>` feature for more flexibility.
+
+Friendly URLs
+=============
+
+As you might guess, since there's a straight relationship between
+URI segments and the controller/method pair that's being called,
+those two determining segments must represent a valid class and
+method name.
+You may however also use dashes in the class/method-representing
+segments, and they will automatically be translated to underscores
+in order to be valid routed segments.
+
+For example::
+
+	example.com/my-settings/change-password/
+
+The above example will route to the ``My_settings`` controller and
+its method ``change_password()``.
 
 Removing the index.php file
 ===========================
@@ -39,7 +57,7 @@
 
 	example.com/index.php/news/article/my_article
 
-If your Apache server has mod_rewrite enabled, you can easily remove this
+If your Apache server has *mod_rewrite* enabled, you can easily remove this
 file by using a .htaccess file with some simple rules. Here is an example
 of such a file, using the "negative" method in which everything is redirected
 except the specified items:
@@ -54,7 +72,10 @@
 In the above example, any HTTP request other than those for existing
 directories and existing files is treated as a request for your index.php file.
 
-.. note:: Note: These specific rules might not work for all server configurations.
+.. note:: These specific rules might not work for all server configurations.
+
+.. note:: Make sure to also exclude from the above rule any assets that you
+	might need to be accessible from the outside world.
 
 Adding a URL Suffix
 ===================
@@ -91,7 +112,7 @@
 
 	index.php?c=controller&m=method
 
-.. note:: If you are using query strings you will have to build
-	your own URLs, rather than utilizing the URL helpers (and other helpers
-	that generate URLs, like some of the form helpers) as these are designed
-	to work with segment based URLs.
+.. note:: If you are using query strings you will have to build your own
+	URLs, rather than utilizing the URL helpers (and other helpers
+	that generate URLs, like some of the form helpers) as these are
+	designed to work with segment based URLs.
\ No newline at end of file
diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst
index 9b7c9da..4b1ab3c 100644
--- a/user_guide_src/source/general/views.rst
+++ b/user_guide_src/source/general/views.rst
@@ -31,20 +31,22 @@
 	</body>
 	</html>
 	
-Then save the file in your application/views/ folder.
+Then save the file in your *application/views/* directory.
 
 Loading a View
 ==============
 
-To load a particular view file you will use the following function::
+To load a particular view file you will use the following method::
 
 	$this->load->view('name');
 
-Where name is the name of your view file. Note: The .php file extension
-does not need to be specified unless you use something other than .php.
+Where name is the name of your view file.
+
+.. note:: The .php file extension does not need to be specified
+	unless you use something other than .php.
 
 Now, open the controller file you made earlier called blog.php, and
-replace the echo statement with the view loading function::
+replace the echo statement with the view loading method::
 
 	<?php
 	class Blog extends CI_Controller {
@@ -54,7 +56,6 @@
 			$this->load->view('blogview');
 		}
 	}
-	?>
 
 If you visit your site using the URL you did earlier you should see your
 new view. The URL was similar to this::
@@ -65,7 +66,7 @@
 ======================
 
 CodeIgniter will intelligently handle multiple calls to
-$this->load->view from within a controller. If more than one call
+``$this->load->view()`` from within a controller. If more than one call
 happens they will be appended together. For example, you may wish to
 have a header view, a menu view, a content view, and a footer view. That
 might look something like this::
@@ -84,32 +85,31 @@
 		}
 
 	}
-	?>
 
 In the example above, we are using "dynamically added data", which you
 will see below.
 
-Storing Views within Sub-folders
-================================
+Storing Views within Sub-directories
+====================================
 
-Your view files can also be stored within sub-folders if you prefer that
-type of organization. When doing so you will need to include the folder
-name loading the view. Example::
+Your view files can also be stored within sub-directories if you prefer
+that type of organization. When doing so you will need to include the
+directory name loading the view. Example::
 
-	$this->load->view('folder_name/file_name');
+	$this->load->view('directory_name/file_name');
 
 Adding Dynamic Data to the View
 ===============================
 
 Data is passed from the controller to the view by way of an **array** or
-an **object** in the second parameter of the view loading function. Here
+an **object** in the second parameter of the view loading method. Here
 is an example using an array::
 
 	$data = array(
-	               'title' => 'My Title',
-	               'heading' => 'My Heading',
-	               'message' => 'My Message'
-	          );
+		'title' => 'My Title',
+		'heading' => 'My Heading',
+		'message' => 'My Message'
+	);
 
 	$this->load->view('blogview', $data);
 
@@ -118,8 +118,8 @@
 	$data = new Someclass();
 	$this->load->view('blogview', $data);
 
-Note: If you use an object, the class variables will be turned into
-array elements.
+.. note:: If you use an object, the class variables will be turned
+	into array elements.
 
 Let's try it with your controller file. Open it add this code::
 
@@ -134,7 +134,6 @@
 			$this->load->view('blogview', $data);
 		}
 	}
-	?>
 
 Now open your view file and change the text to variables that correspond
 to the array keys in your data::
@@ -174,7 +173,6 @@
 			$this->load->view('blogview', $data);
 		}
 	}
-	?>
 
 Now open your view file and create a loop::
 
@@ -200,17 +198,16 @@
 
 .. note:: You'll notice that in the example above we are using PHP's
 	alternative syntax. If you are not familiar with it you can read about
-	it :doc:`here </general/alternative_php>`.
+	it :doc:`here <alternative_php>`.
 
 Returning views as data
 =======================
 
 There is a third **optional** parameter lets you change the behavior of
-the function so that it returns data as a string rather than sending it
+the method so that it returns data as a string rather than sending it
 to your browser. This can be useful if you want to process the data in
-some way. If you set the parameter to true (boolean) it will return
+some way. If you set the parameter to TRUE (boolean) it will return
 data. The default behavior is false, which sends it to your browser.
 Remember to assign it to a variable if you want the data returned::
 
-	$string = $this->load->view('myfile', '', true);
-
+	$string = $this->load->view('myfile', '', TRUE);
\ No newline at end of file
diff --git a/user_guide_src/source/general/welcome.rst b/user_guide_src/source/general/welcome.rst
index b28c3bc..b6f473c 100644
--- a/user_guide_src/source/general/welcome.rst
+++ b/user_guide_src/source/general/welcome.rst
@@ -29,4 +29,4 @@
 -  You do not want to be forced to learn a templating language (although
    a template parser is optionally available if you desire one).
 -  You eschew complexity, favoring simple solutions.
--  You need clear, thorough documentation.
+-  You need clear, thorough documentation.
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/array_helper.rst b/user_guide_src/source/helpers/array_helper.rst
index 15b5e17..9435b3a 100644
--- a/user_guide_src/source/helpers/array_helper.rst
+++ b/user_guide_src/source/helpers/array_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('array');
 
@@ -21,20 +19,19 @@
 element()
 =========
 
-.. php:method:: element($item, $array, $default = NULL)
+.. php:function:: element($item, $array, $default = NULL)
 
-	:param string 	$item: Item to fetch from the array
-	:param array 	$array: Input array
-	:param boolean	$default: What to return if the array isn't valid
-	:returns: NULL on failure or the array item.
-
+	:param	string	$item: Item to fetch from the array
+	:param	array	$array: Input array
+	:param	bool	$default: What to return if the array isn't valid
+	:returns:	NULL on failure or the array item.
 
 Lets you fetch an item from an array. The function tests whether the
 array index is set and whether it has a value. If a value exists it is
 returned. If a value does not exist it returns NULL, or whatever you've
-specified as the default value via the third parameter. Example
+specified as the default value via the third parameter.
 
-::
+Example::
 
 	$array = array(
 		'color'	=> 'red',
@@ -48,21 +45,19 @@
 elements()
 ==========
 
+.. php:function:: elements($items, $array, $default = NULL)
+
+	:param	string	$item: Item to fetch from the array
+	:param	array	$array: Input array
+	:param	bool	$default: What to return if the array isn't valid
+	:returns:	NULL on failure or the array item.
+
 Lets you fetch a number of items from an array. The function tests
 whether each of the array indices is set. If an index does not exist it
 is set to NULL, or whatever you've specified as the default value via
 the third parameter. 
 
-.. php:method:: elements($items, $array, $default = NULL)
-
-	:param string 	$item: Item to fetch from the array
-	:param array 	$array: Input array
-	:param boolean	$default: What to return if the array isn't valid
-	:returns: NULL on failure or the array item.
-
-Example
-
-::
+Example::
 
 	$array = array(
 		'color' => 'red',
@@ -73,9 +68,7 @@
 
 	$my_shape = elements(array('color', 'shape', 'height'), $array);
 
-The above will return the following array
-
-::
+The above will return the following array::
 
 	array(
 		'color' => 'red',
@@ -83,15 +76,12 @@
 		'height' => NULL
 	);
 
-You can set the third parameter to any default value you like
-
+You can set the third parameter to any default value you like.
 ::
 
 	 $my_shape = elements(array('color', 'shape', 'height'), $array, 'foobar');
 
-The above will return the following array
-
-::
+The above will return the following array::
 
 	array(     
 		'color' 	=> 'red',
@@ -99,9 +89,9 @@
 		'height'	=> 'foobar'
 	);
 
-This is useful when sending the $_POST array to one of your Models.
+This is useful when sending the ``$_POST`` array to one of your Models.
 This prevents users from sending additional POST data to be entered into
-your tables
+your tables.
 
 ::
 
@@ -116,15 +106,14 @@
 random_element()
 ================
 
-Takes an array as input and returns a random element from it. Usage
-example
+.. php:function:: random_element($array)
 
-.. php:method:: random_element($array)
+	:param	array	$array: Input array
+	:returns:	string (a random element from the array)
 
-	:param array 	$array: Input array
-	:returns: String - Random element from the array.
+Takes an array as input and returns a random element from it.
 
-::
+Usage example::
 
 	$quotes = array(
 		"I find that the harder I work, the more luck I seem to have. - Thomas Jefferson",
@@ -135,5 +124,4 @@
 		"Chance favors the prepared mind - Louis Pasteur"
 	);
 
-	echo random_element($quotes);
-
+	echo random_element($quotes);
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/captcha_helper.rst b/user_guide_src/source/helpers/captcha_helper.rst
index 48095a1..17462a8 100644
--- a/user_guide_src/source/helpers/captcha_helper.rst
+++ b/user_guide_src/source/helpers/captcha_helper.rst
@@ -11,78 +11,72 @@
 ===================
 
 This helper is loaded using the following code
-
 ::
 
 	$this->load->helper('captcha');
 
 The following functions are available:
 
-create_captcha($data)
-=====================
+create_captcha()
+================
+
+.. php:function:: function create_captcha($data = '', $img_path = '', $img_url = '', $font_path = '')
+
+	:param	array	$data: Array of data for the CAPTCHA
+	:param	string	$img_path: Path to create the image in
+	:param	string	$img_url: URL to the CAPTCHA image folder
+	:param	string	$font_path: Server path to font
+	:returns:	array('word' => $word, 'time' => $now, 'image' => $img)
 
 Takes an array of information to generate the CAPTCHA as input and
 creates the image to your specifications, returning an array of
 associative data about the image.
 
-.. php:method:: function create_captcha($data = '', $img_path = '', $img_url = '', $font_path = '')
-
-	:param array 	$data: array of data for the CAPTCHA
-	:param string 	$img_path: path to create the image in
-	:param string	$img_url: URL to the CAPTCHA image folder
-	:param string	$font_path: server path to font
-	:returns: array('word' => $word, 'time' => $now, 'image' => $img)
-
-
 ::
 
-	[array] (
-		'image' => IMAGE TAG   
-		'time'  => TIMESTAMP (in microtime)   
-		'word'  => CAPTCHA WORD )
+	array(
+		'image'	=> IMAGE TAG
+		'time'	=> TIMESTAMP (in microtime)
+		'word'	=> CAPTCHA WORD
+	)
 
-The "image" is the actual image tag:
-
-::
+The **image** is the actual image tag::
 
 	<img src="http://example.com/captcha/12345.jpg" width="140" height="50" />
 
-
-The "time" is the micro timestamp used as the image name without the
+The **time** is the micro timestamp used as the image name without the
 file extension. It will be a number like this: 1139612155.3422
 
-The "word" is the word that appears in the captcha image, which if not
+The **word** is the word that appears in the captcha image, which if not
 supplied to the function, will be a random string.
 
 Using the CAPTCHA helper
 ------------------------
 
-Once loaded you can generate a captcha like this
+Once loaded you can generate a captcha like this::
 
-::
-
-	$vals = array(     
-		'word'       => 'Random word',     
-		'img_path'   => './captcha/',     
-		'img_url'    => 'http://example.com/captcha/',     
-		'font_path'  => './path/to/fonts/texb.ttf',     
-		'img_width'  => '150',     
-		'img_height' => 30,     
-		'expiration' => 7200     
+	$vals = array(
+		'word'		=> 'Random word',
+		'img_path'	=> './captcha/',
+		'img_url'	=> 'http://example.com/captcha/',
+		'font_path'	=> './path/to/fonts/texb.ttf',
+		'img_width'	=> '150',
+		'img_height'	=> 30,
+		'expiration'	=> 7200
 	);
 
-	$cap = create_captcha($vals); echo $cap['image'];
-
+	$cap = create_captcha($vals);
+	echo $cap['image'];
 
 -  The captcha function requires the GD image library.
--  Only the img_path and img_url are required.
--  If a "word" is not supplied, the function will generate a random
+-  Only the **img_path** and **img_url** are required.
+-  If a **word** is not supplied, the function will generate a random
    ASCII string. You might put together your own word library that you
    can draw randomly from.
 -  If you do not specify a path to a TRUE TYPE font, the native ugly GD
    font will be used.
 -  The "captcha" folder must be writable (666, or 777)
--  The "expiration" (in seconds) signifies how long an image will remain
+-  The **expiration** (in seconds) signifies how long an image will remain
    in the captcha folder before it will be deleted. The default is two
    hours.
 
@@ -90,28 +84,24 @@
 -----------------
 
 In order for the captcha function to prevent someone from submitting,
-you will need to add the information returned from create_captcha()
-function to your database. Then, when the data from the form is
-submitted by the user you will need to verify that the data exists in
-the database and has not expired.
+you will need to add the information returned from ``create_captcha()``
+to your database. Then, when the data from the form is submitted by
+the user you will need to verify that the data exists in the database
+and has not expired.
 
-Here is a table prototype
-
-::
+Here is a table prototype::
 
 	CREATE TABLE captcha (  
 		captcha_id bigint(13) unsigned NOT NULL auto_increment,  
 		captcha_time int(10) unsigned NOT NULL,  
-		ip_address varchar(16) default '0' NOT NULL,  
+		ip_address varchar(45) NOT NULL,  
 		word varchar(20) NOT NULL,  
 		PRIMARY KEY `captcha_id` (`captcha_id`),  
 		KEY `word` (`word`)
 	);
 
 Here is an example of usage with a database. On the page where the
-CAPTCHA will be shown you'll have something like this
-
-::
+CAPTCHA will be shown you'll have something like this::
 
 	$this->load->helper('captcha');
 	$vals = array(     
@@ -134,23 +124,20 @@
 	echo '<input type="text" name="captcha" value="" />';
 
 Then, on the page that accepts the submission you'll have something like
-this
-
-::
+this::
 
 	// First, delete old captchas
 	$expiration = time() - 7200; // Two hour limit
 	$this->db->where('captcha_time < ', $expiration)
-		 ->delete('captcha');
+		->delete('captcha');
 
 	// Then see if a captcha exists:
-	$sql = "SELECT COUNT(*) AS count FROM captcha WHERE word = ? AND ip_address = ? AND captcha_time > ?";
+	$sql = 'SELECT COUNT(*) AS count FROM captcha WHERE word = ? AND ip_address = ? AND captcha_time > ?';
 	$binds = array($_POST['captcha'], $this->input->ip_address(), $expiration);
 	$query = $this->db->query($sql, $binds);
 	$row = $query->row();
 
 	if ($row->count == 0)
 	{     
-		echo "You must submit the word that appears in the image";
-	}
-
+		echo 'You must submit the word that appears in the image.';
+	}
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/cookie_helper.rst b/user_guide_src/source/helpers/cookie_helper.rst
index 30e601c..c41193c 100644
--- a/user_guide_src/source/helpers/cookie_helper.rst
+++ b/user_guide_src/source/helpers/cookie_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('cookie');
 
@@ -21,52 +19,53 @@
 set_cookie()
 ============
 
+.. php:function:: set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = FALSE)
+
+	:param	string	$name: Cookie name
+	:param	string	$value: Cookie value
+	:param	int	$expire: Number of seconds until expiration
+	:param	string	$domain: Cookie domain (usually: .yourdomain.com)
+	:param	string	$path: Cookie path
+	:param	string	$prefix: Cookie name prefix
+	:param	bool	$secure: Whether to only send the cookie through HTTPS
+	:param	bool	$httponly: Whether to hide the cookie from JavaScript
+	:returns:	void
+
 This helper function gives you view file friendly syntax to set browser
-cookies. Refer to the :doc:`Input class <../libraries/input>` for a
-description of use, as this function is an alias to
-`$this->input->set_cookie()`.
-
-.. php:method:: set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE)
-
-	:param string 	$name: the name of the cookie
-	:param string 	$value: the value of the cookie
-	:param string 	$expire: the number of seconds until expiration
-	:param string 	$domain: the cookie domain.  Usually:  .yourdomain.com
-	:param string 	$path: the cookie path
-	:param string 	$prefix: the cookie prefix
-	:param boolean	$secure: secure cookie or not.
-	:returns: void
+cookies. Refer to the :doc:`Input Library <../libraries/input>` for a
+description of its use, as this function is an alias for
+``CI_Input::set_cookie()``.
 
 get_cookie()
 ============
 
+.. php:function:: get_cookie($index = '', $xss_clean = FALSE)
+
+	:param	string	$index: Cookie name
+	:param	bool	$xss_clean: Whether to apply XSS filtering to the returned value
+	:returns:	mixed
+
 This helper function gives you view file friendly syntax to get browser
-cookies. Refer to the :doc:`Input class <../libraries/input>` for a
-description of use, as this function is an alias to `$this->input->cookie()`.
-
-.. php:method:: get_cookie($index = '', $xss_clean = FALSE)
-
-	:param string 	$index: the name of the cookie
-	:param boolean	$xss_clean: If the resulting value should be xss_cleaned or not
-	:returns: mixed
+cookies. Refer to the :doc:`Input Library <../libraries/input>` for a
+description of itsuse, as this function is an alias for ``CI_Input::cookie()``.
 
 delete_cookie()
 ===============
 
-Lets you delete a cookie. Unless you've set a custom path or other
-values, only the name of the cookie is needed
+.. php:function:: delete_cookie($name = '', $domain = '', $path = '/', $prefix = '')
 
-.. php:method:: delete_cookie($name = '', $domain = '', $path = '/', $prefix = '')
-
-	:param string 	$name: the name of the cookie
-	:param string 	$domain: cookie domain (ususally .example.com)
-	:param string 	$path: cookie path
-	:param string 	$prefix: cookie prefix
+	:param	string	$name: Cookie name
+	:param	string	$domain: Cookie domain (usually: .yourdomain.com)
+	:param	string	$path: Cookie path
+	:param	string	$prefix: Cookie name prefix
 	:returns: void
 
+Lets you delete a cookie. Unless you've set a custom path or other
+values, only the name of the cookie is needed.
+
 ::
 
-	delete_cookie("name");
+	delete_cookie('name');
 
 This function is otherwise identical to ``set_cookie()``, except that it
 does not have the value and expiration parameters. You can submit an
diff --git a/user_guide_src/source/helpers/date_helper.rst b/user_guide_src/source/helpers/date_helper.rst
index e332a91..3a3454e 100644
--- a/user_guide_src/source/helpers/date_helper.rst
+++ b/user_guide_src/source/helpers/date_helper.rst
@@ -9,9 +9,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('date');
 
@@ -20,44 +18,45 @@
 now()
 =====
 
-Returns the current time as a Unix timestamp, referenced either to your
-server's local time or any PHP suported timezone, based on the "time reference"
-setting in your config file. If you do not intend to set your master time reference
-to any other PHP suported timezone (which you'll typically do if you run a site that
-lets each user set their own timezone settings) there is no benefit to using this
-function over PHP's time() function.
+.. php:function:: now($timezone = NULL)
 
-.. php:method:: now($timezone = NULL)
+	:param	string	$timezone: Timezone
+	:returns:	int
 
-	:param string 	$timezone: The timezone you want to be returned
-	:returns: integer
+Returns the current time as a UNIX timestamp, referenced either to your server's
+local time or any PHP suported timezone, based on the "time reference" setting
+in your config file. If you do not intend to set your master time reference to
+any other PHP supported timezone (which you'll typically do if you run a site
+that lets each user set their own timezone settings) there is no benefit to using
+this function over PHP's ``time()`` function.
 
 ::
-	echo now("Australia/Victoria");
 
-If a timezone is not provided, it will return time() based on "time_reference" setting.
+	echo now('Australia/Victoria');
+
+If a timezone is not provided, it will return ``time()`` based on the
+**time_reference** setting.
 
 mdate()
 =======
 
+.. php:function:: mdate($datestr = '', $time = '')
+
+	:param	string 	$datestr: Date string
+	:param	int 	$time: UNIX timestamp
+	:returns:	int
+
 This function is identical to PHP's `date() <http://www.php.net/date>`_
 function, except that it lets you use MySQL style date codes, where each
-code letter is preceded with a percent sign: %Y %m %d etc.
+code letter is preceded with a percent sign, e.g. `%Y %m %d`
 
 The benefit of doing dates this way is that you don't have to worry
 about escaping any characters that are not date codes, as you would
-normally have to do with the date() function. Example
+normally have to do with the ``date()`` function.
 
-.. php:method:: mdate($datestr = '', $time = '')
+Example::
 
-	:param string 	$datestr: Date String
-	:param integer 	$time: time
-	:returns: integer
-
-
-::
-
-	$datestring = "Year: %Y Month: %m Day: %d - %h:%i %a";
+	$datestring = 'Year: %Y Month: %m Day: %d - %h:%i %a';
 	$time = time();
 	echo mdate($datestring, $time);
 
@@ -67,32 +66,30 @@
 standard_date()
 ===============
 
+.. php:function:: standard_date($fmt = 'DATE_RFC822', $time = NULL)
+
+	:param	string	$fmt: Date format
+	:param	int 	$time: UNIX timestamp
+	:returns:	string
+
 Lets you generate a date string in one of several standardized formats.
-Example
 
-.. php:method:: standard_date($fmt = 'DATE_RFC822', $time = '')
-
-	:param string 	$fmt: the chosen format
-	:param string 	$time: Unix timestamp
-	:returns: string
-
-::
+Example::
 
 	$format = 'DATE_RFC822';
 	$time = time();
 	echo standard_date($format, $time);
 
-The first parameter must contain the format, the second parameter must
-contain the date as a Unix timestamp.
-
-.. note:: This function is DEPRECATED. Use the native ``date()`` combined
-	with `DateTime's format constants <http://www.php.net/manual/en/class.datetime.php#datetime.constants.types>`_
+.. note:: This function is DEPRECATED.Use the native ``date()`` combined with
+	`DateTime's format constants
+	<http://www.php.net/manual/en/class.datetime.php#datetime.constants.types>`_
 	instead:
 
 	|
 	| echo date(DATE_RFC822, time());
 
-Supported formats:
+Supported formats
+-----------------
 
 ===============	=======================	======================================
 Constant		Description				Example
@@ -112,39 +109,34 @@
 local_to_gmt()
 ==============
 
-Takes a Unix timestamp as input and returns it as GMT.
+.. php:function:: local_to_gmt($time = '')
 
-.. php:method:: local_to_gmt($time = '')
+	:param	int	$time: UNIX timestamp
+	:returns:	string
 
-	:param integer 	$time: Unix timestamp
-	:returns: string
+Takes a UNIX timestamp as input and returns it as GMT.
 
-Example:
+Example::
 
-::
-
-	$now = time();
-	$gmt = local_to_gmt($now);
+	$gmt = local_to_gmt(time());
 
 gmt_to_local()
 ==============
 
-Takes a Unix timestamp (referenced to GMT) as input, and converts it to
-a localized timestamp based on the timezone and Daylight Saving time
+.. php:function:: gmt_to_local($time = '', $timezone = 'UTC', $dst = FALSE)
+
+	:param	int 	$time: UNIX timestamp
+	:param	string	$timezone: Timezone
+	:param	bool 	$dst: Whether DST is active
+	:returns:	int
+
+Takes a UNIX timestamp (referenced to GMT) as input, and converts it to
+a localized timestamp based on the timezone and Daylight Saving Time
 submitted.
 
-.. php:method:: gmt_to_local($time = '', $timezone = 'UTC', $dst = FALSE)
+Example::
 
-	:param integer 	$time: Unix timestamp
-	:param string 	$timezone: timezone
-	:param boolean 	$dst: whether DST is active
-	:returns: integer
-
-Example
-
-::
-
-	$timestamp = '1140153693';
+	$timestamp = 1140153693;
 	$timezone  = 'UM8';
 	$daylight_saving = TRUE;
 	echo gmt_to_local($timestamp, $timezone, $daylight_saving);
@@ -152,40 +144,32 @@
 
 .. note:: For a list of timezones see the reference at the bottom of this page.
 
-
 mysql_to_unix()
 ===============
 
-Takes a MySQL Timestamp as input and returns it as Unix.
+.. php:function:: mysql_to_unix($time = '')
 
-.. php:method:: mysql_to_unix($time = '')
+	:param	int 	$time: UNIX timestamp
+	:returns:	int
 
-	:param integer 	$time: Unix timestamp
-	:returns: integer
+Takes a MySQL Timestamp as input and returns it as a UNIX timestamp.
 
-Example
+Example::
 
-::
-
-	$mysql = '20061124092345';
-	$unix = mysql_to_unix($mysql);
+	$unix = mysql_to_unix('20061124092345');
 
 unix_to_human()
 ===============
 
-Takes a Unix timestamp as input and returns it in a human readable
-format with this prototype
+.. php:function:: unix_to_human($time = '', $seconds = FALSE, $fmt = 'us')
 
-.. php:method:: unix_to_human($time = '', $seconds = FALSE, $fmt = 'us')
-
-	:param integer 	$time: Unix timestamp
-	:param boolean 	$seconds: whether to show seconds
-	:param string 	$fmt: format: us or euro
+	:param	int	$time: UNIX timestamp
+	:param	bool	$seconds: Whether to show seconds
+	:param	string	$fmt: format (us or euro)
 	:returns: integer
 
-Example
-
-::
+Takes a UNIX timestamp as input and returns it in a human readable
+format with this prototype::
 
 	YYYY-MM-DD HH:MM:SS AM/PM
 
@@ -194,9 +178,9 @@
 
 The time can be formatted with or without seconds, and it can be set to
 European or US format. If only the timestamp is submitted it will return
-the time without seconds formatted for the U.S. Examples
+the time without seconds formatted for the U.S.
 
-::
+Examples::
 
 	$now = time();
 	echo unix_to_human($now); // U.S. time, no seconds
@@ -206,19 +190,17 @@
 human_to_unix()
 ===============
 
-The opposite of the above function. Takes a "human" time as input and
-returns it as Unix. This function is useful if you accept "human"
-formatted dates submitted via a form. Returns FALSE (boolean) if the
+.. php:function:: human_to_unix($datestr = '')
+
+	:param	int 	$datestr: Date string
+	:returns:	int UNIX timestamp or FALSE on failure
+
+The opposite of the :php:func:`unix_to_time()` function. Takes a "human"
+time as input and returns it as a UNIX timestamp. This is useful if you
+accept "human" formatted dates submitted via a form. Returns boolean FALSE
 date string passed to it is not formatted as indicated above.
 
-.. php:method:: human_to_unix($datestr = '')
-
-	:param integer 	$datestr: Date String
-	:returns: integer
-
-Example:
-
-::
+Example::
 
 	$now = time();
 	$human = unix_to_human($now);
@@ -227,22 +209,20 @@
 nice_date()
 ===========
 
+.. php:function:: nice_date($bad_date = '', $format = FALSE)
+
+	:param	int	$bad_date: The terribly formatted date-like string
+	:param	string	$format: Date format to return (same as PHP's ``date()`` function)
+	:returns:	string
+
 This function can take a number poorly-formed date formats and convert
 them into something useful. It also accepts well-formed dates.
 
-The function will return a Unix timestamp by default. You can,
-optionally, pass a format string (the same type as the PHP date function
-accepts) as the second parameter.
+The function will return a UNIX timestamp by default. You can, optionally,
+pass a format string (the same type as the PHP ``date()`` function accepts)
+as the second parameter.
 
-.. php:method:: nice_date($bad_date = '', $format = FALSE)
-
-	:param integer 	$bad_date: The terribly formatted date-like string
-	:param string 	$format: Date format to return (same as php date function)
-	:returns: string
-
-Example
-
-::
+Example::
 
 	$bad_date = '199605';
 	// Should Produce: 1996-05-01
@@ -255,28 +235,28 @@
 timespan()
 ==========
 
-Formats a unix timestamp so that is appears similar to this
-::
+.. php:function:: timespan($seconds = 1, $time = '', $units = '')
+
+	:param	int	$seconds: Number of seconds
+	:param	string	$time: UNIX timestamp
+	:param	int	$units: Number of time units to display
+	:returns:	string
+
+Formats a UNIX timestamp so that is appears similar to this::
 
 	1 Year, 10 Months, 2 Weeks, 5 Days, 10 Hours, 16 Minutes
 
-The first parameter must contain a Unix timestamp. The second parameter
-must contain a timestamp that is greater that the first timestamp. If
-the second parameter empty, the current time will be used. The third
-parameter is optional and limits the number of time units to display.
+The first parameter must contain a UNIX timestamp.
+The second parameter must contain a timestamp that is greater that the
+first timestamp.
+The thirdparameter is optional and limits the number of time units to display.
+
+If the second parameter empty, the current time will be used.
+
 The most common purpose for this function is to show how much time has
 elapsed from some point in time in the past to now.
 
-.. php:method:: timespan($seconds = 1, $time = '', $units = '')
-
-	:param integer 	$seconds: a number of seconds
-	:param string 	$time: Unix timestamp
-	:param integer 	$units: a number of time units to display
-	:returns: string
-
-Example
-
-::
+Example::
 
 	$post_date = '1079621429';
 	$now = time();
@@ -284,54 +264,79 @@
 	echo timespan($post_date, $now, $units);
 
 .. note:: The text generated by this function is found in the following language
-	file: language/<your_lang>/date_lang.php
+	file: `language/<your_lang>/date_lang.php`
 
 days_in_month()
 ===============
 
+.. php:function:: days_in_month($month = 0, $year = '')
+
+	:param	int	$month: a numeric month
+	:param	int	$year: a numeric year
+	:returns:	int
+
 Returns the number of days in a given month/year. Takes leap years into
 account.
 
-.. php:method:: days_in_month($month = 0, $year = '')
-
-	:param integer 	$month: a numeric month
-	:param integer 	$year: a numeric year
-	:returns: integer
-
-Example
-
-::
+Example::
 
 	echo days_in_month(06, 2005);
 
 If the second parameter is empty, the current year will be used.
 
+date_range()
+============
+
+.. php:function:: date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d')
+
+	:param	int	$unix_start: UNIX timestamp of the range start date
+	:param	int	$mixed: UNIX timestamp of the range end date or interval in days
+	:param	bool	$is_unix: set to FALSE if $mixed is not a timestamp
+	:param	string	$format: Output date format, same as in ``date()``
+	:returns:	array
+
+Returns a list of dates within a specified period.
+
+Example::
+
+	$range = date_range('2012-01-01', '2012-01-15');
+	echo "First 15 days of 2012:";
+	foreach ($range as $date)
+	{
+		echo $date."\n";
+	}
+
 timezones()
 ===========
 
+.. php:function:: timezones($tz = '')
+
+	:param	string	$tz: a numeric timezone
+	:returns:	string
+
 Takes a timezone reference (for a list of valid timezones, see the
 "Timezone Reference" below) and returns the number of hours offset from
 UTC.
 
-.. php:method:: timezones($tz = '')
-
-	:param string 	$tz: a numeric timezone
-	:returns: string
-
-Example
-
-::
+Example::
 
 	echo timezones('UM5');
 
 
-This function is useful when used with `timezone_menu()`.
+This function is useful when used with :php:func:`timezone_menu()`.
 
 timezone_menu()
 ===============
 
-Generates a pull-down menu of timezones, like this one:
+.. php:function:: timezone_menu($default = 'UTC', $class = '', $name = 'timezones', $attributes = '')
 
+	:param	string	$default: Timezone
+	:param	string	$class: Class name
+	:param	string	$name: Menu name
+	:param	mixed	$attributes: HTML attributes
+	:returns:	string
+
+Generates a pull-down menu of timezones, like this one:
 
 .. raw:: html
 
@@ -385,19 +390,7 @@
 allowed to set their local timezone value.
 
 The first parameter lets you set the "selected" state of the menu. For
-example, to set Pacific time as the default you will do this
-
-.. php:method:: timezone_menu($default = 'UTC', $class = '', $name = 'timezones', $attributes = '')
-
-	:param string 	$default: timezone
-	:param string	$class: classname
-	:param string	$name: menu name
-	:param mixed	$attributes: attributes
-	:returns: string
-
-Example:
-
-::
+example, to set Pacific time as the default you will do this::
 
 	echo timezone_menu('UM8');
 
@@ -421,37 +414,37 @@
 ===========	=====================================================================
 Time Zone	Location
 ===========	=====================================================================
-UM2			(UTC - 12:00) Baker/Howland Island
-UM1			(UTC - 11:00) Samoa Time Zone, Niue
-UM0			(UTC - 10:00) Hawaii-Aleutian Standard Time, Cook Islands
+UM2		(UTC - 12:00) Baker/Howland Island
+UM1		(UTC - 11:00) Samoa Time Zone, Niue
+UM0		(UTC - 10:00) Hawaii-Aleutian Standard Time, Cook Islands
 UM95		(UTC - 09:30) Marquesas Islands
-UM9			(UTC - 09:00) Alaska Standard Time, Gambier Islands
-UM8			(UTC - 08:00) Pacific Standard Time, Clipperton Island
-UM7			(UTC - 11:00) Mountain Standard Time
-UM6			(UTC - 06:00) Central Standard Time
-UM5			(UTC - 05:00) Eastern Standard Time, Western Caribbean
+UM9		(UTC - 09:00) Alaska Standard Time, Gambier Islands
+UM8		(UTC - 08:00) Pacific Standard Time, Clipperton Island
+UM7		(UTC - 11:00) Mountain Standard Time
+UM6		(UTC - 06:00) Central Standard Time
+UM5		(UTC - 05:00) Eastern Standard Time, Western Caribbean
 UM45		(UTC - 04:30) Venezuelan Standard Time
-UM4			(UTC - 04:00) Atlantic Standard Time, Eastern Caribbean
+UM4		(UTC - 04:00) Atlantic Standard Time, Eastern Caribbean
 UM35		(UTC - 03:30) Newfoundland Standard Time
-UM3			(UTC - 03:00) Argentina, Brazil, French Guiana, Uruguay
-UM2			(UTC - 02:00) South Georgia/South Sandwich Islands
-UM			(UTC -1:00) Azores, Cape Verde Islands
-UTC			(UTC) Greenwich Mean Time, Western European Time
-UP1			(UTC +1:00) Central European Time, West Africa Time
-UP2			(UTC +2:00) Central Africa Time, Eastern European Time
-UP3			(UTC +3:00) Moscow Time, East Africa Time
+UM3		(UTC - 03:00) Argentina, Brazil, French Guiana, Uruguay
+UM2		(UTC - 02:00) South Georgia/South Sandwich Islands
+UM		(UTC -1:00) Azores, Cape Verde Islands
+UTC		(UTC) Greenwich Mean Time, Western European Time
+UP1		(UTC +1:00) Central European Time, West Africa Time
+UP2		(UTC +2:00) Central Africa Time, Eastern European Time
+UP3		(UTC +3:00) Moscow Time, East Africa Time
 UP35		(UTC +3:30) Iran Standard Time
-UP4			(UTC +4:00) Azerbaijan Standard Time, Samara Time
+UP4		(UTC +4:00) Azerbaijan Standard Time, Samara Time
 UP45		(UTC +4:30) Afghanistan
-UP5			(UTC +5:00) Pakistan Standard Time, Yekaterinburg Time
+UP5		(UTC +5:00) Pakistan Standard Time, Yekaterinburg Time
 UP55		(UTC +5:30) Indian Standard Time, Sri Lanka Time
 UP575		(UTC +5:45) Nepal Time
-UP6			(UTC +6:00) Bangladesh Standard Time, Bhutan Time, Omsk Time
+UP6		(UTC +6:00) Bangladesh Standard Time, Bhutan Time, Omsk Time
 UP65		(UTC +6:30) Cocos Islands, Myanmar
-UP7			(UTC +7:00) Krasnoyarsk Time, Cambodia, Laos, Thailand, Vietnam
-UP8			(UTC +8:00) Australian Western Standard Time, Beijing Time
+UP7		(UTC +7:00) Krasnoyarsk Time, Cambodia, Laos, Thailand, Vietnam
+UP8		(UTC +8:00) Australian Western Standard Time, Beijing Time
 UP875		(UTC +8:45) Australian Central Western Standard Time
-UP9			(UTC +9:00) Japan Standard Time, Korea Standard Time, Yakutsk
+UP9		(UTC +9:00) Japan Standard Time, Korea Standard Time, Yakutsk
 UP95		(UTC +9:30) Australian Central Standard Time
 UP10		(UTC +10:00) Australian Eastern Standard Time, Vladivostok Time
 UP105		(UTC +10:30) Lord Howe Island
@@ -459,6 +452,6 @@
 UP115		(UTC +11:30) Norfolk Island
 UP12		(UTC +12:00) Fiji, Gilbert Islands, Kamchatka, New Zealand
 UP1275		(UTC +12:45) Chatham Islands Standard Time
-UP1			(UTC +13:00) Phoenix Islands Time, Tonga
+UP1		(UTC +13:00) Phoenix Islands Time, Tonga
 UP14		(UTC +14:00) Line Islands
 ===========	=====================================================================
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/directory_helper.rst b/user_guide_src/source/helpers/directory_helper.rst
index cf88732..a785ebc 100644
--- a/user_guide_src/source/helpers/directory_helper.rst
+++ b/user_guide_src/source/helpers/directory_helper.rst
@@ -57,7 +57,7 @@
 			(        
 				[0] => benchmark.html        
 				[1] => config.html        
-				[database] => Array
+				["database/"] => Array
 					(              
 						[0] => query_builder.html              
 						[1] => binds.html              
@@ -76,5 +76,4 @@
 				[7] => loader.html        
 				[8] => pagination.html        
 				[9] => uri.html
-			)
-
+			)
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/download_helper.rst b/user_guide_src/source/helpers/download_helper.rst
index e6094dc..860c568 100644
--- a/user_guide_src/source/helpers/download_helper.rst
+++ b/user_guide_src/source/helpers/download_helper.rst
@@ -9,34 +9,42 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('download');
 
 The following functions are available:
 
-force_download('filename', 'data')
-==================================
+force_download()
+================
+
+.. php:function:: force_download($filename = '', $data = '', $set_mime = FALSE)
+
+	:param	string	$filename: Filename
+	:param	mixed	$data: File contents
+	:param	bool	$set_mime: Whether to try to send the actual MIME type
+	:returns:	void
 
 Generates server headers which force data to be downloaded to your
 desktop. Useful with file downloads. The first parameter is the **name
 you want the downloaded file to be named**, the second parameter is the
-file data. Example
+file data.
 
-::
+If you set the second parameter to NULL and ``$filename`` is an existing, readable
+file path, then its content will be read instead.
+
+If you set the third parameter to boolean TRUE, then the actual file MIME type
+(based on the filename extension) will be sent, so that if your browser has a
+handler for that type - it can use it.
+
+Example::
 
 	$data = 'Here is some text!';
 	$name = 'mytext.txt';
 	force_download($name, $data);
 
 If you want to download an existing file from your server you'll need to
-read the file into a string
+do the following::
 
-::
-
-	$data = file_get_contents("/path/to/photo.jpg"); // Read the file's contents
-	$name = 'myphoto.jpg';
-	force_download($name, $data);
-
+	// Contents of photo.jpg will be automatically read
+	force_download('/path/to/photo.jpg', NULL);
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/email_helper.rst b/user_guide_src/source/helpers/email_helper.rst
index d4e94b1..10adf1d 100644
--- a/user_guide_src/source/helpers/email_helper.rst
+++ b/user_guide_src/source/helpers/email_helper.rst
@@ -8,6 +8,8 @@
 
 .. contents:: Page Contents
 
+.. important:: The Email helper is DEPRECATED.
+
 Loading this Helper
 ===================
 
@@ -15,21 +17,21 @@
 
 	$this->load->helper('email');
 
-
 The following functions are available:
 
-valid_email('email')
-====================
+valid_email()
+=============
 
-Checks if an email is a correctly formatted email. Note that is doesn't
-actually prove the email will recieve mail, simply that it is a validly
-formed address.
+.. php:function:: valid_email($email)
 
-It returns TRUE/FALSE
+	:param	string	$email: Email address
+	:returns:	bool
 
-::
+Checks if the input is a correctly formatted e-mail address. Note that is
+doesn't actually prove that the address will be able recieve mail, but
+simply that it is a validly formed address.
 
-	$this->load->helper('email');
+Example::
 
 	if (valid_email('email@somesite.com'))
 	{
@@ -40,10 +42,26 @@
 		echo 'email is not valid';
 	}
 
-send_email('recipient', 'subject', 'message')
-=============================================
+.. note:: All that this function does is to use PHP's native ``filter_var()``:
+	|
+	| (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
 
-Sends an email using PHP's native
-`mail() <http://www.php.net/function.mail>`_ function. For a more robust
-email solution, see CodeIgniter's :doc:`Email
-Class <../libraries/email>`.
+send_email()
+============
+
+.. php:function:: send_email($recipient, $subject, $message)
+
+	:param	string	$recipient: E-mail address
+	:param	string	$subject: Mail subject
+	:param	string	$message: Message body
+	:returns:	bool
+
+Sends an email using PHP's native `mail() <http://www.php.net/function.mail>`_
+function.
+
+.. note:: All that this function does is to use PHP's native ``mail``:
+	|
+	| mail($recipient, $subject, $message);
+
+For a more robust email solution, see CodeIgniter's :doc:`Email Library
+<../libraries/email>`.
diff --git a/user_guide_src/source/helpers/file_helper.rst b/user_guide_src/source/helpers/file_helper.rst
index 60c5aa9..194d434 100644
--- a/user_guide_src/source/helpers/file_helper.rst
+++ b/user_guide_src/source/helpers/file_helper.rst
@@ -9,20 +9,23 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('file');
 
 The following functions are available:
 
-read_file('path')
-=================
+read_file()
+===========
 
-Returns the data contained in the file specified in the path. Example
+.. php:function:: read_file($file)
 
-::
+	:param	string	$file: File path
+	:returns:	string or FALSE on failure
+
+Returns the data contained in the file specified in the path.
+
+Example::
 
 	$string = read_file('./path/to/file.php');
 
@@ -35,14 +38,24 @@
 .. note:: This function is DEPRECATED. Use the native ``file_get_contents()``
 	instead.
 
-If your server is running an `open_basedir` restriction this function might not work if you are trying to access a file above the calling script.
+.. important:: If your server is running an **open_basedir** restriction this
+	function might not work if you are trying to access a file above the
+	calling script.
 
-write_file('path', $data)
-=========================
+write_file()
+============
 
-Writes data to the file specified in the path. If the file does not exist the function will create it. Example
+.. php:function:: write_file($path, $data, $mode = 'wb')
 
-::
+	:param	string	$path: File path
+	:param	string	$data: Data to write to file
+	:param	string	$mode: ``fopen()`` mode
+	:returns:	bool
+
+Writes data to the file specified in the path. If the file does not exist then the
+function will create it.
+
+Example::
 
 	$data = 'Some file data';
 	if ( ! write_file('./path/to/file.php', $data))
@@ -54,85 +67,150 @@
 		echo 'File written!';
 	}
 
-You can optionally set the write mode via the third parameter
-
-::
+You can optionally set the write mode via the third parameter::
 
 	write_file('./path/to/file.php', $data, 'r+');
 
-The default mode is wb. Please see the `PHP user guide <http://php.net/fopen>`_ for mode options.
+The default mode is 'wb'. Please see the `PHP user guide <http://php.net/fopen>`_
+for mode options.
 
-Note: In order for this function to write data to a file its file permissions must be set such that it is writable (666, 777, etc.). If the file does not already exist, the directory containing it must be writable.
+.. note: In order for this function to write data to a file, its permissions must
+	be set such that it is writable (666, 777, etc.). If the file does not
+	already exist, the directory containing it must be writable.
 
 .. note:: The path is relative to your main site index.php file, NOT your
 	controller or view files. CodeIgniter uses a front controller so paths
 	are always relative to the main site index.
 
-delete_files('path')
-====================
+.. note:: This function acquires an exclusive lock on the file while writing to it.
 
-Deletes ALL files contained in the supplied path. Example
+delete_files()
+==============
 
-::
+.. php:function:: delete_files($path, $del_dir = FALSE, $htdocs = FALSE)
+
+	:param	string	$path: Directory path
+	:param	bool	$del_dir: Whether to also delete directories
+	:param	bool	$htdocs: Whether to skip deleting .htaccess and index page files
+	:returns:	bool
+
+Deletes ALL files contained in the supplied path.
+
+Example::
 
 	delete_files('./path/to/directory/');
 
-If the second parameter is set to true, any directories contained within the supplied root path will be deleted as well. Example
+If the second parameter is set to TRUE, any directories contained within the supplied
+root path will be deleted as well.
 
-::
+Example::
 
 	delete_files('./path/to/directory/', TRUE);
 
 .. note:: The files must be writable or owned by the system in order to be deleted.
 
-get_filenames('path/to/directory/')
-===================================
+get_filenames()
+===============
 
-Takes a server path as input and returns an array containing the names of all files contained within it. The file path can optionally be added to the file names by setting the second parameter to TRUE.
+.. php:function:: get_filenames($source_dir, $include_path = FALSE)
 
-get_dir_file_info('path/to/directory/', $top_level_only = TRUE)
-===============================================================
+	:param	string	$source_dir: Directory path
+	:param	bool	$include_path: Whether to include the path as part of the filenames
+	:returns:	array
 
-Reads the specified directory and builds an array containing the filenames, filesize, dates, and permissions. Sub-folders contained within the specified path are only read if forced by sending the second parameter, $top_level_only to FALSE, as this can be an intensive operation.
+Takes a server path as input and returns an array containing the names of all files
+contained within it. The file path can optionally be added to the file names by setting
+the second parameter to TRUE.
 
-get_file_info('path/to/file', $file_information)
-================================================
+Example::
 
-Given a file and path, returns the name, path, size, date modified. Second parameter allows you to explicitly declare what information you want returned; options are: `name`, `server_path`, `size`, `date`, `readable`, `writable`, `executable`, `fileperms`. Returns FALSE if the file cannot be found.
+	$controllers = get_filenames(APPPATH.'controllers/');
 
-.. note:: The "writable" uses the PHP function is_writable() which is known
-	to have issues on the IIS webserver. Consider using fileperms instead,
-	which returns information from PHP's fileperms() function.
+get_dir_file_info()
+===================
 
-get_mime_by_extension('file')
-=============================
+.. php:function:: get_dir_file_info($source_dir, $top_level_only)
 
-Translates a file extension into a mime type based on config/mimes.php. Returns FALSE if it can't determine the type, or open the mime config file.
+	:param	string	$source_dir: Directory path
+	:param	bool	$top_level_only: Whether to look only at the specified directory
+			(excluding sub-directories)
+	:returns:	array
+
+Reads the specified directory and builds an array containing the filenames, filesize,
+dates, and permissions. Sub-folders contained within the specified path are only read
+if forced by sending the second parameter to FALSE, as this can be an intensive
+operation.
+
+Example::
+
+	$models_info = get_dir_file_info(APPPATH.'models/');
+
+get_file_info()
+===============
+
+.. php:function: get_file_info($file, $returned_values = array('name', 'server_path', 'size', 'date'))
+
+	:param	string	$file: File path
+	:param	array	$returned_values: What type of info to return
+	:returns:	array or FALSE on failure
+
+Given a file and path, returns (optionally) the *name*, *path*, *size* and *date modified*
+information attributes for a file. Second parameter allows you to explicitly declare what
+information you want returned.
+
+Valid ``$returned_values`` options are: `name`, `size`, `date`, `readable`, `writeable`,
+`executable` and `fileperms`.
+
+.. note:: The *writable* attribute is checked via PHP's ``is_writeable()`` function, which
+	known to have issues on the IIS webserver. Consider using *fileperms* instead,
+	which returns information from PHP's ``fileperms()`` function.
+
+get_mime_by_extension()
+=======================
+
+.. php:function:: get_mime_by_extension($filename)
+
+	:param	string	$filename: File name
+	:returns:	string or FALSE on failure
+
+Translates a filename extension into a MIME type based on *config/mimes.php*.
+Returns FALSE if it can't determine the type, or read the MIME config file.
 
 ::
 
-	$file = "somefile.png";
-	echo $file . ' is has a mime type of ' . get_mime_by_extension($file);
+	$file = 'somefile.png';
+	echo $file.' is has a mime type of '.get_mime_by_extension($file);
 
+.. note:: This is not an accurate way of determining file MIME types, and
+	is here strictly for convenience. It should not be used for security
+	purposes.
 
-.. note:: This is not an accurate way of determining file mime types, and
-	is here strictly as a convenience. It should not be used for security.
+symbolic_permissions()
+======================
 
-symbolic_permissions($perms)
-============================
+.. php:function:: symbolic_permissions($perms)
 
-Takes numeric permissions (such as is returned by `fileperms()` and returns standard symbolic notation of file permissions.
+	:param	int	$perms: Permissions
+	:returns:	string
+
+Takes numeric permissions (such as is returned by ``fileperms()``) and returns
+standard symbolic notation of file permissions.
 
 ::
 
 	echo symbolic_permissions(fileperms('./index.php'));  // -rw-r--r--
 
-octal_permissions($perms)
-=========================
+octal_permissions()
+===================
 
-Takes numeric permissions (such as is returned by fileperms() and returns a three character octal notation of file permissions.
+.. php:function:: octal_permissions($perms)
+
+	:param	int	$perms: Permissions
+	:returns:	string
+
+Takes numeric permissions (such as is returned by ``fileperms()``) and returns
+a three character octal notation of file permissions.
 
 ::
 
-	echo octal_permissions(fileperms('./index.php'));  // 644
-
+	echo octal_permissions(fileperms('./index.php')); // 644
\ 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 fa7b3db..b2a9b6f 100644
--- a/user_guide_src/source/helpers/form_helper.rst
+++ b/user_guide_src/source/helpers/form_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('form');
 
@@ -21,19 +19,27 @@
 form_open()
 ===========
 
-Creates an opening form tag with a base URL **built from your config preferences**. It will optionally let you add form attributes and hidden input fields, and will always add the attribute accept-charset based on the charset value in your config file.
+.. php:function:: form_open($action = '', $attributes = '', $hidden = array())
 
-The main benefit of using this tag rather than hard coding your own HTML is that it permits your site to be more portable in the event your URLs ever change.
+	:param	string	$action: Form action/target URI string
+	:param	string	$attributes: HTML attributes
+	:param	array	$hidden: An array of hidden fields' definitions
+	:returns:	string
 
-Here's a simple example
+Creates an opening form tag with a base URL **built from your config preferences**.
+It will optionally let you add form attributes and hidden input fields, and
+will always add the `accept-charset` attribute based on the charset value in your
+config file.
 
-::
+The main benefit of using this tag rather than hard coding your own HTML is that
+it permits your site to be more portable in the event your URLs ever change.
+
+Here's a simple example::
 
 	echo form_open('email/send');
 
-The above example would create a form that points to your base URL plus the "email/send" URI segments, like this
-
-::
+The above example would create a form that points to your base URL plus the
+"email/send" URI segments, like this::
 
 	<form method="post" accept-charset="utf-8" action="http://example.com/index.php/email/send" />
 
@@ -41,32 +47,25 @@
 ^^^^^^^^^^^^^^^^^
 
 Attributes can be added by passing an associative array to the second
-parameter, like this
-
-::
+parameter, like this::
 
 	$attributes = array('class' => 'email', 'id' => 'myform');
 	echo form_open('email/send', $attributes);
 
-The above example would create a form similar to this
-
-::
+The above example would create a form similar to this::
 
 	<form method="post" accept-charset="utf-8" action="http://example.com/index.php/email/send" class="email" id="myform" />
 
 Adding Hidden Input Fields
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Hidden fields can be added by passing an associative array to the third parameter, like this
+Hidden fields can be added by passing an associative array to the
+third parameter, like this::
 
-::
-
-	 $hidden = array('username' => 'Joe', 'member_id' => '234');
+	$hidden = array('username' => 'Joe', 'member_id' => '234');
 	echo form_open('email/send', '', $hidden);
 
-The above example would create a form similar to this
-
-::
+The above example would create a form similar to this::
 
 	<form method="post" accept-charset="utf-8" action="http://example.com/index.php/email/send">
 		<input type="hidden" name="username" value="Joe" />
@@ -75,29 +74,38 @@
 form_open_multipart()
 =====================
 
-This function is absolutely identical to the `form_open()` tag above
-except that it adds a multipart attribute, which is necessary if you
+.. php:function:: form_open_multipart($action = '', $attributes = array(), $hidden = array())
+
+	:param	string	$action: Form action/target URI string
+	:param	string	$attributes: HTML attributes
+	:param	array	$hidden: An array of hidden fields' definitions
+	:returns:	string
+
+This function is absolutely identical to :php:func:`form_open()` above,
+except that it adds a *multipart* attribute, which is necessary if you
 would like to use the form to upload files with.
 
 form_hidden()
 =============
 
-Lets you generate hidden input fields. You can either submit a
-name/value string to create one field
+.. php:function:: form_hidden($name, $value = '')
 
-::
+	:param	string	$name: Field name
+	:param	string	$value: Field value
+	:returns:	string
+
+Lets you generate hidden input fields. You can either submit a
+name/value string to create one field::
 
 	form_hidden('username', 'johndoe');
 	// Would produce: <input type="hidden" name="username" value="johndoe" />
 
-Or you can submit an associative array to create multiple fields
-
-::
+... or you can submit an associative array to create multiple fields::
 
 	$data = array(
-		'name'  => 'John Doe',
-		'email' => 'john@example.com',
-		'url'   => 'http://example.com'
+		'name'	=> 'John Doe',
+		'email'	=> 'john@example.com',
+		'url'	=> 'http://example.com'
 	);
 
 	echo form_hidden($data);
@@ -109,35 +117,32 @@
 		<input type="hidden" name="url" value="http://example.com" />
 	*/
 
-Or pass an associative array to the value field.
-
-::
+You can also pass an associative array to the value field::
 
 	$data = array(
-		'name'  => 'John Doe',
-		'email' => 'john@example.com',
-		'url'   => 'http://example.com'
+		'name'	=> 'John Doe',
+		'email'	=> 'john@example.com',
+		'url'	=> 'http://example.com'
 	);
 
 	echo form_hidden('my_array', $data);
 
 	/*
 		Would produce:
+
 		<input type="hidden" name="my_array[name]" value="John Doe" />
 		<input type="hidden" name="my_array[email]" value="john@example.com" />
 		<input type="hidden" name="my_array[url]" value="http://example.com" />
 	*/
 
-If you want to create hidden input fields with extra attributes
-
-::
+If you want to create hidden input fields with extra attributes::
 
 	$data = array(
-		'type'        => 'hidden',
-		'name'        => 'email',
-		'id'          => 'hiddenemail',
-		'value'       => 'john@example.com',
-		'class'       => 'hiddenemail'
+		'type'	=> 'hidden',
+		'name'	=> 'email',
+		'id'	=> 'hiddenemail',
+		'value'	=> 'john@example.com',
+		'class'	=> 'hiddenemail'
 	);
 
 	echo form_input($data);
@@ -151,25 +156,28 @@
 form_input()
 ============
 
-Lets you generate a standard text input field. You can minimally pass
-the field name and value in the first and second parameter
+.. php:function:: form_input($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*
+	:returns:	string
+
+Lets you generate a standard text input field. You can minimally pass
+the field name and value in the first and second parameter::
 
 	echo form_input('username', 'johndoe');
 
 Or you can pass an associative array containing any data you wish your
-form to contain
-
-::
+form to contain::
 
 	$data = array(
-		'name'        => 'username',
-		'id'          => 'username',
-		'value'       => 'johndoe',
-		'maxlength'   => '100',
-		'size'        => '50',
-		'style'       => 'width:50%'
+		'name'		=> 'username',
+		'id'		=> 'username',
+		'value'		=> 'johndoe',
+		'maxlength'	=> '100',
+		'size'		=> '50',
+		'style'		=> 'width:50%'
 	);
 
 	echo form_input($data);
@@ -181,9 +189,7 @@
 	*/
 
 If you would like your form to contain some additional data, like
-Javascript, you can pass it as a string in the third parameter
-
-::
+JavaScript, you can pass it as a string in the third parameter::
 
 	$js = 'onClick="some_function()"';
 	echo form_input('username', 'johndoe', $js);
@@ -191,34 +197,70 @@
 form_password()
 ===============
 
-This function is identical in all respects to the `form_input()` function above except that it uses the "password" input type.
+.. 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*
+	:returns:	string
+
+This function is identical in all respects to the :php:func:`form_input()`
+function above except that it uses the "password" input type.
 
 form_upload()
 =============
 
-This function is identical in all respects to the `form_input()` function above except that it uses the "file" input type, allowing it to be used to upload files.
+.. php:function:: form_upload($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*
+	:returns:	string
+
+This function is identical in all respects to the :php:func:`form_input()`
+function above except that it uses the "file" input type, allowing it to
+be used to upload files.
 
 form_textarea()
 ===============
 
-This function is identical in all respects to the `form_input()` function above except that it generates a "textarea" type. Note: Instead of the "maxlength" and "size" attributes in the above example, you will instead specify "rows" and "cols".
+.. php:function:: form_textarea($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*
+	:returns:	string
+
+This function is identical in all respects to the :php:func:`form_input()`
+function above except that it generates a "textarea" type.
+
+.. note: Instead of the *maxlength* and *size* attributes in the above example,
+	you will instead specify *rows* and *cols*.
 
 form_dropdown()
 ===============
 
+.. php:function:: form_dropdown($name = '', $options = array(), $selected = array(), $extra = '')
+
+	: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*
+	:returns:	string
+
 Lets you create a standard drop-down field. The first parameter will
 contain the name of the field, the second parameter will contain an
 associative array of options, and the third parameter will contain the
 value you wish to be selected. You can also pass an array of multiple
 items through the third parameter, and CodeIgniter will create a
-multiple select for you. Example
+multiple select for you.
 
-::
+Example::
 
 	$options = array(
-		'small'  => 'Small Shirt',
-		'med'    => 'Medium Shirt',
-		'large'   => 'Large Shirt',
+		'small'  => 'Small Shirt',
+		'med'    => 'Medium Shirt',
+		'large'  => 'Large Shirt',
 		'xlarge' => 'Extra Large Shirt',
 	);
 
@@ -251,33 +293,47 @@
 
 If you would like the opening <select> to contain additional data, like
 an id attribute or JavaScript, you can pass it as a string in the fourth
-parameter
-
-::
+parameter::
 
 	$js = 'id="shirts" onChange="some_function();"';
 	echo form_dropdown('shirts', $options, 'large', $js);
 
-If the array passed as $options is a multidimensional array,
-`form_dropdown()` will produce an <optgroup> with the array key as the
+If the array passed as ``$options`` is a multidimensional array, then
+``form_dropdown()`` will produce an <optgroup> with the array key as the
 label.
 
 form_multiselect()
 ==================
 
+.. php:function:: form_multiselect($name = '', $options = array(), $selected = array(), $extra = '')
+
+	: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*
+	:returns:	string
+
 Lets you create a standard multiselect field. The first parameter will
 contain the name of the field, the second parameter will contain an
 associative array of options, and the third parameter will contain the
-value or values you wish to be selected. The parameter usage is
-identical to using form_dropdown() above, except of course that the
-name of the field will need to use POST array syntax, e.g. foo[].
+value or values you wish to be selected.
+
+The parameter usage is identical to using :php:func:`form_dropdown()` above,
+except of course that the name of the field will need to use POST array
+syntax, e.g. foo[].
 
 form_fieldset()
-================
+===============
+
+.. php:function:: form_fieldset($legend_text = '', $attributes = array())
+
+	:param	string	$legend_text: Text to put in the <legend> tag
+	:param	array	$attributes: Attributes to be set on the <fieldset> tag
+	:returns:	string
 
 Lets you generate fieldset/legend fields.
 
-::
+Example::
 
 	echo form_fieldset('Address Information');
 	echo "<p>fieldset content here</p>\n";
@@ -285,6 +341,7 @@
 
 	/*
 		Produces:
+
 			<fieldset>
 				<legend>Address Information</legend>
 					<p>form content here</p>
@@ -292,13 +349,11 @@
 	*/
 
 Similar to other functions, you can submit an associative array in the
-second parameter if you prefer to set additional attributes.
-
-::
+second parameter if you prefer to set additional attributes::
 
 	$attributes = array(
-		'id' => 'address_info',
-		'class' => 'address_info'
+		'id'	=> 'address_info',
+		'class'	=> 'address_info'
 	);
 
 	echo form_fieldset('Address Information', $attributes);
@@ -317,22 +372,33 @@
 form_fieldset_close()
 =====================
 
+.. php:function:: form_fieldset_close($extra = '')
+
+	:param	string	$extra: Anything to append after the closing tag, *as is*
+	:returns:	string
+
 Produces a closing </fieldset> tag. The only advantage to using this
 function is it permits you to pass data to it which will be added below
 the tag. For example
 
 ::
 
-	$string = "</div></div>";
+	$string = '</div></div>';
 	echo form_fieldset_close($string);
 	// Would produce: </fieldset></div></div>
 
 form_checkbox()
 ===============
 
-Lets you generate a checkbox field. Simple example
+.. php:function:: form_checkbox($data = '', $value = '', $checked = FALSE, $extra = '')
 
-::
+	: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*
+	:returns:	string
+
+Lets you generate a checkbox field. Simple example::
 
 	echo form_checkbox('newsletter', 'accept', TRUE);
 	// Would produce:  <input type="checkbox" name="newsletter" value="accept" checked="checked" />
@@ -346,21 +412,19 @@
 ::
 
 	$data = array(
-		'name'        => 'newsletter',
-		'id'          => 'newsletter',
-		'value'       => 'accept',
-		'checked'     => TRUE,
-		'style'       => 'margin:10px',
+		'name'    => 'newsletter',
+		'id'      => 'newsletter',
+		'value'   => 'accept',
+		'checked' => TRUE,
+		'style'   => 'margin:10px'
 	);
 
 	echo form_checkbox($data);
 	// Would produce: <input type="checkbox" name="newsletter" id="newsletter" value="accept" checked="checked" style="margin:10px" />
 
-As with other functions, if you would like the tag to contain additional
-data, like JavaScript, you can pass it as a string in the fourth
-parameter
-
-::
+Also as with other functions, if you would like the tag to contain
+additional data like JavaScript, you can pass it as a string in the
+fourth parameter::
 
 	$js = 'onClick="some_function()"';
 	echo form_checkbox('newsletter', 'accept', TRUE, $js)
@@ -368,29 +432,28 @@
 form_radio()
 ============
 
-This function is identical in all respects to the `form_checkbox()`
+.. 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*
+	:returns:	string
+
+This function is identical in all respects to the :php:func:`form_checkbox()`
 function above except that it uses the "radio" input type.
 
-form_submit()
-=============
-
-Lets you generate a standard submit button. Simple example
-
-::
-
-	echo form_submit('mysubmit', 'Submit Post!');
-	// Would produce:  <input type="submit" name="mysubmit" value="Submit Post!" />
-
-Similar to other functions, you can submit an associative array in the
-first parameter if you prefer to set your own attributes. The third
-parameter lets you add extra data to your form, like JavaScript.
-
 form_label()
 ============
 
-Lets you generate a <label>. Simple example
+.. php:function:: form_label($label_text = '', $id = '', $attributes = array())
 
-::
+	:param	string	$label_text: Text to put in the <label> tag
+	:param	string	$id: ID of the form element that we're making a label for
+	:param	string	$attributes: HTML attributes
+	:returns:	string
+
+Lets you generate a <label>. Simple example::
 
 	echo form_label('What is your Name', 'username');
 	// Would produce:  <label for="username">What is your Name</label>
@@ -398,7 +461,7 @@
 Similar to other functions, you can submit an associative array in the
 third parameter if you prefer to set additional attributes.
 
-::
+Example::
 
 	$attributes = array(
 		'class' => 'mycustomclass',
@@ -408,77 +471,110 @@
 	echo form_label('What is your Name', 'username', $attributes);
 	// Would produce:  <label for="username" class="mycustomclass" style="color: #000;">What is your Name</label>
 
+form_submit()
+=============
+
+.. php:function:: form_submit($data = '', $value = '', $extra = '')
+
+	:param	string	$data: Button name
+	:param	string	$value: Button value
+	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:returns:	string
+
+Lets you generate a standard submit button. Simple example::
+
+	echo form_submit('mysubmit', 'Submit Post!');
+	// Would produce:  <input type="submit" name="mysubmit" value="Submit Post!" />
+
+Similar to other functions, you can submit an associative array in the
+first parameter if you prefer to set your own attributes. The third
+parameter lets you add extra data to your form, like JavaScript.
 
 form_reset()
 ============
 
+.. php:function:: form_reset($data = '', $value = '', $extra = '')
+
+	:param	string	$data: Button name
+	:param	string	$value: Button value
+	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:returns:	string
+
 Lets you generate a standard reset button. Use is identical to
-`form_submit()`.
+:php:func:`form_submit()`.
 
 form_button()
 =============
 
+.. php:function:: form_button($data = '', $content = '', $extra = '')
+
+	:param	string	$data: Button name
+	:param	string	$content: Button label
+	:param	string	$extra: Extra attributes to be added to the tag *as is*
+	:returns:	string
+
 Lets you generate a standard button element. You can minimally pass the
-button name and content in the first and second parameter
+button name and content in the first and second parameter::
 
-::
-
-	 echo form_button('name','content');
-	// Would produce <button name="name" type="button">Content</button>
+	echo form_button('name','content');
+	// Would produce: <button name="name" type="button">Content</button>
 
 Or you can pass an associative array containing any data you wish your
-form to contain:
-
-::
+form to contain::
 
 	$data = array(
-		'name' 		=> 'button',
-		'id' 		=> 'button',
-		'value' 	=> 'true',
-		'type' 		=> 'reset',
-		'content' 	=> 'Reset'
+		'name'    => 'button',
+		'id'      => 'button',
+		'value'   => 'true',
+		'type'    => 'reset',
+		'content' => 'Reset'
 	);
 
 	echo form_button($data);
 	// Would produce: <button name="button" id="button" value="true" type="reset">Reset</button>
 
 If you would like your form to contain some additional data, like
-JavaScript, you can pass it as a string in the third parameter:
+JavaScript, you can pass it as a string in the third parameter::
 
-::
-
-	 $js = 'onClick="some_function()"';
+	$js = 'onClick="some_function()"';
 	echo form_button('mybutton', 'Click Me', $js);
 
 form_close()
 ============
 
+.. php:function:: form_close($extra = '')
+
+	:param	string	$extra: Anything to append after the closing tag, *as is*
+	:returns:	string
+
 Produces a closing </form> tag. The only advantage to using this
 function is it permits you to pass data to it which will be added below
-the tag. For example
+the tag. For example::
 
-::
-
-	$string = "</div></div>";
+	$string = '</div></div>';
 	echo form_close($string);
 	// Would produce:  </form> </div></div>
 
 form_prep()
 ===========
 
-Allows you to safely use HTML and characters such as quotes within form
-elements without breaking out of the form. Consider this example
+.. php:function:: form_prep($str = '', $is_textarea = FALSE)
 
-::
+	:param	string	$str: Value to escape
+	:param	bool	$is_textarea: Whether we're preparing for <textarea> or a regular input tag
+	:returns:	string
+
+Allows you to safely use HTML and characters such as quotes within form
+elements without breaking out of the form.
+
+Consider this example::
 
 	$string = 'Here is a string containing "quoted" text.';
 	<input type="text" name="myform" value="$string" />
 
 Since the above string contains a set of quotes it will cause the form
-to break. The `form_prep()` function converts HTML so that it can be used
-safely
-
-::
+to break. The ``form_prep()`` function converts HTML so that it can be used
+safely::
 
 	<input type="text" name="myform" value="<?php echo form_prep($string); ?>" />
 
@@ -489,29 +585,42 @@
 set_value()
 ===========
 
+.. php:function:: set_value($field = '', $default = '', $is_textarea = FALSE)
+
+	:param	string	$field: Field name
+	:param	string	$default: Default value
+	:param	bool	$is_textarea: Whether we're setting <textarea> content
+	:returns:	string
+
 Permits you to set the value of an input form or textarea. You must
 supply the field name via the first parameter of the function. The
 second (optional) parameter allows you to set a default value for the
-form. Example
+form.
 
-::
+Example::
 
-	<input type="text" name="quantity" value="<?php echo set_value('quantity', '0'); ?>" size="50" />
+	<input type="text" name="quantity" value="<?=set_value('quantity', '0');?>" size="50" />
 
 The above form will show "0" when loaded for the first time.
 
 set_select()
 ============
 
+.. php:function:: set_select($field = '', $value = '', $default = FALSE)
+
+	:param	string	$field: Field name
+	:param	string	$value: Value to check for
+	:param	string	$default: Whether the value is also a default one
+	:returns:	string
+
 If you use a <select> menu, this function permits you to display the
-menu item that was selected. The first parameter must contain the name
-of the select menu, the second parameter must contain the value of each
-item, and the third (optional) parameter lets you set an item as the
-default (use boolean TRUE/FALSE).
+menu item that was selected.
 
-Example
+The first parameter must contain the name of the select menu, the second
+parameter must contain the value of each item, and the third (optional)
+parameter lets you set an item as the default (use boolean TRUE/FALSE).
 
-::
+Example::
 
 	<select name="myselect">
 		<option value="one" <?php echo  set_select('myselect', 'one', TRUE); ?> >One</option>
@@ -522,12 +631,20 @@
 set_checkbox()
 ==============
 
-Permits you to display a checkbox in the state it was submitted. The
-first parameter must contain the name of the checkbox, the second
-parameter must contain its value, and the third (optional) parameter
-lets you set an item as the default (use boolean TRUE/FALSE). Example
+.. php:function:: set_checkbox($field = '', $value = '', $default = FALSE)
 
-::
+	:param	string	$field: Field name
+	:param	string	$value: Value to check for
+	:param	string	$default: Whether the value is also a default one
+	:returns:	string
+
+Permits you to display a checkbox in the state it was submitted.
+
+The first parameter must contain the name of the checkbox, the second
+parameter must contain its value, and the third (optional) parameter
+lets you set an item as the default (use boolean TRUE/FALSE).
+
+Example::
 
 	<input type="checkbox" name="mycheck" value="1" <?php echo set_checkbox('mycheck', '1'); ?> />
 	<input type="checkbox" name="mycheck" value="2" <?php echo set_checkbox('mycheck', '2'); ?> />
@@ -535,15 +652,71 @@
 set_radio()
 ===========
 
-Permits you to display radio buttons in the state they were submitted.
-This function is identical to the **set_checkbox()** function above.
+.. php:function:: set_radio($field = '', $value = '', $default = FALSE)
 
-::
+	:param	string	$field: Field name
+	:param	string	$value: Value to check for
+	:param	string	$default: Whether the value is also a default one
+	:returns:	string
+
+Permits you to display radio buttons in the state they were submitted.
+This function is identical to the :php:func:`set_checkbox()` function above.
+
+Example::
 
 	<input type="radio" name="myradio" value="1" <?php echo  set_radio('myradio', '1', TRUE); ?> />
 	<input type="radio" name="myradio" value="2" <?php echo  set_radio('myradio', '2'); ?> />
 
-.. note:: If you are using the Form Validation class, you must always specify a rule for your field,
-	even if empty, in order for the set_*() functions to work. This is because if a Form Validation object
-	is defined, the control for set_*() is handed over to a method of the class instead of the generic helper
-	function.
\ No newline at end of file
+.. note:: If you are using the Form Validation class, you must always specify
+	a rule for your field, even if empty, in order for the ``set_*()``
+	functions to work. This is because if a Form Validation object is
+	defined, the control for ``set_*()`` is handed over to a method of the
+	class instead of the generic helper function.
+
+form_error()
+============
+
+.. php:function:: form_error($field = '', $prefix = '', $suffix = '')
+
+	:param	string	$field:	Field name
+	:param	string	$prefix: Error opening tag
+	:param	string	$suffix: Error closing tag
+	:returns:	string
+
+Returns a validation error message from the :doc:`Form Validation Library
+<../libraries/form_validation>`, associated with the specified field name.
+You can optionally specify opening and closing tag(s) to put around the error
+message.
+
+Example::
+
+	// Assuming that the 'username' field value was incorrect:
+	echo form_error('myfield', '<div class="error">', '</div>');
+
+	// Would produce: <div class="error">Error message associated with the "username" field.</div>
+
+validation_errors()
+===================
+
+.. php:function:: validation_errors($prefix = '', $suffix = '')
+
+	:param	string	$prefix: Error opening tag
+	:param	string	$suffix: Error closing tag
+	:returns:	string
+
+Similarly to the :php:func:`form_error()` function, returns all validation
+error messages produced by the :doc:`Form Validation Library
+<../libraries/form_validation>`, with optional opening and closing tags
+around each of the messages.
+
+Example::
+
+	echo validation_errors('<span class="error">', '</span>');
+
+	/*
+		Would produce, e.g.:
+
+		<span class="error">The "email" field doesn't contain a valid e-mail address!</span>
+		<span class="error">The "password" field doesn't match the "repeat_password" field!</span>
+
+	 */
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/html_helper.rst b/user_guide_src/source/helpers/html_helper.rst
index 17c28cd..df53ebd 100644
--- a/user_guide_src/source/helpers/html_helper.rst
+++ b/user_guide_src/source/helpers/html_helper.rst
@@ -19,6 +19,11 @@
 br()
 ====
 
+.. php:function:: br($count = 1)
+
+	:param	int	$count: Number of times to repeat the tag
+	:returns:	string
+
 Generates line break tags (<br />) based on the number you submit.
 Example::
 
@@ -29,7 +34,14 @@
 heading()
 =========
 
-Lets you create HTML <h1> tags. The first parameter will contain the
+.. php:function:: heading($data = '', $h = '1', $attributes = '')
+
+	:param	string	$data: Content
+	:param	string	$h: Heading level
+	:param	array	$attributes: HTML attributes
+	:returns:	string
+
+Lets you create HTML heading tags. The first parameter will contain the
 data, the second the size of the heading. Example::
 
 	echo heading('Welcome!', 3);
@@ -37,9 +49,7 @@
 The above would produce: <h3>Welcome!</h3>
 
 Additionally, in order to add attributes to the heading tag such as HTML
-classes, ids or inline styles, a third parameter is available.
-
-::
+classes, ids or inline styles, a third parameter is available::
 
 	echo heading('Welcome!', 3, 'class="pink"')
 
@@ -48,28 +58,31 @@
 img()
 =====
 
-Lets you create HTML <img /> tags. The first parameter contains the
-image source. Example
+.. php:function:: img($src = '', $index_page = FALSE, $attributes = '')
 
-::
+	:param	string	$src: Image source data
+	:param	bool	$index_page: Whether to treat $src as a routed URI string
+	:param	array	$attributes: HTML attributes
+	:returns:	string
+
+Lets you create HTML <img /> tags. The first parameter contains the
+image source. Example::
 
 	echo img('images/picture.jpg'); // gives <img src="http://site.com/images/picture.jpg" />
 
 There is an optional second parameter that is a TRUE/FALSE value that
-specifics if the src should have the page specified by
-$config['index_page'] added to the address it creates. Presumably, this
-would be if you were using a media controller.
-
-::
+specifics if the *src* should have the page specified by
+``$config['index_page']`` added to the address it creates.
+Presumably, this would be if you were using a media controller::
 
 	echo img('images/picture.jpg', TRUE); // gives <img src="http://site.com/index.php/images/picture.jpg" alt="" />
 
 
-Additionally, an associative array can be passed to the img() function
-for complete control over all attributes and values. If an alt attribute
+Additionally, an associative array can be passed to the ``img()`` function
+for complete control over all attributes and values. If an *alt* attribute
 is not provided, CodeIgniter will generate an empty string.
 
-::
+Example::
 
 	$image_properties = array(               
 		'src' 	=> 'images/picture.jpg',               
@@ -81,128 +94,136 @@
 		'rel' 	=> 'lightbox'
 	);
 
-		img($image_properties);     // <img src="http://site.com/index.php/images/picture.jpg" alt="Me, demonstrating how to eat 4 slices of pizza at one time" class="post_images" width="200" height="200" title="That was quite a night" rel="lightbox" />
+	img($image_properties);
+	// <img src="http://site.com/index.php/images/picture.jpg" alt="Me, demonstrating how to eat 4 slices of pizza at one time" class="post_images" width="200" height="200" title="That was quite a night" rel="lightbox" />
 
 
 link_tag()
-===========
+==========
+
+.. php:function:: ling_tag($href = '', $rel = 'stylesheet', $type = 'text/css', $title = '', $media = '', $index_page = FALSE)
+
+	:param	string	$href: What are we linking to
+	:param	string	$rel: Relation type
+	:param	string	$type: Type of the related document
+	:param	string	$title: Link title
+	:param	string	$media: Media type
+	:param	bool	$index_page: Whether to treat $src as a routed URI string
+	:returns:	string
 
 Lets you create HTML <link /> tags. This is useful for stylesheet links,
-as well as other links. The parameters are href, with optional rel,
-type, title, media and index_page. index_page is a TRUE/FALSE value
-that specifics if the href should have the page specified by
-$config['index_page'] added to the address it creates.
+as well as other links. The parameters are *href*, with optional *rel*,
+*type*, *title*, *media* and *index_page*.
 
-::
+*index_page* is a boolean value that specifies if the *href* should have
+the page specified by ``$config['index_page']`` added to the address it creates.
 
-	 echo link_tag('css/mystyles.css'); // gives <link href="http://site.com/css/mystyles.css" rel="stylesheet" type="text/css" />
+Example::
+
+	echo link_tag('css/mystyles.css');
+	// gives <link href="http://site.com/css/mystyles.css" rel="stylesheet" type="text/css" />
 
 
-Further examples
+Further examples::
 
-::
+	echo link_tag('favicon.ico', 'shortcut icon', 'image/ico');
+	// <link href="http://site.com/favicon.ico" rel="shortcut icon" type="image/ico" />
 
-	echo link_tag('favicon.ico', 'shortcut icon', 'image/ico');     // <link href="http://site.com/favicon.ico" rel="shortcut icon" type="image/ico" />
+	echo link_tag('feed', 'alternate', 'application/rss+xml', 'My RSS Feed');
+	// <link href="http://site.com/feed" rel="alternate" type="application/rss+xml" title="My RSS Feed" />
 
-	echo link_tag('feed', 'alternate', 'application/rss+xml', 'My RSS Feed');     // <link href="http://site.com/feed" rel="alternate" type="application/rss+xml" title="My RSS Feed" />
-
-Additionally, an associative array can be passed to the link() function
-for complete control over all attributes and values.
-
-::
+Additionally, an associative array can be passed to the ``link()`` function
+for complete control over all attributes and values::
 
 	$link = array(               
-		'href' 	=> 'css/printer.css',               
-		'rel' 	=> 'stylesheet',               
-		'type' 	=> 'text/css',               
-		'media' => 'print'
+		'href'	=> 'css/printer.css',
+		'rel'	=> 'stylesheet',
+		'type'	=> 'text/css',
+		'media'	=> 'print'
 	);
 
-	echo link_tag($link);     // <link href="http://site.com/css/printer.css" rel="stylesheet" type="text/css" media="print" />
-
+	echo link_tag($link);
+	// <link href="http://site.com/css/printer.css" rel="stylesheet" type="text/css" media="print" />
 
 nbs()
 =====
 
-Generates non-breaking spaces (&nbsp;) based on the number you submit.
-Example
+.. php:function:: nbs($num = 1)
 
-::
+	:param	int	$num: Number of space entities to produce
+	:returns:	string
+
+Generates non-breaking spaces (&nbsp;) based on the number you submit.
+Example::
 
 	echo nbs(3);
 
-The above would produce
-
-::
+The above would produce::
 
 	&nbsp;&nbsp;&nbsp;
 
-ol() and ul()
+ul() and ol()
 =============
 
+.. php:function:: ul($list, $attributes = '')
+
+	:param	array	$list: List entries
+	:param	array	$attributes: HTML attributes
+	:returns:	string
+
 Permits you to generate ordered or unordered HTML lists from simple or
-multi-dimensional arrays. Example
+multi-dimensional arrays. Example::
 
-::
-
-	$this->load->helper('html');
-
-	$list = array(             
-		'red',              
-		'blue',              
-		'green',             
-		'yellow'             
+	$list = array(
+		'red',
+		'blue',
+		'green',
+		'yellow'
 	);
 
-	$attributes = array(                     
-		'class' => 'boldlist',                     
-		'id'    => 'mylist'                    
+	$attributes = array(
+		'class'	=> 'boldlist',
+		'id'	=> 'mylist'
 	);
 
 	echo ul($list, $attributes);
 
-The above code will produce this
+The above code will produce this::
 
-::
-
-	 <ul class="boldlist" id="mylist">   
-		<li>red</li>   
-		<li>blue</li>   
-		<li>green</li>   
+	 <ul class="boldlist" id="mylist">
+		<li>red</li>
+		<li>blue</li>
+		<li>green</li>
 		<li>yellow</li>
 	</ul>
 
-Here is a more complex example, using a multi-dimensional array
+Here is a more complex example, using a multi-dimensional array::
 
-::
-
-	$this->load->helper('html');
-
-	$attributes = array(                     
-		'class' => 'boldlist',                     
-		'id'    => 'mylist'                     
+	$attributes = array(
+		'class'	=> 'boldlist',
+		'id'	=> 'mylist'
 	);
 
-	$list = array(             
-		'colors'  => array(                                 
-			'red',                                 
-			'blue',                                 
-			'green'                             
+	$list = array(
+		'colors'  => array(
+			'red',
+			'blue',
+			'green'
 		),
-		'shapes'  => array(                                 
-			'round',                                  
-			'square',                                 
-			'circles' => array(                                             
+		'shapes'  => array(
+			'round',
+			'square',
+			'circles' => array(
 				'ellipse',
 				'oval',
 				'sphere'
-			)                             
-		),             
-		'moods'  => array(                                 
-			'happy',                                  
-			'upset' => array( 	                                       
+			)
+		),
+		'moods'  => array(
+			'happy',
+			'upset' => array(
 				'defeated' => array(
-					'dejected',                
+					'dejected',
 					'disheartened',
 					'depressed'
 				),
@@ -215,59 +236,74 @@
 
 	echo ul($list, $attributes);
 
-The above code will produce this
+The above code will produce this::
 
-::
-
-	<ul class="boldlist" id="mylist">   
-		<li>colors     
-			<ul>       
-				<li>red</li>       
-				<li>blue</li>       
-				<li>green</li>     
-			</ul>   
-		</li>   
-		<li>shapes     
-			<ul>       
-				<li>round</li>       
-				<li>suare</li>       
-				<li>circles         
-					<ul>           
-						<li>elipse</li>           
-						<li>oval</li>           
-						<li>sphere</li>         
-					</ul>       
-				</li>     
-			</ul>   
-		</li>   
-		<li>moods     
-			<ul>       
-				<li>happy</li>       
-				<li>upset         
-					<ul>           
-						<li>defeated             
-							<ul>               
+	<ul class="boldlist" id="mylist">
+		<li>colors
+			<ul>
+				<li>red</li>
+				<li>blue</li>
+				<li>green</li>
+			</ul>
+		</li>
+		<li>shapes
+			<ul>
+				<li>round</li>
+				<li>suare</li>
+				<li>circles
+					<ul>
+						<li>elipse</li>
+						<li>oval</li>
+						<li>sphere</li>
+					</ul>
+				</li>
+			</ul>
+		</li>
+		<li>moods
+			<ul>
+				<li>happy</li>
+				<li>upset
+					<ul>
+						<li>defeated
+							<ul>
 								<li>dejected</li>
 								<li>disheartened</li>
 								<li>depressed</li>
 							</ul>
 						</li>
 						<li>annoyed</li>
-						<li>cross</li>           
-						<li>angry</li>         
-					</ul>       
-				</li>     
-			</ul>   
+						<li>cross</li>
+						<li>angry</li>
+					</ul>
+				</li>
+			</ul>
 		</li>
 	</ul>
 
+.. php:function:: ol($list, $attributes = '')
+
+	:param	array	$list: List entries
+	:param	array	$attributes: HTML attributes
+	:returns:	string
+
+Identical to :php:func:`ul()`, only it produces the <ol> tag for
+ordered lists instead of <ul>.
+
 meta()
 ======
 
-Helps you generate meta tags. You can pass strings to the function, or
-simple arrays, or multidimensional ones. Examples
+.. php:function:: meta($name = '', $content = '', $type = 'name', $newline = "\n")
 
-::
+	:param	string	$name: Meta name
+	:param	string	$content: Meta content
+	:param	string	$type: Meta type
+	:param	string	$newline: Newline character
+	:returns:	string
+
+Helps you generate meta tags. You can pass strings to the function, or
+simple arrays, or multidimensional ones.
+
+Examples::
 
 	echo meta('description', 'My Great site');
 	// Generates:  <meta name="description" content="My Great Site" />
@@ -279,7 +315,7 @@
 	echo meta(array('name' => 'robots', 'content' => 'no-cache'));
 	// Generates:  <meta name="robots" content="no-cache" />
 
-	$meta = array(         
+	$meta = array(
 		array(
 			'name' => 'robots',
 			'content' => 'no-cache'
@@ -291,7 +327,7 @@
 		array(
 			'name' => 'keywords',
 			'content' => 'love, passion, intrigue, deception'
-		),         
+		),
 		array(
 			'name' => 'robots',
 			'content' => 'no-cache'
@@ -313,10 +349,14 @@
 doctype()
 =========
 
+.. php:function:: doctype($type = 'xhtml1-strict')
+
+	:param	string	$type: Doctype name
+
 Helps you generate document type declarations, or DTD's. XHTML 1.0
 Strict is used by default, but many doctypes are available.
 
-::
+Example::
 
 	echo doctype(); // <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
diff --git a/user_guide_src/source/helpers/inflector_helper.rst b/user_guide_src/source/helpers/inflector_helper.rst
index cc46a18..1f54b76 100644
--- a/user_guide_src/source/helpers/inflector_helper.rst
+++ b/user_guide_src/source/helpers/inflector_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('inflector');
 
@@ -21,65 +19,81 @@
 singular()
 ==========
 
-Changes a plural word to singular. Example
+.. php:function:: singular($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
 
-	$word = "dogs";
-	echo singular($word); // Returns "dog"
+Changes a plural word to singular. Example::
+
+	echo singular('dogs'); // Prints 'dog'
 
 plural()
 ========
 
-Changes a singular word to plural. Example
+.. php:function:: plural($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
 
-	$word = "dog";
-	echo plural($word); // Returns "dogs"
+Changes a singular word to plural. Example::
 
-To force a word to end with "es" use a second "true" argument.
-
-::
-
-	$word = "pass";
-	echo plural($word, TRUE); // Returns "passes"
+	echo plural('dog'); // Prints 'dogs'
 
 camelize()
 ==========
 
+.. php:function:: camelize($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
 Changes a string of words separated by spaces or underscores to camel
-case. Example
+case. Example::
 
-::
-
-	$word = "my_dog_spot";
-	echo camelize($word); // Returns "myDogSpot"
+	echo camelize('my_dog_spot'); // Prints 'myDogSpot'
 
 underscore()
 ============
 
-Takes multiple words separated by spaces and underscores them. Example
+.. php:function:: camelize($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
 
-	$word = "my dog spot";
-	echo underscore($word); // Returns "my_dog_spot"
+Takes multiple words separated by spaces and underscores them.
+Example::
+
+	echo underscore('my dog spot'); // Prints 'my_dog_spot'
 
 humanize()
 ==========
 
+.. php:function:: camelize($str)
+
+	:param	string	$str: Input string
+	:param	string	$separator: Input separator
+	:returns:	string
+
 Takes multiple words separated by underscores and adds spaces between
-them. Each word is capitalized. Example
+them. Each word is capitalized.
 
-::
+Example::
 
-	$word = "my_dog_spot";
-	echo humanize($word); // Returns "My Dog Spot"
+	echo humanize('my_dog_spot'); // Prints 'My Dog Spot'
 
-To use dashes instead of underscores
+To use dashes instead of underscores::
 
-::
+	echo humanize('my-dog-spot', '-'); // Prints 'My Dog Spot'
 
-	$word = "my-dog-spot";
-	echo humanize($word, '-'); // Returns "My Dog Spot"
\ No newline at end of file
+is_countable()
+==============
+
+.. php:function:: is_countable($word)
+
+	:param	string	$word: Input string
+	:returns:	bool
+
+Checks if the given word has a plural version. Example::
+
+	is_countable('equipment'); // Returns FALSE
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/language_helper.rst b/user_guide_src/source/helpers/language_helper.rst
index b7b23d1..1911e3b 100644
--- a/user_guide_src/source/helpers/language_helper.rst
+++ b/user_guide_src/source/helpers/language_helper.rst
@@ -10,24 +10,27 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('language');
 
 The following functions are available:
 
-lang('language line', 'element id')
-===================================
+lang()
+======
+
+.. php:function:: lang($line, $for = '', $attributes = array())
+
+	:param	string	$line: Language line key
+	:param	string	$for: HTML "for" attribute (ID of the element we're creating a label for)
+	:param	array	$attributes: Any additional HTML attributes
+	:returns:	string
 
 This function returns a line of text from a loaded language file with
-simplified syntax that may be more desirable for view files than calling
-`$this->lang->line()`. The optional second parameter will also output a
-form label for you. Example
+simplified syntax that may be more desirable for view files than 
+``CI_Lang::line()``.
 
-::
+Example::
 
-	echo lang('language_key', 'form_item_id');
-	// becomes <label for="form_item_id">language_key</label>
-
+	echo lang('language_key', 'form_item_id', array('class' => 'myClass');
+	// Outputs: <label for="form_item_id" class="myClass">Language line</label>
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/number_helper.rst b/user_guide_src/source/helpers/number_helper.rst
index af6cdad..8e0ebda 100644
--- a/user_guide_src/source/helpers/number_helper.rst
+++ b/user_guide_src/source/helpers/number_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('number');
 
@@ -21,25 +19,27 @@
 byte_format()
 =============
 
-Formats a numbers as bytes, based on size, and adds the appropriate
-suffix. Examples
+.. php:function:: byte_format($num, $precision = 1)
 
-::
+	:param	mixed	$num: Number of bytes
+	:param	int	$precision: Floating point precision
+	:returns:	string
+
+Formats numbers as bytes, based on size, and adds the appropriate
+suffix. Examples::
 	
-	echo byte_format(456); // Returns 456 Bytes 
-	echo byte_format(4567); // Returns 4.5 KB 
-	echo byte_format(45678); // Returns 44.6 KB 
-	echo byte_format(456789); // Returns 447.8 KB 
-	echo byte_format(3456789); // Returns 3.3 MB 
-	echo byte_format(12345678912345); // Returns 1.8 GB 
+	echo byte_format(456); // Returns 456 Bytes
+	echo byte_format(4567); // Returns 4.5 KB
+	echo byte_format(45678); // Returns 44.6 KB
+	echo byte_format(456789); // Returns 447.8 KB
+	echo byte_format(3456789); // Returns 3.3 MB
+	echo byte_format(12345678912345); // Returns 1.8 GB
 	echo byte_format(123456789123456789); // Returns 11,228.3 TB
 
 An optional second parameter allows you to set the precision of the
-result.
-
-::
+result::
 
 	 echo byte_format(45678, 2); // Returns 44.61 KB
 
 .. note:: The text generated by this function is found in the following
-	language file: language/<your_lang>/number_lang.php
+	language file: `language/<your_lang>/number_lang.php`
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/path_helper.rst b/user_guide_src/source/helpers/path_helper.rst
index 847f5a0..3a271b2 100644
--- a/user_guide_src/source/helpers/path_helper.rst
+++ b/user_guide_src/source/helpers/path_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('path');
 
@@ -21,23 +19,28 @@
 set_realpath()
 ==============
 
-Checks to see if the path exists. This function will return a server
-path without symbolic links or relative directory structures. An
-optional second argument will cause an error to be triggered if the path
-cannot be resolved.
+.. php:function:: set_realpath($path, $check_existance = FALSE)
 
-::
+	:param	string	$path: Path
+	:param	bool	$check_existance: Whether to check if the path actually exists
+	:returns:	string
+
+This function will return a server path without symbolic links or
+relative directory structures. An optional second argument will
+cause an error to be triggered if the path cannot be resolved.
+
+Examples::
 
 	$file = '/etc/php5/apache2/php.ini';
-	echo set_realpath($file); // returns "/etc/php5/apache2/php.ini"
+	echo set_realpath($file); // Prints '/etc/php5/apache2/php.ini'
 
 	$non_existent_file = '/path/to/non-exist-file.txt';
-	echo set_realpath($non_existent_file, TRUE);	// shows an error, as the path cannot be resolved
-	echo set_realpath($non_existent_file, FALSE);	// returns "/path/to/non-exist-file.txt"
+	echo set_realpath($non_existent_file, TRUE);	// Shows an error, as the path cannot be resolved
+	echo set_realpath($non_existent_file, FALSE);	// Prints '/path/to/non-exist-file.txt'
 
 	$directory = '/etc/php5';
-	echo set_realpath($directory);	// returns "/etc/php5/"
+	echo set_realpath($directory);	// Prints '/etc/php5/'
 	
 	$non_existent_directory = '/path/to/nowhere';
-	echo set_realpath($non_existent_directory, TRUE);	// shows an error, as the path cannot be resolved
-	echo set_realpath($non_existent_directory, FALSE);	// returns "/path/to/nowhere"
+	echo set_realpath($non_existent_directory, TRUE);	// Shows an error, as the path cannot be resolved
+	echo set_realpath($non_existent_directory, FALSE);	// Prints '/path/to/nowhere'
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/security_helper.rst b/user_guide_src/source/helpers/security_helper.rst
index ec0be28..21bf534 100644
--- a/user_guide_src/source/helpers/security_helper.rst
+++ b/user_guide_src/source/helpers/security_helper.rst
@@ -9,9 +9,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('security');
 
@@ -20,25 +18,47 @@
 xss_clean()
 ===========
 
-Provides Cross Site Script Hack filtering. This function is an alias to
-the one in the :doc:`Input class <../libraries/input>`. More info can
-be found there.
+.. php:function:: xss_clean($str, $is_image = FALSE)
+
+	:param	string	$str: Input data
+	:param	bool	$is_image: Whether we're dealing with an image
+	:returns:	string
+
+Provides Cross Site Script Hack filtering.
+
+This function is an alias for ``CI_Input::xss_clean()``. For more info,
+please see the :doc:`Input Library <../libraries/input>` documentation.
 
 sanitize_filename()
 ===================
 
-Provides protection against directory traversal. This function is an
-alias to the one in the :doc:`Security class <../libraries/security>`.
-More info can be found there.
+.. php:function:: sanitize_filename($filename)
+
+	:param	string	$filename: Filename
+	:returns:	string
+
+Provides protection against directory traversal.
+
+This function is an alias for ``CI_Security::sanitize_filename()``.
+For more info, please see the :doc:`Security Library <../libraries/security>`
+documentation.
 
 do_hash()
 =========
 
+.. php:function:: do_hash($str, $type = 'sha1')
+
+	:param	string	$str: Input
+	:param	string	$type: Algorithm
+	:returns:	string
+
 Permits you to create one way hashes suitable for encrypting
-passwords. Will create SHA1 by default. See `hash_algos() <http://php.net/function.hash_algos>`_
+passwords. Will use SHA1 by default.
+
+See `hash_algos() <http://php.net/function.hash_algos>`_
 for a full list of supported algorithms.
 
-::
+Examples::
 
 	$str = do_hash($str); // SHA1
 	$str = do_hash($str, 'md5'); // MD5
@@ -51,20 +71,34 @@
 strip_image_tags()
 ==================
 
-This is a security function that will strip image tags from a string. It
-leaves the image URL as plain text.
+.. php:function:: strip_image_tags($str)
 
-::
+	:param	string	$str: Input
+	:returns:	string
+
+This is a security function that will strip image tags from a string.
+It leaves the image URL as plain text.
+
+Example::
 
 	$string = strip_image_tags($string);
 
+This function is an alias for ``CI_Security::strip_image_tags()``. For
+more info, please see the :doc:`Security Library <../libraries/security>`
+documentation.
+
 encode_php_tags()
 =================
 
-This is a security function that converts PHP tags to entities. Note: If
-you use the XSS filtering function it does this automatically.
+.. php:function:: encode_php_tags($str)
 
-::
+	:param	string	$str: Input
+	:returns:	string
 
-	$string = encode_php_tags($string);
+This is a security function that converts PHP tags to entities.
 
+.. note: :php:func:`xss_clean()` does this automatically, if you use it.
+
+Example::
+
+	$string = encode_php_tags($string);
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/smiley_helper.rst b/user_guide_src/source/helpers/smiley_helper.rst
index 941ba11..13841e8 100644
--- a/user_guide_src/source/helpers/smiley_helper.rst
+++ b/user_guide_src/source/helpers/smiley_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('smiley');
 
@@ -36,10 +34,11 @@
 download and install the smiley images, then create a controller and the
 View as described.
 
-.. important:: Before you begin, please `download the smiley images <http://codeigniter.com/download_files/smileys.zip>`_
-	and put them in a publicly accessible place on your server. This helper
-	also assumes you have the smiley replacement array located at
-	`application/config/smileys.php`
+.. important:: Before you begin, please `download the smiley images
+	<http://codeigniter.com/download_files/smileys.zip>`_
+	and put them in a publicly accessible place on your server.
+	This helper also assumes you have the smiley replacement array
+	located at `application/config/smileys.php`
 
 The Controller
 --------------
@@ -47,18 +46,17 @@
 In your `application/controllers/` folder, create a file called
 smileys.php and place the code below in it.
 
-.. important:: Change the URL in the `get_clickable_smileys()`
+.. important:: Change the URL in the :php:func:`get_clickable_smileys()`
 	function below so that it points to your smiley folder.
 
-You'll notice that in addition to the smiley helper we are using the :doc:`Table Class <../libraries/table>`.
-
-::
+You'll notice that in addition to the smiley helper, we are also using
+the :doc:`Table Class <../libraries/table>`::
 
 	<?php
 
 	class Smileys extends CI_Controller {
 
-		function index()
+		public function index()
 		{
 			$this->load->helper('smiley');
 			$this->load->library('table');
@@ -69,12 +67,11 @@
 			$data['smiley_table'] = $this->table->generate($col_array);
 			$this->load->view('smiley_view', $data);
 		}
+
 	}
 
 In your `application/views/` folder, create a file called `smiley_view.php`
-and place this code in it:
-
-::
+and place this code in it::
 
 	<html>
 		<head>
@@ -102,59 +99,66 @@
 
 	$image_array = get_smiley_links("http://example.com/images/smileys/", "comment_textarea_alias");
 
-To map the alias to the field id, pass them both into the `smiley_js`
-function
-
-::
+To map the alias to the field id, pass them both into the
+:php:func:`smiley_js()` function::
 
 	$image_array = smiley_js("comment_textarea_alias", "comments");
 
-******************
-Function Reference
-******************
-
 get_clickable_smileys()
 =======================
 
+.. php:function:: get_clickable_smileys($image_url, $alias = '', $smileys = NULL)
+
+	:param	string	$image_url: URL path to the smileys directory
+	:param	string	$alias: Field alias
+	:returns:	array
+
 Returns an array containing your smiley images wrapped in a clickable
 link. You must supply the URL to your smiley folder and a field id or
 field alias.
 
-::
+Example::
 
 	$image_array = get_smiley_links("http://example.com/images/smileys/", "comment");
 
-Note: Usage of this function without the second parameter, in
-combination with `js_insert_smiley` has been deprecated.
-
 smiley_js()
 ===========
 
+.. php:function:: smiley_js($alias = '', $field_id = '', $inline = TRUE)
+
+	:param	string	$alias: Field alias
+	:param	string	$field_id: Field ID
+	:param	bool	$inline: Whether we're inserting an inline smiley
+
 Generates the JavaScript that allows the images to be clicked and
 inserted into a form field. If you supplied an alias instead of an id
 when generating your smiley links, you need to pass the alias and
 corresponding form id into the function. This function is designed to be
 placed into the <head> area of your web page.
 
-::
+Example::
 
 	<?php echo smiley_js(); ?>
 
-Note: This function replaces `js_insert_smiley`, which has been
-deprecated.
-
 parse_smileys()
 ===============
 
+.. php:function:: parse_smileys($str = '', $image_url = '', $smileys = NULL)
+
+	:param	string	$str: Text containing smiley codes
+	:param	string	$image_url: URL path to the smileys directory
+	:param	array	$smileys: An array of smileys
+	:returns:	string
+
 Takes a string of text as input and replaces any contained plain text
 smileys into the image equivalent. The first parameter must contain your
 string, the second must contain the URL to your smiley folder
 
-::
+Example::
 
 	$str = 'Here are some simileys: :-)  ;-)';
 	$str = parse_smileys($str, "http://example.com/images/smileys/");
 	echo $str;
 
 
-.. |smile!| image:: ../images/smile.gif
+.. |smile!| image:: ../images/smile.gif
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/string_helper.rst b/user_guide_src/source/helpers/string_helper.rst
index 19500aa..d0d3024 100644
--- a/user_guide_src/source/helpers/string_helper.rst
+++ b/user_guide_src/source/helpers/string_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('string');
 
@@ -21,39 +19,48 @@
 random_string()
 ===============
 
+.. php:function:: random_string($type = 'alnum', $len = 8)
+
+	:param	string	$type: Randomization type
+	:param	int	$len: Output string length
+	:returns:	string
+
 Generates a random string based on the type and length you specify.
 Useful for creating passwords or generating random hashes.
 
 The first parameter specifies the type of string, the second parameter
 specifies the length. The following choices are available:
 
-alpha, alunum, numeric, nozero, unique, md5, encrypt and sha1
-
 -  **alpha**: A string with lower and uppercase letters only.
 -  **alnum**: Alpha-numeric string with lower and uppercase characters.
+-  **basic**: A random number based on ``mt_rand()``.
 -  **numeric**: Numeric string.
 -  **nozero**: Numeric string with no zeros.
--  **unique**: Encrypted with MD5 and uniqid(). Note: The length
-   parameter is not available for this type. Returns a fixed length 32
-   character string.
--  **sha1**: An encrypted random number based on ``sha1()``.
+-  **md5**: An encrypted random number based on ``md5()`` (fixed length of 32).
+-  **sha1**: An encrypted random number based on ``sha1()`` (fixed length of 40).
 
-Usage example
-
-::
+Usage example::
 
 	echo random_string('alnum', 16);
 
+.. note:: Usage of the *unique* and *encrypt* types is DEPRECATED. They
+	are just aliases for *md5* and *sha1* respectively.
+
 increment_string()
 ==================
 
+.. php:function:: increment_string($str, $separator = '_', $first = 1)
+
+	:param	string	$str: Input string
+	:param	string	$separator: Separator to append a duplicate number with
+	:param	int	$first: Starting number
+	:returns:	string
+
 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.
 
-Usage example
-
-::
+Usage example::
 
 	echo increment_string('file', '_'); // "file_1"
 	echo increment_string('file', '-', 2); // "file-2"
@@ -62,10 +69,13 @@
 alternator()
 ============
 
-Allows two or more items to be alternated between, when cycling through
-a loop. Example
+.. php:function:: alternator($args)
 
-::
+	:param	mixed	$args: A variable number of arguments
+	:returns:	mixed
+
+Allows two or more items to be alternated between, when cycling through
+a loop. Example::
 
 	for ($i = 0; $i < 10; $i++)
 	{     
@@ -88,21 +98,34 @@
 repeater()
 ==========
 
-Generates repeating copies of the data you submit. Example
+.. php:function:: repeater($data, $num = 1)
 
-::
+	:param	string	$data: Input
+	:param	int	$num: Number of times to repeat
+	:returns:	string
 
-	$string = "\n"; echo repeater($string, 30);
+Generates repeating copies of the data you submit. Example::
+
+	$string = "\n";
+	echo repeater($string, 30);
 
 The above would generate 30 newlines.
 
+.. note:: This function is DEPRECATED. Use the native ``str_repeat()``
+	instead.
+
 reduce_double_slashes()
 =======================
 
-Converts double slashes in a string to a single slash, except those
-found in http://. Example
+.. php:function:: reduce_double_slashes($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
+
+Converts double slashes in a string to a single slash, except those
+found in URL protocol prefixes (e.g. http://).
+
+Example::
 
 	$string = "http://example.com//index.php";
 	echo reduce_double_slashes($string); // results in "http://example.com/index.php"
@@ -110,16 +133,14 @@
 strip_slashes()
 ===============
 
-Removes any slashes from a string. Example
+.. php:function:: strip_slashes($data)
 
-::
+	:param	array	$data: Input
+	:returns:	array
 
-	$str = "Is your name O\'reilly?";
-	echo strip_slashes($str); // results in Is your name O'reilly?
+Removes any slashes from an array of strings.
 
-You can also use an array. Example
-
-::
+Example::
 	
 	$str = array(
 		'question'  => 'Is your name O\'reilly?',
@@ -128,60 +149,66 @@
 	
 	$str = strip_slashes($str);
 	
-The above will return the following array:
-
-::
+The above will return the following array::
 
 	array(
 		'question'  => "Is your name O'reilly?",
 		'answer' => "No, my name is O'connor."
 	);
 
+.. note:: For historical reasons, this function will also accept
+	and handle string inputs. This however makes it just an
+	alias for ``stripslashes()``.
+
 trim_slashes()
 ==============
 
-Removes any leading/trailing slashes from a string. Example
+.. php:function:: trim_slashes($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
+
+Removes any leading/trailing slashes from a string. Example::
 
 	$string = "/this/that/theother/";
 	echo trim_slashes($string); // results in this/that/theother
 
+.. note:: This function is DEPRECATED. Use the native ``trim()`` instead:
+	|
+	| trim($str, '/');
 
 reduce_multiples()
 ==================
 
+.. php:function:: reduce_multiples($str, $character = '', $trim = FALSE)
+
+	:param	string	$str: Text to search in
+	:param	string	$character: Character to reduce
+	:param	bool	$trim: Whether to also trim the specified character
+	:returns:	string
+
 Reduces multiple instances of a particular character occuring directly
 after each other. Example::
 
 	$string = "Fred, Bill,, Joe, Jimmy";
 	$string = reduce_multiples($string,","); //results in "Fred, Bill, Joe, Jimmy"
 
-The function accepts the following parameters:
-
-::
-
-	reduce_multiples(string: text to search in, string: character to reduce, boolean: whether to remove the character from the front and end of the string)
-
-The first parameter contains the string in which you want to reduce the
-multiplies. The second parameter contains the character you want to have
-reduced. The third parameter is FALSE by default; if set to TRUE it will
-remove occurences of the character at the beginning and the end of the
-string. Example:
-
-::
+If the third parameter is set to TRUE it will remove occurences of the
+character at the beginning and the end of the string. Example::
 
 	$string = ",Fred, Bill,, Joe, Jimmy,";
 	$string = reduce_multiples($string, ", ", TRUE); //results in "Fred, Bill, Joe, Jimmy"
 
-
 quotes_to_entities()
 ====================
 
-Converts single and double quotes in a string to the corresponding HTML
-entities. Example
+.. php:function:: quotes_to_entities($str)
 
-::
+	:param	string	$str: Input string
+	:returns:	string
+
+Converts single and double quotes in a string to the corresponding HTML
+entities. Example::
 
 	$string = "Joe's \"dinner\"";
 	$string = quotes_to_entities($string); //results in "Joe&#39;s &quot;dinner&quot;"
@@ -189,8 +216,12 @@
 strip_quotes()
 ==============
 
+.. php:function:: strip_quotes($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
 Removes single and double quotes from a string. Example::
 
 	$string = "Joe's \"dinner\"";
-	$string = strip_quotes($string); //results in "Joes dinner"
-
+	$string = strip_quotes($string); //results in "Joes dinner"
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/text_helper.rst b/user_guide_src/source/helpers/text_helper.rst
index 8cb2d6f..aec36c9 100644
--- a/user_guide_src/source/helpers/text_helper.rst
+++ b/user_guide_src/source/helpers/text_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('text');
 
@@ -21,7 +19,14 @@
 word_limiter()
 ==============
 
-Truncates a string to the number of **words** specified. Example::
+.. php:function:: word_limiter($str, $limit = 100, $end_char = '&#8230;')
+
+	:param	string	$str: Input string
+	:param	int	$limit: Limit
+	:param	string	$end_char: End character (usually an ellipsis)
+	:returns:	string
+
+Truncates a string to the number of *words* specified. Example::
 
 	$string = "Here is a nice text string consisting of eleven words.";
 	$string = word_limiter($string, 4);
@@ -33,11 +38,18 @@
 character_limiter()
 ===================
 
-Truncates a string to the number of **characters** specified. It
-maintains the integrity of words so the character count may be slightly
-more or less then what you specify. Example
+.. php:function:: character_limiter($str, $n = 500, $end_char = '&#8230;')
 
-::
+	:param	string	$str: Input string
+	:param	int	$n: Number of characters
+	:param	string	$end_char: End character (usually an ellipsis)
+	:returns:	string
+
+Truncates a string to the number of *characters* specified. It
+maintains the integrity of words so the character count may be slightly
+more or less then what you specify.
+
+Example::
 
 	$string = "Here is a nice text string consisting of eleven words.";
 	$string = character_limiter($string, 20);
@@ -46,55 +58,78 @@
 The third parameter is an optional suffix added to the string, if
 undeclared this helper uses an ellipsis.
 
-**Note:** If you need to truncate to an exact number of characters please see
-the :ref:`ellipsize` function below.
+.. note:: If you need to truncate to an exact number of characters please
+	see the :ref:`ellipsize()` function below.
 
 ascii_to_entities()
 ===================
 
+.. php:function:: ascii_to_entities($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
 Converts ASCII values to character entities, including high ASCII and MS
 Word characters that can cause problems when used in a web page, so that
 they can be shown consistently regardless of browser settings or stored
 reliably in a database. There is some dependence on your server's
 supported character sets, so it may not be 100% reliable in all cases,
 but for the most part it should correctly identify characters outside
-the normal range (like accented characters). Example
+the normal range (like accented characters).
 
-::
+Example::
 
 	$string = ascii_to_entities($string);
 
 entities_to_ascii()
 ===================
 
-This function does the opposite of the previous one; it turns character
-entities back into ASCII.
+.. php:function::entities_to_ascii($str, $all = TRUE)
+
+	:param	string	$str: Input string
+	:param	bool	$all: Whether to convert unsafe entities as well
+	:returns:	string
+
+This function does the opposite of :php:func:`ascii_to_entities()`.
+It turns character entities back into ASCII.
 
 convert_accented_characters()
 =============================
 
-Transliterates high ASCII characters to low ASCII equivalents, useful
+.. php:function:: convert_accented_characters($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
+Transliterates high ASCII characters to low ASCII equivalents. Useful
 when non-English characters need to be used where only standard ASCII
 characters are safely used, for instance, in URLs.
 
-::
+Example::
 
 	$string = convert_accented_characters($string);
 
-This function uses a companion config file
-`application/config/foreign_chars.php` to define the to and from array
-for transliteration.
+.. note:: This function uses a companion config file
+	`application/config/foreign_chars.php` to define the to and
+	from array for transliteration.
 
 word_censor()
 =============
 
+.. php:function:: word_censor($str, $censored, $replacement = '')
+
+	:param	string	$str: Input string
+	:param	array	$censored: List of bad words to censor
+	:param	string	$replacement: What to replace bad words with
+	:returns:	string
+
 Enables you to censor words within a text string. The first parameter
 will contain the original string. The second will contain an array of
-words which you disallow. The third (optional) parameter can contain a
-replacement value for the words. If not specified they are replaced with
-pound signs: ####. Example
+words which you disallow. The third (optional) parameter can contain
+a replacement value for the words. If not specified they are replaced
+with pound signs: ####.
 
-::
+Example::
 
 	$disallowed = array('darn', 'shucks', 'golly', 'phooey');
 	$string = word_censor($string, $disallowed, 'Beep!');
@@ -102,48 +137,76 @@
 highlight_code()
 ================
 
+.. php:function:: highlight_code($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
 Colorizes a string of code (PHP, HTML, etc.). Example::
 
 	$string = highlight_code($string);
 
-The function uses PHP's highlight_string() function, so the colors used
-are the ones specified in your php.ini file.
+The function uses PHP's ``highlight_string()`` function, so the
+colors used are the ones specified in your php.ini file.
 
 highlight_phrase()
 ==================
 
+.. php:function:: highlight_phrase($str, $phrase, $tag_open = '<strong>', $tag_close = '</strong>')
+
+	:param	string	$str: Input string
+	:param	string	$phrase: Phrase to highlight
+	:param	string	$tag_open: Opening tag used for the highlight
+	:param	string	$tag_close: Closing tag for the highlight
+	:returns:	string
+
 Will highlight a phrase within a text string. The first parameter will
 contain the original string, the second will contain the phrase you wish
 to highlight. The third and fourth parameters will contain the
-opening/closing HTML tags you would like the phrase wrapped in. Example
+opening/closing HTML tags you would like the phrase wrapped in.
 
-::
+Example::
 
 	$string = "Here is a nice text string about nothing in particular.";
-	$string = highlight_phrase($string, "nice text", '<span style="color:#990000">', '</span>');
+	echo highlight_phrase($string, "nice text", '<span style="color:#990000;">', '</span>');
 
-The above text returns:
+The above code prints::
 
-Here is a nice text string about nothing in particular.
+	Here is a <span style="color:#990000;">nice text</span> string about nothing in particular.
 
 word_wrap()
 ===========
 
-Wraps text at the specified **character** count while maintaining
-complete words. Example
+.. php:function:: word_wrap($str, $charlim = 76)
 
-::
+	:param	string	$str: Input string
+	:param	int	$charlim: Character limit
+	:returns:	string
+
+Wraps text at the specified *character* count while maintaining
+complete words.
+
+Example::
 
 	$string = "Here is a simple string of text that will help us demonstrate this function.";
 	echo word_wrap($string, 25);
 
 	// Would produce:  Here is a simple string of text that will help us demonstrate this function
 
-.. _ellipsize:
+.. _ellipsize():
 
 ellipsize()
 ===========
 
+.. php:function:: ellipsize($str, $max_length, $position = 1, $ellipsis = '&hellip;')
+
+	:param	string	$str: Input string
+	:param	int	$max_length: String length limit
+	:param	mixed	$position: Position to split at
+			(int or float)
+	:param	string	$ellipsis: What to use as the ellipsis character
+	:returns:	string
+
 This function will strip tags from a string, split it at a defined
 maximum length, and insert an ellipsis.
 
@@ -156,14 +219,11 @@
 An optional forth parameter is the kind of ellipsis. By default,
 &hellip; will be inserted.
 
-::
+Example::
 
 	$str = 'this_string_is_entirely_too_long_and_might_break_my_design.jpg';
 	echo ellipsize($str, 32, .5);
 
-Produces:
+Produces::
 
-::
-
-	this_string_is_e&hellip;ak_my_design.jpg
-
+	this_string_is_e&hellip;ak_my_design.jpg
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/typography_helper.rst b/user_guide_src/source/helpers/typography_helper.rst
index f320260..3c81687 100644
--- a/user_guide_src/source/helpers/typography_helper.rst
+++ b/user_guide_src/source/helpers/typography_helper.rst
@@ -10,9 +10,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('typography');
 
@@ -21,9 +19,18 @@
 auto_typography()
 =================
 
+.. php:function:: auto_typography($str, $reduce_linebreaks = FALSE)
+
+	:param	string	$str: Input string
+	:param	bool	$reduce_linebreaks: Whether to reduce multiple instances of double newlines to two
+	:returns:	string
+
 Formats text so that it is semantically and typographically correct
-HTML. Please see the :doc:`Typography Class <../libraries/typography>`
-for more info.
+HTML.
+
+This function is an alias for ``CI_Typography::auto_typography``.
+For more info, please see the :doc:`Typography Library
+<../libraries/typography>` documentation.
 
 Usage example::
 
@@ -31,18 +38,34 @@
 
 .. note:: Typographic formatting can be processor intensive, particularly if
 	you have a lot of content being formatted. If you choose to use this
-	function you may want to consider `caching </general/caching>` your pages.
+	function you may want to consider `caching <../general/caching>` your
+	pages.
 
 nl2br_except_pre()
 ==================
 
+.. php:function:: nl2br_except_pre($str)
+
+	:param	string	$str: Input string
+	:returns:	string
+
 Converts newlines to <br /> tags unless they appear within <pre> tags.
-This function is identical to the native PHP nl2br() function, except
-that it ignores <pre> tags.
+This function is identical to the native PHP ``nl2br()`` function,
+except that it ignores <pre> tags.
 
-Usage example
-
-::
+Usage example::
 
 	$string = nl2br_except_pre($string);
 
+entity_decode()
+===============
+
+.. php:function:: entity_decode($str, $charset = NULL)
+
+	:param	string	$str: Input string
+	:param	string	$charset: Character set
+	:returns:	string
+
+This function is an alias for ``CI_Security::entity_decode()``.
+Fore more info, please see the :doc:`Security Library
+<../libraries/security>` documentation.
\ No newline at end of file
diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst
index 82db6a5..5b8fa5f 100644
--- a/user_guide_src/source/helpers/url_helper.rst
+++ b/user_guide_src/source/helpers/url_helper.rst
@@ -9,9 +9,7 @@
 Loading this Helper
 ===================
 
-This helper is loaded using the following code
-
-::
+This helper is loaded using the following code::
 
 	$this->load->helper('url');
 
@@ -20,119 +18,135 @@
 site_url()
 ==========
 
+.. php:function:: site_url($uri = '')
+
+	:param	string	$uri: URI string
+	:returns:	string
+
 Returns your site URL, as specified in your config file. The index.php
-file (or whatever you have set as your site index_page in your config
+file (or whatever you have set as your site **index_page** in your config
 file) will be added to the URL, as will any URI segments you pass to the
-function, and the url_suffix as set in your config file.
+function, plus the **url_suffix** as set in your config file.
 
 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.
 
 Segments can be optionally passed to the function as a string or an
-array. Here is a string example
+array. Here is a string example::
 
-::
-
-	echo site_url("news/local/123");
+	echo site_url('news/local/123');
 
 The above example would return something like:
-http://example.com/index.php/news/local/123
+*http://example.com/index.php/news/local/123*
 
-Here is an example of segments passed as an array
-
-::
+Here is an example of segments passed as an array::
 
 	$segments = array('news', 'local', '123');
 	echo site_url($segments);
 
+This function is an alias for ``CI_Config::site_url()``. For more info,
+please see the :doc:`Config Library <../libraries/config>` documentation.
+
 base_url()
 ===========
 
-Returns your site base URL, as specified in your config file. Example
+.. php:function:: base_url($uri = '')
 
-::
+	:param	string	$uri: URI string
+	:returns:	string
+
+Returns your site base URL, as specified in your config file. Example::
 
 	echo base_url();
 
-This function returns the same thing as `site_url`, without the
-index_page or url_suffix being appended.
+This function returns the same thing as :php:func:`site_url()`, without
+the *index_page* or *url_suffix* being appended.
 
-Also like site_url, you can supply segments as a string or an array.
-Here is a string example
-
-::
+Also like :php:func:`site_url()`, you can supply segments as a string or
+an array. Here is a string example::
 
 	echo base_url("blog/post/123");
 
 The above example would return something like:
-http://example.com/blog/post/123
+*http://example.com/blog/post/123*
 
-This is useful because unlike `site_url()`, you can supply a string to a
-file, such as an image or stylesheet. For example
-
-::
+This is useful because unlike :php:func:`site_url()`, you can supply a
+string to a file, such as an image or stylesheet. For example::
 
 	echo base_url("images/icons/edit.png");
 
 This would give you something like:
-http://example.com/images/icons/edit.png
+*http://example.com/images/icons/edit.png*
+
+This function is an alias for ``CI_Config::base_url()``. For more info,
+please see the :doc:`Config Library <../libraries/config>` documentation.
 
 current_url()
 =============
 
+.. php:function:: current_url()
+
+	:returns:	string
+
 Returns the full URL (including segments) of the page being currently
 viewed.
 
+.. note:: Calling this function is the same as doing this:
+	|
+	| site_url(uri_string());
+
 uri_string()
 ============
 
-Returns the URI segments of any page that contains this function. For
-example, if your URL was this
+.. php:function:: uri_string()
 
-::
+	:returns:	string
+
+Returns the URI segments of any page that contains this function.
+For example, if your URL was this::
 
 	http://some-site.com/blog/comments/123
 
-The function would return
-
-::
+The function would return::
 
 	/blog/comments/123
 
+This function is an alias for ``CI_Config::uri_string()``. For more info,
+please see the :doc:`Config Library <../libraries/config>` documentation.
+
 index_page()
 ============
 
-Returns your site "index" page, as specified in your config file.
-Example
+.. php:function:: index_page()
 
-::
+	:returns:	string
+
+Returns your site **index_page**, as specified in your config file.
+Example::
 
 	echo index_page();
 
 anchor()
 ========
 
-Creates a standard HTML anchor link based on your local site URL
+.. php:function:: anchor($uri = '', $title = '', $attributes = '')
 
-::
+	:param	string	$uri: URI string
+	:param	string	$title: Anchor title
+	:param	mixed	$attributes: HTML attributes
+	:returns:	string
 
-	<a href="http://example.com">Click Here</a>
-
-The tag has three optional parameters
-
-::
-
-	anchor(uri segments, text, attributes)
+Creates a standard HTML anchor link based on your local site URL.
 
 The first parameter can contain any segments you wish appended to the
-URL. As with the site_url() function above, segments can be a string or
-an array.
+URL. As with the :php:func:`site_url()` function above, segments can
+be a string or an array.
 
 .. note:: If you are building links that are internal to your application
-	do not include the base URL (http://...). This will be added automatically
-	from the information specified in your config file. Include only the
-	URI segments you wish appended to the URL.
+	do not include the base URL (http://...). This will be added
+	automatically from the information specified in your config file.
+	Include only the URI segments you wish appended to the URL.
 
 The second segment is the text you would like the link to say. If you
 leave it blank, the URL will be used.
@@ -141,41 +155,43 @@
 added to the link. The attributes can be a simple string or an
 associative array.
 
-Here are some examples
-
-::
+Here are some examples::
 
 	echo anchor('news/local/123', 'My News', 'title="News title"');
-
-Would produce: <a href="http://example.com/index.php/news/local/123"
-title="News title">My News</a>
-
-::
+	// Prints: <a href="http://example.com/index.php/news/local/123" title="News title">My News</a>
 
 	echo anchor('news/local/123', 'My News', array('title' => 'The best news!'));
+	// Prints: <a href="http://example.com/index.php/news/local/123" title="The best news!">My News</a>
 
-Would produce: <a href="http://example.com/index.php/news/local/123"
-title="The best news!">My News</a>
+	echo anchor('', 'Click here');
+	// Prints: <a href="http://example.com">Click Here</a>
 
 anchor_popup()
 ==============
 
-Nearly identical to the anchor() function except that it opens the URL
-in a new window. You can specify JavaScript window attributes in the
-third parameter to control how the window is opened. If the third
-parameter is not set it will simply open a new window with your own
-browser settings. Here is an example with attributes
+.. php:function:: anchor_popup($uri = '', $title = '', $attributes = FALSE)
 
-::
+	:param	string	$uri: URI string
+	:param	string	$title: Anchor title
+	:param	mixed	$attributes: HTML attributes
+	:returns:	string
+
+Nearly identical to the :php:func:``anchor()`` function except that it
+opens the URL in a new window. You can specify JavaScript window
+attributes in the third parameter to control how the window is opened.
+If the third parameter is not set it will simply open a new window with
+your own browser settings.
+
+Here is an example with attributes::
 
 	$atts = array(
-		'width'       => '800',
-		'height'      => '600',
+		'width'       => 800,
+		'height'      => 600,
 		'scrollbars'  => 'yes',
 		'status'      => 'yes',
 		'resizable'   => 'yes',
-		'screenx'     => '0',
-		'screeny'     => '0',
+		'screenx'     => 0,
+		'screeny'     => 0,
 		'window_name' => '_blank'
 	);
 
@@ -184,13 +200,11 @@
 .. note:: The above attributes are the function defaults so you only need to
 	set the ones that are different from what you need. If you want the
 	function to use all of its defaults simply pass an empty array in the
-	third parameter
+	third parameter:
+	|
+	| echo anchor_popup('news/local/123', 'Click Me!', array());
 
-::
-
-	echo anchor_popup('news/local/123', 'Click Me!', array());
-
-.. note:: The 'window_name' is not really an attribute, but an argument to
+.. note:: The **window_name** is not really an attribute, but an argument to
 	the JavaScript `window.open() <http://www.w3schools.com/jsref/met_win_open.asp>`
 	method, which accepts either a window name or a window target.
 
@@ -200,112 +214,149 @@
 mailto()
 ========
 
-Creates a standard HTML email link. Usage example
+.. php:function:: mailto($email, $title = '', $attributes = '')
 
-::
+	:param	string	$email: E-mail address
+	:param	string	$title: Anchor title
+	:param	mixed	$attributes: HTML attributes
+	:returns:	string
+
+Creates a standard HTML e-mail link. Usage example::
 
 	echo mailto('me@my-site.com', 'Click Here to Contact Me');
 
-As with the anchor() tab above, you can set attributes using the third
-parameter.
+As with the :php:func:`anchor()` tab above, you can set attributes using the
+third parameter::
+
+	$attributes = array('title' => 'Mail me');
+	echo mailto('me@my-site.com', 'Contact Me', $attributes);
 
 safe_mailto()
 =============
 
-Identical to the above function except it writes an obfuscated version
-of the mailto tag using ordinal numbers written with JavaScript to help
-prevent the email address from being harvested by spam bots.
+.. php:function:: safe_mailto($email, $title = '', $attributes = '')
+
+	:param	string	$email: E-mail address
+	:param	string	$title: Anchor title
+	:param	mixed	$attributes: HTML attributes
+	:returns:	string
+
+Identical to the :php:func:`mailto()` function except it writes an obfuscated
+version of the *mailto* tag using ordinal numbers written with JavaScript to
+help prevent the e-mail address from being harvested by spam bots.
 
 auto_link()
 ===========
 
-Automatically turns URLs and email addresses contained in a string into
-links. Example
+.. php:function:: auto_link($str, $type = 'both', $popup = FALSE)
 
-::
+	:param	string	$str: Input string
+	:param	string	$type: Link type ('email', 'url' or 'both')
+	:param	bool	$popup: Whether to create popup links
+	:returns:	string
+
+Automatically turns URLs and e-mail addresses contained in a string into
+links. Example::
 
 	$string = auto_link($string);
 
-The second parameter determines whether URLs and emails are converted or
+The second parameter determines whether URLs and e-mails are converted or
 just one or the other. Default behavior is both if the parameter is not
-specified. Email links are encoded as safe_mailto() as shown above.
+specified. E-mail links are encoded as :php:func:`safe_mailto()` as shown
+above.
 
-Converts only URLs
-
-::
+Converts only URLs::
 
 	$string = auto_link($string, 'url');
 
-Converts only Email addresses
-
-::
+Converts only e-mail addresses::
 
 	$string = auto_link($string, 'email');
 
 The third parameter determines whether links are shown in a new window.
-The value can be TRUE or FALSE (boolean)
-
-::
+The value can be TRUE or FALSE (boolean)::
 
 	$string = auto_link($string, 'both', TRUE);
 
 url_title()
 ===========
 
+.. php:function:: url_title($str, $separator = '-', $lowercase = FALSE)
+
+	:param	string	$str: Input string
+	:param	string	$separator: Word separator
+	:param	string	$lowercase: Whether to transform the output string to lower-case
+	:returns:	string
+
 Takes a string as input and creates a human-friendly URL string. This is
 useful if, for example, you have a blog in which you'd like to use the
-title of your entries in the URL. Example
-
-::
+title of your entries in the URL. Example::
 
 	$title = "What's wrong with CSS?";
-	$url_title = url_title($title);  // Produces:  Whats-wrong-with-CSS
+	$url_title = url_title($title);
+	// Produces: Whats-wrong-with-CSS
 
 The second parameter determines the word delimiter. By default dashes
-are used. Options are: dash, or underscore
+are used. Preferred options are: **-** (dash) or **_** (underscore)
 
-::
+Example::
 
 	$title = "What's wrong with CSS?";
-	$url_title = url_title($title, 'underscore');  // Produces:  Whats_wrong_with_CSS
+	$url_title = url_title($title, 'underscore');
+	// Produces: Whats_wrong_with_CSS
+
+.. note:: Old usage of 'dash' and 'underscore' as the second parameter
+	is DEPRECATED.
 
 The third parameter determines whether or not lowercase characters are
-forced. By default they are not. Options are boolean TRUE/FALSE
+forced. By default they are not. Options are boolean TRUE/FALSE.
 
-::
+Example::
 
 	$title = "What's wrong with CSS?";
-	$url_title = url_title($title, 'underscore', TRUE);  // Produces:  whats_wrong_with_css
+	$url_title = url_title($title, 'underscore', TRUE);
+	// Produces: whats_wrong_with_css
 
 prep_url()
 ----------
 
-This function will add http:// in the event that a scheme is missing
-from a URL. Pass the URL string to the function like this
+.. php:function:: prep_url($str = '')
 
-::
+	:param	string	$str: URL string
+	:returns:	string
 
-	$url = "example.com";
-	$url = prep_url($url);
+This function will add http:// in the event that a protocol prefix
+is missing from a URL.
+
+Pass the URL string to the function like this::
+
+	$url = prep_url('example.com');
 
 redirect()
 ==========
 
+.. php:function:: redirect($uri = '', $method = 'auto', $code = NULL)
+
+	:param	string	$uri: URI string
+	:param	string	$method: Redirect method ('auto', 'location' or 'refresh')
+	:param	string	$code: HTTP Response code (usually 302 or 303)
+	:returns:	void
+
 Does a "header redirect" to the URI specified. If you specify the full
 site URL that link will be built, but for local links simply providing
 the URI segments to the controller you want to direct to will create the
 link. The function will build the URL based on your config file values.
 
 The optional second parameter allows you to force a particular redirection
-method. The available methods are "location" or "refresh", with location
-being faster but less reliable on Windows servers. The default is "auto",
-which will attempt to intelligently choose the method based on the server
-environment.
+method. The available methods are **auto**, **location** and **refresh**,
+with location being faster but less reliable on IIS servers.
+The default is **auto**, which will attempt to intelligently choose the
+method based on the server environment.
 
 The optional third parameter allows you to send a specific HTTP Response
 Code - this could be used for example to create 301 redirects for search
 engine purposes. The default Response Code is 302. The third parameter is
-*only* available with 'location' redirects, and not 'refresh'. Examples::
+*only* available with **location** redirects, and not *refresh*. Examples::
 
 	if ($logged_in == FALSE)
 	{      
@@ -319,4 +370,14 @@
 	is outputted to the browser since it utilizes server headers.
 
 .. note:: For very fine grained control over headers, you should use the
-	`Output Library </libraries/output>` set_header() function.
+	`Output Library </libraries/output>` ``set_header()`` method.
+
+.. note:: To IIS users: if you hide the `Server` HTTP header, the *auto*
+	method won't detect IIS, in that case it is advised you explicitly
+	use the **refresh** method.
+
+.. note:: When the **location** method is used, an HTTP status code of 303
+	will *automatically* be selected when the page is currently accessed
+	via POST and HTTP/1.1 is used.
+
+.. important:: This function will terminate script execution.
\ No newline at end of file
diff --git a/user_guide_src/source/images/ci_quick_ref.png b/user_guide_src/source/images/ci_quick_ref.png
deleted file mode 100644
index c07d6b4..0000000
--- a/user_guide_src/source/images/ci_quick_ref.png
+++ /dev/null
Binary files differ
diff --git a/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.pdf b/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.pdf
deleted file mode 100644
index baec6bc..0000000
--- a/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.pdf
+++ /dev/null
Binary files differ
diff --git a/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.png b/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.png
deleted file mode 100644
index 15a7c15..0000000
--- a/user_guide_src/source/images/codeigniter_1.7.1_helper_reference.png
+++ /dev/null
Binary files differ
diff --git a/user_guide_src/source/images/codeigniter_1.7.1_library_reference.pdf b/user_guide_src/source/images/codeigniter_1.7.1_library_reference.pdf
deleted file mode 100644
index 312d020..0000000
--- a/user_guide_src/source/images/codeigniter_1.7.1_library_reference.pdf
+++ /dev/null
Binary files differ
diff --git a/user_guide_src/source/images/codeigniter_1.7.1_library_reference.png b/user_guide_src/source/images/codeigniter_1.7.1_library_reference.png
deleted file mode 100644
index 554ae2e..0000000
--- a/user_guide_src/source/images/codeigniter_1.7.1_library_reference.png
+++ /dev/null
Binary files differ
diff --git a/user_guide_src/source/index.rst b/user_guide_src/source/index.rst
index e42425b..09bf770 100644
--- a/user_guide_src/source/index.rst
+++ b/user_guide_src/source/index.rst
@@ -37,7 +37,6 @@
 
 - :doc:`overview/getting_started`
 - :doc:`overview/at_a_glance`
-- :doc:`overview/cheatsheets`
 - :doc:`overview/features`
 - :doc:`overview/appflow`
 - :doc:`overview/mvc`
@@ -80,6 +79,7 @@
 - :doc:`libraries/caching`
 - :doc:`database/index`
 - :doc:`libraries/javascript`
+- :doc:`libraries/sessions`
 
 ****************
 Helper Reference
@@ -118,5 +118,4 @@
 	database/index
 	documentation/index
 	tutorial/index
-	general/quick_reference
-	general/credits
\ No newline at end of file
+	general/credits
diff --git a/user_guide_src/source/installation/upgrade_211.rst b/user_guide_src/source/installation/upgrade_211.rst
index 59faca8..f0e70f6 100644
--- a/user_guide_src/source/installation/upgrade_211.rst
+++ b/user_guide_src/source/installation/upgrade_211.rst
@@ -8,9 +8,7 @@
 Step 1: Update your CodeIgniter files
 =====================================
 
-Replace all files and directories in your "system" folder and replace
-your index.php file. If any modifications were made to your index.php
-they will need to be made fresh in this new one.
+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.
diff --git a/user_guide_src/source/installation/upgrade_212.rst b/user_guide_src/source/installation/upgrade_212.rst
index 205ad86..4b76482 100644
--- a/user_guide_src/source/installation/upgrade_212.rst
+++ b/user_guide_src/source/installation/upgrade_212.rst
@@ -8,9 +8,7 @@
 Step 1: Update your CodeIgniter files
 =====================================
 
-Replace all files and directories in your "system" folder and replace
-your index.php file. If any modifications were made to your index.php
-they will need to be made fresh in this new one.
+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.
diff --git a/user_guide_src/source/installation/upgrade_213.rst b/user_guide_src/source/installation/upgrade_213.rst
new file mode 100644
index 0000000..3a3497c
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_213.rst
@@ -0,0 +1,20 @@
+#############################
+Upgrading from 2.1.2 to 2.1.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.
+
+Step 2: Update your user guide
+==============================
+
+Please also replace your local copy of the user guide with the new
+version.
\ No newline at end of file
diff --git a/user_guide_src/source/installation/upgrade_300.rst b/user_guide_src/source/installation/upgrade_300.rst
index f304a71..ff60186 100644
--- a/user_guide_src/source/installation/upgrade_300.rst
+++ b/user_guide_src/source/installation/upgrade_300.rst
@@ -31,36 +31,161 @@
 Use of the ``$autoload['core']`` config array has been deprecated as of CodeIgniter 1.4.1 and is now removed.
 Move any entries that you might have listed there to ``$autoload['libraries']`` instead.
 
+***************************************************
+Step 4: Move your Log class overrides or extensions
+***************************************************
+
+The Log Class is considered as a "core" class and is now located in the
+**system/core/** directory. Therefore, in order for your Log class overrides
+or extensions to work, you need to move them to **application/core/**::
+
+	application/libraries/Log.php -> application/core/Log.php
+	application/libraries/MY_Log.php -> application/core/MY_log.php
+
+*********************************************************
+Step 5: Convert your Session usage from library to driver
+*********************************************************
+
+When you load (or autoload) the Session library, you must now load it as a driver instead of a library. This means
+calling ``$this->load->driver('session')`` instead of ``$this->load->library('session')`` and/or listing 'session'
+in ``$autoload['drivers']`` instead of ``$autoload['libraries']``.
+
+With the change from a single Session Library to the new Session Driver, two new config items have been added:
+
+   -  ``$config['sess_driver']`` selects which driver to initially load. Options are:
+       -  'cookie' (the default) for classic CodeIgniter cookie-based sessions
+       -  'native' for native PHP Session support
+       -  the name of a custom driver you have provided (see :doc:`Session Driver <../libraries/sessions>` for more info)
+   -  ``$config['sess_valid_drivers']`` provides an array of additional custom drivers to make available for loading
+
+As the new Session Driver library loads the classic Cookie driver by default and always makes 'cookie' and 'native'
+available as valid drivers, neither of these configuration items are required. However, it is recommended that you
+add them for clarity and ease of configuration in the future.
+
+If you have written a Session extension, you must move it into a 'Session' sub-directory of 'libraries', following the
+standard for Drivers. Also beware that some functions which are not part of the external Session API have moved into
+the drivers, so your extension may have to be broken down into separate library and driver class extensions.
+
 ***************************************
-Step 4: Update your config/database.php
+Step 6: Update your config/database.php
 ***************************************
 
 Due to 3.0.0's renaming of Active Record to Query Builder, inside your `config/database.php`, you will
-need to rename the `$active_record` variable to `$query_builder`.
+need to rename the `$active_record` variable to `$query_builder`
+::
 
-    $active_group = 'default';
-    // $active_record = TRUE;
-    $query_builder = TRUE;
+	$active_group = 'default';
+	// $active_record = TRUE;
+	$query_builder = TRUE;
 
 *******************************
-Step 5: Move your errors folder
+Step 7: Move your errors folder
 *******************************
 
 In version 3.0.0, the errors folder has been moved from _application/errors* to _application/views/errors*.
 
+*******************************************************
+Step 8: Update your config/routes.php containing (:any)
+*******************************************************
+
+Historically, CodeIgniter has always provided the **:any** wildcard in routing,
+with the intention of providing a way to match any character **within** an URI segment.
+
+However, the **:any** wildcard is actually just an alias for a regular expression
+and used to be executed in that manner as **.+**. This is considered a bug, as it
+also matches the / (forward slash) character, which is the URI segment delimiter
+and that was never the intention. In CodeIgniter 3, the **:any** wildcard will now
+represent **[^/]+**, so that it will not match a forward slash.
+
+There are certainly many developers that have utilized this bug as an actual feature.
+If you're one of them and want to match a forward slash, please use the **.+**
+regular expression::
+
+	(.+)	// matches ANYTHING
+	(:any)	// matches any character, except for '/'
+
+
 ****************************************************************************
-Step 6: Check the calls to Array Helper's element() and elements() functions
+Step 9: Check the calls to Array Helper's element() and elements() functions
 ****************************************************************************
 
 The default return value of these functions, when the required elements
 don't exist, has been changed from FALSE to NULL.
 
-***************************************************************
-Step 7: Remove usage of (previously) deprecated functionalities
-***************************************************************
+*************************************************************
+Step 10: Update usage of Database Forge's drop_table() method
+*************************************************************
 
-In addition to the ``$autoload['core']`` configuration setting, there's a number of other functionalities
-that have been removed in CodeIgniter 3.0.0:
+Up until now, ``drop_table()`` added an IF EXISTS clause by default or it didn't work
+at all with some drivers. In CodeIgniter 3.0, the IF EXISTS condition is no longer added
+by default and has an optional second parameter that allows that instead and is set to
+FALSE by default.
+
+If your application relies on IF EXISTS, you'll have to change its usage.
+
+::
+
+	// Now produces just DROP TABLE `table_name`
+	$this->dbforge->drop_table('table_name');
+
+	// Produces DROP TABLE IF EXISTS `table_name`
+	$this->dbforge->drop_table('table_name', TRUE);
+
+.. note:: The given example users MySQL-specific syntax, but it should work across
+	all drivers with the exception of ODBC.
+
+***********************************************************
+Step 11: Change usage of Email library with multiple emails
+***********************************************************
+
+The :doc:`Email Library <../libraries/email>` will automatically clear the
+set parameters after successfully sending emails. To override this behaviour,
+pass FALSE as the first parameter in the ``send()`` method:
+
+::
+
+	if ($this->email->send(FALSE))
+ 	{
+ 		// Parameters won't be cleared
+ 	}
+
+***************************************************
+Step 12: Update your Form_validation language lines
+***************************************************
+
+Two improvements have been made to the :doc:`Form Validation Library
+<../libraries/form_validation>`'s :doc:`language <../libraries/language>`
+files and error messages format:
+
+ - :doc:`Language Library <../libraries/language>` line keys now must be
+   prefixed with **form_validation_** in order to avoid collisions::
+
+	// Old
+	$lang['rule'] = ...
+
+	// New
+	$lang['form_validation_rule'] = ...
+
+ - The error messages format has been changed to use named parameters, to
+   allow more flexibility than what `sprintf()` offers::
+
+	// Old
+	'The %s field does not match the %s field.'
+
+	// New
+	'The {field} field does not match the {param} field.'
+
+.. note:: The old formatting still works, but the non-prefixed line keys
+	are DEPRECATED and scheduled for removal in CodeIgniter 3.1+.
+	Therefore you're encouraged to update its usage sooner rather than
+	later.
+
+****************************************************************
+Step 13: Remove usage of (previously) deprecated functionalities
+****************************************************************
+
+In addition to the ``$autoload['core']`` configuration setting, there's a
+number of other functionalities that have been removed in CodeIgniter 3.0.0:
 
 The SHA1 library
 ================
@@ -102,6 +227,40 @@
 .. note:: This function is still available, but you're strongly encouraged to remove it's usage sooner
 	rather than later.
 
+String helper repeater()
+========================
+
+:doc:`String Helper <../helpers/string_helper>` function :php:func:`repeater()` is now just an alias for
+PHP's native ``str_repeat()`` function. It is deprecated and scheduled for removal in CodeIgniter 3.1+.
+
+.. note:: This function is still available, but you're strongly encouraged to remove it's usage sooner
+	rather than later.
+
+String helper trim_slashes()
+============================
+
+:doc:`String Helper <../helpers/string_helper>` function :php:func:`trim_slashes()` is now just an alias
+for PHP's native ``trim()`` function (with a slash passed as its second argument). It is deprecated and
+scheduled for removal in CodeIgniter 3.1+.
+
+.. note:: This function is still available, but you're strongly encouraged to remove it's usage sooner
+	rather than later.
+
+Email helper functions
+======================
+
+:doc:`Email Helper <../helpers/email_helper>` only has two functions
+
+ - :php:func:`valid_email()`
+ - :php:func:`send_email()`
+
+Both of them are now aliases for PHP's native ``filter_var()`` and ``mail()`` functions, respectively.
+Therefore the :doc:`Email Helper <../helpers/email_helper>` altogether is being deprecated and
+is scheduled for removal in CodeIgniter 3.1+.
+
+.. note:: These functions are still available, but you're strongly encouraged to remove their usage
+	sooner rather than later.
+
 Date helper standard_date()
 ===========================
 
@@ -138,4 +297,59 @@
 CodeIgniter 3.1+.
 
 .. note:: This setting is still available, but you're strongly encouraged to remove its' usage sooner
-	rather than later.
\ No newline at end of file
+	rather than later.
+
+String helper random_string() types 'unique' and 'encrypt'
+==========================================================
+
+When using the :doc:`String Helper <helpers/string_helper>` function :php:func:`random_string()`,
+you should no longer pass the **unique** and **encrypt** randomization types. They are only
+aliases for **md5** and **sha1** respectively and are now deprecated and scheduled for removal
+in CodeIgniter 3.1+.
+
+.. note:: These options are still available, but you're strongly encouraged to remove their usage
+	sooner rather than later.
+
+URL helper url_title() separators 'dash' and 'underscore'
+=========================================================
+
+When using the :doc:`URL Helper <helpers/url_helper>` function :php:func:`url_title()`, you
+should no longer pass **dash** or **underscore** as the word separator. This function will
+now accept any character and you should just pass the chosen character directly, so you
+should write '-' instead of 'dash' and '_' instead of 'underscore'.
+
+**dash** and **underscore** now act as aliases and are deprecated and scheduled for removal
+in CodeIgniter 3.1+.
+
+.. note:: These options are still available, but you're strongly encouraged to remove their usage
+	sooner rather than later.
+
+Database Forge method add_column() with an AFTER clause
+=======================================================
+
+If you have used the **third parameter** for :doc:`Database Forge <database/forge>` method
+``add_column()`` to add a field for an AFTER clause, then you should change its usage.
+
+That third parameter has been deprecated and scheduled for removal in CodeIgniter 3.1+.
+
+You should now put AFTER clause field names in the field definition array instead::
+
+	// Old usage:
+	$field = array(
+		'new_field' => array('type' => 'TEXT')
+	);
+
+	$this->dbforge->add_column('table_name', $field, 'another_field');
+
+	// New usage:
+	$field = array(
+		'new_field' => array('type' => 'TEXT', 'after' => 'another_field')
+	);
+
+	$this->dbforge->add_column('table_name', $field);
+
+.. note:: The parameter is still available, but you're strongly encouraged to remove its usage
+	sooner rather than later.
+
+.. note:: This is for MySQL and CUBRID databases only! Other drivers don't support this
+	clause and will silently ignore it.
\ 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 545f344..4f27620 100644
--- a/user_guide_src/source/installation/upgrading.rst
+++ b/user_guide_src/source/installation/upgrading.rst
@@ -5,7 +5,8 @@
 Please read the upgrade notes corresponding to the version you are
 upgrading from.
 
--  :doc:`Upgrading from 2.1.2 to 3.0.0 <upgrade_300>`
+-  :doc:`Upgrading from 2.1.3 to 3.0.0 <upgrade_300>`
+-  :doc:`Upgrading from 2.1.2 to 2.1.3 <upgrade_213>`
 -  :doc:`Upgrading from 2.1.1 to 2.1.2 <upgrade_212>`
 -  :doc:`Upgrading from 2.1.0 to 2.1.1 <upgrade_211>`
 -  :doc:`Upgrading from 2.0.3 to 2.1.0 <upgrade_210>`
diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst
index 2f06d29..8d7b4c4 100644
--- a/user_guide_src/source/libraries/caching.rst
+++ b/user_guide_src/source/libraries/caching.rst
@@ -32,6 +32,17 @@
 	
 	echo $foo;
 
+You can also prefix cache item names via the **key_prefix** setting, which is useful
+to avoid collisions when you're running multiple applications on the same environment.
+
+::
+
+	$this->load->driver('cache',
+		array('adapter' => 'apc', 'backup' => 'file', 'key_prefix' => 'my_')
+	);
+
+	$this->cache->get('foo'); // Will get the cache entry named 'my_foo'
+
 ******************
 Function Reference
 ******************
@@ -39,7 +50,7 @@
 .. php:class:: CI_Cache
 
 is_supported()
-===============
+==============
 
 	.. php:method:: is_supported ( $driver )
 
@@ -130,7 +141,7 @@
 			$this->cache->clean();
 
 cache_info()
-=============
+============
 
 	.. php:method:: cache_info ( )
 
@@ -148,7 +159,7 @@
 	
 
 get_metadata()
-===============
+==============
 
 	.. php:method:: get_metadata ( $id )
 	
@@ -166,7 +177,6 @@
 		.. note:: The information returned and the structure of the data is dependent
 			on which adapter is being used.
 
-			
 *******
 Drivers
 *******
@@ -181,7 +191,7 @@
 	$this->cache->apc->save('foo', 'bar', 10);
 
 For more information on APC, please see
-`http://php.net/apc <http://php.net/apc>`_
+`http://php.net/apc <http://php.net/apc>`_.
 
 File-based Caching
 ==================
@@ -201,20 +211,49 @@
 =================
 
 Multiple Memcached servers can be specified in the memcached.php
-configuration file, located in the application/config/ directory.
+configuration file, located in the _application/config/* directory.
 
-All of the functions listed above can be accessed without passing a
+All of the methods listed above can be accessed without passing a
 specific adapter to the driver loader as follows::
 
 	$this->load->driver('cache');
 	$this->cache->memcached->save('foo', 'bar', 10);
 
 For more information on Memcached, please see
-`http://php.net/memcached <http://php.net/memcached>`_
+`http://php.net/memcached <http://php.net/memcached>`_.
+
+WinCache Caching
+================
+
+Under Windows, you can also utilize the WinCache driver.
+
+All of the functions listed above can be accessed without passing a
+specific adapter to the driver loader as follows::
+
+	$this->load->driver('cache');
+	$this->cache->wincache->save('foo', 'bar', 10);
+
+For more information on WinCache, please see
+`http://php.net/wincache <http://php.net/wincache>`_.
+
+Redis Caching
+=============
+
+All of the methods listed above can be accessed without passing a
+specific adapter to the driver loader as follows::
+
+	$this->load->driver('cache');
+	$this->cache->redis->save('foo', 'bar', 10);
+
+.. important:: Redis may require one or more of the following options:
+	**host**, **post**, **timeout**, **password**.
+
+The Redis PHP extension repository is located at
+`https://github.com/nicolasff/phpredis <https://github.com/nicolasff/phpredis>`_.
 
 Dummy Cache
 ===========
 
 This is a caching backend that will always 'miss.' It stores no data,
 but lets you keep your caching code in place in environments that don't
-support your chosen cache.
+support your chosen cache.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/cart.rst b/user_guide_src/source/libraries/cart.rst
index 6594b3b..716e94b 100644
--- a/user_guide_src/source/libraries/cart.rst
+++ b/user_guide_src/source/libraries/cart.rst
@@ -279,16 +279,22 @@
 from newest to oldest, by leaving this function blank, you'll automatically just get
 first added to the basket to last added to the basket.
 
-$this->cart->has_options(rowid);
-********************************
+$this->cart->get_item($row_id);
+*******************************
+
+Returns an array containing data for the item matching the specified row ID,
+or FALSE if no such item exists.
+
+$this->cart->has_options($row_id);
+**********************************
 
 Returns TRUE (boolean) if a particular row in the cart contains options.
 This function is designed to be used in a loop with
 $this->cart->contents(), since you must pass the rowid to this function,
 as shown in the Displaying the Cart example above.
 
-$this->cart->product_options(rowid);
-************************************
+$this->cart->product_options($row_id);
+**************************************
 
 Returns an array of options for a particular product. This function is
 designed to be used in a loop with $this->cart->contents(), since you
diff --git a/user_guide_src/source/libraries/email.rst b/user_guide_src/source/libraries/email.rst
index c5fa680..8643444 100644
--- a/user_guide_src/source/libraries/email.rst
+++ b/user_guide_src/source/libraries/email.rst
@@ -97,7 +97,7 @@
 **mailtype**        text                   text or html                 Type of mail. If you send HTML email you must send it as a complete web
                                                                         page. Make sure you don't have any relative links or relative image
                                                                         paths otherwise they will not work.
-**charset**         utf-8                                               Character set (utf-8, iso-8859-1, etc.).
+**charset**         ``$config['charset']``                              Character set (utf-8, iso-8859-1, etc.).
 **validate**        FALSE                  TRUE or FALSE (boolean)      Whether to validate the email address.
 **priority**        3                      1, 2, 3, 4, 5                Email Priority. 1 = highest. 5 = lowest. 3 = normal.
 **crlf**            \\n                    "\\r\\n" or "\\n" or "\\r"   Newline character. (Use "\\r\\n" to comply with RFC 822).
@@ -117,6 +117,13 @@
 
 	$this->email->from('you@example.com', 'Your Name');
 
+You can also set a Return-Path, to help redirect undelivered mail::
+
+	$this->email->from('you@example.com', 'Your Name', 'returned_emails@example.com');
+	
+.. note:: Return-Path can't be used if you've configured
+	'smtp' as your protocol.
+
 $this->email->reply_to()
 -------------------------
 
@@ -226,6 +233,14 @@
 	    // Generate error
 	}
 
+This function will automatically clear all parameters if the request was
+successful. To stop this behaviour pass FALSE::
+
+ 	if ($this->email->send(FALSE))
+ 	{
+ 		// Parameters won't be cleared
+ 	}
+
 $this->email->attach()
 ----------------------
 
@@ -253,11 +268,21 @@
 	$this->email->attach($buffer, 'attachment', 'report.pdf', 'application/pdf');
 
 $this->email->print_debugger()
--------------------------------
+------------------------------
 
 Returns a string containing any server messages, the email headers, and
 the email messsage. Useful for debugging.
 
+You can optionally specify which parts of the message should be printed.
+Valid options are: **headers**, **subject**, **body**.
+
+Example::
+
+	// Will only print the email headers, excluding the message subject and body
+	$this->email->print_debugger(array('headers'));
+
+.. note:: By default, all of the raw data will be printed.
+
 Overriding Word Wrapping
 ========================
 
diff --git a/user_guide_src/source/libraries/file_uploading.rst b/user_guide_src/source/libraries/file_uploading.rst
index 65cd5c7..c7965ae 100644
--- a/user_guide_src/source/libraries/file_uploading.rst
+++ b/user_guide_src/source/libraries/file_uploading.rst
@@ -26,7 +26,7 @@
 ========================
 
 Using a text editor, create a form called upload_form.php. In it, place
-this code and save it to your applications/views/ folder::
+this code and save it to your **application/views/** directory::
 
 	<html>
 	<head>
@@ -59,7 +59,7 @@
 ================
 
 Using a text editor, create a form called upload_success.php. In it,
-place this code and save it to your applications/views/ folder::
+place this code and save it to your **application/views/** directory::
 
 	<html>
 	<head>
@@ -84,7 +84,7 @@
 ==============
 
 Using a text editor, create a controller called upload.php. In it, place
-this code and save it to your applications/controllers/ folder::
+this code and save it to your **application/controllers/** directory::
 
 	<?php
 
@@ -127,12 +127,12 @@
 	}
 	?>
 
-The Upload Folder
-=================
+The Upload Directory
+====================
 
-You'll need a destination folder for your uploaded images. Create a
-folder at the root of your CodeIgniter installation called uploads and
-set its file permissions to 777.
+You'll need a destination directory for your uploaded images. Create a
+directory at the root of your CodeIgniter installation called uploads
+and set its file permissions to 777.
 
 Try it!
 =======
@@ -153,7 +153,7 @@
 =============================
 
 Like most other classes in CodeIgniter, the Upload class is initialized
-in your controller using the $this->load->library function::
+in your controller using the ``$this->load->library()`` method::
 
 	$this->load->library('upload');
 
@@ -175,7 +175,7 @@
 
 	$this->load->library('upload', $config);
 
-	// Alternately you can set preferences by calling the initialize function. Useful if you auto-load the class:
+	// Alternately you can set preferences by calling the ``initialize()`` method. Useful if you auto-load the class:
 	$this->upload->initialize($config);
 
 The above preferences should be fairly self-explanatory. Below is a
@@ -190,22 +190,27 @@
 ============================ ================= ======================= ======================================================================
 Preference                   Default Value     Options                 Description
 ============================ ================= ======================= ======================================================================
-**upload_path**              None              None                    The path to the folder where the upload should be placed. The folder
-                                                                       must be writable and the path can be absolute or relative.
+**upload_path**              None              None                    The path to the directory where the upload should be placed. The
+                                                                       directory must be writable and the path can be absolute or relative.
 **allowed_types**            None              None                    The mime types corresponding to the types of files you allow to be
                                                                        uploaded. Usually the file extension can be used as the mime type.
                                                                        Separate multiple types with a pipe.
 **file_name**                None              Desired file name       If set CodeIgniter will rename the uploaded file to this name. The
                                                                        extension provided in the file name must also be an allowed file type.
+                                                                       If no extension is provided in the original file_name will be used.
 **overwrite**                FALSE             TRUE/FALSE (boolean)    If set to true, if a file with the same name as the one you are
                                                                        uploading exists, it will be overwritten. If set to false, a number will
                                                                        be appended to the filename if another with the same name exists.
 **max_size**                 0                 None                    The maximum size (in kilobytes) that the file can be. Set to zero for no
                                                                        limit. Note: Most PHP installations have their own limit, as specified
                                                                        in the php.ini file. Usually 2 MB (or 2048 KB) by default.
-**max_width**                0                 None                    The maximum width (in pixels) that the file can be. Set to zero for no
+**max_width**                0                 None                    The maximum width (in pixels) that the image can be. Set to zero for no
                                                                        limit.
-**max_height**               0                 None                    The maximum height (in pixels) that the file can be. Set to zero for no
+**max_height**               0                 None                    The maximum height (in pixels) that the image can be. Set to zero for no
+                                                                       limit.
+**min_width**                0                 None                    The minimum width (in pixels) that the image can be. Set to zero for no
+                                                                       limit.
+**min_height**               0                 None                    The minimum height (in pixels) that the image can be. Set to zero for no
                                                                        limit.
 **max_filename**             0                 None                    The maximum length that a file name can be. Set to zero for no limit.
 **max_filename_increment**   100               None                    When overwrite is set to FALSE, use this to set the maximum filename
@@ -226,42 +231,46 @@
 If you prefer not to set preferences using the above method, you can
 instead put them into a config file. Simply create a new file called the
 upload.php, add the $config array in that file. Then save the file in:
-config/upload.php and it will be used automatically. You will NOT need
-to use the $this->upload->initialize function if you save your
+**config/upload.php** and it will be used automatically. You will NOT
+need to use the ``$this->upload->initialize()`` method if you save your
 preferences in a config file.
 
-******************
-Function Reference
-******************
+***************
+Class Reference
+***************
 
-The following functions are available
+The following methods are available:
 
 $this->upload->do_upload()
-===========================
+==========================
 
-Performs the upload based on the preferences you've set. Note: By
-default the upload routine expects the file to come from a form field
-called userfile, and the form must be a "multipart type::
+Performs the upload based on the preferences you've set.
+
+.. note:: By default the upload routine expects the file to come from
+	a form field called userfile, and the form must be of type
+	"multipart".
+
+::
 
 	<form method="post" action="some_action" enctype="multipart/form-data" />
 
 If you would like to set your own field name simply pass its value to
-the do_upload function::
+the ``do_upload()`` method::
 
 	$field_name = "some_field_name";
 	$this->upload->do_upload($field_name);
 
 $this->upload->display_errors()
-================================
+===============================
 
-Retrieves any error messages if the do_upload() function returned
-false. The function does not echo automatically, it returns the data so
+Retrieves any error messages if the ``do_upload()`` method returned
+false. The method does not echo automatically, it returns the data so
 you can assign it however you need.
 
 Formatting Errors
 *****************
 
-By default the above function wraps any errors within <p> tags. You can
+By default the above method wraps any errors within <p> tags. You can
 set your own delimiters like this::
 
 	$this->upload->display_errors('<p>', '</p>');
@@ -269,25 +278,25 @@
 $this->upload->data()
 =====================
 
-This is a helper function that returns an array containing all of the
+This is a helper method that returns an array containing all of the
 data related to the file you uploaded. Here is the array prototype::
 
 	Array
 	(
-	    [file_name]    => mypic.jpg
-	    [file_type]    => image/jpeg
-	    [file_path]    => /path/to/your/upload/
-	    [full_path]    => /path/to/your/upload/jpg.jpg
-	    [raw_name]     => mypic
-	    [orig_name]    => mypic.jpg
-	    [client_name]  => mypic.jpg
-	    [file_ext]     => .jpg
-	    [file_size]    => 22.2
-	    [is_image]     => 1
-	    [image_width]  => 800
-	    [image_height] => 600
-	    [image_type]   => jpeg
-	    [image_size_str] => width="800" height="200"
+		[file_name]	=> mypic.jpg
+		[file_type]	=> image/jpeg
+		[file_path]	=> /path/to/your/upload/
+		[full_path]	=> /path/to/your/upload/jpg.jpg
+		[raw_name]	=> mypic
+		[orig_name]	=> mypic.jpg
+		[client_name]	=> mypic.jpg
+		[file_ext]	=> .jpg
+		[file_size]	=> 22.2
+		[is_image]	=> 1
+		[image_width]	=> 800
+		[image_height]	=> 600
+		[image_type]	=> jpeg
+		[image_size_str] => width="800" height="200"
 	)
 
 To return one element from the array::
@@ -331,4 +340,4 @@
 Image type. Typically the file extension without the period.
 **image_size_str**
 A string containing the width and height. Useful to put into an image
-tag.
+tag.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst
index 3bcad7b..fbe540c 100644
--- a/user_guide_src/source/libraries/form_validation.rst
+++ b/user_guide_src/source/libraries/form_validation.rst
@@ -53,7 +53,7 @@
 #. A :doc:`View <../general/views>` file containing a form.
 #. A View file containing a "success" message to be displayed upon
    successful submission.
-#. A :doc:`controller <../general/controllers>` function to receive and
+#. A :doc:`controller <../general/controllers>` method to receive and
    process the submitted data.
 
 Let's create those three things, using a member sign-up form as the
@@ -63,7 +63,7 @@
 ========
 
 Using a text editor, create a form called myform.php. In it, place this
-code and save it to your applications/views/ folder::
+code and save it to your application/views/ folder::
 
 	<html>
 	<head>
@@ -98,7 +98,7 @@
 ================
 
 Using a text editor, create a form called formsuccess.php. In it, place
-this code and save it to your applications/views/ folder::
+this code and save it to your application/views/ folder::
 
 	<html>
 	<head>
@@ -117,7 +117,7 @@
 ==============
 
 Using a text editor, create a controller called form.php. In it, place
-this code and save it to your applications/controllers/ folder::
+this code and save it to your application/controllers/ folder::
 
 	<?php
 
@@ -139,7 +139,6 @@
 			}
 		}
 	}
-	?>
 
 Try it!
 =======
@@ -152,7 +151,7 @@
 because you haven't set up any validation rules yet.
 
 **Since you haven't told the Form Validation class to validate anything
-yet, it returns FALSE (boolean false) by default. The run() function
+yet, it returns FALSE (boolean false) by default. ``The run()`` method
 only returns TRUE if it has successfully applied your rules without any
 of them failing.**
 
@@ -176,7 +175,7 @@
    This function will return any error messages sent back by the
    validator. If there are no messages it returns an empty string.
 
-The controller (form.php) has one function: index(). This function
+The controller (form.php) has one method: ``index()``. This method
 initializes the validation class and loads the form helper and URL
 helper used by your view files. It also runs the validation routine.
 Based on whether the validation was successful it either presents the
@@ -190,11 +189,11 @@
 CodeIgniter lets you set as many validation rules as you need for a
 given field, cascading them in order, and it even lets you prep and
 pre-process the field data at the same time. To set validation rules you
-will use the set_rules() function::
+will use the ``set_rules()`` method::
 
 	$this->form_validation->set_rules();
 
-The above function takes **three** parameters as input:
+The above method takes **three** parameters as input:
 
 #. The field name - the exact name you've given the form field.
 #. A "human" name for this field, which will be inserted into the error
@@ -202,11 +201,11 @@
    a human name of "Username".
 #. The validation rules for this form field.
 
-.. note:: If you would like the field
-	name to be stored in a language file, please see :ref:`translating-field-names`.
+.. note:: If you would like the field name to be stored in a language
+	file, please see :ref:`translating-field-names`.
 
 Here is an example. In your controller (form.php), add this code just
-below the validation initialization function::
+below the validation initialization method::
 
 	$this->form_validation->set_rules('username', 'Username', 'required');
 	$this->form_validation->set_rules('password', 'Password', 'required');
@@ -240,7 +239,6 @@
 			}
 		}
 	}
-	?>
 
 Now submit the form with the fields blank and you should see the error
 messages. If you submit the form with all the fields populated you'll
@@ -252,32 +250,32 @@
 Setting Rules Using an Array
 ============================
 
-Before moving on it should be noted that the rule setting function can
+Before moving on it should be noted that the rule setting method can
 be passed an array if you prefer to set all your rules in one action. If
-you use this approach you must name your array keys as indicated::
+you use this approach, you must name your array keys as indicated::
 
 	$config = array(
-	               array(
-	                     'field'   => 'username', 
-	                     'label'   => 'Username', 
-	                     'rules'   => 'required'
-	                  ),
-	               array(
-	                     'field'   => 'password', 
-	                     'label'   => 'Password', 
-	                     'rules'   => 'required'
-	                  ),
-	               array(
-	                     'field'   => 'passconf', 
-	                     'label'   => 'Password Confirmation', 
-	                     'rules'   => 'required'
-	                  ),   
-	               array(
-	                     'field'   => 'email', 
-	                     'label'   => 'Email', 
-	                     'rules'   => 'required'
-	                  )
-	            );
+		array(
+			'field' => 'username',
+			'label' => 'Username',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'password',
+			'label' => 'Password',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'passconf',
+			'label' => 'Password Confirmation',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'email',
+			'label' => 'Email',
+			'rules' => 'required'
+		)
+	);
 
 	$this->form_validation->set_rules($config);
 
@@ -285,13 +283,12 @@
 ===============
 
 CodeIgniter lets you pipe multiple rules together. Let's try it. Change
-your rules in the third parameter of rule setting function, like this::
+your rules in the third parameter of rule setting method, like this::
 
 	$this->form_validation->set_rules('username', 'Username', 'required|min_length[5]|max_length[12]|is_unique[users.username]');
-	$this->form_validation->set_rules('password', 'Password', 'required|matches[passconf]');
-	$this->form_validation->set_rules('passconf', 'Password Confirmation', 'required');
+	$this->form_validation->set_rules('password', 'Password', 'required');
+	$this->form_validation->set_rules('passconf', 'Password Confirmation', 'required|matches[password]');
 	$this->form_validation->set_rules('email', 'Email', 'required|valid_email|is_unique[users.email]');
-	
 
 The above code sets the following rules:
 
@@ -304,32 +301,33 @@
 new error messages that correspond to your new rules. There are numerous
 rules available which you can read about in the validation reference.
 
-.. note:: You can also pass an array of rules to set_rules(), instead of a string. Example::
+.. note:: You can also pass an array of rules to ``set_rules()``,
+	instead of a string. Example::
 
 	$this->form_validation->set_rules('username', 'Username', array('required', 'min_length[5]'));
 
 Prepping Data
 =============
 
-In addition to the validation functions like the ones we used above, you
+In addition to the validation method like the ones we used above, you
 can also prep your data in various ways. For example, you can set up
 rules like this::
 
 	$this->form_validation->set_rules('username', 'Username', 'trim|required|min_length[5]|max_length[12]|xss_clean');
-	$this->form_validation->set_rules('password', 'Password', 'trim|required|matches[passconf]|md5');
-	$this->form_validation->set_rules('passconf', 'Password Confirmation', 'trim|required');
+	$this->form_validation->set_rules('password', 'Password', 'trim|required|md5');
+	$this->form_validation->set_rules('passconf', 'Password Confirmation', 'trim|required|matches[password]');
 	$this->form_validation->set_rules('email', 'Email', 'trim|required|valid_email');
 
 In the above example, we are "trimming" the fields, converting the
-password to MD5, and running the username through the "xss_clean"
-function, which removes malicious data.
+password to MD5, and running the username through the `xss_clean()`
+method, which removes malicious data.
 
 **Any native PHP function that accepts one parameter can be used as a
 rule, like htmlspecialchars, trim, md5, etc.**
 
 .. note:: You will generally want to use the prepping functions
-	**after** the validation rules so if there is an error, the original
-	data will be shown in the form.
+	**after** the validation rules so if there is an error, the
+	original data will be shown in the form.
 
 Re-populating the form
 ======================
@@ -342,9 +340,9 @@
 	set_value('field name')
 
 Open your myform.php view file and update the **value** in each field
-using the set_value() function:
+using the ``set_value()`` function:
 
-**Don't forget to include each field name in the set_value()
+**Don't forget to include each field name in the ``set_value()``
 functions!**
 
 ::
@@ -381,9 +379,9 @@
 Now reload your page and submit the form so that it triggers an error.
 Your form fields should now be re-populated
 
-.. note:: The :ref:`function-reference` section below
-	contains functions that permit you to re-populate <select> menus, radio
-	buttons, and checkboxes.
+.. note:: The :ref:`class-reference` section below
+	contains functions that permit you to re-populate <select> menus,
+	radio buttons, and checkboxes.
 
 **Important Note:** If you use an array as the name of a form field, you
 must supply it as an array to the function. Example::
@@ -392,20 +390,20 @@
 
 For more info please see the :ref:`using-arrays-as-field-names` section below.
 
-Callbacks: Your own Validation Functions
-========================================
+Callbacks: Your own Validation Methods
+======================================
 
 The validation system supports callbacks to your own validation
-functions. This permits you to extend the validation class to meet your
+methods. This permits you to extend the validation class to meet your
 needs. For example, if you need to run a database query to see if the
-user is choosing a unique username, you can create a callback function
-that does that. Let's create a example of this.
+user is choosing a unique username, you can create a callback method
+that does that. Let's create an example of this.
 
 In your controller, change the "username" rule to this::
 
 	$this->form_validation->set_rules('username', 'Username', 'callback_username_check');
 
-Then add a new function called username_check to your controller.
+Then add a new method called ``username_check()`` to your controller.
 Here's how your controller should now look::
 
 	<?php
@@ -433,11 +431,11 @@
 			}
 		}
 
-		public function username_check($str)
+		protected function username_check($str)
 		{
 			if ($str == 'test')
 			{
-				$this->form_validation->set_message('username_check', 'The %s field can not be the word "test"');
+				$this->form_validation->set_message('username_check', 'The {field} field can not be the word "test"');
 				return FALSE;
 			}
 			else
@@ -447,17 +445,16 @@
 		}
 
 	}
-	?>
 
 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
+can see that the form field data was passed to your callback method
 for you to process.
 
-To invoke a callback just put the function name in a rule, with
+To invoke a callback just put the method name in a rule, with
 "callback\_" as the rule **prefix**. 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**[bar]**",
-then it will be passed as the second argument of your callback function.
+parameter in your callback method, just add it normally after the
+method name between square brackets, as in: "callback_foo**[bar]**",
+then it will be passed as the second argument of your callback method.
 
 .. note:: You can also process the form data that is passed to your
 	callback and return it. If your callback returns anything other than a
@@ -470,39 +467,44 @@
 ======================
 
 All of the native error messages are located in the following language
-file: language/english/form_validation_lang.php
+file: **system/language/english/form_validation_lang.php**
 
 To set your own custom message you can either edit that file, or use the
-following function::
+following method::
 
 	$this->form_validation->set_message('rule', 'Error Message');
 
 Where rule corresponds to the name of a particular rule, and Error
 Message is the text you would like displayed.
 
-If you include %s in your error string, it will be replaced with the
-"human" name you used for your field when you set your rules.
+If you'd like to include a field's "human" name, or the optional 
+parameter some rules allow for (such as max_length), you can add the 
+**{field}** and **{param}** tags to your message, respectively.
 
-In the "callback" example above, the error message was set by passing
-the name of the function::
+	$this->form_validation->set_message('min_length', '{field} must have at least {param} characters.');
+
+On a field with the human name Username and a rule of min_length[5], an
+error would display: "Username must have at least 5 characters."
+
+.. note:: The old `sprintf()` method of using **%s** in your error messages
+	will still work, however it will override the tags above. You should
+	use one or the other.
+
+In the callback rule example above, the error message was set by passing
+the name of the method (without the "callback_" prefix)::
 
 	$this->form_validation->set_message('username_check')
 
-You can also override any error message found in the language file. For
-example, to change the message for the "required" rule you will do this::
-
-	$this->form_validation->set_message('required', 'Your custom message here');
-
 .. _translating-field-names:
 
 Translating Field Names
 =======================
 
 If you would like to store the "human" name you passed to the
-set_rules() function in a language file, and therefore make the name
+``set_rules()`` method in a language file, and therefore make the name
 able to be translated, here's how:
 
-First, prefix your "human" name with lang:, as in this example::
+First, prefix your "human" name with **lang:**, as in this example::
 
 	 $this->form_validation->set_rules('first_name', 'lang:first_name', 'required');
 
@@ -530,7 +532,7 @@
 globally, individually, or change the defaults in a config file.
 
 #. **Changing delimiters Globally**
-   To globally change the error delimiters, in your controller function,
+   To globally change the error delimiters, in your controller method,
    just after loading the Form Validation class, add this::
 
       $this->form_validation->set_error_delimiters('<div class="error">', '</div>');
@@ -549,16 +551,15 @@
 
 #. **Set delimiters in a config file**
    You can add your error delimiters in application/config/form_validation.php as follows::
-   
+
       $config['error_prefix'] = '<div class="error_prefix">';
       $config['error_suffix'] = '</div>';
 
-
 Showing Errors Individually
 ===========================
 
 If you prefer to show an error message next to each form field, rather
-than as a list, you can use the form_error() function.
+than as a list, you can use the ``form_error()`` function.
 
 Try it! Change your form so that it looks like this::
 
@@ -581,8 +582,8 @@
 If there are no errors, nothing will be shown. If there is an error, the
 message will appear.
 
-**Important Note:** If you use an array as the name of a form field, you
-must supply it as an array to the function. Example::
+.. important:: If you use an array as the name of a form field, you
+	must supply it as an array to the function. Example::
 
 	<?php echo form_error('options[size]'); ?>
 	<input type="text" name="options[size]" value="<?php echo set_value("options[size]"); ?>" size="50" />
@@ -592,25 +593,26 @@
 Validating an Array (other than $_POST)
 =======================================
 
-Sometimes you may want to validate an array that does not originate from $_POST data.
+Sometimes you may want to validate an array that does not originate from ``$_POST`` data.
 
 In this case, you can specify the array to be validated::
-	
+
 	$data = array(
-			'username' => 'johndoe',
-			'password' => 'mypassword',
-		 	'passconf' => 'mypassword'
-		);
+		'username' => 'johndoe',
+		'password' => 'mypassword',
+		'passconf' => 'mypassword'
+	);
 
 	$this->form_validation->set_data($data);
 
-Creating validation rules, running the validation and retrieving error messages works the same whether you are
-validating $_POST data or an array.
+Creating validation rules, running the validation, and retrieving error messages works the
+same whether you are validating ``$_POST`` data or an array.
 
-**Important Note:** If you want to validate more than one array during a single execution, then you should	
-call the reset_validation() function before setting up rules and validating the new array.
+.. important:: If you want to validate more than one array during a single
+	execution, then you should call the ``reset_validation()`` method
+	before setting up rules and validating the new array.
 
-For more info please see the :ref:`function-reference` section below.
+For more info please see the :ref:`class-reference` section below.
 
 .. _saving-groups:
 
@@ -621,7 +623,7 @@
 A nice feature of the Form Validation class is that it permits you to
 store all your validation rules for your entire application in a config
 file. You can organize these rules into "groups". These groups can
-either be loaded automatically when a matching controller/function is
+either be loaded automatically when a matching controller/method is
 called, or you can manually call each set as needed.
 
 How to save your rules
@@ -633,32 +635,32 @@
 the validation array will have this prototype::
 
 	$config = array(
-	               array(
-	                     'field'   => 'username', 
-	                     'label'   => 'Username', 
-	                     'rules'   => 'required'
-	                  ),
-	               array(
-	                     'field'   => 'password', 
-	                     'label'   => 'Password', 
-	                     'rules'   => 'required'
-	                  ),
-	               array(
-	                     'field'   => 'passconf', 
-	                     'label'   => 'Password Confirmation', 
-	                     'rules'   => 'required'
-	                  ),   
-	               array(
-	                     'field'   => 'email', 
-	                     'label'   => 'Email', 
-	                     'rules'   => 'required'
-	                  )
-	            );
+		array(
+			'field' => 'username',
+			'label' => 'Username',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'password',
+			'label' => 'Password',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'passconf',
+			'label' => 'Password Confirmation',
+			'rules' => 'required'
+		),
+		array(
+			'field' => 'email',
+			'label' => 'Email',
+			'rules' => 'required'
+		)
+	);
 
 Your validation rule file will be loaded automatically and used when you
-call the run() function.
+call the ``run()`` method.
 
-Please note that you MUST name your array $config.
+Please note that you MUST name your ``$config`` array.
 
 Creating Sets of Rules
 ======================
@@ -669,126 +671,125 @@
 You can name your rules anything you want::
 
 	$config = array(
-	                 'signup' => array(
-	                                    array(
-	                                            'field' => 'username',
-	                                            'label' => 'Username',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'password',
-	                                            'label' => 'Password',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'passconf',
-	                                            'label' => 'PasswordConfirmation',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'email',
-	                                            'label' => 'Email',
-	                                            'rules' => 'required'
-	                                         )
-	                                    ),
-	                 'email' => array(
-	                                    array(
-	                                            'field' => 'emailaddress',
-	                                            'label' => 'EmailAddress',
-	                                            'rules' => 'required|valid_email'
-	                                         ),
-	                                    array(
-	                                            'field' => 'name',
-	                                            'label' => 'Name',
-	                                            'rules' => 'required|alpha'
-	                                         ),
-	                                    array(
-	                                            'field' => 'title',
-	                                            'label' => 'Title',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'message',
-	                                            'label' => 'MessageBody',
-	                                            'rules' => 'required'
-	                                         )
-	                                    )                          
-	               );
+		'signup' => array(
+			array(
+				'field' => 'username',
+				'label' => 'Username',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'password',
+				'label' => 'Password',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'passconf',
+				'label' => 'Password Confirmation',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'email',
+				'label' => 'Email',
+				'rules' => 'required'
+			)
+		),
+		'email' => array(
+			array(
+				'field' => 'emailaddress',
+				'label' => 'EmailAddress',
+				'rules' => 'required|valid_email'
+			),
+			array(
+				'field' => 'name',
+				'label' => 'Name',
+				'rules' => 'required|alpha'
+			),
+			array(
+				'field' => 'title',
+				'label' => 'Title',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'message',
+				'label' => 'MessageBody',
+				'rules' => 'required'
+			)
+		)
+	);
 
 Calling a Specific Rule Group
 =============================
 
-In order to call a specific group you will pass its name to the run()
-function. For example, to call the signup rule you will do this::
+In order to call a specific group, you will pass its name to the ``run()``
+method. For example, to call the signup rule you will do this::
 
 	if ($this->form_validation->run('signup') == FALSE)
 	{
-	   $this->load->view('myform');
+		$this->load->view('myform');
 	}
 	else
 	{
-	   $this->load->view('formsuccess');
+		$this->load->view('formsuccess');
 	}
 
-Associating a Controller Function with a Rule Group
-===================================================
+Associating a Controller Method with a Rule Group
+=================================================
 
 An alternate (and more automatic) method of calling a rule group is to
-name it according to the controller class/function you intend to use it
+name it according to the controller class/method you intend to use it
 with. For example, let's say you have a controller named Member and a
-function named signup. Here's what your class might look like::
+method named signup. Here's what your class might look like::
 
 	<?php
 
 	class Member extends CI_Controller {
 
-	   function signup()
-	   {      
-	      $this->load->library('form_validation');
+		public function signup()
+		{
+			$this->load->library('form_validation');
 
-	      if ($this->form_validation->run() == FALSE)
-	      {
-	         $this->load->view('myform');
-	      }
-	      else
-	      {
-	         $this->load->view('formsuccess');
-	      }
-	   }
+			if ($this->form_validation->run() == FALSE)
+			{
+				$this->load->view('myform');
+			}
+			else
+			{
+				$this->load->view('formsuccess');
+			}
+		}
 	}
-	?>
 
 In your validation config file, you will name your rule group
 member/signup::
 
 	$config = array(
-	           'member/signup' => array(
-	                                    array(
-	                                            'field' => 'username',
-	                                            'label' => 'Username',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'password',
-	                                            'label' => 'Password',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'passconf',
-	                                            'label' => 'PasswordConfirmation',
-	                                            'rules' => 'required'
-	                                         ),
-	                                    array(
-	                                            'field' => 'email',
-	                                            'label' => 'Email',
-	                                            'rules' => 'required'
-	                                         )
-	                                    )
-	               );
+		'member/signup' => array(
+			array(
+				'field' => 'username',
+				'label' => 'Username',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'password',
+				'label' => 'Password',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'passconf',
+				'label' => 'PasswordConfirmation',
+				'rules' => 'required'
+			),
+			array(
+				'field' => 'email',
+				'label' => 'Email',
+				'rules' => 'required'
+			)
+		)
+	);
 
-When a rule group is named identically to a controller class/function it
-will be used automatically when the run() function is invoked from that
-class/function.
+When a rule group is named identically to a controller class/method it
+will be used automatically when the ``run()`` method is invoked from that
+class/method.
 
 .. _using-arrays-as-field-names:
 
@@ -861,8 +862,10 @@
 ========================= ========== ============================================================================================= =======================
 **required**              No         Returns FALSE if the form element is empty.                                                                          
 **matches**               Yes        Returns FALSE if the form element does not match the one in the parameter.                    matches[form_item]     
-**is_unique**             Yes        Returns FALSE if the form element is not unique to the                                        is_unique[table.field] 
-                                     table and field name in the parameter. is_unique[table.field]                                                        
+**differs**               Yes        Returns FALSE if the form element does not differ from the one in the parameter.              differs[form_item]     
+**is_unique**             Yes        Returns FALSE if the form element is not unique to the table and field name in the            is_unique[table.field] 
+                                     parameter. Note: This rule requires :doc:`Query Builder <../database/query_builder>` to be                             
+                                     enabled in order to work.
 **max_length**            Yes        Returns FALSE if the form element is longer then the parameter value.                         max_length[12]         
 **exact_length**          Yes        Returns FALSE if the form element is not exactly the parameter value.                         exact_length[8]        
 **greater_than**          Yes        Returns FALSE if the form element is less than or equal to the parameter value or not         greater_than[8]
@@ -884,7 +887,7 @@
                                      0, 1, 2, 3, etc.
 **is_natural_no_zero**    No         Returns FALSE if the form element contains anything other than a natural
                                      number, but not zero: 1, 2, 3, etc.
-**is_unique**             Yes        Returns FALSE if the form element is not unique in a database table.                          is_unique[table.field] 
+**valid_url**             No         Returns FALSE if the form element does not contain a valid URL.
 **valid_email**           No         Returns FALSE if the form element does not contain a valid email address.
 **valid_emails**          No         Returns FALSE if any value provided in a comma separated list is not a valid email.
 **valid_ip**              No         Returns FALSE if the supplied IP is not valid.
@@ -892,7 +895,7 @@
 **valid_base64**          No         Returns FALSE if the supplied string contains anything other than valid Base64 characters.
 ========================= ========== ============================================================================================= =======================
 
-.. note:: These rules can also be called as discrete functions. For
+.. note:: These rules can also be called as discrete methods. For
 	example::
 
 		$this->form_validation->required($string);
@@ -905,35 +908,35 @@
 Prepping Reference
 ******************
 
-The following is a list of all the prepping functions that are available
+The following is a list of all the prepping methods that are available
 to use:
 
 ==================== ========= ===================================================================================================
 Name                 Parameter Description
 ==================== ========= ===================================================================================================
-**xss_clean**        No        Runs the data through the XSS filtering function, described in the :doc:`Input Class <input>` page.
+**xss_clean**        No        Runs the data through the XSS filtering method, described in the :doc:`Input Class <input>` page.
 **prep_for_form**    No        Converts special characters so that HTML data can be shown in a form field without breaking it.
 **prep_url**         No        Adds "\http://" to URLs if missing.
 **strip_image_tags** No        Strips the HTML from image tags leaving the raw URL.
 **encode_php_tags**  No        Converts PHP tags to entities.
 ==================== ========= ===================================================================================================
 
-.. note:: You can also use any native PHP functions that permit one
-	parameter, like trim, htmlspecialchars, urldecode, etc.
+.. note:: You can also use any native PHP functions that permits one
+	parameter, like ``trim()``, ``htmlspecialchars()``, ``urldecode()``,
+	etc.
 
-.. _function-reference:
+.. _class-reference:
 
-******************
-Function Reference
-******************
+***************
+Class Reference
+***************
 
 .. php:class:: Form_validation
 
-The following functions are intended for use in your controller
-functions.
+The following methods are intended for use in your controller.
 
-$this->form_validation->set_rules();
-======================================
+$this->form_validation->set_rules()
+===================================
 
 	.. php:method:: set_rules ($field, $label = '', $rules = '')
 
@@ -941,27 +944,27 @@
 		:param string $label: The field label
 		:param mixed $rules: The rules, as a string with rules separated by a pipe "|", or an array or rules.
 		:rtype: Object
-	
+
 		Permits you to set validation rules, as described in the tutorial
 		sections above:
 
 	-  :ref:`setting-validation-rules`
 	-  :ref:`saving-groups`
 
-$this->form_validation->run();
-===============================
+$this->form_validation->run()
+=============================
 	
 	.. php:method:: run ($group = '')
 
 		:param string $group: The name of the validation group to run
 		:rtype: Boolean
-	
+
 		Runs the validation routines. Returns boolean TRUE on success and FALSE
 		on failure. You can optionally pass the name of the validation group via
-		the function, as described in: :ref:`saving-groups`
+		the method, as described in: :ref:`saving-groups`
 
-$this->form_validation->set_message();
-========================================
+$this->form_validation->set_message()
+=====================================
 	
 	.. php:method:: set_message ($lang, $val = '')
 
@@ -971,8 +974,8 @@
 
 		Permits you to set custom error messages. See :ref:`setting-error-messages`
 
-$this->form_validation->set_data();
-========================================
+$this->form_validation->set_data()
+==================================
 	
 	.. php:method:: set_data ($data = '')
 
@@ -981,16 +984,16 @@
 		Permits you to set an array for validation, instead of using the default
 		$_POST array.
 
-$this->form_validation->reset_validation();
-===========================================
+$this->form_validation->reset_validation()
+==========================================
 
- .. php:method:: reset_validation ()
+	.. php:method:: reset_validation ()
 
-    Permits you to reset the validation when you validate more than one array.
-	This function should be called before validating each new array.
+		Permits you to reset the validation when you validate more than one array.
+		This method should be called before validating each new array.
 
-$this->form_validation->error_array();
-========================================
+$this->form_validation->error_array()
+=====================================
 	
 	.. php:method:: error_array ()
 
@@ -1009,7 +1012,7 @@
 **do not** require you to prepend them with $this->form_validation.
 
 form_error()
-=============
+============
 
 Shows an individual error message associated with the field name
 supplied to the function. Example::
@@ -1020,7 +1023,7 @@
 :ref:`changing-delimiters` section above.
 
 validation_errors()
-====================
+===================
 
 Shows all error messages as a string: Example::
 
@@ -1030,7 +1033,7 @@
 :ref:`changing-delimiters` section above.
 
 set_value()
-============
+===========
 
 Permits you to set the value of an input form or textarea. You must
 supply the field name via the first parameter of the function. The
@@ -1042,7 +1045,7 @@
 The above form will show "0" when loaded for the first time.
 
 set_select()
-=============
+============
 
 If you use a <select> menu, this function permits you to display the
 menu item that was selected. The first parameter must contain the name
@@ -1059,7 +1062,7 @@
 	</select>
 
 set_checkbox()
-===============
+==============
 
 Permits you to display a checkbox in the state it was submitted. The
 first parameter must contain the name of the checkbox, the second
@@ -1070,12 +1073,12 @@
 	<input type="checkbox" name="mycheck[]" value="2" <?php echo set_checkbox('mycheck[]', '2'); ?> />
 
 set_radio()
-============
+===========
 
 Permits you to display radio buttons in the state they were submitted.
 This function is identical to the **set_checkbox()** function above.
 
 ::
 
-	<input type="radio" name="myradio" value="1" <?php echo  set_radio('myradio', '1', TRUE); ?> />
-	<input type="radio" name="myradio" value="2" <?php echo  set_radio('myradio', '2'); ?> />
+	<input type="radio" name="myradio" value="1" <?php echo set_radio('myradio', '1', TRUE); ?> />
+	<input type="radio" name="myradio" value="2" <?php echo set_radio('myradio', '2'); ?> />
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/image_lib.rst b/user_guide_src/source/libraries/image_lib.rst
index ed6575c..dcdccbd 100644
--- a/user_guide_src/source/libraries/image_lib.rst
+++ b/user_guide_src/source/libraries/image_lib.rst
@@ -91,9 +91,9 @@
 	    echo $this->image_lib->display_errors();
 	}
 
-Note: You can optionally specify the HTML formatting to be applied to
-the errors, by submitting the opening/closing tags in the function, like
-this::
+.. note:: You can optionally specify the HTML formatting to be applied to
+	the errors, by submitting the opening/closing tags in the function,
+	like this::
 
 	$this->image_lib->display_errors('<p>', '</p>');
 
@@ -225,8 +225,7 @@
 	$config['y_axis'] = '40';
 
 All preferences listed in the table above are available for this
-function except these: rotation_angle, width, height, create_thumb,
-new_image.
+function except these: rotation_angle, create_thumb, new_image.
 
 Here's an example showing how you might crop an image::
 
@@ -243,11 +242,11 @@
 	    echo $this->image_lib->display_errors();
 	}
 
-Note: Without a visual interface it is difficult to crop images, so this
-function is not very useful unless you intend to build such an
-interface. That's exactly what we did using for the photo gallery module
-in ExpressionEngine, the CMS we develop. We added a JavaScript UI that
-lets the cropping area be selected.
+.. note:: Without a visual interface it is difficult to crop images, so this
+	function is not very useful unless you intend to build such an
+	interface. That's exactly what we did using for the photo gallery module
+	in ExpressionEngine, the CMS we develop. We added a JavaScript UI that
+	lets the cropping area be selected.
 
 $this->image_lib->rotate()
 ===========================
@@ -338,8 +337,8 @@
 bottom/center of the image, 20 pixels from the bottom of the image.
 
 .. note:: In order for the image class to be allowed to do any
-	processing, the image file must have "write" file permissions. For
-	example, 777.
+	processing, the image file must have "write" file permissions
+	For example, 777.
 
 Watermarking Preferences
 ========================
diff --git a/user_guide_src/source/libraries/input.rst b/user_guide_src/source/libraries/input.rst
index c0b9c65..177f5cb 100644
--- a/user_guide_src/source/libraries/input.rst
+++ b/user_guide_src/source/libraries/input.rst
@@ -5,8 +5,7 @@
 The Input Class serves two purposes:
 
 #. It pre-processes global input data for security.
-#. It provides some helper functions for fetching input data and
-   pre-processing it.
+#. It provides some helper methods for fetching input data and pre-processing it.
 
 .. note:: This class is initialized automatically by the system so there
 	is no need to do it manually.
@@ -14,7 +13,7 @@
 Security Filtering
 ==================
 
-The security filtering function is called automatically when a new
+The security filtering method is called automatically when a new
 :doc:`controller <../general/controllers>` is invoked. It does the
 following:
 
@@ -47,7 +46,7 @@
 
 CodeIgniter comes with four helper methods that let you fetch POST, GET,
 COOKIE or SERVER items. The main advantage of using the provided
-functions rather than fetching an item directly ($_POST['something'])
+methods rather than fetching an item directly (``$_POST['something']``)
 is that the methods will check to see if the item is set and return
 NULL if not. This lets you conveniently use data without
 having to test whether an item exists first. In other words, normally
@@ -55,7 +54,7 @@
 
 	$something = isset($_POST['something']) ? $_POST['something'] : NULL;
 
-With CodeIgniter's built in functions you can simply do this::
+With CodeIgniter's built in methods you can simply do this::
 
 	$something = $this->input->post('something');
 
@@ -74,7 +73,7 @@
 
 	$this->input->post('some_data');
 
-The function returns NULL if the item you are attempting to retrieve
+The method returns NULL if the item you are attempting to retrieve
 does not exist.
 
 The second optional parameter lets you run the data through the XSS
@@ -89,7 +88,7 @@
 To return all POST items and pass them through the XSS filter set the
 first parameter NULL while setting the second parameter to boolean;
 
-The function returns NULL if there are no items in the POST.
+The method returns NULL if there are no items in the POST.
 
 ::
 
@@ -99,8 +98,8 @@
 $this->input->get()
 ===================
 
-This function is identical to the post function, only it fetches get
-data::
+This method is identical to the post method, only it fetches get data
+::
 
 	$this->input->get('some_data', TRUE);
 
@@ -109,7 +108,7 @@
 To return all GET items and pass them through the XSS filter set the
 first parameter NULL while setting the second parameter to boolean;
 
-The function returns NULL if there are no items in the GET.
+The method returns NULL if there are no items in the GET.
 
 ::
 
@@ -118,9 +117,9 @@
 
 
 $this->input->get_post()
-=========================
+========================
 
-This function will search through both the post and get streams for
+This method will search through both the post and get streams for
 data, looking first in post, and then in get::
 
 	$this->input->get_post('some_data', TRUE);
@@ -128,8 +127,8 @@
 $this->input->cookie()
 ======================
 
-This function is identical to the post function, only it fetches cookie
-data::
+This method is identical to the post method, only it fetches cookie data
+::
 
 	$this->input->cookie('some_cookie');
 	$this->input->cookie('some_cookie, TRUE); // with XSS filter
@@ -138,16 +137,43 @@
 $this->input->server()
 ======================
 
-This function is identical to the above functions, only it fetches
+This method is identical to the above methods, only it fetches server
 server data::
 
 	$this->input->server('some_data');
 
+Using the php://input stream
+============================
+
+If you want to utilize the PUT, DELETE, PATCH or other exotic request
+methods, they can only be accessed via a special input stream, that
+can only be read once. This isn't as easy as just reading from e.g.
+the ``$_POST`` array, because it will always exist and you can try
+and access multiple variables without caring that you might only have
+one shot at all of the POST data.
+
+CodeIgniter will take care of that for you, and you can access data
+from the **php://input** stream at any time, just by calling the
+``input_stream()`` method::
+
+	$this->input->input_stream('key');
+
+Similar to the methods above, if the requested data is not found, it
+will return NULL and you can also decide whether to run the data
+through ``xss_clean()`` by passing a boolean value as the second
+parameter::
+
+	$this->input->input_stream('key', TRUE); // XSS Clean
+	$this->input->input_stream('key', FALSE); // No XSS filter
+
+.. note:: You can utilize method() in order to know if you're reading
+	PUT, DELETE or PATCH data.
+
 $this->input->set_cookie()
-===========================
+==========================
 
 Sets a cookie containing the values you specify. There are two ways to
-pass information to this function so that a cookie can be set: Array
+pass information to this method so that a cookie can be set: Array
 Method, and Discrete Parameters:
 
 Array Method
@@ -182,7 +208,7 @@
 URL to the **domain** starting with a period, like this:
 .your-domain.com
 
-The path is usually not needed since the function sets a root path.
+The path is usually not needed since the method sets a root path.
 
 The prefix is only needed if you need to avoid name collisions with
 other identically named cookies for your server.
@@ -198,22 +224,25 @@
 
 	$this->input->set_cookie($name, $value, $expire, $domain, $path, $prefix, $secure);
 
+
 $this->input->ip_address()
-===========================
+==========================
 
 Returns the IP address for the current user. If the IP address is not
-valid, the function will return an IP of: 0.0.0.0
+valid, the method will return an IP of: 0.0.0.0
 
 ::
 
 	echo $this->input->ip_address();
 
 $this->input->valid_ip($ip)
-============================
+===========================
 
 Takes an IP address as input and returns TRUE or FALSE (boolean) if it
-is valid or not. Note: The $this->input->ip_address() function above
-validates the IP automatically.
+is valid or not.
+
+.. note:: The $this->input->ip_address() method above automatically
+	validates the IP address.
 
 ::
 
@@ -230,7 +259,7 @@
 an IP format. The default checks for both formats.
 
 $this->input->user_agent()
-===========================
+==========================
 
 Returns the user agent (web browser) being used by the current user.
 Returns FALSE if it's not available.
@@ -243,7 +272,7 @@
 information from the user agent string.
 
 $this->input->request_headers()
-================================
+===============================
 
 Useful if running in a non-Apache environment where
 `apache_request_headers() <http://php.net/apache_request_headers>`_
@@ -253,8 +282,8 @@
 
 	$headers = $this->input->request_headers();
 
-$this->input->get_request_header();
-=====================================
+$this->input->get_request_header()
+==================================
 
 Returns a single member of the request headers array.
 
@@ -263,13 +292,13 @@
 	$this->input->get_request_header('some-header', TRUE);
 
 $this->input->is_ajax_request()
-=================================
+===============================
 
 Checks to see if the HTTP_X_REQUESTED_WITH server header has been
 set, and returns a boolean response.
 
 $this->input->is_cli_request()
-================================
+==============================
 
 Checks to see if the STDIN constant is set, which is a failsafe way to
 see if PHP is being run on the command line.
@@ -278,8 +307,8 @@
 
 	$this->input->is_cli_request()
 
-$this->input->method();
-=====================================
+$this->input->method()
+======================
 
 Returns the $_SERVER['REQUEST_METHOD'], optional set uppercase or lowercase (default lowercase).
 
@@ -287,4 +316,4 @@
 
 	echo $this->input->method(TRUE); // Outputs: POST
 	echo $this->input->method(FALSE); // Outputs: post
-	echo $this->input->method(); // Outputs: post
+	echo $this->input->method(); // Outputs: post
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/javascript.rst b/user_guide_src/source/libraries/javascript.rst
index d5e09c3..393d4e3 100644
--- a/user_guide_src/source/libraries/javascript.rst
+++ b/user_guide_src/source/libraries/javascript.rst
@@ -192,7 +192,7 @@
 	'width' => '50%',
 	'marginLeft' => 125
 	);
-	$this->jquery->click('#trigger', $this->jquery->animate('#note', $params, normal));
+	$this->jquery->click('#trigger', $this->jquery->animate('#note', $params, 'normal'));
 
 fadeIn() / fadeOut()
 --------------------
diff --git a/user_guide_src/source/libraries/language.rst b/user_guide_src/source/libraries/language.rst
index ec678cd..d288cd6 100644
--- a/user_guide_src/source/libraries/language.rst
+++ b/user_guide_src/source/libraries/language.rst
@@ -10,12 +10,11 @@
 files as needed in order to display error and other messages in other
 languages.
 
-Language files are typically stored in your system/language directory.
-Alternately you can create a folder called language inside your
-application folder and store them there. CodeIgniter will look first in
-your application/language directory. If the directory does not exist or
-the specified language is not located there CI will instead look in your
-global system/language folder.
+Language files are typically stored in your **system/language/** directory.
+Alternately you can create a directory called language inside your
+application folder and store them there. CodeIgniter will always load the
+one in **system/language/** first and will then look for an override in
+your **application/language/** directory.
 
 .. note:: Each language should be stored in its own folder. For example,
 	the English files are located at: system/language/english
@@ -23,14 +22,14 @@
 Creating Language Files
 =======================
 
-Language files must be named with _lang.php as the file extension. For
+Language files must be named with **_lang.php** as the file extension. For
 example, let's say you want to create a file containing error messages.
 You might name it: error_lang.php
 
 Within the file you will assign each line of text to an array called
-$lang with this prototype::
+``$lang`` with this prototype::
 
-	$lang['language_key'] = "The actual message to be shown";
+	$lang['language_key'] = 'The actual message to be shown';
 
 .. note:: It's a good practice to use a common prefix for all messages
 	in a given file to avoid collisions with similarly named items in other
@@ -39,9 +38,9 @@
 
 ::
 
-	$lang['error_email_missing'] = "You must submit an email address";
-	$lang['error_url_missing'] = "You must submit a URL";
-	$lang['error_username_missing'] = "You must submit a username";
+	$lang['error_email_missing'] = 'You must submit an email address';
+	$lang['error_url_missing'] = 'You must submit a URL';
+	$lang['error_username_missing'] = 'You must submit a username';
 
 Loading A Language File
 =======================
@@ -54,7 +53,9 @@
 Where filename is the name of the file you wish to load (without the
 file extension), and language is the language set containing it (ie,
 english). If the second parameter is missing, the default language set
-in your application/config/config.php file will be used.
+in your **application/config/config.php** file will be used.
+
+.. note:: The *language* parameter can only consist of letters.
 
 Fetching a Line of Text
 =======================
@@ -64,27 +65,28 @@
 
 	$this->lang->line('language_key');
 
-Where language_key is the array key corresponding to the line you wish
+Where *language_key* is the array key corresponding to the line you wish
 to show.
 
-Note: This function simply returns the line. It does not echo it for
-you.
+You can optionally pass FALSE as the second argument of that method to
+disable error logging, in case you're not sure if the line exists::
+
+	$this->lang->line('misc_key', FALSE);
+
+.. note:: This method simply returns the line. It does not echo it.
 
 Using language lines as form labels
 -----------------------------------
 
 This feature has been deprecated from the language library and moved to
-the lang() function of the :doc:`Language
-helper <../helpers/language_helper>`.
+the :php:func:`lang()` function of the :doc:`Language Helper
+<../helpers/language_helper>`.
 
 Auto-loading Languages
 ======================
 
 If you find that you need a particular language globally throughout your
-application, you can tell CodeIgniter to
-:doc:`auto-load <../general/autoloader>` it during system
-initialization. This is done by opening the
-application/config/autoload.php file and adding the language(s) to the
-autoload array.
-
-
+application, you can tell CodeIgniter to :doc:`auto-load
+<../general/autoloader>` it during system initialization. This is done
+by opening the **application/config/autoload.php** file and adding the
+language(s) to the autoload array.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/loader.rst b/user_guide_src/source/libraries/loader.rst
index aadf974..615aba1 100644
--- a/user_guide_src/source/libraries/loader.rst
+++ b/user_guide_src/source/libraries/loader.rst
@@ -4,6 +4,7 @@
 
 Loader, as the name suggests, is used to load elements. These elements
 can be libraries (classes) :doc:`View files <../general/views>`,
+:doc:`Drivers <../general/drivers>`,
 :doc:`Helpers <../general/helpers>`,
 :doc:`Models <../general/models>`, or your own files.
 
@@ -74,6 +75,70 @@
 
 If the third (optional) parameter is blank, the library will usually be
 assigned to an object with the same name as the library. For example, if
+the library is named Calendar, it will be assigned to a variable named
+$this->calendar.
+
+If you prefer to set your own class names you can pass its value to the
+third parameter::
+
+	$this->load->library('calendar', '', 'my_calendar');
+
+	// Calendar class is now accessed using:
+
+	$this->my_calendar
+
+Please take note, when multiple libraries are supplied in an array for
+the first parameter, this parameter is discarded.
+
+$this->load->driver('parent_name', $config, 'object name')
+===========================================================
+
+This function is used to load driver libraries. Where parent_name is the
+name of the parent class you want to load.
+
+As an example, if you would like to use sessions with CodeIgniter, the first
+step is to load the session driver within your controller::
+
+	$this->load->driver('session');
+
+Once loaded, the library will be ready for use, using
+$this->session->*some_function*().
+
+Driver files must be stored in a subdirectory within the main
+"libraries" folder, or within your personal application/libraries
+folder. The subdirectory must match the parent class name. Read the
+:doc:`Drivers <../general/drivers>` description for details.
+
+Additionally, multiple driver libraries can be loaded at the same time by
+passing an array of drivers to the load function.
+
+::
+
+	$this->load->driver(array('session', 'cache'));
+
+Setting options
+---------------
+
+The second (optional) parameter allows you to optionally pass
+configuration settings. You will typically pass these as an array::
+
+	$config = array (
+	                  'sess_driver' => 'cookie',
+	                  'sess_encrypt_cookie'  => true,
+	                  'encryption_key' => 'mysecretkey'
+	               );
+
+	$this->load->driver('session', $config);
+
+Config options can usually also be set via a config file. Each library
+is explained in detail in its own page, so please read the information
+regarding each one you would like to use.
+
+Assigning a Driver to a different object name
+----------------------------------------------
+
+If the third (optional) parameter is blank, the library will be assigned
+to an object with the same name as the parent class. For example, if
 the library is named Session, it will be assigned to a variable named
 $this->session.
 
@@ -86,8 +151,8 @@
 
 	$this->my_session
 
-Please take note, when multiple libraries are supplied in an array for
-the first parameter, this parameter is discarded.
+.. note:: Driver libraries may also be loaded with the library() method,
+	but it is faster to use driver()
 
 $this->load->view('file_name', $data, true/false)
 ==================================================
@@ -279,6 +344,6 @@
 	$this->load->remove_package_path(APPPATH.'my_app');
 
 	// Again without the second parameter:
-	$this->load->add_package_path(APPPATH.'my_app', TRUE);
+	$this->load->add_package_path(APPPATH.'my_app');
 	$this->load->view('my_app_index'); // Loads
-	$this->load->view('welcome_message'); // Loads
\ No newline at end of file
+	$this->load->view('welcome_message'); // Loads
diff --git a/user_guide_src/source/libraries/migration.rst b/user_guide_src/source/libraries/migration.rst
index 5192f1f..1a73fb7 100644
--- a/user_guide_src/source/libraries/migration.rst
+++ b/user_guide_src/source/libraries/migration.rst
@@ -2,4 +2,162 @@
 Migrations Class
 ################
 
-Coming soon.
\ No newline at end of file
+Migrations are a convenient way for you to alter your database in a 
+structured and organized manner. You could edit fragments of SQL by hand 
+but you would then be responsible for telling other developers that they 
+need to go and run them. You would also have to keep track of which changes 
+need to be run against the production machines next time you deploy.
+
+The database table **migration** tracks which migrations have already been 
+run so all you have to do is update your application files and 
+call **$this->migrate->current()** to work out which migrations should be run. 
+The current version is found in **config/migration.php**.
+
+********************
+Migration file names
+********************
+
+Each Migration is run in numeric order forward or backwards depending on the
+method taken. Two numbering styles are available:
+
+* **Sequential:** each migration is numbered in sequence, starting with **001**.
+  Each number must be three digits, and there must not be any gaps in the
+  sequence. (This was the numbering scheme prior to CodeIgniter 3.0.)
+* **Timestamp:** each migration is numbered using the timestamp when the migration
+  was created, in **YYYYMMDDHHIISS** format (e.g. **20121031100537**). This
+  helps prevent numbering conflicts when working in a team environment, and is
+  the preferred scheme in CodeIgniter 3.0 and later.
+
+The desired style may be selected using the **$config['migration_type']**
+setting in your **migration.php** config file.
+
+Regardless of which numbering style you choose to use, prefix your migration
+files with the migration number followed by an underscore and a descriptive
+name for the migration. For example:
+
+* **001_add_blog.php** (sequential numbering)
+* **20121031100537_add_blog.php** (timestamp numbering)
+
+******************
+Create a Migration
+******************
+	
+This will be the first migration for a new site which has a blog. All 
+migrations go in the folder **application/migrations/** and have names such 
+as **20121031100537_add_blog.php**.::
+
+	<?php
+
+	defined('BASEPATH') OR exit('No direct script access allowed');
+
+	class Migration_Add_blog extends CI_Migration {
+
+		public function up()
+		{
+			$this->dbforge->add_field(array(
+				'blog_id' => array(
+					'type' => 'INT',
+					'constraint' => 5,
+					'unsigned' => TRUE,
+					'auto_increment' => TRUE
+				),
+				'blog_title' => array(
+					'type' => 'VARCHAR',
+					'constraint' => '100',
+				),
+				'blog_description' => array(
+					'type' => 'TEXT',
+					'null' => TRUE,
+				),
+			));
+			$this->dbforge->add_key('blog_id', TRUE);
+			$this->dbforge->create_table('blog');
+		}
+
+		public function down()
+		{
+			$this->dbforge->drop_table('blog');
+		}
+	}
+
+Then in **application/config/migration.php** set **$config['migration_version'] = 1;**.
+
+*************
+Usage Example
+*************
+
+In this example some simple code is placed in **application/controllers/migrate.php** 
+to update the schema.::
+
+	<?php
+	
+	class Migrate extends CI_Controller
+	{
+	    public function index()
+	    {
+	    	$this->load->library('migration');
+	    	
+	    	if ($this->migration->current() === FALSE)
+	    	{
+	    		show_error($this->migration->error_string());
+	    	}
+	    }
+	}
+
+******************
+Function Reference
+******************
+
+$this->migration->current()
+============================
+
+The current migration is whatever is set for **$config['migration_version']** in 
+**application/config/migration.php**.
+
+$this->migration->error_string()
+=================================
+
+This returns a string of errors while performing a migration.
+
+$this->migration->find_migrations()
+====================================
+
+An array of migration filenames are returned that are found in the **migration_path** 
+property.
+
+$this->migration->latest()
+===========================
+
+This works much the same way as current() but instead of looking for 
+the **$config['migration_version']** the Migration class will use the very 
+newest migration found in the filesystem.
+
+$this->migration->version()
+============================
+
+Version can be used to roll back changes or step forwards programmatically to 
+specific versions. It works just like current but ignores **$config['migration_version']**.::
+
+	$this->load->library('migration');
+
+	$this->migration->version(5);
+
+*********************
+Migration Preferences
+*********************
+
+The following is a table of all the config options for migrations.
+
+========================== ====================== ========================== =============================================
+Preference                 Default                Options                    Description
+========================== ====================== ========================== =============================================
+**migration_enabled**      FALSE                  TRUE / FALSE               Enable or disable migrations.
+**migration_path**         APPPATH.'migrations/'  None                       The path to your migrations folder.
+**migration_version**      0                      None                       The current version your database should use.
+**migration_table**        migrations             None                       The table name for storing the schema
+                                                                             version number.
+**migration_auto_latest**  FALSE                  TRUE / FALSE               Enable or disable automatically 
+                                                                             running migrations.
+**migration_type**        'timestamp'            'timestamp' / 'sequential' The type of numeric identifier used to name
+                                                                             migration files.
+========================== ====================== ========================== =============================================
diff --git a/user_guide_src/source/libraries/output.rst b/user_guide_src/source/libraries/output.rst
index 0472d14..a3d67b8 100644
--- a/user_guide_src/source/libraries/output.rst
+++ b/user_guide_src/source/libraries/output.rst
@@ -53,17 +53,37 @@
 
 	$this->output->set_content_type('css', 'utf-8');
 
-$this->output->get_content_type();
-==========================================
+$this->output->get_content_type()
+=================================
 
-Returns the Content-Type HTTP header that's currently in use.
+Returns the Content-Type HTTP header that's currently in use,
+excluding the character set value.
 
 	$mime = $this->output->get_content_type();
 
 .. note:: If not set, the default return value is 'text/html'.
 
-$this->output->get_output();
-=============================
+$this->output->get_header()
+===========================
+
+Gets the requested HTTP header value, if set.
+
+If the header is not set, NULL will be returned.
+If an empty value is passed to the method, it will return FALSE.
+
+Example::
+
+	$this->output->set_content_type('text/plain', 'UTF-8');
+	echo $this->output->get_header('content-type');
+	// Outputs: text/plain; charset=utf-8
+
+.. note:: The header name is compared in a case-insensitive manner.
+
+.. note:: Raw headers sent via PHP's native ``header()`` function are
+	also detected.
+
+$this->output->get_output()
+===========================
 
 Permits you to manually retrieve any output that has been sent for
 storage in the output class. Usage example::
@@ -105,6 +125,9 @@
 `See here <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>`_ for
 a full list of headers.
 
+.. note:: This method is an alias for :doc:`Common function <../general/common_functions>`
+	``set_status_header()``.
+
 $this->output->enable_profiler();
 ==================================
 
diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst
index 7d750bd..d9d3f50 100644
--- a/user_guide_src/source/libraries/pagination.rst
+++ b/user_guide_src/source/libraries/pagination.rst
@@ -80,8 +80,8 @@
 page number. For example, the number 2 will place two digits on either
 side, as in the example links at the very top of this page.
 
-$config['use_page_number'] = TRUE;
-==================================
+$config['use_page_numbers'] = TRUE;
+===================================
 
 By default, the URI segment will use the starting index for the items
 you are paginating. If you prefer to show the the actual page number,
diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst
index e7d2555..0555314 100644
--- a/user_guide_src/source/libraries/security.rst
+++ b/user_guide_src/source/libraries/security.rst
@@ -26,7 +26,7 @@
 To filter data through the XSS filter use this function:
 
 $this->security->xss_clean()
-=============================
+============================
 
 Here is an usage example::
 
@@ -56,7 +56,7 @@
 	}
 
 $this->security->sanitize_filename()
-=====================================
+====================================
 
 When accepting filenames from user input, it is best to sanitize them to
 prevent directory traversal and other security related issues. To do so,
@@ -76,16 +76,35 @@
 Cross-site request forgery (CSRF)
 =================================
 
-You can enable csrf protection by opening your
+You can enable CSRF protection by opening your
 application/config/config.php file and setting this::
 
 	$config['csrf_protection'] = TRUE;
 
-If you use the :doc:`form helper <../helpers/form_helper>` the
-form_open() function will automatically insert a hidden csrf field in
-your forms.
+If you use the :doc:`form helper <../helpers/form_helper>`, then
+``form_open()`` will automatically insert a hidden csrf field in
+your forms. If not, then you can use ``csrf_get_token_name()``
+and ``csrf_get_hash()``
 
-Tokens may be either regenerated on every submission (default) or kept the same throughout the life of the CSRF cookie. The default regeneration of tokens provides stricter security but may result in usability concerns as other tokens become invalid (back/forward navigation, multiple tabs/windows, asynchronous actions, etc). You may alter this behavior by editing the following config parameter::
+::
+
+	$csrf = array(
+		'name' => $this->security->csrf_get_token_name(),
+		'hash' => $this->security->csrf_get_hash()
+	);
+
+	...
+
+	<input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />
+
+Tokens may be either regenerated on every submission (default) or
+kept the same throughout the life of the CSRF cookie. The default
+regeneration of tokens provides stricter security, but may result
+in usability concerns as other tokens become invalid (back/forward
+navigation, multiple tabs/windows, asynchronous actions, etc). You
+may alter this behavior by editing the following config parameter
+
+::
 
 	$config['csrf_regeneration'] = TRUE;
 
@@ -95,3 +114,15 @@
 
 	$config['csrf_exclude_uris'] = array('api/person/add');
 
+$this->security->get_csrf_token_name()
+======================================
+
+Returns the CSRF token name, which is set by
+``$config['csrf_token_name']``.
+
+$this->security->get_csrf_hash()
+================================
+
+Returns the CSRF hash value. Useful in combination with
+``get_csrf_token_name()`` for manually building forms or 
+sending valid AJAX POST requests.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst
index 5400524..36c7c1d 100644
--- a/user_guide_src/source/libraries/sessions.rst
+++ b/user_guide_src/source/libraries/sessions.rst
@@ -1,29 +1,19 @@
-#############
-Session Class
-#############
+##############
+Session Driver
+##############
 
 The Session class permits you maintain a user's "state" and track their
-activity while they browse your site. The Session class stores session
-information for each user as serialized (and optionally encrypted) data
-in a cookie. It can also store the session data in a database table for
-added security, as this permits the session ID in the user's cookie to
-be matched against the stored session ID. By default only the cookie is
-saved. If you choose to use the database option you'll need to create
-the session table as indicated below.
-
-.. note:: The Session class does **not** utilize native PHP sessions. It
-	generates its own session data, offering more flexibility for
-	developers.
-
-.. note:: Even if you are not using encrypted sessions, you must set
-	an :doc:`encryption key <./encryption>` in your config file which is used
-	to aid in preventing session data manipulation.
+activity while they browse your site. CodeIgniter offers two default
+session drivers: the classic `Cookie Driver`_, and the `Native Driver`_,
+which supports usage of the native PHP Session mechanism. In addition,
+you may create your own `Custom Drivers`_ to store session data however
+you wish, while still taking advantage of the features of the Session class.
 
 Initializing a Session
 ======================
 
 Sessions will typically run globally with each page load, so the session
-class must either be :doc:`initialized <../general/libraries>` in your
+class must either be :doc:`initialized <../general/drivers>` in your
 :doc:`controller <../general/controllers>` constructors, or it can be
 :doc:`auto-loaded <../general/autoloader>` by the system. For the most
 part the session class will run unattended in the background, so simply
@@ -31,9 +21,9 @@
 sessions.
 
 To initialize the Session class manually in your controller constructor,
-use the $this->load->library function::
+use the $this->load->driver function::
 
-	$this->load->library('session');
+	$this->load->driver('session');
 
 Once loaded, the Sessions library object will be available using:
 $this->session
@@ -42,11 +32,10 @@
 =====================
 
 When a page is loaded, the session class will check to see if valid
-session data exists in the user's session cookie. If sessions data does
-**not** exist (or if it has expired) a new session will be created and
-saved in the cookie. If a session does exist, its information will be
-updated and the cookie will be updated. With each update, the
-session_id will be regenerated.
+session data exists in the user's session. If sessions data does **not**
+exist (or if it has expired) a new session will be created and saved.
+If a session does exist, its information will be updated. With each update,
+the session_id will be regenerated.
 
 It's important for you to understand that once initialized, the Session
 class runs automatically. There is nothing you need to do to cause the
@@ -79,19 +68,12 @@
 	     'last_activity' => timestamp
 	)
 
-If you have the encryption option enabled, the serialized array will be
-encrypted before being stored in the cookie, making the data highly
-secure and impervious to being read or altered by someone. More info
-regarding encryption can be :doc:`found here <encryption>`, although
-the Session class will take care of initializing and encrypting the data
-automatically.
-
-Note: Session cookies are only updated every five minutes by default to
-reduce processor load. If you repeatedly reload a page you'll notice
-that the "last activity" time only updates if five minutes or more has
-passed since the last time the cookie was written. This time is
-configurable by changing the $config['sess_time_to_update'] line in
-your system/config/config.php file.
+.. note:: Sessions are only updated every five minutes by default to
+	reduce processor load. If you repeatedly reload a page you'll notice
+	that the "last activity" time only updates if five minutes or more has
+	passed since the last time the cookie was written. This time is
+	configurable by changing the $config['sess_time_to_update'] line in
+	your system/config/config.php file.
 
 Retrieving Session Data
 =======================
@@ -106,7 +88,7 @@
 
 	$session_id = $this->session->userdata('session_id');
 
-.. note:: The function returns FALSE (boolean) if the item you are
+.. note:: The function returns NULL if the item you are
 	trying to access does not exist.
 
 Adding Custom Session Data
@@ -117,7 +99,7 @@
 do this? Here's one example:
 
 Let's say a particular user logs into your site. Once authenticated, you
-could add their username and email address to the session cookie, making
+could add their username and email address to the session, making
 that data globally available to you without having to run a database
 query when you need it.
 
@@ -144,11 +126,11 @@
 
 	$this->session->set_userdata('some_name', 'some_value');
 
+If you want to verify that a userdata value exists, call has_userdata().
 
-.. note:: Cookies can only hold 4KB of data, so be careful not to exceed
-	the capacity. The encryption process in particular produces a longer
-	data string than the original so keep careful track of how much data you
-	are storing.
+::
+
+	$this->session->has_userdata('some_name');
 
 Retrieving All Session Data
 ===========================
@@ -195,8 +177,8 @@
 cleared. These can be very useful, and are typically used for
 informational or status messages (for example: "record 2 deleted").
 
-Note: Flash variables are prefaced with "flash\_" so avoid this prefix
-in your own session names.
+.. note:: Flash variables are prefaced with "flash\_" so avoid this prefix
+	in your own session names.
 
 To add flashdata::
 
@@ -209,7 +191,7 @@
 To read a flashdata variable::
 
 	$this->session->flashdata('item');
-	
+
 An array of all flashdata can be retrieved as follows::
 
 	$this->session->all_flashdata();
@@ -217,14 +199,169 @@
 
 If you find that you need to preserve a flashdata variable through an
 additional request, you can do so using the keep_flashdata() function.
+You can either pass a single item or an array of flashdata items to keep.
 
 ::
 
 	$this->session->keep_flashdata('item');
+	$this->session->keep_flashdata(array('item1', 'item2', 'item3'));
 
+Tempdata
+========
+
+CodeIgniter also supports "tempdata", or session data with a specific
+expiration time. After the value expires, or the session expires or is
+deleted, the value is automatically removed.
+
+To add tempdata::
+
+	$expire = 300;	// Expire in 5 minutes
+
+	$this->session->set_tempdata('item', 'value', $expire);
+
+You can also pass an array to set_tempdata()::
+
+	$tempdata = array('newuser' => TRUE, 'message' => 'Thanks for joining!');
+
+	$this->session->set_tempdata($tempdata, '', $expire);
+
+.. note:: If the expiration is omitted or set to 0, the default expiration of
+	5 minutes will be used.
+
+To read a tempdata variable::
+
+	$this->session->tempdata('item');
+
+If you need to remove a tempdata value before it expires,
+use unset_tempdata()::
+
+	$this->session->unset_tempdata('item');
+
+Destroying a Session
+====================
+
+To clear the current session::
+
+	$this->session->sess_destroy();
+
+.. note:: This function should be the last one called, and even flash
+	variables will no longer be available. If you only want some items
+	destroyed and not all, use unset_userdata().
+
+Session Preferences
+===================
+
+You'll find the following Session related preferences in your
+application/config/config.php file:
+
+=========================== =============== =========================== ==========================================================================
+Preference                  Default         Options                     Description
+=========================== =============== =========================== ==========================================================================
+**sess_driver**             cookie          cookie/native/*custom*      The initial session driver to load.
+**sess_valid_drivers**      cookie, native  None                        Additional valid drivers which may be loaded.
+**sess_cookie_name**        ci_session      None                        The name you want the session cookie saved as (data for Cookie driver or
+                                                                        session ID for Native driver).
+**sess_expiration**         7200            None                        The number of seconds you would like the session to last. The default
+                                                                        value is 2 hours (7200 seconds). If you would like a non-expiring
+                                                                        session set the value to zero: 0
+**sess_expire_on_close**    FALSE           TRUE/FALSE (boolean)        Whether to cause the session to expire automatically when the browser
+                                                                        window is closed.
+**sess_encrypt_cookie**     FALSE           TRUE/FALSE (boolean)        Whether to encrypt the session data (Cookie driver only).
+**sess_use_database**       FALSE           TRUE/FALSE (boolean)        Whether to save the session data to a database. You must create the
+                                                                        table before enabling this option (Cookie driver only).
+**sess_table_name**         ci_sessions     Any valid SQL table name    The name of the session database table (Cookie driver only).
+**sess_time_to_update**     300             Time in seconds             This options controls how often the session class will regenerate itself
+                                                                        and create a new session id.
+**sess_match_ip**           FALSE           TRUE/FALSE (boolean)        Whether to match the user's IP address when reading the session data.
+                                                                        Note that some ISPs dynamically changes the IP, so if you want a
+                                                                        non-expiring session you will likely set this to FALSE.
+**sess_match_useragent**    TRUE            TRUE/FALSE (boolean)        Whether to match the User Agent when reading the session data.
+=========================== =============== =========================== ==========================================================================
+
+In addition to the values above, the cookie and native drivers apply the
+following configuration values shared by the :doc:`Input <input>` and
+:doc:`Security <security>` classes:
+
+=========================== =============== ==========================================================================
+Preference                  Default         Description
+=========================== =============== ==========================================================================
+**cookie_prefix**           ''              Set a cookie name prefix in order to avoid name collisions
+**cookie_domain**           ''              The domain for which the session is applicable
+**cookie_path**             /               The path to which the session is applicable
+=========================== =============== ==========================================================================
+
+Session Drivers
+===============
+
+By default, the `Cookie Driver`_ is loaded when a session is initialized.
+However, any valid driver may be selected with the $config['sess_driver']
+line in your config.php file.
+
+The session driver library comes with the cookie and native drivers
+installed, and `Custom Drivers`_ may also be installed by the user.
+
+Typically, only one driver will be used at a time, but CodeIgniter does
+support loading multiple drivers. If a specific valid driver is called, it
+will be automatically loaded. Or, an additional driver may be explicitly
+loaded by calling load_driver()::
+
+	$this->session->load_driver('native');
+
+The Session library keeps track of the most recently selected driver to call
+for driver methods. Normally, session class methods are called directly on
+the parent class, as illustrated above. However, any methods called through
+a specific driver will select that driver before invoking the parent method.
+
+So, alternation between multiple drivers can be achieved by specifying which
+driver to use for each call::
+
+	$this->session->native->set_userdata('foo', 'bar');
+
+	$this->session->cookie->userdata('foo');
+
+	$this->session->native->unset_userdata('foo');
+
+Notice in the previous example that the *native* userdata value 'foo'
+would be set to 'bar', which would NOT be returned by the call for
+the *cookie* userdata 'foo', nor would the *cookie* value be unset by
+the call to unset the *native* 'foo' value. The drivers maintain independent
+sets of values, regardless of key names.
+
+A specific driver may also be explicitly selected for use by pursuant
+methods with the select_driver() call::
+
+	$this->session->select_driver('native');
+
+	$this->session->userdata('item');	// Uses the native driver
+
+Cookie Driver
+-------------
+
+The Cookie driver stores session information for each user as serialized
+(and optionally encrypted) data in a cookie. It can also store the session
+data in a database table for added security, as this permits the session ID
+in the user's cookie to be matched against the stored session ID. By default
+only the cookie is saved. If you choose to use the database option you'll
+need to create the session table as indicated below.
+
+If you have the encryption option enabled, the serialized array will be
+encrypted before being stored in the cookie, making the data highly
+secure and impervious to being read or altered by someone. More info
+regarding encryption can be :doc:`found here <encryption>`, although
+the Session class will take care of initializing and encrypting the data
+automatically.
+
+.. note:: Even if you are not using encrypted sessions, you must set
+	an :doc:`encryption key <./encryption>` in your config file which is used
+	to aid in preventing session data manipulation.
+
+.. note:: Cookies can only hold 4KB of data, so be careful not to exceed
+	the capacity. The encryption process in particular produces a longer
+	data string than the original so keep careful track of how much data you
+	are storing.
 
 Saving Session Data to a Database
-=================================
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 While the session data array stored in the user's cookie contains a
 Session ID, unless you store session data in a database there is no way
@@ -249,7 +386,7 @@
 		user_agent varchar(120) NOT NULL,
 		last_activity int(10) unsigned DEFAULT 0 NOT NULL,
 		user_data text NOT NULL,
-		PRIMARY KEY (session_id),
+		PRIMARY KEY (session_id, ip_address, user_agent),
 		KEY `last_activity_idx` (`last_activity`)
 	);
 
@@ -267,44 +404,82 @@
 
 		$config['sess_table_name'] = 'ci_sessions';
 
-.. note:: The Session class has built-in garbage collection which clears
+.. note:: The Cookie driver has built-in garbage collection which clears
 	out expired sessions so you do not need to write your own routine to do
 	it.
 
-Destroying a Session
-====================
+Native Driver
+-------------
 
-To clear the current session::
+The Native driver relies on native PHP sessions to store data in the
+$_SESSION superglobal array. All stored values continue to be available
+through $_SESSION, but flash- and temp- data items carry special prefixes.
 
-	$this->session->sess_destroy();
+Custom Drivers
+--------------
 
-.. note:: This function should be the last one called, and even flash
-	variables will no longer be available. If you only want some items
-	destroyed and not all, use unset_userdata().
+You may also :doc:`create your own <../general/creating_drivers>` custom
+session drivers. A session driver basically manages an array of name/value
+pairs with some sort of storage mechanism.
 
-Session Preferences
-===================
+To make a new driver, extend CI_Session_driver. Overload the initialize()
+method and read or create session data. Then implement a save handler to
+write changed data to storage (sess_save), a destroy handler to remove
+deleted data (sess_destroy), a regenerate handler to make a new session ID
+(sess_regenerate), and an access handler to expose the data (get_userdata).
+Your initial class might look like::
 
-You'll find the following Session related preferences in your
-application/config/config.php file:
+	class CI_Session_custom extends CI_Session_driver {
+		protected function initialize()
+		{
+			// Read existing session data or create a new one
+		}
 
-=========================== =============== =========================== ==========================================================================
-Preference                  Default         Options                     Description
-=========================== =============== =========================== ==========================================================================
-**sess_cookie_name**        ci_session      None                        The name you want the session cookie saved as.
-**sess_expiration**         7200            None                        The number of seconds you would like the session to last. The default
-                                                                        value is 2 hours (7200 seconds). If you would like a non-expiring
-                                                                        session set the value to zero: 0
-**sess_expire_on_close**    FALSE           TRUE/FALSE (boolean)        Whether to cause the session to expire automatically when the browser
-                                                                        window is closed.
-**sess_encrypt_cookie**     FALSE           TRUE/FALSE (boolean)        Whether to encrypt the session data.
-**sess_use_database**       FALSE           TRUE/FALSE (boolean)        Whether to save the session data to a database. You must create the
-                                                                        table before enabling this option.
-**sess_table_name**         ci_sessions     Any valid SQL table name    The name of the session database table.
-**sess_time_to_update**     300             Time in seconds             This options controls how often the session class will regenerate itself
-                                                                        and create a new session id.
-**sess_match_ip**           FALSE           TRUE/FALSE (boolean)        Whether to match the user's IP address when reading the session data.
-                                                                        Note that some ISPs dynamically changes the IP, so if you want a
-                                                                        non-expiring session you will likely set this to FALSE.
-**sess_match_useragent**    TRUE            TRUE/FALSE (boolean)        Whether to match the User Agent when reading the session data.
-=========================== =============== =========================== ==========================================================================
\ No newline at end of file
+		public function sess_save()
+		{
+			// Save current data to storage
+		}
+
+		public function sess_destroy()
+		{
+			// Destroy the current session and clean up storage
+		}
+
+		public function sess_regenerate()
+		{
+			// Create new session ID
+		}
+
+		public function &get_userdata()
+		{
+			// Return a reference to your userdata array
+		}
+	}
+
+Notice that get_userdata() returns a reference so the parent library is
+accessing the same array the driver object is using. This saves memory
+and avoids synchronization issues during usage.
+
+Put your driver in the libraries/Session/drivers folder anywhere in your
+package paths. This includes the application directory, the system directory,
+or any path you add with $CI->load->add_package_path(). Your driver must be
+named CI_Session_<name>, and your filename must be Session_<name>.php,
+preferably also capitalized, such as::
+
+	CI_Session_foo in libraries/Session/drivers/Session_foo.php
+
+Then specify the driver by setting 'sess_driver' in your config.php file or as a
+parameter when loading the CI_Session object::
+
+	$config['sess_driver'] = 'foo';
+
+OR::
+
+	$CI->load->driver('session', array('sess_driver' => 'foo'));
+
+The driver specified by 'sess_driver' is automatically included as a valid
+driver. However, if you want to make a custom driver available as an option
+without making it the initially loaded driver, set 'sess_valid_drivers' in
+your config.php file to an array including your driver name::
+
+	$config['sess_valid_drivers'] = array('sess_driver');
diff --git a/user_guide_src/source/libraries/trackback.rst b/user_guide_src/source/libraries/trackback.rst
index 07b2b21..f9e0df8 100644
--- a/user_guide_src/source/libraries/trackback.rst
+++ b/user_guide_src/source/libraries/trackback.rst
@@ -114,7 +114,7 @@
 	 excerpt text NOT NULL,
 	 blog_name varchar(100) NOT NULL,
 	 tb_date int(10) NOT NULL,
-	 ip_address varchar(16) NOT NULL,
+	 ip_address varchar(45) NOT NULL,
 	 PRIMARY KEY `tb_id` (`tb_id`),
 	 KEY `entry_id` (`entry_id`)
 	);
diff --git a/user_guide_src/source/libraries/unit_testing.rst b/user_guide_src/source/libraries/unit_testing.rst
index 03819b2..6bd91bf 100644
--- a/user_guide_src/source/libraries/unit_testing.rst
+++ b/user_guide_src/source/libraries/unit_testing.rst
@@ -131,7 +131,7 @@
 -  Any notes you entered for the test (notes)
 
 You can customize which of these items get displayed by using
-$this->unit->set_items(). For example, if you only wanted the test name
+$this->unit->set_test_items(). For example, if you only wanted the test name
 and the result displayed:
 
 Customizing displayed tests
diff --git a/user_guide_src/source/libraries/user_agent.rst b/user_guide_src/source/libraries/user_agent.rst
index 855ece2..97abd22 100644
--- a/user_guide_src/source/libraries/user_agent.rst
+++ b/user_guide_src/source/libraries/user_agent.rst
@@ -72,7 +72,7 @@
 	{
 	    echo 'You are using Safari.';
 	}
-	else if ($this->agent->is_browser())
+	elseif ($this->agent->is_browser())
 	{
 	    echo 'You are using a browser.';
 	}
@@ -94,7 +94,7 @@
 	{
 	    $this->load->view('iphone/home');
 	}
-	else if ($this->agent->is_mobile())
+	elseif ($this->agent->is_mobile())
 	{
 	    $this->load->view('mobile/home');
 	}
diff --git a/user_guide_src/source/libraries/xmlrpc.rst b/user_guide_src/source/libraries/xmlrpc.rst
index dfb8811..b478a2d 100644
--- a/user_guide_src/source/libraries/xmlrpc.rst
+++ b/user_guide_src/source/libraries/xmlrpc.rst
@@ -297,7 +297,7 @@
 ----------
 
 Using a text editor, create a controller called xmlrpc_client.php. In
-it, place this code and save it to your applications/controllers/
+it, place this code and save it to your application/controllers/
 folder::
 
 	<?php
@@ -338,7 +338,7 @@
 ----------
 
 Using a text editor, create a controller called xmlrpc_server.php. In
-it, place this code and save it to your applications/controllers/
+it, place this code and save it to your application/controllers/
 folder::
 
 	<?php
diff --git a/user_guide_src/source/overview/cheatsheets.rst b/user_guide_src/source/overview/cheatsheets.rst
deleted file mode 100644
index 2e277aa..0000000
--- a/user_guide_src/source/overview/cheatsheets.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-#######################
-CodeIgniter Cheatsheets
-#######################
-
-Library Reference
-=================
-
-`|CodeIgniter Library
-Reference| <../images/codeigniter_1.7.1_library_reference.pdf>`_
-Helpers Reference
-=================
-
-`|image1| <../images/codeigniter_1.7.1_helper_reference.pdf>`_
-
-.. |CodeIgniter Library Reference| image:: ../images/codeigniter_1.7.1_library_reference.png
-.. |image1| image:: ../images/codeigniter_1.7.1_helper_reference.png
diff --git a/user_guide_src/source/overview/index.rst b/user_guide_src/source/overview/index.rst
index dc91f78..d48a0bb 100644
--- a/user_guide_src/source/overview/index.rst
+++ b/user_guide_src/source/overview/index.rst
@@ -9,7 +9,6 @@
 	
 	Getting Started <getting_started>
 	CodeIgniter at a Glance <at_a_glance>
-	CodeIgniter Cheatsheets <cheatsheets>
 	Supported Features <features>
 	Application Flow Chart <appflow>
 	Model-View-Controller <mvc>
diff --git a/user_guide_src/source/tutorial/news_section.rst b/user_guide_src/source/tutorial/news_section.rst
index 82b3e3b..b64ea2a 100644
--- a/user_guide_src/source/tutorial/news_section.rst
+++ b/user_guide_src/source/tutorial/news_section.rst
@@ -22,19 +22,19 @@
 
 ::
 
-    <?php
-    class News_model extends CI_Model {
+	<?php
+	class News_model extends CI_Model {
 
-        public function __construct()
-        {
-            $this->load->database();
-        }
-    }
+		public function __construct()
+		{
+			$this->load->database();
+		}
+	}
 
 This code looks similar to the controller code that was used earlier. It
-creates a new model by extending CI\_Model and loads the database
+creates a new model by extending ``CI_Model`` and loads the database
 library. This will make the database class available through the
-$this->db object.
+``$this->db`` object.
 
 Before querying the database, a database schema has to be created.
 Connect to your database and run the SQL command below. Also add some
@@ -42,14 +42,14 @@
 
 ::
 
-    CREATE TABLE news (
-        id int(11) NOT NULL AUTO_INCREMENT,
-        title varchar(128) NOT NULL,
-        slug varchar(128) NOT NULL,
-        text text NOT NULL,
-        PRIMARY KEY (id),
-        KEY slug (slug)
-    );
+	CREATE TABLE news (
+		id int(11) NOT NULL AUTO_INCREMENT,
+		title varchar(128) NOT NULL,
+		slug varchar(128) NOT NULL,
+		text text NOT NULL,
+		PRIMARY KEY (id),
+		KEY slug (slug)
+	);
 
 Now that the database and a model have been set up, you'll need a method
 to get all of our posts from our database. To do this, the database
@@ -61,22 +61,22 @@
 
 ::
 
-    public function get_news($slug = FALSE)
-    {
-        if ($slug === FALSE)
-        {
-            $query = $this->db->get('news');
-            return $query->result_array();
-        }
+	public function get_news($slug = FALSE)
+	{
+		if ($slug === FALSE)
+		{
+			$query = $this->db->get('news');
+			return $query->result_array();
+		}
         
-        $query = $this->db->get_where('news', array('slug' => $slug));
-        return $query->row_array();
-    }
+		$query = $this->db->get_where('news', array('slug' => $slug));
+		return $query->row_array();
+	}
 
 With this code you can perform two different queries. You can get all
 news records, or get a news item by its `slug <#>`_. You might have
 noticed that the $slug variable wasn't sanitized before running the
-query; Query Builder does this for you.
+query; :doc:`Query Builder <../database/query_builder>` does this for you.
 
 Display the news
 ----------------
@@ -89,30 +89,30 @@
 
 ::
 
-    <?php
-    class News extends CI_Controller {
+	<?php
+	class News extends CI_Controller {
 
-        public function __construct()
-        {
-            parent::__construct();
-            $this->load->model('news_model');
-        }
+		public function __construct()
+		{
+			parent::__construct();
+			$this->load->model('news_model');
+		}
 
-        public function index()
-        {
-            $data['news'] = $this->news_model->get_news();
-        }
+		public function index()
+		{
+			$data['news'] = $this->news_model->get_news();
+		}
 
-        public function view($slug)
-        {
-            $data['news'] = $this->news_model->get_news($slug);
-        }
-    }
+		public function view($slug = NULL)
+		{
+			$data['news_item'] = $this->news_model->get_news($slug);
+		}
+	}
 
 Looking at the code, you may see some similarity with the files we
-created earlier. First, the "\_\_construct" method: it calls the
-constructor of its parent class (CI\_Controller) and loads the model, so
-it can be used in all other methods in this controller.
+created earlier. First, the ``__construct()`` method: it calls the
+constructor of its parent class (``CI_Controller``) and loads the model,
+so it can be used in all other methods in this controller.
 
 Next, there are two methods to view all news items and one for a
 specific news item. You can see that the $slug variable is passed to the
@@ -125,15 +125,15 @@
 
 ::
 
-    public function index()
-    {
-        $data['news'] = $this->news_model->get_news();
-        $data['title'] = 'News archive';
+	public function index()
+	{
+		data['news'] = $this->news_model->get_news();
+		$data['title'] = 'News archive';
 
-        $this->load->view('templates/header', $data);
-        $this->load->view('news/index', $data);
-        $this->load->view('templates/footer');
-    }
+		$this->load->view('templates/header', $data);
+		$this->load->view('news/index', $data);
+		$this->load->view('templates/footer');
+	}
 
 The code above gets all news records from the model and assigns it to a
 variable. The value for the title is also assigned to the $data['title']
@@ -143,20 +143,20 @@
 
 ::
 
-    <?php foreach ($news as $news_item): ?>
+	<?php foreach ($news as $news_item): ?>
 
-        <h2><?php echo $news_item['title'] ?></h2>
-        <div id="main">
-            <?php echo $news_item['text'] ?>
-        </div>
-        <p><a href="<?php echo $news_item['slug'] ?>">View article</a></p>
+		<h2><?php echo $news_item['title'] ?></h2>
+		<div id="main">
+			<?php echo $news_item['text'] ?>
+		</div>
+		<p><a href="<?php echo $news_item['slug'] ?>">View article</a></p>
 
-    <?php endforeach ?>
+	<?php endforeach ?>
 
 Here, each news item is looped and displayed to the user. You can see we
 wrote our template in PHP mixed with HTML. If you prefer to use a
 template language, you can use CodeIgniter's `Template
-Parser <../libraries/parser.html>`_ class or a third party parser.
+Parser <../libraries/parser>`_ class or a third party parser.
 
 The news overview page is now done, but a page to display individual
 news items is still absent. The model created earlier is made in such
@@ -166,32 +166,32 @@
 
 ::
 
-    public function view($slug)
-    {
-        $data['news_item'] = $this->news_model->get_news($slug);
+	public function view($slug = NULL)
+	{
+		$data['news_item'] = $this->news_model->get_news($slug);
 
-        if (empty($data['news_item']))
-        {
-            show_404();
-        }
+		if (empty($data['news_item']))
+		{
+			show_404();
+		}
 
-        $data['title'] = $data['news_item']['title'];
+		$data['title'] = $data['news_item']['title'];
 
-        $this->load->view('templates/header', $data);
-        $this->load->view('news/view', $data);
-        $this->load->view('templates/footer');
-    }
+		$this->load->view('templates/header', $data);
+		$this->load->view('news/view', $data);
+		$this->load->view('templates/footer');
+	}
 
-Instead of calling the get\_news() method without a parameter, the $slug
-variable is passed, so it will return the specific news item. The only
-things left to do is create the corresponding view at
-application/views/news/view.php. Put the following code in this file.
+Instead of calling the ``get_news()`` method without a parameter, the
+``$slug`` variable is passed, so it will return the specific news item.
+The only things left to do is create the corresponding view at
+*application/views/news/view.php*. Put the following code in this file.
 
 ::
 
-    <?php
-    echo '<h2>'.$news_item['title'].'</h2>';
-    echo $news_item['text'];
+	<?php
+	echo '<h2>'.$news_item['title'].'</h2>';
+	echo $news_item['text'];
 
 Routing
 -------
@@ -205,10 +205,10 @@
 
 ::
 
-    $route['news/(:any)'] = 'news/view/$1';
-    $route['news'] = 'news';
-    $route['(:any)'] = 'pages/view/$1';
-    $route['default_controller'] = 'pages/view';
+	$route['news/(:any)'] = 'news/view/$1';
+	$route['news'] = 'news';
+	$route['(:any)'] = 'pages/view/$1';
+	$route['default_controller'] = 'pages/view';
 
 Point your browser to your document root, followed by index.php/news and
-watch your news page.
+watch your news page.
\ No newline at end of file