PHP get file listing including sub directories - php

I am trying to retrieve all images in a directory, including all subdirectories. I am currently using
$images = glob("{images/portfolio/*.jpg,images/portfolio/*/*.jpg,images/portfolio/*/*/*.jpg,images/portfolio/*/*/*/*.jpg}",GLOB_BRACE);
This works, however the results are:
images/portfolio/1.jpg
images/portfolio/2.jpg
images/portfolio/subdirectory1/1.jpg
images/portfolio/subdirectory1/2.jpg
images/portfolio/subdirectory2/1.jpg
images/portfolio/subdirectory2/2.jpg
images/portfolio/subdirectory1/subdirectory1/1.jpg
images/portfolio/subdirectory1/subdirectory1/2.jpg
I want it to do a whole directory branch at a time so the results are:
images/portfolio/1.jpg
images/portfolio/2.jpg
images/portfolio/subdirectory1/1.jpg
images/portfolio/subdirectory1/2.jpg
images/portfolio/subdirectory1/subdirectory1/1.jpg
images/portfolio/subdirectory1/subdirectory1/2.jpg
images/portfolio/subdirectory2/1.jpg
images/portfolio/subdirectory2/2.jpg
Greatly appreciate any help, cheers!
P.S It would also be great if I could just get all subdirectories under portfolio without having to specifically state each directory with a wild card.

from glob example
if ( ! function_exists('glob_recursive'))
{
// Does not support flag GLOB_BRACE
function glob_recursive($pattern, $flags = 0)
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
{
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}

Solution:
<?php
$path = realpath('yourfolder/examplefolder');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename)
{
echo "$filename</br>";
}
?>

Here's a simpler approach:
Instead of using:
$path = realpath('yourfolder/examplefolder/*');
glob($path);
You'll have to use:
$path = realpath('yourfolder/examplefolder').'/{**/*,*}';
glob($path, GLOB_BRACE);
This last one will use bracing, and it is, in fact, a shorthand for this code:
$path = realpath('yourfolder/examplefolder');
$self_files = glob($path . '/*');
$recursive_files = glob($path . '/**/*');
$all_files = $self_files + $recursive_files; // That's the result you want
You may also want to filter directories from your result. glob() function has GLOB_ONLYDIR flag. Let's use it to diff out our result.
$path = realpath('yourfolder/examplefolder/') . '{**/*,*}';
$all_files = array_diff(
glob($path, GLOB_BRACE),
glob($path, GLOB_BRACE | GLOB_ONLYDIR)
);

This function supports GLOB_BRACE:
function rglob($pattern_in, $flags = 0) {
$patterns = array ();
if ($flags & GLOB_BRACE) {
$matches;
if (preg_match_all ( '#\{[^.\}]*\}#i', $pattern_in, $matches )) {
// Get all GLOB_BRACE entries.
$brace_entries = array ();
foreach ( $matches [0] as $index => $match ) {
$brace_entries [$index] = explode ( ',', substr ( $match, 1, - 1 ) );
}
// Create cartesian product.
// #source: https://stackoverflow.com/questions/6311779/finding-cartesian-product-with-php-associative-arrays
$cart = array (
array ()
);
foreach ( $brace_entries as $key => $values ) {
$append = array ();
foreach ( $cart as $product ) {
foreach ( $values as $item ) {
$product [$key] = $item;
$append [] = $product;
}
}
$cart = $append;
}
// Create multiple glob patterns based on the cartesian product.
foreach ( $cart as $vals ) {
$c_pattern = $pattern_in;
foreach ( $vals as $index => $val ) {
$c_pattern = preg_replace ( '/' . $matches [0] [$index] . '/', $val, $c_pattern, 1 );
}
$patterns [] = $c_pattern;
}
} else
$patterns [] = $pattern_in;
} else
$patterns [] = $pattern_in;
// #source: http://php.net/manual/en/function.glob.php#106595
$result = array ();
foreach ( $patterns as $pattern ) {
$files = glob ( $pattern, $flags );
foreach ( glob ( dirname ( $pattern ) . '/*', GLOB_ONLYDIR | GLOB_NOSORT ) as $dir ) {
$files = array_merge ( $files, rglob ( $dir . '/' . basename ( $pattern ), $flags ) );
}
$result = array_merge ( $result, $files );
}
return $result;
}

