Jeans CMS 製作日記 http://www.rad51.net/blog/jeans/ 自分のための次世代CMS製作メモ ja Jeans CMS © Weblog http://backend.userland.com/rss https://www.rad51.net/jeans/skins/jeans/images/jeans2.gif Jeans CMS 製作日記 http://www.rad51.net/blog/jeans/ Jeans CMSのコードを、PHP5.3以上で記述することについて http://www.rad51.net/blog/jeans/?itemid=905
1)遅延静的束縛 (Late Static Bindings) の使用

Jeans CMSでは、クラスのスタティックなメソッド呼び出しが多用されており、オブジェクトはほとんど形成されない(一部、例外あり;PDO-SQLiteと、プラグインオプション)。規約では、個々のプラグインのクラス(jp_xxxx)はすべてpluginクラスを継承して静的に作成される。

プラグインクラスでは、必要な場合、メソッドをオーバーライドして用いる。その際、"self::"の挙動がクラスインスタンス内での"$this->"の挙動と異なる。具体的には、pluginクラス(プラグインの親クラス)内で"self::"を用いた際、継承先でオーバーライドしたメソッドは呼び出されず、元のpluginクラス内のメソッドが呼び出されてしまう。これを解決するのが、PHP5.3で用意された機能、"static::"で、pluginクラス内での記述で"static::"を用いれば、継承先のプラグインからこのコードが呼び出された場合に、継承先のプラグインのメソッドを呼び出すことが出来る。

改めて、plugin.phpのコードを見直してみたが、"static::"を使わずにこの辺りのことを実装するために、プラグインクラスを最初に呼び出したときに、いくらかの初期化コードが実行されるようになっている。"static::"を利用すれば、これらの初期化コードを実行する必要がないから、スピードアップが図れることになる。

2)名前空間

名前空間を使うことにより、Jeans CMSと他のツールと共存することが可能になると思われる。ただし、__autoload()関数は、グローバル空間に書かなければならないようで、他のツールと共存させることは出来ないようだ。ただし、__autoload()関数内で、呼び出し元のコードがどのディレクトリに存在しているかを捜査することで、Jeansの__autoloadなのか、他のツールの__autoloadなのかを判断することは出来るだろうと思われる。

3)クロージャ

viewクラスで、create_function()が使われている。この部分をPHP5.3のクロージャに置き換えることで、安定した動作が見込まれる。また、イベントの実装でクロージャを用いれば、色々と便利になる場面が出てくることが、予想できる。]]>
General http://www.rad51.net/blog/jeans/?itemid=905 Tue, 21 Oct 2014 14:15:23 PDT ITEM905_20141021
アイコン http://www.rad51.net/blog/jeans/?itemid=783
Chimply
Ajax用のさまざまなbusyアイコンを作成することができる。

FAMFAMFAM - Mini Icon
このサイトのSILKアイコンはさらに充実しているが、ライセンスの関係で、使えない。]]>
General http://www.rad51.net/blog/jeans/?itemid=783 Tue, 16 Mar 2010 14:57:55 PDT ITEM783_20100316
Jeans CMS 0.7.0 alphaを公開 http://www.rad51.net/blog/jeans/?itemid=781
http://jeanscms.sourceforge.jp/index.php?itemid=25

今後(実際には、数ヶ月前から)、情報発信はjeanscms.sourceforge.jpでさせていただきますので、そちらをご参照ください。]]>
General http://www.rad51.net/blog/jeans/?itemid=781 Tue, 02 Mar 2010 13:39:14 PST ITEM781_20100302
イベントの実装についての考察 http://www.rad51.net/blog/jeans/?itemid=768
sourceforge.jpにおいて、Jeans CMSがプロジェクトの一つとして承認されました。現在開発中のJeans CMSは、SVNレポジトリの閲覧ページで閲覧する事が出来ます。機能がある程度まとまれば、ダウンロードページにファイルを上げる予定です。

さて、Jeansにおいて、プラグイン用のイベントをどのように実装するかについて。Nucleusでは、$manager->notify()というメソッドを用い、プラグインに閲覧させる変数は値渡しで、プラグインに変更を許可する変数は参照渡しとする事により、必要なデータのやり取りをしている。これをもう少し、フレキシブルで簡潔で効率的なやり方に出来ないかと考えている。

