# [メタ情報] # 識別子: 公開用スクリプトのリスト表示_exe # システム名: 公開用スクリプトのリスト表示、functions.phpに置く # 技術種別: Misc # 機能名: Misc # 使用言語: php # 状態: 実行用 # [/メタ情報] 要約:WordPressのショートコード [pub_txt_list] による公開用TXT一覧を生成するコード。 ?pubtxt_dir パラメータで階層移動、見出しクリックで sort/order 切替が可能。 pmedia/scripts_pub 配下のTXTのみ対象とし、@eaDir やドット始まり・key_patterns.txt を除外。 更新日時(mtime desc)→名前→サイズの優先順でソートし、パンくず表示も実装。 各TXTはクリックで閲覧または &dl=1 でUTF-8添付ダウンロード可。 ディレクトリトラバーサル防止のため realpath 検証を行う安全設計。 functions.php /* ========================================================= * 公開用TXTリスト: [pub_txt_list] ページ遷移型+クリックソート * - ?pubtxt_dir= 相対パスで階層移動(同じ固定ページ内で遷移) * - 見出しクリックで sort/order 切替 * - 初期ソート: mtime desc → name asc → size asc * - @eaDir/ドット始まりを除外 * =======================================================*/ add_shortcode('pub_txt_list', function($atts){ $base_dir = WP_CONTENT_DIR . '/pmedia/scripts_pub'; $allowed_exts = ['txt']; $exclude_files = ['key_patterns.txt']; $exclude_dirs = ['@eaDir']; $uid = 'pub-txt-' . (function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid()); if (!is_dir($base_dir)) return '
一覧ディレクトリが存在しません。
'; // === ページURL(この固定ページ自身) === $page_url = get_permalink(); // ← これが肝。home_url()は使わない if (!$page_url) $page_url = home_url(add_query_arg([],'/')); // フォールバック // === 現在ディレクトリ === $rel_dir = ''; if (isset($_GET['pubtxt_dir']) && is_string($_GET['pubtxt_dir'])) { $rel_dir = trim(str_replace('\\','/', $_GET['pubtxt_dir']), '/'); } if ($rel_dir === '.' || strpos($rel_dir, '..') !== false || strpos($rel_dir, "\0") !== false) $rel_dir = ''; $current_dir = $base_dir . ($rel_dir ? '/'.$rel_dir : ''); if (!is_dir($current_dir)) { $rel_dir=''; $current_dir=$base_dir; } // === ソートパラメータ === $validSort = ['name','mtime','size']; $sort = (isset($_GET['sort']) && in_array($_GET['sort'], $validSort, true)) ? $_GET['sort'] : ''; $order = (isset($_GET['order']) && in_array(strtolower($_GET['order']), ['asc','desc'], true)) ? strtolower($_GET['order']) : ''; // 初期値:mtime desc → name asc → size asc $initial_sort_chain = [ ['key'=>'mtime','dir'=>'desc'], ['key'=>'name','dir'=>'asc'], ['key'=>'size','dir'=>'asc'], ]; if ($sort === '') { $sort = 'mtime'; $order = 'desc'; } // 見出しクリック時の次のorder決定 $nextOrder = function($key) use ($sort,$order){ if ($key !== $sort) { // 新しく選ばれたキーのデフォルト向き return ($key === 'mtime') ? 'desc' : 'asc'; } return ($order === 'asc') ? 'desc' : 'asc'; }; // URL生成(現在の pubtxt_dir を保持) $url_with = function(array $add) use ($page_url, $rel_dir, $sort, $order){ $args = ['pubtxt_dir' => $rel_dir]; if ($sort) $args['sort'] = $sort; if ($order) $args['order'] = $order; foreach ($add as $k=>$v){ if ($v === null) unset($args[$k]); else $args[$k] = $v; } return add_query_arg($args, $page_url); }; // === フォーマッタ === $fmt = function($f,$ts){ if(function_exists('wp_date')) { $s=wp_date($f,$ts); if($s) return $s; } if(function_exists('date_i18n')) { $s=date_i18n($f,$ts,false); if($s) return $s; } return gmdate($f,$ts); }; // === 1階層走査 === $items = []; foreach (scandir($current_dir) as $name) { if ($name === '.' || $name === '..') continue; if ($name[0] === '.' || in_array($name, $exclude_dirs, true)) continue; // 隠し & @eaDir if (in_array($name, $exclude_files, true)) continue; $full = $current_dir . '/' . $name; if (!is_readable($full)) continue; $rel = ltrim(($rel_dir ? $rel_dir.'/' : '').$name, '/'); $mtime = @filemtime($full) ?: 0; if (is_dir($full)) { $items[] = [ 'type'=>'dir','name'=>$name,'rel'=>$rel,'mtime'=>$mtime,'size'=>0, 'href'=>$url_with(['pubtxt_dir'=>$rel,'sort'=>null,'order'=>null]), // 階層移動はソート無しで開始 ]; } elseif (is_file($full)) { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if (!in_array($ext, $allowed_exts, true)) continue; $size = @filesize($full) ?: 0; $items[] = [ 'type'=>'file','name'=>$name,'rel'=>$rel,'mtime'=>$mtime,'size'=>$size, 'view'=>add_query_arg(['pubtxt'=>$rel], $page_url), 'dl' =>add_query_arg(['pubtxt'=>$rel,'dl'=>'1'], $page_url), ]; } } if (!$items) return 'このフォルダには対象ファイルがありません。
'; // === ソート実装(主キー:$sort/$order、従キー:mtime desc → name asc → size asc) === usort($items, function($a,$b) use($sort,$order,$initial_sort_chain){ $compare = function($x,$y,$key,$dir){ $vx = $x[$key] ?? null; $vy = $y[$key] ?? null; $cmp = ($key==='name') ? strcasecmp((string)$vx,(string)$vy) : (($vx <=> $vy)); return ($dir==='asc') ? $cmp : -$cmp; }; // 主キー $primary = $compare($a,$b,$sort,$order); if ($primary !== 0) return $primary; // 従キー foreach ($initial_sort_chain as $rule){ if ($rule['key'] === $sort) continue; $c = $compare($a,$b,$rule['key'],$rule['dir']); if ($c !== 0) return $c; } return 0; }); // === パンくず === $crumbs=[['label'=>'root','rel'=>'']]; if ($rel_dir) { $acc=[]; foreach (explode('/',$rel_dir) as $seg){ $acc[]=$seg; $crumbs[]=['label'=>$seg,'rel'=>implode('/',$acc)]; } } $crumb_html=[]; foreach($crumbs as $i=>$c){ if ($i === count($crumbs)-1) $crumb_html[] = ''.esc_html($c['label']).''; else $crumb_html[] = ''.esc_html($c['label']).''; } $latest = max(array_column($items,'mtime')); // 見出しリンク $th_link = function($key,$label) use ($page_url,$rel_dir,$nextOrder){ $url = add_query_arg(['pubtxt_dir'=>$rel_dir,'sort'=>$key,'order'=>$nextOrder($key)], $page_url); return ''.$label.''; }; ob_start(); ?>| 操作 | .. | 戻る | '; } foreach ($items as $it): $date = esc_html($fmt('Y-m-d H:i:s', $it['mtime'])); $size = $it['size'] ? size_format($it['size'],2) : ''; if ($it['type']==='dir') { echo '|
|---|---|---|---|
| 📁 '.esc_html($it['name']).' | '.$date.' | 開く | |
| '.esc_html($it['name']).' | '.$date.' | '.esc_html($size).' | DL |