紅穆ノート
レコメンド 言語ノート PHPを使用してウェブサイトのディレクトリ構造を出力 – ディレクトリツリーブラウザ – 指定されたフォルダのツリー構造を安全に表示します。
言語ノート PHP

PHPを使用してウェブサイトのディレクトリ構造を出力 – ディレクトリツリーブラウザ – 指定されたフォルダのツリー構造を安全に表示します。

PHPを使用してウェブサイトのディレクトリ構造を出力 – ディレクトリツリーブラウザ – 指定されたフォルダのツリー構造を安全に表示します。

目次ツリーブラウザ – 指定されたフォルダのツリーモデルを安全に表示します

20260320154932124-image

 

使用方法:
1. 本ファイルをウェブサイトのルートディレクトリに配置し、「tree.php」またはその他の名前を付けてください。
2. 下記の `TARGET_BASE` 定数を変更し、閲覧対象のルートフォルダ(ウェブサイトのルートディレクトリに対する相対パス)を設定してください。
3. ブラウザでアクセス:http://あなたのウェブサイト/tree.php
4. オプションパラメータ:`?dir=サブフォルダのパス` —— サブディレクトリを表示します。例:`?dir=images/icons`

セキュリティ特性:
- すべてのパスは `realpath` によって検証されており、ディレクトリ越境攻撃を防止しています。
- TARGET_BASEディレクトリおよびそのサブディレクトリへのアクセスのみ許可されます。
- 出力時には htmlspecialchars を用いてエンコードし、XSS攻撃を防ぐ。

<?php
/**
 * 目录树浏览器(文本风格)- 使用树形连线符号代替缩进空格
 * 
 * 使用方法:
 * 1. 将本文件放置于网站根目录,命名为 tree.php 或其他名称
 * 2. 修改下方的 TARGET_BASE 常量,设置您要浏览的根文件夹(相对于网站根目录)
 * 3. 通过浏览器访问:http://您的网站/tree.php
 * 4. 可选参数 ?dir=子文件夹路径 来浏览子目录,例如:?dir=images/icons
 * 
 * 安全特性:
 * - 所有路径均经过 realpath 验证,防止目录遍历攻击
 * - 只允许访问 TARGET_BASE 目录及其子目录
 * - 输出使用 htmlspecialchars 转义,防止 XSS 攻击
 */

// ---------- 配置区域 ----------
// 定义要浏览的根文件夹(相对于网站根目录 $_SERVER['DOCUMENT_ROOT'])
// 例如:'storage' 表示浏览 /网站根目录/storage
define('TARGET_BASE', '');  // 请修改为您需要显示的文件夹名称

// 是否显示隐藏文件(以点开头的文件/文件夹)
define('SHOW_HIDDEN', false);

// 图标定义(仍保留,使输出更友好)
define('ICON_FOLDER', '');
define('ICON_FILE', '');
// -----------------------------

// 设置时区与编码
date_default_timezone_set('Asia/Shanghai');
header('Content-Type: text/html; charset=utf-8');

// 基础路径:网站根目录下的目标文件夹
$basePath = rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . TARGET_BASE;
$baseReal = realpath($basePath);

// 检查基目录是否存在且可读
if ($baseReal === false) {
    die("<h2>错误:基目录不存在或不可读</h2><p>请检查配置:<code>" . htmlspecialchars($basePath) . "</code></p>");
}

// 获取请求的子目录参数,并清理
$requestDir = isset($_GET['dir']) ? trim($_GET['dir'], '/\\ ') : '';
$requestDir = str_replace(['../', '..\\'], '', $requestDir); // 额外过滤,但主要依赖 realpath

// 构建完整目标路径
if ($requestDir !== '') {
    // 替换目录分隔符为系统分隔符
    $requestDir = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $requestDir);
    $targetPath = $baseReal . DIRECTORY_SEPARATOR . $requestDir;
} else {
    $targetPath = $baseReal;
}

// 获取目标路径的真实路径(必须存在)
$targetReal = realpath($targetPath);
if ($targetReal === false) {
    die("<h2>错误:请求的路径不存在或无法访问</h2><p>路径:" . htmlspecialchars($targetPath) . "</p>");
}

// 安全检查:确保目标路径在基目录之下
$baseRealWithSep = $baseReal . DIRECTORY_SEPARATOR;
if (strpos($targetReal . DIRECTORY_SEPARATOR, $baseRealWithSep) !== 0 && $targetReal !== $baseReal) {
    die("<h2>错误:无权访问指定目录</h2>");
}

