If use this function to remove a directory + all files inside.
function delete_files($target)
{
if(is_dir($target))
{
$files = glob($target . '*', GLOB_MARK);
foreach($files as $file)
{
delete_files($file);
}
rmdir($target);
}
elseif(is_file($target))
{
unlink($target);
}
}
delete_files($directory);
But whenever I do this, I get this error message:
Warning: rmdir(directory/12) [function.rmdir]: No such file or directory
in delete_files.php
"directory/12" is the correct name of the directory I wanted to delete. I don't understand why it says that it does not exist because it does! Weirdly though, even though I got the error message, the directory DID get deleted.
So I added a line of code print_n($files); before the for-loop and it game me two arrays -- one containing the directory ("directory/12") and the other containing all the files of the directory ("directory/12/01.gif", "directory/12/02.gif" etc). So I figured the directory must have gotten deleted in the for-loop and removed the line rmdir($target) and tried it again. This time, all the files within the directory got deleted but the directory itself remained.
So apparently, rmdir DOES indeed remove the directory correctly. But then, why does it give me the error message beforehand that it doesn't exist?
It will work if you append a slash to the directory name.
Explanation: When you initially call the function as delete_files("directory/12"), the parameters passed to the glob() call will look like this:
$files = glob("directory/12*", GLOB_MARK);
Assuming that you have no other files in directory/ with names beginning with 12, this will just return "directory/12/" (with a slash appended because of GLOB_MARK). The function will then recursively call itself with that parameter, resulting in the top-level directory being processed twice.
Of course, if you did happen to have some other file or directory named, say, directory/123, then it would also get deleted, which is presumably not what you want.
To fix this properly, you should make sure your function can properly handle directories even if they get passed in without a trailing slash. The simplest way to do that would be to always append the slash to directory names before globbing them, like this:
$files = glob($target . '/*');
However, note that this could still fail (albeit less destructively) if your directory happened to contain some files not matched by *, such as dotfiles, since they would not get deleted, causing the subsequent rmdir() to fail because the directory will not be empty.
A more robust solution would be to use scandir() instead of glob(), like this:
$files = array_diff( scandir($target), array('.', '..') );
foreach ($files as $file) {
delete_files("$target/$file");
}
(The array_diff() is needed to eliminate the special . and .. directory entries, which would cause the code to recurse forever if they weren't excluded.)
One remaining potential failure mode is that this code will happily follow symlinks to directories and try to delete everything in the directory they point to (and then fail to remove the link itself, because rmdir() can't remove symlinks). To fix this issue, you'll probably want to replace the is_dir($target) test with !is_link($target) && is_dir($target).
All put together, the resulting code would look like this:
function delete_files($target)
{
if(!is_link($target) && is_dir($target))
{
// it's a directory; recursively delete everything in it
$files = array_diff( scandir($target), array('.', '..') );
foreach($files as $file) {
delete_files("$target/$file");
}
rmdir($target);
}
else
{
// probably a normal file or a symlink; either way, just unlink() it
unlink($target);
}
}
delete_files($directory);
Ps. See also How do I recursively delete a directory and its entire contents (files + sub dirs) in PHP?
cause you call it twice the first time it works the second time it gives a error.
I can't prove it, but with recursive code like that it is the problem.
Related
I have a php function that renames two separate image files from a temporary to permanent path after first confirming that the temporary path exists.
When it checks for the fist file it works fine but, for some reason, the second file never passes the if(file_exists()) even though I can confirm with 100% certainty that the file path being checked does, in fact, exist.
The image files have different names but the codes are otherwise structured exactly the same so I can't see why one would work and the other wouldn't.
if(file_exists('temp/'.strtolower($option['image1']))){
$path1 = 'images/'.strtolower($option['image1']); // upload directory
$tmp1 = 'temp/'.strtolower($option['image1']);
if(rename($tmp1, $path1)){
$error = 0;
}else{
$error = 4;
}
}
if(file_exists('temp/'.strtolower($option['image2']))){
$path2 = 'images/'.strtolower($option['image2']); // upload directory
$tmp2 = 'temp/'.strtolower($option['image2']);
if(rename($tmp2, $path2)){
$error = 0;
}else{
$error = 5;
}
}
Is there an issue with calling file_exists() twice? How else can I check for both paths?
Edit
As per Marco-A's suggestion, I added clearstatcache(); between the two if/then blocks and it worked like a charm.
The only two possibilities (if you're absolutely sure the file path exists) I'm seeing are either 1.) a stat cache problem (you can clear the cache with clearstatcache) or 2.) a permission issue. Consider this:
$ touch /tmp/locked/file
$ php is_file_test.php
$ bool(true)
$ chmod -x /tmp/locked
$ php is_file_test.php
$ bool(false)
So it might be, that the parent directory of that file doesn't have the x (executable) permission bit set. This prevents any process from iterating and accessing the directory's content.
The uploaded file names can have uppercase characters. If you use strtolower in the file_exists function, you probably wouldn't be looking for the original file path.
if(file_exists('temp/' . strtolower($option['image']))){
// ...
}
Should be changed to:
if(file_exists('temp/' . $option['image'])){
// ...
}
Strange behaviour, exentially:
(the name of the folder depends on the date - the purpose is a hit counter of the website, broken down by day)
if (!is_dir($folder)) { // first access in the day
mkdir($folder);
}
Well: on the server in internet all works well.
But when i try in local, with the server simulator of Easy PHP, happens that:
(a) The first time, no problem. The folder doesn't exists and it is created.
(b) subsequently, for example to a page refresh, the program flow again goes in the IF (!!!) generating the error (at line of mkdir) of kind: "Warning: mkdir(): No such file or directory in [...]".
All parent part of the directory $folder exists.
Thanks
.
Try using a recursive directory creation function:
function mkdir_r($dirName, $rights = 0777)
{
$dirs = explode(DIRECTORY_SEPARATOR , $dirName);
$dir = '';
if (strpos($dirs[count($dirs) - 1], '.')) {
array_pop($dirs);
}
foreach ($dirs as $part) {
$dir .= $part . DIRECTORY_SEPARATOR ;
if (!is_dir($dir) && strlen($dir) > 0) {
mkdir($dir, $rights);
}
}
}
This way all directories up to the directry you wanted to create are created if they don't exist.
mkdir doesn't work recursively unfortunately.
If anyone faces the issue; Use the native clearstatcache() function after you delete the file.
I'm quoting the interesting part of the original documentation
You should also note that PHP doesn't cache information about non-existent files. So, if you call file_exists() on a file that doesn't exist, it will return false until you create the file. If you create the file, it will return true even if you then delete the file. However unlink() clears the cache automatically.
For further information here is the documentation page: https://www.php.net/manual/en/function.clearstatcache.php
This is probably just my inexperience and there's got to be a way, but it seems everything I'm trying leads me to one road block: I can't get php to 'act' on the sub-directories that don't have the specific file (index.php).
In the following code, it just sees it as true or false, so the result will be the 'else' giving me a list of the 'good folders (with index)+the message, or if I removed the (!), it would delete the good folders. I need it to give me the results of the bad folders (without index) so we can destroy them! haha (the deleteDirectory is a function to allow deletion of non-empty folders). Thank you!
edit: btw, if it is not possible this way, i'm open to work arounds..I've tried to 'rename' the good directories temporarily for the purge which I was having problems with the array, and also to create a 'dummy file' in non index directories, but the end result is the same as far as there being a point it has to differentiate.
$path = glob(DIR_DIR . '*' . '/index.php');
foreach ($path as $path) {
if (!is_file($path)) {
$path = str_replace('/index.php', '', $path);
deleteDirectory($path);
} else {
echo $path . '...all folders contain index.php...<br/>';
}
}
if ($zip->open($zipFile, ZipArchive::CREATE) !== TRUE) {
(...)
} else {
$zip->addFile($tempDir.$fileName.".xls", $fileName.".xls");
// The array contains the directory structure of the files to add
foreach ($list_attachments as $dir_name => $attachment_files) {
if (!empty($attachment_files)) {
$zip->addEmptyDir($dir_name);
foreach ($attachment_files as $attachment) {
$zip->addFile($tempDir.$dir_name."/".$attachment, $dir_name."/".$attachment));
unlink($tempDir.$dir_name."/".$attachment);
}
rmdir($tempDir.$dir_name."/");
}
}
$zip->close();
}
Please don't mind potential typos in the variable names, I rewrote them and the comment in English to make them more readable.
If I run the code as is, it will delete the files and the directories but won't create the archive. I ran checks on return values and addEmptyDir, addFile, unlink and rmdir all work fine. However, it seems that removing the files prevents the archive from closing properly, and thus the file isn't created.
I circumvented it by moving the unlink and rmdir calls after the $zip->close(), so the files are only deleted after the archive is created. However, is forces me to have twice the loops, and from what I've gathered looking at the documentation and zip-related questions here there shouldn't be any issue with unlinking like I did.
Does anyone know for which reason this could happen?
The zip will be finally written to file AFTER you've called $zip->close(). Until this point everything happens in memory, no 'zipping' is done. That's why you can delete the unzipped files only after you've called $zip->close() successfully.
The documentation even says the following:
When a file is set to be added to the archive, PHP will attempt to lock the file and it is only released once the ZIP operation is done. In short, it means you can first delete an added file after the archive is closed.
However, the locks will not prevent you from deleting the files anyway, they are just "hints", the big problem is that the files need to be there for processing on close().
So the inner loop should look like this:
foreach ($attachment_files as $attachment) {
$zip->addFile($tempDir.$dir_name."/".$attachment, $dir_name."/".$attachment));
$to_be_unlinked []= $tempDir.$dir_name."/".$attachment;
}
Later on, unlink the files:
...
foreach($to_be_unlinked as $file) {
unlink($file);
}
I have the following folder structure:
images/photo-gallery/2e/
72/
rk/
u3/
va/
yk/
... and so on. Basically, each time an image is uploaded it hashes the name and then creates a folder with the first two letters. So inside of 2e is 2e0gpw1p.jpg
Here's the thing... if I delete an image, it will delete the file but it will keep the folder that it's in. Now when I have a TON of images uploaded, that will be fine since a lot of images will share the same folder.. but until then, I will end up having a bunch of empty directories.
What I want to do is search through the photo-gallery folder and go through each directory and see which folders are empty.. if there are any empty folders then it will remove it.
I know how to do that for a single directory, like the 2e folder. But how would I do it for all the folders inside the photo-gallery folder?
The PHP function rmdir() will throw a warning if the directory is not empty, so you can use it on non-empty directories without risking deleting them. Combine that with scandir() and array_slice (to remove . and ..), and you can do this:
foreach(array_slice(scandir('images/photo-gallery'),2) as $dir) {
#rmdir('images/photo-gallery/' . $dir); // use # to silence the warning
}
while you could do with with php, i'm inclined to use the os for such a task. Of course you can call the below with php
find <parent-dir> -depth -type d -empty -exec rmdir -v {} \;
PLEASE READ THIS WARNING I DID NOT TEST BUT HAVE USED SIMILAR CODE DOZENS OF TIMES. FAMILURIZE YOURSELF WITH THIS AND DO NOT USE IF YOU DO NOT UNDERSTAND WHAT IT IS DOING THIS COULD POTENTIALLY WIPE YOUR SITE FROM THE SERVER.
EDIT BACKUP EVERYTHING BEFORE TRYING THIS YOUR FIRST TIME THE PATH IS VERY VERY IMPORTANT!
Ok with that said this is quite easy :)
<?php
function recursiveDelete($path){
$ignore = array(
'cgi-bin',
'.',
'..'
); // Directories to ignore
$dh = opendir($path); // Open the directory
while(false !== ($file = readdir($dh))){ // Loop through the directory
if(!in_array($file, $ignore)){ // Check that this file is not to be ignored
if(is_dir($path."/".$file)){ // Its a directory, keep going
if(!iterator_count(new DirectoryIterator($path."/".$file)))
rmdir($path."/".$file); // its empty delete it
} else {
recursiveDelete($path."/".$file);// Recursive call to self
}
}
}
}
closedir($dh); // All Done close the directory
}
// WARNING IMPROPERLY USED YOU CAN DUMP YOUR ENTIRE SERVER USE WITH CAUTION!!!!
// I WILL NOT BE HELD RESPONSIBLE FOR MISUSE
recursiveDelete('/some/directoy/path/to/your/gallery');
?>