Help us understand the problem. What is going on with this article?

プチ・クラウドストレージ作ってみた

初めまして

60歳を間近にウェブデザイナーを目指して独学で勉強しているお婆ちゃんです。

去年の暮れからphpを勉強して、初めてシステムらしきものを作ってみました。

やりたいこと
プチ・クラウドストレージ
・ファイルをどこからでもアップ、保管してダウンロードもできる。
・セキュリティも兼ねてIDとパスワードでログイン形式にする。

まずはパワーポイントでサイトの系図を設計しました。
Excel、Word、パワポは商工会議所で習いたてホヤホヤです。
それぞれ基礎編までクリアして1月半くらいかかりました。
全部で5万円くらいはかかったかな。

siteroot.jpg

次は手順を考えてイメージを具体化。
これは無料版のAdobeのXDを使ってみました。

操作も簡単で、感覚的に作れちゃうので便利です。
ページ自体はシンプルにしたかったのでフォントだけで作りました。
コードを書くのもAdobeの無料Brackets。Adobeドップリ。


ログインページ

AdobeXD イメージ図
Top.jpg
パスワードとIDはここで決めてます。
空文字NGの条件も設定。

login.php
//XSS
function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}
$logid = '';
$pass = '';
$messege = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    //isset入れると空文字条件が効かない
    $logid = $_POST['logid'];
    $pass = $_POST['pass'];
    $logid = html_escape($logid);
    $pass = html_escape($pass);

    //IDとパスワード設定
    if($logid === 'keserasera' && $pass === 'keserasera'){
        session_start();
        $_SESSION['login'] = 1;
        //ファイル一覧へリロード
        header('Location: file_list.php');
        exit();
    } elseif ($logid === '' || $pass === ''){
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDとパスワードを空文字にしないで入力してください</p>';
    } else {
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDかパスワード、もしくは両方違います</p>';
    }
}

?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル預かり処・マイ保管庫</title>
   <!-- headerインクルード -->
    <?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
          <p class="tx14">ログインページ</p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="topMain">
      <form action="" method="post" class="clearfix">
         <label>ログインID</label>
         <input type="text" name="logid">
         <label>パスワード</label>
         <input type="password" name="pass">
         <p id="logBtn"><input type="submit" value="ログイン"></p>
         <?php echo $messege; ?>
      </form>      
    </main>
    <!-- footerインクルード -->
    <?php require_once(dirname(__FILE__).'/footer.php'); ?>

ファイルリスト一覧ページ

AdobeXD イメージ図
file_list.jpg

アップロードとダウンロード、削除ファイルの3つのformがあります。

  • アップロードは同一ページで処理
  • ダウンロードはチェックページに飛ばしてリロード処理
  • 削除ファイルは確認ページを別に作ってpostデータを渡す

ちなみにダウンロードと削除ファイル一覧リストはタブ切替。
フォルダーの中身は一緒でアップすると自動でリストが増えて、削除すると減っていきます。

file_list.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
$up_file = '';
$messege = '';
$select_file = '<p id="take">ファイルを選択して下さい</p>';
$restore = '';
$up_before = 'upBefore';//非表示css
$filename = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    $up_file = $_FILES['file_up'];
    $up_file['name'] = html_escape($up_file['name']);
    $up_file['name'] = strtolower($up_file['name']);//英小文字に変換  
    //var_dump($up_file['name']);

    $extension = Pathinfo($up_file['name'],PATHINFO_EXTENSION);//.以降の拡張子を重複しないよう整形
    $filename = Pathinfo($up_file['name'],PATHINFO_FILENAME);
    $filename = str_replace(['.',' ',' '], '', $filename);//ファイル名に.と半角全角の空白があったら除去

    //ファイルがNGの場合条件処理
    if($up_file['size'] > 30000000 ){
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ファイルサイズが容量を超えています</p>';
        $restore = '<a id="restore" href="file_list.php">こちらからUPし直してください</a>';
        //phpよりエラー表示を出すときファイル選択ボタンを出さない為、空文字設定に
        $select_file = '';
    } elseif(file_exists('../../../up_file/'.$filename.'.'.$extension) === TRUE) {
        //file_exists関数でディレクトリ内を調べて、同じファイル名があった場合はアップさせない
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>同名のファイルがあります。違うファイル名にしてください。</p>';       
    } elseif(file_exists('../../../up_file') === FALSE) {
        //ディレクトリーが無かった場合の処理
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ディレクトリーが見当たりません。ファイルパスを確認するかもしくは新規で作成してください。</p>';
    } else {
        $messege = '';
        $up_before = 'upAfter';

        //同名ファイルを連番で上げる場合、file_exists()の同名ファイル条件を削除
        /*$suffix = 0;
        while(file_exists('../../../up_file/'. $filename. ($suffix ? sprintf('_%d', $suffix) : ''). "." . $extension)) $suffix++;
       move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.($suffix ? sprintf('_%d', $suffix) : '').'.'.$extension);*/

       //fileをアップする関数
        move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.'.'.$extension);     
    }
}

   //upフォルダの中身
    $dw_items =  glob('../../../up_file/*');//DL用、同じでも変数変えないとエラーになる
    $del_items = glob('../../../up_file/*');//削除用、grobは配列形式でファイルパスを取得
