携帯電話のUserAgent判定クラス

携帯のUserAgentを判定するファクトリメソッドなクラス これは、PEAR::Net_UserAgent_Mobileを学習目的にコピーしたものです。

キャリアごとにパースの仕方が異なるので、それぞれ別のクラスに分けているが使用するメソッドは同じため、基本クラスを継承している。しかし、インスタンス化に別々の new をかますのは面倒だからまとめて面倒をみてくれるファクトリ メソッド パターンを使っているよ。

<?php
$ua =& Keitai::singleton();
if ( !$ua->isValidIp() ) { // Keitai_IP_List.class.phpが必要です
  die('携帯シミュレータなどのアクセスは禁止です。');
}
$id = $ua->getKeitaiId();
?>
  • メソッドリスト

object factory ([string $userAgent = null])
object singleton ([string $userAgent = null])
bool isError ()
string errorMessage ()
bool isDoCoMo ()
bool isKDDI ()
bool isSoftBank ()
bool isWillcom ()
bool isMobile ()
bool isNonMobile ()
bool isSmartPhone()
string getCarrier() : キャリア名 DoCoMo, KDDI, SoftBank, Willcom
string getComment()
string getKeitaiId() : UIDを取得する UIDがない場合は端末製造番号など固有の番号を取得する
string getModel() : 端末モデル名を取得する
string getRawModel() : 端末モデル名の文字列をそのまま取得する
string getUA() : UserAgentをそのまま取得する
string getVendor() : 端末メーカー名またはブラウザ名を取得する
string getVersion() : ブラウザのバージョンを取得する
bool isValidIp() : アクセス元がキャリアゲートウェイであるか判定する

  • Keitai.class.php ソース
<?php /* -*-java-*- */
require_once('PEAR.php');
/*
 * Constants for error handling.
 */
define('KEITAI_OK',               1);
define('KEITAI_ERROR',           -1);
define('KEITAI_ERROR_NOMATCH',   -2);
define('KEITAI_ERROR_NOT_FOUND', -3);

/*
 * Globals for fallback on no match.
 * 
 * @global boolean $GLOBALS['_KEITAI_FALLBACK_ON_NOMATCH']
 */
$GLOBALS['_KEITAI_FALLBACK_ON_NOMATCH'] = false;


/**
 * 携帯のUserAgentを判定するファクトリメソッドなクラス
 * これは、PEAR::Net_UserAgent_Mobile を学習目的にコピーしたものです。
 * 
 * キャリアごとにパースの仕方が異なるので、それぞれ別のクラスに分けているが
 * 使用するメソッドは同じため、基本クラスを継承している。
 * しかし、インスタンス化に別々の new をかますのは面倒だからまとめて面倒を
 * みてくれるファクトリ・メソッド・パターンを使っているよ。
 * <code>
 * $ua =& Keitai::singleton();
 * if ( !$ua->isValidIp() ) {
 *     // シミュレータなどによるアクセスで、gwのip帯ではない
 *     die('invalid access.');
 * }
 * $id = $ua->getKeitaiId();
 * </code>
 * isValidIp() を使う場合には、Keitai_IP_List.class.php が必要です。
 * 
 * @auther k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 * @see    Keitai_IP_List, isValidIp()
 */
class Keitai {
    
    /**
     * ファクトリ・メソッド
     * 使用するクラスを隠蔽して、お任せでインスタンス化するための
     * メソッドだよ。普通は、直接呼び出されることはないよ。(singleton経由)
     * 
     * @access public
     * @param  string $userAgent
     * @return object 成功なら Keitai_* オブジェクト、
     *                失敗なら Keitai_Errorオブジェクトを返す。
     */
    function &factory( $userAgent=null ) {
	
	if ( is_null($userAgent)) {
	    $userAgent = $_SERVER['HTTP_USER_AGENT'];
	}
	
	if ( Keitai::isDoCoMo($userAgent) ) {
	    $driver = 'DoCoMo';
	}
	elseif ( Keitai::isKDDI($userAgent) ) {
	    $driver = 'KDDI';
	}
	elseif ( Keitai::isSoftBank($userAgent) ) {
	    $driver = 'SoftBank';
	}
	elseif ( Keitai::isWillcom($userAgent) ) {
	    $driver = 'Willcom';
	}
	elseif ( Keitai::isSmartPhone($userAgent) ) {
	    $driver = 'SmartPhone';
	}
	else {
	    $driver = 'NonMobile';
	}
	
	$class = "Keitai_".$driver;
	
	$instance =& new $class($userAgent);
	
	$error =& $instance->isError();
	if ( Keitai::isError($error) ) {
	    if ( $GLOBALS['_KEITAI_FALLBACK_ON_NOMATCH']
		 && $error->getCode() == KEITAI_ERROR_NOMATCH ) {
		$instance =& Keitai::factory('Keitai_Fallback_On_NoMatch');
		return $instance;
	    }
	    $instance =& $error;
	}
	
	return $instance;
    }
    
