PAGE TOP


追加ボタンで画像フォームを増やしていく(編集画面・更新処理)

2017年7月10日Javascript, PHP

実装概要

今回は、前回の追加ボタンで画像フォームを増やしていく(データベース挿入・表示)の続編です。前回は、フォームからのテキストデータと画像名をデータベースに登録し、フォルダを作り、そこに画像をアップロードしました。そして、それらの一覧を表示しました。今回は、その続きで、テキストや画像の編集を行う画面を作っていきます。画像編集画面では、データの読み込みをおこない、それらを表示し、そこからアップデート又は、削除できる機能をつけます。また、ボタンを押すと追加できるフォームも付け加えます。

配布しているコードは、今回の実装でプラスした部分のみの配布ですので、一連の流れを確認する場合は、前回の配布コードとあわせてお使いください。また、続編ということで、データベースのテーブルは前回のものを利用していますので、前回のものを参考にください。

編集画面

まず編集画面では、追加されたデータを読み込む必要があります。また、追加されたデータに関しては、今回の実装では、個別のイメージの削除ボタンと、イメージだけではなく、テキストも含めて行そのものを削除するボタンを設置しましす。これに関しては、ajaxで処理しますので、そのhtml部分にidを添付します。そして、それぞれのボタンを押すと、イメージ削除ならdel_img.phpで非同期処理され、ファイルの削除と、その部分のデータ名を空白にアップロードし、行そのものを削除する場合は、del_line.phpで非同期処理によりデータとファイルの両方を削除します。一方で、新しく追加するフォームについては、前回のindex.phpと同じ処理を行います。

【edit.php】

<?php

require_once("db.php");
$dbh = db_connect();

try{
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   $sql = "SELECT * FROM img_data WHERE user_id = 1 AND post_id = 1";
   $stmt = $dbh->query($sql);
   $data = $stmt->fetchAll();
   
}catch (PDOException $e){
   print('Error:'.$e->getMessage());
   die();
}

?>


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>SumidaiNET</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <style type="text/css">
        img {
            margin: 0 5px 5px 0;
            max-width: 160px;
            vertical-align: bottom;
        }
    </style>
</head>
<body>
<form action="update.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="1048576">

    <table>
        <thead>
        <tr>
       <th>テキスト</th>
        <th>画像</th>
        </tr>
        </thead>
        <tbody>

<?php
$num = 1;

foreach($data as $key){
  list($year, $month, $day) = preg_split('/[-: ]/', $key['date']);  
?>
        <tr  id="tr_<?=$num?>">
            <td><input type="text" name="text[]" value="<?=$key['text']?>"></td>
            <td id="td_<?=$num?>">
                <div id="view_<?=$num?>"></div>
                <?php if(!empty($key['img'])){ ?>
                <a href="http://sample.com/img_full/<?=$year ?>/<?=$month ?>/<?=$day?>/f<?=$key['img']?>"><img id="img_<?=$num?>" src="http://sample.com/img_med/<?=$year ?>/<?=$month ?>/<?=$day?>/m<?=$key['img']?>"/></a><br>
                <button class="del" rel= "<?=$year ?>/<?=$month ?>/<?=$day?>/<?=$key['img']?>"type="button">イメージ削除</button>
                <button class="del_line" rel= "<?=$year ?>/<?=$month ?>/<?=$day?>/<?=$key['img']?>/<?=$key['ID']?>"type="button">行削除</button>
                <?php } ?>
                <input type="file" id="file_<?=$num?>" name="img[]" accept="image/*" <?php if(!empty($key['img'])){ ?>style="display: none"<?php } ?>  >
                <input type="hidden" name="ID[]" value="<?=$key['ID']?>">
            </td>
        </tr>
<?php 
  $num ++;
} ?>
        </tbody>
        <tfoot>
        <tr>
            <td>
                <button id="add" type="button">追加</button><span id="reload"></span>
            </td>
        </tr>
        </tfoot>
    </table>
    <span id="message"></span>
    <input type="submit" name="send" value="送信">
</form>

<?php if(!empty($data[0]['ID'])){ ?>
<p><button type="button" onclick="location.href='del_b.php'">全て削除する</button></p>
<?php } ?>



