* pukiwikiと連携するアプリケーションの書き方 [#bd337ce6]

- ページ: [[BugTrack2]]
- 投稿者: [[pai]]
- 優先順位: 低
- 状態: 提案
- カテゴリー: その他
- 投稿日: 2007-09-07 (金) 19:00:42
- バージョン: 

** メッセージ [#n9739616]
wikiのページから外部スクリプトにリンクを張り、外部スクリプトではRefererを参照してwikiのページを読み込むことで、pukiwiki本体の改造なしに機能を拡張することができます。
[[BugTrack2]]に書いてよいものかわからなかったので、問題があれば適宜対処をお願いします。
--------

#contents

** 用途 [#h0aac9fb]

pukiwikiはGPLライセンスですが、会社内で使用する場合などで、異なるライセンスのシステムと連携したい場合があります。また、会社内で使用する場合、機能拡張をしたくてもpukiwiki本体に手を入れることが難しい場合があります。本項目の方法を使うと、pukiwikiと連携した拡張機能を、pukiwikiの改造なしにおこなうことができます。また、pukiwikiが稼動しているサーバとは別のサーバで拡張をおこなうこともできます。

** 対象者 [#qd59a0ea]

*** 一般ユーザー [#f3b4f838]

- 各種の図版の埋め込みを、一般ユーザー自身がおこなうことができます。
// ↑自分で読んで意味がわかりませんm(. .)m。
- 誰かが用意した自動処理を呼び出すことができます。
- 自分用のテスト用ページを作ってリンクをコピーしても、正しく動作します(ページ名をクエリに埋め込む方法では、コピー前のページに作用してしまいます)。
- 自分用のテスト用ページを作って、そこに自動処理のリンクをコピーしても、正しく動作します(ページ名をクエリに埋め込む方法では、コピー前のページに作用してしまいます)。

*** 開発者 [#o539b415]

開発者向けに、以下の機能を備えたシステムとしてpukiwikiを利用してもらうことを想定しています。

- 共有ファイルシステム
- 更新履歴の管理
- 排他処理・競合時のマージ処理
- ハイパーリンクを含むマークアップ言語

開発ドキュメントの共有は、ネットワークファイルシステム上でおこなうことが多いと思いますが、編集の競合を防止するための工夫(ロック機構)が必要です。また、誰かがファイルを壊してしまった場合のためのバックアップも、独自におこなう必要があります。wikiであれば、こういった問題はありません。

しかし開発者が共有ファイルを扱う場合、自動処理をおこなう手段が提供されていることが要求されます(たとえば#tracker_listを印刷用に展開したい、など)。ここで紹介するような仕組みであれば、どのページでも任意のスクリプトに渡して処理することができます。

** ライセンス [#w97bec5b]

このページ内のPHPスクリプト部分は、改造したり他のソフトウェア等に組み込んだりした場合でも、ソース提供の義務はないものとします。たぶん[[LGPL]]になるのだと思いますが、確認中です。どなたか詳しい人がいれば、コメントいただけると幸いです。

** 原理 [#q31974a8]

pukiwikiのページにリンクを書き、そのリンクをクリックすると、リンク先にはpukiwikiのページのURLがRefererとして渡ります。リンク先のページでは、受け取ったRefererを見ることでリンク元のpukiwikiのページを読み込むことができます。読み込んだページを加工して表示することで、pukiwikiの機能拡張をおこなうことができます。

この考え方は、TRONの「本文引数」にヒントを得たものです。本文引数とは、文章中にアプリケーションへのリンクが埋め込まれている場合に、アプリケーションに元の文章が引数として渡る仕組みです。
> TRONプロジェクト'88-'89 (パーソナルメディア) p.101

*** 共通ソース:env.php [#i939b04e]

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

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

*** 方法1 [#i48ef0d7]

 <?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 [#be893d61]

 <?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ソースの取得ですので、プラグイン等は展開されません。

** 応用 [#s7de2523]

以下のような応用が考えられます。pukiwikiのpluginにするには特殊すぎるものを扱うことができます。

- wikiに書かれたテストスクリプトを、リンクのクリックで実行
-- デモスクリプトやゲームシナリオをwikiで共同作成
-- 別バージョンを個人ページに作ることもできる
- wikiに書かれたHTMLソースを、プレビュー・公開ページに反映
- wikiに書かれたビルドスクリプトに従ってビルド
- 指定したページから日報を自動生成など
- テーブルからグラフ画像やスケジュールチャートを生成など
- サポート回答メールをwikiで共同作成し、wiki上のリンクをクリックして送信

** サンプル [#s93519aa]

*** textdownload.php [#i5167c35]

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 [#kcbd67ec]

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>

** コメント [#i15a8033]
- 対象者と応用の項を追記しました。  -- [[pai]] &new{2007-09-12 (水) 02:25:17};

#comment

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Site admin: PukiWiki Development Team

PukiWiki 1.5.4+ © 2001-2022 PukiWiki Development Team. Powered by PHP 5.6.40-0+deb8u12. HTML convert time: 0.075 sec.

OSDN