追加ボタンで画像フォームを増やしていく(編集画面・更新処理)
実装概要
今回は、前回の追加ボタンで画像フォームを増やしていく(データベース挿入・表示)の続編です。前回は、フォームからのテキストデータと画像名をデータベースに登録し、フォルダを作り、そこに画像をアップロードしました。そして、それらの一覧を表示しました。今回は、その続きで、テキストや画像の編集を行う画面を作っていきます。画像編集画面では、データの読み込みをおこない、それらを表示し、そこからアップデート又は、削除できる機能をつけます。また、ボタンを押すと追加できるフォームも付け加えます。
配布しているコードは、今回の実装でプラスした部分のみの配布ですので、一連の流れを確認する場合は、前回の配布コードとあわせてお使いください。また、続編ということで、データベースのテーブルは前回のものを利用していますので、前回のものを参考にください。
編集画面
まず編集画面では、追加されたデータを読み込む必要があります。また、追加されたデータに関しては、今回の実装では、個別のイメージの削除ボタンと、イメージだけではなく、テキストも含めて行そのものを削除するボタンを設置しましす。これに関しては、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を添付しています。従って、ごちゃごちゃして見えます。実は、もっと見直せば短縮できるはずです。もし、参考にされている方なら、改良して使用してみてください。