PHP & JavaScript 게시판 사이트 제작 3

저번 글에서는 글 목록 및 글쓰기 기능을 구현하는 코드에 대하여 설명하였다. 이번 글에서는 글 페이지 및 글 수정, 삭제를 구현하는 코드에 대하여 설명하겠다. 글 페이지에는 댓글 쓰기, 수정, 삭제 기능도 포함되어있다. 모든 디자인은 부트스트랩을 사용했다.


1. 댓글 DB 생성 및 설정

댓글 작성 시 댓글 정보가 저장되는 DB를 생성한다. 댓글 내용은 300자로 제한한다.

CREATE TABLE `board_reply` (
	`no` INT(11) NOT NULL AUTO_INCREMENT,
	`board_no` INT(11) NOT NULL,
	`reply_content` VARCHAR(300) NOT NULL COLLATE 'utf8_general_ci',
	`reply_writer` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
	`reply_writer_id` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
	`reply_date` DATETIME NOT NULL,
	`delYn` ENUM('Y','N') NOT NULL DEFAULT 'N' COLLATE 'utf8_general_ci',
	PRIMARY KEY (`no`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=7
;

2. 함수 추가 작성

저번 글에서 작성한 get_board_data 함수의 foreach문 내에 글 목록에서 출력할 댓글 수를 불러오는 코드를 추가한다.

글 페이지 조회 시 조회수를 업데이트하고 COOKIE 값에 글 조회 여부를 저장하는 함수를 추가하고 댓글 데이터를 불러오는 함수도 추가한다.

<?php
    // get_board_data 함수의 foreach문 내에 추가
    $reply_cnt_sql = "select count(*) as cnt from board_reply where delYn = 'N' and board_no = '".$row["no"]."'";
    $reply_cnt_result = $mysqli->query($reply_cnt_sql);
    $reply_cnt_row = $reply_cnt_result->fetch_array();

    $row["board_reply_cnt"] = $reply_cnt_row["cnt"];
?>
<?php
    function update_board_cnt($no){
        global $mysqli;
        $sql = "UPDATE board set board_cnt=board_cnt+1 where no='".$no."'";
        $result = $mysqli->query($sql);
        setcookie("viewed".$no, "viewed", time() + 60*60*24, "/");
    }

    function get_reply_data($where){
        global $mysqli;

        $sql = "select * from board_reply where delYn = 'N' ".$where." order by no asc";
        $result = $mysqli->query($sql);

        if($result){
            while($row = $result->fetch_array()){      
                $rows[] = $row;
            }
        } 

        $data = array();
        $data["rows"] = $rows;
        
        return $data;
    }
?>

3. 글 목록 폼 댓글 수 추가 작성

저번 글에서 작성한 글 목록 폼에서 제목을 불러오는 코드 다음에 댓글 수를 추가한다.

<span class="fs-5 col-9"><b><?=$row["board_title"]?></b><span class="opacity-85"> <?php if(!empty($row["board_reply_cnt"])) echo "[".$row["board_reply_cnt"]."]"; ?></span></span>

4. 글 페이지 작성

COOKIE 값의 여부를 검사해서 조회수를 업데이트하고 글을 불러온다.

SESSION 값에 저장되어있는 id가 작성자의 id와 일치한다면 글 수정 및 삭제 버튼을 출력한다. 그 다음 댓글 목록과 작성 폼을 불러온다. 댓글도 글과 같이 SESSION 값으로 확인해서 댓글 작성자면 댓글 수정 및 삭제 버튼을 출력한다.

Display none으로 숨긴 부분은 댓글 수정 폼이며 댓글 수정 버튼 클릭 시 표시되도록 동작 구현 파일에서 구현할 것이다.

<?php
    if(!$_COOKIE["viewed".$_GET["no"]]){
        $_COOKIE["viewed".$_GET["no"]] = "";
        update_board_cnt($_GET["no"]);
    }

    $where = " and no = '".$_GET["no"]."'"; 
    $row = get_board_data($where, "view");
	if(!empty($row)){
?>
        <div class="list-group w-auto mb-2">
            <div class="list-group-item list-group-item-action py-3">
                <div class="d-flex w-100 justify-content-between">
                    <div class="w-100">
                        <b class="fs-2"><?=$row["board_title"]?></b><br>
                        <span class="fs-5 opacity-85"><?=$row["board_writer"]?></span><br>
                        <span class="fs-6 opacity-85" style="margin-right: 9px;"><?=$row["board_date"]?></span><span class="fs-6 opacity-85">조회 <?=$row["board_cnt"]?></span><br><hr>
                        <span id="post_content" class="mb-0 opacity-85"><?=$row["board_content"]?></span>
                    </div>
                </div>
            </div>
         </div>
<?php 
    }
?>
<?php
    if($_SESSION["id"] == $row["board_writer_id"]){ 
?>
        <div class="d-flex justify-content-end">
            <button type="button" class="btn btn-success" onclick="location.href='글쓰기 폼 경로?no=<?=$_GET['no']?>'">수정</button>&nbsp;
            <button type="button" class="btn btn-secondary" id="del_btn" del_no="<?=$_GET["no"]?>">삭제</button>
        </div>
<?php
    }
?>
<div class="mb-2">
    <span class="fs-3">댓글</span>
</div>
<div class="list-group w-auto">
    <div class="list-group-item list-group-item-action py-3">
        <div class="d-flex w-100 justify-content-between">
            <div class="form-group row w-100">
            <?php
                $where = " and board_no = '".$_GET["no"]."'"; 
                $data = get_reply_data($where);
                if(!empty($data["rows"])){
            ?>
                    <hr>
            <?php
                    foreach($data["rows"] as $reply_row){
            ?>
                        <div class="mb-3">
                            <div class="mb-2">
                                <span class="fs-5 opacity-85"><?=$reply_row["reply_writer"]?></span>
                            </div>
                            <div id="mod_reply_form_org_<?=$reply_row["no"]?>">
                                <span class="fs-5 opacity-85" id="reply_content_no_<?=$reply_row["no"]?>" style="white-space:pre;"><?=$reply_row["reply_content"]?></span><br>
                                <span class="fs-6 opacity-85"><?=$reply_row["reply_date"]?></span><br>
                            </div>
                            <div id="mod_reply_form_<?=$reply_row["no"]?>" style="display:none;">
                                <div class="input-group">
                                    <textarea class="form-control" id="mod_reply_content_<?=$reply_row["no"]?>" rows="2"><?=$reply_row["reply_content"]?></textarea>
                                    <button type="button" id="mod_reply_btn_<?=$reply_row["no"]?>" class="btn btn-success" board_no="<?=$_GET["no"]?>">수정</button>
                                    <button type="button" id="mod_reply_cancel_btn_<?=$reply_row["no"]?>" class="btn btn-secondary">취소</button></div>
                            </div>
                        <?php
                            if($_SESSION["id"] == $reply_row["reply_writer_id"]){ 
                        ?>
                                <div id="reply_btn_form_<?=$reply_row["no"]?>" class="d-flex justify-content-end">
                                    <button type="button" class="btn btn-success btn-sm" id="mod_reply_form_btn_<?=$reply_row["no"]?>">수정</button>&nbsp;
                                    <button type="button" class="btn btn-secondary btn-sm" id="del_reply_btn_<?=$reply_row["no"]?>" board_no="<?=$_GET["no"]?>">삭제</button>
                                </div>
                        <?php
                            }
                        ?>
                        </div>
                        <hr>
            <?php
                    }
                }
            ?>
                <div class="mb-2">
                    <span class="fs-5 opacity-85"><?=$_SESSION["name"]?></span>
                </div>
                <div class="input-group">
                    <textarea class="form-control" id="reply_content" rows="2" placeholder="댓글을 남겨보세요."></textarea>
                    <button type="button" id="reply_btn" class="btn btn-primary" board_no="<?=$_GET["no"]?>">등록</button>
                </div>
            </div>
        </div>
    </div>
</div>

<script type="text/javascript" src="글 페이지 동작 구현 파일 경로"></script>

5. 글쓰기 폼 수정

저번 글에서 작성한 글쓰기 폼에 글 수정 버튼 클릭 시 글 데이터를 불러올 수 있게 수정한다. 작성자가 아닌 회원이 URL로 들어올 수 있으므로 작성자가 아니라면 접근을 제한한다.

<?php
    if($_GET["no"]){
        $where = " and no = '".$_GET["no"]."'"; 
        $row = get_board_data($where, "view");
        
        if($row["board_writer_id"] != $_SESSION["id"] || $row["board_writer"] != $_SESSION["name"]){
            echo "<script>alert('잘못된 접근입니다.');history.back();</script>";
        }
    }
?>
<script type="text/javascript" src="HuskyEZCreator.js 경로"></script>
<form id="writefrm" action="데이터 처리 파일 경로" method="post" enctype="multipart/form-data">
    <?php
        if($_GET["no"]){
    ?>
            <input type="hidden" name="mod_no" value="<?=$row["no"]?>">
    <?php
        }
    ?>
    <textarea class="form-control mb-1" name="board_title" id="board_title" rows="1" maxlength="100" placeholder="제목을 입력해 주세요."><?php if($_GET["no"]) echo $row["board_title"];?></textarea>
    <textarea class="form-control" name="board_content" id="ir1" rows="20" style="width:100%;"><?php if($_GET["no"]) echo $row["board_content"];?></textarea>
</form><br>
<div class="pagination justify-content-end">
    <button type="button" class="btn btn-secondary" onclick="location.href='글 목록 폼 경로'">취소</button>&nbsp;
    <button type="button" id="write_btn" class="btn 
        <?php
            if($_GET["no"]){
                echo "btn-success";
            }else{
                echo "btn-primary";
            }
        ?>
    ">
        <?php
            if($_GET["no"]){
                echo "수정";
            }else{
                echo "등록";
            }
        ?>
    </button>
</div>

<script type="text/javascript" src="글쓰기 동작 구현 파일 경로"></script>

6. 글 페이지 동작 구현 파일 작성

요소의 id 값 뒤에 있는 글, 댓글 번호를 불러와서 각 요소를 따로 제어한다. 삭제 버튼 클릭 시 메시지를 띄우고, 확인을 클릭하면 계속 진행한다. 필요한 값들은 임의의 폼을 만들어 모두 전송한다.

댓글 수정 버튼 클릭 시 다른 요소들은 모두 숨기고 댓글 수정 폼을 표시한다. 댓글 수정 취소 버튼 클릭 시 반대로 댓글 수정 폼을 숨기고 나머지 요소들은 다시 표시한다.

$("#del_btn").click(function(){
    if(confirm("글을 삭제하시겠습니까?")){
        var del_no = $("#del_btn").attr("del_no");

        var delForm = $("<form></form>");
        delForm.attr("method", "Post");
        delForm.attr("action", "데이터 처리 파일 경로");
    
        delForm.append($("<input/>", {type: "hidden", name: "del_no", value: del_no}));
    
        delForm.appendTo("body");
        delForm.submit();
    }
});

$('#reply_btn').click(function(){
    if($("#board_reply").val() == ""){
        alert("댓글을 입력해 주세요.");
        return false;
    }

    var board_no = $("#reply_btn").attr("board_no");
    var reply_content = $("#reply_content").val();

    var replyForm = $("<form></form>");
    replyForm.attr("method", "Post");
    replyForm.attr("action", "데이터 처리 파일 경로");
    
    replyForm.append($("<input/>", {type: "hidden", name: "board_no", value: board_no}));
    replyForm.append($("<input/>", {type: "hidden", name: "reply_content", value: reply_content}));
    
    replyForm.appendTo("body");
    replyForm.submit();
});

$(document).on("click", "button[id^='mod_reply_form_btn_']", function(){
    var reply_no = $(this).attr("id").split("mod_reply_form_btn_"); 
    $("#mod_reply_form_org_"+reply_no[1]).hide();
    $("#reply_btn_form_"+reply_no[1]).hide();
    $("#reply_btn_form_"+reply_no[1]).removeClass("d-flex");
    $("#mod_reply_form_"+reply_no[1]).show();
});

$(document).on("click", "button[id^='mod_reply_cancel_btn_']", function(){
    var reply_no = $(this).attr("id").split("mod_reply_cancel_btn_"); 
    $("#mod_reply_form_org_"+reply_no[1]).show();
    $("#reply_btn_form_"+reply_no[1]).show();
    $("#reply_btn_form_"+reply_no[1]).addClass("d-flex");
    $("#mod_reply_form_"+reply_no[1]).hide();
});

$(document).on("click", "button[id^='mod_reply_btn_']", function(){
    var mod_reply_no = $(this).attr("id").split("mod_reply_btn_"); 
    var mod_reply_content = $("#mod_reply_content_"+mod_reply_no[1]).val();
    var board_no = $("#del_reply_btn_"+mod_reply_no[1]).attr("board_no");

    var modForm = $("<form></form>");
    modForm.attr("method", "Post");
    modForm.attr("action", "데이터 처리 파일 경로");

    modForm.append($("<input/>", {type: "hidden", name: "mod_reply_no", value: mod_reply_no[1]}));
    modForm.append($("<input/>", {type: "hidden", name: "reply_content", value: mod_reply_content}));
    modForm.append($("<input/>", {type: "hidden", name: "board_no", value: board_no}));

    modForm.appendTo("body");
    modForm.submit();
});

$(document).on("click", "button[id^='del_reply_btn_']", function(){
    if(confirm("댓글을 삭제하시겠습니까?")){
        var del_reply_no = $(this).attr("id").split("del_reply_btn_"); 
        var board_no = $("#del_reply_btn_"+del_reply_no[1]).attr("board_no");

        var delForm = $("<form></form>");
        delForm.attr("method", "Post");
        delForm.attr("action", "데이터 처리 파일 경로");

        delForm.append($("<input/>", {type: "hidden", name: "del_reply_no", value: del_reply_no[1]}));
        delForm.append($("<input/>", {type: "hidden", name: "board_no", value: board_no}));

        delForm.appendTo("body");
        delForm.submit();    
    }
});

7. 데이터 처리 파일 수정

저번 글에서 작성한 데이터 처리 파일을 다음과 같이 수정 및 추가한다. mod_no, mod_reply_no에 대한 예외 처리는 반드시 한다. 예외 처리를 하지 않으면 쓰기와 수정이 동시에 실행된다.

글 수정, 삭제 및 댓글 쓰기, 수정, 삭제 시 값이 모두 잘 들어왔는지 확인 후 DB에 데이터를 저장 또는 업데이트하고 글 목록 폼 또는 글 페이지로 이동한다.

<?php
    global $mysqli;

    if(empty($_POST["mod_no"])){
        if(!empty($_POST["board_title"]) && !empty($_POST["board_content"])){
            $column = "";
            $values = "";
            foreach($_POST as $key => $value){
                $column .= $key.", ";
                $values .= "'".$value."', ";
            }
            $sql = "insert into board (".$column." board_writer, board_writer_id, board_date) values (".$values." '".$_SESSION["name"]."', '".$_SESSION["id"]."', now())";
            $result = $mysqli->query($sql);
            if($result){
                echo "<script>alert('글이 등록되었습니다.');location.replace('글 목록 폼 경로');</script>";
            }else{
                echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
            }
        }
    }

    if(!empty($_POST["mod_no"])){
        if(!empty($_POST["board_title"]) && !empty($_POST["board_content"])){
            $update_sql = "";

            foreach($_POST as $key => $value){
                if($key == "mod_no") continue;
                $update_sql .= $key."='".$value."', ";
            }

            $update_sql = substr($update_sql, 0, -2);

            $sql = "update board set ".$update_sql." where no = '".$_POST["mod_no"]."'";
            $result = $mysqli->query($sql);
            if($result){
                echo "<script>alert('글이 수정되었습니다.');location.replace('글 페이지 경로?no=".$_POST["mod_no"]."');</script>";
            }else{
                echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
            }
        }
    }

    if(!empty($_POST["del_no"])){
        $sql = "update board set delYn = 'Y' where no = '".$_POST["del_no"]."'";
        $result = $mysqli->query($sql);
        if($result){
            echo "<script>alert('글이 삭제되었습니다.');location.replace('글 목록 폼 경로');</script>";
        }else{
            echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
        }
    }

    if(empty($_POST["mod_reply_no"])){
        if(!empty($_POST["reply_content"]) && !empty($_POST["board_no"])){
            $column = "";
            $values = "";

            foreach($_POST as $key => $value){
                $column .= $key.", ";
                $values .= "'".$value."', ";
            }

            $sql = "insert into board_reply (".$column." reply_writer, reply_writer_id, reply_date) values (".$values." '".$_SESSION["name"]."', '".$_SESSION["id"]."' ,now())";
            $result = $mysqli->query($sql);
            if($result){
                echo "<script>alert('댓글이 등록되었습니다.');location.replace('글 페이지 경로?no=".$_POST["board_no"]."');</script>";
            }else{
                echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
            }
        }
    }


    if(!empty($_POST["mod_reply_no"])){
        if(!empty($_POST["reply_content"]) && !empty($_POST["board_no"])){
            $update_sql = "";

            foreach($_POST as $key => $value){
                if($key == "mod_reply_no" || $key == "board_no")  continue;
                $update_sql .= $key."='".$value."', ";
            }

                $update_sql = substr($update_sql, 0, -2);

                $sql = "update board_reply set ".$update_sql." where no = '".$_POST["mod_reply_no"]."'";
                $result = $mysqli->query($sql);
                if($result){
                    echo "<script>alert('댓글이 수정되었습니다.');location.replace('글 페이지 경로?no=".$_POST["board_no"]."');</script>";
                }else{
                    echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
                }
        }
    }

    if(!empty($_POST["del_reply_no"]) && !empty($_POST["board_no"])){
        $sql = "update board_reply set delYn = 'Y' where no = '".$_POST["del_reply_no"]."'";
        $result = $mysqli->query($sql);
        if($result){
            echo "<script>alert('댓글이 삭제되었습니다.');location.replace('글 페이지 경로?no=".$_POST["board_no"]."');</script>";
        }else{
            echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
        }
    }
?>

댓글 쓰기 후 페이지 이동이 잘 되는지 확인하고 글 및 댓글 수정, 삭제도 잘 동작하는지 확인한다.


위로 스크롤