<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>情報と音楽 &#187; PHP</title>
	<atom:link href="http://penguinlab.jp/blog/post/tag/php/feed" rel="self" type="application/rss+xml" />
	<link>http://penguinlab.jp/blog</link>
	<description>labochoの日々のアウトプット</description>
	<lastBuildDate>Mon, 16 Apr 2012 04:24:05 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Photologをflickrからtumblrへ移行しました</title>
		<link>http://penguinlab.jp/blog/post/1496</link>
		<comments>http://penguinlab.jp/blog/post/1496#comments</comments>
		<pubDate>Thu, 24 Sep 2009 13:11:27 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[flickr]]></category>
		<category><![CDATA[Photolog]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WebAPI]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=1496</guid>
		<description><![CDATA[以前にも書いたように、携帯電話で撮ったそのへんのものをコメントつきでflickrにアップしてたんですが、flickrの無料アカウントでの制限(200枚)に達してしまったのを機に、tumblrへ移行しました。 labocho&#8217;s photolog もうちょっとがんばって撮った写真(携帯電話以外で撮った写真)はflickrに置いたまま。これが200枚に達したらProアカウントにするか考えよう。 Flickr: labocho&#8217;s Photostream で、移行ですが、手動で1つづつ移すのは面倒なので、両サービスのAPIを利用して、flickrの特定セットの写真をtumblrに投稿するスクリプトを作成、ローカルサーバで走らせました。 自分で、しかも1回使うだけなので、使い勝手はひどいですが、参考にしたい人もいるかと思いますので、掲載しておきます。 &#60;?php //flickrのSetのid。SetのURLに含まれています $flickr_photoset_id = '○○○○○○○○○○'; //flickrのAPI Key。 http://www.flickr.com/services/api/ で取得してください。 $flickr_api_key = '○○○○○○○○○○'; //tumblrに登録したメールアドレス $tumblr_email = '○○○○○○@○○○○○○○○○○'; //tumblrのパスワード $tumblr_password = '○○○○○○○○○○'; //投稿先のtumblr $tumblr_group = '○○○○○○.tumblr.com'; //Setの情報を取得する $s = simplexml_load_file('http://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos&#038;api_key='.&#038;flickr_api_key.'&#038;photoset_id='.$flickr_photoset_id.'&#038;extras=url_o,date_upload,date_taken'); foreach($s->photoset->photo as $p_no => $p_val){ //写真1枚の情報を取得 $p_info = simplexml_load_file('http://api.flickr.com/services/rest/?method=flickr.photos.getInfo&#038;api_key='.&#038;flickr_api_key.'&#038;photo_id='.$p_val->attributes()->id); //tumblrへのリクエスト作成 $request_multipart = array( 'email' => $tumblr_email, 'password' => [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://penguinlab.jp/blog/post/174" title="フォトログ。 - 情報と音楽">以前にも書いた</a>ように、携帯電話で撮ったそのへんのものをコメントつきでflickrにアップしてたんですが、flickrの無料アカウントでの制限(200枚)に達してしまったのを機に、tumblrへ移行しました。</p>
<ul><li><a href="http://photolog.penguinlab.jp/" title="labocho's photolog" onclick="pageTracker._trackPageview('/outgoing/photolog.penguinlab.jp/?referer=');">labocho&#8217;s photolog</a></li></ul>
<p>もうちょっとがんばって撮った写真(携帯電話以外で撮った写真)はflickrに置いたまま。これが200枚に達したらProアカウントにするか考えよう。</p>
<ul><li><a href="http://www.flickr.com/photos/10974997@N08/" title="Flickr: labocho's Photostream" onclick="pageTracker._trackPageview('/outgoing/www.flickr.com/photos/10974997_N08/?referer=');">Flickr: labocho&#8217;s Photostream</a></li></ul>
<p>で、移行ですが、手動で1つづつ移すのは面倒なので、両サービスのAPIを利用して、flickrの特定セットの写真をtumblrに投稿するスクリプトを作成、ローカルサーバで走らせました。</p>
<p>自分で、しかも1回使うだけなので、使い勝手はひどいですが、参考にしたい人もいるかと思いますので、掲載しておきます。</p>
<code class="prettyprint lang-php">&lt;?php
//flickrのSetのid。SetのURLに含まれています
$flickr_photoset_id = '○○○○○○○○○○';
//flickrのAPI Key。 http://www.flickr.com/services/api/ で取得してください。
$flickr_api_key = '○○○○○○○○○○';
//tumblrに登録したメールアドレス
$tumblr_email = '○○○○○○@○○○○○○○○○○';
//tumblrのパスワード
$tumblr_password = '○○○○○○○○○○';
//投稿先のtumblr
$tumblr_group = '○○○○○○.tumblr.com';

//Setの情報を取得する
$s = simplexml_load_file('http://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos&#038;api_key='.&#038;flickr_api_key.'&#038;photoset_id='.$flickr_photoset_id.'&#038;extras=url_o,date_upload,date_taken');

foreach($s->photoset->photo as $p_no => $p_val){
	//写真1枚の情報を取得
	$p_info = simplexml_load_file('http://api.flickr.com/services/rest/?method=flickr.photos.getInfo&#038;api_key='.&#038;flickr_api_key.'&#038;photo_id='.$p_val->attributes()->id);
	//tumblrへのリクエスト作成
	$request_multipart = array(
	        'email'     => $tumblr_email,
	        'password'  => $tumblr_password,
	        'type'      => 'photo',
	        'source'    => substr($p_val->attributes()->url_o,7),
	        'caption'   => $p_info->photo->description,
	        'date' => $p_info->photo->dates->attributes()->taken,
	        'group' => $tumblr_group
	);
	//tumblrへのリクエスト
	$c = curl_init('http://www.tumblr.com/api/write');
	curl_setopt($c, CURLOPT_POST, true);
	curl_setopt($c, CURLOPT_POSTFIELDS, $request_multipart);
	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
	$result = curl_exec($c);
	$status = curl_getinfo($c, CURLINFO_HTTP_CODE);
	if (curl_errno($c)) {
		echo "curl_error($c)\n";
	}
	curl_close($c);
	print_r($result."\n");
}
?&gt;</code>
<p>flickr.photosets.getPhotosだとオリジナル画像のURLが取得できるのに、flickr.photos.getInfoだと取得できないのがよくわからんけど、まあ問題なし。</p>
<p>これをxampp上で実行して、tumblrを確認しだい、手動でflickr側の写真を削除していきました。削除は手でやったほうが安心だもんね。</p>
<p>その後、サブドメインの設定をドメイン管理会社の管理ページとtumblrのカスタマイズページで行い、めでたく完了。</p>
<p>いやしかし、API提供されてるといろいろ便利です。Webサービスを選ぶ際にはAPIやインポート/エクスポート機能をちゃんと見ておくべきですね。</p>
<h3>参考</h3>
<ul>
<li><a href="http://www.flickr.com/services/api/" title="Flickr Services" onclick="pageTracker._trackPageview('/outgoing/www.flickr.com/services/api/?referer=');">Flickr Services</a><li>
<li><a href="http://www.tumblr.com/docs/api" title="API | Tumblr" onclick="pageTracker._trackPageview('/outgoing/www.tumblr.com/docs/api?referer=');">API | Tumblr</a></li>
<li><a href="http://www.tumblr.com/docs/custom_domains" title="Using a custom domain name | Tumblr" onclick="pageTracker._trackPageview('/outgoing/www.tumblr.com/docs/custom_domains?referer=');">Using a custom domain name | Tumblr</a></li>
<li><a href="http://www.undakovr.com/2009/08/21/sub-domain-for-tumblr/" title="Tumblrにサブドメインを割り当ててみた | www.undakovr.com" onclick="pageTracker._trackPageview('/outgoing/www.undakovr.com/2009/08/21/sub-domain-for-tumblr/?referer=');">Tumblrにサブドメインを割り当ててみた | www.undakovr.com</a></li>
<li><a href="http://www.msng.info/archives/2007/09/tumblr_favicon.php" title="tumblrにfaviconをつける - 頭ん中" onclick="pageTracker._trackPageview('/outgoing/www.msng.info/archives/2007/09/tumblr_favicon.php?referer=');">tumblrにfaviconをつける &#8211; 頭ん中</a></li>
</ul>]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/1496/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WordPressのURLをhackする</title>
		<link>http://penguinlab.jp/blog/post/1164</link>
		<comments>http://penguinlab.jp/blog/post/1164#comments</comments>
		<pubDate>Sun, 05 Jul 2009 07:07:29 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[mod_rewrite]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webデザイン]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=1164</guid>
		<description><![CDATA[hackする、ってほどのことではないんですが、ようやく構造がわかってきたので。メモをかねて。 このブログでは、Wordpressを http://penguinlab.jp/blog にインストールし、記事単一のページのURLを http://penguinlab.jp/blog/post/(一意の記事番号、記事スラッグを利用) にするため、管理ページの「設定」-「パーマリンク設定」で、パーマリンクを post/%postname% とした。 さらに同じ設定ページでカテゴリベースとタグベースを設定した(/blog/category/(カテゴリ名) および /blog/tag/(タグ名) とした)のだが、日付別表示に関してはベースURLの設定がなく、/blog/post/2009/4 のようになってしまう。/blog/postは単一記事専用としたいので、ここは /blog/date/2009/4 のようなかたちにしたいところだ。 いろいろ調べてもいい方法がなさそうだったので、結局プラグインを作ることになった。 WordPressがURLを解決する仕組み まずはWordpressがインストールされているディレクトリにある.htaccessを見てみよう。 # BEGIN WordPress &#60;IfModule mod_rewrite.c&#62; RewriteEngine On RewriteBase /blog/ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /blog/index.php [L] &#60;/IfModule&#62; # END WordPress 細かいところは違うだろうが、だいたいこのかたちになっているはずだ。5,6行目の「RewriteCond %{REQUEST_FILENAME} !-f 」「RewriteCond %{REQUEST_FILENAME} !-d 」は、リクエストされたファイル/ディレクトリがなければ、7行目の書き換えルールを適用するという意味だ。7行目は全部index.phpにアクセスさせるということを意味する。つまり、リクエストされたとこにファイルかディレクトリがればそれを返し、なければWordpressの index.phpに丸投げしている。 で、index.phpでは、リクエストされたURLを解析してindex.phpにパラメータを付けたかたちに書き換える。たとえば /blog/2009/4は/blog/index.php?year=2009&#038;month=4 のように書き換えられ、投稿日が2009年4月の記事が表示される。試してみるとわかるが、この整形後のURLをブラウザのアドレスバー/ロケーションバーに直接入力してもアクセスできる。 URL書き換えルール で、じゃあそのURL書き換えルール(Rewrite Rules)をどうやって設定してるかというと、(wordpressのインストールディレクトリ)/wp-include/rewrite.phpでそのルールを作っているらしい。Wordpressの管理者ページの「設定」-「パーマリンク設定」で「変更を保存」した際などにこのルールを作り直してるものと思われる。 [...]]]></description>
			<content:encoded><![CDATA[<p>hackする、ってほどのことではないんですが、ようやく構造がわかってきたので。メモをかねて。</p>
<p>このブログでは、Wordpressを http://penguinlab.jp/blog にインストールし、記事単一のページのURLを http://penguinlab.jp/blog/post/(一意の記事番号、記事スラッグを利用) にするため、管理ページの「設定」-「パーマリンク設定」で、パーマリンクを post/%postname% とした。</p>
<p>さらに同じ設定ページでカテゴリベースとタグベースを設定した(/blog/category/(カテゴリ名) および /blog/tag/(タグ名) とした)のだが、日付別表示に関してはベースURLの設定がなく、/blog/post/2009/4 のようになってしまう。/blog/postは単一記事専用としたいので、ここは /blog/date/2009/4 のようなかたちにしたいところだ。</p> 
<p>いろいろ調べてもいい方法がなさそうだったので、結局プラグインを作ることになった。</p>
<h3>WordPressがURLを解決する仕組み</h3>
<p>まずはWordpressがインストールされているディレクトリにある.htaccessを見てみよう。</p>
<p><code># BEGIN WordPress
&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
RewriteBase /blog/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
&lt;/IfModule&gt;
# END WordPress
</code></p>
<p>細かいところは違うだろうが、だいたいこのかたちになっているはずだ。5,6行目の「RewriteCond %{REQUEST_FILENAME} !-f 」「RewriteCond %{REQUEST_FILENAME} !-d 」は、リクエストされたファイル/ディレクトリがなければ、7行目の書き換えルールを適用するという意味だ。7行目は全部index.phpにアクセスさせるということを意味する。つまり、リクエストされたとこにファイルかディレクトリがればそれを返し、なければWordpressの
index.phpに丸投げしている。</p>
<p>で、index.phpでは、リクエストされたURLを解析してindex.phpにパラメータを付けたかたちに書き換える。たとえば /blog/2009/4は/blog/index.php?year=2009&#038;month=4 のように書き換えられ、投稿日が2009年4月の記事が表示される。試してみるとわかるが、この整形後のURLをブラウザのアドレスバー/ロケーションバーに直接入力してもアクセスできる。</p>
<h3>URL書き換えルール</h3>
<p>で、じゃあそのURL書き換えルール(Rewrite Rules)をどうやって設定してるかというと、(wordpressのインストールディレクトリ)/wp-include/rewrite.phpでそのルールを作っているらしい。Wordpressの管理者ページの「設定」-「パーマリンク設定」で「変更を保存」した際などにこのルールを作り直してるものと思われる。</p>
<p>で、いま、その整形のルールがどんな風になっているかは <a href="http://www.dagondesign.com/articles/wordpress-internal-rewrite-viewer-plugin/" title="Internal Rewrite Viewer Plugin for WordPress - Dagon Design" onclick="pageTracker._trackPageview('/outgoing/www.dagondesign.com/articles/wordpress-internal-rewrite-viewer-plugin/?referer=');">Internal Rewrite Viewer</a> というプラグインを導入することでみることができる(インストール→有効化→「設定」-「パーマリンク設定」-「変更を保存」→(ブログURL)/rewritesでアクセスできる)。以下は今回の変更を加える前のURL書き換えルールの抜粋である。</p>
<p><code class="prettyprint lang-php">Array
(
    [post/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$1&#038;monthnum=$2&#038;day=$3
    [post/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$1&#038;monthnum=$2
    [post/([0-9]{4})/?$] => index.php?year=$1
)
</code></p>
<p>URL書き換えルールは連想配列で、キーが表向きのURL、値がindex.phpにパラメータが付いたかたちで定義されているのがわかる。
さてこれをどのように弄ればいいだろうか。</p>
<h3>フィルターでURL書き換えルールを書き換える</h3>
<p>WordPressはプラグインを使うために、フィルターという仕組みを用意している。</p>
<p>WordPressのソースをみると、プラグインで処理を挟みたいような部分で、apply_filtersという関数が呼ばれている。これは、プラグインがadd_filterという関数で登録した関数を呼び出してくれるものだ。</p>
<p>で、URL書き換えルールを作る部分にもフィルターが設定できる。今回は日付別一覧を弄りたいので、date_rewrite_rulesにフィルターを設定する(<a href="http://wpdocs.sourceforge.jp/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3_API/%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC%E3%83%95%E3%83%83%E3%82%AF%E4%B8%80%E8%A6%A7#URL.E3.83.AA.E3.83.A9.E3.82.A4.E3.83.88.E3.83.AB.E3.83.BC.E3.83.AB" title="プラグイン API/フィルターフック一覧 - WordPress Codex 日本語版" onclick="pageTracker._trackPageview('/outgoing/wpdocs.sourceforge.jp/_E3_83_97_E3_83_A9_E3_82_B0_E3_82_A4_E3_83_B3_API/_E3_83_95_E3_82_A3_E3_83_AB_E3_82_BF_E3_83_BC_E3_83_95_E3_83_83_E3_82_AF_E4_B8_80_E8_A6_A7_URL.E3.83.AA.E3.83.A9.E3.82.A4.E3.83.88.E3.83.AB.E3.83.BC.E3.83.AB?referer=');">「プラグイン API/フィルターフック一覧 &#8211; WordPress Codex 日本語版」</a>にURL書き換えのフィルターフック一覧がある)。</p>
<p>フィルター追加のひな形はこんな感じである。</p>
<p><code class="prettyprint lang-php">function my_filter($arg){
    $result = "";
    return $result;
}
add_filter('date_rewrite_rules', 'my_filter');
</code></p>
<p>呼び出される関数にどんな引数が渡されるかは、フィルターフックによって変わる。どんなものが渡されるかについてはあんまり情報がないので、場合によってはWordpressのソースからapply_filter呼び出してるとこを探すことになる。</p>
<p>date_rewrite_rulesの場合、上でInternal Rewrite Rulesプラグインで見たような、連想配列のかたちで渡される。</p>
<p>あとはフィルター内で好きなように書き換えて返してやればよい。今回は最初のpost/部分をdate/に書き換える。</p>
<p><code class="prettyprint lang-php">function add_date_base($rewrite_rules){
    $r = array();
    while(list ($key, $val) = each($rewrite_rules)) {
        $r[str_replace('post/', 'date/', $key)] = $val;
    }
    return $r;
}
add_filter('date_rewrite_rules', 'add_date_base');
</code></p>
<h3>テンプレートで書き出されるリンク先の変更</h3>
<p>これで、思い通りのURLでアクセスする方法はわかったが、まだ1つ問題がある。テンプレートで書き出されるリンクが変更前のままなのである。これもまたフィルターを設定することで解決できる。</p>
<p><a href="http://wpdocs.sourceforge.jp/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3_API/%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC%E3%83%95%E3%83%83%E3%82%AF%E4%B8%80%E8%A6%A7#.E3.83.AA.E3.83.B3.E3.82.AF.E9.96.A2.E9.80.A3.E3.81.AE.E3.83.95.E3.82.A3.E3.83.AB.E3.82.BF.E3.83.BC" title="プラグイン API/フィルターフック一覧 - WordPress Codex 日本語版" onclick="pageTracker._trackPageview('/outgoing/wpdocs.sourceforge.jp/_E3_83_97_E3_83_A9_E3_82_B0_E3_82_A4_E3_83_B3_API/_E3_83_95_E3_82_A3_E3_83_AB_E3_82_BF_E3_83_BC_E3_83_95_E3_83_83_E3_82_AF_E4_B8_80_E8_A6_A7_.E3.83.AA.E3.83.B3.E3.82.AF.E9.96.A2.E9.80.A3.E3.81.AE.E3.83.95.E3.82.A3.E3.83.AB.E3.82.BF.E3.83.BC?referer=');">プラグイン API/フィルターフック一覧 &#8211; WordPress Codex 日本語版</a>にリンク関連のフィルターフック一覧がある。これらのフィルターフックは引数として、URL文字列を渡してくる(http://penguinlab.jp/blog/date/2009 のような、プロトコル名から始まる完全なかたちのURLである)。単なる文字列なので、文字列関数で適当に書き換えて返してやればOKだ。なお、日付別表示のリンクは年別、年月別、年月日別の3種あるので、すべて書き換える。</p>
<p><code class="prettyprint lang-php">function add_date_base_link($string){
    $string = str_replace('post/', 'date/', $string);
    return $string;
}
add_filter('year_link', 'add_date_base_link');
add_filter('month_link', 'add_date_base_link');
add_filter('day_link', 'add_date_base_link');</code></p>
<p>あとはこれをプラグインにするだけだ。</p>
<h3>プラグインの作成、有効化、URL書き換えルールの再作成</h3>
<p>WordPressプラグインの作成方法は他所にいくらでも書かれていると思うので、簡単な説明にとどめておく。</p>
<p>まず、さきほど書いたコードにタイトルなどのメタデータを書き加え、適当な名前で(ローカルに)保存しておく。</p>
<p><code class="prettyprint lang-php">&lt;?php
/*
Plugin Name: Add Date Base
Description: Add date base
Version: 1.0
Author: labocho
*/
function add_date_base($rewrite_rules){
    $r = array();
    while(list ($key, $val) = each($rewrite_rules)) {
        $r[str_replace('post/', 'date/', $key)] = $val;
    }
    return $r;
}
function add_date_base_link($string){
    $string = str_replace('post/', 'date/', $string);
    return $string;
}
add_filter('year_link', 'add_date_base_link');
add_filter('month_link', 'add_date_base_link');
add_filter('day_link', 'add_date_base_link');
add_filter('date_rewrite_rules', 'add_date_base');
?&gt;</code></p>
<p>さらに適当な名前のディレクトリを作成し、この中にさきほど保存したファイルを移動しておく。</p>
<p>次に、これを (WordPressのインストールディレクトリ)/wp-content/plugins の下にディレクトリごとコピーする(あるいはディレクトリごとzipにして、管理ページからアップロードしてもよい)。</p>
<p>あとはWordpressの管理ページを開き「プラグイン」-「インストール済み」にさきほど作成したプラグインが表示されているので、有効化する。</p>
<p>無事、有効化できたら、URL書き換えルールを再作成するために、管理者ページから、「設定」-「パーマリンク設定」-「変更を保存」をクリックする。うまくいっていればこれで作業は完了である。思い通りのURLでアクセス出来るか、確認する。うまくいかない場合は、いや、うまくいった場合でもInternal Rewrite RulesでどのようにURL書き換えルールが変化したか確認しよう。</p>
<p>今回の変更では、上に挙げた部分が以下のように変わっていることを確認した。</p>
<p><code class="prettyprint lang-php">Array
(
    [<strong>date</strong>/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$1&#038;monthnum=$2&#038;day=$3
    [<strong>date</strong>/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$1&#038;monthnum=$2
    [<strong>date</strong>/([0-9]{4})/?$] => index.php?year=$1
)</code></p>
<h3>あとがき</h3>
<p>このような方法で、WordpressのURLをかなり柔軟にいじれる。本来ならば、変更前のURLへのアクセスを301リダイレクトするとかも考えるべきだろうが、まあ、許してください。ほんとはこれをもっと汎用的なプラグインにしてwordpress.orgから配信できるといいんだけど、いまのところやる気が足りません。要望があれば考えます。</p>]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/1164/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHPでreCAPTCHAによる画像認証を実装するまで</title>
		<link>http://penguinlab.jp/blog/post/205</link>
		<comments>http://penguinlab.jp/blog/post/205#comments</comments>
		<pubDate>Tue, 12 May 2009 19:05:57 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[reCAPTCHA]]></category>
		<category><![CDATA[スパム対策]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=395</guid>
		<description><![CDATA[GWからCakePHPでつくりはじめたWebサービスで、画像認証を実装したいと思って、以前から気になっていたreCAPTCHAを導入した。びっくりするくらい簡単だったけど、意外とreCAPTCHA実装の記事が少ないので、丁寧に解説します。 reCAPTCHAってなんだって人はこちらをどうぞ。 秋元@サイボウズラボ・プログラマー・ブログ: reCAPTCHA &#8211; キャプチャを利用した人力高性能OCR なお、CakePHPは1.2を使ってます。 1. まずは登録 reCAPTCHAを使うにはWebサイトでアカウントを作る必要があります。 まずはhttp://recaptcha.net/へアクセスし、「USE reCAPTCHA ON YOUR SITE」へ。 「Sign up Now!」へ。 「Sign up」へ。 ユーザー名、パスワード、パスワード再入力、連絡先メールアドレス、そしてもちろんreCAPTCHAによる画像認証を入力して「Sign up Now!」へ。 次に、どのドメインでreCAPTCHAを使うかを聞いてきます。たぶんここで入れたドメイン以外からの検証リクエストは受け付けないんでしょう(少なくともlocalhostからは受付けてましたが)。 親ドメインを入れとけばサブドメインでも使えるし、一つのアカウントで複数のドメインを登録できるし、大量のドメインから使うんだったら「Enable this key on all domains (global keys)」にチェックを入れとけば、ドメインの制限は受けないようです。 入力したら「Create Key」へ。 これで登録完了です！ 「Public Key」(公開鍵)と「Private Key」(秘密鍵)は実装のさいに使用します。「Public Key」(公開鍵)は別に誰に知られてもまったく問題ないですが、「Private Key」(秘密鍵)は絶対に知られないようにしましょう(でないと認証の意味ないです)。 なお、ここで表示される情報はメニューの「MY ACCOUNT」からいつでも確認できます。 2. PHP用のライブラリを用意する メニューの「RESOURCES」に「PHP」の項目があるので、そこへ。 「Download」へ。 Google Codeに飛ぶのでこのzipをダウンロードします。 zipを展開するといくつかファイルが出てきます。 この中の「recaptchalib.php」をCakePHPの/app/vendors下に置いておきます。 3. View側の実装 View側では&#60;form&#62;&#60;/form&#62;の中に入るように、以下のコードを書きます。 App::import('Vendor', 'recaptchalib'); [...]]]></description>
			<content:encoded><![CDATA[<p>GWから<a href="http://cakephp.jp/" title="CakePHP" onclick="pageTracker._trackPageview('/outgoing/cakephp.jp/?referer=');">CakePHP</a>でつくりはじめたWebサービスで、画像認証を実装したいと思って、以前から気になっていた<a href="hhttp://recaptcha.net/" title="reCAPTCHA">reCAPTCHA</a>を導入した。びっくりするくらい簡単だったけど、意外とreCAPTCHA実装の記事が少ないので、丁寧に解説します。</p>
<p>reCAPTCHAってなんだって人はこちらをどうぞ。</p>
<ul>
<li><a href="http://labs.cybozu.co.jp/blog/akky/archives/2007/05/recaptcha-human-group-ocr.html" title="秋元@サイボウズラボ・プログラマー・ブログ: reCAPTCHA - キャプチャを利用した人力高性能OCR" onclick="pageTracker._trackPageview('/outgoing/labs.cybozu.co.jp/blog/akky/archives/2007/05/recaptcha-human-group-ocr.html?referer=');">秋元@サイボウズラボ・プログラマー・ブログ: reCAPTCHA &#8211; キャプチャを利用した人力高性能OCR</a></li>
</ul>
<p>なお、CakePHPは1.2を使ってます。</p>
<h3>1. まずは登録</h3>
<p>reCAPTCHAを使うにはWebサイトでアカウントを作る必要があります。</p>
<p>まずは<a href="http://recaptcha.net/" title="http://recaptcha.net/" onclick="pageTracker._trackPageview('/outgoing/recaptcha.net/?referer=');">http://recaptcha.net/</a>へアクセスし、「<a href="http://recaptcha.net/whyrecaptcha.html" title="USE reCAPTCHA ON YOUR SITE" onclick="pageTracker._trackPageview('/outgoing/recaptcha.net/whyrecaptcha.html?referer=');">USE reCAPTCHA ON YOUR SITE</a>」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha001.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha001s.png" alt="recaptcha001.png" border="0" width="150" height="106" /></a></p>
<p>「Sign up Now!」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha002.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha002s.png" alt="recaptcha002.png" border="0" width="150" height="106" /></a></p>
<p>「Sign up」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha002_2.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha002_2s.png" alt="recaptcha002_2.png" border="0" width="150" height="94" /></a></p>
<p>ユーザー名、パスワード、パスワード再入力、連絡先メールアドレス、そしてもちろんreCAPTCHAによる画像認証を入力して「Sign up Now!」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha003.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha003s.png" alt="recaptcha003.png" border="0" width="133" height="150" /></a></p>
<p>次に、どのドメインでreCAPTCHAを使うかを聞いてきます。たぶんここで入れたドメイン以外からの検証リクエストは受け付けないんでしょう(少なくともlocalhostからは受付けてましたが)。</p>
<p>親ドメインを入れとけばサブドメインでも使えるし、一つのアカウントで複数のドメインを登録できるし、大量のドメインから使うんだったら「Enable this key on all domains (global keys)」にチェックを入れとけば、ドメインの制限は受けないようです。</p>
<p>入力したら「Create Key」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha004.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha004s.png" alt="recaptcha004.png" border="0" width="150" height="106" /></a></p>
<p>これで登録完了です！</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha005.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha005s.png" alt="recaptcha005.png" border="0" width="150" height="106" /></a></p>
<p><strong>「Public Key」(公開鍵)</strong>と<strong>「Private Key」(秘密鍵)</strong>は実装のさいに使用します。「Public Key」(公開鍵)は別に誰に知られてもまったく問題ないですが、「Private Key」(秘密鍵)は絶対に知られないようにしましょう(でないと認証の意味ないです)。</p>
<p>なお、ここで表示される情報はメニューの「<a href="https://admin.recaptcha.net/" title="MY ACCOUNT" onclick="pageTracker._trackPageview('/outgoing/admin.recaptcha.net/?referer=');">MY ACCOUNT</a>」からいつでも確認できます。</p>
<h3>2. PHP用のライブラリを用意する</h3>
<p>メニューの「<a href="http://recaptcha.net/resources.html" title="RESOURCES" onclick="pageTracker._trackPageview('/outgoing/recaptcha.net/resources.html?referer=');">RESOURCES</a>」に「<a href="http://recaptcha.net/plugins/php/" title="PHP" onclick="pageTracker._trackPageview('/outgoing/recaptcha.net/plugins/php/?referer=');">PHP</a>」の項目があるので、そこへ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha006.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha006s.png" alt="recaptcha006.png" border="0" width="150" height="106" /></a></p>
<p>「<a href="http://code.google.com/p/recaptcha/downloads/list?q=label:phplib-Latest" title="Download" onclick="pageTracker._trackPageview('/outgoing/code.google.com/p/recaptcha/downloads/list?q=label_phplib-Latest&amp;referer=');">Download</a>」へ。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha007.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha007s.png" alt="recaptcha007.png" border="0" width="150" height="132" /></a></p>
<p>Google Codeに飛ぶのでこのzipをダウンロードします。</p>
<p><a href="http://penguinlab.jp/blog/wp-content/uploads/recaptcha008.png"><img src="http://penguinlab.jp/blog/wp-content/uploads/recaptcha008s.png" alt="recaptcha008.png" border="0" width="150" height="98" /></a></p>
<p>zipを展開するといくつかファイルが出てきます。</p>
<p>この中の「<strong>recaptchalib.php</strong>」をCakePHPの<strong>/app/vendors</strong>下に置いておきます。</p>
<h3>3. View側の実装</h3>
<p>View側では<strong>&lt;form&gt;&lt;/form&gt;</strong>の中に入るように、以下のコードを書きます。</p>
<p><code>App::import('Vendor', 'recaptchalib');
$publickey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
echo recaptcha_get_html($publickey);
</code></p>
<p>1行目はさきほどコピーしたライブラリの読み込み。</p>
<p>2行目はreCAPTCHAの<strong>Public Key (公開鍵)</strong>を書きます。</p>
<p>3行目はreCAPTCHAのウィジェットを表示します。</p>
<p>たったこれだけでOK!</p>
<h3>4. Controller側の実装</h3>
<p>Controller側では先ほどのViewのformから呼ばれるアクション内に以下のコードを書きます。</p>
<p><code>App::import('Vendor', 'recaptchalib');
$privatekey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$resp = recaptcha_check_answer(
            $privatekey,
            $_SERVER["REMOTE_ADDR"],
            $this->params['form']['recaptcha_challenge_field'],
            $this->params['form']['recaptcha_response_field']);
</code></p>
<p>1行目はやはりライブラリの読み込み。</p>
<p>2行目はreCAPTCHAの<strong>Private Key (秘密鍵)</strong>を書きます。</p>
<p>3行目はユーザーが入力した文字列を検証します。</p>
<p>これで$resp->is_validに検証結果が入るので、あとはお好きなように。</p>
]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/205/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHPで jQuery Star Rating Plugin を使う。</title>
		<link>http://penguinlab.jp/blog/post/195</link>
		<comments>http://penguinlab.jp/blog/post/195#comments</comments>
		<pubDate>Tue, 17 Feb 2009 15:02:48 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=385</guid>
		<description><![CDATA[今回の記事は、CakePHPで jQuery Star Rating Plugin を使いたい人にしか役に立たない情報です。ニッチです。 いまCakePHPでWebアプリケーションを作ってて(趣味です)、スターレイティングを導入しようと思い、いくらか調べて、「jQuery Star Rating Plugin」が良さそうという結論に達した。 で、さっそく実装して試してみてたら、inputのnamｅがサンプルのままの「star」だと問題なく動作するんだけど、CakePHPのdata[モデル名][カラム名]の形式のnameにすると、表示もPOSTもうまくいかない。 調べてみると、jquery.rating.js は、inputのnameをもとにクラスやなんかを作るんだけど、そのさいに[ ]をエスケープしてる。 //99行目 var n = (this.name &#124;&#124; 'unnamed-rating').replace(/\[&#124;\]/, "_"); まずこのエスケープがまずくて、最初の一個めしか置換してくれないので、直す。 //gオプションを追加 var n = (this.name &#124;&#124; 'unnamed-rating').replace(/\[&#124;\]/g, "_"); で、値をPOSTするためにhiddenのinputを作ってる。 //111行目 $.rating.groups[n].valueElem = $('&#60;input type="hidden" name="' + n + '" value=""' + (settings.readOnly ? ' disabled="disabled"' : '') + '/&#62;'); ここはエスケープしちゃうとCakePHPで$this->dataでアクセスできないので、エスケープする前の名前にする。 //さっきのnの宣言部からエスケープ前の名前をもってくる $.rating.groups[n].valueElem [...]]]></description>
			<content:encoded><![CDATA[<p>今回の記事は、CakePHPで <a href="http://www.fyneworks.com/jquery/star-rating/" title="jQuery Star Rating Plugin" onclick="pageTracker._trackPageview('/outgoing/www.fyneworks.com/jquery/star-rating/?referer=');">jQuery Star Rating Plugin</a> を使いたい人にしか役に立たない情報です。ニッチです。</p>
<p>いまCakePHPでWebアプリケーションを作ってて(趣味です)、スターレイティングを導入しようと思い、いくらか調べて、「jQuery Star Rating Plugin」が良さそうという結論に達した。</p>
<p>で、さっそく実装して試してみてたら、inputのnamｅがサンプルのままの「star」だと問題なく動作するんだけど、CakePHPのdata[モデル名][カラム名]の形式のnameにすると、表示もPOSTもうまくいかない。</p>
<p>調べてみると、jquery.rating.js は、inputのnameをもとにクラスやなんかを作るんだけど、そのさいに[ ]をエスケープしてる。</p>
<p><code>//99行目
var n = (this.name || 'unnamed-rating').replace(/\[|\]/, "_");
</code></p>
<p>まずこのエスケープがまずくて、最初の一個めしか置換してくれないので、直す。</p>
<p><code>//gオプションを追加
var n = (this.name || 'unnamed-rating').replace(/\[|\]/g, "_");
</code></p>
<p>で、値をPOSTするためにhiddenのinputを作ってる。</p>
<p><code>//111行目
$.rating.groups[n].valueElem = $('&lt;input type="hidden" name="' + n + '" value=""' + (settings.readOnly ? ' disabled="disabled"' : '') + '/&gt;');
</code></p>
<p>ここはエスケープしちゃうとCakePHPで$this->dataでアクセスできないので、エスケープする前の名前にする。</p>
<p><code>//さっきのnの宣言部からエスケープ前の名前をもってくる
$.rating.groups[n].valueElem = $('&lt;input type="hidden" name="' + (this.name || 'unnamed-rating') + '" value=""' + (settings.readOnly ? ' disabled="disabled"' : '') + '/&gt;');
</code></p>
<p>これでテストしたら、表示・挙動・POSTデータいづれも問題なさそうだった。コードを熟読してないので問題あるかもだけど。</p>
<p>・・・とまあ、ここまで書いといて気づいたんだけど、jQuery Star Rating Plugin ってGPL(と MIT License)なんですね。フリーだと思い込んでたよ。Webアプリケーションのソース公開はセキュリティ上問題ありそうだしなあ。同じようなの作るのめんどくさいなあ・・・。Creative Commons や MIT License なんかに比べるとGPLやLGPLって制限厳しいくせに線引きがあいまいすぎて、どうも好きになれない。</p>
]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/195/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PORTAのPHPサンプル数種。</title>
		<link>http://penguinlab.jp/blog/post/190</link>
		<comments>http://penguinlab.jp/blog/post/190#comments</comments>
		<pubDate>Mon, 29 Dec 2008 15:35:28 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[NDL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PORTA]]></category>
		<category><![CDATA[図書館]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=381</guid>
		<description><![CDATA[先日の記事(PORTAのAPI使ってみた、けど。)のあと、いろいろ試してようやくまともに書誌情報が取得できたので、報告します。 解説すると長くなるので、とりあえずPHPのサンプルを各種。プログラマー各氏にはこのほうが話が早いかも。 FC2ブログの仕様上、拡張子がphpだとアップ出来ないのでtxtにしてます。 PORTAへのリクエストでは、書誌情報のスキーマがdcかdcndl_portaか選べ、そのパッキング方法をxmlとstringから選べる。はじめどれがいいかわかんなかったので、都合この組み合わせ4つのサンプルを用意した。 スキーマ:dc パッキング:xml porta_dc_xml.txt スキーマ:dc パッキング:string porta_dc_string.txt スキーマ:dcndl_porta パッキング:xml porta_dcndl_xml.txt スキーマ:dcndl_porta パッキング:string porta_dcndl_string.txt また、先日の記事の追記でも書いたけど、PHPのSimpleXMLだと名前空間の扱いがめんどくさいので、名前空間付きの要素名を適当に置換するバージョンも2つのスキーマ向けに作った。 スキーマ:dc パッキング:xml 名前空間付きの要素名を置換 porta_dc_replace.txt スキーマ:dcndl_porta パッキング:xml 名前空間付きの要素名を置換 porta_dcndl_replace.txt 結局、最後のやつが一番使いやすい気がします。 あと、おまけ。 最後のやつの出力サンプル porta_dcndl_replace.html にしても、dc:identifierの種類が属性を参照しないとわからんとか、全角英数字を使ってるとか、複数の著者名と役割表示が1つの文字列にまとめられてるとか、Amazonに比べると扱いにくいこと扱いにくいこと。既存の目録規則やDublin Coreにとらわれずに、もっとまともな構造のデータで出力してくれると嬉しいんだけどなあ。]]></description>
			<content:encoded><![CDATA[<p>先日の記事(<a href="http://penguinlab.jp/blog/post/186" title="PORTAのAPI使ってみた、けど。">PORTAのAPI使ってみた、けど。</a>)のあと、いろいろ試してようやくまともに書誌情報が取得できたので、報告します。</p>
<p>解説すると長くなるので、とりあえずPHPのサンプルを各種。プログラマー各氏にはこのほうが話が早いかも。</p>
<p>FC2ブログの仕様上、拡張子がphpだとアップ出来ないのでtxtにしてます。</p>
<p>PORTAへのリクエストでは、書誌情報のスキーマがdcかdcndl_portaか選べ、そのパッキング方法をxmlとstringから選べる。はじめどれがいいかわかんなかったので、都合この組み合わせ4つのサンプルを用意した。</p>
<ul>
<li>スキーマ:dc パッキング:xml<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dc_xml.txt">porta_dc_xml.txt</a></li>
<li>スキーマ:dc パッキング:string<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dc_string.txt">porta_dc_string.txt</a></li>
<li>スキーマ:dcndl_porta パッキング:xml<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dcndl_xml.txt">porta_dcndl_xml.txt</a></li>
<li>スキーマ:dcndl_porta パッキング:string<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dcndl_string.txt">porta_dcndl_string.txt</a></li>
</ul>
<p>また、先日の記事の追記でも書いたけど、PHPのSimpleXMLだと名前空間の扱いがめんどくさいので、名前空間付きの要素名を適当に置換するバージョンも2つのスキーマ向けに作った。</p>
<ul>
<li>スキーマ:dc パッキング:xml 名前空間付きの要素名を置換<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dc_replace.txt">porta_dc_replace.txt</a></li>
<li>スキーマ:dcndl_porta パッキング:xml 名前空間付きの要素名を置換<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dcndl_replace.txt">porta_dcndl_replace.txt</a></li>
</ul>
<p>結局、最後のやつが一番使いやすい気がします。</p>
<p>あと、おまけ。</p>
<ul>
<li>最後のやつの出力サンプル<br/>
<a href="http://penguinlab.jp/blog/wp-content/uploads/porta_dcndl_replace.html">porta_dcndl_replace.html</a></li>
</ul>
<p>にしても、dc:identifierの種類が属性を参照しないとわからんとか、全角英数字を使ってるとか、複数の著者名と役割表示が1つの文字列にまとめられてるとか、Amazonに比べると扱いにくいこと扱いにくいこと。既存の目録規則やDublin Coreにとらわれずに、もっとまともな構造のデータで出力してくれると嬉しいんだけどなあ。</p>
]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/190/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PORTAのAPI使ってみた、けど。</title>
		<link>http://penguinlab.jp/blog/post/186</link>
		<comments>http://penguinlab.jp/blog/post/186#comments</comments>
		<pubDate>Thu, 13 Nov 2008 09:31:28 +0000</pubDate>
		<dc:creator>labocho</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[NDL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PORTA]]></category>
		<category><![CDATA[図書館]]></category>

		<guid isPermaLink="false">http://penguinlab.jp/blog/?p=378</guid>
		<description><![CDATA[最近、更新してないなーと思ったので、いつもなら記事にしない程度（というか段階）の内容。 いま、蔵書管理のためのWebサービス（SocialtunesやLibraryThingみたいなやつだ）を試作してて、書誌情報をどこから取ろうかと考えた結果、amazonを主に、NDL(国立国会図書館)を補助として使うことに決めた。 NDLだけでは最新の図書の書誌情報が得られず、amazonには古い図書の情報が少ないというのが主な理由だ（ほかにもNDLにはシリーズやNDCなんかの情報があったり、書誌情報の信頼性・正確性が高いというのもある）。 もちろんWebcat/Webcat Plusも考えたが、APIとして公開しているものがなく、HTMLからの抽出は、処理の重さや信頼性、あとマナー的にもどうなのということで断念。はやくAPI作ってくださいな・・・。 WorldCatやLCも視野に入れたけど、労力の割にそれほどメリットもないので、今回は無視。NDLがWorldCatに情報提供してれば使ったかもなのになあ。 で、amazonのAPI(Amazon Associates Web Service)は超有名なだけあって、簡単に使えた。 PHPのサンプル探してたら、こちらのページがわかりやすかった。 週末プログラマのメモ帳:PHPでAmazon E-Commerce Service (ECS) 2 ※こちらではクエリ文字列をrawurlencodeでエンコードしているが、うちの環境では必要なかった。 で、今度はNDLがやってる電子リソースポータル「PORTA」（今回はNDLの所蔵データベースしか使わないが）のAPIを使ってみた。※APIについてはPORTAの「このサイトについて」から「外部提供インタフェースについて」で見られる。なんでパーマリンクがないんじゃあ！ なんかSRU(Seach/Retrieval via URL;LCの作った検索プロトコル。URLでリクエストしてXMLでレスポンスがくる)はともかくSRW（SRUのリクエストがSOAP仕様のXMLになったやつ）の情報があまりにも少なくて、なんでSRUを採用しないのかちょっと理解に苦しむところではあるが、まあそれしかないんだから使うしか仕方ない。 で、以下のコードで、一応結果のオブジェクトは返ってくる。 &#60;?php $client = new SoapClient("http://api.porta.ndl.go.jp/servicedp/services/SRWDp?wsdl"); $q = '(title = architecture) AND (dpid ANY "zomoku zassaku okayama")'; $req = array('version' =&#62; '1.1', 'query' =&#62; $q, 'startRecord' =&#62; '1', 'maximumRecords' =&#62; '200', 'recordPacking' =&#62; 'string', [...]]]></description>
			<content:encoded><![CDATA[<p>最近、更新してないなーと思ったので、いつもなら記事にしない程度（というか段階）の内容。</p>
<p>いま、蔵書管理のためのWebサービス（<a href="http://socialtunes.net/" title="Socialtunes" onclick="pageTracker._trackPageview('/outgoing/socialtunes.net/?referer=');">Socialtunes</a>や<a href="http://jp.librarything.com/" title="LibraryThing" onclick="pageTracker._trackPageview('/outgoing/jp.librarything.com/?referer=');">LibraryThing</a>みたいなやつだ）を試作してて、書誌情報をどこから取ろうかと考えた結果、<a href="http://www.amazon.co.jp/" title="amazon" onclick="pageTracker._trackPageview('/outgoing/www.amazon.co.jp/?referer=');">amazon</a>を主に、<a href="http://www.ndl.go.jp/" title="NDL(国立国会図書館)" onclick="pageTracker._trackPageview('/outgoing/www.ndl.go.jp/?referer=');">NDL(国立国会図書館)</a>を補助として使うことに決めた。</p>
<p>NDLだけでは最新の図書の書誌情報が得られず、amazonには古い図書の情報が少ないというのが主な理由だ（ほかにもNDLにはシリーズやNDCなんかの情報があったり、書誌情報の信頼性・正確性が高いというのもある）。</p>
<p>もちろん<a href="http://webcat.nii.ac.jp/" title="Webcat" onclick="pageTracker._trackPageview('/outgoing/webcat.nii.ac.jp/?referer=');">Webcat</a>/<a href="http://webcatplus.nii.ac.jp/" title="Webcat Plus" onclick="pageTracker._trackPageview('/outgoing/webcatplus.nii.ac.jp/?referer=');">Webcat Plus</a>も考えたが、APIとして公開しているものがなく、HTMLからの抽出は、処理の重さや信頼性、あとマナー的にもどうなのということで断念。はやくAPI作ってくださいな・・・。</p>
<p><a href="http://www.worldcat.org/" title="WorldCat" onclick="pageTracker._trackPageview('/outgoing/www.worldcat.org/?referer=');">WorldCat</a>や<a href="http://www.loc.gov/" title="LC" onclick="pageTracker._trackPageview('/outgoing/www.loc.gov/?referer=');">LC</a>も視野に入れたけど、労力の割にそれほどメリットもないので、今回は無視。NDLがWorldCatに情報提供してれば使ったかもなのになあ。</p>
<p>で、amazonのAPI(<a href="http://aws.amazon.com/" title="Amazon Associates Web Service" onclick="pageTracker._trackPageview('/outgoing/aws.amazon.com/?referer=');">Amazon Associates Web Service</a>)は超有名なだけあって、簡単に使えた。</p>
<p>PHPのサンプル探してたら、こちらのページがわかりやすかった。</p>
<ul>
<li><a href="http://blog.livedoor.jp/anthtx/archives/9959716.html" title="週末プログラマのメモ帳:PHPでAmazon E-Commerce Service (ECS) 2" onclick="pageTracker._trackPageview('/outgoing/blog.livedoor.jp/anthtx/archives/9959716.html?referer=');">週末プログラマのメモ帳:PHPでAmazon E-Commerce Service (ECS) 2</a><br/>
※こちらではクエリ文字列をrawurlencodeでエンコードしているが、うちの環境では必要なかった。</li>
</ul>
<p>で、今度はNDLがやってる電子リソースポータル「<a href="http://porta.ndl.go.jp/" title="PORTA" onclick="pageTracker._trackPageview('/outgoing/porta.ndl.go.jp/?referer=');">PORTA</a>」（今回はNDLの所蔵データベースしか使わないが）のAPIを使ってみた。※APIについてはPORTAの「このサイトについて」から「外部提供インタフェースについて」で見られる。なんでパーマリンクがないんじゃあ！</p>
<p>なんか<a href="http://www.loc.gov/standards/sru/" title="SRU" onclick="pageTracker._trackPageview('/outgoing/www.loc.gov/standards/sru/?referer=');">SRU</a>(Seach/Retrieval via URL;LCの作った検索プロトコル。URLでリクエストしてXMLでレスポンスがくる)はともかくSRW（SRUのリクエストがSOAP仕様のXMLになったやつ）の情報があまりにも少なくて、なんでSRUを採用しないのかちょっと理解に苦しむところではあるが、まあそれしかないんだから使うしか仕方ない。</p>
<p>で、以下のコードで、一応結果のオブジェクトは返ってくる。</p>
<p><code>&lt;?php
$client = new SoapClient("http://api.porta.ndl.go.jp/servicedp/services/SRWDp?wsdl");
$q = '(title = architecture) AND (dpid ANY "zomoku zassaku okayama")';
$req = array('version' =&gt; '1.1', 'query' =&gt; $q,
             'startRecord' =&gt; '1', 'maximumRecords' =&gt; '200',
             'recordPacking' =&gt; 'string', 'recordSchema' =&gt; '',
             'sortKeys' =&gt; 'title,,1');
$result = $client-&gt;searchRetrieveOperation($req);
print_r($result);
?&gt;
</code></p>
<p>しかし、返ってきたデータを見てみると、肝心の書誌情報が入っているrecordDataオブジェクトの中身がなぜか空。ヒット数とかはPORTAのWebで検索したときと同じなので、いいとこまでいってるとは思うんだけどなあ。</p>
<p>ちなみに渡すパラメータが足りないとエラー（java.lang.NullPointerException）が返ってくる。省略可能なのもあるし、仕様書で「設定されない場合」とかあるやつでも省略不能っぽいのもある。</p>
<p>ところで、このAPI、公開されてそこそこ日が経つけど、誰か使ってるんやろか。情報すくないんだよなあ。</p>
<p>ちなみに、今回、開発はWindows上で<a href="http://www.apachefriends.org/jp/xampp-windows.html" title="xampp" onclick="pageTracker._trackPageview('/outgoing/www.apachefriends.org/jp/xampp-windows.html?referer=');">xampp</a>を走らせてやってます。xamppはApache、PHP5、MySQLやらが一気に使えて（しかもPortable）たいへん便利。ただ、ドライブのルートに配置しないといかんという制限があるので、仮想ドライブを指定するコマンドを書いた、下記のバッチファイルを作ってxammpディレクトリと同じディレクトリに置いて、開発時に実行してます。</p>
<p><code>rem connect current directory to x: drive
subst x: "%cd%"
exit
</code></p>
<ins>
<hr />
<h3>追記(2008/12/17)</h3>
<p>さっき、この追記を書き終えたところで、さあ更新しようと思って、フォームからフォーカス外すために適当なとこクリックしたら、ちょうどそこがリンクになってて、追記分まるまる消えましたよ・・・。もうやる気ないんで、雑な書き方で。箇条書きって楽だなあ。</p>
<ul>
<li>PHPが原因じゃないかとC#で試すもWeb参照でエラー。なんでや。</li>
<li><a href="http://www.moongift.jp/2007/10/soapui/" title="soapUI" onclick="pageTracker._trackPageview('/outgoing/www.moongift.jp/2007/10/soapui/?referer=');">soapUI</a>を発見。試してみるとrecordDataにデータ入ってる!</li>
<li>どうもrecordDataは&lt;!CDATA[[]]&gt;に囲まれて返ってくる模様。</li>
<li>PHPのSoapClientがレスポンスXMLを配列にする時に落ちてるんかな(参考:<a href="http://okwave.jp/qa4358114.html" title="xml_parse_into_structを使うと、xmlの中のCDATAが消える -OKWave" onclick="pageTracker._trackPageview('/outgoing/okwave.jp/qa4358114.html?referer=');">xml_parse_into_structを使うと、xmlの中のCDATAが消える -OKWave</a>)。</li>
<li>ということは、これが落ちないようにするか、配列にする前のXMLをそのまま取得できればOK。</li>
<li>後者ならsimplexml_load_stringでLIBXML_NOCDATAオプション指定で配列に変換。
</ul>
<p>まだ最後に書いた解決策のどちらも見つけられてないんですが、また、進展があれば、書きます。</p>
<p>新しく記事起こした場合はこの記事にトラックバックしときます。</p>
</ins>
<ins>
<hr /><h3>追記(2008/12/27)</h3></p>
<p>進展があったのでご報告。ようやく書誌情報にアクセス出来ました。</p>
<p>まず、前の追記に書いていた、配列にする前のXMLを取得する方法がわかった。</p>
<p>SoapClient->__getResponseで最後のレスポンスを文字列で取得できる。てっきりこれも配列で返ってくると思ってたよ。</p>
<p>上のコードの例だと、$client-&gt;searchRetrieveOperation($req);のあとに、</p>
<p><code>$xmlstr = $client-&gt;__getRenponse();
</code></p>
<p>で、レスポンスXMLを文字列として取得できる。</p>
<p>こいつをsimplexml_load_stringでCDATAをテキストノードに展開しつつ、配列に変換する。(参考:<a href="http://blog.fkoji.com/2008/07210130.html" title="SimpleXMLでCDATAを取得したいときはLIBXML_NOCDATAを使う - F.Ko-Jiの「一秒後は未来」" onclick="pageTracker._trackPageview('/outgoing/blog.fkoji.com/2008/07210130.html?referer=');">SimpleXMLでCDATAを取得したいときはLIBXML_NOCDATAを使う &#8211; F.Ko-Jiの「一秒後は未来」</a>)</p>
<p><code>$r = simplexml_load_string($xmlstr, 'SimpleXMLElement', LIBXML_NOCDATA);
</code></p>
<p>これをprint_rで見てみると、</p>
<p><code>SimpleXMLElement Object ()
</code></p>
<p>あれ?パースエラーかなーなどといろいろ調べてみると、パース自体はできてるけど、ルート要素と名前空間が違う（あるいは名前空間が指定されてる）要素は、SimpleXMLElement->children()で名前空間のURIを指定してアクセスしないといかんらしい。(参考:<a href="http://stl.blog.shinobi.jp/Entry/34/" title=".☆★ ステレオタイプラボ ★☆. [php]simplexml_load_fileでうまくパース出来ない、なんて事はない。&#038; 正解とお手軽方法" onclick="pageTracker._trackPageview('/outgoing/stl.blog.shinobi.jp/Entry/34/?referer=');">.☆★ ステレオタイプラボ ★☆. [php]simplexml_load_fileでうまくパース出来ない、なんて事はない。&#038; 正解とお手軽方法</a>)</p>
<p>名前空間を指定する:を別の文字に置換しちゃうという手も魅力的だけど、まあ正攻法でやってみる。</p>
<p>先ほどの$xmlstrをprint_rで見てみると、こんな構成になってる。</p>
<p><code>&lt;soapenv:envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    &lt;soapenv:body&gt;
        &lt;searchretrieveresponse xmlns="http://www.loc.gov/zing/srw/"&gt;
            (レスポンスされたデータ本体)
        &lt;/searchretrieveresponse&gt;
    &lt;/soapenv:body&gt;
&lt;/soapenv:envelope&gt;
</code></p>
<p>よって以下のコードでsearchretrieveresponseにアクセスする。</p>
<p><code>$srr = $r->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->children('http://www.loc.gov/zing/srw/');
</code></p>
<p>これですべてのレコードにアクセスできるが、個々の書誌情報がはいってるrecordData要素には、書誌情報のXMLがCDATAとして入ってる。このままだとアクセスしにくいので、これも配列に変換する。例では1つめのレコードの書誌情報をrdに入れている。今回はLIBXML_NOCDATAは(たぶん)いらない。</p>
<p><code> $rd = simplexml_load_string($srr->searchRetrieveResponse->records->record[0]->recordData[0]);
</code></p>
<p>で、さらにこの中身はルート要素と名前空間が違うので・・・</p>
<p><code>$dc = $rd->children('http://purl.org/dc/elements/1.1/');
</code></p>
<p>で、ようやく書誌情報にまともにアクセスできます。めんどくせー!</p>
<p>で、上記の方法で取得してみたデータのサンプル。(クエリは$q = &#8216;(isbn = 4000074628) AND (dpid ANY &#8220;zomoku&#8221;)&#8217;;に変えてる)</p>
<p><code>SimpleXMLElement Object
(
    [title] =&gt; クマムシ？！ : 小さな怪物
    [creator] =&gt; 鈴木忠‖著
    [description] =&gt; Array
    (
        [0] =&gt; 本体価格 : １３００円
        [1] =&gt; シリーズよみ : イワナミ　カガク　ライブラリー ; １２２
        [2] =&gt; シリーズ : 岩波科学ライブラリー ; １２２
        [3] =&gt; 形態 : １１２ｐ ; １９ｃｍ
        [4] =&gt; 出版地 : 東京
    )
    [publisher] =&gt; 岩波書店
)
</code></p>
<p>ほとんどdescriptionにフィールド名とデータをまとめていれちゃってるという、なめてんのかってデータですね(スキーマがdcだからか?また追って調べます。今日はもう疲れた。)。</p>
</ins>]]></content:encoded>
			<wfw:commentRss>http://penguinlab.jp/blog/post/186/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using disk
Page Caching using disk (enhanced)

Served from: penguinlab.jp @ 2012-05-19 21:24:24 -->
