Search code examples
phpselectdelete-filesubdirectorychdir

Display and delete files from subfolders based on select value


My folder structure has 4 layers with my form in the top layer, currently it displays the files in the top layer only, I want to be able to select a subfolder and display the files in it so they can be deleted if necessary.

Produce
Produce/Meat
Produce/Meat/Beef
Produce/Meat/Beef/Portions
Produce/Meat/Beef/Packaged
Produce/Vegtables
Produce/Vegetables/Fresh
Produce/Vegetables/Fresh/Local etc,.

My form displays the contents of the folder it is in with checkboxes, I can then tick boxes and delete files, but I have added a select and want to be able to display the contents of the selected subfolder and delete files. I made two submit buttons and both work, but the delete feature only works if it's in the top folder.

 if ($_POST['delete'] == 'Submit')
    {
    foreach ((array) $_POST['select'] as $file) {

    if(file_exists($file)) {
        unlink($file); 
    }
    elseif(is_dir($file)) {
        rmdir($file);
    }
}
}

$files = array();
$dir = opendir('.');
    while(false != ($file = readdir($dir))) {
        if(($file != ".") and ($file != "..")and ($file != "error_log")) {
                $files[] = $file; 
        }   
    }

if ($_POST['action'] == 'Change') {

if($_POST['folder'] == 'AAA'){
$files = array();
$dir = opendir('/home/mysite/public_html/Produce/Vegetables/');
    while(false != ($file = readdir($dir))) {
        if(($file != ".") and ($file != "..")) {
                $files[] = $file; 
        }   
    }
}
if($_POST['folder'] == 'BBB'){
$files = array();
$dir = opendir('/home/mysite/public_html/Produce/Meat');
    while(false != ($file = readdir($dir))) {
        if(($file != ".") and ($file != "..")) {
                $files[] = $file; 
        }   
    }
}
}
    natcasesort($files);
?>

<form id="delete" action="" method="POST">

<?php
echo '<table><tr>'; 
for($i=0; $i<count($files); $i++) { 
    if ($i%5 == 0) { 
        echo '</tr>';
        echo '<tr>'; 
    }       
    echo '<td style="width:180px">
            <div class="select-all-col"><input name="select[]" type="checkbox" class="select" value="'.$files[$i].'"/>
            '.$files[$i].'</div>
            <br />
        </td>';


 }
    echo '</table>';
    ?>
    </table>
    <br>
    Choose a folder:
            <select name="folder"><option value="this" selected>This folder</option><option value="BBB">Meat</option><option value="CCC">Meat/Beef</option><option value="DDD">Meat/Beef/Portions</option><option value="EEE">Meat/Beef/Packaged</option><option value="FFF">Vegetables</option><option value="GGG">Vegetables/Fresh</option><option value="HHH">Vegetables/Fresh/Local</option><option value="III">Vegetables/Fresh/Packaged</option></select>
            <br>
<input class="button" type="submit" form="delete" name="action" value="Change"><br>
    <button type="submit" form="delete" value="Submit">Delete File/s</button>
    </form><br>

How can I utilise the selected value to accomplish this?