一つ思いついたので、ここにメモしておく。それは、イベントを呼び出す側のメソッド内で、evalを行う方法。これだと、コードの実行は呼び出し側のメソッド内のスコープでおこなわれ、すべてが参照渡しのようになる。evalする文字列はcoreクラスから引き渡され、各プラグインはコード文字列をsql tableに登録する形になる。実行されるコードはスタッティクである。ダイナミックに何かを行いたい場合、evalするコードの中でプラグインのメソッドを呼び出せば良い。

evalの多用は、ともすればスピードの低下につながりかねない。が、今の場合だと、簡単なコードの実行の場合、プラグインクラスの呼び出しを行わずに済む場合もあり、むしろスピードアップにつながるかもしれないと考える。]]>
General http://www.rad51.net/blog/jeans/?itemid=768 Fri, 18 Dec 2009 10:52:49 PST ITEM768_20091218
テキストエリアで、リターン時に自動的にbrを入れる。 http://www.rad51.net/blog/jeans/?itemid=766
ただこのやり方では、例えばテーブルを作成したい場合などに自動的に追加された<br />が邪魔になってしまう。

そこでJeansでは、アイテム編集画面でJavascriptを使って解決することを考えた。リターンキーを押したときに<br />が自動的に追加されるようにする。このあたりは、HTMLのことをよく知らないユーザーにはあまりフレンドリーではないが、そこは別途WYSIWYGを用意することで対処することにして、とりあえず自分自身が使いやすいと思うインターフェースにしてみる。

試行錯誤の後に完成したJavascriptが、次のとおり。とりあえず、FireFoxとIEでの動作を確認した。OperaとかSafariではまだ未試験だが、とりあえず後回しでよかろう。

<html><body>
<script type="text/javascript">
function test(element,event){
    if (event.keyCode!=13) return true;
    if (event.shiftKey) return true;
    if (element.selectionStart) {
        // FireFox
        var t=''+element.value;
        var start=element.selectionStart;
        var end=element.selectionEnd;
        element.value=t.substring(0,start)+'<br />'+t.substring(start);
        element.selectionStart=start+6;
        element.selectionEnd=end+6;
        return true;
    } else if (element.createTextRange) {
        // IE
        var range = document.selection.createRange();
        range.text='<br />';
        range.select();
        return true;
    }
    return true;
}
</script>
<textarea onKeyPress="return test(this,event);" cols="100" rows="30"></textarea>
</body></html>

紆余曲折の結果、上のコードになった。リターンキーを押すと、<br />が自動的に付加される。付加したくない場合は、Shift + Enterで。

以下、不採用になった書きかけのコード。IEで、selectionStartとselectionEndを実現するためのもの。この部分はうまく動いていたので捨てるにしのびず、メモしておく。

var range = document.selection.createRange();
var range2= range.duplicate();
range2.moveToElementText(element);
range2.setEndPoint('EndToEnd',range);
var start = range2.text.length - range.text.length;
var end = start + range.text.length;

]]>
General http://www.rad51.net/blog/jeans/?itemid=766 Sat, 28 Nov 2009 14:18:38 PST ITEM766_20091128
定数が速いか、スタティック変数が速いか http://www.rad51.net/blog/jeans/?itemid=765
    static public function compile($source) {
        static $search=array(
            '/(\r\n|\r|\n)(?:\t)([^<%]|<[^%]||%[^>])/',
            '/(\r\n|\r|\n?)<%([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)%>/',
            '/(\r\n|\r|\n?)<%([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\(([\s\S]*?)\)%>/');
        static $replace=array('self','compile_cb');
        // Actually, JIT is just a preg_replace_callback.
        $compiled=preg_replace_callback($search,$replace,$source);
        $code=create_function('$data','?>'.$compiled.'<?php return true;');
        if (!is_callable($code)) return error::compile_error($source,$compiled);
        return $code;
    }


ローカル変数$search,$replaceをスタティックに設定しているのは、同じ内容の変数を何回も定義しなおすのが遅いと思ったから。

でもこれ、本当なの?スタティック変数じゃなく定数を使ったら?…など疑問に思ったので、実験してみた。本来はPHPのソースコードをじっくり眺めて解決すればよいのだろうけれど、それも面倒なので、とにかく実験。

実験に使ったソース。
<?php

function test1($text){
    static $test='/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/';
    return preg_replace($test,$test,$text);
}

define('_TEST','/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/');
function test2($text){
    return preg_replace(_TEST,_TEST,$text);
}

