Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions system/helpers/file_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ function get_filenames($source_dir, $include_path = FALSE, $_recursion = FALSE)
*
* Any sub-folders contained within the specified path are read as well.
*
* Keys are the file paths relative to $source_dir.
*
* @param string path to source
* @param bool Look only at the top level directory specified?
* @param bool internal variable to determine recursion status - do not use in calls
Expand All @@ -227,6 +229,7 @@ function get_filenames($source_dir, $include_path = FALSE, $_recursion = FALSE)
function get_dir_file_info($source_dir, $top_level_only = TRUE, $_recursion = FALSE)
{
static $_filedata = array();
static $_root_dir = '';
$relative_path = $source_dir;

if ($fp = @opendir($source_dir))
Expand All @@ -236,6 +239,7 @@ function get_dir_file_info($source_dir, $top_level_only = TRUE, $_recursion = FA
{
$_filedata = array();
$source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
$_root_dir = $source_dir;
}

// Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast
Expand All @@ -247,9 +251,13 @@ function get_dir_file_info($source_dir, $top_level_only = TRUE, $_recursion = FA
}
elseif ($file[0] !== '.')
{
$filedata = get_dir_file_info($source_dir.$file);
$filedata['relative_path'] = $relative_path;
$_filedata[] = $filedata;
$filedata = get_file_info($source_dir.$file);

if (is_array($filedata))
{
$filedata['relative_path'] = $relative_path;
$_filedata[substr($source_dir.$file, strlen($_root_dir))] = $filedata;
}
}
}

Expand Down
92 changes: 92 additions & 0 deletions tests/codeigniter/helpers/file_helper_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

class File_helper_Test extends CI_TestCase {

private $_real_dir = NULL;

public function set_up()
{
$this->helper('file');
Expand All @@ -11,6 +13,17 @@ public function set_up()

// --------------------------------------------------------------------

public function tear_down()
{
if ($this->_real_dir !== NULL)
{
$this->_delete_real_dir($this->_real_dir);
$this->_real_dir = NULL;
}
}

// --------------------------------------------------------------------

public function test_octal_permissions()
{
$content = 'Jack and Jill went up the mountain to fight a billy goat.';
Expand Down Expand Up @@ -116,6 +129,47 @@ private function _test_get_file_info($vals)

// --------------------------------------------------------------------

public function test_get_dir_file_info()
{
$this->assertFalse(get_dir_file_info('i_am_not_a_directory'));

$dir = $this->_create_real_dir();
mkdir($dir.'sub');
file_put_contents($dir.'top.txt', 'a');
file_put_contents($dir.'shared.txt', 'bb');
file_put_contents($dir.'sub'.DIRECTORY_SEPARATOR.'shared.txt', 'ccc');

// Default is top-level only: sub/ gets an entry of its own, its contents don't
$info = get_dir_file_info($dir);

$this->assertEquals(array('shared.txt', 'sub', 'top.txt'), $this->_sorted_keys($info));
$this->assertEquals('shared.txt', $info['shared.txt']['name']);
$this->assertEquals(2, $info['shared.txt']['size']);
$this->assertEquals($dir, $info['shared.txt']['relative_path']);
}

// --------------------------------------------------------------------

public function test_get_dir_file_info_recursive()
{
$dir = $this->_create_real_dir();
mkdir($dir.'sub');
file_put_contents($dir.'top.txt', 'a');
file_put_contents($dir.'shared.txt', 'bb');
file_put_contents($dir.'sub'.DIRECTORY_SEPARATOR.'shared.txt', 'ccc');

$info = get_dir_file_info($dir, FALSE);
$nested = 'sub'.DIRECTORY_SEPARATOR.'shared.txt';

// Identically named files in different folders must not overwrite each other
$this->assertEquals(array('shared.txt', $nested, 'top.txt'), $this->_sorted_keys($info));
$this->assertEquals(2, $info['shared.txt']['size']);
$this->assertEquals(3, $info[$nested]['size']);
$this->assertEquals('shared.txt', $info[$nested]['name']);
}

// --------------------------------------------------------------------

public function test_write_file()
{
$content = 'Jack and Jill went up the mountain to fight a billy goat.';
Expand All @@ -128,4 +182,42 @@ public function test_write_file()
$this->assertTrue(write_file(vfsStream::url('write.txt'), $content));
}

// --------------------------------------------------------------------

/**
* get_dir_file_info() resolves its source path with realpath(), which returns
* FALSE for vfsStream URLs, so these tests can't use vfsStream like the rest.
*/
private function _create_real_dir()
{
$this->_real_dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('ci_file_helper_', TRUE).DIRECTORY_SEPARATOR;
mkdir($this->_real_dir);

return $this->_real_dir;
}

private function _delete_real_dir($dir)
{
foreach (scandir($dir) as $file)
{
if ($file === '.' OR $file === '..')
{
continue;
}

$path = rtrim($dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file;
is_dir($path) ? $this->_delete_real_dir($path) : unlink($path);
}

rmdir($dir);
}

private function _sorted_keys($info)
{
$keys = array_keys($info);
sort($keys);

return $keys;
}

}
4 changes: 4 additions & 0 deletions user_guide_src/source/helpers/file_helper.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ The following functions are available:
if forced by sending the second parameter to FALSE, as this can be an intensive
operation.

Array keys are the file paths relative to ``$source_dir``. For a top-level read that
is the filename; a recursive read keys files in sub-folders by their sub-path, e.g.
``blog/post.php`` (``blog\post.php`` on Windows).

Example::

$models_info = get_dir_file_info(APPPATH.'models/');
Expand Down
25 changes: 25 additions & 0 deletions user_guide_src/source/installation/upgrade_320.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,28 @@ if you haven't copied the new one over.

The ``$config['log_file_extension']`` option still works as a fallback,
but ``$config['log_filename']`` takes precedence when set.

Step 15: Check recursive usage of get_dir_file_info()
=====================================================

In 3.1.x, the :doc:`File Helper <../helpers/file_helper>` function
:php:func:`get_dir_file_info()` used to key its output by filename. When
reading recursively (by passing FALSE as the second parameter), two files
with the same name in different sub-folders collided, and only the last one
read was kept.

Output is now keyed by each file's path relative to ``$source_dir``::

// A directory containing config.php and cache/config.php
$info = get_dir_file_info('./path/to/directory/', FALSE);

// 3.1.x: array('config.php' => ...)
// now: array('config.php' => ..., 'cache/config.php' => ...)

For a top-level read (the default), a file's relative path is its filename,
so those keys are unchanged. Only code that reads recursively and looks up
entries by key needs review.

.. note:: In 3.2-dev, this function was broken and returned entries holding
only a ``relative_path`` key, so there is nothing to migrate if you are
coming from there.
Loading