Added support for extending individual driver classes and driver unit tests

Signed-off-by: dchill42 <dchill42@gmail.com>
diff --git a/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php
index e76fdc5..48bd958 100644
--- a/system/libraries/Cache/Cache.php
+++ b/system/libraries/Cache/Cache.php
@@ -43,12 +43,12 @@
 	 * @var array
 	 */
 	protected $valid_drivers = array(
-		'cache_apc',
-		'cache_dummy',
-		'cache_file',
-		'cache_memcached',
-		'cache_redis',
-		'cache_wincache'
+		'apc',
+		'dummy',
+		'file',
+		'memcached',
+		'redis',
+		'wincache'
 	);
 
 	/**
diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php
index 621d226..8f5107b 100644
--- a/system/libraries/Driver.php
+++ b/system/libraries/Driver.php
@@ -60,8 +60,8 @@
 	 * 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   string  Child class name
-	 * @return  object  Child class
+	 * @param	string	Child class name
+	 * @return	object	Child class
 	 */
 	public function __get($child)
 	{
@@ -74,61 +74,120 @@
 	 *
 	 * Separate load_driver call to support explicit driver load by library or user
 	 *
-	 * @param   string  Child class name
-	 * @return  object  Child class
+	 * @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, array_map('strtolower', $this->valid_drivers)))
 		{
-			// check and see if the driver is in a separate file
-			if ( ! class_exists($child_class))
-			{
-				// check application path first
-				foreach (get_instance()->load->get_package_paths(TRUE) as $path)
-				{
-					// loves me some nesting!
-					foreach (array(ucfirst($driver_name), $driver_name) as $class)
-					{
-						$filepath = $path.'libraries/'.$lib_name.'/drivers/'.$class.'.php';
+			// The requested driver isn't valid!
+			$msg = 'Invalid driver requested: '.$child_name;
+			log_message('error', $msg);
+			show_error($msg);
+		}
 
-						if (file_exists($filepath))
+		// All driver files should be in a library subdirectory - capitalized
+		$subdir = ucfirst(strtolower($this->lib_name));
+
+		// Get package paths and filename case variations to search
+		$paths = $CI->load->get_package_paths(TRUE);
+		$cases = array(ucfirst($child_name), strtolower($child_name));
+
+		// Is there an extension?
+		$class_name = $prefix.$child_name;
+		$found = class_exists($class_name);
+		if ( ! $found)
+		{
+			// Check for subclass file
+			foreach ($paths as $path)
+			{
+				// Extension will be in drivers subdirectory
+				$path .= 'libraries/'.$subdir.'/drivers/';
+
+				// Try filename with caps and all lowercase
+				foreach ($cases as $name)
+				{
+					// Does the file exist?
+					$file = $path.$prefix.$name.'.php';
+					if (file_exists($file))
+					{
+						// Yes - require base class from BASEPATH
+						$basepath = BASEPATH.'libraries/'.$subdir.'/drivers/'.ucfirst($child_name).'.php';
+						if ( ! file_exists($basepath))
 						{
-							include_once $filepath;
+							$msg = 'Unable to load the requested class: CI_'.$child_name;
+							log_message('error', $msg);
+							show_error($msg);
+						}
+
+						// Include both sources and mark found
+						include($basepath);
+						include($file);
+						$found = TRUE;
+						break 2;
+					}
+				}
+			}
+		}
+
+		// 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)
+				{
+					// Class will be in drivers subdirectory
+					$path .= 'libraries/'.$subdir.'/drivers/';
+
+					// Try filename with caps and all lowercase
+					foreach ($cases as $name)
+					{
+						// Does the file exist?
+						$file = $path.$name.'.php';
+						if (file_exists($file))
+						{
+							// Include source
+							include($file);
 							break 2;
 						}
 					}
 				}
-
-				// 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);
-				}
 			}
-
-			$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);
+		// Did we finally find the class?
+		if ( ! class_exists($class_name))
+		{
+			$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;
 	}
 
 }
diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php
index 96e65f1..b6c862d 100644
--- a/system/libraries/Session/Session.php
+++ b/system/libraries/Session/Session.php
@@ -107,17 +107,15 @@
 
 		// Get valid drivers list
 		$this->valid_drivers = array(
-			'Session_native',
-			'Session_cookie'
+			'native',
+			'cookie'
 		);
 		$key = 'sess_valid_drivers';
 		$drivers = isset($params[$key]) ? $params[$key] : $CI->config->item($key);
 		if ($drivers)
 		{
-			is_array($drivers) OR $drivers = array($drivers);
-
 			// Add driver names to valid list
-			foreach ($drivers as $driver)
+			foreach ((array) $drivers as $driver)
 			{
 				if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
 				{
@@ -134,9 +132,9 @@
 			$driver = 'cookie';
 		}
 
-		if ( ! in_array('session_'.strtolower($driver), array_map('strtolower', $this->valid_drivers)))
+		if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
 		{
-			$this->valid_drivers[] = 'Session_'.$driver;
+			$this->valid_drivers[] = $driver;
 		}
 
 		// Save a copy of parameters in case drivers need access
@@ -178,17 +176,17 @@
 	/**
 	 * Select default session storage driver
 	 *
-	 * @param	string	Driver classname
+	 * @param	string	Driver name
 	 * @return	void
 	 */
 	public function select_driver($driver)
 	{
 		// Validate driver name
-		$lowername = strtolower(str_replace('CI_', '', $driver));
-		if (in_array($lowername, array_map('strtolower', $this->valid_drivers)))
+		$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
-			$child = str_replace($this->lib_name.'_', '', $driver);
 			if (isset($this->$child))
 			{
 				// See if driver is already current