$text='sdnvrf[sdc;werqwpefdsvad';
$t1a=$t2a=0;
for($i=0;$j<20;$j++){
    $tc=get_msec();
    for($i=0;$i<1000;$i++) $text2=false;
    $tc=get_msec()-$tc;
    $t1=get_msec();
    for($i=0;$i<1000;$i++) $text2=test1($text);
    $t1=get_msec()-$t1-$tc;
    $t2=get_msec();
    for($i=0;$i<1000;$i++) $text2=test2($text);
    $t2=get_msec()-$t2-$tc;
    echo substr($t1,0,5), $t1<$t2?' < ':' > ', substr($t2,0,5), ' ', substr(abs($t1-$t2),0,5), "\n";
    $t1a+=$t1;
    $t2a+=$t2;
}

echo "\n";
$t1=$t1a/20;
$t2=$t2a/20;
echo substr($t1,0,5), $t1<$t2?' < ':' > ', substr($t2,0,5), ' ', substr(abs($t1-$t2),0,5), "\n";

function get_msec(){
    $t=explode(' ',microtime());
    return ($t[0]+$t[1])*1000;
}

実験結果(PHP 5.2.5.5を使用)
5.544 > 5.002 0.541
5.500 > 5.216 0.284
4.776 > 4.677 0.098
4.527 > 4.363 0.164
4.653 > 4.421 0.231
4.604 > 4.495 0.108
4.712 > 4.517 0.195
5.213 > 4.660 0.552
4.455 > 4.375 0.079
4.636 < 4.676 0.040
4.713 > 4.600 0.113
4.637 > 4.567 0.069
5.439 > 5.179 0.260
5.497 > 4.406 1.090
4.771 > 4.661 0.109
4.618 > 4.410 0.208
4.717 > 4.468 0.248
4.780 > 4.428 0.351
4.623 > 4.583 0.040
4.694 > 4.522 0.171

4.855 > 4.611 0.244

結果として、定数を使ったほうが平均で0.2マイクロ秒ほど早い。ちなみに、PHP 5.3.0.0を使った結果もほぼ同じだった。定数が使える場合は、そちらの方が若干速い。

なら、文字列の直書きではどうか。
function test1($text){
    return preg_replace('/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/',
        '/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/',$text);
}

define('_TEST','/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/');
function test2($text){
    return preg_replace(_TEST,_TEST,$text);
}

結果(平均のみ表示)
4.305 < 4.559 0.254

直書きの方が早かった。直書きするよりスタティック変数を使ったほうが速いのではないかと思っていたからそれを多用していたが、それが間違いであることが判明。

では、ローカル変数をスタティック指定する意味はあったのかというと。
function test1($text){
    $test='/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/';
    return preg_replace($test,$test,$text);
}

function test2($text){
    static $test='/sdfwersdfcnwer139r2WEHDFqwr3rASFADSF/';
    return preg_replace($test,$test,$text);
}

結果
5.802 > 5.234 0.567
6.070 > 5.839 0.230
4.562 < 4.573 0.010
4.829 < 5.054 0.224
4.836 > 4.823 0.012
4.614 < 4.672 0.058
4.694 > 4.685 0.009
4.631 < 4.645 0.013
4.623 < 4.824 0.201
4.720 > 4.680 0.040
5.002 > 4.933 0.069
4.660 < 4.678 0.018
4.760 > 4.719 0.040
4.526 < 4.551 0.025
4.738 > 4.645 0.092
4.500 < 4.623 0.122
4.646 < 4.700 0.053
5.519 > 4.614 0.904
4.493 < 4.602 0.108
5.523 > 5.429 0.093

4.887 > 4.826 0.061

ほとんど同じ……。

実験して分かったのは、この辺の工夫はスピードには余り影響せず、ソースをきれいにする以外の意味はあまりないこと。スピードを考えるのであれば、定数や変数を定義せずに、直接文字列を与えたほうが速いということが分かった。覚えておこう。

でもね、もし変数に入れておく場合(配列は定数で定義できない)、やっぱりスタティックに定義するだろうなぁ。ソースを眺めた場合、その関数が呼び出されるたびに変数が定義しなおされるのは、どう考えても無駄だし。

定数を使う場合や、変数に文字列を代入するだけの場合、PHP内部では、JITによりメモリ上に構築された文字列へのポインタを与えるだけなんだろう、多分。文字列の演算を行う場合のみ、そのコピーが作成されるということか。

