<?php

namespace Acms\Services\Backup;

/**
 * storage/ 直下のディレクトリ名がバックアップ対象（メディアファイルの実体）かを判定する。
 *
 * Why not: バックアップ・エクスポート類のディレクトリ名を除外する方式（除外リスト）にしない。
 * storage/ 直下は運用機能の追加ごとに増え、sql_export / export_wxr / entry_tmp のような
 * 生成物を列挙しきれない。実際に除外名の記述ミスで backup_database と backup_blog が漏れ、
 * アーカイブのバックアップが過去のバックアップと DB ダンプを取り込んでいた。数字ディレクトリ
 * （ブログ ID）だけを通す許可リスト（既定拒否）にする。
 *
 * Why not: 静的書き出しの Acms\Services\StaticExport\StorageDirPolicy を再利用しない。
 * 目的が別で（バックアップは復元のための保全、静的書き出しは公開）、片方の都合で対象を
 * 広げた／狭めた変更がもう片方へ波及してはいけない。静的書き出し側が持つ「許可名を設定で
 * 追加する」仕組みもこちらには設けない。バックアップの対象は storage/ の構造そのもので
 * 決まり、運用者が絞り込む必要がないため。
 */
class StorageDirPolicy
{
    /**
     * ディレクトリ名として受け付ける文字種。英数字・アンダースコア・ハイフン・ピリオドのみ。
     *
     * Why not: 呼び出し元（ACMS_POST_Backup_ArchiveExport）は readdir() が返す名前しか
     * 渡さないためパス区切り文字は実害が無いが、この判定はクラス名どおり「安全な名前だけを
     * true にする」ことを保証する必要がある。判定の入口で '/' '\' 等を含む名前を拒否しておく。
     */
    private const SAFE_NAME_PATTERN = '/\A[0-9A-Za-z_.-]+\z/';

    /**
     * storage/ 直下のディレクトリ名がバックアップ対象（メディアファイルの実体）かを判定する。
     *
     * ブログ数が 1000 を超えると sprintf('%03d') は 4 桁を返すため桁数は固定しない。
     *
     * @param string $name ディレクトリ名（basename）
     * @return bool
     */
    public static function isBackupTargetDirName(string $name): bool
    {
        if (!self::isSafeName($name)) {
            return false;
        }
        return preg_match('/\A[0-9]+\z/', $name) === 1;
    }

    /**
     * ディレクトリ名として安全な文字種のみで構成され、'.' '..' でないかを判定する。
     *
     * @param string $name
     * @return bool
     */
    private static function isSafeName(string $name): bool
    {
        if ($name === '.' || $name === '..') {
            return false;
        }
        return preg_match(self::SAFE_NAME_PATTERN, $name) === 1;
    }
}
