wikiのページから外部スクリプトにリンクを張り、外部スクリプトではRefererを参照してwikiのページを読み込むことで、pukiwiki本体の改造なしに機能を拡張することができます。 BugTrack2に書いてよいものかわからなかったので、問題があれば適宜対処をお願いします。
pukiwikiはGPLライセンスですが、会社内で使用する場合などで、異なるライセンスのシステムと連携したい場合があります。また、会社内で使用する場合、機能拡張をしたくてもpukiwiki本体に手を入れることが難しい場合があります。本項目の方法を使うと、pukiwikiと連携した拡張機能を、pukiwikiの改造なしにおこなうことができます。また、pukiwikiが稼動しているサーバとは別のサーバで拡張をおこなうこともできます。
開発者向けに、以下の機能を備えたシステムとしてpukiwikiを利用してもらうことを想定しています。
開発ドキュメントの共有は、ネットワークファイルシステム上でおこなうことが多いと思いますが、編集の競合を防止するための工夫(ロック機構)が必要です。また、誰かがファイルを壊してしまった場合のためのバックアップも、独自におこなう必要があります。wikiであれば、こういった問題はありません。
しかし開発者が共有ファイルを扱う場合、自動処理をおこなう手段が提供されていることが要求されます(たとえば#tracker_listを印刷用に展開したい、など)。ここで紹介するような仕組みであれば、どのページでも任意のスクリプトに渡して処理することができます。
このページ内のPHPスクリプト部分は、改造したり他のソフトウェア等に組み込んだりした場合でも、ソース提供の義務はないものとします。たぶんLGPLになるのだと思いますが、確認中です。どなたか詳しい人がいれば、コメントいただけると幸いです。
pukiwikiのページにリンクを書き、そのリンクをクリックすると、リンク先にはpukiwikiのページのURLがRefererとして渡ります。リンク先のページでは、受け取ったRefererを見ることでリンク元のpukiwikiのページを読み込むことができます。読み込んだページを加工して表示することで、pukiwikiの機能拡張をおこなうことができます。
この考え方は、TRONの「本文引数」にヒントを得たものです。本文引数とは、文章中にアプリケーションへのリンクが埋め込まれている場合に、アプリケーションに元の文章が引数として渡る仕組みです。
TRONプロジェクト'88-'89 (パーソナルメディア) p.101
このページで紹介しているPHPスクリプトは、pukiwikiとは独立したものであり、pukiwikiを更新するわけではありません。別のサーバ上においても構いません。
<?php $scheme = "http://"; $wikipath = "localhost/wiki/index.php"; $toolspath = "localhost/wikitools/"; ?>
<?php require("env.php"); if (($pagename = @$_GET["page"]) !== null) ; else if (($referer = @$_SERVER["HTTP_REFERER"]) === null) die("<H1>referer not set</H1>"); else if (!ereg('index.php\?([-_.a-zA-Z0-9%]+)$', $referer, $array)) die("<H1>referer not wiki</H1>"); else $pagename = urldecode($array[1]); $user = urlencode(@$_SERVER["PHP_AUTH_USER"].""); $pass = urlencode(@$_SERVER["PHP_AUTH_PW"].""); $contents = file_get_contents("{$scheme}{$user}:{$pass}@{$wikipath}?".urlencode($pagename)) or die("file_get_content failed.");
これで、$contentsにはアクセス元のwikiのページ全体のHTMLが取得できます(wikiソースではありません)。プラグインも展開されます。
なおここでは、自分の管理していないページからリンクされるような攻撃(サーバが相手ページを読み込みにいくため、DDOSなどに利用できる)を防ぐために、refererからはページ名だけを受け取り、$wikipathのURLに固定的にアクセスしています。公開wikiの場合には、Refererが自分のwikiを指していることを確認するのがよいと思います。また、複数のwiki(たとえば http://pukiwiki.sourceforge.jp/ と http://pukiwiki.sourceforge.jp/dev/ )から1つのスクリプトにアクセスされる場合にも、このような対策が必要です。
<?php require("env.php"); if (($pagename = @$_GET["page"]) !== null) ; else if (($referer = @$_SERVER["HTTP_REFERER"]) === null) die("<H1>referer not set</H1>"); else if (!ereg('index.php\?([-_.a-zA-Z0-9%]+)$', $referer, $array)) die("<H1>referer not wiki</H1>"); else $pagename = urldecode($array[1]); function getwikipage($pagename = "FrontPage") { global $scheme, $wikipath; $user = urlencode(@$_SERVER["PHP_AUTH_USER"].""); $pass = urlencode(@$_SERVER["PHP_AUTH_PW"].""); $contents = file_get_contents("{$scheme}{$user}:{$pass}@{$wikipath}?cmd=edit&page=".urlencode($pagename)) or die("file_get_content failed."); if (!ereg("<textarea [^>]*>([^<]*)<", $contents, $array)) die("textarea not found : ".htmlspecialchars($pagename)); return html_entity_decode($array[1]); } $contents = getwikipage($pagename);
これは?cmd=editを付加して、wikiソースを取得しています。またgetwikipage()が関数になっているので、リンクをたどってページを取得することもできます。ただし、この方法はpukiwikiのバージョンに依存します。またwikiソースの取得ですので、プラグイン等は展開されません。
以下のような応用が考えられます。pukiwikiのpluginにするには特殊すぎるものを扱うことができます。
wikiからリンクを張ると、半角スペースで始まる行(<PRE>タグになります)だけを抽出して、テキスト形式で出力します。文字コードを指定してのダウンロードもできます。readmeのようなテキストファイルを共同執筆するような場合に、コメントを混ぜて書くことができます。
<?php require("env.php"); if (($pagename = @$_GET["page"]) !== null) ; else if (($referer = @$_SERVER["HTTP_REFERER"]) === null) die("<H1>referer not set</H1>"); else if (!ereg('index.php\?([-_.a-zA-Z0-9%]+)$', $referer, $array)) die("<H1>referer not wiki</H1>"); else $pagename = urldecode($array[1]); $user = urlencode(@$_SERVER["PHP_AUTH_USER"].""); $pass = urlencode(@$_SERVER["PHP_AUTH_PW"].""); $contents = file_get_contents("{$scheme}{$user}:{$pass}@{$wikipath}?".urlencode($pagename)) or die("file_get_content failed."); if (strpos($contents, "<pre>") === FALSE) die("<H1><pre> not found.</H1>"); $filename = "download.txt"; if (eregi('<h2 [^>]+>([-a-zA-Z0-9_.]+\.txt)', $contents, $array)) $filename = $array[1]; $crlf = "\n"; switch (@$_GET["crlf"]) { case 1: $crlf = "\r"; break; case 2: $crlf = "\r\n"; break; } $encode = "EUC-JP"; switch (@$_GET["encode"]) { case 1: $encode = "SJIS"; break; case 2: $crlf = "ISO-2022-JP"; break; case 3: $crlf = "UTF-8"; break; } $text = ""; foreach (explode("<pre>", $contents) as $chunk) { $array = explode("</pre>", $chunk, 2); if (count($array) != 2) continue; foreach (split("\r|\n|\r\n", $array[0]) as $line) { $text .= mb_convert_encoding(html_entity_decode($line), $encode, "EUC-JP").$crlf; } } if (@$_GET["download"] !== null) { header("Content-Type: application/x-octet-stream"); header('Content-Disposition: attached; filename="'.$filename.'"'); header('Content-Length: '.strlen($text)); print $text; die(); } ?> <HTML><HEAD><TITLE><?=htmlspecialchars($filename) ?></TITLE></HEAD><BODY> <H1><?=htmlspecialchars($filename) ?></H1> <FORM> <INPUT type=hidden name=page value="<?=htmlspecialchars($pagename, ENT_QUOTES) ?>"> <INPUT type=hidden name=download value=1> <SELECT size=0 name=encode> <OPTION value=0>EUC-JP</OPTION> <OPTION value=1 selected>SHIFT-JIS</OPTION> <OPTION value=2>ISO-2022-JP</OPTION> <OPTION value=3>UTF-8</OPTION> </SELECT> <SELECT size=0 name=crlf> <OPTION value=0>LF</OPTION> <OPTION value=1>CR</OPTION> <OPTION value=2 selected>CR+LF</OPTION> </SELECT> <INPUT type=submit value=download> </FORM> <HR> <PRE><?=htmlspecialchars($text) ?></PRE> <HR> </BODY></HTML>
textdownload.phpとは逆に、手元にあるテキストファイルをアップロードすると、指定したページに半角スペース付きでテキストを追加した編集画面になります。同時に、textdownload.phpへのリンクも張られるので、そのままダウンロードできます。pukiwikiからもらった編集画面に<BASE>タグを追加し、テキストを追加しています。
nkfが使用できる場合には、#ではじまるコメント部分を有効にし、その直前の行をコメントアウトしてください。
<?php require("env.php"); if (is_uploaded_file($fn = @$_FILES["file"]["tmp_name"])) { $orgfn = htmlspecialchars(@$_FILES["file"]["name"]); $pagename = @$_POST["pagename"].""; if (get_magic_quotes_gpc()) $pagename = stripslashes($pagename); $user = urlencode(@$_SERVER["PHP_AUTH_USER"].""); $pass = urlencode(@$_SERVER["PHP_AUTH_PW"].""); $contents = file_get_contents("{$scheme}{$user}:{$pass}@{$wikipath}?cmd=edit&page=".urlencode($pagename)) or die("file_get_content failed."); $array1 = explode("<textarea ", $contents, 3); $array2 = explode("<head>", $array1[0], 2); $array3 = explode("</textarea>", $array1[1], 2); print $array2[0].'<head><BASE href="'.$scheme.$wikipath.'" />'.$array2[1]."<textarea ".$array3[0]; print <<<EOO * {$orgfn} ** action : [[textdownload.php>{$scheme}{$toolspath}/textdownload.php]] EOO; $contents = file_get_contents($fn) or die("file_get_contents failed."); # $contents = shell_exec("nkf -e ".escape_shell_arg($fn)); foreach (split("\r|\n|\r\n", $contents) as $line) print " ".htmlspecialchars($line)."\n"; print "</textarea>".$array3[1]."<textarea ".$array1[2]; die(); } $pagename = ""; if (($referer = @$_SERVER["HTTP_REFERER"]) !== null) { if ((ereg('index.php\?([-_.a-zA-Z0-9%]+)$', $referer, $array))) $pagename = htmlspecialchars(urldecode($array[1])); } ?> <HTML><HEAD><TITLE>textupload.php</TITLE></HEAD><BODY> <H1>textupload.php</H1> <FORM enctype="multipart/form-data" method=POST> <UL> <LI>pagename: <INPUT type=text name=pagename value="<?=$pagename ?>"> <LI>textfile: <INPUT type=file name=file> <LI><INPUT type=submit> </UL> </FORM> <HR> </BODY></HTML>