銀河鉄道

【PHP】アップロード・ダウンロード機能

サムネイル
アップロードダウンロード

呼び出し側

<?php
    $array = array (
        // ページの基本情報
        'base' => array (
            'page_title'       => 'タイトル', // ページタイトル
            'directory_path'   => '../uploads/', // ファイルの格納場所
            'after_page'       => '../xx/xx.php', // アップ後の表示ページ
        ),

        // ダウンロードリスト(indexをゼロ始まりで指定:ファイルの数だけindexを増やす)
        'download_lists' => array (
            '0' => array (
                "text"         => "Excelファイル", // 画面に表示させる文字
                "file_name"    => "aa.xlsx",
                "text_class"   => "c-icon--excel", // テキストに個別にクラス名を付ける場合
                "name"         => "download_file1", // for,name,idにつける名前
                "accept"       => ".xlsx", // 許可する拡張子(複数の場合はコンマ区切りで書く)
                "type"         => "submit", // inputタイプ
                "value"        => "ダウンロード", // ボタンに表示する文字
                "rename"       => false, // ダウンロード時にリネームする場合はtrue(=ファイル名の末尾に日付追加)
            ),

            '1' => array (
                "text"         => "HTMLファイル",
                "file_name"    => "bb.html",
                "text_class"   => "c-icon--html",
                "name"         => "download_file2",
                "accept"       => ".html",
                "type"         => "submit",
                "value"        => "ダウンロード",
                "rename"       => true,
            ),
        ),

        // アップロードリスト
        'upload_lists' => array (
            "text" => array (
                "Excelファイル" => "aa.xlsx",
                "HTMLファイル" => "bb.html",
                "PDFファイル" => "cc.pdf",
            ),
            "text_class"   => "",
            "accept"       => ".xlsx,.html,.pdf", // 複数の場合:カンマ区切り
            "type"         => "file",
            "value"        => "アップロード",
        ),
    );

    // 配列から変数へ格納
    $page_title = $array['base']['page_title'];
    $directory_path = $array['base']['directory_path'];
    $after_page = $array['base']['after_page'];
    $download_lists = $array['download_lists'];
    $upload_lists = $array['upload_lists'];

    // ダウンロードボタンが押された時の処理
    usa_processing_download($directory_path, $download_lists);

    // アップロードボタンが押された時の処理
    usa_processing_upload($directory_path, $after_page);

    // フロント描画
    usa_make_maintenance_page($page_title, $download_lists, $upload_lists);

ダウンロードボタンが押された時の処理

<?php
/**
* 機能 ダウンロードボタンが押された時の処理
* @param array $directory_path ダウンロードするディレクトリパス
* @param array $download_lists 必要データが入ってる配列
*/
function usa_processing_download($directory_path, $download_lists) {
    // 配列の要素数
    $count = count($download_lists);

    // 配列の要素数分、ループ
    for ($i = 0 ; $i < $count; $i++) {
        $fullpath = $directory_path . $download_lists[$i]['file_name'];
        $post_name = $download_lists[$i]['name'];
        $my_mime_type = $download_lists[$i]['accept'];
        $rename = $download_lists[$i]['rename'];

        if ( isset( $_POST[$post_name] ) ) {
            usa_download($fullpath, $my_mime_type, $rename);
        }
    };
}

アップロードボタンが押された時の処理

<?php
/**
* 機能 アップロードボタンが押された時の処理
* @param string $directory_path アップ先のディレクトリパス
* @param string $after_page アップ後に表示する該当の掲載ページ(デフォルトはandroidページ)
*/
function usa_processing_upload($directory_path, $after_page="") {
    $up_btn_name = "_upload";
    $up_name = "userfile";

    // ボタンが押されたら関数へフルパスを渡す
    if ( isset( $_POST[$up_btn_name] ) ) {
        usa_upload($directory_path, $up_name, $after_page);
    };
}

サーバーから指定ファイルをダウンロードする

