LoginSignup
0
5

More than 3 years have passed since last update.

PHPとメモ帳で掲示板をつくってみた話

Last updated at Posted at 2019-10-26

はじめに

ファイルはこちらからダウンロードできます。御自由にお使いください。

ファイル構造と各部の解説

wordtile lite
├─css ……文字通りcssファイルを入れるとこ。ありていに言えば見た目を装飾するものなので、なくても動作するし実際いま入れてない
├─log
│ └─log.txt ……投稿された情報が記録されるファイル。書き込みも削除もすべてプログラムで行うので触る必要なし。
├─index.php ……トップページ。現状、list.phpに移動する為だけの存在。
├─edit.php ……投稿された記録の修正画面のページ。
├─list.php ……投稿された記録を一覧表示するページ。新規追加用のフォームもここに同梱されている。
├─result.php ……後述。
├─header.php ……ヘッダーパーツ。サイトタイトルとページ上部のレイアウトを担う部品で、全ページで使われている。
├─footer.php ……フッターパーツ。ページ下部のレイアウトを担う部品で、全ページで使われている。
├─regist.php ……投稿の新規追加、編集、削除の3種類の処理を行うプログラムファイル。処理が終わるとresult.phpを表示する。
└─system.php ……サイトの設定や利用する関数などをまとめてあるファイル。

※このうち、regist.php、system.phpは各種処理を行うシステムファイル、header.php、footer.phpはパーツファイルであることには注意。
つまり、regist.php、system.php、header.php、footer.phpはウェブサイトでいう「ページ」ではないのだ。

記事の保存

テキストファイル(≒記事データ)の読み込みと表示

応用:記事のソート

応用:記事の検索

記事の登録

応用:バリデーション

応用:BOMについて

記事の削除

記事の編集

応用:ひとつのページで処理を完結させる

test.php


    <form action="" name="admin_options" method="post">

        <div class="textbox">
            <span>メッセージ変更(改行は反映されません。)</span>
            <textarea name="sekoucontent" cols="80" rows="5"><?php echo $text_validated; ?></textarea>
        </div>

        <div class="textbox">
            <span>メッセージの流れる速度</span>
            <input type="" value="" />
        </div>


        <input type="submit" name="change" value="変更">
    </form>

参考文献

https://qiita.com/tadsan/items/0955b3de7dc58490ddaf
https://qiita.com/tadsan/items/bbc23ee596d55159f044

index.php

index.php
<?php

require_once(dirname(__FILE__).'/system.php');

MakeHeader(); ?>
<div id="index_page">
    <div class="main">

        <a href="./list.php">一覧表示ページへ。。。</a>

    </div>
</div>
<?php MakeFooter(); ?>

list.php

list.php
<?php

require_once(dirname(__FILE__).'/system.php');


// クッキーから取り出し
$post_hall_cookie = $_COOKIE['posthall'];
$post_name_cookie = $_COOKIE['postname'];


// ログデータ取り出し
$log_list = ReadLog();

// 取り出したログデータを降順にソート
foreach($log_list as $val){
    $timestamplist[] = $val[2];
}
array_multisort($timestamplist, SORT_DESC, $log_list);


MakeHeader(); ?>


<div id="list_page">
    <div class="main">

        <article class="list_wrap">
            <?php
            foreach($log_list as $one_log){

                $post_id = $one_log[0];
                $post_time_first = $one_log[2];
                $post_time_last = $one_log[3];
                $post_name = $one_log[4];
                $post_content = $one_log[5];

                if($post_time_last === $post_time_first){
                    $date_result = StampToDate($post_time_first);
                }
                else{
                    $date_result = StampToDate($post_time_first)." (編集済み)";
                }

                ?>

            <section class="list_cell">
                <h3><?php echo $post_name; ?></h3>
                <p class="content"><?php echo OpenText($post_content, 'html'); ?></p>
                <p class="post_time"><?php echo OpenText($date_result, 'html'); ?></p>
                <div class="actions">
                    <a href="./edit.php?id=<?php echo $post_id ?>">編集する</a>
                    <a href="./regist.php?type=delete&id=<?php echo $post_id ?>">削除する</a>
                </div>
            </section>

                <?php
            } ?>
        </article>

        <div id="form">
            <h2>form</h2>
            <form action="./regist.php?type=regist" method="post">
                <div class="textbox">
                    <input type="text" name="name" size="30" value="<?php echo $post_name_cookie; ?>" placeholder="お名前">
                </div>
                <div class="textbox">
                    <textarea name="content" cols="80" rows="5" placeholder="すきなコメントをいれてね"></textarea>
                </div>
                <div class="submit">
                    <input type="submit" value="書き込む">
                </div>
            </form>
        </div>

    </div>