Simple class:
<?php
class AllFiles {
public $files = [];
function __construct($folder) {
$this->read($folder);
}
function read($folder) {
$folders = glob("$folder/*", GLOB_ONLYDIR);
foreach ($folders as $folder) {
$this->files[] = $folder . "/";
$this->read( $folder );
}
$files = array_filter(glob("$folder/*"), 'is_file');
foreach ($files as $file) {
$this->files[] = $file;
}
}
function __toString() {
return implode( "\n", $this->files );
}
};
$allfiles = new AllFiles("baseq3");
echo $allfiles;
Example output:
baseq3/gfx/
baseq3/gfx/2d/
baseq3/gfx/2d/numbers/
baseq3/gfx/2d/numbers/eight_32b.tga
baseq3/gfx/2d/numbers/five_32b.tga
baseq3/gfx/2d/numbers/four_32b.tga
baseq3/gfx/2d/numbers/minus_32b.tga
baseq3/gfx/2d/numbers/nine_32b.tga
If you don't want the folders in the list, just comment this line out:
$this->files[] = $folder . "/";

Related

Getting a list of sub-directories form an array of paths based on current path

I want to get an array of immediate sub-directories based on the current path from a given list of paths. Here is an example list of paths and the desired result:
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$current_path = "/chris";
// Required result:
$sub_dirs = ['/usa', '/canada'];
The current path could be anything from / to the last sub directory, in which case I would end up with an empty $sub_dirs.
My best attempt at it is this:
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$sub_dirs = [];
$current_path = "/";
foreach($dirs as $dir){
if(strstr($dir, $current_path)){
$sub_dirs[] = str_replace($current_path, '', $dir);
}
}
The above fails at two things. If the $current_path = "/" then the returned array is just paths without slashes since I strip them out with strstr() and if the $current_path = "/chris" then I still get the sub-directories.
How should I correctly solve it?
Thank you!
You can search for /chris/ to get only sub directories.
To get only the first directory of matching paths, you could use explode(), and get the first.
As of PHP8, you can use str_starts_with()
$dirs = ['/chris', '/chris/usa', '/david', '/', '/chris/canada/2022', '/david/uk'];
$current_path = "/";
$sub_dirs = [];
foreach($dirs as $dir)
{
// Ensure $dir starts with path + '/' (sub dir of $current_path)
if (!str_starts_with($dir, $current_path)) {
continue;
}
// Get the relative path from $current_path
$relPath = substr($dir, strlen($current_path));
// Get the first dir of this path
[$dir2] = explode('/', ltrim($relPath,'/'));
// skip same directory
if (empty($dir2)) {
continue;
}
// Store and add removed slash
$sub_dirs[] = '/' . $dir2;
}
var_export(array_unique($sub_dirs));
Output
array (
0 => '/usa',
1 => '/canada',
)
I like the approach of creating a tree out of the paths (adapted from this answer).
Then it's easier to get the children based on a path.
function buildTree($paths)
{
// Create a hierarchy where keys are the labels
$rootChildren = [];
foreach ($paths as $path) {
$branch = explode("/", $path);
$children = &$rootChildren;
foreach ($branch as $label) {
if ($label) {
if (!isset($children[$label])) {
$children[$label] = [];
}
$children = &$children[$label];
}
}
}
// Create target structure from that hierarchy
function recur($children)
{
$result = [];
foreach ($children as $label => $grandchildren) {
$result[$label] = recur($grandchildren);
}
return $result;
}
return recur($rootChildren);
}
function findChildren($tree, $path)
{
$branch = explode("/", $path);
$pointer = $tree;
foreach ($branch as $label) {
if ($label) {
$pointer = $pointer[$label];
}
}
return array_keys($pointer);
}
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$tree = buildTree($dirs);
print_r($tree);
$current_path = "/chris";
print_r(findChildren($tree, $current_path));
Output:
Array
(
[chris] => Array
(
[usa] => Array
(
)
[canada] => Array
(
[2022] => Array
(
)
)
)
[david] => Array
(
[uk] => Array
(
)
)
)
Array
(
[0] => usa
[1] => canada
)
I feel like I have a bit shorter answer than the previous ones, but not sure if it's the most efficient one though.
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$current_path = "/chris";
$sub_dirs = array_map(function($s) { return '/'.explode("/", $s)[2]; },array_filter($dirs, function($dir) use ($current_path) {
return substr($dir, 0, strlen($current_path) + 1) === $current_path . '/';
}));
print_r($sub_dirs); // Output: Array ( [1] => /usa [4] => /canada )

Foreach loop not displaying the 0 element in codeignter

