デリゲートでBBCodeの拡張を
この記事の情報は一部古いため、訂正等の関連文書が存在します。
今までのXOOPS2.0.xに対するHackと呼ばれる物の中に、class/module.textsanitizer.php を改変してBBCode(XOOPSではXoopsCodesと呼んでいますが^^)タグの拡張を行っている物が、幾つかあります。
siteurl タグの img版である siteimgタグの追加や、中にはflashタグなんて物もあったりします、多言語対応をする場合にjaとかenとかの言語タグ拡張などもこのHackの一部とも考えられます。私もmodPukiWikiを使用したwikiタグ拡張を行った物を公開したりもしています。
XOOPS Cube2.1では、これらをHack無しで実現する手段を、preloadとデリゲートの併用によって提供しています。今回はこのBBCodeの拡張方法について説明を行います。
但し、本当はこのmodule.textsanitizer.php 自体の作りを見直さないといけないので、今回紹介する方法が、いつまで使えるかは保証の限りではありません。
まず、class/module.textsanitizer.php では、BBCodeの変換を、xoopsCodeDecode()というメソッドにて行っています。このメソッドがBBCodeをHTMLに変換するのには、基本的にはPHPのpreg_replace関数に渡す検索パターンと置換文字列の組合せにした変換ルールを配列にして、これをもとにして文字列の置き換えを行っているだけです。(実は[code]タグだけは別の方式にて変換されています。)
従来のほとんどのHackは、この変換配列に新しい変換ルールを追加する事によって新たな機能拡張を行っています。
このBBCodeの変換に拡張性を持たせるためには、この変換ルール配列を外からメンテナンス出来るようにしてやれば良いわけです。
そこで、xoopsCodeDecode()メソッド内でpreg_replace()関数を呼び出している部分に以下のようなロジックをXOOPS Cube2.1では追加を行っています。
<?php
// RaiseEvent 'Legacy.TextSanitizer.XoopsCodePre'
// Delegate may replace conversion table
// varArgs :
// 'patterns' [I/O] : &Array of pattern RegExp
// 'replacements' [I/O] : &Array of replacing string
// 'allowimage' [I] : xoopsCodeDecode $allowimage parameter
//
if ($result =& XCube_EventUtils::quickRaiseEvent('Legacy.TextSanitizer.XoopsCodePre',
array('patterns'=> &$patterns,
'replacements' => &$replacements,
'allowimage'=>$allowimage))) {
$patterns =& $result['patterns'];
$replacements =& $result['replacements'];
}
$text = preg_replace($patterns, $replacements, $text);
?>
ここでは、変換用の配列である、$patternsと$replacementsの参照を、EventArgsに積んで、'Legacy.TextSanitizer.XoopsCodePre'というイベントを発行を行っています。
$patternsと$replacementsそれぞれが参照で渡されていますので、イベント発行で呼び出されたデリゲート側で、これらの配列をメンテナンスしてやれば、メンテナンスされた配列が、preg_replace()関数に引き渡される事になります。
ここで、「なんで、イベント名にpreって付いているのだろう?postもあるの?」思われた方がいるかもしれませんが、 そのとおり、preg_replace()関数呼出の後に、
<?php
// RaiseEvent : 'Legacy.TextSanitizer.XoopsCodePostFilter'
// Delegate may convert output text with quickApplyFilter rule
// varArgs :
// 'string' [I/O] : Text to convert;
// 'allowimage' [I] : xoopsCodeDecode $allowimage parameter
//
$text = XCube_EventUtils::quickApplyFilter('Legacy.TextSanitizer.XoopsCodePostFilter', $text,
array('allowimage'=>$allowimage));
?>
という行が続いており、この部分で'Legacy.TextSanitizer.XoopsCodePost'というイベントが発行されます。こちらのEventArgsには、preg_replace()関数にて変換された文字列がそのまま引き渡されますので、呼び出されたデリゲート側では、その文字列を直接編集するロジックを組み込む事が出来ます。
たとえば、XoopsCodePreのデリゲート側で変換ルール配列をすべてクリアして、XoopsCodePostのデリゲート側では全く新しい変換ルールに則ったロジックを組み込む事というような使い方も出来ます。
さて、xoopsCodeDecode()内で発行されるイベントと呼ばれるデリゲートの役割をご紹介したので、ここで簡単な事例を紹介しましょう。
XOOPSの[url][siteurl]タグで生成される<a>タグではtarget属性で_blankが指定されておりサイト内へのリンクでも新しいブラウザウィンドウが開いてしまいます。
これをせめて[siteurl]タグでは、target属性を出力しないようにしてみましょう。
以下のソースを、preload/LinkTarget.class.php というファイル名で作成して下さい。
<?php
class LinkTarget extends XCube_ActionFilter
{
function preBlockFilter() {
$this->mController->mRoot->mEventManager->add("Legacy.TextSanitizer.XoopsCodePre",
new XCube_InstanceDelegate($this, "BBCodePre"));
}
function BBCodePre(&$controller,&$eventArgs) {
foreach(array_keys($eventArgs['patterns']) as $key) {
if (preg_match('/[siteurl=/',$eventArgs['patterns'][$key])) {
$eventArgs['replacements'][$key] = str_replace('target="_blank"', '', $eventArgs['replacements'][$key]);
}
}
}
}
?>
上記のソースで実行している事は、
- ファイル名と同じLinkTargetというクラスを作成(XCube_ActionFilterクラスを継承する事をお奨めしますが必須ではありません。)
- preBlockFilter()メソッドを作成して、その中でLegacy.TextSanitizer.XoopsCodePreイベントに対応したデリゲートを準備します。
(この例の場合には、デリゲートとしてBBCodePreメソッドを定義している事になります) - BBCodePre()メソッド内で、][siteurl]タグのHTMLへの置換ルールから、target属性を削除するロジックを記述します。
これだけの、コードで見事target属性が消えてくれます。
ちなみに、このサイトでは、同様に[url]のtarget属性も一旦削除して、別途当サイト外へのリンクに対して、targetの指定及び外部リンクを表現するアイコンを表示するスタイルシート用のclass属性を付加するoutput buffer filtering を行っています。
コメント
コメントの投稿
ごめんなさい、現在コメントを付けることは出来ません
面白い記事ありがとうございます。
PreがRaiseEventで、参照を書き換えたら、$resultに収まる一方で、
PostはApplyFilterで、return で返さなければいけない(ですよね?)、というあたり、判りづらい気がしないでもないですね。
> siteurl タグの img版である siteurlタグ
siteimgのtypoですね
> 中にはflashタグなんて物もあったりします
これ、そのまんま、Script Insertion 脆弱性なのですが (^^;;;
Comment by GIJOE — 2006年6月16日(金曜日) @ 10時24分22秒
> PreがRaiseEventで、参照を書き換えたら、$resultに収まる一方で、
> PostはApplyFilterで、return で返さなければいけない(ですよね?)、というあたり、判りづらい気がしないでもないですね。
すみません、XCube_EventUtils::quickApplyFilter というのは、RaiseEvent処理ををFilter的な使い方をしやすいように用意したユーティリティ関数なんです。(説明不足でしたね)
基本的にはraiseEventと同様の呼び出し手順でデリゲートで要したメソッドが呼ばれます。
で呼ばれた側では、常に$eventArgsを窓口として、データのやりとりをする事が前提です。
コメント内に記述してあるように、呼ばれた側では、文字列は$eventArgs[’string’]の形の配列要素として渡されるので、この$eventArgs[’string’]を加工する処理を実行する事が求められています。
Preで使用しているXCube_EventUtils::quickRaiseEvent()も、EventManagerに対するraiseEvent()発行を、少々すっきりとした形で書くために用意したユーティリティ関数になっています。
>> 中にはflashタグなんて物もあったりします
> これ、そのまんま、Script Insertion 脆弱性なのですが (^^;;;
「なんて物」っていう表現で暗示させて頂いておりましたが(^^;;;
管理者権限ユーザだけ使用出来るとかすれば、便利かもしれませんが・・・
Comment by nobunobu — 2006年6月16日(金曜日) @ 19時24分51秒
>以下のソースを、preload/LinkTarget.class.php というファイル名で作成して下さい。
失礼致します。ちょうど、targetにblank属性がついてしまうのに
困っていたところ、ここにたどり着きました。
が、すみません、うまくいきません。。
これは、classディレクトリの中にpreloadディレクトリを作る、
という意味でしょうか。
もしくは、FTPソフトでアップロードしたのですが、
そのあと「再構築?」のような処理が必要なのでしょうか。
Comment by カイジ — 2006年11月21日(火曜日) @ 22時45分40秒
>が、すみません、うまくいきません。。
XOOPS Cube2.1 BetaになってDelegateの仕様が変わりました。
コメントでソースを示す事ができないので、別途に[訂正記事]に変更後のソースを置きました。
で、このLinkTarget.class.phpを、XOOPS_ROOT_PATH直下にpreloadというフォルダを作成しておいて頂くだけでOKのはずです。
あくまでも、XOOPS Cube 2.1 Beta以降専用です。
Comment by nobunobu — 2006年11月21日(火曜日) @ 23時43分05秒
nobunobuさま、
丁寧なご説明、ありがとうございました。
XOOPS Cube 2.1 Beta以前の場合で、
この問題に対応する方法がもしわかられるようでしたら、
恐縮ですがご指示いただければ、ありがたいかぎりです。
Comment by カイジ — 2006年11月22日(水曜日) @ 02時50分06秒
> XOOPS Cube 2.1 Beta以前の場合
これが、XOOPS 2.0.xの事を言われているのであれば、
http://xoopscube.jp 内の 旧フォーラムをtargetで検索されれば、お望みに近い答が見つかると思いますよ。
Comment by nobunobu — 2006年11月22日(水曜日) @ 14時48分38秒
module.textsanitizer.phpをいじることで、
うまくいきました!どうもありがとうございました!!
Comment by カイジ — 2006年11月23日(木曜日) @ 00時10分48秒
XOOPS Cube Legacy 2.1.2では、
また変更があったのでしょうか?
_blankの新規で開いてしまうようです。
Comment by yn — 2007年12月15日(土曜日) @ 17時43分06秒