【PHP簡易掲示板2】投稿機能


# 必要なページをリストアップする

| ページ | ファイル名 
|:-----------|------------:|:------------:|
| 投稿一覧表示兼投稿画面 | posts.php 
| 投稿編集画面 | post_edit.php
| 投稿フォーム【共通化】 | post_form.php
| 投稿【共通化】 | post_info.php


投稿削除は画面なしです。
投稿一覧画面で削除処理をします。

# 投稿のテーブルの作成

```
CREATE TABLE `posts` (
  `id` int(255)  NOT NULL AUTO_INCREMENT,
  `title` varchar(50) NOT NULL,
  `content` text NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `user_id` int(255) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

user_id カラムをusersテーブルのidカラムと紐づけます。（外部キー）

```
ALTER TABLE `posts` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;
```

# 投稿フォーム


```post_form.php
<div class="container">
    <h1><?php print( TEXT );?></h1>
    <form method="POST">
        <div class="form-group">
            <label for="title">タイトル</label>
            <input type="text" class="form-control" placeholder="タイトルを30文字以内で入力してください">
        </div>
        <div class="form-group">
            <label for="content">本文</label>
            <textarea class="form-control" rows="5" name="content" placeholder="本文を200文字以内で入力してください"></textarea>
        </div>
        <button type="submit" class="btn btn-info">Submit</button>
    </form>
</div>
```

# 投稿画面

```posts.php
<?php 

include ( "dbconfig.php" );
include ( "session.php" );
include ( "posts.php" );
include ( "navbar.php" );

// submitボタンが押された場合の処理
if ( isset( $_POST['action'] ) ) {
    post_register( $_POST['title'], $_POST['content'] );
}

define( "TEXT", "新規投稿" );

require ( get_partials_dir() . "/posts_form.php" );
```

# 投稿処理

```post.php
<?php 

function post_register( $title, $content ) {
    
    $errors = post_validation( $title, $content );

    if ( count( $errors) == 0 ) {
        post_insert( $title, $content );
    }

    error_display( $errors );
}

function post_validation( $title, $content ) {

    $errors = array();

    if ( ! mb_strlen( trim( $title ) ) ) {
        $errors[] = "タイトルが空です";
    } else if ( mb_strlen( trim( $title ) ) > 30 ) {
        $errors[] = "タイトルは30文字以内で入力してください";
    } else if ( ! isUniq( 'posts', 'title', trim( $title ) ) ) {
        $errors[] = "違うタイトルを指定してください";
    } else if ( is_numeric( trim( $title ) ) ) {
        $errors[] = "タイトルは文字列でなくてはいけません";
    }

    if ( ! mb_strlen( trim( $content ) ) ) {
        $errors[] = "本文が空です";
    } else if ( mb_strlen( trim( $content ) ) > 200 ) {
        $errors[] = "本文は200文字以内で入力してください";
    } else if ( is_numeric( trim( $content ) ) ) {
        $errors[] = "本文は文字列でなくてはいけません";
    }

    return $errors;
}

function post_insert( $title, $content ) {

    $title = escape( $title );
    $content = escape( $content );

    $now = get_current_datetime();
    $user_id = session_get( 'user_id' );

    $query = "
        INSERT INTO posts
            ( title, content, created_at, updated_at, user_id
        ) VALUES (
            '$title', '$content', '$now', '$now', '$user_id' )
        ";

    query( $query );

    print 'ユーザの作成に成功しました';
}   
```

# 投稿一覧表示処理

`SELECT *FROM posts` で全件レコードを抽出します。

```post.php

function post_all() {
    $query = "
        SELECT * FROM posts 
        ORDER BY created_at DESC
        ";

    $result = query( $query );

    $posts = array();

    while ($row = $result->fetch_assoc()) {
        $posts[] = $row;
    }

    $result->close();
    
    return $posts;
}
```

投稿画面でこの関数を呼び出します。（投稿部分は何度も使用するので外部ファイルに保存してそれを読みこみます。）

追記

```posts.php

$posts = post_all();

foreach ( $posts as $post ) {
    require ( "post_info.php" );
}
```

```post_info.php
<div class="container">
    <div class="card card--extend">
    <div class="card-body">
        <h1 class="card-title"><?php print h( $post['title'] ); ?></h1>
        <p class="card-body"><?php print h( $post['content'] ); ?></p>
        <!-- // ログインユーザの投稿の場合のみ、編集ボタン、削除ボタンを設置します。 -->
        <?php if ( isCurrentUserPost( $post['id'] ) ): ?>
        <a class="btn btn-info" href="post_edit.php?id=<?php print h( $post['id'] ); ?>" role="button">編集する</a>
        <form action="" method="post">
        <input type="hidden" name="post_id" value="<?php print h( $post['id'] ); ?>">
        <input type="submit" value="削除する" name="post_delete" class="btn btn-danger" onsubmit="return check()">
        <?php endif; ?>
        </form>
    </div>
    </div>
</div>
```

# 投稿削除機能

ログインユーザの投稿であるかどうかを判定してから削除処理をします。

追記

```post.php
function post_delete( $post_id ) {

    $post_id = escape( $post_id );
    
    if ( ! isCurrentUserPost( $post_id ) ) {
        header( "Location: posts.php" );
        exit();
    }

    $user_id = session_get( 'user_id' );

    $query ="
        DELETE FROM posts 
        WHERE id = '$post_id'
        AND user_id = '$user_id'
        ";

    query( $query );

    message_display( 'success', '削除に成功しました' );
}

