How to generate a recursive list of directories and files with hyperlinks to each one?

585 views php
5

I'm at my wit's end here trying to generate a list of all directories and files in PHP. The idea was so that the navigation bar could be dynamically updated simply by adding more files.

An example of what the directory structure might look like is as follows:

  • Directory 1
    • Subdirectory 1
      • Category 1
        • Page 1
        • Page 2
      • Category 2
        • ...
  • Directory 2
    • Subdirectory...
  • Directory 3
    • ...

Each page would be linked to. For example, Directory 1 -> Subdirectory 1 -> Category 1 -> Page 1 would be /directory-1/subdirectory-1/category-1/page-1.

I found a few potential solutions but none that really fitted the bill. I chose to build off of this comment on the PHP website.

This is my code (first function is lifted directly from that linked comment):

function dirToArray($dir) { 
   $result = array(); 

   $cdir = scandir($dir); 
   foreach ($cdir as $key => $value) 
   { 
      if (!in_array($value,array(".",".."))) 
      { 
         if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) 
         { 
            $result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value); 
         } 
         else 
         { 
            $result[] = $value; 
         } 
      } 
   } 

   return $result; 
} 

$contentsArray = dirToArray("contents");

function listStuff($contentsArray, $contentsArrayArray, $parentDirectory) {
    foreach ($contentsArrayArray as $key => $value) {
        if (is_array($value)) {
            if (!empty($GLOBALS["depthWorkaround"])) {
                $parentDirectory = $GLOBALS["depthWorkaround"];
                $GLOBALS["depthWorkaround"] = null;
            }
            $isDirectory = true;
            $directoryName = explode("_", $key)[1];
            $directoryURL = str_replace(" ", "-", strtolower($directoryName));
            echo "<li>???? <a href=\"$parentDirectory/$directoryURL\">$directoryName</a></li>";
            if (!empty($contentsArrayArray[$key])) {
                $parentDirectory = "$parentDirectory/" . $directoryURL;
                echo "<ul>";
                listStuff($contentsArray, $contentsArrayArray[$key], $parentDirectory);
                echo "</ul>";
            }
        } else {
            $isDirectory = false;
            $directoryName = explode("_", $value)[1];
            $directoryURL = str_replace(" ", "-", strtolower($directoryName));
            echo "<li>???? <a href=\"$parentDirectory/$directoryURL\">$directoryName</a></li>";
        }
    }
    $GLOBALS["depthWorkaround"] = explode("/", $parentDirectory)[1];
}
listStuff($contentsArray, $contentsArray, null);

I think it's best I try and explain what is going on here. The dirToArray function lists everything in a directory as a multidimensional array. It's really rather neat. In this case, my chosen directory is "contents".

Next up is listStuff. It does a number of things, mostly turning the array into a list, similar to shown above, and making them all hyperlinks, with the URLs being lowercase and no spaces.

If you are testing this yourself, note that all directories and files must have an underscore in them, or things will go wrong. This is because it's designed to have them listed like "00_File 1", "01_File 2" and so on to keep the items in the intended order. The numbers are then stripped from the resulting output, but I haven't done anything to make it handle the lack of an underscore yet, as it would be nice to get the essential parts working first, and it is only for personal use.

The issue is that I haven't been able to get the resulting URLs quite right. Here's an example of what it produces:

  • Directory: /directory
    • Subdirectory: /directory/subdirectory
      • Subsubdirectory 1: /directory/subdirectory/subsubdirectory-1
        • File: /directory/subdirectory/subsubdirectory/file
      • Subsubdirectory 2: /directory/subsubdirectory-2
        • File: /directory/subsubdirectory/file
      • Subsubdirectory 3: /directory/subsubdirectory-3
        • File: /directory/subsubdirectory-3/file
      • File: /directory/subdirectory/subsubdirectory/file
    • File: /directory/subdirectory/file

As you might be able to see, the URLs are completely out of whack, often pointing to an incorrect directory and one level deeper than they should after going up a level (or multiple levels).

I'm really not

answered question

@Mathias Vind Nielsen That is not really what I'm looking for. I'm after both directories and files, it needs to be recursive (meaning it will list what is inside a directory, and then what is in each subdirectory and so on until it reaches the end of each path) and it must also be possible to set each one as a hyperlink (which in all fairness would be straight forward with the answers on that page due to the lack of recursion).

1 Answer

6

I would rewrite to something like the following:

  • Create the directory structure, with DirectoryIterator
  • Loop over the structured array recursively to create the menu