<?php
/**
* 機能    サーバーから指定ファイルをダウンロードする
* @param string $fullpath アクセスするフルパス
* @param string $my_mime_type mimeタイプを指定したい場合(省略可)
* @param bool $ng_rename リネームNGの場合はtrueにする(デフォルト値はfalse)
*/
function usa_download($fullpath, $my_mime_type = null, $rename) {
    // ファイルの読み込みができない時はエラー
    if (!is_readable($fullpath)) {
        die($fullpath);
    }

    // Content-Type用にMIMEタイプ設定(第2引数がない場合は自動判定)
    $mimeType = (isset($my_mime_type)) ? $my_mime_type : (new finfo(FILEINFO_MIME_TYPE))->file($fullpath);

    // MIMEタイプが得られない時は、未知のファイルを示すapplication/octet-streamとする
    if (!preg_match('/\A\S+?\/\S+/', $mimeType)) {
        $mimeType = 'application/octet-stream';
    }

    // ※もしも上記が使えない場合はこっちで
    // if (empty($mime_type)) {
    //     $mime_type = "application/octet-stream";
    // }

    // ファイル名のリネーム
    $file_name = basename($fullpath);

    // renameがtrueなら、ファイル名の末尾に秒付きの日付を追加する
    if ($rename) {
        $file_name = usa_rename_filename_before_extension($file_name);
    }

    header('Content-Type: ' . $mimeType); // Content-Typeにmimeタイプを設定
    header('X-Content-Type-Options: nosniff'); // ウェブブラウザが独自にMIMEタイプを判定する処理を抑止する
    header('Content-Length: ' . filesize($fullpath)); // ダウンロードファイルのサイズ
    header('Content-Disposition: attachment; filename="' . $file_name . '"'); // ダウンロード時のファイル名
    header('Connection: close'); // keep-aliveの無効化

    // 出力バッファリングの無効化 ※メモリの大量消費を回避するため
    while (ob_get_level()) {
        ob_end_clean();
    }

    // 出力
    readfile($fullpath);

    // 必ず終了処理をする
    exit;
};

サーバーへ選択されたファイルをアップロードし、結果メッセージを表示する