    /**
     * シングルトン・メソッド
     * おんなじようなオブジェクトを使いまわすときに、新たにインスタンス化しない
     * ためのパターン。(んーでも、コンストラクタでやることが重要な場合は
     * 気をつけないといけないね。)
     * 
     * @access public
     * @param  string $userAgent
     * @return object $userAgentをキーとしてオブジェクトを管理。
     *                同じuserAgentならインスタンスつくらないよ。
     */
    function &singleton($userAgent=null) {
	static $instance;
	
	if ( !isset($instance) ) {
	    $instances = array();
	}
	
	if ( is_null($userAgent) ) {
	    $userAgent = $_SERVER['HTTP_USER_AGENT'];
	}
	
	if ( !array_key_exists($userAgent, $instances) ) {
	    $instances["$userAgent"] = Keitai::factory($userAgent);
	}
	
	return $instances["$userAgent"];
    }
    
    /**
     * エラーオブジェクト判定
     * Keitai_Error オブジェクトが生成されているかを判定するよ
     * 
     * @access public
     * @param  var     $value
     * @return bool    $valueが Keitai_Error オブジェクトなら TRUE,
     *                 それ以外なら FALSE を返す。
     */
    function isError($value) {
	return is_a($value, 'Keitai_Error');
    }
    
    
    /**
     * コードごとに割当てられたエラーメッセージを取得する
     * 
     * @access public
     * @param  int    $value
     * @return string
     */
    function errorMessage($value) {
	static $errorMessage;
	
	if ( !isset($errorMessage) ) {
	    $errorMessages = array( KEITAI_ERROR => 'unkown error',
				    KEITAI_ERROR_NOMATCH => 'no match',
				    KEITAI_ERROR_NOT_FOUND => 'not found',
				    KEITAI_OK => 'no error'
				    );
	}
	
	if ( Keitai::isError($value) ) {
	    $value = $value->getCode();
	}
	
	return isset($errorMessage["$value"]) ? $errorMessage["$value"]
	                                      : $errorMessage[KEITAI_ERROR];
    }
    
    /**
     * モバイルか非モバイルかを判定する
     * (パースしているので無駄なメソッドともいえる。)
     * 
     * @access public
     * @param  string $userAgent
     * @return bool   モバイルならtrue, 非モバイルならfalse を返す。
     */
    function isMobile ( $userAgent=null ) {
	
	if ( Keitai::isDoCoMo($userAgent) ) {
	    return true;
	}
	elseif ( Keitai::isSoftBank($userAgent) ) {
	    return true;
	}
	elseif ( Keitai::isKDDI($userAgent) ) {
	    return true;
	}
	elseif ( Keitai::isWillcom($userAgent) ) {
	    return true;
	}
	elseif ( Keitai::isSmartPhone($userAgent) ) {
	    return true;
	}
	return false;
    }
    