// 获取当前目录相对于基目录的路径(用于URL)
$relativePath = ($targetReal === $baseReal) ? '' : substr($targetReal, strlen($baseReal) + 1);
$relativePathForUrl = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath);

/**
 * 递归生成文本风格的目录树(带连线符号)
 *
 * @param string $path      绝对路径
 * @param string $prefix    前缀字符串(用于递归构建连线)
 * @return array            每行文本组成的数组
 */
function buildTreeLines($path, $prefix = '') {
    $lines = [];
    $items = @scandir($path);
    if ($items === false) {
        return ['<无法读取目录>'];
    }

    // 过滤掉 . 和 ..
    $items = array_diff($items, ['.', '..']);
    
    // 如果不显示隐藏文件,过滤以点开头的项目
    if (!SHOW_HIDDEN) {
        $items = array_filter($items, function($item) {
            return $item[0] !== '.';
        });
    }

    // 自然排序:文件夹在前,文件在后,各自按名称排序
    $folders = [];
    $files   = [];
    foreach ($items as $item) {
        $fullPath = $path . DIRECTORY_SEPARATOR . $item;
        if (is_dir($fullPath)) {
            $folders[] = $item;
        } else {
            $files[] = $item;
        }
    }
    natcasesort($folders);
    natcasesort($files);
    $sortedItems = array_merge($folders, $files);

    $count = count($sortedItems);
    $i = 0;
    foreach ($sortedItems as $item) {
        $i++;
        $isLast = ($i === $count);
        $fullPath = $path . DIRECTORY_SEPARATOR . $item;
        $safeName = htmlspecialchars($item);
        
        // 选择连接符
        $connector = $isLast ? '└── ' : '├── ';
        
        if (is_dir($fullPath)) {
            // 文件夹行
            $lines[] = $prefix . $connector . ICON_FOLDER . ' ' . $safeName;
            // 递归子文件夹,生成新的前缀
            $subPrefix = $prefix . ($isLast ? '    ' : '│   ');
            $subLines = buildTreeLines($fullPath, $subPrefix);
            $lines = array_merge($lines, $subLines);
        } else {
            // 文件行:附加文件大小
            $size = filesize($fullPath);
            $sizeFormatted = formatBytes($size);
            $lines[] = $prefix . $connector . ICON_FILE . ' ' . $safeName . ' (' . $sizeFormatted . ')';
        }
    }
    return $lines;
}

/**
 * 格式化字节数
 */
function formatBytes($bytes, $precision = 2) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= (1 << (10 * $pow));
    return round($bytes, $precision) . ' ' . $units[$pow];
}

/**
 * 获取父目录的相对路径(用于返回上一级)
 */
function getParentRelative($currentRelative) {
    if (empty($currentRelative)) {
        return null; // 已经在根目录,无上级
    }
    $parts = explode(DIRECTORY_SEPARATOR, $currentRelative);
    array_pop($parts);
    return implode(DIRECTORY_SEPARATOR, $parts);
}

// 生成当前目录的文本树行数组
$treeLines = buildTreeLines($targetReal);
$treeText = implode("\n", $treeLines);

