masimaro blog

menu

home > WEBアプリ開発:CMS > CMSを自作してオリジナル動的ホームページをフルスクラッチ開発③CMSの記事投稿画面(HTMLエディタ)の作成

CMSを自作してオリジナル動的ホームページをフルスクラッチ開発③CMSの記事投稿画面(HTMLエディタ)の作成


CMS
2021-07-22


今回は、記事投稿画面(HTMLエディタ)~データベースに登録までを作成したいと思います。

星マークがすでに作成済みのファイルになります。雲マークのファイルを、今回作成していきます。

※他に、javascriptファイルと、cssファイルも作成します。

HTMLエディタを自作して、それを投稿画面に利用するのですが、先に概要だけさらっと説明しておきます。

まず、作成するHTMLエディタ(edit_single.php)の画面はこんな感じです。↓

使い方としては、まず、画像を挿入したい場合、上部のファイル選択ボタンから任意のimgファイルを選択し、アップロードを押します。imgファイルは、settingディレクトリ配下にimgディレクトリを作成し、その中に保存するようにします。

続いてカテゴリを選択。

<h1>タグに囲まれている部分にタイトルを入力すると、右側のタイトルに入力した見出しが反映されます。

サムネイルを挿入したい場合は、imgファイル検索から、アップロードした画像(imgディレクト内にあるもの)が選択できるので、そこから「ファイル名」をコピーし、サムネイルのテキストボックスに貼り付け、OKをクリックします。右のサムネイルに画僧が反映されます。

本文については、上部にあるタグをクリックすれば、選択したタグがテキストボックス内に反映される仕組みになっています。したがって、基本はタグを選択して、そのなかに文章を入れていく感じになります。

画像も、先ほどと同じくファイル名をコピーし、img srcタグに埋め込めば、右側の本文に反映される仕組みです。

見切れていますが、下部にOKボタンがあるので、それをクリックすれば、次ページでプレビューが見れるようにしています。

このように、テキストボックスに入力した内容が、リアルタイムで他の要素に反映される仕組みは、後述しますがkey upイベントによるjavascriptによって実現しています。Ajaxなどを使えば、もっとスマートなものが出来たと思いますが、とりあえずこのような仕様のエディタを作成します。

では、初めに投稿記事を保存するためのテーブルと、imgファイル名を保存するテーブルを作成します。

 

テーブルの作成

blog(投稿記事を保存するためのテーブル)

データベース「test」に「blog」のテーブルを作成します。カラムは6とし、下記のように設定します。

名前データ型その他
codeintAI
categoryvarchar 値20-
titlevarchar 値100-
imgvarchar 値100-
honbunvarchar 値5000-
timedata-

 

img(imgファイル名を保存するためのテーブル)

データベース「test」に「img」のテーブルを作成します。カラムは6とし、下記のように設定します。

名前データ型その他
codeintAI
namevarchar 値100-

投稿記事作成画面

edit_single.php

<?php

