作成を思い立った悲劇はひとつまえの記事を参照してください。
MovableTypeへの恨みは技術の壁を乗り越えました。タイトルの通り、書きかけの記事(でも何でも)を自動保存してくれるAjax(JavaScript+PHP)なプログラムを突貫で作りました。もともと俺clap!で導入するはずだった機能を必要な部分だけ切り取って持ってきたのでMovableType4だろうがMovableType3だろうが理論上はmixiの日記だろうが果てはあなたのパソコンのメモ帳だろうが、なんでも書きかけの記事を自動保存してくれます。
流れとしてはJavaScriptで記事作成画面のテキストエリアの内容を読み込み、それをサーバー上のPHPのプログラムに送信してファイルに保存する、といった感じです。つまりJavaScriptはテキスト内容を送るだけ、PHPは送られてきた内容を保存するだけ、という役割分担です。逆を言えば両方揃って使えないと意味がありません。
以下JavaScriptとPHPのソースを書きます。なおJavaScriptはXMLHttpRequestという、最近Ajaxなどで利用されている関数を用いています。旧いブラウザだと動かない恐れがあります。また確か違うドメインへのアクセスはできないはずなので、JavaScriptと同じドメイン下でPHPが動かせないとダメです(普通は大丈夫)。こちらではOpera9とIE7、FireFox2でJavaScriptの動作確認を、PHP5.23でPHPの動作確認をそれぞれ行いました(PHP4でも問題なく動くはず)。
var SaveID;
window.TimerList = new Array();
function noticeStatus(notice){
var StatusLayer = "StatusLayer";
// StatusLayerというidのブロックをページ上に作ると自動保存の成否を報告してくれる(Ajax?)
if(statusBar = document.getElementById(StatusLayer)){
statusBar.innerHTML = notice;
}
}
function setAutoSaver(SaveID,SaveBody){
if(!window.TimerList[SaveID] || !isNaN(window.TimerList[SaveID])){
exeSeconds = 60; // 指定秒ごとにformSaverを呼び出す
noticeStatus('Save : ' + SaveID + ' (every ' + exeSeconds + ' seconds)');
window.TimerList[SaveID] = setInterval(function(){formSaver(SaveID,SaveBody);},1000 * exeSeconds);
return;
}
}
//データを保存するプログラム(PHP)を呼び出す
function formSaver(SaveID,SaveBody){
var httpObj;
var dt = new Date();
var dtH = dt.getHours();
var dtM = dt.getMinutes();
var dtS = dt.getSeconds();
var AutoSaverProgram = "http://~.php(ここに後述のPHPプログラムのURL)";
if(httpObj = createXMLHttpRequest()){
noticeStatus('ID:' + SaveID + ' - Now Saving...');
}else{
noticeStatus('ID:' + SaveID + ' - Fail?');
}
if(SaveBody.value == ""){
noticeStatus('ID:' + SaveID + ' is not auto-saved( = NULL)');
return;
}
httpObj.open("post",AutoSaverProgram,false);
httpObj.setRequestHeader("content-type","application/x-www-form-urlencoded;charset=UTF-8");
httpObj.onReadyStateChange = function() {
//サーバ側の処理終了
if(httpObj.readyState == 4){
if(httpObj.Status == 200) {
noticeStatus('ID:' + SaveID + ' - Success Auto-Saving!!');
}else{
noticeStatus('Connect Error');
}
}else{
noticeStatus('ID:' + SaveID + ' - Fail Auto-Saving!!');
}
}
//フォームの入力データを送信
httpObj.send("Body=" + encodeURIComponent(SaveBody.value) + "&ID=" + encodeURIComponent(SaveID));
noticeStatus('ID:' + SaveID + ' - Auto-Saved(' + dtH + ':' + dtM + ')');
return true;
}
function createXMLHttpRequest(){
if(window.XMLHttpRequest){
httpObj = new XMLHttpRequest();
}else if(window.ActiveXObject){
try{
httpObj = new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){
httpObj = new ActiveXObject("Microsoft.XMLHTTP");
}
}
return httpObj;
}
以上を"DraftAutoSaver.js"とでも名前をつけて保存、アップロードしてください。オートセーヴァーって書くとかっこよいですね(どうでもいい)。
なおXMLHttpRequestまわりの処理についてはOpenSpaceさんのAjaxを勉強しようコーナーのこの記事を参考に作りました。よって正しい書き方かどうかわかりません。ま、動いているから大丈夫ですよ(無責任)!
さてそれではもう一対のPHPプログラムのほうを。JavaScriptの働きを“オート”と表現するならこちらは“セーブ”です。
if(isset($_POST['ID']) && isset($_POST['Body'])){
$ID = $_POST['ID'];
$Body = $_POST['Body'];
$FileName = "ログファイルを置くサーバー上のフォルダ" . "DraftAutoSaved_". $ID . ".cgi";
if(file_exists($FileName)){
$PutData = file_get_contents($FileName);
$PutData = unserialize($PutData);
$LogCount = count($PutData);
$LogFileSize = filesize($FileName);
if($LogCount >= 30 || ($LogFileSize >= 150000 && $LogCount >= 10)){
array_shift($PutData);
}
$LastPutData = end($PutData);
}else{
$LogCount = 0;
$PutData = array();
$LastPutData = "";
}
$LogCount++;
$NowDate = $LogCount . " " . date("Y/m/d G:i:s",time());
if($Body != $LastPutData){
$PutData[$NowDate] = $Body;
$PutData = serialize($PutData);
file_put_contents($FileName,$PutData,LOCK_EX);
}
header("HTTP/1.0 200 OK");
exit();
}
ログファイルを置くサーバー上のフォルダ、という部分だけ書き換えてください(ex. /public_html/php/log/)。ここにログが保存されます(パーミッションは666くらいに設定)。あとは普通のPHPファイルとして保存してサーバー上に設置するだけです。パーミッションは755くらい? 先述のJavaScriptのformSaverという関数にある“後述のPHPプログラムのURL”のところと、このPHPプログラムの名前をあわせることも忘れずに。
このPHPプログラムは『PHP的な意味でシリアライズして配列のデータとして保存する』、『30コ以上配列(=ログ)は保存しない(古い順から消していく)』、『最後に書き込んだ内容と同じ内容が送信されてきた場合は上書き保存しない(何もせずにフェーズ終了)』仕様となっています。my_file_put_contentsなんて使いませんよ(PHP4の方は左記の記事を参照してください)。
あとは自動保存したいページのヘッダでJavaScriptを呼び出し、そして自動保存したいtextareaタグに以下のように記述するだけ。
onclick="setAutoSaver('Body',this);"
これで指定したフォルダに毎指定秒、『DraftAutoSaved_Body.cgi』という名前で自動保存されていきます。setAutoSaver関数の第1引数である"Body"の部分を書き換えると保存するログの名前も変わるので、いくつでもこの自動保存機能をつけることができます。また保存するファイルの拡張子がcgiとなっているのは外部からのアクセスを遮断するためです。
セキュリティ面で言えばこのPHPプログラムと生成されるログファイルは隠蔽+保護しておくべきです。しかしプログラムのURLはJavaScriptのなかに書き込まなければならない都合上隠蔽できません。よって必ずhtaccessなどで悪意のある第三者がアクセスできないよう対策を施してください。保存開始のたびに認証が求められますがそれ以降は認証が出てきませんし、逆に自分の気づかないところで自動保存が始まっていて負荷が高まっていたなんていうこともなくなるので安心です。ログを出力するフォルダもそもそもhtaccessで保護しておけば完璧でしょう。
さて今回のこの一連のプログラムをMovableType4に適用するための方法を以下にご紹介します。
エントリー編集画面は『mt/tmpl/cms/edit_entry.tmpl』というテンプレートによって作成されています(MovableType4.01基準)。このedit_entry.tmplを下記の2ヶ所カスタマイズします。
edit_entry.tmplの207行目(に挿入)
旧 : (挿入)
新 : <script type="text/javascript" src="/DraftAutoSaver.js"></script>
↑ヘッダの部分にJavaScriptを呼び出す一文を加えるわけです
edit_entry.tmplの867行目(を書き換え)
旧 : <textarea id="editor-content-textarea" ~ >
新 : <textarea onclick="setAutoSaver('Body',this);" id="editor-content-textarea" ~ >
onclickで関数を呼び出すわけです(このあたりは関数を呼び出せればなんでも良)
これで稼動できる状態になりました。必要なファイルを設置したら記事を作成する画面にアクセスしてみてください。見た目は何も変わっていませんが、きちんと“自動保存してくれる”textareaに変わっているはずです。ソースを表示して確認しておくと良いかと思います。なおこの部分は残念なことにMobvableTypeをバージョンアップさせるたびに自分で書き直す必要がありますのでお忘れずに(かくいう自分も)。
さて先述のとおりこの2つのファイルを組み合わせればどんなフォームの文章でも下書きを保存することができます。ApacheとPHPをパソコンにインストールしてローカルでもPHPを動かせるようにすれば、普段使っているパソコンで同等の機能が実現します。これでメモ帳も強まりますね。
実際たいしたことのないプログラムですが、あると嬉しい+組み合わせれば無限の可能性が広がる、ということで取り急ぎご紹介しました。これで僕も無用な心配から解放されます。
[超ご注意] 思いつきで作ったものなので処理に穴がある危険性があります。利用は自己責任でお願いいたします。またこのページ(と各関数)は更新する場合がありますので、定期的に確認することをオススメします。またこのプログラムがAjaxと呼んでいいかどうかわかりませんのでそのあたりはお互いこっそり“Ajaxだぜウヒヒ”とか思い込んでおきましょう。
[そういえば] 保存することだけに夢中になっていたため閲覧するプログラムを作り忘れていました。まぁ、気が向いたら作ります。
[超ご注意II] 現に一部を書き換えましたのでお知らせいたします(1/17 AM5:00)。formSaverの関数内でencodeURIという関数を使っていましたが、encodeURIComponentでないと一部の文字列で不具合があることがわかりました。具体的には"+"などの文字があるとうまくJavaScriptでpost送信できないようです。理論上は確かにその通り PHPばかりやっているとそのあたりのことを忘れがちになるから困る。
[超ご注意III] とても危険な誤りがありました(1/18 PM4:00)。
× : if($LogCount >= 30 && ($LogFileSize >= 150000000 && $LogCount >= 10)){
○ : if($LogCount >= 30 || ($LogFileSize >= 150000 && $LogCount >= 10)){
150MBのギガントなログがオートセーブされ続けるところでした。
2008年08月21日 0時更新
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
|---|---|---|---|---|---|---|
| « 12月 | - | 02月 » | ||||
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | 31 | ||