Solution

  • First, I'd like to address why you are unable to delete files outside of the top folder. You never change the "current working directory" so calling the deleting functions on deep files will never work as intended and could delete the files in the top folder. To correct this, you will either need to include the path with each file/directory to be deleted or call chdir() once so that unlink() and rmdir() are looking in the right place.

    I believe your project still has some natural maturing to do including security and UX. I'll provide a generalized/simple snippet for you to consider/compare against your project to hopefully give you a bit more traction in your development.


    Your users will be able to make one of two choices on submission: Change Directory & Remove Files/Directories

    For the directory change, your program will need to submit two necessary pieces of information:

    • The action (action="change")
    • The new folder (newfolder={variable})

    For the file/directory deletion, there will be three necessary pieces of information:

    • The action (action="delete")
    • The files/directory (files[]={variable})
    • The directory to access (folder={variable}) * the value in the <select> cannot be trusted, because a user could change the selected value before selecting files in the current directory for deletion. This value must be statically preserved.
      *Note, you could just add the paths to the filenames in the checkbox values and eliminate the hidden input -- this will be a matter of programming preference.

    Purely for demonstration purposes, I'll reference this static array of folders in my code:

    $valid_folders=[
        'Produce',
        'Produce/Meat',
        'Produce/Meat/Beef',
        'Produce/Meat/Beef/Portions',
        'Produce/Meat/Beef/Packaged',
        'Produce/Vegetables',
        'Produce/Vegetables/Fresh',
        'Produce/Vegetables/Fresh/Local',
        'Produce/Vegetables/Fresh/Packaged'
    ];
    

    In reality, you'll probably want to generate an array of valid/permitted/existing folders. I might recommend that you have a look at this link: List all the files and folders in a Directory with PHP recursive function

    if(isset($_POST['action'])){                               // if there is a submission
        if($_POST['action']=="Delete"){                        // if delete clicked
            if(in_array($_POST['folder'],$valid_folders)){
                $folder=$_POST['folder'];                      // use valid directory
            }else{
                $folder=$valid_folders[0];                     // set a default directory
            }
            chdir($folder);                                    // set current working directory
            //echo "<div>",getcwd(),"</div>";                  // confirm directory is correct
            foreach($_POST['files'] as $file){                 // loop through all files submitted
                if(is_dir($file)){                             // check if a directory
                    rmdir($file);                              // delete it
                }else{                                         // or a file
                    unlink($file);                             // delete it
                }
            }
        }elseif($_POST['action']=="Change"){                   // if change clicked
            if(in_array($_POST['newfolder'],$valid_folders)){  // use valid new directory
                $folder=$_POST['newfolder'];
            }else{
                //echo "Sorry, invalid folder submitted";
                $folder=$valid_folders[0];                     // set a default directory
            }
        }
    }else{
        $folder=$valid_folders[0];                             // no submission, set a default directory
    }
    
    $dir = opendir("/{$folder}");                              // set this to whatever you need it to be -- considering parent directories
    //echo "Accessing: /$folder<br>";
    while(false!=($file=readdir($dir))){
        if(!in_array($file,['.','..','error_log'])){           // deny dots and error_log; you should also consider preventing the deletion of THIS file as well!  Alternatively, you could skip this iterated condition and filter the $files array after the loop is finished.
                $files[] = $file; 
        }   
    }
    
    natcasesort($files);
    
    echo "<form action=\"\" method=\"POST\">";
        echo "<select name=\"newfolder\">";
            //echo "<option value=\"\">Select a folder</option>";  // this isn't necessary if the neighboring button is descriptive
            foreach($valid_folders as $f){
                echo "<option",($folder==$f?" selected":""),">{$f}</option>";  // if a previously submitted directory, show it as selected
            }
        echo "</select> ";
        echo "<button name=\"action\" value=\"Change\">Change To Selected Folder</button>";
        echo "<br><br>";
        echo "Delete one or more files:";
        echo "<table><tr>"; 
            for($i=0,$count=sizeof($files); $i<$count; ++$i){ 
                if($i!=0 && $i%5==0){  // see the reason for this change @ https://stackoverflow.com/questions/43565075/new-containing-div-after-every-3-records/43566227#43566227
                    echo "</tr><tr>"; 
                }       
                echo "<td style=\"width:180px;\">";
                    echo "<div><input name=\"files[]\" type=\"checkbox\" value=\"{$files[$i]}\">{$files[$i]}</div>";
                echo "</td>";
            }
        echo "</tr></table>";
        echo "<input type=\"hidden\" name=\"folder\" value=\"{$folder}\">";  // retain current directory
        echo "<button name=\"action\" value=\"Delete\">Delete Checked File(s)</button>";
    echo "</form>";
    

    As for form structure, you could implement <input type="submit"> or <button> to submit the form. I won't discuss the caveats for this question.

    You see, in the form, $folder is a value that invisibly passed with the submission. This stops the user from moving to an unintended directory when deleting files.

    When action=Delete then $folder and $files are used for processing.
    When action=Change only newfolder is used for processing.
    When there is no action a default folder is declared and files will be listed.