?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル一覧 | ファイル預かり処・マイ保管庫</title>  
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="main">
        <h2 class="pageTitle">ファイルアップロード</h2>
     <!-- ファイルup -->
        <form id="upBox" action="" method="post" enctype="multipart/form-data">
              <p class="notice">※ファイルsizeは1つにつき30MBまで、名前は英小文字で</p>
              <!-- UPし直し表示 -->
              <?php echo $restore; ?>
              <p id="<?php echo $up_before; ?>">ファイル「<?php echo $filename; ?>」はアップされました。続けてUPできます</p>
              <label for="up">
                  <!-- ファイルを選択ボタン -->
                  <?php echo $select_file; ?>
              </label>
              <input id="up" type="file" name="file_up">
              <p id="send"><input type="submit" value=""></p>
              <!-- エラーメッセージ -->
              <?php echo $messege; ?>
        </form>

      <div id="fileBox">
          <p id="fileCount">ファイル数<?php echo count($dw_items); ?>項目</p>         
          <h3 class="selectFile" id="dowTab">DL用ファイル一覧</h3>  
          <form id="dowList" action="download_file.php" method="post">
             <!-- 飛び先でLocation リダイレクト処理-->
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($dw_items as $items): $dw_name = Pathinfo($items,PATHINFO_BASENAME); ?>                   
                     <li><input type="radio" name="dowl" value="<?php echo $dw_name; ?>"><?php echo $dw_name; ?></li>                     
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のラジオボタンにチェックしてダウンロードボタンをクリックしてください</p>
              <p class="pibtn"><input type="submit" value="Download"></p>
          </form>

          <h3 class="selectFile" id="delTab">削除用ファイル一覧</h3>
          <form id="delList" action="delete_confilm.php" method="post">
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($del_items as $items): $del_name = Pathinfo($items,PATHINFO_BASENAME); ?>
                         <li><input type="checkbox" name="del[]" value="<?php echo $del_name; ?>"><?php echo $del_name; ?></li>  
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のチェックボックスを選択(複数可)して確認ボタンをクリックしてください</p>
              <p class="vibtn"><input type="submit" value="削除確認"></p>
          </form>
      </div><!-- //id="fileBox"-->

    </main>  
<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>



input type="file"は特殊で、デフォルトのボタンを使うのはビジュアル面で抵抗があったのでカスタマイズしました。

ファイルが選択されたらボタンがファイル名に変わって、エラーだったら上にメッセージ表示。
OKだったらtype="file"ブロックは非表示になって、type="submit"にすり替え。
見た目は一緒のボタンです。

ファイルアップが成功したらボタンの上にファイル名が表示して下のリストに追加といった仕様。

ただ、ファイル選択時はsubmitボタンは押されてないのでphpでの処理が難しい。
そこでjsのchangeイベントを活用。
ここは頭がこんがらがりました。phpとjsとcssのトリプル連携。

submit_btn.js
jQuery('#up').on('change',function(){
      const upfile = jQuery('#up').get(0).files;
      console.log(upfile);
      //多次元配列になるのかな? upfile[[name: xxx,size: xxx]]
      const fileName = upfile[0].name;            
      console.log(fileName);
       //file選択をsubmitにすり替え
      jQuery('#take').css('display','none');
      jQuery('#send').css('display','block');
      jQuery('#send input').val(fileName);
   });


ダウンロードチェックページ

ダウンロードのコードはどうしても分からなかったのでググって動いたものをコピペさせてもらいました。
それまではダウンロードできても開けられなかったり不具合続出。

ここのコードが一番難しい。
今の段階では理解できなかったのですが、そのうち自分でも組めるようになりたいです。

参考サイトはこちら

download_file.php
//ダウンロードチェック
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
$dowload_file = $_POST['dowl'];
$dowload_file = html_escape($dowload_file);

//var_dump($dowload_file);