Goodday,
I need some assistant with my foreach loop.
I am uploading multiple images/files to my server and then i am trying to send the images as attachments in an email.
I get the data to the email function and can git it in the loop but for some reason i only see the last item in the array and con see why tjis is happing.
Please see attached the output and code of the function.
function forwardCallEmail($emaildetails)
{
$data = $emaildetails;
pre($data['files']);
echo('<br/>');
//die;
$CI = setProtocol();
$CI->email->from('');
$CI->email->subject("");
$CI->email->message($CI->load->view('calls/forwardCallEmail', $data, TRUE));
$path = base_url() . "uploads/Calls/";
foreach ((array) $data['files'] as $files){
echo($files);
echo('<br/>');
$images = explode(',', $files);
var_dump($images);
foreach($images as $files);
echo('<br/>');
echo $files;
die;
$CI->email->attach($path . $files);
pre($CI);
die;
}
$CI->email->to($data['email']);
$status = $CI->email->send();
return $status;
You can try something like this
Replace this part
foreach ((array) $data['files'] as $files){
echo($files);
echo('<br/>');
$images = explode(',', $files);
var_dump($images);
foreach($images as $files);
echo('<br/>');
echo $files;
die;
$CI->email->attach($path . $files);
pre($CI);
die;
}
To this one
foreach ((array) $data['files'] as $files){
echo($files);
echo('<br/>');
$images = explode(',', $files);
var_dump($images);
foreach($images as $files) {
echo('<br/>');
echo $files;
$CI->email->attach($path . $files);
pre($CI);
}
}
Anyway this example to explain a logic of foreach
Ok, just as example
$data = [
'files' => [
"image1, image2, image3",
"image4, image5, image6",
]
];
foreach ($data['files'] as $fileKey => $file){
echo($file);
$images = explode(',', $file);
foreach($images as $imageKey => $imageValue) {
$out[$fileKey][$imageKey] = $imageValue;
}
}
print_r($out);
And result will be
(
[0] => Array
(
[0] => image1
[1] => image2
[2] => image3
)
[1] => Array
(
[0] => image4
[1] => image5
[2] => image6
)
)
I got it to work, not sure if it is the right way it is working.
Email sending code
CI = setProtocol();
$CI->email->from('helpdesk#ziec.co.za', 'HTCC Helpdesk');
$CI->email->subject("HTCC Call Assistants");
$CI->email->message($CI->load->view('calls/forwardCallEmail', $data, TRUE));
$path = 'c:/xampp/htdocs/Helpdeskv2.1/uploads/Calls/';
$images = explode(',', $data['files']);
foreach($images as $file){
$path = 'c:/xampp/htdocs/Helpdeskv2.1/uploads/Calls/'. $file;
$CI->email->attach($path);
}
$CI->email->to($data['email']);
$status = $CI->email->send();
return $status;

get entire folders/files tree and rename

I have a folders/files tree inside admin folder (windows, localhost).
All files are .html.
Each of them (files and folders) is starting with some numbers and middle dash, for example
32-somefolder
624-somefile.html
I need to list all of them and remove all prefixes from their names.
So the result should be:
somefolder
somefile.html
foreach(glob("admin/*") as $el) {
echo $el . '.' . filetype($el) . '<br>';
}
First problem - only folders are listed:
admin/32-somefolder.dir
How to get files too, and how to rename i.e. remove prefixes from all the names?
You can use the second choice to list files : scandir, and recursive function :
function removePrefixFiles($dir, &$results = array()){
$files = scandir($dir);
foreach ($files as $key => $value){
$path = realpath($dir . DIRECTORY_SEPARATOR . $value);
if (! is_dir($path)) {
// treat the filename
$file = pathinfo($path);
$filename = explode('-', $file['filename']);
if (count($filename) > 0) {
// '-' is found, rename file
rename($path, $file['dirname'] .'/'. $filename[1] .'.'. $file['extension'];
}
$results[] = $path;
} else if ($value != '.' && $value != '..') {
removePrefixFiles($path, $results);
$results[] = $path;
}
}
// no real need to return something here, but can log the files
return $results;
}
$dir = '/admin';
removePrefixFiles($dir);
I have created two folder inside admin/ name as
1-files and 2-abc
then inside folder 1-files i have two files
11-java.html
11-text.html
then inside folder 2-abc i have two files
22-php.html
22-sql.html
<?php
$dir = "admin/";
// Sort in ascending order - this is default
$a = scandir($dir);
echo "<pre>";
if(count($a)>0){
$newArr = array();
for($i=2;$i<count($a);$i++){
$test = array();
$folderArr = array();
$folderName = explode('-',$a[$i]);
$test['folder'] = $folderName[1];
$b = scandir($dir.'/'.$a[$i]);
for($j=2;$j<count($b);$j++){
$fileName = explode('-',$b[$j]);
$folderArr[] = substr($fileName[1], 0, strpos($fileName[1], "."));;
}
$test['files'] = $folderArr;
$newArr[] = $test;
}
}
print_r($newArr);
?>
This will be the output
Array
(
[0] => Array
(
[folder] => files
[files] => Array
(
[0] => java
[1] => text
)
)
[1] => Array
(
[folder] => abc
[files] => Array
(
[0] => php
[1] => sql
)
)
)
Hope this willl hellp you.

Building paths from multidimensional array in PHP

I have an array such as:
$tree = array(
'folder_1' => array(
'folder_1_1',
'folder_1_2' => array(
'folder_1_2_1',
'folder_1_2_2'
),
'folder_1_3'
),
'folder_2' => array(
'folder_2_1' => array(
'folder_2_1_1' => array(
'folder_2_1_1_1',
'folder_2_1_1_2'
)
),
)
);
And I'm trying to build an array of paths:
$paths = array(
'folder_1',
'folder_1/folder_1_1',
'folder_1/folder_1_2',
'folder_1/folder_1_2/folder_1_2_1',
'folder_1/folder_1_2/folder_1_2_2',
'folder_2',
'folder_2/folder_2_1',
...
);
I can't seem to find a way to achieve this. The problem I encounter is that folder names can be array keys, but also array elements.
This is what I have done so far, but I'm not near a solution...
$paths = transform_tree_to_paths($trees);
function transform_tree_to_paths($trees, $current_path = '', $paths = array())
{
if (is_array($trees)) {
foreach ($trees as $tree => $children) {
$current_path .= $tree . '/';
return transform_tree_to_paths($children, $current_path, $paths);
}
$paths[] = $current_path;
$current_path = '';
} else {
$paths[] = $trees;
}
return $paths;
}
How about something like this?
function gen_path($tree, $parent=null) {
$paths = array();
//add trailing slash to parent if it is not null
if($parent !== null) {
$parent = $parent.'/';
}
//loop through the tree array
foreach($tree as $k => $v) {
if(is_array($v)) {
$currentPath = $parent.$k;
$paths[] = $currentPath;
$paths = array_merge($paths, gen_path($v, $currentPath));
} else {
$paths[] = $parent.$v;
}
}
return $paths;
}
You were headed in the right direction, but missed the mark a bit. The return statement before the recursive function call in your function caused everything after the foreach loop to never be called.
Here's another solution, utilizing RecursiveArrayIterator and RecursiveIteratorIterator:
function generatePaths( array $tree ) {
$result = array();
$currentPath = array();
$rii = new RecursiveIteratorIterator( new RecursiveArrayIterator( $tree ), RecursiveIteratorIterator::SELF_FIRST );
foreach( $rii as $key => $value ) {
if( ( $currentDepth = $rii->getDepth() ) < count( $currentPath ) ) {
array_splice( $currentPath, $currentDepth );
}
$currentPath[] = is_array( $value ) ? $key : $value;
$result[] = implode( '/', $currentPath );
}
return $result;
}
PS.: Baconics' solution appears to be about twice as fast as mine, though.

PHP Dynamically adding dimensions to an array using for loop

Here is my dilemma and thank you in advance!
I am trying to create a variable variable or something of the sort for a dynamic associative array and having a hell of a time figuring out how to do this. I am creating a file explorer so I am using the directories as the keys in the array.
Example:
I need to get this so I can assign it values
$dir_list['root']['folder1']['folder2'] = value;
so I was thinking of doing something along these lines...
if ( $handle2 = #opendir( $theDir.'/'.$file ))
{
$tmp_dir_url = explode($theDir);
for ( $k = 1; $k < sizeof ( $tmp_dir_url ); $k++ )
{
$dir_list [ $dir_array [ sizeof ( $dir_array ) - 1 ] ][$tmp_dir_url[$k]]
}
this is where I get stuck, I need to dynamically append a new dimension to the array durring each iteration through the for loop...but i have NO CLUE how
I would use a recursive approach like this:
function read_dir_recursive( $dir ) {
$results = array( 'subdirs' => array(), 'files' => array() );
foreach( scandir( $dir ) as $item ) {
// skip . & ..
if ( preg_match( '/^\.\.?$/', $item ) )
continue;
$full = "$dir/$item";
if ( is_dir( $full ) )
$results['subdirs'][$item] = scan_dir_recursive( $full );
else
$results['files'][] = $item;
}
}
The code is untested as I have no PHP here to try it out.
Cheers,haggi
You can freely put an array into array cell, effectively adding 1 dimension for necessary directories only.
I.e.
$a['x'] = 'text';
$a['y'] = new array('q', 'w');
print($a['x']);
print($a['y']['q']);
How about this? This will stack array values into multiple dimensions.
$keys = array(
'year',
'make',
'model',
'submodel',
);
$array = array();
print_r(array_concatenate($array, $keys));
function array_concatenate($array, $keys){
if(count($keys) === 0){
return $array;
}
$key = array_shift($keys);
$array[$key] = array();
$array[$key] = array_concatenate($array[$key], $keys);
return $array;
}
In my case, I knew what i wanted $keys to contain. I used it to take the place of:
if(isset($array[$key0]) && isset($array[$key0][$key1] && isset($array[$key0][$key1][$key2])){
// do this
}
Cheers.

Categories