    /**
     * HTTP-UserAgent からキャリアを判定して返す。最初にパースをするところ
     * 
     * SoftBankの端末の中にも UP.Link とかだすやつがいるので
     * KDDIよりも先にSoftBankを判定するよ。
     * 
     * @access private
     * @param  string  $userAgent
     * @return string
     */
    function _parse($userAgent=null) {
	
	if ( is_null($userAgent) ) {
	    $userAgent = $_SERVER['HTTP_USER_AGENT'];
	}
	
	/*
	 * DoCoMo
	 */
	if ( !strncmp($userAgent, 'DoCoMo', 6) ) {
	    return 'DoCoMo';
	}
	
	/*
	 * SoftBank (inc. J-Phone Vodafone)
	 */
	if ( !strncmp($userAgent, 'J-PHONE', 7) ) {
	    return 'SoftBank';
	}
	if ( !strncmp($userAgent, 'Vodafone', 8)
	     || !strncmp($userAgent, 'MOT', 3) ) {
	    return 'SoftBank';
	}
	if ( !strncmp($userAgent, 'SoftBank', 8) ) {
	    return 'SoftBank';
	}
	
	/*
	 * KDDI au
	 */
	if ( !strncmp($userAgent, 'KDDI', 4)
	     || !strncmp($userAgent, 'UP.Browser', 10) ) {
	    return 'KDDI';
	}
	
	/*
	 * WILLCOM (inc. DDIpocket )
	 */
	if ( strpos($userAgent, 'WILLCOM') !== false
	     || strpos($userAgent, 'SHARP/WS') !== false
	     || strpos($userAgent, 'DDIPOCKET') !== false ) {
	    return 'Willcom';
	}
	
	/*
	 * SmartPhone
	 * - Android
	 * - iPhone
	 * - Windows Mobile
	 * - etc.
	 */
	if ( strpos($userAgent, 'Android') !== false
	     && strpos($userAgent, 'Mobile') !== false
	     ) {
	    return 'SmartPhone';
	}
	if ( strpos($userAgent, 'iPhone') !== false
	     && strpos($userAgent, 'Mobile') !== false
	     ) {
	    return 'SmartPhone';
	}
	if ( strcmp($userAgent, 'iPod') !== false
	     && strpos($userAgent, 'Mobile') !== false
	     ) {
	    return 'SmartPhone';
	}
	if ( isset($_SERVER['HTTP_UA_OS'])
	     && strpos($_SERVER['HTTP_UA_OS'], 'Windows CE') !== false
	     ) {
	    return 'SmartPhone';
	}
	
	return 'NonMobile';
    }
    
    /**
     * DoCoMo判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isDoCoMo($userAgent) {
	return ('DoCoMo'==Keitai::_parse($userAgent)) ? true : false;
    }
    
    /**
     * KDDI判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isKDDI($userAgent) {
	return ('KDDI'==Keitai::_parse($userAgent)) ? true : false;
    }
    
    /**
     * SoftBank判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isSoftBank($userAgent) {
	return ('SoftBank'==Keitai::_parse($userAgent)) ? true : false;
    }
    
    /**
     * Willcom判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isWillcom($userAgent) {
	return ('Willcom'==Keitai::_parse($userAgent)) ? true : false;
    }
    
    /**
     * SmartPhone判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isSmartPhone($userAgent) {
	return ('SmartPhone'==Keitai::_parse($userAgent)) ? true : false;
    }
    
    /**
     * 非モバイル判定
     * 
     * @access public
     * @param  string $userAgent
     * @return bool
     */
    function isNonMobile($userAgent) {
	return ('NonMobile'==Keitai::_parse($userAgent)) ? true : false;
    }
}


/**
 * エラー処理用クラス
 * うむー、自分で作ったものにも組み込まないトナー
 * 
 * @auther k5959k+yamada@gmail.com
 * @package ya--mada
 * @create  2008-02-13
 */
class Keitai_Error extends PEAR_Error {
    
    /**
     * Constractor
     * 
     * @access public
     * @param  int    $code
     * @param  int    $mode
     * @param  int    $level
     * @param  bool   $userinfo
     */
    function Keitai_Error ( $code=KEITAI_ERROR,
			    $mode=PEAR_ERROR_RETURN,
			    $level=E_USER_NOTICE,
			    $userinfo=null ) {
	if ( is_int($code) ) {
	    $this->PEAR_Error('Keitai Error: '.Keitai::errorMessage($code),
			      $code, $mode, $level, $userinfo );
	}
	else {
	    $this->PEAR_Error("Keitai Error: $code",
			      KEITAI_ERROR, $mode, $level, $userinfo );
	}
    }
    
    /**
     * PEAR::isError() のインタフェース的
     * 
     * @access public
     * @return bool
     */
    function isError() {
	return PEAR::isError($this);
    }
}


/**
 * ケータイクラスの基本となるスーパークラス
 * インターフェース的に使うわけだが、共通化できるものはするぜ的。
 * 
 * @auther k5959k+yamada@gmail.com
 * @package ya--mada
 * @create  2007-02-13
 */
class Keitai_Common extends PEAR {
    
    var $version;     // 
    var $carrier;     // DoCoMo, KDDI, SoftBank, Willcom, NonMobile
    