//ここのコード分からなかったのでネットからググって拾ってきた
    function download($pPath, $pMimeType = null){
    //-- ファイルが読めない時はエラー(もっときちんと書いた方が良いが今回は割愛)
    if (!is_readable($pPath)) { die($pPath); }

    //-- Content-Typeとして送信するMIMEタイプ(第2引数を渡さない場合は自動判定) ※詳細は後述
    $mimeType = (isset($pMimeType)) ? $pMimeType
                                    : (new finfo(FILEINFO_MIME_TYPE))->file($pPath);

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

    //-- Content-Type
    header('Content-Type: ' . $mimeType);

    //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する
    header('X-Content-Type-Options: nosniff');

    //-- ダウンロードファイルのサイズ
    header('Content-Length: ' . filesize($pPath));

    //-- ダウンロード時のファイル名
    header('Content-Disposition: attachment; filename="' . basename($pPath) . '"');

    //-- keep-aliveを無効にする
    header('Connection: close');

    //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述
    while (ob_get_level()) { ob_end_clean(); }

    //-- 出力
    readfile($pPath);

    //-- 最後に終了させるのを忘れない
    exit;
}

//選択されたファイルがあったらダウンロード、なかったらそのままリダイレクト
//選択されないままダウンロード処理されちゃうと変なファイルがDLされる
if(isset($_POST['dowl'])){
    download('../../../up_file/*'.$dowload_file);
    header('Location: file_list.php');   
} else {
   header('Location: file_list.php');
}


削除確認ページ

AdobeXD イメージ図
check.jpg

削除だけは誤って消してしまって後悔しないように、確認してからの導線にしました。
ファイルが選択されていない場合は戻るボタンだけにしています。

delete_confilm.php
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}

$delete_file = '';
$confiText = '';
$btnText = '';
$none = '';

//エラーが出るのでfor文中にエスケープをかけた
if(isset($_POST['del'])){
    for($i = 0; $i < count($_POST['del']); $i++){
        $delete_file .= '<li><input type="hidden" name="check[]" value="'.html_escape($_POST['del'][$i]).'"><i class="far fa-file"></i>'.html_escape($_POST['del'][$i]).'</li>';
    }
    $confiText = '以下のファイルを削除してもいいですか?';
    $btnText = 'キャンセル';
} else {
    $delete_file = '<li><i class="far fa-file"></i>ファイルが選択されていません</li>';
    $confiText = '';
    $btnText = '戻る';
    $none = 'none';
}
?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル削除確認 | ファイル預かり処・マイ保管庫</title>
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>

    <main id="confiBox">
        <h2 class="pageTitle">ファイル削除確認</h2>
        <p class="confiText"><?php echo $confiText; ?></p>
        <form action="delete_done.php" method="post">
            <ul>
                <?php echo $delete_file; ?>
            </ul>
            <div id="btnBlock">
                <p id="cansel"><a href="file_list.php"><?php echo $btnText; ?></a></p>
                <p id="decision" class="<?php echo $none; ?>"><input type="submit" value="削除"></p>
            </div>
        </form>
    </main>

<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>


削除完了ページ

AdobeXD イメージ図
del_done.jpg

削除確認ページのレイアウトとほぼ一緒です。

delete_done.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}

$delete_file = '';

//POSTで渡されたファイルを削除
if(isset($_POST['check'])){
    for($i = 0; $i < count($_POST['check']); $i++){           
        unlink('../../../up_file/*'.html_escape($_POST['check'][$i]));
        //削除ファイルli書出し
        $delete_file .= '<li><i class="far fa-file"></i>'.html_escape($_POST['check'][$i]).'</li>';
    }    
}

?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル削除完了 | ファイル預かり処・マイ保管庫</title>
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>

    <main id="confiBox">
        <h2 class="pageTitle">ファイル削除完了</h2>
        <p class="confiText">以下のファイルを削除しました</p>
        <ul>
            <?php echo $delete_file; ?>         
        </ul>
        <p id="toListpage"><a href="file_list.php">ファイル一覧ページへ</a></p>
    </main>

<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>

ログアウトページ

ログアウトページは、お約束のセッション破棄だけなのでコードは省きます。
レイアウトはログインページと同じにしました。


ひとまず完成

あとは実際のサーバに上げて動作確認。

動いた時は大感動。
あれ?
でもアップできないファイルがある。

色々試して、どうやら日本語名のファイルはアップできないみたい。

XAMPP開発時では日本語のファイル名でも大丈夫だったんですけどね。
そういえば日本語とサーバの相性は良くないと昔から言われてました。

とりあえず注意書きに日本語NGと追加して応急対処。
preg_matchで条件設定した方がいいのかなと思ったりしています。

