2013/02/08
nanocカスタマイズ‐コメント欄の追加
コメント欄を追加する方法は Facebook のコメント欄や Disqus コメントを利用したり、いくつかあると思いますが、自作してみました。
mod_ruby をインストールして ruby で作ってみようかなと試行錯誤しましたが、うまくできませんでした。ruby でうまく動作しなっかので、php で作ってみました。
コメントの表示、入力を php で作成し、その php ファイルを Apache の SSI(Server Side Includes) を利用して nanoc サイトの layouts/default.html に埋め込みます。コメント用のファイルは output ディレクトリ下に comment ディレクトリを作成し、そこにすべて配置しました。
ソースが必要な方はダウンロードしてください。comment.zip
以下は、それぞれのファイルの内容です。
Ajax については を参考にしました。
また、TEXTARIA や INPUT にデフォルト表示をし、クリックするとデフォルト表示が消えるようにしました。 を参考にしました。
function pressSubmit() ではコメントが投稿されたページの URL(document.URL)、投稿者の名前、メールアドレス、ホームページ、コメント及びスパム予防のためのイメージデータを Ajax でサーバへ送ります。comment_save.php がサーバで処理します。
function responseServer() ではサーバからの応答を受け取り、エラーならばエラーメッセージを表示し、正常ならばコメントリストを表示します。そして、スパム予防のためのイメージデータを書き換えます。
function commentTitle() はコメントがあれば「コメント一覧」を表示し、コメントがなければ「コメント一覧」を表示しません。
function onLoad() ではTEXTARIA や INPUT にデフォルト表示するための処理を行い、イメージデータ選択用のイメージを表示します。この関数は body の onLoad イベントでコールします。
function offDefault(element) はデフォルト表示を無効にします。
function onDefault(element) はデフォルト表示を有効にします。
function getValue(element) はデフォルト表示へ入力されたデータを取得します。
function selectImage(element) は選択されたイメージの情報を保持します。
function clearSelect() はクリアボタンをクリックしたときの処理を行います。選択されたイメージを破棄します。
comment.php はデータベース操作クラスです。
$_SERVER["DOCUMENT_URI"] には SSI で呼び出した URI が格納されているようです。この URI に対するコメントをデータベースより抽出します。
なまえ、メール、URL、コメントのアイコンは からダウンロードして使用しています。
comment_image.php はスパム予防のためのイメージデータを作成します。
データベースは SQLite3 を使用します。データベースファイル名は comment.sqlite、テーブル名は comments とします。
function __construct() はデータベースファイルが存在しないならばデータベースファイルを作成します。従って、 comment ディレクトリには Apache ユーザに rw 権限を付与してください。テーブルが存在しないならばテーブルを作成します。page はコメントが投稿されたページの URI、name は投稿者の名前、mail は投稿者のメールアドレス、url は投稿者のホームページ、comment は投稿内容、datetime は投稿日時を格納します。flag は新規に投稿されたら 0、承認したら 1、拒否は 2 とします。
function __destruct() はデータベースをクローズします。
get_comments($page) は引数で指定されたページのコメントの中から新規と承認のデータを抽出し、表示可能な HTML 文字列にします。
function make_html($name, $datetime, $comment) は投稿者の名前、投稿日時、コメントを HTML 文字列にします。
select_page($page, $flags = null) は引数で指定された条件でデータを抽出し、抽出したデータを配列にします。
function save($page, $name, $mail, $url, $comment) はデータをテーブルに格納します。
乱数で4つのイメージを決め、それのイメージを合成します。イメージ合成は を参考にしました。
4つのイメージのデータは暗号化してセッションに保存して、照合用に使用します。crypt() 関数に引数として与えた salt が crypt() 関数の戻り値の先頭にくっついてくるようですので、先頭の salt を除外したものをセッションに保存します。
function alltrim($str) は全角スペースを含む trim です。 を参考にしました。
メールアドレスのチェックには を参考にしました。
URL のチェックには を参考にしました。
メールアドレスや URL のチェックは存在確認をするのがベターでしょうが、ここでは形式チェックのみをしています。
メール送信に mb_send_mail() 関数を使用しています。この関数は localhost がメールサーバであることを前提とした関数のようです。メールサーバが他のホストであったり、SMTP 認証が必要ならば PEAR::Mail を使用するのがいいでしょう。
サイトディレクトリの直下にある Rules ファイルの index.html を index.shtml に変えれば出力するファイルの拡張子が shtml に変わります。
mod_ruby をインストールして ruby で作ってみようかなと試行錯誤しましたが、うまくできませんでした。ruby でうまく動作しなっかので、php で作ってみました。
コメントの表示、入力を php で作成し、その php ファイルを Apache の SSI(Server Side Includes) を利用して nanoc サイトの layouts/default.html に埋め込みます。コメント用のファイルは output ディレクトリ下に comment ディレクトリを作成し、そこにすべて配置しました。
ソースが必要な方はダウンロードしてください。comment.zip
以下は、それぞれのファイルの内容です。
layouts/default.html(赤字の部分を追加、変更)
<!DOCTYPE HTML> <html> ← lang="en" を削除 <head> <meta http-equiv="Content-Type" Content="text/html;charset=UTF-8"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <title>A Brand New nanoc Site - <%= @item[:title] %></title> <link rel="stylesheet" type="text/css" href="/style.css" media="screen"> <link rel="stylesheet" type="text/css" href="/comment/comment.css"> <script type="text/javascript" src="/comment/comment.js"></script> <!-- you don't need to keep this, but it's cool for stats! --> <meta name="generator" content="nanoc <%= Nanoc::VERSION %>"> </head> <body onLoad="onLoad();"> <div id="main"> <%= yield %> <div><!--#include virtual="/comment/comment_get.php" --></div> </div> <div id="sidebar"> <h2>Documentation</h2> <ul> <li><a href="http://nanoc.stoneship.org/docs/">Documentation</a></li> <li><a href="http://nanoc.stoneship.org/docs/3-getting-started/">Getting Started</a></li> </ul> <h2>Community</h2> <ul> <li><a href="http://groups.google.com/group/nanoc/">Discussion Group</a></li> <li><a href="irc://chat.freenode.net/#nanoc">IRC Channel</a></li> <li><a href="http://projects.stoneship.org/trac/nanoc/">Wiki</a></li> </ul> </div> </body> </html>
output/comment/comment.css
.onDefault { color: #999; } .comment_title { margin-top: 1.0em; display: none; } .comment_content { margin-top: 0.5em; margin-left: 0.5em; font-size: 0.9em; } .comment_name { color: #c00; } .comment_datetime { margin-left: 0.5em; font-size: 0.8em; color: #f93; } .comment_input { margin-top: 0.5em; margin-left: 0.5em; font-size: 0.9em; } .dispImage { width: 128px; height: 32px; border: 1px solid #ccc; } .selectImage { float: left; width: 128px; height: 32px; border: 1px solid #ccc; } .clearSelect { visibility: hidden; position: relative; top: 11px; } .clickImage { width: 320px; height: 32px; border: 1px solid #ccc; } .selectImage1 { float: left; background-image: url(1.png); width: 32px; height: 32px; } .selectImage2 { float: left; background-image: url(2.png); width: 32px; height: 32px; } .selectImage3 { float: left; background-image: url(3.png); width: 32px; height: 32px; } .selectImage4 { float: left; background-image: url(4.png); width: 32px; height: 32px; } .selectImage5 { float: left; background-image: url(5.png); width: 32px; height: 32px; } .selectImage6 { float: left; background-image: url(6.png); width: 32px; height: 32px; } .selectImage7 { float: left; background-image: url(7.png); width: 32px; height: 32px; } .selectImage8 { float: left; background-image: url(8.png); width: 32px; height: 32px; } .selectImage9 { float: left; background-image: url(9.png); width: 32px; height: 32px; } .selectImage0 { float: left; background-image: url(0.png); width: 32px; height: 32px; }
output/comment/comment.js
var xmlHttp; var selectedImage = ""; var selectedImageNum = 0; // 投稿ボタン押下時の処理 function pressSubmit() { xmlHttp = null; if(window.XMLHttpRequest) { // firefox ie7,8 safai opera xmlHttp = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { // ie6 xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { // ie5.5 xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } } if (xmlHttp) { // サーバへデータ送出 xmlHttp.onreadystatechange = responseServer; xmlHttp.open("POST", "/comment/comment_save.php", true); xmlHttp.setRequestHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8"); sendData = ""; sendData += "docurl=" + document.URL; sendData += "&"; sendData += "commentator_name=" + getValue(document.getElementById("commentator_name")); sendData += "&"; sendData += "commentator_mail=" + getValue(document.getElementById("commentator_mail")); sendData += "&"; sendData += "commentator_url=" + getValue(document.getElementById("commentator_url")); sendData += "&"; sendData += "comment=" + getValue(document.getElementById("comment")); sendData += "&"; sendData += "selectImage=" + selectedImage; xmlHttp.send(encodeURI(sendData)); } } // サーバからの応答処理 function responseServer() { if ((xmlHttp.readyState == 4) && (xmlHttp.status == 200)) { response = xmlHttp.responseText; separate = response.indexOf(";"); replaceImage = response.substr(0, separate); response = response.substr(separate + 1); separate = replaceImage.indexOf(":"); replaceImage = (replaceImage.substr(0, separate) == "REPLACEIMAGE") ? replaceImage.substr(separate + 1) : ""; separate = response.indexOf(":"); header = response.substr(0, separate); body = response.substr(separate + 1); if (header != "DATA") { // エラー window.alert(body); } else { // 正常 document.getElementById("comment_list").innerHTML = body; commentTitle(); element = document.getElementById("commentator_name"); element.value = ""; onDefault(element); element = document.getElementById("commentator_mail"); element.value = ""; onDefault(element); element = document.getElementById("commentator_url"); element.value = ""; onDefault(element); element = document.getElementById("comment"); element.value = ""; onDefault(element); } if (replaceImage == "1") { document.getElementById("dispImage").innerHTML = '<img src="/comment/comment_image.php" />'; clearSelect(); } } } // コメントリストのタイトルの表示/非表示 function commentTitle() { document.getElementById("comment_title").style.display = (document.getElementById("comment_list").innerHTML.trim()) ? "block" : "none"; } // onLoad処理 function onLoad() { textarea = document.getElementsByTagName("textarea"); for (i = 0; i < textarea.length; i++) { if (textarea[i].className.search("nodes") < 0) { if (textarea[i].value == textarea[i].defaultValue) { textarea[i].className += " onDefault"; } textarea[i].onfocus = function() {offDefault(this);} textarea[i].onblur = function() {onDefault(this);} } } input = document.getElementsByTagName("input"); for (i = 0; i < input.length; i++) { if ((input[i].className.search("noDefault") < 0) && ((input[i].getAttribute("type") == "text") || (input[i].getAttribute("type") == null))) { if (input[i].value == input[i].defaultValue) { input[i].className += " onDefault"; } input[i].onfocus = function() {offDefault(this);} input[i].onblur = function() {onDefault(this);} } } // クリック用のイメージの表示 elementClickimg = document.getElementById("clickImage"); for (i = 1; i <= 10; i++) { elementClickimg.innerHTML += '<div class="' + "selectImage" + (i % 10) + '" onClick="selectImage(this)"></div>'; } // コメントリストのタイトルの表示/非表示 commentTitle(); } // デフォルト表示無効 function offDefault(element) { if (element.className.search("onDefault") >= 0) { element.className = element.className.replace(/onDefault/g, "").trim(); element.value = ""; } } // デフォルト表示有効 function onDefault(element) { if (element.value == "") { element.className += " onDefault"; element.value = element.defaultValue; } } // デフォルト表示のデータ取得 function getValue(element) { return (element.className.search("onDefault") < 0) ? element.value : ""; } // 選択イメージの取得 function selectImage(element) { if (selectedImageNum < 4) { selectedImageNum++; className = element.className; selectedImage += className; document.getElementById("selectImage").innerHTML += '<div class="' + className + '"></div>'; document.getElementById("clearSelect").style.visibility = "visible"; } } // 選択イメージの破棄 function clearSelect() { document.getElementById("selectImage").innerHTML = ""; document.getElementById("clearSelect").style.visibility = "hidden"; selectedImage = ""; selectedImageNum = 0; }入力された項目のチェックや登録は Ajax を使用し、php で処理するようにしました。
Ajax については を参考にしました。
また、TEXTARIA や INPUT にデフォルト表示をし、クリックするとデフォルト表示が消えるようにしました。 を参考にしました。
function pressSubmit() ではコメントが投稿されたページの URL(document.URL)、投稿者の名前、メールアドレス、ホームページ、コメント及びスパム予防のためのイメージデータを Ajax でサーバへ送ります。comment_save.php がサーバで処理します。
function responseServer() ではサーバからの応答を受け取り、エラーならばエラーメッセージを表示し、正常ならばコメントリストを表示します。そして、スパム予防のためのイメージデータを書き換えます。
function commentTitle() はコメントがあれば「コメント一覧」を表示し、コメントがなければ「コメント一覧」を表示しません。
function onLoad() ではTEXTARIA や INPUT にデフォルト表示するための処理を行い、イメージデータ選択用のイメージを表示します。この関数は body の onLoad イベントでコールします。
function offDefault(element) はデフォルト表示を無効にします。
function onDefault(element) はデフォルト表示を有効にします。
function getValue(element) はデフォルト表示へ入力されたデータを取得します。
function selectImage(element) は選択されたイメージの情報を保持します。
function clearSelect() はクリアボタンをクリックしたときの処理を行います。選択されたイメージを破棄します。
output/comment/comment_get.php
<div id="comment_title" class="comment_title"><strong>コメント一覧</strong></div> <div id="comment_list"> <?php require_once("comment.php"); $db = new Comment(); echo $db->get_comments($_SERVER["DOCUMENT_URI"]); $db->close(); ?> </div> <br /> <div> <strong>コメントする</strong> <div class="comment_input"> <table> <tr> <td> <img src="/comment/commentator_name.png" width="16px" height="16px" /> </td> <td> <input type="text" id="commentator_name" size="30" value="なまえ" /> </td> </tr> <tr> <td> <img src="/comment/commentator_mail.png" width="16px" height="16px" /> </td> <td> <input type="text" id="commentator_mail" size="30" value="メール(非公開)" /> </td> </tr> <tr> <td> <img src="/comment/commentator_url.png" width="16px" height="16px" /> </td> <td> <input type="text" id="commentator_url" size="50" value="URL" /> </td> </tr> <tr> <td style="vertical-align: top;"> <img src="/comment/comment.png" width="16px" height="16px" /> </td> <td> <textarea id="comment" cols="50" rows="5">コメント</textarea> </td> </tr> <tr> <td colspan="2"> <div id="dispImage" class="dispImage"><img src="/comment/comment_image.php" /></div> </td> </tr> <tr> <td colspan="2"> <div id="selectImage" class="selectImage"></div> <input type="button" id="clearSelect" class="clearSelect" value="クリア" onClick="clearSelect()" /> </td> </tr> <tr> <td colspan="2"> 上と同じものを下から選びクリックしてください。 </td> </tr> <tr> <td colspan="2"> <div id="clickImage" class="clickImage"></div> </td> </tr> <tr> <td colspan="2"> <input type="button" value="投稿" onClick="pressSubmit()" /> </td> </tr> </table> </div> </div>コメントの表示と入力部分を出力します。
comment.php はデータベース操作クラスです。
$_SERVER["DOCUMENT_URI"] には SSI で呼び出した URI が格納されているようです。この URI に対するコメントをデータベースより抽出します。
なまえ、メール、URL、コメントのアイコンは からダウンロードして使用しています。
comment_image.php はスパム予防のためのイメージデータを作成します。
output/comment/comment.php
<?php define("CryptSalt", '$2a$07$12345678901234567890$'); class Comment extends SQLite3 { protected $table = "comments"; // テーブル名 // コンストラクタ function __construct() { parent::__construct("comment.sqlite"); $sql = "CREATE TABLE IF NOT EXISTS"; $sql .= " " . $this->table; $sql .= " ("; $sql .= " id INTEGER PRIMARY KEY AUTOINCREMENT"; $sql .= " , page TEXT"; $sql .= " , name TEXT"; $sql .= " , mail TEXT"; $sql .= " , url TEXT"; $sql .= " , comment TEXT"; $sql .= " , datetime TEXT"; $sql .= " , flag INTEGER DEFAULT 0"; // 0 : 新規、1 : 承認、2 : 拒否 $sql .= " )"; $sql .= ";"; $this->exec($sql); } // デストラクタ public function __destruct() { $this->close(); } // 新規、承認されたコメントの取得(html) public function get_comments($page) { $retval = ""; foreach ($this->select_page($page, array(0, 1)) as $row) { $retval .= $this->make_html($row["name"], $row["datetime"], $row["comment"]); } return $retval; } // html コメントの作成 public function make_html($name, $datetime, $comment) { $retval = '<div class="comment_content">'; $retval .= '<span class="comment_name">'; $retval .= $name; $retval .= '</span>'; $retval .= '<span class="comment_datetime">'; $retval .= date_format(date_create($datetime), "Y年m月d日 H:i"); $retval .= '</span>'; $retval .= '<div style="margin-left: 0.5em">'; $retval .= $comment; $retval .= '</div>'; $retval .= '</div>'; return $retval; } // データ取得 public function select_page($page, $flags = null) { $rows = array(); $sql = "SELECT * FROM " . $this->table; $sql .= " WHERE page = '" . $this->escapeString($page) . "'"; if (is_int($flags) != false) { $sql .= " AND flag = " . $flags; } if (is_array($flags) != false) { $rest = ""; foreach ($flags as $flag) { if (strlen($rest) != 0) { $rest .= ", "; } $rest .= $flag; } if (strlen($rest) != 0) { $sql .= " AND flag IN (" . $rest . ")"; } } $sql .= " ORDER BY id"; $sql .= ";"; $results = $this->query($sql); while ($row = $results->fetchArray()) { array_push($rows, $row); } return $rows; } // データ保存 public function save($page, $name, $mail, $url, $comment) { $sql = "INSERT INTO " . $this->table; $sql .= " ("; $sql .= " page"; $sql .= " , name"; $sql .= " , mail"; $sql .= " , url"; $sql .= " , comment"; $sql .= " , datetime"; $sql .= " )"; $sql .= " VALUES"; $sql .= " ("; $sql .= " '" . $this->escapeString($page) . "'"; $sql .= " , '" . $this->escapeString($name) . "'"; $sql .= " , '" . $this->escapeString($mail) . "'"; $sql .= " , '" . $this->escapeString($url) . "'"; $sql .= " , '" . $this->escapeString($comment) . "'"; $sql .= " , '" . $this->escapeString(date("Y-m-d H:i:s")) . "'"; $sql .= " )"; $sql .= ";"; $this->exec($sql); } } ?>define している CryptSalt は後述する comment_image.php 及び comment_save.php で使用する crypt() 関数に引数として与える salt です。"12345678901234567890" の20バイトを適当な文字に置き換えてください。
データベースは SQLite3 を使用します。データベースファイル名は comment.sqlite、テーブル名は comments とします。
function __construct() はデータベースファイルが存在しないならばデータベースファイルを作成します。従って、 comment ディレクトリには Apache ユーザに rw 権限を付与してください。テーブルが存在しないならばテーブルを作成します。page はコメントが投稿されたページの URI、name は投稿者の名前、mail は投稿者のメールアドレス、url は投稿者のホームページ、comment は投稿内容、datetime は投稿日時を格納します。flag は新規に投稿されたら 0、承認したら 1、拒否は 2 とします。
function __destruct() はデータベースをクローズします。
get_comments($page) は引数で指定されたページのコメントの中から新規と承認のデータを抽出し、表示可能な HTML 文字列にします。
function make_html($name, $datetime, $comment) は投稿者の名前、投稿日時、コメントを HTML 文字列にします。
select_page($page, $flags = null) は引数で指定された条件でデータを抽出し、抽出したデータを配列にします。
function save($page, $name, $mail, $url, $comment) はデータをテーブルに格納します。
output/comment/comment_image.php
<?php require_once("comment.php"); // 乱数によりイメージファイルを決定する $selectImage = ""; $imageFiles = array(); for ($i = 0; $i < 4; $i++) { $num = intval(mt_rand()) % 10; $selectImage .= "selectImage" . $num; array_push($imageFiles, $num . ".png"); } // image resourceの生成 $widthTotal = 0; $heightMax = 0; $images = array(); foreach ($imageFiles as $imageFile) { list($width, $height) = getimagesize($imageFile); $widthTotal += $width; $heightMax = max($heightMax, $height); array_push($images, array("image" => imagecreatefrompng($imageFile), "width" => $width, "height" => $height)); } // ベースのimage resourceを生成 $baseImage = imagecreatetruecolor($widthTotal, $heightMax); // ベース画像にイメージファイルの画像を順に合成 $dst_x = 0; foreach ($images as $image) { imagecopy($baseImage, $image["image"], $dst_x, 0, 0, 0, $image["width"], $image["height"]); $dst_x += $image["width"]; } foreach ($images as $image) { imagedestroy($image["image"]); } // PNG形式で出力 header('Content-Type: image/png'); imagepng($baseImage); imagedestroy($baseImage); // セッションに出力したデータを暗号化して記録 session_name("セッション名"); session_start(); $_SESSION["selectImage"] = substr(crypt($selectImage, CryptSalt), strlen(CryptSalt)); ?>スパム防止のため4つのイメージを選択してもらうようにしました。どの程度の効果があるかはわかりません。当サイトにスパムがあるのかどうか?イメージは適当に GIMP で数字として作成しました。
乱数で4つのイメージを決め、それのイメージを合成します。イメージ合成は を参考にしました。
4つのイメージのデータは暗号化してセッションに保存して、照合用に使用します。crypt() 関数に引数として与えた salt が crypt() 関数の戻り値の先頭にくっついてくるようですので、先頭の salt を除外したものをセッションに保存します。
output/comment/comment_save.php
<?php require_once("comment.php"); function alltrim($str) { return mb_ereg_replace("(^[[:space:] ]+)|([[:space:] ]+$)", "", $str); } $errmsg = ""; // なまえチェック $commentator_name = alltrim(@$_REQUEST["commentator_name"]); if (!strlen($commentator_name)) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "なまえが入力されていません。"; } // メールアドレスチェック $commentator_mail = alltrim(@$_REQUEST["commentator_mail"]); if (!strlen($commentator_mail)) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "メールアドレスが入力されていません。"; } else { if (!preg_match('/[!#-9A-~]+@+[a-z0-9]+.+[^.]$/i', $commentator_mail)) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "メールアドレスをご確認ください。"; } } // URLチェック $commentator_url = alltrim(@$_REQUEST["commentator_url"]); if ((strlen($commentator_url)) && (!preg_match('/(http|ftp):\/\/.+/i', $commentator_url))) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "URLをご確認ください。"; } // コメントチェック $comment = alltrim(@$_REQUEST["comment"]); if (!strlen($comment)) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "コメントが入力されていません。"; } // セッションに記録したデータと選択されたデータを比較し // 違えばエラー、同じならデータ保存 $replaceImage = false; session_name("セッション名"); session_start(); $selectImage = @$_SESSION["selectImage"]; $selectedImage = @$_REQUEST["selectImage"]; if ((strlen($selectedImage) == 0) || (crypt($selectedImage, CryptSalt) != (CryptSalt . $selectImage))) { $errmsg .= (strlen($errmsg)) ? "\n" : ""; $errmsg .= "同じ画像を選んでください。"; if (strlen($selectedImage) != 0) { $replaceImage = true; } } if (strlen($errmsg) == 0) { $replaceImage = true; } echo "REPLACEIMAGE:"; echo $replaceImage; echo ";"; if (strlen($errmsg) != 0) { // エラー送出 echo "ERROR:"; echo $errmsg; } else { // データ送出 echo "DATA:"; $page = parse_url(@$_REQUEST["docurl"], PHP_URL_PATH); $db = new Comment(); $db->save($page, $commentator_name, $commentator_mail, $commentator_url, $comment); echo $db->get_comments($page); $db->close(); // メール送信 mb_language("ja") ; mb_internal_encoding("UTF-8") ; $message = "ページ: " . $page; $message .= "\r\n"; $message .= "なまえ: " . $commentator_name; $message .= "\r\n"; $message .= "メール: " . $commentator_mail; $message .= "\r\n"; $message .= "URL: " . $commentator_url; $message .= "\r\n"; $message .= "コメント: " . $comment; mb_send_mail("宛先メールアドレス", "コメント登録", $message, "From: 送信元メールアドレス"); } ?>投稿されたコメントを Ajax で受け取り、データベースに登録します。登録されたことをメールで通知するようにしました。
function alltrim($str) は全角スペースを含む trim です。 を参考にしました。
メールアドレスのチェックには を参考にしました。
URL のチェックには を参考にしました。
メールアドレスや URL のチェックは存在確認をするのがベターでしょうが、ここでは形式チェックのみをしています。
メール送信に mb_send_mail() 関数を使用しています。この関数は localhost がメールサーバであることを前提とした関数のようです。メールサーバが他のホストであったり、SMTP 認証が必要ならば PEAR::Mail を使用するのがいいでしょう。
Rules
route '*' do
if item.binary?
# Write item with identifier /foo/ to /foo.ext
item.identifier.chop + '.' + item[:extension]
else
# Write item with identifier /foo/ to /foo/index.html
item.identifier + 'index.shtml'
end
end
Apache の SSI はデフォルトでは拡張子が shtml となっています。拡張子が html でも SSI が可能なように Apace の設定を変更してもいいでしょうが、ここでは nanoc が compile して出力するファイルの拡張子を shtml に変えるようにしました。サイトディレクトリの直下にある Rules ファイルの index.html を index.shtml に変えれば出力するファイルの拡張子が shtml に変わります。
/etc/apache2/mods-available/dir.conf
<IfModule mod_dir.c>
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm index.shtml
</IfModule>
Apache のデフォルトでは DirectoryIndex に index.shtml は設定されていません。Ubuntu Linux 10.04.3 では /etc/apache2/mods-available/dir.conf にその指定があります。これに index.shtml を追加します。これでファイル名を省略してもアクセスできます。しかし、nanoc view で確認するときには index.shtml を指定する必要があります。nanoc view で確認するとき、WEBrick は SSI を処理しないようですのでコメント欄は表示されません。
投稿があった時にその投稿の承認、拒否をする手段が必要ですが、当分の間 TkSQLite を使用することにします。