<script type="text/javascript">
$(function () {

   var num = <?=$num -1 ?>;
   var count = document.querySelectorAll("td[id]").length;
   $(':hidden[name="row_length"]').val(count);
   var view_count = document.querySelectorAll("div[id]").length;

  //イメージ削除
  $("button.del").click(function(){
    var td = $(this).parent().attr('id');
    td = td.split("_");
    var data = $(this).attr('rel');
    data = data.split("/");
    $(this).replaceWith();

     $.ajax({
        url: 'del_img.php',
        type: 'POST',
        data:{ year:data[0],month:data[1],day:data[2],img:data[3] },
        timeout: 10000,
        dataType: 'text'
        })
      .done(function( data ) {
        $('#img_' + td[1]).parent().next().replaceWith();
        $('#img_' + td[1]).parent().replaceWith();
        $('#img_' + td[1]).replaceWith();
        $('#file_' + td[1]).show();
        })
      .fail(function( data ) {
        })
      .always(function( data ) {
     });

  });

  //テキスト・イメージ削除
  $("button.del_line").click(function(){
    var td = $(this).parent().attr('id');
    td = td.split("_");
    var data = $(this).attr('rel');
    data = data.split("/");
    $(this).replaceWith();

     $.ajax({
        url: 'del_line.php',
        type: 'POST',
        data:{ year:data[0],month:data[1],day:data[2],img:data[3],ID:data[4] },
        timeout: 10000,
        dataType: 'text'
        })
      .done(function( data ) {
        $('#tr_' + td[1]).replaceWith();
        })
      .fail(function( data ) {
        })
      .always(function( data ) {
     });

  });

   function fileChange(n) {
      var reader = new FileReader();
      document.getElementById('file_' + n).onchange = function (e) {
      reader.addEventListener('load', function (e) {
         $('#view_' + n).html('<img src="' + e.target.result + '" />');
      });
         reader.readAsDataURL(this.files[0]);
      }
   }

   $('button#add').click(function () {

      if(view_count ===5 ){
         $('#message').html('※ 追加フォームは' + view_count + 'つまでです。<br>');
       }else{
         count = count + 1;
         view_count = view_count + 1;
         var tr_row = '' +
           '<tr>' +
           '<td><input type="text" name="text[]"></td>' +
           '<td><div  id="view_' + count + '"></div><input type="file" id="file_' + count + '" name="img[]" accept="image/*" /></td>' +
           '</tr>';
       $(tr_row).appendTo($('table > tbody'));
       $('#reload').html('<input type="button" value="リロードする" onclick="window.location.reload();" /><br>');
       fileChange(count);
      }
  });

  for(var i = 1; i <= <?=$num -1 ?>; i++) {
     fileChange(i);
  }

});
</script>
</body>
</html>

アップロード処理

update.phpでは、前回のupload.phpと基本的な部分は同じです。ただ異なる点は、データベースの操作で、既存のデータについては、アップデートを行い、新たに追加されたフォームからのデータについては、インサートを行います。また、それらを区別するのは、idを持っているかどうかです。既存データに関しては、edit.phpの<input type=”hidden” name=”ID[]” value=”<?=$key[‘ID’]?>”>に添付しているので、それがあるないかで判断しています。

【update.php】

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>TEST</title>
</head>
<body>


<?php

$array_count = count($_POST['text']);

if($array_count > 5){
echo "5つまでです。";
exit;
}

define('IMAGES_DIR', dirname($_SERVER['SCRIPT_FILENAME']).'/');
define('MIDDLE_WIDTH', 200);
define('THUMBNAIL_WIDTH', 72);
//define('MAX_FILE_SIZE', 30720000); // 300KB = 1KB/1024bytes * 300

error_reporting(E_ALL & ~E_NOTICE);

//保存先フォルダの作成
$img_full = "img_full/".date("Y")."/".date("m")."/".date("d");
$img_med = "img_med/".date("Y")."/".date("m")."/".date("d");
$img_thu = "img_thu/".date("Y")."/".date("m")."/".date("d");

umask(0);  //0022回避

if(!file_exists($img_full)){
  mkdir( $img_full, 0777, TRUE );
}

if(!file_exists($img_med)){
  mkdir( $img_med, 0777, TRUE );
}

if(!file_exists($img_thu)){
  mkdir( $img_thu, 0777, TRUE );
}

//エラー確認・バリテーション(サイズ)・ファイル形式
for($i=0; $i<$array_count; $i++)
{
  if ($_FILES['img']['error'][$i] != UPLOAD_ERR_OK && $_FILES['img']['error'][$i] !=4) {
    echo "エラーが発生しました : ".$_FILES['img']['error'][$i] . "<br>";
  }

  if ($_FILES['img']['error'][$i]===2) {
    echo $_FILES['img']['name'][$i]." のファイルサイズが大きすぎます!<br>";
  }
}

//エラーありの場合処理を止める。
for($i=0; $i<$array_count; $i++)
{
  if ($_FILES['img']['error'][$i] != UPLOAD_ERR_OK && $_FILES['img']['error'][$i] !=4) {
    exit;
  }
}