</div>



<?php MakeFooter(); ?>

edit.php

edit.php
<?php

require_once(dirname(__FILE__).'/system.php');


// GETからIDを取得
if($_GET['id'] == ""){
    header("Location: index.php");
    exit;
}
else{
    $target_id = $_GET['id'];
}

// ログデータ取り出し
$log_list = ReadLog();


// 編集対象となる投稿情報の取得
$switch = 0;
foreach($log_list as $line){
    if($line[0] == $target_id){

        $post_time_first = $line[2];
        $post_time_last = $line[3];
        $post_name = $line[4];
        $post_content = $line[5];

        $post_name = OpenText($post_name, 'textarea');
        $post_content = OpenText($post_content, 'textarea');

        $switch++;
    }
}
if($switch === 0){
    $resultmessage = urlencode("対象となる投稿が存在しません。");
    header("Location: result.php?message=".$resultmessage);
    exit;
}



MakeHeader(); ?>

<div id="edit_page">
    <div class="main">

        <ul>
            <li>
                <span>post</span>
                <span><?php echo StampToDate($post_time_first); ?></span>
            </li>
            <li>
                <span>last edit</span>
                <span><?php echo StampToDate($post_time_last); ?></span>
            </li>
        </ul>

        <div id="form">
            <h2>form</h2>
            <form action="./regist.php?type=edit&id=<?php echo $target_id; ?>" method="post">

                <div class="textbox">
                    <input type="text" name="name" size="30" value="<?php echo $post_name; ?>" placeholder="お名前">
                </div>
                <div class="textbox">
                    <textarea name="content" cols="80" rows="5" placeholder="コメントをいれてね"><?php echo $post_content; ?></textarea>
                </div>
                <div class="submit">
                    <input type="submit" value="上書きする">
                </div>
            </form>
        </div>

    </div>
</div>




<?php MakeFooter(); ?>

result.php

result.php
<?php

// 動作ファイル認識
require_once(dirname(__FILE__).'/system.php');

if($_GET["message"] == ""){
    header("Location: index.php");
    exit;
}
else{
    $message_acquired = $_GET["message"];
}

MakeHeader(); ?>
<div id="edit_page">
    <div class="main">


    <p><?php echo $message_acquired; ?></p>
    <ul>
      <li><a href="index.php">一覧へ戻る</a></li>
    </ul>


    </div>
</div>
<?php MakeFooter(); ?>

header.php

header.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>PHP WORDTILE</title>
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="description" content="さいとのせつめい。"/>
    <meta name="keywords" content="検索のときに使うキーワード"/>
    <!-- 電話番号等の自動認識を阻害 -->
    <meta name="format-detection" content="telephone=no">
    <!-- jQueryライブラリの読み込み -->
    <!-- <script src="//code.jquery.com/jquery-2.2.4.min.js"></script> -->
    <!-- 共通css -->
    <link rel="stylesheet" href="./css/style.css">
    <!-- 共通js -->
</head>
<body>

<div id="header">
    <h1>PHP WORDTILE</h1>
</div>

footer.php

footer.php
<div id="footer">
</div>
</body>
</html>

regist.php

regist.php
<?php

require_once(dirname(__FILE__).'/system.php');


// アクションの種類の判定
if($_GET['type'] == "regist"){
    RegistAction($_POST, $_SERVER);
}
else if($_GET['type'] == "delete" && $_GET['id'] != ""){
    $target_id = $_GET['id'];
    DeleteAction($target_id);

}
else if($_GET['type'] == "edit" && $_GET['id'] != ""){
    $target_id = $_GET['id'];
    EditAction($_POST, $_SERVER, $target_id);

}
else{
    header("Location: index.php");
    exit;

}