<?php
/**
* 機能: サーバーへ、選択されたファイルをアップロードし、結果メッセージを表示する
* @param string $upload_dir アップする場所
* @param string $arr_name 格納されている配列の名前(name属性が名前になる)
* @param string $after_page アップ後にデータが表示されるページ(確認用に開く)
*
*/
function usa_upload($upload_dir, $arr_name, $after_page) {
    // リファラの取得
    $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
    $src = parse_url($referer, PHP_URL_PATH);
    $result = '';
    $up_name = '';
    $flag_succeed = false;
    $rename_file ='';

    if (isset($_POST['_upload'])) {
        $count_files = count($_FILES[$arr_name]['name']);

        // アップ数が1つの場合
        if ($count_files === 1) {

            // ファイル名取得にはbasenameを使う:ファイルシステムトラバーサル攻撃からの防護のため
            $base_name = basename($_FILES[$arr_name]['name']);

            // アップ名をフルパスで定義
            $up_fullpath = makeFullPathUploadFile($upload_dir, $base_name);

            // ファイルを仮置き場(=tmp)から、指定場所($up_fullpath)へ移動できたら成功
            if (move_uploaded_file($_FILES[$arr_name]['tmp_name'], $up_fullpath)) {
                $flag_succeed = true;

                // up_name = アップされた名前
                $up_name = $_FILES[$arr_name]['name'];

                // リネームした場合は、表示用のタグを生成し、up_nameを書き換える
                makeRenameTag($base_name, $up_name, $rename_file);

                // フロント表示用の成功タグの生成
                $result = makeSucceedTag($result, $up_name);

            // 指定場所へ移動できなかったらエラー表示
            } else {
                $up_name = $_FILES[$arr_name]['name'];
                $err = errorMassage($_FILES[$arr_name]['error']);

                // フロント表示用のエラータグの生成
                $result = makeErrTag($result, $up_name, $err);
            };

        // 複数の場合
        } else {
            foreach ($_FILES[$arr_name]['tmp_name'] as $i => $tmp_name) {
                // ファイル名取得にはbasenameを使う:ファイルシステムトラバーサル攻撃からの防護のため
                $base_name = basename($_FILES[$arr_name]['name'][$i]);

                // アップ名をフルパスで定義
                $up_fullpath = makeFullPathUploadFile($upload_dir, $base_name);

                // ファイルを仮置き場(=tmp)から、指定場所へ移動できたら成功
                if (move_uploaded_file($tmp_name, $up_fullpath)) {
                    $flag_succeed = true;

                    // up_name = アップされた名前
                    $up_name = $_FILES[$arr_name]['name'][$i];

                    // リネームした場合は、表示用のタグを生成し、up_nameを書き換える
                    makeRenameTag($base_name, $up_name, $rename_file);

                    // フロント表示用の成功タグの生成
                    $result = makeSucceedTag($result, $up_name);

                 // 指定場所へ移動できなかったらエラー表示
                } else {
                    $up_name = $_FILES[$arr_name]['name'][$i];
                    $err = errorMassage($_FILES[$arr_name]['error'][$i]);

                    // フロント表示用のエラータグの生成
                    $result = makeErrTag($result, $up_name, $err);
                };
            }
        }
    } else {
        $result = 'ボタンのクリック時に通信エラーがありました。恐れ入りますが、もう一度やり直してください';
    };

    // メッセージボックス(サマリー)のクラス名設定
    $class_summary = "p-summary-box c-mb--40";

    // 成功の場合はクラス名にsuccessを追加し、該当の掲載ページを表示する(画面がかぶらないように高さは低めに指定)
    if ( $flag_succeed ) {
        $class_summary = $class_summary . " success";
        ?>

        <script> window.open('<?php echo $after_page; ?>', '_blank', 'width=800, Height=300'); </script>
        <?php
    }
?>
    <img src="../assets/img/dog-face.png" class="c-confetti-dog" alt="喜んでる犬のイラスト">
    <article class="p-msg-box" id="js-msg-box">
        <article class="<?php echo $class_summary;?>" id="js-summary-box">
            <article class="p-summary-box__inner">
                <div class="c-text-box c-fs--big">
                    <p class="text">【 <?php echo $count_files;?>ファイルの結果 】</p>
                    <p class="c-fs--big result"><?php echo $result;?></p>
                    <?php if ($rename_file !== ''): ?>
                        <p class="rename text c-fs--normal"><span style="display: inline;">(リネーム)</span><?php echo $rename_file; ?></p>
                    <?php endif; ?>
                    <?php if ( $flag_succeed ): ?>
                        <p class="text bless">ダウンロード成功!</p>
                    <?php endif;?>
                    <button class="c-button c-gap-inner--normal c-bg-color--btn-secondary c-btn-shape--rounded c-shadow--blur c-fs--big" id="js-back-button">元のページに戻る</button>
                </div>
            </article>
        </article>

        <article class="p-msg-box__detail c-fs--small">
            <p class="c-fs--normal">結果表示</p>
            <span>---↓ここから↓---</span>
            <span>送信元:<?php echo $src;?></span>
            <span>送信先:<?php echo $upload_dir;?></span>
            <?php if ($rename_file !== ''): ?>
                <span style="display: inline;">リネーム:</span><?php echo $rename_file; ?>
            <?php endif; ?>
            <span>↓↓アップロード結果</span>
            <pre><?php var_dump($_FILES[$arr_name]);?></pre>
            <span>---↑ここまで↑---</span>
        </article>
    </article>

<?php
    header('Content-Type: text/html; charset=UTF-8');
    include('../templates/footer/read-js/after-upload.php');
};

エラーメッセージ

<?php
function errorMassage($error_num) {
    switch ($error_num) {
        case UPLOAD_ERR_OK:
            // 0
            break;
        case 1:
            // UPLOAD_ERR_INT_SIZE
            $result = 'エラー:ファイルサイズが大きすぎます';
            break;

        case UPLOAD_ERR_FORM_SIZE:
            // 2
            $result = 'エラー:ファイルサイズが大きすぎます';
            break;

        case UPLOAD_ERR_PARTIAL:
            // 3
            $result = '通信エラー:恐れ入りますが、もう一度やり直してください';
            break;

        case UPLOAD_ERR_NO_FILE:
            // 4
            $result = 'システムエラー:選択ファイルを読み取れませんでした。恐れ入りますが、もう一度やり直してください';
            break;

        case UPLOAD_ERR_NO_TMP_DIR:
            // 6
            $result = 'システムエラー:選択ファイルを読み取れませんでした。恐れ入りますが、もう一度やり直してください';
            break;

        default:
            $result = 'システムエラー:恐れ入りますが、もう一度やり直してください';
            break;
    }

    return $result;
};

アップロードファイル名をフルパスで定義する

