はじめに
ファイルはこちらからダウンロードできます。御自由にお使いください。
ファイル構造と各部の解説
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について
記事の削除
記事の編集
応用:ひとつのページで処理を完結させる
<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
<?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
<?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
<?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
<?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
<!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
<div id="footer">
</div>
</body>
</html>
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
<?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;
}
?>