diff --git a/includes/hooks/auto-fill-client-address-by-ip.php b/includes/hooks/auto-fill-client-address-by-ip.php new file mode 100644 index 0000000..3e87630 --- /dev/null +++ b/includes/hooks/auto-fill-client-address-by-ip.php @@ -0,0 +1,504 @@ +memorySearch($ip); + $arr = explode('|', str_replace(['0|'], '|', isset($geo['region']) ? $geo['region'] : '')); + + // $country = $arr[0]; + $province = $arr[2]; + $city = $arr[3]; + // $isp = $arr[4]; + + // 释放资源 + unset($ip2region); + + $currentUser = new CurrentUser(); + $selectedClient = $currentUser->client(); + + if (!is_null($selectedClient)) { + // if (empty($selectedClient->address1)) { + // $selectedClient->address1 = $city; + // } + + // 更新客户信息 + // if (empty($selectedClient->country)) { + // $selectedClient->country = $country; + // } + + if (empty($selectedClient->state)) { + $selectedClient->state = $province; + } + + if (empty($selectedClient->city)) { + $selectedClient->city = $city; + } + + // if (empty($selectedClient->companyname)) { + // $selectedClient->companyname = $isp; + // } + + $selectedClient->save(); + } +} + +add_hook('ClientAreaRegister', 1, 'updateClientInfo'); +add_hook('UserLogin', 1, 'updateClientInfo'); + + +/** + * class Ip2Region + * 为兼容老版本调度而创建 + * @author Anyon + * @datetime 2022/07/18 + */ +class Ip2Region +{ + /** + * 查询实例对象 + * @var XdbSearcher + */ + private $searcher; + + /** + * 初始化构造方法 + * @throws Exception + */ + public function __construct() + { + class_exists('XdbSearcher'); + $this->searcher = XdbSearcher::newWithFileOnly(ROOTDIR . "/resources/xdb/ip2region.xdb"); + } + + /** + * 兼容原 memorySearch 查询 + * @param string $ip + * @return array + * @throws Exception + */ + public function memorySearch($ip) + { + return ['city_id' => 0, 'region' => $this->searcher->search($ip)]; + } + + /** + * 兼容原 binarySearch 查询 + * @param string $ip + * @return array + * @throws Exception + */ + public function binarySearch($ip) + { + return $this->memorySearch($ip); + } + + /** + * 兼容原 btreeSearch 查询 + * @param string $ip + * @return array + * @throws Exception + */ + public function btreeSearch($ip) + { + return $this->memorySearch($ip); + } + + /** + * 直接查询并返回名称 + * @param string $ip + * @return string + * @throws \Exception + */ + public function simple($ip) + { + $geo = $this->memorySearch($ip); + $arr = explode('|', str_replace(['0|'], '|', isset($geo['region']) ? $geo['region'] : '')); + if (($last = array_pop($arr)) === '内网IP') + $last = ''; + return join('', $arr) . (empty($last) ? '' : "【{$last}】"); + } + + /** + * destruct method + * resource destroy + */ + public function __destruct() + { + $this->searcher->close(); + unset($this->searcher); + } +} + +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. +// +// @Author Lion +// @Date 2022/06/21 + +class XdbSearcher +{ + const HeaderInfoLength = 256; + const VectorIndexRows = 256; + const VectorIndexCols = 256; + const VectorIndexSize = 8; + const SegmentIndexSize = 14; + + // xdb file handle + private $handle = null; + + // header info + private $header = null; + private $ioCount = 0; + + // vector index in binary string. + // string decode will be faster than the map based Array. + private $vectorIndex = null; + + // xdb content buffer + private $contentBuff = null; + + // --- + // static function to create searcher + + /** + * @throws Exception + */ + public static function newWithFileOnly($dbFile) + { + return new XdbSearcher($dbFile, null, null); + } + + /** + * @throws Exception + */ + public static function newWithVectorIndex($dbFile, $vIndex) + { + return new XdbSearcher($dbFile, $vIndex); + } + + /** + * @throws Exception + */ + public static function newWithBuffer($cBuff) + { + return new XdbSearcher(null, null, $cBuff); + } + + // --- End of static creator + + /** + * initialize the xdb searcher + * @throws Exception + */ + function __construct($dbFile = null, $vectorIndex = null, $cBuff = null) + { + // check the content buffer first + if ($cBuff != null) { + $this->vectorIndex = null; + $this->contentBuff = $cBuff; + } else { + // 加载默认数据文件 by Anyon + if (is_null($dbFile)) { + $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'ip2region.xdb'; + } + // open the xdb binary file + $this->handle = fopen($dbFile, "r"); + if ($this->handle === false) { + throw new Exception("failed to open xdb file '%s'", $dbFile); + } + + $this->vectorIndex = $vectorIndex; + } + } + + function close() + { + if ($this->handle != null) { + fclose($this->handle); + } + } + + function getIOCount() + { + return $this->ioCount; + } + + /** + * find the region info for the specified ip address + * @throws Exception + */ + function search($ip) + { + // check and convert the sting ip to a 4-bytes long + if (is_string($ip)) { + $t = self::ip2long($ip); + if ($t === null) { + throw new Exception("invalid ip address `$ip`"); + } + $ip = $t; + } + + // reset the global counter + $this->ioCount = 0; + + // locate the segment index block based on the vector index + $il0 = ($ip >> 24) & 0xFF; + $il1 = ($ip >> 16) & 0xFF; + $idx = $il0 * self::VectorIndexCols * self::VectorIndexSize + $il1 * self::VectorIndexSize; + if ($this->vectorIndex != null) { + $sPtr = self::getLong($this->vectorIndex, $idx); + $ePtr = self::getLong($this->vectorIndex, $idx + 4); + } elseif ($this->contentBuff != null) { + $sPtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx); + $ePtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx + 4); + } else { + // read the vector index block + $buff = $this->read(self::HeaderInfoLength + $idx, 8); + if ($buff === null) { + throw new Exception("failed to read vector index at {$idx}"); + } + + $sPtr = self::getLong($buff, 0); + $ePtr = self::getLong($buff, 4); + } + + // printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr); + + // binary search the segment index to get the region info + $dataLen = 0; + $dataPtr = null; + $l = 0; + $h = ($ePtr - $sPtr) / self::SegmentIndexSize; + while ($l <= $h) { + $m = ($l + $h) >> 1; + $p = $sPtr + $m * self::SegmentIndexSize; + + // read the segment index + $buff = $this->read($p, self::SegmentIndexSize); + if ($buff == null) { + throw new Exception("failed to read segment index at {$p}"); + } + + $sip = self::getLong($buff, 0); + if ($ip < $sip) { + $h = $m - 1; + } else { + $eip = self::getLong($buff, 4); + if ($ip > $eip) { + $l = $m + 1; + } else { + $dataLen = self::getShort($buff, 8); + $dataPtr = self::getLong($buff, 10); + break; + } + } + } + + // match nothing interception. + // @TODO: could this even be a case ? + // printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr); + if ($dataPtr == null) { + return null; + } + + // load and return the region data + $buff = $this->read($dataPtr, $dataLen); + if ($buff == null) { + return null; + } + + return $buff; + } + + // read specified bytes from the specified index + private function read($offset, $len) + { + // check the in-memory buffer first + if ($this->contentBuff != null) { + return substr($this->contentBuff, $offset, $len); + } + + // read from the file + $r = fseek($this->handle, $offset); + if ($r == -1) { + return null; + } + + $this->ioCount++; + $buff = fread($this->handle, $len); + if ($buff === false) { + return null; + } + + if (strlen($buff) != $len) { + return null; + } + + return $buff; + } + + // --- static util functions ---- + + // convert a string ip to long + public static function ip2long($ip) + { + $ip = ip2long($ip); + if ($ip === false) { + return null; + } + + // convert signed int to unsigned int if on 32 bit operating system + if ($ip < 0 && PHP_INT_SIZE == 4) { + $ip = sprintf("%u", $ip); + } + + return $ip; + } + + // read a 4bytes long from a byte buffer + public static function getLong($b, $idx) + { + $val = (ord($b[$idx])) | (ord($b[$idx + 1]) << 8) + | (ord($b[$idx + 2]) << 16) | (ord($b[$idx + 3]) << 24); + + // convert signed int to unsigned int if on 32 bit operating system + if ($val < 0 && PHP_INT_SIZE == 4) { + $val = sprintf("%u", $val); + } + + return $val; + } + + // read a 2bytes short from a byte buffer + public static function getShort($b, $idx) + { + return ((ord($b[$idx])) | (ord($b[$idx + 1]) << 8)); + } + + // load header info from a specified file handle + public static function loadHeader($handle) + { + if (fseek($handle, 0) == -1) { + return null; + } + + $buff = fread($handle, self::HeaderInfoLength); + if ($buff === false) { + return null; + } + + // read bytes length checking + if (strlen($buff) != self::HeaderInfoLength) { + return null; + } + + // return the decoded header info + return [ + 'version' => self::getShort($buff, 0), + 'indexPolicy' => self::getShort($buff, 2), + 'createdAt' => self::getLong($buff, 4), + 'startIndexPtr' => self::getLong($buff, 8), + 'endIndexPtr' => self::getLong($buff, 12) + ]; + } + + // load header info from the specified xdb file path + public static function loadHeaderFromFile($dbFile) + { + $handle = fopen($dbFile, 'r'); + if ($handle === false) { + return null; + } + + $header = self::loadHeader($handle); + fclose($handle); + return $header; + } + + // load vector index from a file handle + public static function loadVectorIndex($handle) + { + if (fseek($handle, self::HeaderInfoLength) == -1) { + return null; + } + + $rLen = self::VectorIndexRows * self::VectorIndexCols * self::SegmentIndexSize; + $buff = fread($handle, $rLen); + if ($buff === false) { + return null; + } + + if (strlen($buff) != $rLen) { + return null; + } + + return $buff; + } + + // load vector index from a specified xdb file path + public static function loadVectorIndexFromFile($dbFile) + { + $handle = fopen($dbFile, 'r'); + if ($handle === false) { + return null; + } + + $vIndex = self::loadVectorIndex($handle); + fclose($handle); + return $vIndex; + } + + // load the xdb content from a file handle + public static function loadContent($handle) + { + if (fseek($handle, 0, SEEK_END) == -1) { + return null; + } + + $size = ftell($handle); + if ($size === false) { + return null; + } + + // seek to the head for reading + if (fseek($handle, 0) == -1) { + return null; + } + + $buff = fread($handle, $size); + if ($buff === false) { + return null; + } + + // read length checking + if (strlen($buff) != $size) { + return null; + } + + return $buff; + } + + // load the xdb content from a file path + public static function loadContentFromFile($dbFile) + { + $str = file_get_contents($dbFile, false); + if ($str === false) { + return null; + } else { + return $str; + } + } + + public static function now() + { + return (microtime(true) * 1000); + } + +} \ No newline at end of file diff --git a/resources/xdb/ip2region.xdb b/resources/xdb/ip2region.xdb new file mode 100644 index 0000000..c78b792 Binary files /dev/null and b/resources/xdb/ip2region.xdb differ