<?php
/**
* アップロードファイル名をフルパスで定義する
*/
function makeFullPathUploadFile($upload_dir, &$base_name) {
    // 日付入りであれば、日付無しに書き換え
    $base_name = isDateInBaseName($base_name);

    // フルパスで定義
    $up_fullpath = $upload_dir . $base_name;
    return $up_fullpath;
}

アップしたファイル名に日付部分が含まれる場合はリネームする

<?php
/**
* アップしたファイル名に日付部分が含まれる場合はリネームする
*/
function isDateInBaseName($base_name) {
    $base_name_len = mb_strlen($base_name);

    // '_yyyymmddhhmmss'部分の文字数
    $date_num = 15;
    $bar_num = '';
    $date_str = '';

    // base_name_lenが日付分よりも大きい場合のみ判定(拡張子分の文字数(最低4文字)も足す)
    if ($base_name_len > $date_num + 4) {
        $dot_num = mb_strrpos($base_name, "."); // 拡張子.の位置
        $bar_num = $dot_num - $date_num; // アンダーバーの位置
    };

    // $bar_numが設定されている&その位置にアンダーバーがあれば、アンダーバー以降を抽出
    if ($bar_num !== '' && $base_name[$bar_num] === '_') {
        $date_str = mb_substr($base_name, $bar_num + 1, $date_num - 1); //'yyyymmddhhmmss'部分を抽出(アンダーバー無し)
    };

    // $date_strに値が入っていて、その値がすべて数字なら、$base_nameから$date_strを削除する
    if ($date_str !== '' && is_numeric($date_str)) {
        $date_str = mb_substr($base_name, $bar_num, $date_num); //'_yyyymmddhhmmss'部分を抽出(アンダーバー付きに変換)
        $base_name = str_replace($date_str, '', $base_name);
    };
    return $base_name;
};

ファイル名をリネームしたとき、表示用のタグを生成しup_nameの値を変更する

<?php
/**
* 機能: ファイル名をリネームしたとき、表示用のタグを生成し、up_nameの値を変更する
*/
function makeRenameTag($base_name, &$up_name, &$rename_file) {
    // アップされた名前が、最初の名前と違う場合
    if ( $up_name !== $base_name ) {
        $rename_file = $rename_file . '<span>' . $up_name . ' → ' . $base_name . '</span>';
        $up_name = $base_name;
    }
}

最終結果を表示するタグの生成

<?php
/**
* 機能: 最終結果を表示するタグの生成
*/
function makeSucceedTag($result, $up_name) {
    $result = $result . '<span class="c-bg-color--yellow">' . $up_name . ":" . "アップしました</span>";
    return $result;
}

エラー時、エラー表示のためのタグを生成

<?php
/**
* 機能: エラー時、エラー表示のためのタグを生成
*/
function makeErrTag($result, $up_name, $err) {
    $result = $result . '<span class="error c-bg-color--red">' . $up_name . ':' . $err . '</span>';
    return $result;
}

文字列の後ろに日付(秒付き)を追加する

<?php
// **
// 機能    文字列の後ろに日付(秒付き)を追加する
// @param string $string 対象の文字列
//
// 結果 string_yymmdd_hhiiss
// **

function usa_add_date_after_string($string) {
    $adding = $string . '_' . date("Ymd") . '_' . date("His");
    return $adding;
};

拡張子の前に文字列を追加する

<?php
// **
// 機能    拡張子の前に文字列を追加する
// @param string $filename 元のファイル名(拡張子付き)
// @param string $adding 拡張子の直前に追加したい文字列 ※省略された場合は日付(秒付き)を自動付与する
//
// 備考    strrpos関数:文字列中に、ある部分文字列が最後に現れる場所($pos)を取得する
//        →「$posの前半 + 追加文字 + $posの後半」でリネームする
// **
function usa_rename_filename_before_extension($filename, $adding = "") {
    if ($adding == "") {
        $adding = '_' . date("YmdHis");
    }
    $pos = strrpos($filename, '.'); // .が最後に現れる位置
    if ($pos) {
        return (substr($filename, 0, $pos) . $adding . substr($filename, $pos));
    } else {
        return ($filename . $adding);
    }
};

著者

author
月うさぎ

編集後記:
この記事の内容がベストではないかもしれません。