// regist(新規投稿)
function RegistAction($data, $server){

    // postされた値のセキュリティチェック
    if($server['REQUEST_METHOD'] !== "POST"){
        $resultmessage = urlencode("不正なリクエストではありませんか?");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }

    if($data['content'] == ""){
        $resultmessage = urlencode("本文が空欄のままです。");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }
    else{
        // タイムスタンプの取得
        $timestamp = time();

        // IPアドレスの取得
        $ipaddr = $server['REMOTE_ADDR'];


        // POSTの値は変数に格納しておくと、ハッキング攻撃を受けても中身を不正に変えられなくなる。
        if($data['name'] == ""){
            $post_name = "名無しさん。";
        }
        else{
            $post_name = $data['name'];
        }
        $post_content = $data['content'];


        $post_content = CloseText($post_content);
        $post_name = CloseText($post_name);

        // ログファイルの有無をチェック。ないなら生成する
        if (!file_exists(setinits("root").'/log/log.txt')){
            if($filepass = fopen(setinits("root").'/log/log.txt', 'w')){
                fclose($filepass);
            }
            else{
                $resultmessage = urlencode("ファイルを開ける際に何らかの問題が発生しました。");
                header("Location: result.php?message=".$resultmessage);
                exit;
            }
        }


        // ファイル読み込み
        $logfiles_array = file(setinits("root").'/log/log.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

        $cntall = count($logfiles_array);
        $cnt = 1;
        foreach($logfiles_array as $logfile_line){

            // 一行になった文字列を分割
            $log_parts = explode("<>", $logfile_line);
            list($log_id, $log_addr, $log_first_timestamp, $log_timestamp, $log_name, $log_content) = $log_parts;

            // 既存の投稿からidだけを抜き出して一覧をつくる。すぐ下のid生成で利用。
            $idlist[] = $log_id;

            if($cnt == $cntall){

                // 連続投稿を制限する関数
                $log_span = $timestamp - $log_timestamp;
                if($log_span < 60 && $ipaddr == $log_addr && $post_name == $log_name && $post_content == $log_content){
                    $resultmessage = urlencode("同じ端末から同じ内容の投稿が投稿されたばかりです。1分ほどお待ちください");
                    header("Location: result.php?message=".$resultmessage);
                    exit;
                }

            }
            $cnt++;
        }



        // idを生成しつつ、重複チェック。このidは編集、削除するときに利用する。
        if( $idlist == null ){
            $new_id = uniqid( rand(7,7) );
        }
        else{
            $new_id = uniqid( rand(7,7) );
            while( array_search($new_id, $idlist) !== false ){
                $new_id = uniqid( rand(7,7) );
            }
        }

        // この $first_timestamp は、編集するときに使います。
        $first_timestamp = $timestamp;

        // 文生成
        $datatext = $new_id."<>".$ipaddr."<>".$first_timestamp."<>".$timestamp."<>".$post_name."<>".$post_content."\n";

        // 投稿処理
        if($filepass = fopen(setinits("root").'/log/log.txt', 'a+')){
            fwrite($filepass, $datatext);
            fclose($filepass);
        }
        else{
            $resultmessage = urlencode("ファイルを開ける際に何らかの問題が発生しました。");
            header("Location: result.php?message=".$resultmessage);
            exit;
        }

        // 投稿した内容をクッキーに格納
        setcookie('postname', $post_name, time()+900);

        // 処理が終わったらresultページに移動。これには、二重処理を防ぐ意味合いがある。
        $resultmessage = urlencode("メッセージを投稿しました。");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }
}


// delete(削除)
function DeleteAction($id){
    // ログファイルのチェック
    if(file_exists(setinits("root").'/log/log.txt')){
        if($filehandle = fopen(setinits("root").'/log/log.txt', 'r+')){

            // 削除対象の読み込み処理

            $switch = 'off';
            while( ($logfile_line = fgets($filehandle)) !== false ){

                // ここでは、消去したい行のスタート位置座標&消去したい行より後のすべての行を取得。
                // 消去したい行の次以降の行が欲しいので、このifは消去したい行かどうかをチェックするifよりも前に書いてある
                if($switch === 'on'){
                    // 後で使うのでまとめておく。
                    $rewritestr[] = $logfile_line;
                }
                else{
                    $log_parts = explode("<>", $logfile_line);
                    if($log_parts[0] == $id){

                        // 今取得している行の長さ(バイト数)
                        $target_strlen = strlen($logfile_line);

                        // 今取得している行の終了地点座標(バイト数)
                        $target_finish = ftell($filehandle);

                        // 上書き(≒消去)したい行のスタート位置座標。
                        // 現在読み込んでいる位置から、$target_strlenを引くと求められる。
                        $rewritestart = $target_finish - $target_strlen;

                        $switch = 'on';
                    }
                }
            }
            if($switch === 'off'){
                $resultmessage = urlencode("対象となる投稿が存在しません。");
                header("Location: result.php?message=".$resultmessage);
                exit;
            }

            // 削除処理

            // ポインタを消去したい行の先頭に移動させる
            fseek($filehandle, $rewritestart, SEEK_SET);

            // ファイル内のデータ(≒文字)のうち、$rewritestart以降の文字を削除。つまり、ここの処理で削除したい行とそれ以降の行が削除される。
            ftruncate($filehandle, $rewritestart);

            // 復元処理

            // 先ほどのftruncateで消したくない行まで消してしまっているので、あらかじめ$rewritestrに保存しておいたそれらの行を再度書いていく。
            if(count($rewritestr) !== 0){
                foreach($rewritestr as $onecolomn){
                    fwrite($filehandle, $onecolomn);
                }
            }
            fclose($filehandle);
        }
        else{
            $resultmessage = urlencode("ファイルを開ける際に何らかの問題が発生しました。");
            header("Location: result.php?message=".$resultmessage);
            exit;
        }
    }
    else{
        $resultmessage = urlencode("ログファイルが存在しません。");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }

    // resultページに移動。
    $resultmessage = urlencode("投稿は正常に削除されました。");
    header("Location: result.php?message=".$resultmessage);
    exit;
}

// edit(編集)
function EditAction($data, $server, $id){

    // postされた値のセキュリティチェック
    if($server['REQUEST_METHOD'] !== "POST"){
        $resultmessage = urlencode("不正なリクエストではありませんか?");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }

    if($data['content'] == ""){
        $resultmessage = urlencode("本文が空欄のままです。");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }
    else{
        // タイムスタンプの取得
        $current_timestamp = time();

        // POSTの値は変数に格納しておくと、ハッキング攻撃を受けても中身を不正に変えられなくなる。
        if($data['name'] == ""){
            $post_name = "名無しさん。";
        }
        else{
            $post_name = $data['name'];
        }
        $post_content = $data['content'];

        $post_content = CloseText($post_content);
        $post_name = CloseText($post_name);

    }

    if(file_exists(setinits("root").'/log/log.txt')){
        if($filehandle = fopen(setinits("root").'/log/log.txt', 'r+')){


            // 削除対象の読み込み処理

            $switch = 'off';
            while( ($logfile_line = fgets($filehandle)) !== false ){
                $log_parts = explode("<>", $logfile_line);

                // 消去したい行より後の記述のみすべて取得。消去したい行で$switchオンになる=つまり、その次の行以降が欲しいので、このifは消去したい行かどうかをチェックするifよりも前に書いてある
                if($switch === 'on'){
                    // 消去したい行より後にある記事はまとめておく。
                    $rewritestr[] = $logfile_line;
                }

                // 消去したい行のポインタ位置と投稿情報を取得する
                if($log_parts[0] == $id){

                    // 今取得している行の長さ(バイト数)
                    $edit_strlen = strlen($logfile_line);

                    // 今取得している行の終了地点座標(バイト数)
                    $edit_finish = ftell($filehandle);

                    // 上書き(≒消去)したい行のスタート位置座標。
                    // 現在読み込んでいる位置から、$edit_strlenを引くと求められる。
                    $rewritestart = $edit_finish - $edit_strlen;

                    // 上書き前の投稿情報取得
                    $log_id = $log_parts[0];
                    $log_addr = $log_parts[1];
                    $log_first_timestamp = $log_parts[2];

                    // $log_parts[3]は上書きするので使いません

                    $log_name = $log_parts[4];
                    $log_hall = $log_parts[5];
                    $log_content = $log_parts[6];

                    $switch = 'on';
                }
            }
            if($switch === 'off'){
                $resultmessage = urlencode("対象となる投稿が存在しません。");
                header("Location: result.php?message=".$resultmessage);
                exit;
            }

            // 削除処理

            // ポインタを消去したい行の先頭に移動させる
            fseek($filehandle, $rewritestart, SEEK_SET);

            // ファイル内のデータ(≒文字)のうち、$rewritestart以降の文字を削除。つまり、ここの処理で削除したい行とそれ以降の行が削除される。
            ftruncate($filehandle, $rewritestart);

            // 上書き処理

            // 書き換えたい投稿のスタート地点までを消しているので、ここに変更後の投稿を書き込む
            fwrite($filehandle, $log_id."<>".$log_addr."<>".$log_first_timestamp."<>".$current_timestamp."<>".$post_name."<>".$post_hall."<>".$post_content."\n");

            // 先ほどのftruncateで消したくない行まで消してしまっているので、あらかじめ$rewritestrに保存しておいたそれらの行を再度書いていく。
            if(count($rewritestr) !== 0){
                foreach($rewritestr as $onecolomn){
                    fwrite($filehandle, $onecolomn);
                }
            }
            fclose($filehandle);
        }
        else{
            $resultmessage = urlencode("ファイルを開ける際に何らかの問題が発生しました。");
            header("Location: result.php?message=".$resultmessage);
            exit;
        }
    }
    else{
        $resultmessage = urlencode("ログファイルが存在しません。");
        header("Location: result.php?message=".$resultmessage);
        exit;
    }

    // resultページに移動。これには、二重処理を防ぐ意味合いがある。
    $resultmessage = urlencode("投稿は正常に変更されました。");
    header("Location: result.php?message=".$resultmessage);
    exit;
}



?>

system.php

system.php
<?php

// 読み込み時の動作~~~~~~~~~~

// 基本情報を格納しておく関数
function setinits($target){
// ======================

    // 基本ファイルパス
    $tmpl['root'] = dirname(__FILE__);

    // 基本URLパス
    $tmpl['site_root'] = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"];

// ======================

    // 設定値を返す
    return $tmpl[$target];
}

// 関数~~~~~~~~~~

// ヘッダーを生成する関数
function MakeHeader(){
    if(file_exists(setinits("root").'/header.php')){
        include_once setinits("root").'/header.php';
    }
    else{
        print '<!DOCTYPE html>';
        print '<html lang="ja">';
        print '<head>';
        print '<meta charset="utf-8">';
        print '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">';
        print '<title>'.setinits("site_title").'</title>';
        print '</head>';
        print '<body>';
        print '<!-- header.phpの読み込みに失敗しました -->';
    }
}

// フッターを生成する関数
function MakeFooter(){
    if(file_exists(setinits("root").'/footer.php')){
        include_once setinits("root").'/footer.php';
    }
    else{
        print '<!-- footerパーツの読み込みに失敗しました -->';
        print '</body></html>';
    }
}


// ログファイルのチェックと内容の取得
function ReadLog(){
    if( file_exists(setinits("root").'/log/log.txt') ){
        // 存在するなら、ファイルの中身を読み込む
        $logfiles_array = file(setinits("root").'/log/log.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

        foreach($logfiles_array as $logfile_line){
            $log_parts = explode("<>", $logfile_line);
            $logdata[] = $log_parts;
        }
        return $logdata;
    }
    else{
        // 存在しなかった場合、falseを返す
        return false;
    }
}


// BOM付きファイルからBOMをとり、ファイルを掃除する
function TrimBOM($target_txt){
    $result = preg_replace('/^\xEF\xBB\xBF/', '', $target_txt);
    return $result;
}


// 文字のバリデーションを行う関数
function OpenText($text, $mode){
    if($mode == 'html'){
        $text = htmlspecialchars($text);
        $text = str_replace("_", "<br>", $text);
        return $text;
    }
    else if($mode == 'textarea'){
        $text = htmlspecialchars($text);
        $text = str_replace("_", "\n", $text);
        return $text;
    }
}

// バリデーションした文字列を表示用に元に戻す関数
function CloseText($text){
    $text = preg_replace('/[@$<>_,\-]+/u', '', $text);
    $result = preg_replace("/\r\n|\r|\n/", "_", $text);
    return $result;
}

// タイムスタンプを日付表示にする関数
function StampToDate($timestamp){
    $week_name = array("日", "月", "火", "水", "木", "金", "土");
    $result = date( "Y/m/d", $timestamp ).' ('.$week_name[date("w", $timestamp)].') '.date( "H:i", $timestamp );
    return $result;
}

?>
0
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
5