Function
function hierarchify(array $files) {
/* prepare root node */
$root = new stdClass;
$root->children = array();
/* file iteration */
foreach ($files as $file) {
/* argument validation */
switch (true) {
case !isset($file['name'], $file['virtual_path']):
case !is_string($name = $file['name']):
case !is_string($virtual_path = $file['virtual_path']):
throw new InvalidArgumentException('invalid array structure detected.');
case strpos($virtual_path, '/') === 0:
throw new InvalidArgumentException('absolute path is not allowed.');
}
/* virtual url normalization */
$parts = array();
$segments = explode('/', preg_replace('@/++@', '/', $virtual_path));
foreach ($segments as $segment) {
if ($segment === '.') {
continue;
}
if (null === $tail = array_pop($parts)) {
$parts[] = $segment;
} elseif ($segment === '..') {
if ($tail === '..') {
$parts[] = $tail;
}
if ($tail === '..' or $tail === '') {
$parts[] = $segment;
}
} else {
$parts[] = $tail;
$parts[] = $segment;
}
}
if ('' !== $tail = array_pop($parts)) {
// skip empty
$parts[] = $tail;
}
if (reset($parts) === '..') {
// invalid upper traversal
throw new InvalidArgumentException('invalid upper traversal detected.');
}
$currents = &$root->children;
/* hierarchy iteration */
foreach ($parts as $part) {
while (true) {
foreach ($currents as $current) {
if ($current->type === 'dir' and $current->name === $part) {
// directory already exists!
$currents = &$current->children;
break 2;
}
}
// create new directory...
$currents[] = $new = new stdClass;
$new->type = 'dir';
$new->name = $part;
$new->children = array();
$currents = &$new->children;
break;
}
}
// create new file...
$currents[] = $new = new stdClass;
$new->type = 'file';
$new->name = $name;
}
/* convert into array completely */
return json_decode(json_encode($root->children), true);
}
Example
Case 1:
$files = array(
0 => array (
'name' => 'b.txt',
'virtual_path' => 'A/B//',
),
1 => array(
'name' => 'a.txt',
'virtual_path' => '././A/B/C/../..',
),
2 => array(
'name' => 'c.txt',
'virtual_path' => './A/../A/B/C//////',
),
3 => array(
'name' => 'root.txt',
'virtual_path' => '',
),
);
var_dump(hierarchify($files));
will output...
array(2) {
[0]=>
array(3) {
["type"]=>
string(3) "dir"
["name"]=>
string(1) "A"
["children"]=>
array(2) {
[0]=>
array(3) {
["type"]=>
string(3) "dir"
["name"]=>
string(1) "B"
["children"]=>
array(2) {
[0]=>
array(2) {
["type"]=>
string(4) "file"
["name"]=>
string(5) "b.txt"
}
[1]=>
array(3) {
["type"]=>
string(3) "dir"
["name"]=>
string(1) "C"
["children"]=>
array(1) {
[0]=>
array(2) {
["type"]=>
string(4) "file"
["name"]=>
string(5) "c.txt"
}
}
}
}
}
[1]=>
array(2) {
["type"]=>
string(4) "file"
["name"]=>
string(5) "a.txt"
}
}
}
[1]=>
array(2) {
["type"]=>
string(4) "file"
["name"]=>
string(8) "root.txt"
}
}
Case 2:
$files = array(
0 => array (
'name' => 'invalid.txt',
'virtual_path' => '/A/B/C',
),
);
var_dump(hierarchify($files));
will throw...
Fatal error: Uncaught exception 'InvalidArgumentException' with message 'absolute path is not allowed.'
Case 3:
$files = array(
0 => array (
'name' => 'invalid.txt',
'virtual_path' => 'A/B/C/../../../../../../../..',
),
);
var_dump(hierarchify($files));
will throw...
Fatal error: Uncaught exception 'InvalidArgumentException' with message 'invalid upper traversal detected.'