public static function listDir(string $dir, bool $recursive = true)
    {
        $contents = array();
        $subdirs = array();
        $reader = dir($dir);
        while ($entry = $reader->read())
        {
            if ($entry === "." || $entry === "..")
                continue;

            $entry = $dir . DIRECTORY_SEPARATOR . $entry;
            if (substr($entry, -4) === ".php")
            {
                $contents[] = $entry;
            }
            elseif (is_dir($entry) && $recursive)
            {
                $subdirs = array_merge($subdirs, self::listDir($entry));
            }
        }

        // Sort the direct contents of the directory so that index.php comes first
        usort($contents, function ($a, $b) {
            $sla = strlen($a);
            $slb = strlen($b);
            
            // index files come first
            $a_idx = substr($a, -10) === "/index.php";
            $b_idx = substr($b, -10) === "/index.php";
            if ($a_idx !== $b_idx)
                return $a_idx ? -1 : 1;

            // sort the rest alphabetically
            return strcasecmp($a, $b);
        });

        // Add the contents of subdirectories to the direct contents
        return array_merge($contents, $subdirs);
    }