for($i=0; $i<$array_count; $i++)
{
if(!empty($_FILES['img']['name'][$i]) ){

  $imagesize[$i] = getimagesize($_FILES['img']['tmp_name'][$i]);

  switch($imagesize[$i]['mime']){
    case 'image/gif':
        $ext[$i] = '.gif';
        break;
    case 'image/jpeg':
        $ext[$i] = '.jpg';
        break;
    case 'image/png':
        $ext[$i] = '.png';
        break;
    default:
        echo $_FILES['img']['name'][$i]."は画像ファイル(GIF/JPEG/PNG) ではありません!!<br>";
       $ext[$i] = 0;
    }
}
}

//ファイル形式が異なる場合処理を止める
for($i=0; $i<$array_count; $i++)
{
  if ($ext[$i] === 0) {
    exit;
  }
}

for($i=0; $i<$array_count; $i++)
{

if(!empty($_FILES['img']['name'][$i]) ){

  $imageFileName[$i] = sha1(time().mt_rand()) . $ext[$i];

  $imageFilePath[$i] = IMAGES_DIR. $img_full . '/f' . $imageFileName[$i];   //fullサイズリネイム

  echo $imageFilePath[$i]; //フルパス

  $rs[$i] = move_uploaded_file($_FILES['img']['tmp_name'][$i], $imageFilePath[$i]);

  if (!$rs[$i]) {
     echo 'no'. $i . 'could not upload!';
    exit;
  } 

  $width = $imagesize[$i][0];
  $height = $imagesize[$i][1];


  //MIDDLE画像を作成、保存

  if ($width > MIDEEL_WIDTH) {
    // 元ファイルを画像タイプによって作る
    switch($imagesize[$i]['mime']){
    case 'image/gif':
        $srcImage[$i] = imagecreatefromgif($imageFilePath[$i]);
        break;
    case 'image/jpeg':
        $srcImage[$i] = imagecreatefromjpeg($imageFilePath[$i]);
        break;
    case 'image/png':
        $srcImage[$i] = imagecreatefrompng($imageFilePath[$i]);
        break;
    }

    // 新しいサイズを作る
    $middleHeight[$i] = round($height * MIDDLE_WIDTH / $width);

    // 縮小画像を生成
    $middleImage[$i] = imagecreatetruecolor(MIDDLE_WIDTH, $middleHeight[$i]);
    imagecopyresampled($middleImage[$i], $srcImage[$i], 0, 0, 0, 0, 200, $middleHeight[$i], $width, $height);
    
    // 縮小画像を保存する
    switch($imagesize[$i]['mime']){
    case 'image/gif':
        imagegif($middleImage[$i], IMAGES_DIR. $img_med .'/m'.$imageFileName[$i]);  //fullサイズリネイム
        break;
    case 'image/jpeg':
        imagejpeg($middleImage[$i], IMAGES_DIR. $img_med .'/m'.$imageFileName[$i]);
        break;
    case 'image/png':
        imagepng($middleImage[$i], IMAGES_DIR. $img_med .'/m'.$imageFileName[$i]);
        break;
    }

  } //if MIDDLE



  //サルネイム
  if (MIDDLE_WIDTH > THUMBNAIL_WIDTH) {
    // 元ファイルを画像タイプによって作る
    switch($imagesize[$i]['mime']){
    case 'image/gif':
        $srcImage[$i] = imagecreatefromgif($imageFilePath[$i]);
        break;
    case 'image/jpeg':
        $srcImage[$i] = imagecreatefromjpeg($imageFilePath[$i]);
        break;
    case 'image/png':
        $srcImage[$i] = imagecreatefrompng($imageFilePath[$i]);
        break;
    }
    // 新しいサイズを作る
    $thumbHeight[$i] = round($height * THUMBNAIL_WIDTH / $width);

    // 縮小画像を生成
    $thumbImage[$i] = imagecreatetruecolor(THUMBNAIL_WIDTH, $thumbHeight[$i]);
    imagecopyresampled($thumbImage[$i], $srcImage[$i], 0, 0, 0, 0, 72, $thumbHeight[$i], $width, $height);
    
    // 縮小画像を保存する
    switch($imagesize[$i]['mime']){
    case 'image/gif':
        imagegif($thumbImage[$i], IMAGES_DIR. $img_thu .'/t'.$imageFileName[$i]);  //tサイズリネイム
        break;
    case 'image/jpeg':
        imagejpeg($thumbImage[$i], IMAGES_DIR. $img_thu .'/t'.$imageFileName[$i]);
        break;
    case 'image/png':
        imagepng($thumbImage[$i], IMAGES_DIR. $img_thu .'/t'.$imageFileName[$i]);
        break;
    }

 } //if サルネイム

}
}

//データベースへの更新

define("USER_ID", 1);
define("POST_ID", 1);

require_once("db.php");
$dbh = db_connect();

