Add compatibility layer for array_column(), array_replace(), array_replace_recursive()
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index 2bdd764..eb3927e 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -191,6 +191,7 @@
 	require_once(BASEPATH.'core/compat/mbstring.php');
 	require_once(BASEPATH.'core/compat/hash.php');
 	require_once(BASEPATH.'core/compat/password.php');
+	require_once(BASEPATH.'core/compat/array.php');
 
 /*
  * ------------------------------------------------------
diff --git a/system/core/Common.php b/system/core/Common.php
index c83d80a..7591cd7 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -587,7 +587,7 @@
 		if ($is_error)
 		{
 			set_status_header(500);
-		}		
+		}
 
 		// Should we ignore the error? We'll get the current error_reporting
 		// level and add its bits with the severity bits to find out.
diff --git a/system/core/compat/array.php b/system/core/compat/array.php
new file mode 100644
index 0000000..b291703
--- /dev/null
+++ b/system/core/compat/array.php
@@ -0,0 +1,246 @@
+<?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 - 2014, 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');
+
+/**
+ * PHP ext/standard/array compatibility package
+ *
+ * @package		CodeIgniter
+ * @subpackage	CodeIgniter
+ * @category	Compatibility
+ * @author		Andrey Andreev
+ * @link		http://codeigniter.com/user_guide/
+ * @link		http://php.net/book.array
+ */
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.5'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('array_column'))
+{
+	/**
+	 * array_column()
+	 *
+	 * @link	http://php.net/array_column
+	 * @param	string	$array
+	 * @param	mixed	$column_key
+	 * @param	mixed	$index_key
+	 * @return	array
+	 */
+	function array_column(array $array, $column_key, $index_key = NULL)
+	{
+		if ( ! in_array($type = gettype($column_key), array('integer', 'string', 'NULL'), TRUE))
+		{
+			if ($type === 'double')
+			{
+				$column_key = (int) $column_key;
+			}
+			elseif ($type === 'object' && method_exists($column_key, '__toString'))
+			{
+				$column_key = (string) $column_key;
+			}
+			else
+			{
+				trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);
+				return FALSE;
+			}
+		}
+
+		if ( ! in_array($type = gettype($index_key), array('integer', 'string', 'NULL'), TRUE))
+		{
+			if ($type === 'double')
+			{
+				$index_key = (int) $index_key;
+			}
+			elseif ($type === 'object' && method_exists($index_key, '__toString'))
+			{
+				$index_key = (string) $index_key;
+			}
+			else
+			{
+				trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);
+				return FALSE;
+			}
+		}
+
+		$result = array();
+		foreach ($array as &$a)
+		{
+			if ($column_key === NULL)
+			{
+				$value = $a;
+			}
+			elseif (is_array($a) && array_key_exists($column_key, $a))
+			{
+				$value = $a[$column_key];
+			}
+			else
+			{
+				continue;
+			}
+
+			if ($index_key === NULL OR ! array_key_exists($index_key, $a))
+			{
+				$result[] = $value;
+			}
+			else
+			{
+				$result[$a[$index_key]] = $value;
+			}
+		}
+
+		return $result;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if (is_php('5.3'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('array_replace'))
+{
+	/**
+	 * array_replace()
+	 *
+	 * @link	http://php.net/array_replace
+	 * @return	array
+	 */
+	function array_replace()
+	{
+		$arrays = func_get_args();
+
+		if (($c = count($arrays)) === 0)
+		{
+			trigger_error('array_replace() expects at least 1 parameter, 0 given', E_USER_WARNING);
+			return NULL;
+		}
+		elseif ($c === 1)
+		{
+			if ( ! is_array($arrays[0]))
+			{
+				trigger_error('array_replace(): Argument #1 is not an array', E_USER_WARNING);
+				return NULL;
+			}
+
+			return $arrays[0];
+		}
+
+		$array = array_shift($arrays);
+		$c--;
+
+		for ($i = 0, $c = count($arrays); $i < $c; $i++)
+		{
+			if ( ! is_array($arrays[$i]))
+			{
+				trigger_error('array_replace(): Argument #'.($i + 2).' is not an array', E_USER_WARNING);
+				return NULL;
+			}
+			elseif (empty($arrays[$i]))
+			{
+				continue;
+			}
+
+			foreach (array_keys($arrays[$i]) as $key)
+			{
+				$array[$key] = $arrays[$i][$key];
+			}
+		}
+
+		return $array;
+	}
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('array_replace_recursive'))
+{
+	/**
+	 * array_replace_recursive()
+	 *
+	 * @link	http://php.net/array_replace_recursive
+	 * @return	array
+	 */
+	function array_replace_recursive()
+	{
+		$arrays = func_get_args();
+
+		if (($c = count($arrays)) === 0)
+		{
+			trigger_error('array_replace_recursive() expects at least 1 parameter, 0 given', E_USER_WARNING);
+			return NULL;
+		}
+		elseif ($c === 1)
+		{
+			if ( ! is_array($arrays[0]))
+			{
+				trigger_error('array_replace_recursive(): Argument #1 is not an array', E_USER_WARNING);
+				return NULL;
+			}
+
+			return $arrays[0];
+		}
+
+		$array = array_shift($arrays);
+		$c--;
+
+		for ($i = 0, $c = count($arrays); $i < $c; $i++)
+		{
+			if ( ! is_array($arrays[$i]))
+			{
+				trigger_error('array_replace_recursive(): Argument #'.($i + 2).' is not an array', E_USER_WARNING);
+				return NULL;
+			}
+			elseif (empty($arrays[$i]))
+			{
+				continue;
+			}
+
+			foreach (array_keys($arrays[$i]) as $key)
+			{
+				$array[$key] = (is_array($arrays[$i][$key]) && isset($array[$key]) && is_array($array[$key]))
+					? array_replace_recursive($array[$key], $arrays[$i][$key])
+					: $arrays[$i][$key];
+			}
+		}
+
+		return $array;
+	}
+}
+
+/* End of file array.php */
+/* Location: ./system/core/compat/array.php */
\ No newline at end of file
diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php
index 5441f71..9a06f9e 100644
--- a/tests/Bootstrap.php
+++ b/tests/Bootstrap.php
@@ -66,6 +66,7 @@
 include_once SYSTEM_PATH.'core/compat/mbstring.php';
 include_once SYSTEM_PATH.'core/compat/hash.php';
 include_once SYSTEM_PATH.'core/compat/password.php';
+include_once SYSTEM_PATH.'core/compat/array.php';
 
 include_once $dir.'/mocks/autoloader.php';
 spl_autoload_register('autoload');
diff --git a/tests/codeigniter/core/compat/array_test.php b/tests/codeigniter/core/compat/array_test.php
new file mode 100644
index 0000000..9d2deab
--- /dev/null
+++ b/tests/codeigniter/core/compat/array_test.php
@@ -0,0 +1,429 @@
+<?php
+
+class array_test extends CI_TestCase {
+
+	public function test_bootstrap()
+	{
+		if (is_php('5.5'))
+		{
+			return $this->markTestSkipped('All array functions are already available on PHP 5.5');
+		}
+		elseif ( ! is_php('5.3'))
+		{
+			$this->assertTrue(function_exists('array_replace'));
+			$this->assertTrue(function_exists('array_replace_recursive'));
+		}
+
+		$this->assertTrue(function_exists('array_column'));
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * array_column() test
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_array_column()
+	{
+		// Basic tests
+
+		$input = array(
+			array(
+				'id' => 1,
+				'first_name' => 'John',
+				'last_name' => 'Doe'
+			),
+			array(
+				'id' => 2,
+				'first_name' => 'Sally',
+				'last_name' => 'Smith'
+			),
+			array(
+				'id' => 3,
+				'first_name' => 'Jane',
+				'last_name' => 'Jones'
+			)
+		);
+
+		// Ensure internal array position doesn't break it
+		next($input);
+
+		$this->assertEquals(
+			array('John', 'Sally', 'Jane'),
+			array_column($input, 'first_name')
+		);
+
+		$this->assertEquals(
+			array(1, 2, 3),
+			array_column($input, 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				1 => 'Doe',
+				2 => 'Smith',
+				3 => 'Jones'
+			),
+			array_column($input, 'last_name', 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				'John' => 'Doe',
+				'Sally' => 'Smith',
+				'Jane' => 'Jones'
+			),
+			array_column($input, 'last_name', 'first_name')
+		);
+
+		// Object key search
+
+		$f = new Foo();
+		$b = new Bar();
+
+		$this->assertEquals(
+			array('Doe', 'Smith', 'Jones'),
+			array_column($input, $f)
+		);
+
+		$this->assertEquals(
+			array(
+				'John' => 'Doe',
+				'Sally' => 'Smith',
+				'Jane' => 'Jones'
+			),
+			array_column($input, $f, $b)
+		);
+
+		// NULL parameters
+
+		$input = array(
+			456 => array(
+				'id' => '3',
+				'title' => 'Foo',
+				'date' => '2013-03-25'
+			),
+			457 => array(
+				'id' => '5',
+				'title' => 'Bar',
+				'date' => '2012-05-20'
+			)
+		);
+
+		$this->assertEquals(
+			array(
+				3 => array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				5 => array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL, 'id')
+		);
+
+		$this->assertEquals(
+			array(
+				array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL, 'foo')
+		);
+
+		$this->assertEquals(
+			array(
+				array(
+					'id' => '3',
+					'title' => 'Foo',
+					'date' => '2013-03-25'
+				),
+				array(
+					'id' => '5',
+					'title' => 'Bar',
+					'date' => '2012-05-20'
+				)
+			),
+			array_column($input, NULL)
+		);
+
+		// Data types
+
+		$fh = fopen(__FILE__, 'r', TRUE);
+		$stdClass = new stdClass();
+		$input = array(
+			array(
+				'id' => 1,
+				'value' => $stdClass
+			),
+			array(
+				'id' => 2,
+				'value' => 34.2345
+			),
+			array(
+				'id' => 3,
+				'value' => TRUE
+			),
+			array(
+				'id' => 4,
+				'value' => FALSE
+			),
+			array(
+				'id' => 5,
+				'value' => NULL
+			),
+			array(
+				'id' => 6,
+				'value' => 1234
+			),
+			array(
+				'id' => 7,
+				'value' => 'Foo'
+			),
+			array(
+				'id' => 8,
+				'value' => $fh
+			)
+		);
+
+		$this->assertEquals(
+			array(
+				$stdClass,
+				34.2345,
+				TRUE,
+				FALSE,
+				NULL,
+				1234,
+				'Foo',
+				$fh
+			),
+			array_column($input, 'value')
+		);
+
+		$this->assertEquals(
+			array(
+				1 => $stdClass,
+				2 => 34.2345,
+				3 => TRUE,
+				4 => FALSE,
+				5 => NULL,
+				6 => 1234,
+				7 => 'Foo',
+				8 => $fh
+			),
+			array_column($input, 'value', 'id')
+		);
+
+		// Numeric column keys
+
+		$input = array(
+			array('aaa', '111'),
+			array('bbb', '222'),
+			array('ccc', '333', -1 => 'ddd')
+		);
+
+		$this->assertEquals(
+			array('111', '222', '333'),
+			array_column($input, 1)
+		);
+
+		$this->assertEquals(
+			array(
+				'aaa' => '111',
+				'bbb' => '222',
+				'ccc' => '333'
+			),
+			array_column($input, 1, 0)
+		);
+
+		$this->assertEquals(
+			array(
+				'aaa' => '111',
+				'bbb' => '222',
+				'ccc' => '333'
+			),
+			array_column($input, 1, 0.123)
+		);
+
+		$this->assertEquals(
+			array(
+				0 => '111',
+				1 => '222',
+				'ddd' => '333'
+			),
+			array_column($input, 1, -1)
+		);
+
+		// Non-existing columns
+
+		$this->assertEquals(array(), array_column($input, 2));
+		$this->assertEquals(array(), array_column($input, 'foo'));
+		$this->assertEquals(
+			array('aaa', 'bbb', 'ccc'),
+			array_column($input, 0, 'foo')
+		);
+		$this->assertEquals(array(), array_column($input, 3.14));
+
+		// One-dimensional array
+		$this->assertEquals(array(), array_column(array('foo', 'bar', 'baz'), 1));
+
+		// Columns not present in all rows
+
+		$input = array(
+			array('a' => 'foo', 'b' => 'bar', 'e' => 'bbb'),
+			array('a' => 'baz', 'c' => 'qux', 'd' => 'aaa'),
+			array('a' => 'eee', 'b' => 'fff', 'e' => 'ggg')
+		);
+
+		$this->assertEquals(
+			array('qux'),
+			array_column($input, 'c')
+		);
+
+		$this->assertEquals(
+			array('baz' => 'qux'),
+			array_column($input, 'c', 'a')
+		);
+
+		$this->assertEquals(
+			array(
+				0 => 'foo',
+				'aaa' => 'baz',
+				1 => 'eee'
+			),
+			array_column($input, 'a', 'd')
+		);
+
+		$this->assertEquals(
+			array(
+				'bbb' => 'foo',
+				0 => 'baz',
+				'ggg' => 'eee'
+			),
+			array_column($input, 'a', 'e')
+		);
+
+		$this->assertEquals(
+			array('bar', 'fff'),
+			array_column($input, 'b')
+		);
+
+		$this->assertEquals(
+			array(
+				'foo' => 'bar',
+				'eee' => 'fff'
+			),
+			array_column($input, 'b', 'a')
+		);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * array_replace(), array_replace_recursive() tests
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_array_replace_recursive()
+	{
+		if (is_php('5.3'))
+		{
+			return $this->markTestSkipped('array_replace() and array_replace_recursive() are already available on PHP 5.3');
+		}
+
+		$array1 = array(
+			0 => 'dontclobber',
+			'1' => 'unclobbered',
+			'test2' => 0.0,
+			'test3' => array(
+				'testarray2' => TRUE,
+				1 => array(
+					'testsubarray1' => 'dontclobber2',
+					'testsubarray2' => 'dontclobber3'
+				)
+			)
+		);
+
+		$array2 = array(
+			1 => 'clobbered',
+			'test3' => array(
+				'testarray2' => FALSE
+			),
+			'test4' => array(
+				'clobbered3' => array(0, 1, 2)
+			)
+		);
+
+		// array_replace()
+		$this->assertEquals(
+			array(
+				0 => 'dontclobber',
+				1 => 'clobbered',
+				'test2' => 0.0,
+				'test3' => array(
+					'testarray2' => FALSE
+				),
+				'test4' => array(
+					'clobbered3' => array(0, 1, 2)
+				)
+			),
+			array_replace($array1, $array2)
+		);
+
+		// array_replace_recursive()
+		$this->assertEquals(
+			array(
+				0 => 'dontclobber',
+				1 => 'clobbered',
+				'test2' => 0.0,
+				'test3' => array(
+					'testarray2' => FALSE,
+					1 => array(
+						'testsubarray1' => 'dontclobber2',
+						'testsubarray2' => 'dontclobber3'
+					)
+				),
+				'test4' => array(
+					'clobbered3' => array(0, 1, 2)
+				)
+			),
+			array_replace_recursive($array1, $array2)
+		);
+	}
+}
+
+// ------------------------------------------------------------------------
+
+// These are necessary for the array_column() tests
+
+class Foo {
+
+	public function __toString()
+	{
+		return 'last_name';
+	}
+}
+
+class Bar {
+
+	public function __toString()
+	{
+		return 'first_name';
+	}
+}
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 9ec4d0d..ccbb950 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -508,7 +508,13 @@
       -  Changed method ``clean_string()`` to utilize ``mb_convert_encoding()`` if it is available but ``iconv()`` is not.
       -  Renamed method ``_is_ascii()`` to ``is_ascii()`` and made it public.
 
-   -  Added `compatibility layers <general/compatibility_functions>` for PHP's `mbstring <http://php.net/mbstring>`_ (limited support), `hash <http://php.net/hash>`_ and `password <http://php.net/password>`_ extensions.
+   -  Added `compatibility layers <general/compatibility_functions>` for:
+
+      - `Multibyte String <http://php.net/mbstring>`_ (limited support).
+      - `Hash <http://php.net/hash>`_ (just ``hash_pbkdf2()``).
+      - `Password Hashing <http://php.net/password>`_.
+      - `Array Functions <http://php.net/book.array>`_ (``array_column()``, ``array_replace()``, ``array_replace_recursive()``).
+
    -  Removed ``CI_CORE`` boolean constant from *CodeIgniter.php* (no longer Reactor and Core versions).
    -  Log Library will now try to create the **log_path** directory if it doesn't exist.
    -  Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE).
diff --git a/user_guide_src/source/general/compatibility_functions.rst b/user_guide_src/source/general/compatibility_functions.rst
index 3495101..398403e 100644
--- a/user_guide_src/source/general/compatibility_functions.rst
+++ b/user_guide_src/source/general/compatibility_functions.rst
@@ -183,4 +183,54 @@
 	:rtype:	string
 
 	For more information, please refer to the `PHP manual for
-	mb_substr() <http://php.net/mb_substr>`_.
\ No newline at end of file
+	mb_substr() <http://php.net/mb_substr>`_.
+
+***************
+Array Functions
+***************
+
+This set of compatibility functions offers support for a few
+standard `Array Functions <http://php.net/book.array>`_ in PHP
+that otherwise require a newer PHP version.
+
+Dependancies
+============
+
+- None
+
+Function reference
+==================
+
+.. function:: array_column(array $array, $column_key[, $index_key = NULL])
+
+	:param	array	$array: Array to fetch results from
+	:param	mixed	$column_key: Key of the column to return values from
+	:param	mixed	$index_key: Key to use for the returned values
+	:returns:	An array of values representing a single column from the input array
+	:rtype:	array
+
+	For more information, please refer to the `PHP manual for
+	array_column() <http://php.net/array_column>`_.
+
+.. function:: array_replace(array $array1[, ...])
+
+	:param	array	$array1: Array in which to replace elements
+	:param	array	...: Array (or multiple ones) from which to extract elements
+	:returns:	Modified array
+	:rtype:	array
+
+	For more information, please refer to the `PHP manual for
+	array_replace() <http://php.net/array_replace>`_.
+
+.. function:: array_replace_recursive(array $array1[, ...])
+
+	:param	array	$array1: Array in which to replace elements
+	:param	array	...: Array (or multiple ones) from which to extract elements
+	:returns:	Modified array
+	:rtype:	array
+
+	For more information, please refer to the `PHP manual for
+	array_replace_recursive() <http://php.net/array_replace_recursive>`_.
+
+	.. important:: Only PHP's native function can detect endless recursion.
+		Unless you are running PHP 5.3+, be careful with references!
\ No newline at end of file