    var $_ua;         // HTTP-UserAgent
    var $_keitaiId;   // terminal unique serial number
    var $_model;
    var $_rawModel;
    var $_display;
    var $_vendor;     // vendor code like 'SH'
    var $_cacheSize;
    var $_comment;    // like 'Google WAP Proxy/1.0'
    var $_isValidIp;
    var $_rawCarrier; // UserAgentの最初のワード J-PHONEとかUP.Linkとか
    
    var $_error;
    
    /**
     * Constractor
     * インターフェースだね。
     * 
     * @access public
     * @param  string $userAgent
     */
    function Keitai_Common ( $userAgent ) {
	
	parent::PEAR('Keitai_Error'); // 親コンストラクタの呼び出し
	
	$result = $this->parse($userAgent);
	if ( Keitai::isError($result) ) {
	    $this->isError($result);
	}
	$this->_ua = $userAgent;
	$this->carrier = $this->getCarrier();
    }
    
    /**
     * エラー判定
     * 
     * @access public
     * @param  object 引数がnullならオブジェクト内のプロパティを返す。
     * @return object
     */
    function &isError( $error=null ) {
	if ( !is_null($error) ) {
	    $this->_error = &$error;
	}
	
	return $this->_error;
    }
    
    /**
     * エラーオブジェクトの生成
     * 
     * @access public
     * @param  int    $code
     * @param  int    $mode
     * @param  int    $level
     * @param  bool   $userinfo
     */
    function &raiseError( $code = KEITAI_ERROR, $mode = null,
			  $options = null, $userinfo = null ) {
	
	if ( is_object($code) ) {
	    $error =& PEAR::raiseError( $code, null, null, null, null, null,
					true );
	    return $error;
	}
	
	$error =& PEAR::raiseError( null, $code, $mode, $options, $userinfo,
				    'Keitai_Error', true );
	return $error;
    }
    
    /**
     * get UserAgent
     * 
     * @access public
     * @return string _userAgent
     */
    function getUA () {
	return $this->_ua;
    }
    
    /**
     * get HTTP header
     * 
     * @access public
     * @param  string $header HTTP header entory
     * @return string $_SERVER['HTTP_'.$header]
     */
    function getHeader ( $header ) {
	return @$_SERVER['HTTP_'.str_replace( '-', '_', $header ) ];
    }
    
    /**
     * 携帯電話会社名を返します。
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return $this->carrier;
    }
    
    /**
     * 携帯アクセスのパースにマッチしないときの処理をするメソッド
     * 
     * @access public
     * @return object Keitai_Errorを返す
     */
    function noMatch () {
	return $this->raiseError( KEITAI_ERROR_NOMATCH, null,
				  null, $this->getUA(),
				  ': might be new variants. Please contact the authoer of Keitai class!'
				  );
    }
    
    /**
     * パースメソッドのインターフェース
     * 
     * @access public
     */
    function parse ( $userAgent ) {}
    
    function isNonMobile() {
	return false;
    }
    
    function isDoCoMo() {
	return false;
    }
    
    function isKDDI() {
	return false;
    }
    
    function isSoftBank() {
	return false;
    }
    
    function isWillcom() {
	return false;
    }
    
    function isSmartPhone() {
	return false;
    }
    
    /**
     * モデル名(機種名)
     * 
     * @access public
     * @return string
     */
    function getModel() {
	if ( is_null($this->_model) ) {
	    return $this->_rawModel;
	}
	
	return $this->_model;
    }
    
    /**
     * モデル名(加工なし)
     * 
     * @access public
     * @return string
     */
    function getRawModel() {
	return $this->_rawModel;
    }
    
    /**
     * ヴァージョン番号
     * 
     * @access public
     * @return string
     */
    function getVersion() {
	return $this->version;
    }
    
    /**
     * ディスプレイオブジェクト
     * 
     * @todo   使えないし、使えるようにする気にならない
     * @access public
     * @return object
     */
    function getDisplay() {
	if ( !is_object($this->_display) ) {
	    $this->_display = $this->makeDisplay();
	}
	return $this->_display;
    }
    
    /**
     * アクセス元のIP判定
     * 携帯キャリアのgwのipからのアクセスかどうかを調べる
     * Keitai_IP_List.class.php に記述した IPaddr からのアクセスか確認する
     * 
     * @access public
     * @return bool
     * @see    PEAR::Net::IPv4, Keitai_IP_List
     */
    function isValidIp () {
	
	include_once('Net/IPv4.php');
	include_once('Keitai_IP_List.class.php');
	
	$list =& Keitai_IP_List::getList($this->carrier);
	$this->_isValidIp = false;
	
	foreach ( $list as $valid_ip ) {
	    if ( Net_IPv4::ipInNetwork($_SERVER['REMOTE_ADDR'], $valid_ip) ) {
		$this->_isValidIp = true;
		break;
	    }
	}
	
	return $this->_isValidIp;
    }
    