<%media(20091125-jeans0563.zip|ver 0.5.63)%>]]>
General http://www.rad51.net/blog/jeans/?itemid=765 Sat, 21 Nov 2009 13:08:15 PST ITEM765_20091121
pageライブラリを作成 http://www.rad51.net/blog/jeans/?itemid=764
呼び出しは、こんな風に。

<%page.init%>
Page <%data(page,this)%> of <%data(page,pages)%>
<%if.data.ismorethan(1,page,pages)%><%page(paging.inc)%><%endif%>

テンプレートの記述は、こんな風に。

<%select(template)%>
<%case(head)%>
 [
<%case(body)%>
<%if.data.ismorethan(1,count)%>,<%endif%>
<%if.data.isempty(this)%>
<a href="<%data(link)%>">&nbsp;<%data(count)%>&nbsp;</a>
<%else%>
<b style="color:red;">&nbsp;<%data(count)%>&nbsp;</b>
<%endif%>
<%case(foot)%>
] 
<%endselect%>


管理画面を少し作成し、新しい記事の追加と記事の編集がもう少しでできそう。とりあえず、<%media(20091114-jeans0541.zip|バックアップ)%>(管理画面を見てみたいという方は、ログインID:admin、パスワード:adminでログインしてみてください)。]]>
General http://www.rad51.net/blog/jeans/?itemid=764 Sat, 14 Nov 2009 00:48:24 PST ITEM764_20091114
時刻管理 http://www.rad51.net/blog/jeans/?itemid=763
グローバル設定で、タイムゾーンを指定。(もし、ブログごとにタイムゾーンを切り替えたいのなら、その指定はプラグインで。)
date_default_timezone_set(_CONF_TIMEZONE);

データベース上では、すべてGMTで保存。
$sql_time=gmdate('Y-m-d H:i:s', time());

表示は、次のように。
$time=strtotime($data['time'].' GMT');
$text=strftime($format,$time);

ブラウザから受け取ったデータをデータベースに保存するときは、
$time=strtotime($_GET['time_text']);
$sql_time=gmdate('Y-m-d H:i:s', $time);
]]>
General http://www.rad51.net/blog/jeans/?itemid=763 Fri, 13 Nov 2009 13:20:56 PST ITEM763_20091113
Jeans CMSについて http://www.rad51.net/blog/jeans/?itemid=762
Jeans CMSとは、つまり次のようなものであろう。

利用環境

 八方美人的なツールではない。かなり限られた環境での使用を目的とする。次のような条件に一致することが必要。

・Apache/PHP(>5.2)が使えること。
・SQLite (version 3)が、PDOで使えること。
・記事の編集ができるメンバーに最高権限を与えられること。
・文字コードはUTF8のみ

 DBは、SQLite固定とする。以前は、SQLite/MySQL/PgSQLのサポートを考えたが、クエリーの方言をラップするコードが複雑になるため、断念した。PHPのバージョンは今のところ5.2以上を考えているが、5.3以上にする可能性も有る。メンバーの権限の問題もコードの簡略化に大きく影響する。いまのところ、ログイン可能なメンバーは、コメントのみ書き込めるゲストと最高権限という、両極端な2つだけを考えている。

フレームワークとしてのJeans

 Housekeeping Jeansと名づけたフレームワークを考える。今のところ、次のコンポーネントがこれに相当する。

・次のPHPスクリプト:jeans.php, view.php, member.php, error.php, plugin.php
・次のDBテーブル:jeans_config, jeans_login

 役割は、次のとおり

・セキュリティー関連(XSS, SQL-injection, CSRFなどの対策)
・メンバーのログインの管理
・スキンのパース
・ライブラリの管理
・プラグインの管理

 現在、PHPスクリプトは合計で800行ほど。2000行以内を目安にしたい。これは、Nucleusのglobalfunctions.phpの行数に相当する。セキュリティーに大きく関係するこの部分は、なるだけ簡潔で、かつ、最小限の機能を提供するようにし、結果としてセキュリティー管理の簡便性・確実性が図れるようにしたい。

 グローバルに定義する関数は使用しない。変わりに、クラスのスタティックメソッドを多用する(クラスの命名方法に規約あり)。オブジェクトは、次の例外を除き、利用しない。

