I have this code so far which perfectly but relies on there being a directory in place:
$path = '/home/sites/therealbeercompany.co.uk/public_html/public/themes/trbc/images/backgrounds/'.$this->slug;
$bgimagearray = array();
$iterator = new DirectoryIterator($path);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isFile() && !preg_match('\.jpg$/', $fileinfo->getFilename())) {
$bgimagearray[] = "'" . $fileinfo->getFilename() . "'";
}
}
I need to work in a bit at the top so that if the directory doesnt exist it defaults to the images sat in the root of the background directory...
Any help would be appreciated.
You want is_dir. Test your slug directory, and if it doesn't exist, use the root background directory instead.
Use is_dir to see if the dir is there, and if not, set $path to the current path (where the script is running from)
if (!is_dir($path)) {
$path = $_SERVER["PATH_TRANSLATED"];
}
I very much dangerously assumed that $path is not going to be used anywhere else :)
(is_dir is better, thanks!)
DirectoryIterator will throw an UnexpectedValueException when the path cannot be opened, so you can wrap the call into a try/catch block and then fallback to the root path. In a function:
function getBackgroundImages($path, $rootPath = NULL)
{
$bgImages = array();
try {
$iterator = new DirectoryIterator($path);
// foreach code
} catch(UnexpectedValueException $e) {
if($rootPath === NULL) {
throw $e;
}
$bgImages = getBackgroundImages($rootPath);
}
return $bgImages;
}
But of course file_exists or is_dir are a valid options too.
You could also use the file_exists function to check for the directory.
Related
I'm trying to list all PHP files in a specified directory and for it to recursively check all sub-directories until it finds no more, there could be numerous levels.
The function I have below works fine with the exception that it only recurses down one level.
I've spent hours trying to see where I'm going wrong, I'm calling the scanFiles() when it finds a new directory but this only seems to work one level down and stop, any help greatly appreciated.
Updated:
function scanFiles($pParentDirectory)
{
$vFileArray = scandir($pParentDirectory);
$vDirectories = array();
foreach ($vFileArray as $vKey => $vValue)
{
if (!in_array($vValue, array('.', '..')) && (strpos($vValue, '.php') || is_dir($vValue)))
{
if (!is_dir($vValue))
$vDirectories[] = $vValue;
else
{
$vDirectory = $vValue;
$vSubFiles = scanFiles($vDirectory);
foreach ($vSubFiles as $vKey => $vValue)
$vDirectories[] = $vDirectory.DIRECTORY_SEPARATOR.$vValue;
}
}
}
return $vDirectories;
}
You can do this easily like this:
// helper function
function getFiles(&$files, $dir) {
$items = glob($dir . "/*");
foreach ($items as $item) {
if (is_dir($item)) {
getFiles($files, $item);
} else {
if (end(explode('.', $item)) == 'php') {
$files[] = basename($item);
}
}
}
}
// usage
$files = array();
getFiles($files, "myDir");
// debug
var_dump($files);
myDir looks like this: has php files in all dirs
Output:
P.S. if you want the function to return the full path to the found .php files, remove the basename() from this line:
$files[] = basename($item);
This will then produce result like this:
hope this helps.
This is because $vDirectory is just a folder name, so scanDir looks in the current folder for it, not the sub folder.
What you want to do is to pass in the path to the folder, not just the name. This should be as simple as changing your recursive call to scanFiles($pParentDirectory . DIRECTORY_SEPARATOR . $vDirectory)
Your main problem is functions like scanDir or isDir need the full file path to work.
If you pass the full file path to them, it should work correctly.
I had to list all files and folders in a directory:
$images = array();
$dirs = array();
$dir = new DirectoryIterator($upload_dir_real);
foreach ($dir as $file) {
if ($file->isDot()) {
continue;
}
if ($file->isDir()) {
// dir
$scanned_dirs[] = $file->getPath();
continue;
} else {
// file
//echo $file->getFilename() . "<br>\n";//DEBUG
$realfile = $file->getFilename() . "<br>\n";
$realpath = $file->getPathname();
echo realpath($realfile);//DEBUG
$file->getFilename();
$images[] = realpath( $realpath );
}
}
This works fine (no errors) but of course counted only the root, so I tried recursive:
$images = array();
$dirs = array();
$dir = new RecursiveDirectoryIterator($upload_dir_real);
foreach ($dir as $file) {
if ($file->isDot()) {
continue;
}
if ($file->isDir()) {
// dir
$scanned_dirs[] = $file->getsubPath();
continue;
} else {
// file
//echo $file->getFilename() . "<br>\n"; //DEBUG
$realfile = $file->getsubFilename() . "<br>\n";
$realpath = $file->getsubPathname();
echo realpath($realfile);//DEBUG
$file->getFilename();
$images[] = realpath( $realpath );
}
}
Basically, I changed the getPath(); with getsubPath() (and equivalent). The problem is that it give me an error:
Fatal error: Call to undefined method SplFileInfo::isDot() in blah blah path
so I searched a while and found this:
Why does isDot() fail on me? (PHP)
This is basically the same problem, but when I try, I get this error:
Fatal error: Class 'FilesystemIterator' not found in in blah blah path
Questions:
1 - why is the method described in the other accepted answer not working for me?
2 - in that same answer, what is the following code:
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$pathToFolder,
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_SELF));
This actually calls RecursiveIteratorIterator twice? I mean, if it is recursive, it can not be recursive twice :-)
2b - how come FilesystemIterator is not found, even if the PHP manual states (to my understanding) that it is a part of what the recursive iterator is built upon?
(Those questions are because I want to understand better, not to just copy and paste answers).
3 - is there a better way to list all folders and files cross platform?
1 - why is the method described in the other accepted answer not working for me ??`
As far as i can tell . the code works perfectly but your implementation is wrong you are using the following
Code
$dir = new RecursiveDirectoryIterator($upload_dir_real);
Instead of
$dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($upload_dir_real));
In that same answer actually calls RecursiveIteratorIterator twice ?? I mean, if it is recursive , it can not be recursive twice ... :-))
No it does not its different
RecursiveIteratorIterator != RecursiveDirectoryIterator != FilesystemIterator
^ ^
how come FilesystemIterator is not found , even if the php manual states (to my understanding) that it is a part of what the recursive iterator is built upon??
You already answered that your self in your comment you are using PHP version 5.2.9 which is no longer supported or recommended
3 - Is there a better way to list all folder and files cross platform ??
Since that is resolved all you need is FilesystemIterator::SKIP_DOTS you don't have to call $file->isDot()
Example
$fullPath = __DIR__;
$dirs = $files = array();
$directory = new RecursiveDirectoryIterator($fullPath, FilesystemIterator::SKIP_DOTS);
foreach (new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST) as $path ) {
$path->isDir() ? $dirs[] = $path->__toString() : $files[] = realpath($path->__toString());
}
var_dump($files, $dirs);
Here is another method, utilizing setFlags:
<?php
$o_dir = new RecursiveDirectoryIterator('.');
$o_dir->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
$o_iter = new RecursiveIteratorIterator($o_dir);
foreach ($o_iter as $o_info) {
echo $o_info->getPathname(), "\n";
}
https://php.net/filesystemiterator.setflags
I'm working on a Minecraft Server Dashboard, and one of it's functions is to backup and restore your world (a directory). I already have a function (see below), but, as you can probably see, it's pretty bad code. Anyone know of a better, cleaner function?
function backupOrRestoreWorld($source,$target){
foreach(glob($target.'*.*')as$v){
unlink($v);
}
if(is_dir($source)){
#mkdir($target);
$d=dir($source);
while(FALSE!==($entry=$d->read())){
if($entry=='.'||$entry=='..'){
continue;
}
$Entry=$source.'/'.$entry;
if(is_dir($Entry)){
backupOrRestoreWorld($Entry,$target.'/'.$entry);
continue;
}
copy($Entry,$target.'/'.$entry);
}
$d->close();
}
else{
copy($source,$target);
}
if($source == "server/world"){
return "World backed up.";
}
else {
return "World restored from backup.";
}
}
I wouldn't do this in PHP. Just use system("cp -a $source $dest"). (And make sure the user can not in any way control the contents of $source and $dest or you will be hacked.)
I would create more functions out of it, each one doing a distinctive job, probably encapsulated into a class, like
empty_directory
copy_directory
You can still maintain your single function then making use of the subroutines / objects to provide a façade into your application, for example dealing with exceptions for error handling and such.
Next to that you're having not really bad code. It's a recursive function for copying the data which might stress the file-system a bit - which presumably can be neglected. If you move the decent functionality into units of it's own, you can change that over time if you run into actual problems.
But the first benefit would be to make use of exceptions in subroutines I think:
function backupOrRestoreWorld($source, $target)
{
empty_directory($target);
copy_directory($source, $target);
if($source == "server/world"){
return "World backed up.";
}
else {
return "World restored from backup.";
}
}
function empty_directory($path)
{
$path = rtrim($path, '/');
if (!is_dir($path))
{
throw new InvalidArgumentException(sprintf('Not a directory ("%s").', $path));
}
if (!is_writable($path))
{
throw new InvalidArgumentException(sprintf('Directory ("%s") is not a writeable.', $path));
}
$paths = glob($path.'/*.*');
if (false === $paths)
{
throw new Exception(sprintf('Unable to get path list on path "%s" (glob failed).', $path));
}
foreach ($paths as $v)
{
unlink($v);
}
}
function copy_directory($source, $target)
{
$source = rtrim($source, '/');
$target = rtrim($target, '/');
if (!is_dir($source))
{
throw new InvalidArgumentException(sprintf('Source ("%s") is not a valid directory.', $source));
}
if (!is_readable($source))
{
throw new InvalidArgumentException(sprintf('Source ("%s") is not readable.', $source));
}
if (!is_dir($target))
$r = mkdir($target);
if (!is_dir($target))
{
throw new InvalidArgumentException(sprintf('Target ("%s") is not a valid directory.', $target));
}
if (!is_writable($target))
{
throw new InvalidArgumentException(sprintf('Target ("%s") is not a writeable.', $target));
}
$dirs = array('');
while(count($dirs))
{
$dir = array_shift($dirs)
$base = $source.'/'.$dir;
$d = dir($base);
if (!$d)
{
throw new Exception(sprintf('Unable to open directory "%s".', $base));
}
while(false !== ($entry = $d->read()))
{
// skip self and parent directories
if (in_array($entry, array('.', '..'))
{
continue;
}
// put subdirectories on stack
if (is_dir($base.'/'.$entry))
{
$dirs[] = $dir.'/'.$entry;
continue;
}
// copy file
$from = $base.'/'.$entry;
$to = $target.'/'.$dir.'/'.$entry;
$result = copy($from, $to);
if (!$result)
{
throw new Exception(sprintf('Failed to copy file (from "%s" to "%s").', $from, $to);
}
}
$d->close();
}
}
This example is basically introducing two functions, one to empty a directory and another one to copy the contents of one directory to another. Both functions do throw exceptions now with more or less useful descriptions what happens. I tried to reveal errors early, that's by checking input parameters and by performing some additional tests.
The empty_directory function might be a bit short. I don't know for example, if there a subdirectories and those aren't empty, if unlink would work. I leave this for an exercise for you.
The copy_directory function is working non-recursive. This is done by providing a stack of directories to process. In case there is a subdirectory, the directory is put on the stack and processed after the all files of the current directory are copied. This helps to prevent switching directories too often and is normally faster. But as you can see, it's very similar to your code.
So these functions concentrate on the file-system work. As it's clear what they do and for what they are, you can concentrate inside your function on the main work, like the logic to determine in which direction the copying has been done. As the helper functions now throw exceptions, you can catch those, too. Additionally you could verify that $source and $target actually contain values that you explicitly allow. For example, you don't want to have .. or / inside of them probably, but just characters from a-z.
This will help you as well to find other causes of error, like overwriting attempts etc.:
function backupOrRestoreWorld($source, $target)
{
$basePath = 'path/to/server';
$world = 'world';
$pattern = '[a-z]+';
try
{
if (!preg_match("/^{$pattern}\$/", $source))
{
throw new InvalidArgumentException('Invalid source path.');
}
if (!preg_match("/^{$pattern}\$/", $target))
{
throw new InvalidArgumentException('Invalid target path.');
}
if ($source === $target)
{
throw new InvalidArgumentException('Can not backup or restore to itself.');
}
$targetPath = $basePath.'/'.$target;
if (is_dir($targetPath))
empty_directory($targetPath);
copy_directory($basePath.'/'.$source, $targetPath);
if($source === $world)
{
return "World backed up.";
}
else
{
return "World restored from backup.";
}
}
catch(Exception $e)
{
return 'World not backed up. Error: '.$e->getMessage();
}
}
In the example backupOrRestoreWorld still acts as the original function but now returns an error message and more specifically checks for error conditions in it's own logic. It's a bit borked because it converts exceptions to the return value, which are two sorts of error handling (which you might not want), but it's a compromise to interface with your existing code (the façade) while covering itself's input validation with exceptions.
Additionally, the values it works on (parameters) are specified at the top of the function instead of later on in the function's code.
Hope this helps. What's left?
The copy_directory function could check if a directory already exists, that it is empty. Otherwise it would not truly copy a world, but mix two worlds.
The empty_directory function should be properly checked if it actually does the job to empty a directory in a fail-safe manner, especially while dealing with subdirectories.
You can make up your mind about a general way of doing error handling inside your application so that you can more easily deal with errors inside your application.
You can think about creating objects to deal with storing and retrieving worlds so things can be extended easily in the long run.
.1. Using # operator will lead you to trouble. NEVER use it. If there is a possibility of unexisting file - CHECK IT first!
if (!file_exists($target)) mkdir($target);
.2. I am quite surprised why you're using glob in the first part and don't use it in the second.
.3. Your "clean target directory code first" code won't clean subdirectories.
I used, from here: http://codestips.com/php-copy-directory-from-source-to-destination/
<?
function copy_directory( $source, $destination ) {
if ( is_dir( $source ) ) {
mkdir( $destination );
$directory = dir( $source );
while ( FALSE !== ( $readdirectory = $directory->read() ) ) {
if ( $readdirectory == '.' || $readdirectory == '..' ) {
continue;
}
$PathDir = $source . '/' . $readdirectory;
if ( is_dir( $PathDir ) ) {
copy_directory( $PathDir, $destination . '/' . $readdirectory );
continue;
}
copy( $PathDir, $destination . '/' . $readdirectory );
}
$directory->close();
}else {
copy( $source, $destination );
}
}
?>
Works so long as you don't try to copy a directory inside itself and go into an infinite loop..
I have some recursive copying code that follows, but first a few thoughts.
Conversely to what some others think - I believe the file system functions are about the only place where the # operator makes sense. It allows you to raise your own exceptions rather than having to handle the functions built in warnings. With all file system calls you should check for failure conditions.
I would not do something that was suggested to you:
if (!file_exists($target)) mkdir($target);
(which assumes that mkdir will succeed). Always check for failure (the file_exists check does not match the complete set of possibilities where mkdir would fail). (e.g broken sym links, inaccessible safe mode files).
You must always decide how you will handle exceptions, and what are the initial conditions required for your function to succeed. I treat any operation that fails with the file system as an exception which should be caught and dealt with.
Here is the recursive copying code that I use:
/** Copy file(s) recursively from source to destination.
* \param from \string The source of the file(s) to copy.
* \param to \string The desitination to copy the file(s) into.
* \param mode \int Octal integer to specify the permissions.
*/
public function copy($from, $to, $mode=0777)
{
if (is_dir($from))
{
// Recursively copy the directory.
$rDir = new RecursiveDirectoryIterator(
$from, FilesystemIterator::SKIP_DOTS);
$rIt = new RecursiveIteratorIterator(
$rDir, RecursiveIteratorIterator::SELF_FIRST);
// Make the directory - recursively creating the path required.
if (!#mkdir($to, $mode, true))
{
throw new Exception(
__METHOD__ .
'Unable to make destination directory: ' . var_export($to, true));
}
foreach ($rIt as $file)
{
$src = $file->getPathname();
$dest = $to . $rIt->getInnerIterator()->getSubPathname();
if (is_dir($src))
{
if (!#mkdir($dest, $mode))
{
throw new Exception(
__METHOD__ .
'From: ' . $from . ' To: ' . $to .
' Copying subdirectory from:' . $src . ' to: ' . $dest);
}
}
else
if (!#copy($src, $dest))
{
throw new Exception(
__METHOD__ .
'From: ' . $from . ' To: ' . $to .
' Copying file from: ' . $src . ' to: ' . $dest);
}
}
}
}
else
{
if (!#copy($from, $to))
{
throw new Exception(
__METHOD__ .
'Copying single file from: ' . $from . ' to: ' . $to);
}
}
}
Before copy all files, you must to make "/destination" permission to be 0777
$dst = '/destination'; // path for past
$src = '/source'; // path for copy
$files = glob($src.'/*.*');
foreach($files as $file){
$file_to_go = str_replace($src, $dst, $file);
copy($file, $file_to_go);
}
$dir_array = array();
$dir_array[] = $src;
while ($dir_array != null) {
$newDir = array ();
foreach ($dir_array as $d) {
$results = scandir($d);
foreach ($results as $r) {
if ($r == '.' or $r == '..') continue;
if (is_file($d . '/' . $r)) {
} else {
$path = $d.'/'.$r;
$newDir[] = $path;
$new = str_replace($src, $dst, $path);
mkdir($new, 0777, true);
$files = glob($path.'/*.*');
foreach($files as $file) {
$file_to_go = str_replace($src, $dst, $file);
copy($file, $file_to_go);
}
continue;
}
}
}
$dir_array = $newDir;
}
all done.
Thanks.
$value can = a folder structure to the language file. Example: languages/english.php
$value can also = the files name. Example: english.php
So I need to get the current folder that $value is in and delete the folder ONLY if there are no other files/folders within that directory (after deleting the actual file as I am doing already, ofcourse).
foreach($module['languages'] as $lang => $langFile)
{
foreach ($langFile as $type => $value)
{
#unlink($module_path . '/' . $value);
// Now I need to delete the folder ONLY if there are no other directories inside the folder where it is currently at.
// And ONLY if there are NO OTHER files within that folder also.
}
}
How can I do this?? And wondering if this can be done without using a while loop, since a while loop within a foreach loop could take some time, and need this to be as quick as possible.
And just FYI, the $module_path should never be deleted. So if $value = english.php, it should never delete the $module_path. Ofcourse, there will always be another file in there, so checking for this is not necessary, but won't hurt either way.
Thanks guys :)
EDIT
Ok, now I'm using this code here and it is NOT working, it is not removing the folders or the files, and I don't get any errors either... so not sure what the problem is here:
foreach($module['languages'] as $lang => $langFile)
{
foreach ($langFile as $type => $value)
{
if (#unlink($module_path . '/' . $value))
#rmdir(dirname($module_path . '/' . $value));
}
}
NEVERMIND, this works a CHARM!!! Cheers Everyone!!
The easyest way is try to use rmdir. This don't delete folder if it is not empty
rmdir($module_path);
also you can check is folder empty by
if(count(glob($module_path.'*'))<3)//delete
2 for . and ..
UPD: as I reviewed maybe you should replace $module_path by dirname($module_path.'.'.$value);
Since the directory you care about might be part of the $value, you need to use dirname to figure out what the parent directory is, you can't just assume that it's $module_path.
$file_path = $module_path . '/' . $value;
if (#unlink($file_path)) {
#rmdir(dirname($file_path));
}
if (is_file($value)) {
unlink($value);
} else if (is_dir($value)) {
if (count(scandir($value)) == 2) }
unlink($value)
}
}
http://php.net/manual/en/function.is-dir.php
http://www.php.net/manual/en/function.scandir.php
The code below will take a path, check if it is a file (i.e. not a directory). If it is a file, it will extract the directory name, then delete the file, then iterate over the dir and count the files in it, if the files are zero it'll delete the dir.
Code is as an example and should work, however privileges and environment setup may result in it not working.
<?php
if(!is_dir ( string $filename )){ //if it is a file
$fileDir = dirname ( $filename );
if ($handle = opendir($fileDir)) {
echo "Directory handle: $handle\n";
echo "Files:\n";
$numFiles=0;
//delete the file
unlink($myFile);
//Loop the dir and count the file in it
while (false !== ($file = readdir($handle))) {
$numFiles = $numFiles + 1;
}
if($numFiles == 0) {
//delete the dir
rmdir($fileDir);
}
closedir($handle);
}
}
?>
I'd love some help....i'm not sure where to start in creating a script that searches for a folder in a directory and if it doesnt exist then it will simply move up one level (not keep going up till it finds one)
I am using this code to get a list of images. But if this folder didn't exist i would want it to move up to its parent.
$iterator = new DirectoryIterator("/home/domain.co.uk/public_html/assets/images/bg-images/{last_segment}"); foreach ($iterator as $fileinfo) {
if ($fileinfo->isFile() && !preg_match('/-c\.jpg$/', $fileinfo->getFilename())) {
$bgimagearray[] = "'" . $fileinfo->getFilename() . "'";
} }
Put your directory name in a variable.
$directory = "/home/domain.co.uk/public_html/assets/images/bg-images/{last_segment}";
// if directory does not exist, set it to directory above.
if(!is_dir($directory)){
$directory = dirname($directory)
}
$iterator = new DirectoryIterator($directory);
It works: file_exists($pathToDir)
To test if a directory exists, use is_dir()
http://php.net/function.is-dir
To move up to the parent directory would be by chdir('..');
http://php.net/function.chdir