session_start();
session_regenerate_id(true);
if(isset($_SESSION["login"]) === false) {
    print "ログインしていません。<br><br>";
    print "<a href='setting/set_login.php'>ログイン画面へ</a>";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>cms</title>
<link rel="stylesheet" href="setting/style2.css">
</head>
<body>
<?php 
  try {

$dsn = "mysql:host=localhost;dbname=test;charset=utf8";
$user = "root";
$password = "";
$dbh = new PDO($dsn, $user, $password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
$sql = "SELECT name FROM k_menu WHERE1";
$stmt = $dbh -> prepare($sql);
$stmt -> execute();

while(true) {
$rec = $stmt -> fetch(PDO::FETCH_ASSOC);
if(empty($rec["name"]) === true) {
break;
}
$k_menu[] = $rec["name"];
}
$dbh = null;
}
catch(Exception $e) {
    print "只今障害が発生しております。<br><br>";
    print "<a href='../staff_login/staff_login.html'>ログイン画面へ</a>";
}
?>
<div id="edit">
<div id="box2">
<form action="setting/upload.php" method="post" enctype="multipart/form-data"> 
イメージファイルアップロード<br>    
<input type="file" name="image_data[]" multiple="multiple">
<input type="submit" value="アップロード">
</form>
<br>
<form action="single_check.php" method="post" enctype="multipart/form-data">
カテゴリ<br>
    <select name='cate'>
    <?php foreach($k_menu as $key => $value) {;?>
    <option value='<?php print $value;?>'><?php print $value;?></option>
    <?php };?>
    </select>
    <br>
タイトル
<br>
<textarea name="title" id="title1"><h1></h1></textarea>
<br>
サムネイル<br>
<input type="text" id="gazou" name="img">
<input type="button" id="bt" value="ok">
<br><br>

<div class="tag">
<div id="h1">h1 </div>
<div id="p">p </div>
<div id="br">br </div>
<div id="h2">h2 </div>
<div id="img">img </div>
<a href="setting/img.php" target="blank">imgファイル名検索</a>
</div>
    
<textarea id="area" name="honbun"></textarea>
<br>
<input type="submit" value="ok">
<input type="button" onclick="history.back()" value="戻る">
</form>
</div>
<div id="write">
<h3>タイトル</h3><div id="title"></div>
<h3>サムネイル</h3><div id="sam"></div>
<h3>本文</h3><div id="box"></div>
 
</div>
</div>
   
<script src="setting/main2.js"></script>   
</body>


</html>

今回はcmsディレクトリ内にedit_single.phpを作成します。(settingディレクトリ配下に置くと、設計上の都合で後ほど作成する公開ページにイメージファイルが反映されない為です。)

このページは投稿記事作成画面(htmlエディタ)になります。

予め、settingディレクトリ配下に、img名のディレクトリを作成しておいてくださ。

formタグのmultipleで複数の画像をアップロード出来るようにしています。

今回のエディタでは、画像を先にアップロードさせておき、記事内に埋め込むにはアップロードした画像の名前をりようして、埋め込む形になっています。

ですので、submitボタンを押せば、upload.phpに遷移し、そこでimgディレクトリへの画像ファイルのアップロード、およびデータベースへの保存を行っています。

続くもう1つのformタグでは、タイトル、サムネイル、本文、の入力欄を作成しています。

ここで入力した内容がリアルタイムで右側のdiv boxに反映されるようにします。

これはjavascriptで操作するので、各入力欄にはidを振っておきます。また、post送信で値を次ページに送り、最終的にデータベースに登録出来るように、nameも付けています。

サムネイルの画像の欄には、アップロードした画像の名前を入力し、ボタンを押せばjavascriptのクリックイベントで右側のBOXに画像が表示されるようにしています。

h1やpタグのボタンは、これもjavascriptのクリックイベントで本文のエディタ欄に任意のタグが反映されるようにしています。

ファイル名検索のURLは、飛んだ先のページ(img.php)でアップロードした画像の一覧と名前が表示されるようにしています。つまり、そこで画像の名前をコピーして、サムネイルや本文中の任意の箇所に埋め組むことで、画像が表示される仕組みです。

CSS

#edit {
display: flex;
}
.tag {
    display: flex;
}
#box {
    width: 100%;
    height: 80vh;
    padding: 10px;
    margin: 10px;
    border: solid 1px;
    overflow: scroll;
}
#area {
    width: 440px;
    height: 75vh;
    padding: 10px;
    margin: 10px;
}
#title {
    width: 100%;
    height: 10vh;
    padding: 10px;
    margin: 10px;
    border: solid 1px;
    overflow: scroll;
}
#sam {
    width: 100%;
    height: 15vh;
    padding: 10px;
    margin: 10px;
    border: solid 1px;
    overflow: scroll;
}
#write {
    width: 1000px;
}

settingディレクトリ配下にstyle2.cssを作成します。

エディタを使いやすくするためにスタイリングしているだけです。

この段階で、冒頭のエディタ画面になっているはずです。

javascript

const i = "<img src='setting/img/";
const x = "'>";

document.getElementById("bt").addEventListener("click", () => {
    let n = document.getElementById("gazou").value;
    document.getElementById("sam").innerHTML = i + n + x;
});
    
document.body.addEventListener("keyup", () => {
    document.getElementById("box").innerHTML = document.getElementById("area").value;
});
    
document.body.addEventListener("keyup", () => {
    document.getElementById("title").innerHTML = document.getElementById("title1").value;
}); 

document.getElementById("h1").addEventListener("click", () => {
    document.getElementById("area").value += "<h1></h1>";
});
    
document.getElementById("p").addEventListener("click", () => {
    document.getElementById("area").value += "<p></p>";
});

document.getElementById("br").addEventListener("click", () => {
    document.getElementById("area").value += "<br>";
});
document.getElementById("h2").addEventListener("click", () => {
    document.getElementById("area").value += "<h2></h2>";
});
document.getElementById("img").addEventListener("click", () => {
    document.getElementById("area").value += "<img class='bunimg' src='setting/img/ここにimg'>";
});

settingディレクトリ配下にmain2.jsを作成します。

初めの定数には、画像が名前だけで表示されるように下準備しています。<img src..のhtmlタグですね。