    /**
     * 携帯電話のサブスクライバIDを取得する
     * 実際には、各クラスのparse()内で取得します。
     * 
     * DoCoMo: UserAgentの最後に serXXXXX;iccxxxxxx で付加される
     * au: $_SERVER['HTTP_X_UP_SUBNO'] xxxxxx_t*.ezweb.ne.jp で取得できる
     * SoftBank: UserAgent内に埋め込み、端末によって異なる
     */
    function getKeitaiId () {
	return $this->_keitaiId;
    }
    
    /**
     * コメント扱いの値
     * 
     * @access public
     * @return string
     */
    function getComment () {
	return $this->_comment;
    }
    
    /**
     * ベンダー
     * 
     * @access public
     * @return string
     */
    function getVendor () {
	return $this->_vendor;
    }
    
}


/**
 * NTT DoCoMo 用のクラス
 * コンストラクタ無しですが、コンストラクタを加えるときには
 * parent::PEAR($userAgent) を入れるのが良いと思います。
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 */
class Keitai_DoCoMo extends Keitai_Common {
    
    var $_status;
    var $_bandWidth;
    var $_isFoma;
    var $_serialId;
    var $_fomaCardId;
    var $_displayBytes;
    
    /**
     * ドコモ判定
     * 
     * @access public
     * @return bool
     */
    function isDoCoMo() {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'DoCoMo';
    }
    
    /**
     * DoCoMoキャリアのアクセスをパースするよ
     * mova と FOMA でパースが異なるよ。
     * 
     * @access public
     * @param  string $userAgent
     * @see    _parseKeitaiId(), _parseMain(), _parseFoma()
     */
    function parse($userAgent) {
	
	@list($main, $foma_or_comment) = explode(' ', $userAgent, 2);
	
	if ( $foma_or_comment
	     && preg_match('/^\((.*)\)$/', $foma_or_comment, $matches) ) {
	    // ex. DoCoMo/1.0/P209is (Google CHTML Proxy/1.0)
	    $this->_comment = $matches[1];
	    $result = $this->_parseMain($main);
	}
	elseif ( $foma_or_comment ) {
	    // ex. DoCoMo/2.0 N2001(c10;ser01234abc;icc0123456)
	    $this->_isFoma = true;
	    list($this->name, $this->version) = explode('/', $main);
	    $result = $this->_parseFoma($foma_or_comment);
	}
	else {
	    // ex. DoCoMo/1.0/R692i/c10
	    $result = $this->_parseMain($main);
	}
	
	if ( Keitai::isError($result) ) {
	    return $result;
	}
	
	/*
	 * サブスクライバIDの取得
	 */
	$keitaiId = $this->_parseKeitaiId($userAgent);
	if ( !is_null($keitaiId) ) {
	    $this->_keitaiId = $keitaiId;
	}
	
	
	/*
	 * iモードID に対応する
	 * HTTP拡張ヘッダ X-DCMGUID を取得する
	 */
	if ( isset($_SERVER['HTTP_X_DCMGUID']) ) {
	    $this->_keitaiId = $this->getHeader('X-DCMGUID');
	}
	elseif ( !is_null($this->_serialId) ) {
	    $this->_keitaiId = $this->_serialId;
	}
	else {
	    $this->_keitaiId = null;
	}
	
    }
    
    
    /**
     * 端末の個体識別番号を取得します
     * 
     * @access public
     * @param  string $userAgent
     * @return string $id  エラーの場合は null を返します。
     * @see    _parseKeitaiId(), _parseMain(), _parseFoma()
     */
    function _parseKeitaiId($userAgent) {
	
	$id = null;
	
	// mova
	if ( substr($userAgent, 7, 3) === '1.0' ) {
	    // "/"区切りで最後を取り出す
	    $pieces = explode('/', $userAgent);
	    $ser = array_pop($pieces);
	    
	    if ( !strncmp($ser, 'ser', 3) ) {
		$id = $ser;
	    }
	}
	// FOMA
	elseif ( substr($userAgent, 7, 3) === '2.0' ) {
	    $ser = substr($userAgent, -24, -1);
	    
	    if ( !strncmp($ser, 'ser', 3) ) {
		$id = $ser;
	    }
	}
	
	return $id;
    }
    
