Search code examples
phpfilebrowseriteratorfilesystems

PHP how to display folder and its content while also being able to download the content


So I am trying to create a file server,

Basicaly the way it should work is you have lots of folders that contain files and when you click on a file you will download it, if it is a folder you can open it and see the content inside it.

My question is how can I create such system ? I have been trying to use FilesystemIterator but I do not know how to proceed further.

this is my PHP code that I am using, this code is set inside of div element

 <?php 
        $filesInFolder = array();
        $directory = "src";
        $iterator = new FilesystemIterator($directory);

        foreach($iterator as $entry){
            $filesInFolder[] = $entry->getFilename();
        }

        foreach($filesInFolder as $file){
            echo "<a href='/$file'> $file </a>";
        }
  ?>

And this is my file structure file structure

I know that it is possible to create this, I just don't know how to create it.


Solution

  • So here is how you would do it. First you create a FileReader.php file like so ( I have also added Icons and file size, you will need to create icons folders where all your icons will be located)

    <?php
    /**
     * File reader, reads directory and outputs it in an array
     */
    class FileReader {
    
        public function __construct(
            public string $root
        ) {}
        
        /**
         * @param string $path 
         */
        public function removeRootFromPath( $path) {
            $path = preg_replace('/' . preg_quote($this->root, '/') . '/', '', $path);
            $path = $this->cleanPath($path);
            return DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
        }
    
        /**
         * @param string $path
         */
        public function addRootToPath( $path) {
            $path = $this->removeRootFromPath($path);
            $path = ltrim($path, DIRECTORY_SEPARATOR);
            $root = rtrim($this->root, DIRECTORY_SEPARATOR);
            return $root . DIRECTORY_SEPARATOR . $path;
        }
    
        /**
         * @param string $dir Directory to load
         */
        public function cleanPath( $dir) {
            $sep = preg_quote(DIRECTORY_SEPARATOR, '/');
            return preg_replace('/\.\.' . $sep . '|\.' . $sep . '/', '', $dir);
        }
    
        /**
         * @param string $dir Directory to load
         * @return FilesystemIterator|null
         */
        public function readDirectory( $dir) {
            $dir = $this->addRootToPath($dir);
    
            try {
                return new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
            } catch (UnexpectedValueException $exception) {
                return null;
            }
        }
    
        /** 
        * @param string $size File size in bytes
        * @param int $precision File size conversion precision
        * @return string round($size, $precision).$units[$i] 
        */
    
        public function humanFilesize($size, $precision = 1) {
            $units = array(' B',' kB',' MB',' GB',' TB',' PB',' EB',' ZB',' YB');
            $step = 1024;
            $i = 0;
            while (($size / $step) > 0.9) {
                $size = $size / $step;
                $i++;
            }
            return round($size, $precision).$units[$i];
        }
    
        /** 
        * @param string $file File to load
        * @return string $type <- File type
        * @return string $image <- File image
        */
        public function returnFileExtensionAndImage($file) {
    
            $val = strtolower($file);
            switch($val) {
            case "avi":
                $type = "Video file";
                $image = "avi.png";
                break;
            case "bat":
                $type = "Batch file";
                $image = "bat.png";
                break;
            case "css":
                $type = "Cascading Style Sheet";
                $image = "txt.png";
                break;
            case "exe":
                $type = "Executable file";
                $image = "exe.png";
                break;
            case "fla":
                $type = "Flash file";
                $image = "fla.png";
                break;
            case "gif":
                $type = "GIF Image";
                $image = "gif.png";
                break;
            case "html":
                $type = "HTML file";
                $image = "html.png";
                break;
            case "htm":
                $type = "HTM file";
                $image = "html.png";
                break;
            case "jpg":
                $type = "JPEG Image";
                $image = "jpg.png";
                break;
            case "mp3":
                $type = "Music File";
                $image = "mp3.png";
                break;
            case "msg":
                $type = "Email message";
                $image = "msg.png";
                break;
            case "pdf":
                $type = "PDF file";
                $image = "pdf.png";
                break;
            case "psd":
                $type = "Photoshop file";
                $image = "psd.png";
                break;
            case "php":
                $type = "PHP file";
                $image = "php.png";
                break;
            case "ppt":
                $type = "PowerPoint presentation";
                $image = "ppt.png";
                break;
            case "pptx":
                $type = "PowerPoint presentation";
                $image = "ppt.png";
                break;
            case "swf":
                $type = "SWF Flash file";
                $image = "swf.png";
                break;
            case "txt":
                $type = "Text file";
                $image = "txt.png";
                break;
            case "csv":
                $type = "CSV file";
                $image = "txt.png";
                break;
    
            case "wma":
                $type = "Windows Media Audio";
                $image = "wma.png";
                break;
            case "xls":
                $type = "Excel file";
                $image = "xls.jpg";
                break;
            case "xlsx":
                $type = "Excel file";
                $image = "xls.jpg";
                break;
            case "zip":
                $type = "Zip file";
                $image = "zip.png";
                break;
            case "7zip":
                $type = "Zip file";
                $image = "zip.png";
                break;
            case "zip":
                $type = "Zip file";
                $image = "zip.png";
            case "7z":
                $type = "7Zip file";
                $image = "rar.png";
                break;
            case "doc":
                $type = "Word document";
                $image = "doc.png";
                break;
            case "docx":
                $type = "Word document";
                $image = "doc.png";
                break;
            case "docs":
                $type = "Word document";
                $image = "doc.png";
            case "rar":
                $type = "Rar file";
                $image = "rar.png";
            //--- New Here---//
            default:
                $type = "Unknown file";
                $image = "unknown.jpg";
            }
            return $type . "?" . $image;
        }
    }
    ?>
    

    Then you Create FileTable.php in which all of your files and folders will be displayed and you will be able to access them and download selected files (Only files not folders) + (I have added simple filtering)

        <?php 
    $cleanPath = $target;
    
    /**
     * CURRENT DIRECTORY LOCATION DISPLAY FIX
     */
    switch (strlen($cleanPath)) {
        case 1:
            $cleanPath[0] = " ";
            break;
    
        case 2:
            if($cleanPath[0] == "\\" && $cleanPath[1] == "/"){
                $cleanPath[0] = " ";
                $cleanPath[1] = " ";
                $cleanPath[2] = " ";
                break;
            }else {
                $cleanPath[0] = " ";
                break;
            }
        default:
            $cleanPath[0] = " ";
            break;
    }
    
    /**
     * HERE WRITE ALL FILES YOU WANT FileReader TO IGNORE
     * - WRITE file + its extension
     * - FOR DIRECTORY WRITE THE NAME OF THE DIRECTORY TO IGNORE
     */
    $filesToIgnore = [
        'index.php',
        'index.css',
        'index.html',
        'Restricted',
        'PUT YOUR FILES HERE.txt'
    ];
    ?>
    
    <?php if(strlen($target) != 0): ?>
        <p class="CurrentFolderLocation"><?= $cleanPath; ?></p>
    <?php else: ?>
        <p class="CurrentFolderLocation"></p>
    <?php endif ?>
    
    <table class="FileTable">
        <div class="InputArea">
            <input type="text" id="filterInput" onkeyup="filterTable()" placeholder="Name of the file to search for...">
        </div>
        <thead class="TableHead">
            <tr>
                <th>
                    <a class="ReturnImg"  href="?path=<?= urlencode($reader->removeRootFromPath(dirname($target))); ?>">
                        <img src= "../Components/icons/levelup.png" alt="level up"/>
                    </a> 
                </th>
                <th>File name</th>
                <th>File size</th>
                <th>File type</th>
                <th>Last file modification date</th>
            </tr>
            
        </thead>
        <tbody id="tableBody" class="TableBody">            
            <?php if ($results = $reader->readDirectory($target)): ?>
                <?php foreach($results as $result): ?>
                    <?php
                        $currentFileToCheck = explode("\\",$result);
                        $currentFileToCheck = $currentFileToCheck[array_key_last($currentFileToCheck)];
                    ?>
                    <?php if(!in_array($currentFileToCheck,$filesToIgnore)): ?>
                        <tr>
                            <?php
    
                            // Make the full path user friendly by removing the root directory.
                            $user_friendly = $reader->removeRootFromPath($result->getFileInfo());
    
                            //File information
                            $fileName = pathinfo($result,PATHINFO_BASENAME);
                            $fileInfo = explode("?",($reader->returnFileExtensionAndImage(pathinfo($result,PATHINFO_EXTENSION))),2);
                            $fileExtension = $fileInfo[0];
                            $fileIcon = $iconsPath . $fileInfo[1];
                            $fileDateModified = explode(" ",date("F d.Y - H:i:s",filemtime($result)),4);
                            $fileDateModified = implode(" ",$fileDateModified);
    
                            $type = $result->getType();
                                if($type !== 'dir'){
                                    $fileSize = $reader->humanFilesize(filesize($result));
                                }
                            ?>
        
                            <?php if($type === 'dir'): ?>
                                <td><img class="FileImage" src="../Components/icons/folder.jpg"></td>
                                <td>
                                    <a href="?path=<?= urlencode($user_friendly); ?>"><?= $fileName; ?></a>
                                </td>
                                <td></td>
                                <td></td>
                                <td></td>
                            <?php else: ?>  
                                <td><img class="FileImage" src=<?= $fileIcon; ?> alt="Ikonka souboru"></td>
    
                                <?php if(pathinfo($result,PATHINFO_EXTENSION) == "pdf"): ?>
                                    <td><a target="_blank" href="./<?= $user_friendly; ?>"><?= $fileName; ?></a></td>
                                <?php else: ?>
                                    <td><a download href="./<?= $directoryToScan . "/" . $user_friendly; ?>"><?= $fileName; ?></a></td>
                                <?php endif ?>
    
                                <td><?= $fileSize; ?></td>
                                <td><?= $fileExtension; ?></td>
                                <td><?= $fileDateModified; ?></td>
    
                            <?php endif ?>
                        </tr>
                    <?php endif ?>
                <?php endforeach ?>
            <?php else: ?>
                <tr>
                    <td></td>
                    <td>Directory/File doesn't exist</td>
                    <td></td>
                    <td></td>
                    <td></td>
                </tr>
            <?php endif ?>
        </tbody>
    </table>
    
    <script>
        function filterTable() {
        // Declare variables
        let input = document.getElementById("filterInput"),
            filter = input.value.toUpperCase(),
            table = document.getElementById("tableBody"),
            tr = table.getElementsByTagName("tr"),
            td, 
            i, 
            txtValue;
    
    
        // Loop through all table rows, and hide those who don't match the search query
        for (i = 0; i < tr.length; i++) {
            td = tr[i].getElementsByTagName("td")[1];
                if (td) {
                    txtValue = td.textContent || td.innerText;
                    if (txtValue.toUpperCase().indexOf(filter) > -1) {
                        tr[i].style.display = "";
                    } else {
                        tr[i].style.display = "none";
                    }
                }
            }
        }
    </script>
    

    And at last you will create index.php where you will connect this together

        <?php
    
    $path = $_SERVER['DOCUMENT_ROOT']; // Get Root path of your file system
    
    $componentPath = $path . "/Components/";
    $componentFileReader = $componentPath . "FileReader.php";
    $componentFileTable = $componentPath . "FileTable.php";
    
    $iconsPath = "/Components/icons/";
    $cssStyle = "./Components/index.css";
    
    include($componentFileReader);
    $directoryToScan = 'src'; // Relative to current file. Change to your path!
    $reader = new FileReader(__DIR__ . DIRECTORY_SEPARATOR . $directoryToScan);
    $target = $reader->removeRootFromPath(!empty($_GET['path']) ? $_GET['path'] : '/');
    
    ?>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        
        <link rel="stylesheet" href=<?=$cssStyle; ?>> 
        <title>File browser</title>
        
    </head>
    <body>
    
        <main class="PageBody flex-column">
            <?php include($componentFileTable) ?>
        </main>
    
    </body>
    </html>
    

    Should you need it -> CODE HERE <- Here is my code on Github with some CSS styling and some icons -> Simply download it and put it in your htdocs folder if you are using Apache