Php function to delete all files and subfolders except one nominated subfolder

Hi
I have a function to delete a folder and all contents including subfolders and files. This works ok, here is code :-

function delete_folder_and_contents($dir) {
	if (is_dir($dir)) {
		$objects = scandir($dir);
		foreach ($objects as $object) {
		if ($object != "." && $object != "..") {
			if (filetype($dir."/".$object) == "dir") 
			delete_folder_and_contents($dir."/".$object); 
			else unlink   ($dir."/".$object);
		}
		}
		reset($objects);
		if (! rmdir($dir)){
			echo '<div class = "warning">Could not delete existing content, please ensure no other programs or 
			utilities are accessing the folder or contents - <strong>' . $dir . '</strong></div><br>';
		}
	}
 }

However I really want to use this function in 3 ways -

1 - Delete folder and contents (including subfolders and files)
2 - Just delete contents (subfolders and files but leave original folder)
3 - either of the above but ignore (do not delete ONE designated subfolder)

I have been trying and struggling for hours now to amend this function to accept three parameters instead of one to do the following:-

  • first parameter - is the folder name (this works ok) and deletes folder and all subfolders and files
  • second parameter - delete root/parent folder yes/no - by this I mean option to delete designated folder and contents or just contents (subfolders and files but leave the root/designated folder)
  • third parameter - subfolder to ignore (including subfolder contents)

For example -

 delete_folder_and_contents($root, $delete_root, $ignore )

so :-

 delete_folder_and_contents('main', 'yes', '' )

would delete the folder main and all contents

 delete_folder_and_contents('main', 'no', '')

would delete the contents of the folder ‘main’ but keep the empty folder

 delete_folder_and_contents('main', 'no', 'child')

would delete the folder contents, keep the empty folder and the subfolder ‘child’ and subfolder contents.

Basically I need to be able to delete entire folder or just delete contents of folder or just delete folder contents and skip/ignore 1 subfolder.

I dont actually mind if I have to have 3 different functions, but all in one would be great.

Any advice or ideas eagerly accepted, even advice on original function structure.

Thanks guys

1 Like

Show the code you’ve tried, and describe what was wrong with it please.

Option 1 seems to be what you have now. Option 2 is just a case of checking to see if the parameter $delete_root matches a positive value (I’d use a boolean true/false rather than a text string) and only doing the rmdir() line if it does. Option 3 presumably just means comparing $object inside the loop to see if it is the subfolder to ignore, in which case an if() clause inside the loop may do it. Of course, if this option is set and a matching folder exists, then $delete_root must be false.

It will get more complex if you want to specify a folder to ignore which is somewhere deep down the tree.

1 Like

Is it? You basically want to avoid calling rmdir on the original call, not when the function has called itself (recursion).

This sounds easy, but it’s very much underspecified. What if there is a sub-sub-sub directory with that name, should that be excluded too?

2 Likes

Hi guys and thanks for feed back. The reason I did not include code for what I have tried is because it is frankly an embarassing array of attempts that solve one problem and create another error.

Yes absolutely, my intention was once it works then amend code to enforce this.

This is one of the main problems I am hitting. I am having problems understanding / coding so that rmdir only occurs when required.

For my application I will not want to apply ctiteria to sub sub folders etc. I have one main folder that contains my script in a sub folder (plus other folders / files). I want to delete all files in the main folder EXCEPT my folder and any files/ folders in it. So basically kill my parent (optional) and siblings but leave me and my kids alone :grinning:

Everything I try leaves a folder, deletes the wrong folders, doesn’t delete or tries to delete a folder that still has content.

I guess I’ll keep trying and post the errors and try and just fix each error as it arises. Sorry guys for not making it easier to assist. I need to do more work myself. Then maybe you can help me fix any errors I cant fix on my own - cheers

That’s what I was getting at here:

Yes, good point, I hadn’t thought of that.