    /**
     * digital MOVA 用のパーサー
     * 
     * @access public
     * @param  string
     * @see    _parseKeitaiId(), _parseMain(), _parseFoma()
     */
    function _parseMain ( $main ) {
	
	@list($this->_rawCarrier, $this->version, $this->_rawModel, $cache, $rest)
	    = explode('/', $main, 5);
	
	if ( $this->_rawModel == 'SH505i2' ) {
	    $this->_model = 'SH505i';
	}
	
	if ($cache) {
	    if (!preg_match('/^c(\d+)$/', $cache, $matches)) {
		return $this->noMatch();
	    }
	    $this->_cacheSize = (integer)$matches[1];
	}
	
	if ($rest) {
	    $rest = explode('/', $rest);
	    foreach ( $rest as $value ) {
		if (preg_match('/^ser(\w{11})$/', $value, $matches)) {
		    $this->_serialId = $matches[1];
		    continue;
		}
		if (preg_match('/^(T[CDBJ])$/', $value, $matches)) {
		    $this->_status = $matches[1];
		    continue;
		}
		if (preg_match('/^s(\d+)$/', $value, $matches)) {
		    $this->_bandwidth = (integer)$matches[1];
		    continue;
		}
		if (preg_match('/^W(\d+)H(\d+)$/', $value, $matches)) {
		    $this->_displayBytes = "{$matches[1]}*{$matches[2]}";
		    continue;
		}
	    }
	}
    }
    
    /**
     * FOMA 用のパーサー
     * 
     * @access public
     * @param  string
     * @see    _parseKeitaiId(), _parseMain(), _parseFoma()
     */
    function _parseFoma ($foma) {
	
	if (!preg_match('/^([^(\s]+)/', $foma, $matches)) {
	    return $this->noMatch();
	}
	
	
	$this->_rawModel = $matches[1];
	if ( $this->_rawModel == 'MS_v_SH2101V' ) {
	    $this->_model = 'SH2101V';
	}
	
	if (preg_match('/^[^(\s]+\s?\((.*?)\)$/', $foma, $matches)) {
	    
	    // DoCoMo compatible user-agent.
	    if (preg_match('/^compatible/', $matches[1])) {
		$this->_comment = $matches[1];
		return;
	    }
	    
	    $rest = explode(';', $matches[1]);
	    
	    foreach ( $rest as $value ) {
		if ( preg_match('/^c(\d+)$/', $value, $matches) ) {
		    $this->_cacheSize = (integer)$matches[1];
		    continue;
		}
		if ( preg_match('/^ser(\w{15})$/', $value, $matches) ) {
		    $this->_serialId = $matches[1];
		    continue;
		}
		if ( preg_match('/^(T[CDBJ])$/', $value, $matches) ) {
		    $this->_status = $matches[1];
		    continue;
		}
		if ( preg_match('/^icc(\w{20})?$/', $value, $matches) ) {
		    if (count($matches) == 2) {
			$this->_fomaCardId = $matches[1];
		    }
		    continue;
		}
		if ( preg_match('/^W(\d+)H(\d+)$/', $value, $matches) ) {
		    $this->_displayBytes = "{$matches[1]}*{$matches[2]}";
		    continue;
		}
		return $this->noMatch($value);
	    }
	}
    }
}


/**
 * KDDI au 用のクラス
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 */
class Keitai_KDDI extends Keitai_Common {
    
    var $_serverName; // server string like 'UP.Link/3.2.1.2
    var $_xhtmlCompliant;
    
    /**
     * KDDI判定
     * 
     * @access public
     * @return bool
     */
    function isKDDI() {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'KDDI';
    }
    