続くクリックイベントは、サムネイルのテキストBOXの文字列を、ボタンが押された場合、nの変数に格納してます。

そして、定数のiとxの間にnを挟んでいます。つまり、<img src=”img/画像名”>が作成されます。

しかしこのままでは文字列として扱われるので、innerhtmlでその値を渡す事でhtmlタグと解釈され、右側のサムネイル画面に画像が表示されるようになっています。

後は同じようなものです。kye up関数は、キーボードのキーが上がった瞬間、つまり押された時に左側のtextareaの文字列が、innerhtmlで右側のBoxにリアルタイムで反映されるようになっています。

.valueによって、入力した文字列が消える事なく、キー入力した文字が連なり、そのたびに合計の文字列が右側に反映されています。

htmlのタグ挿入も、同じ要領ですね。

imgアップロード画面

upload.php

<?php

session_start();
session_regenerate_id(true);
if(isset($_SESSION["login"]) === false) {
    print "ログインしていません。<br><br>";
    print "<a href='set_login.php'>ログイン画面へ</a>";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cms</title>
<link rel="stylesheet" href="style2.css">
</head>
    
<body>
    
<?php

$img = $_FILES["image_data"];
    $max = count($img["name"]);
    
//print $max;
    
//for($i=0; $i < $max; $i)
    

foreach($img["tmp_name"] as $key => $value) {
    //move_uploaded_file($value["tmp_name"],"./img/".$value["name"]);
    $tmp_name[] = $value;
}
    
foreach($img["name"] as $key => $value) {
    //move_uploaded_file($value["tmp_name"],"./img/".$value["name"]);
    $name[] = $value;
}
    
for($i=0; $i < $max; $i++) {
 move_uploaded_file($tmp_name[$i],"./img/".$name[$i]);
}
    
$dsn = "mysql:host=localhost;dbname=test;charset=utf8";
$user = "root";
$password = "";
$dbh = new PDO($dsn, $user, $password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
for($i = 0; $i < $max; $i++) {
$sql = "INSERT INTO img(name) VAlUES(?)";
$stmt = $dbh -> prepare($sql);
$data[] = $name[$i];
$stmt -> execute($data);
$data = array();
}

print "<br><br>完了しました。";
//print "<br><br>";

//print "<form>";
//print "<input type='button' onclick='history.back()' value='戻る'>";

?>
 
    <form>
<input type='button' onclick='history.back()' value='戻る'>
    </form>
    </body>
</html>

settingディレクトリ配下にupload.phpを作成します。

このページでは、記事に埋め込みたい画像ファイルをsetting/imgディレクトリにアップロードし、データベースに登録しています。

$_FILES["image_data"]で、複数のimgデータを受け取っています。

count($img["name"])で画像データの数をカウントし、foreachで受け取った画像データのtmp_nameとnameをそれぞれ変数に格納。forでカウント分ループさせて、tmp_nameとnameの配列を作っています。

続いてのfor文で、配列の内容をすべてimgディレクトリにアップロードさせています。

あとはデータベースに接続し、カウント分forでループさせて、nameの配列をテーブルに登録しています。

それでは、適当に画像ファイルをいくつかアップロードしてみます。imgディレクトリとデータベースに反映していればOKです。

img upload
imgテーブル反映

imgファイル名選択画面

img.php

<?php

session_start();
session_regenerate_id(true);
if(isset($_SESSION["login"]) === false) {
    print "ログインしていません。<br><br>";
    print "<a href='set_login.php'>ログイン画面へ</a>";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cms</title>
<link rel="stylesheet" href="style.css">
</head>
    
<body>

<?php
    
$dsn = "mysql:host=localhost;dbname=test;charset=utf8";
$user = "root";
$password = "";
$dbh = new PDO($dsn, $user, $password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
$sql = "SELECT name FROM img WHERE1";
$stmt = $dbh -> prepare($sql);
$stmt -> execute();
    
while(true) {
    $rec = $stmt -> fetch(PDO::FETCH_ASSOC);
    if(empty($rec["name"]) === true) {
        break;
    }
    print "<img src='img/".$rec['name']."'>";
    print $rec["name"];
    print "<br>";
}
    
?>
    
    
    
    
    
    
</body>
</html>

settingディレクトリ配下にimg.phpを作成します。

このページでは、データベースを参照し、imgディレクトリにアップロードされている画像ファイルの一覧を名前つきで一覧表示させるページです。

記事に埋め込みたい画像ファイルは、このページから任意の画像名をコピーし、エディタのimg srcタグに埋め込みます。

プログラムについてはデータベースに接続して、テーブルの内容を表示させてるだけです。

img選択

ここまで出来れば、適当に記事を作成してみます。下のような感じになるはずです。↓

投稿記事チェック画面

single_check.php

<?php

session_start();
session_regenerate_id(true);
if(isset($_SESSION["login"]) === false) {
    print "ログインしていません。<br><br>";
    print "<a href='set_login.php'>ログイン画面へ</a>";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cms</title>
<link rel="stylesheet" href="style.css">
</head>
    
<body>
    
<?php
    
$cate = $_POST["cate"];
$title = $_POST["title"];
$honbun = $_POST["honbun"];
$img = $_POST["img"];

if(empty($title) === true or empty($honbun) === true or empty($cate) === true or empty($img) === true) {
    print "必要項目を入力して下さい";
    print "<br><br>";
    print "<form>";
    print "<input type='button' onclick='history.back()' value='戻る'>";
    print "</form>";
    exit();
}
    
    
print $title;
print "<img class='bunimg' src='setting/img/".$img."'>";
print $honbun;

?>

<br><br>
上記内容で登録しますか?<br><br>
<form action="setting/blogdone.php" method="post">
<input type="hidden" name="cate" value="<?php print $cate;?>">
<input type="hidden" name="title" value="<?php print $title;?>">
<input type="hidden" name="honbun" value="<?php print $honbun;?>">
<input type="hidden" name="img" value="<?php print $img;?>">
<input type="submit" value="OK">   
    
</form>
    
</body>
    
</html>

cmsディレクトリ内にsingle_check.phpを作成します。(こちらもsettingディレクトリ配下に置くと、設計上の都合で後ほど作成する公開ページにイメージファイルが反映されない為です。)

このページは投稿記事のチェック画面になります。

postデータをエスケープしていないのは、htmlタグと解釈させてデータベースに保存させたい為です。

後は、必要な項目(タイトル、カテゴリ、本文、サムネイル)がちゃんと入力されているかのチェックを行っています。

問題なければ、プレビューとして投稿記事を表示させます。(後ほど、公開ページ作成の際にまとめてCSSでスタイリングさせますが、今の段階ではそのまま表示させます。)

formタグのhiddenで、submitをクリックすれば次ページへ各値をpostさせます。

投稿記事プレビュー

投稿記事登録画面

blogdone.php

<?php

session_start();
session_regenerate_id(true);
if(isset($_SESSION["login"]) === false) {
    print "ログインしていません。<br><br>";
    print "<a href='set_login.php'>ログイン画面へ</a>";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>cms</title>
<link rel="stylesheet" href="style2.css">
</head>
<body>
    
<?php
    try {
        
$cate = $_POST["cate"];
$title = $_POST["title"];
$honbun = $_POST["honbun"];
$img = $_POST["img"];
    
$dsn = "mysql:host=localhost;dbname=test;charset=utf8";
$user = "root";
$password = "";
$dbh = new PDO($dsn, $user, $password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
$sql = "INSERT INTO blog(category, title, honbun, img, time) VALUES(?,?,?,?,NOW())";
$stmt = $dbh -> prepare($sql);
$data[] = $cate;
$data[] = $title;
$data[] = $honbun;
$data[] = $img;
$stmt -> execute($data);

}
catch(Exception $e) {
    print "只今障害が発生しております。<br><br>";
    print "<a href='../staff_login/staff_login.html'>ログイン画面へ</a>";
}
?>

登録しました。
<br><br>
<a href="../edit_single.php">記事投稿ページへ戻る</a>
<a href="set_top.php">トップメニューへ</a>
    
</body>
</html>
    

settingディレクトリ配下にblogdone.phpを作成します。

このページでは、投稿記事をデータベースに保存しています。

先ほどと同じく、postデータをエスケープしていないのは、htmlタグと解釈させてデータベースに保存させたい為です。

特に説明する箇所はないですね、データベース接続後、blogテーブルに各値を保存しているだけです。

それでは、1つ記事を作成して、データベースに保存してみましょう。データベースに反映されていればOKです。

長くなりましたが、投稿エディタの一連のシステムはこんな感じになります。

次回は、似た内容になりますが、固定ページの投稿ページを作成します。

コメントを残す

お名前

コメント



コメント一覧

管理人
2021-12-16
2021/12/16:修正致しました。
管理人です
2021-12-16
ご指摘ありがとうございます。 おっしゃる通り、不完全な状態で掲載しておりました。 近日中に修正致します。
kame
2021-12-16
このページって途中で終わってますか?? blogdone.phpのコードが途中で終わっているような気がしますが。。。
MENU