ユーザのブラウザの種類を判別する情報として、以下のようなユーザエージェント名が取得できるが、これを見ても知らなければ詳しい情報がわからない。
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Sleipnir/2.6.1 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727) Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/523.12.2 (KHTML, like Gecko) Version/3.0.4 Safari/523.12.2 Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11 Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Opera/9.50 (X11; Linux i686; U; en) Hatena RSS/0.3 (http://r.hatena.ne.jp; 1 subscribers)
PHPにはこういったユーザエージェント名から詳しい情報を取得するget_browser関数がある。 しかし、これは標準のPHPの設定では使うことができずiniファイルの設定が必要になる。 そこで、get_browser関数の使い方とiniファイルが書きかえれない場合の対処方法を紹介する。
まずBrowser Capabilities ProjectのDownloadsからphp_browscap.iniをDLする。 browscap.iniではないことに注意。 軽量版のlite_php_browscap.iniでもOK。
(※Browser Capabilities Projectで公開されているユーザエージェント情報のファイルはCreative Commons Attribution-NonCommercial Licenseの下で公開されているため商用目的には使えないことに注意。)
次にphp.iniまたはhttpd.confで、browscapにそのDLしたファイルのパスを設定する。
[browscap] browscap = /etc/php5/php_browscap.ini
設定後、apacheを再起動すると、get_browser関数が使えるようになる。
例えば、以下のようにするとアクセスした人のユーザエージェント情報を表示する。
<?php print_r(get_browser()); ?>
特定のユーザエージェントの情報がほしい場合にはユーザエージェント名を第一引数に渡す。
<?php
print_r(get_browser('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'));
?>
stdClass Object ( [browser_name_regex] => ^mozilla/4\.0 (compatible; msie 6\.0; .*windows nt 5\.1.*).*$ [browser_name_pattern] => Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1*)* [parent] => IE 6.0 [platform] => WinXP [browser] => IE [version] => 6.0 [majorver] => 6 [win32] => 1 [frames] => 1 [iframes] => 1 [tables] => 1 [cookies] => 1 [backgroundsounds] => 1 [cdf] => 1 [vbscript] => 1 [javaapplets] => 1 [javascript] => 1 [activexcontrols] => 1 [css] => 2 [cssversion] => 2 [supportscss] => 1 [minorver] => 0 [alpha] => [beta] => [win16] => [win64] => [authenticodeupdate] => [stripper] => [isbanned] => [wap] => [ismobiledevice] => [issyndicationreader] => [crawler] => [aol] => [aolversion] => 0 [netclr] => [clrversion] => 0 )
get_browser関数の返す値は通常オブジェクトなので、配列で返してほしければ第二引数にtrueを渡す。 このときアクセスした人のユーザエージェント情報を取得したければ第一引数にnullを渡す。
<?php print_r(get_browser(null, true)); ?>
自分のブラウザではどうなるのか見たい場合はBrowser Capabilities Projectのメニューの「Your Browser」から、あるユーザエージェントの場合を見たい場合は「User Agent Checker」から見ることができる。
get_browser関数は簡単に使えて便利なのだが、いくつかの問題点がある。
- browscap.iniは商用目的に使えない。
- browscapの設定項目はPHP_INI_SYSTEMであるため、php.ini または httpd.confでしか設定できない。
- 新しいユーザエージェントに対応するためにはbrowscap.iniを更新しなければならない。
- browscap.ini更新後はapacheの再起動が必要。(一部のサーバでは必要なし。)
商用目的に使えないというのはいいとして、残りの問題点がやっかいだ。 管理者が導入していない限り使うことができず、使えたとしても管理者が更新していなければ古いデータでしか使うことができない。 また情報に誤りがあって修正したい場合にも管理者権限が必要になる。
実際、これを書いている時点でおそらく誤りだと思われる箇所が一つ見つかった。
browscap.iniの6470行目。
[Motorola Web Browser] Parent=DefaultProperties Browser="Motorola Internet Browser" Frames=true Tables=true Cookies=true WAP=true isMobileDevice=true
上記の"[Motorola Web Browser]"は、「Motorola Internet Browser」を親としているユーザエージェントが親の情報を取得できていないことから、おそらく"[Motorola Internet Browser]"が正しいと思われる。
[Motorola Internet Browser] Parent=DefaultProperties Browser="Motorola Internet Browser" Frames=true Tables=true Cookies=true WAP=true isMobileDevice=true
管理者権限がなく、browscapも設定されていないため、get_browser関数を使うのを諦めてしまう人もいるだろう。 そこでphp_browscap.iniを使ってget_browser関数と同じ働きをする関数を作ってみる。
まずphp_browscap.iniの中を説明する。 php_browscap.iniの中身は以下のようになっている。
[Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)*] Parent=IE 7.0 Platform=WinVista [Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0;*.NET CLR 1*)*] Parent=IE 7.0 Platform=WinVista netCLR=true ClrVersion=1
[]で挟まれた部分がワイルドカードを使って表したユーザエージェント名である。 その下がそのユーザエージェントの情報を表している。 Parentがある場合、その値と同じ名前を持つ項目の情報も持つ。 上の2つのユーザエージェントの場合、「IE 7.0」という項目の情報をそれぞれ持つということになる。
[IE 7.0] Parent=DefaultProperties Browser="IE" Version=7.0 MajorVer=7 Win32=true Frames=true IFrames=true Tables=true Cookies=true BackgroundSounds=true CDF=true VBScript=true JavaApplets=true JavaScript=true ActiveXControls=true CSS=2 CssVersion=2 supportsCSS=true
さらに、「IE 7.0」は「DefaultProperties」をの情報を持つ。
[DefaultProperties] Browser="DefaultProperties" Version=0 MajorVer=0 MinorVer=0 Platform=unknown Alpha=false Beta=false Win16=false Win32=false Win64=false Frames=false IFrames=false Tables=false Cookies=false BackgroundSounds=false AuthenticodeUpdate= CDF=false VBScript=false JavaApplets=false JavaScript=false ActiveXControls=false Stripper=false isBanned=false WAP=false isMobileDevice=false isSyndicationReader=false Crawler=false CSS=0 CssVersion=0 supportsCSS=false AOL=false aolVersion=0 netCLR=false ClrVersion=0
同じ名前のデータは親の値が子の値で上書きされる。例えば、"[Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)*]"をget_browser関数で取得すると以下のようになる。
Array ( [browser_name_regex] => ^mozilla/4\.0 (compatible; msie 7\.0; .*windows nt 6\.0.*).*$ [browser_name_pattern] => Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)* [parent] => IE 7.0 [platform] => WinVista [browser] => IE [version] => 7.0 [majorver] => 7 [win32] => 1 [frames] => 1 [iframes] => 1 [tables] => 1 [cookies] => 1 [backgroundsounds] => 1 [cdf] => 1 [vbscript] => 1 [javaapplets] => 1 [javascript] => 1 [activexcontrols] => 1 [css] => 2 [cssversion] => 2 [supportscss] => 1 [minorver] => 0 [alpha] => [beta] => [win16] => [win64] => [authenticodeupdate] => [stripper] => [isbanned] => [wap] => [ismobiledevice] => [issyndicationreader] => [crawler] => [aol] => [aolversion] => 0 [netclr] => [clrversion] => 0 )
このようにphp_browscap.iniからユーザエージェントの情報を取り出すわけだが、これだけでは少し問題がある。 例えば、"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)"というユーザエージェントでは以下の5つにマッチする。
Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1*)* Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 1*)* Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 1*.NET CLR 2*)* Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 2*)* *
正解は3つ目で、get_browser関数でもこの情報が取得される。 上の5つはファイルに書かれている順で、単純に最初にマッチしたものというわけにはいかないため、5つの中からもっとも近い1つを選択する必要がある。 これは単純にマッチしたパターンの中で一番長い文字列を選択しても良さそうだが、文字列の類似度を計算した方が確実そうなのでこれを使うことにする。
(※文字列の類似度計算については似た文字列を探す [PHP, Tips]参照。)
以上よりget_browser関数と同じ働きをする関数を作ってみると以下のようになる。
<?php
function get_browser_info($user_agent = null, $ini_path = null){
//引数が省略された場合のデフォルト値を設定
if($user_agent === null)
$user_agent = $_SERVER['HTTP_USER_AGENT'];
if($ini_path === null)
$ini_path = ini_get('browscap');
//iniファイルからデータを取得
$browser_info_list = parse_ini_file($ini_path, true);
//マッチするパターンを探す
foreach($browser_info_list as $pattern => $browser_info){
if(fnmatch($pattern, $user_agent, FNM_CASEFOLD))
$match_pattern_list[] = $pattern;
}
//もっともマッチするパターンを選択
$best_score = PHP_INT_MAX;
foreach($match_pattern_list as $pattern){
$score = levenshtein($user_agent, $pattern);
if($best_score > $score){
$best_score = $score;
$best_pattern = $pattern;
}
}
//情報を取得
$result = array();
$regex = preg_replace(array("/\./","/\*/","/\?/"), array("\.",".*","."), $best_pattern);
$result['browser_name_regex'] = strtolower("^{$regex}$");
$result['browser_name_pattern'] = $best_pattern;
$result += $browser_info_list[$best_pattern];
//親を辿って、親の情報を取得
$parent = @$result['Parent'];
while($parent){
$result += (array)$browser_info_list[$parent];
$parent = @$browser_info_list[$parent]['Parent'];
}
//キーを全て小文字に変換
$r = array();
foreach($result as $key => $value){
$r[strtolower($key)] = $value;
}
return $r;
}
?>
ちなみに、この関数はパターンのマッチングにfnmatch関数を使っているためWindowsサーバでは動かない。 また、オリジナルのget_browser関数と3000件ほどのデータで速度の比較をしたところおよそ50秒と150秒で3倍ほど遅かった。