    /**
     * KDDIキャリアのアクセスをパースするよ
     * 
     * @access public
     * @param  string $userAgent
     */
    function parse($userAgent) {
	
	/*
	 * HTTP-UserAgent をパースしてプロパティに代入する
	 */
	if ( preg_match('/^KDDI-(.*)/', $userAgent, $matches) ) {
	    // ex. KDDI-ST33 UP.Browser/6.2.0.13.2 (GUI) MMP/2.0
	    $this->_xhtmlCompliant = true;
	    list($this->_rawModel, $browser, $opt, $this->_serverName) = 
		explode( ' ', $matches[1], 4 );
	    list($this->_rawCarrier, $version) = explode('/',$browser);
	    $this->version = "$version $opt";
	}
	else {
	    // ex. UP.Browser/3.01-HI01 UP.Link/3.4.5.2
	    @list($browser, $this->_serverName, $comment) =
		explode(' ', $userAgent, 3);
	    list($this->_rawCarrier, $software) = explode('/', $browser);
	    list($this->version, $this->_rawModel) = explode('-', $software);
	    if ( $comment ) {
		$this->_comment = preg_replace('/^\((.*)\)$/', '$1', $comment);
	    }
	}
	
	/*
	 * subscriver id
	 * au ではhttp拡張ヘッダで情報を送ってくる
	 */
	if ( isset($_SERVER['HTTP_X_UP_SUBNO']) ) {
	    $this->_keitaiId = $this->getHeader('X-UP-SUBNO');
	}
	else {
	    $this->_keitaiId = null;
	}
	
    }
    
    /**
     * HDMLではなく、XHTMLを解釈できますよフラグ
     * 
     * @access public
     * @return bool
     */
    function isXHTMLCompliant() {
	return $this->_xhtmlCompliant;
    }
    
    /**
     * 機種名(rawModel)の3番目の文字が「3」だったら WIN端末です
     * 
     * @access public
     * @return bool
     */
    function isWIN () {
	return (substr($this->_rawModel, 2,1)==3) ? true : false;
    }
}

/**
 * 
 *
 */
/**
 * SoftBank include J-Phone, Vodafone 用のクラス
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 */
class Keitai_SoftBank extends Keitai_Common {
    
    var $_packetCompliant;
    var $_vendorVersion;     // vendor version like '0001a'
    var $_javaInfo;          // Java profiles
    var $_is3G;
    var $_msName;            // the name of the mobile phone
    var $_carrierModel;      // Motorolaもあるでよ メーカーとも違う
    var $_serialId;
    
    /**
     * ソフトバンク判定
     * 
     * @access public
     * @return bool
     */
    function isSoftBank () {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'SoftBank';
    }
    
    /**
     * SoftBankキャリアのアクセスをパースするよ
     * モトローラの扱いがちょっと厄介な感じだね。
     * HTTP拡張ヘッダ X_JPHONE_UID を取得するように変更 2008-12-09
     * 拡張ヘッダがない場合は、UserAgent内のシリアルidを使用する。
     * 
     * @access public
     * @param  string $userAgent
     * @see    _parseKeitaiId(), _parseMotorola()
     */
    function parse ($userAgent) {
	
	$agent = explode(' ', $userAgent);
	preg_match('!^(?:(SoftBank|Vodafone|J-PHONE)/\d.\d|MOT-|MOTEMULATOR)!',
		    $agent[0], $matches );
	if (count($matches)>1) {
	    $this->_rawCarrier = $matches[1];
	}
	else {
	    $this->_rawCarrier = 'Motorola';
	}
	
	switch ($this->_rawCarrier) {
	case 'SoftBank':
	case 'Vodafone':
	case 'J-PHONE':
	case 'Motorola':
	case 'MOTEMULATOR':
	    $result = $this->_parseMotorola($agent);
	    break;
	}
	
	if (Keitai::isError($result)) {
	    return $result;
	}
	
	$this->_mSname = $this->getHeader('X-JPHONE-MSNAME');
	
	/*
	 * サブスクライバIDの取得
	 */
	$this->_serialId = $this->_parseKeitaiId($userAgent);
	
	/*
	 * HTTP拡張ヘッダ X_JPHONE_UID を取得する
	 */
	if ( isset($_SERVER['HTTP_X_JPHONE_UID']) ) {
	    $this->_keitaiId = $this->getHeader('X-JPHONE-UID');
	}
	else {
	    $this->_keitaiId = $this->_serialId;
	}
    }
    
    
    /**
     * 端末の個体識別番号を取得します
     * 
     * @access public
     * @param  string $userAgent
     * @return string $id  エラーの場合は null を返します。
     * @see    _parseKeitaiId(), _parseMotorola()
     */
    function _parseKeitaiId($userAgent) {
	
	$id = null;
	
	// J-Phone(PDC)
	if ( !strncmp($userAgent, 'J-PHONE', 7) ) {
	    $pieces = explode('/', $userAgent);
	    $piece_sn = explode(' ', $pieces[3]);
	    $sn = array_shift($piece_sn);
	    
	    if (!strncmp($sn, 'SN', 2)) {
		$id = $sn;
	    }
	}
	// Vodafone(3G)
	// MOTは製造番号を取得できない?ほんとに?
	elseif (!strncmp($userAgent, 'Vodafone', 8)) {
	    $pieces = explode('/', $userAgent);
	    $piece_sn = explode(' ', $pieces[4]);
	    $sn = array_shift($piece_sn);
	    
	    if (!strncmp($sn, 'SN', 2)) {
		$id = $sn;
	    }
	}
	// SoftBank
	elseif (!strncmp($userAgent, 'SoftBank', 8)) {
	    $pieces = explode('/', $userAgent);
	    $piece_sn = explode(' ', $pieces[4]);
	    $sn = array_shift($piece_sn);
	    
	    if (!strncmp($sn, 'SN', 2)) {
		$id = $sn;
	    }
	}
	
	return $id;
    }
    