Ignore ./node_modules/standard it's all I had at hand to test ;p change to suit.

<?php
function get_file_listing($path = '', $depth = 0)
{
    $return = [];
    foreach (new IteratorIterator(new DirectoryIterator($path)) as $item) {
        if ($item->isDot()) {
            continue;
        }
        $info = [
            'text' => $item->getFilename(),
            'href' => str_replace('\\', '/', $item->getPathname())
        ];
        if ($item->isDir()) {
            $nodes = get_file_listing($item->getPathname(), $depth + 1);
            if (!empty($nodes)) {
                $info['nodes'] = $nodes;
            }
        }
        $return[] = $info;
    }
    return $return;
}

function makeNav($item) {
    $return = '<li><a href="'.$item['href'].'">'.$item['text'].'</a>'.PHP_EOL;

    if (isset($item['nodes']) && is_array($item['nodes']) && count($item['nodes']) > 0) {
        $return .= '<ul>'.PHP_EOL;
        foreach ($item['nodes'] as $node) {
            $return .= makeNav($node);
        }
        $return .= '</ul>'.PHP_EOL;
    } else {
        $return .= '</li>'.PHP_EOL;
    }

    if (isset($item['nodes']) && is_array($item['nodes']) && count($item['nodes']) > 0) {
        $return .= "</li>".PHP_EOL;
    }

    return $return;
}

$nav = '<ul>';
foreach (get_file_listing('./node_modules/standard') as $item) {
    $nav .= makeNav($item);
}
echo $nav.'</ul>';

Result

<li>
   <a href="./node_modules/standard">standard</a>
   <ul>
      <li>
         <a href="./node_modules/standard/docs">docs</a>
         <ul>
            <li><a href="./node_modules/standard/docs/RULES-zhtw.md">RULES-zhtw.md</a></li>
            <li><a href="./node_modules/standard/docs/RULES-kokr.md">RULES-kokr.md</a></li>
            <li><a href="./node_modules/standard/docs/README-iteu.md">README-iteu.md</a></li>
            <li><a href="./node_modules/standard/docs/RULES-zhcn.md">RULES-zhcn.md</a></li>
            <li><a href="./node_modules/standard/docs/README-ptbr.md">README-ptbr.md</a></li>
            <li><a href="./node_modules/standard/docs/README-zhtw.md">README-zhtw.md</a></li>
            <li><a href="./node_modules/standard/docs/webstorm.md">webstorm.md</a></li>
            <li><a href="./node_modules/standard/docs/RULES-iteu.md">RULES-iteu.md</a></li>
            <li><a href="./node_modules/standard/docs/RULES-esla.md">RULES-esla.md</a></li>
            <li><a href="./node_modules/standard/docs/README-esla.md">README-esla.md</a></li>
            <li><a href="./node_modules/standard/docs/RULES-ptbr.md">RULES-ptbr.md</a></li>
            <li><a href="./node_modules/standard/docs/README-kokr.md">README-kokr.md</a></li>
            <li><a href="./node_modules/standard/docs/README-zhcn.md">README-zhcn.md</a></li>
         </ul>
      </li>
      <li><a href="./node_modules/standard/SECURITY.md">SECURITY.md</a></li>
      <li><a href="./node_modules/standard/index.js">index.js</a></li>
      <li><a href="./node_modules/standard/LICENSE">LICENSE</a></li>
      <li><a href="./node_modules/standard/RULES.md">RULES.md</a></li>
      <li><a href="./node_modules/standard/eslintrc.json">eslintrc.json</a></li>
      <li><a href="./node_modules/standard/.travis.yml">.travis.yml</a></li>
      <li><a href="./node_modules/standard/AUTHORS.md">AUTHORS.md</a></li>
      <li><a href="./node_modules/standard/package.json">package.json</a></li>
      <li><a href="./node_modules/standard/options.js">options.js</a></li>
      <li><a href="./node_modules/standard/.editorconfig">.editorconfig</a></li>
      <li>
         <a href="./node_modules/standard/bin">bin</a>
         <ul>
            <li><a href="./node_modules/standard/bin/cmd.js">cmd.js</a></li>
         </ul>
      </li>
      <li><a href="./node_modules/standard/README.md">README.md</a></li>
      <li><a href="./node_modules/standard/CHANGELOG.md">CHANGELOG.md</a></li>
   </ul>
</li>

posted this

Have an answer?

JD

Please login first before posting an answer.