// 获取当前目录名称(用于显示)
$currentDisplayName = ($targetReal === $baseReal) ? TARGET_BASE . ' (根)' : basename($targetReal);
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>目录树浏览器(文本风格) - <?php echo htmlspecialchars($currentDisplayName); ?></title>
    <style>
        * {
            box-sizing: border-box;
        }
        body {
            font-family: 'Segoe UI', 'SF Pro Text', 'Helvetica Neue', sans-serif;
            background: #f8f9fa;
            margin: 0;
            padding: 20px;
            color: #2c3e50;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
            overflow: hidden;
            padding: 25px 30px;
        }
        h1 {
            margin-top: 0;
            font-size: 1.8rem;
            font-weight: 400;
            border-bottom: 1px solid #e9ecef;
            padding-bottom: 15px;
            color: #1e3c5c;
        }
        h1 small {
            font-size: 0.9rem;
            font-weight: 300;
            color: #6c757d;
            margin-left: 10px;
        }
        .path-bar {
            background: #e9ecef;
            border-radius: 30px;
            padding: 10px 20px;
            margin-bottom: 25px;
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
            font-size: 0.95rem;
        }
        .path-label {
            font-weight: 600;
            color: #495057;
            margin-right: 5px;
        }
        .path-links {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 5px;
        }
        .path-links a {
            color: #0d6efd;
            text-decoration: none;
            padding: 2px 8px;
            border-radius: 20px;
            background: white;
            border: 1px solid #dee2e6;
            transition: 0.2s;
        }
        .path-links a:hover {
            background: #0d6efd;
            color: white;
            border-color: #0d6efd;
        }
        .path-links span.sep {
            color: #adb5bd;
            margin: 0 2px;
        }
        .nav-form {
            margin-left: auto;
            display: flex;
            gap: 8px;
        }
        .nav-form input {
            padding: 8px 15px;
            border-radius: 30px;
            border: 1px solid #ced4da;
            width: 250px;
            font-size: 0.9rem;
        }
        .nav-form button {
            background: #0d6efd;
            color: white;
            border: none;
            border-radius: 30px;
            padding: 8px 20px;
            cursor: pointer;
            font-weight: 500;
            transition: 0.2s;
        }
        .nav-form button:hover {
            background: #0b5ed7;
        }
        /* 文本树样式 */
        .tree-container {
            background: #fefefe;
            border-radius: 12px;
            padding: 20px;
            font-family: 'SF Mono', 'Fira Code', 'Consolas', 'Courier New', monospace;
            font-size: 1rem;
            line-height: 1.5;
            overflow-x: auto;
            border: 1px solid #e9ecef;
            white-space: pre;
            margin: 15px 0;
        }
        .tree-text {
            margin: 0;
            color: #212529;
        }
        .footer {
            margin-top: 30px;
            text-align: center;
            font-size: 0.85rem;
            color: #6c757d;
            border-top: 1px solid #e9ecef;
            padding-top: 15px;
        }
        .footer code {
            background: #e9ecef;
            padding: 2px 6px;
            border-radius: 6px;
        }
        .parent-link {
            display: inline-block;
            background: #e7f3ff;
            color: #0d6efd;
            padding: 8px 18px;
            border-radius: 30px;
            text-decoration: none;
            font-weight: 500;
            border: 1px solid #b8daff;
            transition: 0.2s;
        }
        .parent-link:hover {
            background: #0d6efd;
            color: white;
            border-color: #0d6efd;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>
             目录树浏览器(文本风格)
            <small><?php echo htmlspecialchars(TARGET_BASE); ?> 及其子目录</small>
        </h1>

        <!-- 路径导航 -->
        <div class="path-bar">
            <span class="path-label">当前位置:</span>
            <div class="path-links">
                <a href="?">根目录</a>
                <?php
                // 生成面包屑导航
                if (!empty($relativePath)) {
                    $parts = explode(DIRECTORY_SEPARATOR, $relativePath);
                    $cumulative = '';
                    echo '<span class="sep">/</span>';
                    foreach ($parts as $index => $part) {
                        $cumulative .= ($cumulative ? DIRECTORY_SEPARATOR : '') . $part;
                        $safePart = htmlspecialchars($part);
                        $url = '?dir=' . urlencode(str_replace(DIRECTORY_SEPARATOR, '/', $cumulative));
                        if ($index === count($parts) - 1) {
                            echo '<span>' . $safePart . '</span>';
                        } else {
                            echo '<a href="' . $url . '">' . $safePart . '</a><span class="sep">/</span>';
                        }
                    }
                }
                ?>
            </div>

            <!-- 快速跳转子目录表单 -->
            <form class="nav-form" method="get" action="">
                <input type="text" name="dir" placeholder="输入子目录路径 (如 images/icons)" value="<?php echo htmlspecialchars($relativePathForUrl); ?>">
                <button type="submit">跳转</button>
            </form>
        </div>

        <!-- 文本树输出 -->
        <div class="tree-container">
            <pre class="tree-text"><?php
                // 输出根目录行,然后输出树内容
                echo ICON_FOLDER . ' ' . htmlspecialchars($currentDisplayName) . "\n";
                echo $treeText;
            ?></pre>
        </div>

        <!-- 父目录链接与说明 -->
        <div style="margin-top: 20px;">
            <?php
            $parentRelative = getParentRelative($relativePath);
            if ($parentRelative !== null):
                $parentUrl = '?dir=' . urlencode(str_replace(DIRECTORY_SEPARATOR, '/', $parentRelative));
            ?>
                <a href="<?php echo $parentUrl; ?>" class="parent-link">⬆ 返回上一级 (<?php echo htmlspecialchars(basename($parentRelative) ?: '根目录'); ?>)</a>
            <?php endif; ?>
        </div>

        <div class="footer">
            <p> 安全限制:只能浏览 <code><?php echo htmlspecialchars($baseReal); ?></code> 及其子目录 | 隐藏文件<?php echo SHOW_HIDDEN ? '显示' : '不显示'; ?> | 连线符号: ├── └── │</p>
        </div>
    </div>
</body>
</html>
微信赞赏

WeChat

支付宝赞赏

Alipay

✍️ クリエイター: 紅穆

ウェブサイト管理者 · ご覧いただきありがとうございます!さらに多くの魅力的なコンテンツをお楽しみいただけますよう、引き続きご支援をお願いいたします。

著者プロフィール プロフィールを見る →

関連記事

PHPのob関数の記録

PHPのob関数の記録 言語ノート PHP

以下の3つの関数(`ob_get_contents()`、`ob_end_clean()`、`ob_start()`)を用いることで、ローカルファイルのバッファリングやローカルスクリプトコードの実行を実現できます。`ob_start()`を用いて出力されるコードをバッファに保存すると、ページは即座に表示されません。その後、`ob_get_contents()`を呼び出してバッファに保存されたデータを取得します。
👁 154
PHP preg

PHP preg 言語ノート PHP

`preg_match_all` 関数は、グローバルな正規表現マッチングを実行するために使用されます。`preg_match_all()` の構文は以下の通りです:`int preg_match_all( string $pattern , string $subject [, array &$matches [, int $flags = PREG… ] )`
👁 174
PHPにおける三項演算子と`if`文の詳細解説

PHPにおける三項演算子と`if`文の詳細解説 言語ノート PHP

三元演算子(? 表示式1 : 表示式2):説明:問号の前に続く部分が条件であり、条件が満たされた場合に表示式1、そうでない場合に表示式2が返されます。本記事では、三元演算子と `if...else...` 文を比較・詳細に解説しており、皆様の参考になれば幸いです。本日、論文のオンライン修正作業中に、以下の文が理解できなかったことに遭遇しました:`$if_summary = $row['IF_SUMMARY'] = ...`
👁 198
PHPにおける型強制変換

PHPにおける型強制変換 言語ノート PHP PHPデータ収集 PHPとMySQL

データ型の確認:1. 指定された式の値および型を確認する場合、`var_dump()` を使用します。2. デバッグ目的で、読みやすい型表現を取得する場合、`gettype()` を使用します。3. 指定された型を確認する場合は、`gettype()` ではなく `is_type()` 関数を使用してください。文字列を数値に変換:文字列が数値として評価される場合、結果は以下のルールに基づいて決定されます…
👁 240
PHPで指定された文字列が文字列に含まれているかどうかを判定するさまざまな方法

PHPで指定された文字列が文字列に含まれているかどうかを判定するさまざまな方法 言語ノート PHP

`str strstr()` 関数は、ある文字列が別の文字列内で最初に出現する位置を検索します。この関数は、一致した位置から始まる文字列の残りの部分を返します。対象の文字列が見つからない場合、`false` を返します。コード例:`< ?php $email = 'user@example.com'; $domain = strstr($email, ...
👁 145

おすすめ読書

There are large item groups in navigation panel which may affect the performance. Consider disabling item grouping in the navigation panel.

There are large item groups in navigation panel which may affect the performance. Consider disabling item grouping in the navigation panel. 落とし穴のまとめ

これは警告メッセージであり、通常、開発者ツールや特定のアプリケーションを使用している際に表示されます。このメッセージは、ナビゲーションパネルに大規模なプロジェクトグループが存在していることを通知し、これによりナビゲーションパネルのパフォーマンスに影響が出る可能性があることを示しています。大規模なプロジェクトグループでは、ナビゲーションパネルが大量のデータを読み込み・表示する必要があり、これによりパフォーマンスが低下したり、遅延や動作停止(カスケード)が発生したりする可能性があります。このような状況を回避するため、プロジェクトグループを無効化するか、他の方法でナビゲーションパネルをグループ化することを推奨します。以下に、参考になる情報がいくつか掲載されています…
👁 155
(PC+WAP)窓・ドアのカスタマイズ用PBootCMSウェブサイトテンプレート/アルミニウム合金製窓・ドア用ウェブサイトソースコードのダウンロード(0126)

(PC+WAP)窓・ドアのカスタマイズ用PBootCMSウェブサイトテンプレート/アルミニウム合金製窓・ドア用ウェブサイトソースコードのダウンロード(0126) 実用収集 pbootcmsテンプレート

窓・ドアのカスタマイズサービスおよびアルミニウム合金製窓・ドア業界向けのPbootCMSウェブサイトテンプレートです。PCおよびWAPプラットフォームでのアクセスに対応しています。モダンでシンプルなデザインにより、窓・ドア製品ラインナップや設置事例を効果的に紹介できます。窓・ドアブランドやアルミニウム合金加工企業が公式ウェブサイトを構築し、オンライン顧客獲得能力を向上させるのに最適な選択肢です。テンプレート表示・設置説明・ウェブサイト管理画面:/admin.php ユーザー名:admin パスワード:admin 解凍…
👁 41
(モバイル対応)簡体字・繁体字対応・レスポンシブHTML5による汎用金融・資本コンサルティングサイト用シングルページ用pbootcmsテンプレート 0377

(モバイル対応)簡体字・繁体字対応・レスポンシブHTML5による汎用金融・資本コンサルティングサイト用シングルページ用pbootcmsテンプレート 0377 実用収集 pbootcmsテンプレート

このPbootCMSテンプレートは、スマートフォン向けに適応型の簡体字・繁体字両言語対応・レスポンシブ設計の汎用金融資本コンサルティング用1ページ形式のウェブページテンプレートです。専門的なマーケティングデザインを採用しており、金融コンサルティングや資本サービスを提供する企業がオンラインでのプロモーションや顧客獲得に適しています。簡体字・繁体字の両言語に対応しており、台湾海峡両岸市場への展開を支援します。テンプレートの表示・インストール手順:ウェブサイトバックエンド:/admin.php|ユーザー名:admin|パスワード:admin|解凍用パスワード:www.4s5.cn|関連記事…
👁 47
ブルー系ミニアプリ用ウェブサイトテンプレート「pbootcms」-ミニアプリ型ECソフトウェア開発会社用ウェブサイトソースコード(PC+WAP)0449

ブルー系ミニアプリ用ウェブサイトテンプレート「pbootcms」-ミニアプリ型ECソフトウェア開発会社用ウェブサイトソースコード(PC+WAP)0449 実用収集 pbootcmsテンプレート

この青色のミニアプリウェブサイトテンプレートはPbootCMSをベースに開発されており、ミニアプリ型ECサイト運営企業やソフトウェア開発会社に最適です。テクノロジー感のある専門的なデザインスタイルにより、ミニアプリ開発事例、ソフトウェア製品、および技術的強みを効果的に紹介できます。また、PC端末およびWAP端末からのアクセスに対応しています。テンプレート表示・インストール手順:ウェブサイトバックエンド:/admin.php、ユーザー名:admin、パスワード:admin、解凍用パスワード:www.4s5.cn。関連記事:P…
👁 63
(スマートフォン向け適応型)レスポンシブ設計・簡体字・繁体字両対応のバイリンガル電子技術機器製造業向けPbootCMSウェブサイトテンプレート – HTML5電子部品ウェブサイトソースコードダウンロード(2014年6月14日)

(スマートフォン向け適応型)レスポンシブ設計・簡体字・繁体字両対応のバイリンガル電子技術機器製造業向けPbootCMSウェブサイトテンプレート – HTML5電子部品ウェブサイトソースコードダウンロード(2014年6月14日) 実用収集 pbootcmsテンプレート

レスポンシブ設計の簡体字・繁体字両言語対応電子技術機器製造・電子部品向けPbootCMSウェブサイトテンプレートです。PC端末およびWAP端末に対応しています。テクノロジー感あふれる精密なデザインで、電子部品メーカーによる製品紹介に最適です。企業が中国本土および海外の華人市場での事業拡大を支援します。テンプレート表示・インストール手順:ウェブサイトバックエンド:/admin.php|ユーザー名:admin|パスワード:admin|解凍用パスワード:www.4s5.cn|関連記事…
👁 55