    /**
     * モトローラ(シミュレータ?)のパーサー
     * 
     * @access public
     * @param  string
     */
    function _parseMotorola($agent) {
	$count = count($agent);
	$this->_packetCompliant = true;
	$this->_vendor = 'MOT';
	
	// ex. MOT-V980/80.2F.2E MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1
	list($this->_rawModel, $this->_vendorVersion) = explode('/', $agent[0]);
	$this->_model = substr(strrchr($this->_rawModel, '-'), 1);
	
	for ( $i=2 ; $i<$count ; ++$i ) {
	    list($key, $value) = explode('/', $agent[$i]);
	    $this->_javaInfo["$key"] = $value;
	}
    }
    
    /**
     * packet compliant なんやパケット完全性?
     * 
     * @access public
     * @return 
     */
    function isPacketCompliant() {
	return $this->_packetCompliant;
    }
        
}


/**
 * WILLCOM include DDI Pocket, AirH" 用のクラス
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 */
class Keitai_Willcom extends Keitai_Common {
    
    var $_modelVersion;
    var $_browserVersion;
    
    /**
     * ウィルコム判定
     * 
     * @access public
     * @return bool
     */
    function isWillcom () {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'Willcomm';
    }
    
    
    /**
     * WILLCOMキャリアのアクセスをパースするよ
     * 
     * @access public
     * @param  string $userAgent
     */
    function parse($userAgent) {
	
	if ( preg_match('!^Mozilla/3\.0\((?:DDIPOCKET|WILLCOM);(.*)\)!',
			$userAgent, $matches) ) {
	    list($this->_vendor, $this->_rawModel, $this->_modelVersion,
		 $this->_browserVersion, $cache) = expload('/', $matches[1]);
	    if (!preg_match('/^[Cc](\d+)/', $cache, $matches) ) {
		return $this->noMatch();
	    }
	    $this->_cacheSize = (integer)$matches[1];
	}
	else {
	    $this->noMatch();
	}
	
    }
    
    /**
     * キャッシュサイズ
     * 
     * @access public
     * @return string
     */
    function getCacheSize () {
	return $this->_cacheSize;
    }
}


/**
 * スマートフォン用のクラス
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2009-11-16
 */
class Keitai_SmartPhone extends Keitai_Common {
    
    /**
     * スマートフォン判定
     * 
     * @access public
     * @return bool
     */
    function isSmartPhone() {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'SmartPhone';
    }
    
    
    /**
     * スマートフォンのアクセスをパースするよ
     * といっても / で分解するだけだよ。
     * 
     * @todo   各OSをちゃんと見分けられるようなメソッドを用意する
     * @access public
     * @param  string $userAgent
     */
    function parse($userAgent) {
	@list($this->_rawCarrier, $this->version) = explode('/',$userAgent);
    }
}


/**
 * 非モバイル用のクラス
 * 
 * @author k5959k+yamada@gmail.com
 * @package ya--mada
 * @create 2008-02-13
 */
class Keitai_NonMobile extends Keitai_Common {
    
    /**
     * 非モバイル判定
     * 
     * @access public
     * @return bool
     */
    function isNonMobile() {
	return true;
    }
    /**
     * キャリア名を返すよ
     * 
     * @access public
     * @return string
     */
    function getCarrier () {
	return 'NonMobile';
    }
    
    
    /**
     * 非モバイルのアクセスをパースするよ
     * といっても / で分解するだけだよ。
     * 
     * @access public
     * @param  string $userAgent
     */
    function parse($userAgent) {
	@list($this->_rawCarrier, $this->version) = explode('/',$userAgent);
    }
}