try{

for($i=0; $i<$array_count; $i++)
{
   $sql = "UPDATE img_data SET text= :text WHERE user_id = :user_id AND post_id = :post_id AND ID = :ID";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':text', $_POST['text'][$i]);
   $stmt->bindValue(':user_id', USER_ID);
   $stmt->bindValue(':post_id', POST_ID);
   $stmt->bindValue(':ID', $_POST['ID'][$i]);
   $stmt->execute();

   if($_FILES['img']['size'][$i] != 0 && isset($_POST['ID'][$i])){
   $sql = "UPDATE img_data SET img=:img WHERE user_id = :user_id AND post_id = :post_id AND ID = :ID";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':img',$imageFileName[$i]);
   $stmt->bindValue(':user_id',USER_ID);
   $stmt->bindValue(':post_id', POST_ID);
   $stmt->bindValue(':ID', $_POST['ID'][$i]);
   $stmt->execute();
   }else{
   $sql = "INSERT INTO img_data (user_id, post_id, text, img) VALUES(:user_id, :post_id, :text, :img)";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':user_id',USER_ID);
   $stmt->bindValue(':post_id', POST_ID);
   $stmt->bindValue(':text',$_POST['text'][$i]);
   $stmt->bindValue(':img', $imageFileName[$i]);
   $stmt->execute();
   }

}

}catch (PDOException $e){
   print('Error:'.$e->getMessage());
   die();
}

header('Location: http://sample.com/edit.php');
exit;
?>
</body>
</html>

削除処理

del_img.phpでは、edit.phpのイメージ削除ボタンを押した際に、こちらにajaxで非同期で、<button class=”del” rel= “<?=$year ?>/<?=$month ?>/<?=$day?>/<?=$key[‘img’]?>”type=”button”>イメージ削除</button> のrelにある情報がPOST送信されるようになっています。その情報を元に、画像保存先を判定して、削除を行います。また、同時にデータベースの画像名の更新を行います。

【del_img.php】

<?php
$year = $_POST['year'];
$month = $_POST['month'];
$day = $_POST['day'];
$img = $_POST['img'];

unlink( "img_full/{$year}/{$month}/{$day}/f{$img}" );
unlink( "img_med/{$year}/{$month}/{$day}/m{$img}" );
unlink( "img_thu/{$year}/{$month}/{$day}/t{$img}" );

define("USER_ID", 1);
define("POST_ID", 1);

require_once("db.php");
$dbh = db_connect();

try{
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   $sql = "UPDATE img_data SET img='' WHERE user_id = :user_id AND post_id = :post_id AND img = :img";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':user_id',USER_ID);
   $stmt->bindValue(':post_id', POST_ID);
   $stmt->bindValue(':img',$img);
   $stmt->execute();

}catch (PDOException $e){
   print('Error:'.$e->getMessage());
   die();
}
?>

【del_line.php】
del_line.phpに関しても、del_img.phpと同様の処理で、一点異なる点は、既に、イメージ削除ボタンが押されている場合と、押されていない場合は異なる動きをするということです。既にイメージが削除されている場合は、画像名を指定して、その行を削除することができないので、もし、画像が既に削除されている場合には、その行のIDを使います。

<?php

$year = $_POST['year'];
$month = $_POST['month'];
$day = $_POST['day'];
$img = $_POST['img'];
$id = $_POST['ID'];

$img_full = "img_full/".$year."/".$month."/".$day."/f".$img;
$img_med = "img_med/".$year."/".$month."/".$day."/m".$img;
$img_thu = "img_thu/".$year."/".$month."/".$day."/t".$img;

if(file_exists($img_full)){
  unlink( "img_full/{$year}/{$month}/{$day}/f{$img}" );
}

if(file_exists($img_med)){
unlink( "img_med/{$year}/{$month}/{$day}/m{$img}" );
}

if(file_exists($img_thu)){
unlink( "img_thu/{$year}/{$month}/{$day}/t{$img}" );
}


define("USER_ID", 1);
define("POST_ID", 1);

require_once("db.php");
$dbh = db_connect();

try{
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

   if(file_exists($img_thu)){

   $sql = "DELETE FROM img_data WHERE user_id = :user_id AND post_id = :post_id AND img = :img";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':user_id',USER_ID);
   $stmt->bindValue(':post_id', POST_ID);
   $stmt->bindValue(':img',$img);

   }else{

   $sql = "DELETE FROM img_data WHERE id = :id";
   $stmt = $dbh->prepare($sql);
   $stmt->bindValue(':id',$id);

   }

   $stmt->execute();

}catch (PDOException $e){
   print('Error:'.$e->getMessage());
   die();
}

?>

感想

今回の実装では、なかり手早にやったので、極力見やすくは心がけていますが、かなりhtml部分は成り行きで、idを添付しています。従って、ごちゃごちゃして見えます。実は、もっと見直せば短縮できるはずです。もし、参考にされている方なら、改良して使用してみてください。