function isCurrentUserPost( $post_id ) {

    $post_id = escape( $post_id );
    $user_id = session_get( 'user_id' );

    $query ="
        SELECT * FROM posts
        WHERE id = '$post_id'
        AND user_id = '$user_id'
        ";

    $result = query( $query );

    if ( mysqli_num_rows( $result ) == 1 ) {
        $result->close();
        return true;
    }

    $result->close();
    return false;
}
```

# 投稿編集機能

```post_edit.php
<?php

include ( "dbconfig.php" );
include ( "common.php" );
include ( "session.php" );
include ( "post.php" );
include ( "navbar.php" );

// getパラメータでidが入力されていてそれが数値の場合
if ( isset( $_REQUEST['id'] ) && is_numeric( $_REQUEST['id'] ) )  {
    post_edit( $_REQUEST['id'] );
    require ( "post_form.php" );
} else {
    header( "Location: posts.php" );
    exit();    
}
```


```post.php
function post_edit( $post_id ) {
    
    if ( ! isCurrentUserPost( $post_id ) ) {
        header( "Location: posts.php" );
        exit();
    }

    $post = get_post( $post_id );

}

function get_post( $post_id ) {
    $mysqli = get_db();

    $post_id = escape( $post_id );

    $query = "SELECT * FROM posts 
        WHERE id = '$post_id'
        ";
    
    $result = query( $query );

    $post = array();

    while ( $row = $result->fetch_assoc() ) {
        $post['id'] = $row['id'];
        $post['title'] = $row['title'];
        $post["content"] = $row["content"];
        $post["created_at"] = $row["created_at"];
        $post["updated_at"] = $row["updated_at"];
        $post["user_id"] = $row["user_id"];
    }

    return $post;
}
```

投稿フォームのコードを、編集画面では編集しようとしている投稿の値をplaceholderの初期値にします。

```post_info.php
<div class="container">
    <div class="card">
        <div class="card-header"><h1><?php print( TEXT );?></h1></div>
        <div class="card-body">
            <form method="POST">
                <div class="form-group">
                    <label for="title">タイトル</label>
                    <input type="text" class="form-control" name="title" placeholder="タイトルを30文字以内で入力してください" 
                    <?php if ( isset( $_POST['title'] ) ): ?> 
                    value="<?php print h( $_POST['title'] ); ?>"
                    <?php elseif ( isset( $post['title'] ) ): ?>
                    value="<?php print h( $post['title'] ); ?>"
                    <?php else: ?>
                    value=""
                    <?php endif; ?>
                     "/required>
                </div>
                <?php if ( isset( $_POST['content'] ) ): ?> 
                <?php $textarea_value= $_POST['content']; ?>
                <?php elseif ( isset( $post['content'] ) ): ?>
                <?php $textarea_value= $post['content']; ?>
                <?php else: ?>
                <?php $textarea_value= ''; ?>
                <?php endif; ?>
                <div class="form-group">
                    <label for="content">本文</label>
                    <textarea placeholder="本文を200文字以内で入力してください" rows="5" class="form-control" name="content" 
                    /required><?php print h( $textarea_value ); ?></textarea>
                </div>
                <button type="submit" class="btn btn-info" name="action">Submit</button>
            </form>
        </div>
    </div>
</div>
```
修正（post投稿時とupdate時は同じ処理が多いので、一つの関数にまとめます。第一引数で投稿なのか、更新なのか受け取ります。）

```post.php
function post_insertOrUpdate( $order, $title, $content, $post_id =null ) {

    $title = escape( $title );
    $content = escape( $content );

    $now = get_current_datetime();
    $user_id = session_get( 'user_id' );
    
    $errors = post_validation( $title, $content, $post_id );

    if ( count( $errors ) > 0 ) {
        error_display( $errors );
        return;
    }

    // create
    if ( $order == 'insert' ) {
        $query = "
            INSERT INTO posts
                ( title, content, created_at, updated_at, user_id
            ) VALUES (
                '$title', '$content', '$now', '$now', '$user_id' )
            ";

        $message = '新規投稿に成功しました';
    } else if ( $order == 'update' ) {

        if ( ! isCurrentUserPost( $post_id ) ) {
            header( "Location: posts.php" );
            exit(); 
        }

        $query = "
        UPDATE posts SET 
            title = '$title',
            content = '$content',
            updated_at = '$now'
        WHERE id = '$post_id'
        AND user_id = '$user_id'
        ";

        $message = '投稿の編集に成功しました';
    } 

    query( $query );
    message_display( 'success', $message );
}   
```
入力チェックを担当する関数は以下のようにします

```post.php
// updateの際は、引数にpostのidを指定する
function post_validation( $title, $content, $post_id = null ) {

    $errors = array();

    // updateの際は、編集する投稿の情報を先に取得する
    if ( $post_id ) {
        $post = get_post( $post_id );
    } else {
        $post['title'] = null;
    }

    if ( ! mb_strlen( trim( $title ) ) ) {
        $errors[] = "タイトルが空です";
    } else if ( mb_strlen( trim( $title ) ) > 30 ) {
        $errors[] = "タイトルは30文字以内で入力してください";
    } else if ( ! isUniq( 'posts', 'title', trim( $title ) ) // 第一条件は、入力したタイトルが一意の場合,true
                && $title != $post['title'] ) { // 第二条件はinsert時は常にtrue, update時は編集するpostとタイトルを指定した場合true
        $errors[] = "違うタイトルを指定してください";
    } else if ( is_numeric( trim( $title ) ) ) {
        $errors[] = "タイトルは文字列でなくてはいけません";
    }

    if ( ! mb_strlen( trim( $content ) ) ) {
        $errors[] = "本文が空です";
    } else if ( mb_strlen( trim( $content ) ) > 200 ) {
        $errors[] = "本文は200文字以内で入力してください";
    } else if ( is_numeric( trim( $content ) ) ) {
        $errors[] = "本文は文字列でなくてはいけません";
    }

    return $errors;
}
```