・PDOクラスのインスタンス
・plugin派生クラス(プラグイン)のインスタンス
・デストラクタが必要な場合

 PDOに関しては、オブジェクトはJeans内部でのみ利用され、フレームワーク外からのアクセスはスタティックメソッドを通じて行う。プラグインは現在のところ、Nucleusと同様に、インスタンスを作成する予定である。ただし、ベータバージョンでPHP5.3が採用になった場合、ここもスタティックで行く可能性もある。

 スキンは、派生させることが可能(例えば、defaultスキンの派生スキンを作成し、変更部分だけ記述するなど)。一見実装が難しそうに思えるこの機能は、実際にはコードの簡略化に大きく貢献しそうだ。

 ライブラリ、プラグインという2つの概念を考える。これらは互いに良く似ているが、ライブラリの方がより基本的な機能を提供し、プラグインの方がより高度な機能を提供することとする。

CMSとしてのJeans

 JeansをCMSとして捉えた場合、今のところ、次のものがここに含まれる。

・次のPHPスクリプト:admin.php
・次のスキン:admin
・次のDBテーブル:jeans_group, jeans_item, jeans_member

 Nucleusにおけるブログ・カテゴリー・アイテムという概念は簡略化され、グループ・アイテムの二本立てになった。グループは、OSのファイルシステムにおけるディレクトリのような存在で、入れ子にすることができる。グループ、アイテムの両方に、追加の情報を持たせることが可能で、これらの情報はjeans_configテーブルに記述することになるだろう。

 管理画面は、adminスキン及びadminライブラリの二つで構成される。管理画面を好みのものに変更したい場合、adminスキンの派生スキンを作成すれば、比較的簡単に行えるはず。

 メンバーの権限管理は、このレベルで行う(具体的には、adminライブラリで)。Housekeeping Jeansはどのような権限管理でも対応できるため、独自のadminライブラリを作成すれば、複雑な権限管理も可能なはずである。

ブログツールとしてのJeans

 Jeansをブログツールとして捉えた場合、今のところ、次のものがここに含まれる。

・次のPHPスクリプト:blog.php, item.php
・次のスキン:default

 ここは、まだまだ変更される部分。item.phpは、CMSとしての分類に入れることになる可能性が大。今のところ、$_GET['catid']などを利用しているので、ブログツールの分類に入れた。とにかく、CMSとしての姿と、ブログツールとしての姿に違いをつけることで、汎用的なCMSとして使えるようにしたい。

<%media(20091111-jeans0526.zip|現在のバージョン(0.5.26α)のバックアップはここ(LGPL2ライセンス)。)%>]]>
General http://www.rad51.net/blog/jeans/?itemid=762 Tue, 10 Nov 2009 11:08:33 PST ITEM762_20091110
文字コードの問題 http://www.rad51.net/blog/jeans/?itemid=760
サポートする文字コードは、UTF-8のみにすることにした。これも、コードの簡潔化のため。その他、さまざまな規約を設けることで、簡潔なコードでの完成を目指す。今月は、Jeans月間かな?

で、表題の件。文字コードの問題は主に、XSS対策でのこと。2つ考え方があって、ひとつはJeansに入力される文字列をチェックする方法。もう一つは、出力するときにチェックする方法。

最初に書いたとおり、最初のバージョンではなるだけ全体のコードを簡潔にしたい。で、出力側にはhtmlspecialchars()を通すのだが、ここでUTF-8エンコード指定がどのように考慮されているのかを調べてみた。

<?php

show('試験の文章(SJIS)');
show('Alphabet only.');
ob_start();
show(mb_convert_encoding('試験の文章(UTF-8)','UTF-8','SJIS'));
echo mb_convert_encoding(ob_get_clean(),'SJIS','UTF-8');

function show($text){
    $text=htmlspecialchars($text,ENT_QUOTES,'UTF-8');
    echo "'$text'(".strlen($text).")";
    echo rawurlencode($text);
    echo "\n-----------------------\n";
}

Windowsのコンソールからこのコードを実行すると、次のようになる。

''(0)
-----------------------
'Alphabet only.'(14)Alphabet%20only.
-----------------------
'試験の文章(UTF-8)'(22)%E8%A9%A6%E9%A8%93%E3%81%AE%E6%96%87%E7%AB%A0%28UTF-8%29
-----------------------

SJISの文字列をUTF-8でhtmlspecialchars()に通すと、空の文字列が帰ってきた。とりあえず、最初のバージョンでは、出力側のチェックとして、htmlspecialchars()を通すだけでよさそうだ。]]>
Security http://www.rad51.net/blog/jeans/?itemid=760 Tue, 03 Nov 2009 14:34:35 PST ITEM760_20091103