Sorry for being unclear, for my purposes it would just be one subfolder of the main, but that then adds another level of complexity because it should ONLY ignore the first subfolder but should still delete sub sub sub folders if they appear in the main folder and happen to have the same name - It is obvious I need to think about it more and be more clear to make it possible for anyone to assist

This is the first thing I am still trying to achieve and to be honest my brain is bursting - how can I do exactly this. I want to avoid rmdir on the initial call but since the function is recursive, every call is the initial one as far as the function is concerned when it calls itself. I cannot even use the $dir value because it changes on every recursion.

I can’t see how to store the original $dir entered when I first call the function without overwriting it on each recursion. Then I could ignore rmdir based on folder name.

A quick-and-dirty way would be a fourth parameter which you set as “false” when you call it from your code, and “true” when you call it from within the function, so you can see from that whether it’s a recursive call or not. Will be interesting to see if there’s a better way.

1 Like

@droopsnoot - thats actually a pretty cool idea and gives me some other thoughts as well. Let me experiment - thanks dude !

class ExcludedDirs
{
    private $dirs = [];

    public function __construct(string $root, array $dirs)
    {
        foreach ($dirs as $dir) {
            $this->dirs[] = $root . '/' . $dir;
        }
    }

    public function contains(string $dir)
    {
        return in_array($dir, $this->dirs);
    }
}

function delete_folder_and_contents($dir, ExcludedDirs $excludedDirs = null)
{
    ...
    if (filetype($dir . '/' . $object) == "dir") {
        if (!($excludedDirs instanceof ExcludedDirs) 
                || !$excludedDirs->contains($dir . '/' . $object)) {
            delete_folder_and_contents( $dir . '/' . $object, $excludedDirs);
        }
    }
    ...
}

OK, finally got a solution that works for me - I post it for comment, advice and possibly to help others. I am sure it can be improved.

It is strange because there are multiple search results to delete a folder together with files and subfolders but I could not find any search results to delete the complete contents of a folder but keep the main folder unless you delete everything and then re-create the main folder. This would not normally be much of a problem, unless as in my case you want to keep one subfolder and its contents.

Maybe this could be expanded as an array so you could delete a folders contents but keep several nominated folders - may be useful - or maybe I’m just weird!

Anyway here is my solution that works for me, in this case, just for testing I am using a Wordpress install and deleting all files and subfolders and their contents in the wp-admin folder, except the css subfolder and all its contents.

The second and third arguements for the function are what I want to keep, namely the main folder name and the subfolder name. The second argument is the same as the first but this means the first argument can change recursively but I can use the 2nd and 3rd arguments each time the function calls itself and maintain the values through all recusrions. In this way, also, the rmdir() is not executed on the main folder or the nominated subfolder.

The calling code :-

delete_contents('../wp-admin', '../wp-admin', '../wp-admin/css');

The function :-

// Recursively delete folder and contents but keep main folder 
// and one selected subfolder
function delete_contents($dir, $main, $keep) {	
	if (is_dir($dir)) {	
		if ($dir == $keep) {
			// do nothing;
		}
		else {
		$objects = scandir($dir);
		foreach ($objects as $object) {
		if ($object != "." && $object != "..") {
			if (filetype($dir."/".$object) == "dir") 
			delete_contents($dir."/".$object, $main, $keep); 			
			else unlink   ($dir."/".$object);
		}
		}
		reset($objects);
		if ($dir != $main && $dir != $keep) {
			if (! rmdir($dir)){
				echo '<div class = "warning">Could not delete existing content, please ensure no other programs or 
				utilities are accessing the folder or contents - <strong>' . $dir . '</strong></div><br>';
			}
		}
	}
	}
 }

Hi, sorry, you must have been posting same time as me - I didn’t see yours until I posted mine. Thanks, I’ll check it out

I have amended my code slightly and replaced / with PHPs DIRECRORY_SEPARATOR since whilst both linux and windows will resolve paths with / ok it does cause problems in matching because obviously / is not the same as \ when comparing characters