ひとまず、これで完成として使い込んでみようと思ってます。
制作期間は約1週間、畑仕事と家事の合間にコツコツと作業

お婆ちゃんのweb制作奮闘記でした。


追記

@rfc828さんからご意見いただいたので以下の項目を直してみました。

  • 削除確認ページのコード追加
  • ファイルアップのディレクトリーをwebからアクセス出来ない場所へ移動
  • 同名ファイルの上書き防止機能追加
  • 日本語名ファイルでもアップロード可能に

丁寧に教えていただき有り難うございます!

修正前の状態では同名ファイルをアップすると上書きされてしまいました。
なので以下のコードをファイルリスト一覧ページの条件文に追加しました。

elseif(file_exists('../../../up_file/'.$filename.'.'.$extension) === TRUE) {
        //file_exists関数でディレクトリ内を調べて、同じファイル名があった場合はアップさせない
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>同名のファイルがあります。違うファイル名にしてください。</p>';       
    }

同じ名前だったらメッセージのみを出してアップ出来ないようにしています。
乱数に変換する方法とかがありますが、自分が使うことを前提にしてるのでファイル名が分からなくなると困るかなと。
また同じファイルがたまると乱雑になるので、いっそのことアップ出来なくする方がベストかなと思いました。

教えてもらったsetlocale(LC_ALL, "ja_JP.UTF-8")をファイル名が記述しているページに全て追記したら日本語名でアップ出来るようになりました。
サーバの中を覗いてもちゃんと日本語で表示されてて、とても感動です!

ただ、日本語名ファイルの入ったフォルダーをMacでzipに圧縮してWindowsで解凍すると、日本語名が文字化けになってしまいます。
この現象を考えると初めから英小文字にしておいた方が無難かもしれません。
でも日本語名でアップ出来たのは嬉しい。

後の不具合は追々修正していこうと思ってます。

ずっと一人で開発していたので、今回のようにコードを見てくれて教えてもらったことは大変有り難く感謝です!
これからもご意見いただけると嬉しいので、どうぞ宜しくお願いします!


追加修正しました

  • ファイル名の余白を削除するようにしました
  • 保存先のディレクトリーが無かったらUP出来ない表示に修正しました
  • 連番で同名ファイルがUP出来る方法を教えてもらったのでコメントアウトで追記しました

今まで「・」だけを削除するよう文字整形してましたが(img.png.pngみたいな)半角と全角スペースも削除するよう要素を増やしました。

ファイルリスト一覧ページ

旧コード
$filename = str_replace('.', '', $filename);

修正コード
$filename = str_replace(['.',' ',' '], '', $filename);


保存先のディレクトリーがなくても「UP出来ました」と表記されていたのをエラーメッセージが出るよう条件分岐を追加しました。

追加コード

//ディレクトリーが無かった場合の処理
} elseif(file_exists('../../../up_file') === FALSE) {
$messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ディレクトリーが見当たりません。ファイルパスを確認するかもしくは新規で作成してください。</p>';
}



連番でファイルアップが出来る方法は@rfc828さんに教えていただきました。
今回はファイルを増やさない方向でいきますが、場合によっては同名ファイルをアップしたいということもあるかと思います。
そんな時は大変重宝します!
ちゃんと動くことも確認しました。
早速コメントアウトしておいて永久保存。

//同名ファイルを連番で上げる場合、上のfile_exists()の同名ファイル条件を削除
$suffix = 0;
 while(file_exists('../../../up_file/'. $filename. ($suffix ? sprintf('_%d', $suffix) : ''). "." . $extension)) $suffix++;

move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.($suffix ? sprintf('_%d', $suffix) : '').'.'.$extension);

本当にありがとうございます!


ダウンロードチェックページのDL処理について

ダウンロードの関数は初心者には難易度が高かったです。
理解出来てない部分も多々あり、まだまだ知識は浅いです。

@libraplanetさんからアドバイスいただいた、エンコードのことことですが、ブラウザから日本語のURLをコピペするとファイル名が%のついた長い暗号のようなものになっているあの現象のことでしょうか?
調べて見ましたが、rawurlencodeやmb_convert_encodingなのかなと思ってしまいましたが…

教えてもらったにも関わらず、分からないことが多くて修正にまで及びませんでした。
ですがクロスブラウザやOS対応はこれからも考えて行きたいと思っています。
ご意見いただけて本当にありがとうございます。


kanrek_jyosi
もうすぐ還暦を迎えるお婆ちゃんです。 最近パソコンを触り出したら面白くてハマってしまいました。 web制作に必要なツールやコードを勉強中です。 難しい事とは重々承知してますが、一生懸命頑張りますので宜しくお願いします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした