pukiwikiと連携するアプリケーションの書き方

メッセージ

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

共通ソース:env.php

このページで紹介しているPHPスクリプトは、pukiwikiとは独立したものであり、pukiwikiを更新するわけではありません。別のサーバ上においても構いません。

<?php
$scheme = "http://";
$wikipath = "localhost/wiki/index.php";
$toolspath = "localhost/wikitools/";
?>

方法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]);

$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つのスクリプトにアクセスされる場合にも、このような対策が必要です。

方法2

<?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にするには特殊すぎるものを扱うことができます。

サンプル

textdownload.php

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>&lt;pre&gt; 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>

textupload.php

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>

コメント



トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-12-07 (金) 00:49:28
Site admin: PukiWiki Development Team

PukiWiki 1.5.4+ © 2001-2022 PukiWiki Development Team. Powered by PHP 8.2.12. HTML convert time: 0.229 sec.

SourceForge