504 lines
12 KiB
PHP
504 lines
12 KiB
PHP
|
<?php
|
||
|
|
||
|
use WHMCS\Authentication\CurrentUser;
|
||
|
|
||
|
function updateClientInfo()
|
||
|
{
|
||
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||
|
|
||
|
$ip2region = new Ip2Region();
|
||
|
|
||
|
$geo = $ip2region->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<zoujingli@qq.com>
|
||
|
* @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 <chenxin619315@gmail.com>
|
||
|
// @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);
|
||
|
}
|
||
|
|
||
|
}
|