public function before(RequestInterface $request)
	{
		if ($request->isCLI())
		{
			return;
		}

		$security = Services::security();

		try
		{
			$security->CSRFVerify($request);
		}
		catch (SecurityException $e)
		{
			if (config('App')->CSRFRedirect && ! $request->isAJAX())
			{
				return redirect()->back()->with('error', $e->getMessage());
			}

			throw $e;
		}
	}

public function before ( RequestInterface $ request ) { if ( $ request -> isCLI ( ) ) { return ; } $ security = Services :: security ( ) ; try { $ security -> CSRFVerify ( $ request ) ; } catch ( SecurityException $ e ) { if ( config ( 'App' ) -> CSRFRedirect && ! $ request -> isAJAX ( ) ) { return redirect ( ) -> back ( ) -> with ( 'error' , $ e -> getMessage ( ) ) ; } throw $ e ; } }

Do whatever processing this filter needs to do.
By default it should not return anything during
normal execution. However, when an abnormal state
is found, it should return an instance of
CodeIgniter\HTTP\Response. If it does, script
execution will end and that Response will be
sent back to the client, allowing for error pages,
redirects, etc.

@param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request

@return mixed

Do whatever processing this filter needs to do . By default it should not return anything during normal execution . However when an abnormal state is found it should return an instance of CodeIgniter \ HTTP \ Response . If it does script execution will end and that Response will be sent back to the client allowing for error pages redirects etc .


public static function initialize()
	{
		// Don't overwrite anything....
		if (static::$initialized)
		{
			return;
		}

		$config = config('Modules');

		$files = [APPPATH . 'Config/Events.php'];

		if ($config->shouldDiscover('events'))
		{
			$locator = Services::locator();
			$files   = $locator->search('Config/Events.php');
		}

		static::$files = $files;

		foreach (static::$files as $file)
		{
			if (is_file($file))
			{
				include $file;
			}
		}

		static::$initialized = true;
	}

public
static
function
initialize
(
)
{
// Don't overwrite anything....
if
(
static
::
$
initialized
)
{
return
;
}
$
config
=
config
(
'Modules'
)
;
$
files
=
[
APPPATH
.
'Config/Events.php'
]
;
if
(
$
config
->
shouldDiscover
(
'events'
)
)
{
$
locator
=
Services
::
locator
(
)
;
$
files
=
$
locator
->
search
(
'Config/Events.php'
)
;
}
static
::
$
files
=
$
files
;
foreach
(
static
::
$
files
as
$
file
)
{
if
(
is_file
(
$
file
)
)
{
include
$
file
;
}
}
static
::
$
initialized
=
true
;
}

Ensures that we have a events file ready.

Ensures that we have a events file ready .


public static function listeners($event_name): array
	{
		if (! isset(static::$listeners[$event_name]))
		{
			return [];
		}

		// The list is not sorted
		if (! static::$listeners[$event_name][0])
		{
			// Sort it!
			array_multisort(static::$listeners[$event_name][1], SORT_NUMERIC, static::$listeners[$event_name][2]);

			// Mark it as sorted already!
			static::$listeners[$event_name][0] = true;
		}

		return static::$listeners[$event_name][2];
	}

public
static
function
listeners
(
$
event_name
)
:
array
{
if
(
!
isset
(
static
::
$
listeners
[
$
event_name
]
)
)
{
return
[
]
;
}
// The list is not sorted
if
(
!
static
::
$
listeners
[
$
event_name
]
[
0
]
)
{
// Sort it!
array_multisort
(
static
::
$
listeners
[
$
event_name
]
[
1
]
,
SORT_NUMERIC
,
static
::
$
listeners
[
$
event_name
]
[
2
]
)
;
// Mark it as sorted already!
static
::
$
listeners
[
$
event_name
]
[
0
]
=
true
;
}
return
static
::
$
listeners
[
$
event_name
]
[
2
]
;
}

Returns an array of listeners for a single event. They are
sorted by priority.

If the listener could not be found, returns FALSE, or TRUE if
it was removed.

@param $event_name

@return array

Returns an array of listeners for a single event . They are sorted by priority .


public static function removeListener($event_name, callable $listener): bool
	{
		if (! isset(static::$listeners[$event_name]))
		{
			return false;
		}

		foreach (static::$listeners[$event_name][2] as $index => $check)
		{
			if ($check === $listener)
			{
				unset(static::$listeners[$event_name][1][$index]);
				unset(static::$listeners[$event_name][2][$index]);

				return true;
			}
		}

		return false;
	}

public static function removeListener ( $ event_name , callable $ listener ) : bool { if ( ! isset ( static :: $ listeners [ $ event_name ] ) ) { return false ; } foreach ( static :: $ listeners [ $ event_name ] [ 2 ] as $ index => $ check ) { if ( $ check === $ listener ) { unset ( static :: $ listeners [ $ event_name ] [ 1 ] [ $ index ] ) ; unset ( static :: $ listeners [ $ event_name ] [ 2 ] [ $ index ] ) ; return true ; } } return false ; }

Removes a single listener from an event.

If the listener couldn't be found, returns FALSE, else TRUE if
it was removed.

@param $event_name
@param callable   $listener

@return boolean

Removes a single listener from an event .


public function isReferral(): bool
	{
		if (! isset($this->referrer))
		{
			if (empty($_SERVER['HTTP_REFERER']))
			{
				$this->referrer = false;
			}
			else
			{
				$referer_host = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
				$own_host     = parse_url(\base_url(), PHP_URL_HOST);

				$this->referrer = ($referer_host && $referer_host !== $own_host);
			}
		}

		return $this->referrer;
	}

public function isReferral ( ) : bool { if ( ! isset ( $ this -> referrer ) ) { if ( empty ( $ _SERVER [ 'HTTP_REFERER' ] ) ) { $ this -> referrer = false ; } else { $ referer_host = @ parse_url ( $ _SERVER [ 'HTTP_REFERER' ] , PHP_URL_HOST ) ; $ own_host = parse_url ( \ base_url ( ) , PHP_URL_HOST ) ; $ this -> referrer = ( $ referer_host && $ referer_host !== $ own_host ) ; } } return $ this -> referrer ; }

Is this a referral from another site?

@return boolean


protected function setPlatform(): bool
	{
		if (is_array($this->config->platforms) && $this->config->platforms)
		{
			foreach ($this->config->platforms as $key => $val)
			{
				if (preg_match('|' . preg_quote($key) . '|i', $this->agent))
				{
					$this->platform = $val;

					return true;
				}
			}
		}

		$this->platform = 'Unknown Platform';

		return false;
	}

protected
function
setPlatform
(
)
:
bool
{
if
(
is_array
(
$
this
->
config
->
platforms
)
&&
$
this
->
config
->
platforms
)
{
foreach
(
$
this
->
config
->
platforms
as
$
key
=>
$
val
)
{
if
(
preg_match
(
'|'
.
preg_quote
(
$
key
)
.
'|i'
,
$
this
->
agent
)
)
{
$
this
->
platform
=
$
val
;
return
true
;
}
}
}
$
this
->
platform
=
'Unknown Platform'
;
return
false
;
}


protected function setBrowser(): bool
	{
		if (is_array($this->config->browsers) && $this->config->browsers)
		{
			foreach ($this->config->browsers as $key => $val)
			{
				if (preg_match('|' . $key . '.*?([0-9\.]+)|i', $this->agent, $match))
				{
					$this->isBrowser = true;
					$this->version   = $match[1];
					$this->browser   = $val;
					$this->setMobile();

					return true;
				}
			}
		}

		return false;
	}

protected function setBrowser ( ) : bool { if ( is_array ( $ this -> config -> browsers ) && $ this -> config -> browsers ) { foreach ( $ this -> config -> browsers as $ key => $ val ) { if ( preg_match ( '|' . $ key . '.*?([0-9\.]+)|i' , $ this -> agent , $ match ) ) { $ this -> isBrowser = true ; $ this -> version = $ match [ 1 ] ; $ this -> browser = $ val ; $ this -> setMobile ( ) ; return true ; } } } return false ; }


protected function setRobot(): bool
	{
		if (is_array($this->config->robots) && $this->config->robots)
		{
			foreach ($this->config->robots as $key => $val)
			{
				if (preg_match('|' . preg_quote($key) . '|i', $this->agent))
				{
					$this->isRobot = true;
					$this->robot   = $val;
					$this->setMobile();

					return true;
				}
			}
		}

		return false;
	}

protected function setRobot ( ) : bool { if ( is_array ( $ this -> config -> robots ) && $ this -> config -> robots ) { foreach ( $ this -> config -> robots as $ key => $ val ) { if ( preg_match ( '|' . preg_quote ( $ key ) . '|i' , $ this -> agent ) ) { $ this -> isRobot = true ; $ this -> robot = $ val ; $ this -> setMobile ( ) ; return true ; } } } return false ; }


protected function setMobile(): bool
	{
		if (is_array($this->config->mobiles) && $this->config->mobiles)
		{
			foreach ($this->config->mobiles as $key => $val)
			{
				if (false !== (stripos($this->agent, $key)))
				{
					$this->isMobile = true;
					$this->mobile   = $val;

					return true;
				}
			}
		}

		return false;
	}

protected function setMobile ( ) : bool { if ( is_array ( $ this -> config -> mobiles ) && $ this -> config -> mobiles ) { foreach ( $ this -> config -> mobiles as $ key => $ val ) { if ( false !== ( stripos ( $ this -> agent , $ key ) ) ) { $ this -> isMobile = true ; $ this -> mobile = $ val ; return true ; } } } return false ; }


protected function _attributeType(array &$attributes)
	{
		// Reset field lengths for data types that don't support it
		if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false)
		{
			$attributes['CONSTRAINT'] = null;
		}

		switch (strtoupper($attributes['TYPE']))
		{
			case 'TINYINT':
				$attributes['TYPE']     = 'SMALLINT';
				$attributes['UNSIGNED'] = false;
				break;
			case 'MEDIUMINT':
				$attributes['TYPE']     = 'INTEGER';
				$attributes['UNSIGNED'] = false;
				break;
			case 'DATETIME':
				$attributes['TYPE'] = 'TIMESTAMP';
				break;
			default:
				break;
		}
	}

Field attribute TYPE

Performs a data type mapping between different databases.

@param array &$attributes

@return void


private function initializeConfig()
    {
        if (!is_dir($this->vbot->config['path'])) {
            mkdir($this->vbot->config['path'], 0755, true);
        }

        $this->vbot->config['storage'] = $this->vbot->config['storage'] ?: 'collection';

        $this->vbot->config['path'] = realpath($this->vbot->config['path']);
    }

private function initializeConfig ( ) { if ( ! is_dir ( $ this -> vbot -> config [ 'path' ] ) ) { mkdir ( $ this -> vbot -> config [ 'path' ] , 0755 , true ) ; } $ this -> vbot -> config [ 'storage' ] = $ this -> vbot -> config [ 'storage' ] ? : 'collection' ; $ this -> vbot -> config [ 'path' ] = realpath ( $ this -> vbot -> config [ 'path' ] ) ; }


public function show($text)
    {
        if (!array_get($this->config, 'qrcode', true)) {
            return false;
        }
        $output = new ConsoleOutput();
        static::initQrcodeStyle($output);

        $pxMap[0] = Console::isWin() ? '<whitec>mm</whitec>' : '<whitec>  </whitec>';
        $pxMap[1] = '<blackc>  </blackc>';

        $text = QrCodeConsole::text($text);

        $length = strlen($text[0]);

        $output->write("\n");
        foreach ($text as $line) {
            $output->write($pxMap[0]);
            for ($i = 0; $i < $length; $i++) {
                $type = substr($line, $i, 1);
                $output->write($pxMap[$type]);
            }
            $output->writeln($pxMap[0]);
        }
    }

public
function
show
(
$
text
)
{
if
(
!
array_get
(
$
this
->
config
,
'qrcode'
,
true
)
)
{
return
false
;
}
$
output
=
new
ConsoleOutput
(
)
;
static
::
initQrcodeStyle
(
$
output
)
;
$
pxMap
[
0
]
=
Console
::
isWin
(
)
'<whitec>mm</whitec>'
:
'<whitec>  </whitec>'
;
$
pxMap
[
1
]
=
'<blackc>  </blackc>'
;
$
text
=
QrCodeConsole
::
text
(
$
text
)
;
$
length
=
strlen
(
$
text
[
0
]
)
;
$
output
->
write
(
"\n"
)
;
foreach
(
$
text
as
$
line
)
{
$
output
->
write
(
$
pxMap
[
0
]
)
;
for
(
$
i
=
0
;
$
i
<
$
length
;
$
i
++
)
{
$
type
=
substr
(
$
line
,
$
i
,
1
)
;
$
output
->
write
(
$
pxMap
[
$
type
]
)
;
}
$
output
->
writeln
(
$
pxMap
[
0
]
)
;
}
}

show qrCode on console.

@param $text

@return bool


private static function initQrcodeStyle(OutputInterface $output)
    {
        $style = new OutputFormatterStyle('black', 'black', ['bold']);
        $output->getFormatter()->setStyle('blackc', $style);
        $style = new OutputFormatterStyle('white', 'white', ['bold']);
        $output->getFormatter()->setStyle('whitec', $style);
    }

private static function initQrcodeStyle ( OutputInterface $ output ) { $ style = new OutputFormatterStyle ( 'black' , 'black' , [ 'bold' ] ) ; $ output -> getFormatter ( ) -> setStyle ( 'blackc' , $ style ) ; $ style = new OutputFormatterStyle ( 'white' , 'white' , [ 'bold' ] ) ; $ output -> getFormatter ( ) -> setStyle ( 'whitec' , $ style ) ; }

init qrCode style.

@param OutputInterface $output


public static function formatContent($content)
    {
        $content = self::emojiHandle($content);
        $content = self::replaceBr($content);

        return self::htmlDecode($content);
    }

public static function formatContent ( $ content ) { $ content = self :: emojiHandle ( $ content ) ; $ content = self :: replaceBr ( $ content ) ; return self :: htmlDecode ( $ content ) ; }

format XML for Content.

@param $content

@return string


private function heartbeat($time)
    {
        if (time() - $time > 1800) {
            Text::send('filehelper', 'heart beat '.Carbon::now()->toDateTimeString());

            return time();
        }

        return $time;
    }

private
function
heartbeat
(
$
time
)
{
if
(
time
(
)
$
time
>
1800
)
{
Text
::
send
(
'filehelper'
,
'heart beat '
.
Carbon
::
now
(
)
->
toDateTimeString
(
)
)
;
return
time
(
)
;
}
return
$
time
;
}

make a heartbeat every 30 minutes.

@param $time

@return int


public function handleCheckSync($retCode, $selector, $test = false)
    {
        if (in_array($retCode, [1100, 1101, 1102, 1205])) { // 微信客户端上登出或者其他设备登录

            $this->vbot->console->log('vbot exit normally.');
            $this->vbot->cache->forget('session.'.$this->vbot->config['session']);

            return false;
        } elseif ($retCode != 0) {
            $this->vbot->needActivateObserver->trigger();
        } else {
            if (!$test) {
                $this->handleMessage($selector);
            }

            return true;
        }
    }

public
function
handleCheckSync
(
$
retCode
,
$
selector
,
$
test
=
false
)
{
if
(
in_array
(
$
retCode
,
[
1100
,
1101
,
1102
,
1205
]
)
)
{
// 微信客户端上登出或者其他设备登录
$
this
->
vbot
->
console
->
log
(
'vbot exit normally.'
)
;
$
this
->
vbot
->
cache
->
forget
(
'session.'
.
$
this
->
vbot
->
config
[
'session'
]
)
;
return
false
;
}
elseif
(
$
retCode
!=
0
)
{
$
this
->
vbot
->
needActivateObserver
->
trigger
(
)
;
}
else
{
if
(
!
$
test
)
{
$
this
->
handleMessage
(
$
selector
)
;
}
return
true
;
}
}

handle a sync from wechat.

@param $retCode
@param $selector
@param bool $test

@return bool


private function log($message)
    {
        if ($this->vbot->messageLog && ($message['ModContactList'] || $message['AddMsgList'])) {
            $this->vbot->messageLog->info(json_encode($message));
        }
    }

private function log ( $ message ) { if ( $ this -> vbot -> messageLog && ( $ message [ 'ModContactList' ] || $ message [ 'AddMsgList' ] ) ) { $ this -> vbot -> messageLog -> info ( json_encode ( $ message ) ) ; } }


protected function getUuid()
    {
        $content = $this->vbot->http->get('https://login.weixin.qq.com/jslogin', ['query' => [
            'appid' => 'wx782c26e4c19acffb',
            'fun'   => 'new',
            'lang'  => 'zh_CN',
            '_'     => time(),
        ]]);

        preg_match('/window.QRLogin.code = (\d+); window.QRLogin.uuid = \"(\S+?)\"/', $content, $matches);

        if (!$matches) {
            throw new FetchUuidException('fetch uuid failed.');
        }

        $this->vbot->config['server.uuid'] = $matches[2];
    }


public function showQrCode()
    {
        $url = 'https://login.weixin.qq.com/l/'.$this->vbot->config['server.uuid'];

        $this->vbot->qrCodeObserver->trigger($url);

        $this->vbot->qrCode->show($url);
    }

public function showQrCode ( ) { $ url = 'https://login.weixin.qq.com/l/' . $ this -> vbot -> config [ 'server.uuid' ] ; $ this -> vbot -> qrCodeObserver -> trigger ( $ url ) ; $ this -> vbot -> qrCode -> show ( $ url ) ; }


protected function waitForLogin()
    {
        $retryTime = 10;
        $tip = 1;

        $this->vbot->console->log('please scan the qrCode with wechat.');
        while ($retryTime > 0) {
            $url = sprintf('https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s', $tip, $this->vbot->config['server.uuid'], time());

            $content = $this->vbot->http->get($url, ['timeout' => 35]);

            preg_match('/window.code=(\d+);/', $content, $matches);

            $code = $matches[1];
            switch ($code) {
                case '201':
                    $this->vbot->console->log('please confirm login in wechat.');
                    $tip = 0;
                    break;
                case '200':
                    preg_match('/window.redirect_uri="(https:\/\/(\S+?)\/\S+?)";/', $content, $matches);

                    $this->vbot->config['server.uri.redirect'] = $matches[1].'&fun=new';
                    $url = 'https://%s/cgi-bin/mmwebwx-bin';
                    $this->vbot->config['server.uri.file'] = sprintf($url, 'file.'.$matches[2]);
                    $this->vbot->config['server.uri.push'] = sprintf($url, 'webpush.'.$matches[2]);
                    $this->vbot->config['server.uri.base'] = sprintf($url, $matches[2]);

                    return;
                case '408':
                    $tip = 1;
                    $retryTime -= 1;
                    sleep(1);
                    break;
                default:
                    $tip = 1;
                    $retryTime -= 1;
                    sleep(1);
                    break;
            }
        }

        $this->vbot->console->log('login time out!', Console::ERROR);

        throw new LoginTimeoutException('Login time out.');
    }

waiting user to login.

@throws \Exception


private function getLogin()
    {
        $content = $this->vbot->http->get($this->vbot->config['server.uri.redirect']);

        $data = (array) simplexml_load_string($content, 'SimpleXMLElement', LIBXML_NOCDATA);

        $this->vbot->config['server.skey'] = $data['skey'];
        $this->vbot->config['server.sid'] = $data['wxsid'];
        $this->vbot->config['server.uin'] = $data['wxuin'];
        $this->vbot->config['server.passTicket'] = $data['pass_ticket'];

        if (in_array('', [$data['wxsid'], $data['wxuin'], $data['pass_ticket']])) {
            throw new LoginFailedException('Login failed.');
        }

        $this->vbot->config['server.deviceId'] = 'e'.substr(mt_rand().mt_rand(), 1, 15);

        $this->vbot->config['server.baseRequest'] = [
            'Uin'      => $data['wxuin'],
            'Sid'      => $data['wxsid'],
            'Skey'     => $data['skey'],
            'DeviceID' => $this->vbot->config['server.deviceId'],
        ];

        $this->saveServer();
    }

private
function
getLogin
(
)
{
$
content
=
$
this
->
vbot
->
http
->
get
(
$
this
->
vbot
->
config
[
'server.uri.redirect'
]
)
;
$
data
=
(
array
)
simplexml_load_string
(
$
content
,
'SimpleXMLElement'
,
LIBXML_NOCDATA
)
;
$
this
->
vbot
->
config
[
'server.skey'
]
=
$
data
[
'skey'
]
;
$
this
->
vbot
->
config
[
'server.sid'
]
=
$
data
[
'wxsid'
]
;
$
this
->
vbot
->
config
[
'server.uin'
]
=
$
data
[
'wxuin'
]
;
$
this
->
vbot
->
config
[
'server.passTicket'
]
=
$
data
[
'pass_ticket'
]
;
if
(
in_array
(
''
,
[
$
data
[
'wxsid'
]
,
$
data
[
'wxuin'
]
,
$
data
[
'pass_ticket'
]
]
)
)
{
throw
new
LoginFailedException
(
'Login failed.'
)
;
}
$
this
->
vbot
->
config
[
'server.deviceId'
]
=
'e'
.
substr
(
mt_rand
(
)
.
mt_rand
(
)
,
1
,
15
)
;
$
this
->
vbot
->
config
[
'server.baseRequest'
]
=
[
'Uin'
=>
$
data
[
'wxuin'
]
,
'Sid'
=>
$
data
[
'wxsid'
]
,
'Skey'
=>
$
data
[
'skey'
]
,
'DeviceID'
=>
$
this
->
vbot
->
config
[
'server.deviceId'
]
,
]
;
$
this
->
saveServer
(
)
;
}


private function saveServer()
    {
        $this->vbot->cache->forever('session.'.$this->vbot->config['session'], json_encode($this->vbot->config['server']));
    }

private function saveServer ( ) { $ this -> vbot -> cache -> forever ( 'session.' . $ this -> vbot -> config [ 'session' ] , json_encode ( $ this -> vbot -> config [ 'server' ] ) ) ; }


private function beforeInitSuccess()
    {
        $this->vbot->console->log('current session: '.$this->vbot->config['session']);
        $this->vbot->console->log('init begin.');
    }

private
function
beforeInitSuccess
(
)
{
$
this
->
vbot
->
console
->
log
(
'current session: '
.
$
this
->
vbot
->
config
[
'session'
]
)
;
$
this
->
vbot
->
console
->
log
(
'init begin.'
)
;
}


private function afterInitSuccess($content)
    {
        $this->vbot->log->info('response:'.json_encode($content));
        $this->vbot->console->log('init success.');
        $this->vbot->loginSuccessObserver->trigger();
        $this->vbot->console->log('init contacts begin.');
    }

private
function
afterInitSuccess
(
$
content
)
{
$
this
->
vbot
->
log
->
info
(
'response:'
.
json_encode
(
$
content
)
)
;
$
this
->
vbot
->
console
->
log
(
'init success.'
)
;
$
this
->
vbot
->
loginSuccessObserver
->
trigger
(
)
;
$
this
->
vbot
->
console
->
log
(
'init contacts begin.'
)
;
}


protected function statusNotify()
    {
        $url = sprintf($this->vbot->config['server.uri.base'].'/webwxstatusnotify?lang=zh_CN&pass_ticket=%s', $this->vbot->config['server.passTicket']);

        $this->vbot->http->json($url, [
            'BaseRequest'  => $this->vbot->config['server.baseRequest'],
            'Code'         => 3,
            'FromUserName' => $this->vbot->myself->username,
            'ToUserName'   => $this->vbot->myself->username,
            'ClientMsgId'  => time(),
        ]);
    }


public static function download($message, $callback = null)
    {
        if (!$callback) {
            static::autoDownload($message['raw'], true);

            return true;
        }

        if ($callback && !is_callable($callback)) {
            throw new ArgumentException();
        }

        call_user_func_array($callback, [static::getResource($message['raw'])]);

        return true;
    }

public static function download ( $ message , $ callback = null ) { if ( ! $ callback ) { static :: autoDownload ( $ message [ 'raw' ] , true ) ; return true ; } if ( $ callback && ! is_callable ( $ callback ) ) { throw new ArgumentException ( ) ; } call_user_func_array ( $ callback , [ static :: getResource ( $ message [ 'raw' ] ) ] ) ; return true ; }

download multimedia.

@param $message
@param null $callback

@throws ArgumentException

@return bool


private static function getResource($message)
    {
        $url = static::getDownloadUrl($message);

        $content = vbot('http')->get($url, static::getDownloadOption($message));

        if (!$content) {
            vbot('console')->log('download file failed.', Console::WARNING);
        } else {
            return $content;
        }
    }

private
static
function
getResource
(
$
message
)
{
$
url
=
static
::
getDownloadUrl
(
$
message
)
;
$
content
=
vbot
(
'http'
)
->
get
(
$
url
,
static
::
getDownloadOption
(
$
message
)
)
;
if
(
!
$
content
)
{
vbot
(
'console'
)
->
log
(
'download file failed.'
,
Console
::
WARNING
)
;
}
else
{
return
$
content
;
}
}

get a resource through api.

@param $message

@return mixed


protected static function autoDownload($message, $force = false)
    {
        $isDownload = vbot('config')['download.'.static::TYPE];

        if ($isDownload || $force) {
            $resource = static::getResource($message);

            if ($resource) {
                File::saveTo(vbot('config')['user_path'].static::TYPE.DIRECTORY_SEPARATOR.
                    static::fileName($message), $resource);
            }
        }
    }

protected static function autoDownload ( $ message , $ force = false ) { $ isDownload = vbot ( 'config' ) [ 'download.' . static :: TYPE ] ; if ( $ isDownload || $ force ) { $ resource = static :: getResource ( $ message ) ; if ( $ resource ) { File :: saveTo ( vbot ( 'config' ) [ 'user_path' ] . static :: TYPE . DIRECTORY_SEPARATOR . static :: fileName ( $ message ) , $ resource ) ; } } }

download resource to a default path.

@param $message
@param bool $force


public function checkSync()
    {
        $content = $this->vbot->http->get($this->vbot->config['server.uri.push'].'/synccheck', ['timeout' => 35, 'query' => [
            'r'        => time(),
            'sid'      => $this->vbot->config['server.sid'],
            'uin'      => $this->vbot->config['server.uin'],
            'skey'     => $this->vbot->config['server.skey'],
            'deviceid' => $this->vbot->config['server.deviceId'],
            'synckey'  => $this->vbot->config['server.syncKeyStr'],
            '_'        => time(),
        ]]);

        if (!$content) {
            $this->vbot->console->log('checkSync no response');

            return false;
        }

        return preg_match('/window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}/', $content, $matches) ?
            [$matches[1], $matches[2]] : false;
    }

check if got a new message.

@return array|bool


public function sync()
    {
        $url = sprintf($this->vbot->config['server.uri.base'].'/webwxsync?sid=%s&skey=%s&lang=zh_CN&pass_ticket=%s',
            $this->vbot->config['server.sid'],
            $this->vbot->config['server.skey'],
            $this->vbot->config['server.passTicket']
        );

        $result = $this->vbot->http->json($url, [
            'BaseRequest' => $this->vbot->config['server.baseRequest'],
            'SyncKey'     => $this->vbot->config['server.syncKey'],
            'rr'          => ~time(),
        ], true);

        if ($result && $result['BaseResponse']['Ret'] == 0) {
            $this->generateSyncKey($result);
        }

        return $result;
    }

get a message.

@throws WebSyncException

@return mixed|string


public function generateSyncKey($result)
    {
        $this->vbot->config['server.syncKey'] = $result['SyncKey'];

        $syncKey = [];

        if (is_array($this->vbot->config['server.syncKey.List'])) {
            foreach ($this->vbot->config['server.syncKey.List'] as $item) {
                $syncKey[] = $item['Key'].'_'.$item['Val'];
            }
        }

        $this->vbot->config['server.syncKeyStr'] = implode('|', $syncKey);
    }

public function generateSyncKey ( $ result ) { $ this -> vbot -> config [ 'server.syncKey' ] = $ result [ 'SyncKey' ] ; $ syncKey = [ ] ; if ( is_array ( $ this -> vbot -> config [ 'server.syncKey.List' ] ) ) { foreach ( $ this -> vbot -> config [ 'server.syncKey.List' ] as $ item ) { $ syncKey [ ] = $ item [ 'Key' ] . '_' . $ item [ 'Val' ] ; } } $ this -> vbot -> config [ 'server.syncKeyStr' ] = implode ( '|' , $ syncKey ) ; }


public function log($str, $level = 'INFO', $log = false)
    {
        if ($this->isOutput()) {
            if ($log && in_array($level, array_keys(Logger::getLevels()))) {
                $this->vbot->log->log($level, $str);
            }
            echo '['.Carbon::now()->toDateTimeString().']'."[{$level}] ".$str.PHP_EOL;
        }
    }

public
function
log
(
$
str
,
$
level
=
'INFO'
,
$
log
=
false
)
{
if
(
$
this
->
isOutput
(
)
)
{
if
(
$
log
&&
in_array
(
$
level
,
array_keys
(
Logger
::
getLevels
(
)
)
)
)
{
$
this
->
vbot
->
log
->
log
(
$
level
,
$
str
)
;
}
echo
'['
.
Carbon
::
now
(
)
->
toDateTimeString
(
)
.
']'
.
"[{$level}] "
.
$
str
.
PHP_EOL
;
}
}

print in terminal.

@param $str
@param string $level
@param bool   $log


public function message($str)
    {
        if (array_get($this->config, 'message', true)) {
            $this->log($str, self::MESSAGE);
        }
    }

public function message ( $ str ) { if ( array_get ( $ this -> config , 'message' , true ) ) { $ this -> log ( $ str , self :: MESSAGE ) ; } }


public static function send($username, $word)
    {
        if (!$word || !$username) {
            return false;
        }

        return static::sendMsg([
            'Type'         => 1,
            'Content'      => $word,
            'FromUserName' => vbot('myself')->username,
            'ToUserName'   => $username,
            'LocalID'      => time() * 1e4,
            'ClientMsgId'  => time() * 1e4,
        ]);
    }

public static function send ( $ username , $ word ) { if ( ! $ word || ! $ username ) { return false ; } return static :: sendMsg ( [ 'Type' => 1 , 'Content' => $ word , 'FromUserName' => vbot ( 'myself' ) -> username , 'ToUserName' => $ username , 'LocalID' => time ( ) * 1e4 , 'ClientMsgId' => time ( ) * 1e4 , ] ) ; }

send a text message.

@param $word string
@param $username string

@return bool|mixed


public function fetchAllContacts($seq = 0)
    {
        $url = sprintf($this->vbot->config['server.uri.base'].'/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s&seq=%s',
            $this->vbot->config['server.passTicket'],
            $this->vbot->config['server.skey'],
            time(),
            $seq
        );

        $result = $this->vbot->http->json($url, [], true, ['timeout' => 60]);

        if (isset($result['MemberList']) && $result['MemberList']) {
            $this->store($result['MemberList']);
        }

        if (isset($result['Seq']) && $result['Seq'] != 0) {
            $this->fetchAllContacts($result['Seq']);
        }
    }

fetch all contacts through api.

@param $seq


public function store($memberList)
    {
        foreach ($memberList as $contact) {
            if (in_array($contact['UserName'], static::SPECIAL_USERS)) {
                $this->vbot->specials->put($contact['UserName'], $contact);
            } elseif ($this->vbot->officials->isOfficial($contact['VerifyFlag'])) {
                $this->vbot->officials->put($contact['UserName'], $contact);
            } elseif (strstr($contact['UserName'], '@@') !== false) {
                $this->vbot->groups->put($contact['UserName'], $contact);
            } else {
                $this->vbot->friends->put($contact['UserName'], $contact);
            }
        }
    }

public function store ( $ memberList ) { foreach ( $ memberList as $ contact ) { if ( in_array ( $ contact [ 'UserName' ] , static :: SPECIAL_USERS ) ) { $ this -> vbot -> specials -> put ( $ contact [ 'UserName' ] , $ contact ) ; } elseif ( $ this -> vbot -> officials -> isOfficial ( $ contact [ 'VerifyFlag' ] ) ) { $ this -> vbot -> officials -> put ( $ contact [ 'UserName' ] , $ contact ) ; } elseif ( strstr ( $ contact [ 'UserName' ] , '@@' ) !== false ) { $ this -> vbot -> groups -> put ( $ contact [ 'UserName' ] , $ contact ) ; } else { $ this -> vbot -> friends -> put ( $ contact [ 'UserName' ] , $ contact ) ; } } }

create and save contacts to collections.

@param $memberList

create and save contacts to collections .


public function fetchGroupMembers()
    {
        $url = sprintf($this->vbot->config['server.uri.base'].'/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s',
            time(), $this->vbot->config['server.passTicket']
        );

        $list = [];
        $this->vbot->groups->each(function ($item, $key) use (&$list) {
            $list[] = ['UserName' => $key, 'EncryChatRoomId' => ''];
        });

        $content = $this->vbot->http->json($url, [
            'BaseRequest' => $this->vbot->config['server.baseRequest'],
            'Count'       => $this->vbot->groups->count(),
            'List'        => $list,
        ], true, ['timeout' => 60]);

        $this->storeMembers($content);
    }


private function storeMembers($array)
    {
        if (isset($array['ContactList']) && $array['ContactList']) {
            foreach ($array['ContactList'] as $group) {
                $groupAccount = $this->vbot->groups->get($group['UserName']);
                $groupAccount['MemberList'] = $group['MemberList'];
                $groupAccount['ChatRoomId'] = $group['EncryChatRoomId'];
                $this->vbot->groups->put($group['UserName'], $groupAccount);
                foreach ($group['MemberList'] as $member) {
                    $this->vbot->members->put($member['UserName'], $member);
                }
            }
        }
    }

private function storeMembers ( $ array ) { if ( isset ( $ array [ 'ContactList' ] ) && $ array [ 'ContactList' ] ) { foreach ( $ array [ 'ContactList' ] as $ group ) { $ groupAccount = $ this -> vbot -> groups -> get ( $ group [ 'UserName' ] ) ; $ groupAccount [ 'MemberList' ] = $ group [ 'MemberList' ] ; $ groupAccount [ 'ChatRoomId' ] = $ group [ 'EncryChatRoomId' ] ; $ this -> vbot -> groups -> put ( $ group [ 'UserName' ] , $ groupAccount ) ; foreach ( $ group [ 'MemberList' ] as $ member ) { $ this -> vbot -> members -> put ( $ member [ 'UserName' ] , $ member ) ; } } } }


public function report(Exception $e)
    {
        if ($this->shouldntReport($e)) {
            return true;
        }

        if ($this->handler) {
            call_user_func_array($this->handler, [$e]);
        }
    }

public function report ( Exception $ e ) { if ( $ this -> shouldntReport ( $ e ) ) { return true ; } if ( $ this -> handler ) { call_user_func_array ( $ this -> handler , [ $ e ] ) ; } }

report while exception.

@param Exception $e

@throws Exception

@return bool


private function throwFatalException(Throwable $e)
    {
        foreach ($this->fatalException as $exception) {
            if ($e instanceof $exception) {
                throw $e;
            }
        }
    }

private function throwFatalException ( Throwable $ e ) { foreach ( $ this -> fatalException as $ exception ) { if ( $ e instanceof $ exception ) { throw $ e ; } } }

Exception that make vbot couldn 't work.

@param Throwable $e

@throws Throwable

Exception that make vbot couldn t work .


private function validateKey($key): void
    {
        if (! is_resource($key)) {
            throw new InvalidArgumentException(
                'It was not possible to parse your key, reason: ' . openssl_error_string()
            );
        }

        $details = openssl_pkey_get_details($key);
        assert(is_array($details));

        if (! isset($details['key']) || $details['type'] !== $this->getKeyType()) {
            throw new InvalidArgumentException('This key is not compatible with this signer');
        }
    }

Raises an exception when the key type is not the expected type

@param resource|bool $key

@throws InvalidArgumentException


private function splitJwt(string $jwt): array
    {
        $data = explode('.', $jwt);

        if (count($data) !== 3) {
            throw new InvalidArgumentException('The JWT string must have two dots');
        }

        return $data;
    }

private
function
splitJwt
(
string
$
jwt
)
:
array
{
$
data
=
explode
(
'.'
,
$
jwt
)
;
if
(
count
(
$
data
)
!==
3
)
{
throw
new
InvalidArgumentException
(
'The JWT string must have two dots'
)
;
}
return
$
data
;
}

Splits the JWT string into an array

@return string[]

@throws InvalidArgumentException When JWT doesn't have all parts.


private function parseHeader(string $data): array
    {
        $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));

        if (! is_array($header)) {
            throw new InvalidArgumentException('Headers must be an array');
        }

        if (isset($header['enc'])) {
            throw new InvalidArgumentException('Encryption is not supported yet');
        }

        if (! isset($header['typ'])) {
            throw new InvalidArgumentException('The header "typ" must be present');
        }

        return $header;
    }

private
function
parseHeader
(
string
$
data
)
:
array
{
$
header
=
$
this
->
decoder
->
jsonDecode
(
$
this
->
decoder
->
base64UrlDecode
(
$
data
)
)
;
if
(
!
is_array
(
$
header
)
)
{
throw
new
InvalidArgumentException
(
'Headers must be an array'
)
;
}
if
(
isset
(
$
header
[
'enc'
]
)
)
{
throw
new
InvalidArgumentException
(
'Encryption is not supported yet'
)
;
}
if
(
!
isset
(
$
header
[
'typ'
]
)
)
{
throw
new
InvalidArgumentException
(
'The header "typ" must be present'
)
;
}
return
$
header
;
}

Parses the header from a string

@return mixed[]

@throws InvalidArgumentException When an invalid header is informed.


private function parseClaims(string $data): array
    {
        $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));

        if (! is_array($claims)) {
            throw new InvalidArgumentException('Claims must be an array');
        }

        if (isset($claims[RegisteredClaims::AUDIENCE])) {
            $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE];
        }

        foreach (array_intersect(RegisteredClaims::DATE_CLAIMS, array_keys($claims)) as $claim) {
            $claims[$claim] = $this->convertDate((string) $claims[$claim]);
        }

        return $claims;
    }

private
function
parseClaims
(
string
$
data
)
:
array
{
$
claims
=
$
this
->
decoder
->
jsonDecode
(
$
this
->
decoder
->
base64UrlDecode
(
$
data
)
)
;
if
(
!
is_array
(
$
claims
)
)
{
throw
new
InvalidArgumentException
(
'Claims must be an array'
)
;
}
if
(
isset
(
$
claims
[
RegisteredClaims
::
AUDIENCE
]
)
)
{
$
claims
[
RegisteredClaims
::
AUDIENCE
]
=
(
array
)
$
claims
[
RegisteredClaims
::
AUDIENCE
]
;
}
foreach
(
array_intersect
(
RegisteredClaims
::
DATE_CLAIMS
,
array_keys
(
$
claims
)
)
as
$
claim
)
{
$
claims
[
$
claim
]
=
$
this
->
convertDate
(
(
string
)
$
claims
[
$
claim
]
)
;
}
return
$
claims
;
}

Parses the claim set from a string

@return mixed[]

@throws InvalidArgumentException When an invalid claim set is informed.


private function parseSignature(array $header, string $data): Signature
    {
        if ($data === '' || ! isset($header['alg']) || $header['alg'] === 'none') {
            return Signature::fromEmptyData();
        }

        $hash = $this->decoder->base64UrlDecode($data);

        return new Signature($hash, $data);
    }

private function parseSignature ( array $ header , string $ data ) : Signature { if ( $ data === '' || ! isset ( $ header [ 'alg' ] ) || $ header [ 'alg' ] === 'none' ) { return Signature :: fromEmptyData ( ) ; } $ hash = $ this -> decoder -> base64UrlDecode ( $ data ) ; return new Signature ( $ hash , $ data ) ; }

Returns the signature from given data

@param mixed[] $header


public function negotiateLanguage()
    {
        $matches = $this->getMatchesFromAcceptedLanguages();
        foreach ($matches as $key => $q) {

            $key = ($this->configRepository->get('laravellocalization.localesMapping')[$key]) ?? $key;

            if (!empty($this->supportedLanguages[$key])) {
                return $key;
            }

            if ($this->use_intl) {
                $key = Locale::canonicalize($key);
            }

            // Search for acceptable locale by 'regional' => 'af_ZA' or 'lang' => 'af-ZA' match.
            foreach ( $this->supportedLanguages as $key_supported => $locale ) {
                if ( (isset($locale['regional']) && $locale['regional'] == $key) || (isset($locale['lang']) && $locale['lang'] == $key) ) {
                    return $key_supported;
                }
            }
        }
        // If any (i.e. "*") is acceptable, return the first supported format
        if (isset($matches['*'])) {
            reset($this->supportedLanguages);

            return key($this->supportedLanguages);
        }

        if ($this->use_intl && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            $http_accept_language = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);

            if (!empty($this->supportedLanguages[$http_accept_language])) {
                return $http_accept_language;
            }
        }

        if ($this->request->server('REMOTE_HOST')) {
            $remote_host = explode('.', $this->request->server('REMOTE_HOST'));
            $lang = strtolower(end($remote_host));

            if (!empty($this->supportedLanguages[$lang])) {
                return $lang;
            }
        }

        return $this->defaultLocale;
    }

Negotiates language with the user's browser through the Accept-Language
HTTP header or the user's host address.  Language codes are generally in
the form "ll" for a language spoken in only one country, or "ll-CC" for a
language spoken in a particular country.  For example, U.S. English is
"en-US", while British English is "en-UK".  Portuguese as spoken in
Portugal is "pt-PT", while Brazilian Portuguese is "pt-BR".

This function is based on negotiateLanguage from Pear HTTP2
http://pear.php.net/package/HTTP2/

Quality factors in the Accept-Language: header are supported, e.g.:
Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8

@return string The negotiated language result or app.locale.

Negotiates language with the user s browser through the Accept - Language HTTP header or the user s host address . Language codes are generally in the form ll for a language spoken in only one country or ll - CC for a language spoken in a particular country . For example U . S . English is en - US while British English is en - UK . Portuguese as spoken in Portugal is pt - PT while Brazilian Portuguese is pt - BR .


private function getMatchesFromAcceptedLanguages()
    {
        $matches = [];

        if ($acceptLanguages = $this->request->header('Accept-Language')) {
            $acceptLanguages = explode(',', $acceptLanguages);

            $generic_matches = [];
            foreach ($acceptLanguages as $option) {
                $option = array_map('trim', explode(';', $option));
                $l = $option[0];
                if (isset($option[1])) {
                    $q = (float) str_replace('q=', '', $option[1]);
                } else {
                    $q = null;
                    // Assign default low weight for generic values
                    if ($l == '*/*') {
                        $q = 0.01;
                    } elseif (substr($l, -1) == '*') {
                        $q = 0.02;
                    }
                }
                // Unweighted values, get high weight by their position in the
                // list
                $q = $q ?? 1000 - \count($matches);
                $matches[$l] = $q;

                //If for some reason the Accept-Language header only sends language with country
                //we should make the language without country an accepted option, with a value
                //less than it's parent.
                $l_ops = explode('-', $l);
                array_pop($l_ops);
                while (!empty($l_ops)) {
                    //The new generic option needs to be slightly less important than it's base
                    $q -= 0.001;
                    $op = implode('-', $l_ops);
                    if (empty($generic_matches[$op]) || $generic_matches[$op] > $q) {
                        $generic_matches[$op] = $q;
                    }
                    array_pop($l_ops);
                }
            }
            $matches = array_merge($generic_matches, $matches);

            arsort($matches, SORT_NUMERIC);
        }

        return $matches;
    }

Return all the accepted languages from the browser.

@return array Matches from the header field Accept-Languages

Return all the accepted languages from the browser .


protected function cacheRoutesPerLocale()
    {
        // Store the default routes cache,
        // this way the Application will detect that routes are cached.
        $allLocales = $this->getSupportedLocales();

        array_push($allLocales, null);

        foreach ($allLocales as $locale) {

            $routes = $this->getFreshApplicationRoutes($locale);

            if (count($routes) == 0) {
                $this->error("Your application doesn't have any routes.");
                return;
            }

            foreach ($routes as $route) {
                $route->prepareForSerialization();
            }

            $this->files->put(
                $this->makeLocaleRoutesPath($locale), $this->buildRouteCacheFile($routes)
            );
        }
    }

Cache the routes separately for each locale.

Cache the routes separately for each locale .


protected function buildRouteCacheFile(RouteCollection $routes)
    {
        $stub = $this->files->get(
            realpath(
                __DIR__
                . DIRECTORY_SEPARATOR . '..'
                . DIRECTORY_SEPARATOR . '..'
                . DIRECTORY_SEPARATOR . '..'
                . DIRECTORY_SEPARATOR . 'stubs'
                . DIRECTORY_SEPARATOR . 'routes.stub'
            )
        );

        return str_replace(
            [
                '{{routes}}',
                '{{translatedRoutes}}',
            ],
            [
                base64_encode(serialize($routes)),
                $this->getLaravelLocalization()->getSerializedTranslatedRoutes(),
            ],
            $stub
        );
    }

protected function buildRouteCacheFile ( RouteCollection $ routes ) { $ stub = $ this -> files -> get ( realpath ( __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'routes.stub' ) ) ; return str_replace ( [ '{{routes}}' , '{{translatedRoutes}}' , ] , [ base64_encode ( serialize ( $ routes ) ) , $ this -> getLaravelLocalization ( ) -> getSerializedTranslatedRoutes ( ) , ] , $ stub ) ; }

Build the route cache file.

@param  \Illuminate\Routing\RouteCollection $routes
@return string
@throws \Illuminate\Contracts\Filesystem\FileNotFoundException


protected function registerBindings()
    {
        $this->app->singleton(LaravelLocalization::class, function () {
            return new LaravelLocalization();
        });

        $this->app->alias(LaravelLocalization::class, 'laravellocalization');
    }

protected function registerBindings ( ) { $ this -> app -> singleton ( LaravelLocalization :: class , function ( ) { return new LaravelLocalization ( ) ; } ) ; $ this -> app -> alias ( LaravelLocalization :: class , 'laravellocalization' ) ; }


protected function registerCommands()
    {
        $this->app->singleton('laravellocalizationroutecache.cache', Commands\RouteTranslationsCacheCommand::class);
        $this->app->singleton('laravellocalizationroutecache.clear', Commands\RouteTranslationsClearCommand::class);
        $this->app->singleton('laravellocalizationroutecache.list', Commands\RouteTranslationsListCommand::class);

        $this->commands([
            'laravellocalizationroutecache.cache',
            'laravellocalizationroutecache.clear',
            'laravellocalizationroutecache.list',
        ]);
    }

protected function registerCommands ( ) { $ this -> app -> singleton ( 'laravellocalizationroutecache.cache' , Commands \ RouteTranslationsCacheCommand :: class ) ; $ this -> app -> singleton ( 'laravellocalizationroutecache.clear' , Commands \ RouteTranslationsClearCommand :: class ) ; $ this -> app -> singleton ( 'laravellocalizationroutecache.list' , Commands \ RouteTranslationsListCommand :: class ) ; $ this -> commands ( [ 'laravellocalizationroutecache.cache' , 'laravellocalizationroutecache.clear' , 'laravellocalizationroutecache.list' , ] ) ; }


public function setLocale($locale = null)
    {
        if (empty($locale) || !\is_string($locale)) {
            // If the locale has not been passed through the function
            // it tries to get it from the first segment of the url
            $locale = $this->request->segment(1);

            // If the locale is determined by env, use that
            // Note that this is how per-locale route caching is performed.
            if ( ! $locale) {
                $locale = $this->getForcedLocale();
            }
        }

        if (!empty($this->supportedLocales[$locale])) {
            $this->currentLocale = $locale;
        } else {
            // if the first segment/locale passed is not valid
            // the system would ask which locale have to take
            // it could be taken by the browser
            // depending on your configuration

            $locale = null;

            // if we reached this point and hideDefaultLocaleInURL is true
            // we have to assume we are routing to a defaultLocale route.
            if ($this->hideDefaultLocaleInURL()) {
                $this->currentLocale = $this->defaultLocale;
            }
            // but if hideDefaultLocaleInURL is false, we have
            // to retrieve it from the browser...
            else {
                $this->currentLocale = $this->getCurrentLocale();
            }
        }

        $this->app->setLocale($this->currentLocale);

        // Regional locale such as de_DE, so formatLocalized works in Carbon
        $regional = $this->getCurrentLocaleRegional();
        $suffix = $this->configRepository->get('laravellocalization.utf8suffix');
        if ($regional) {
            setlocale(LC_TIME, $regional . $suffix);
            setlocale(LC_MONETARY, $regional . $suffix);
        }

        return $locale;
    }

Set and return current locale.

@param string $locale Locale to set the App to (optional)

@return string Returns locale (if route has any) or null (if route does not have a locale)


public function getURLFromRouteNameTranslated($locale, $transKeyName, $attributes = [], $forceDefaultLocation = false)
    {
        if (!$this->checkLocaleInSupportedLocales($locale)) {
            throw new UnsupportedLocaleException('Locale \''.$locale.'\' is not in the list of supported locales.');
        }

        if (!\is_string($locale)) {
            $locale = $this->getDefaultLocale();
        }

        $route = '';

        if ($forceDefaultLocation || !($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL())) {
            $route = '/'.$locale;
        }
        if (\is_string($locale) && $this->translator->has($transKeyName, $locale)) {
            $translation = $this->translator->trans($transKeyName, [], $locale);
            $route .= '/'.$translation;

            $route = $this->substituteAttributesInRoute($attributes, $route);
        }

        if (empty($route)) {
            // This locale does not have any key for this route name
            return false;
        }

        return rtrim($this->createUrlFromUri($route), '/');
    }

Returns an URL adapted to the route name and the locale given.

@param string|bool $locale       Locale to adapt
@param string      $transKeyName Translation key name of the url to adapt
@param array       $attributes   Attributes for the route (only needed if transKeyName needs them)
@param bool        $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE

@throws SupportedLocalesNotDefined
@throws UnsupportedLocaleException

@return string|false URL translated

Returns an URL adapted to the route name and the locale given .


public function getSupportedLocales()
    {
        if (!empty($this->supportedLocales)) {
            return $this->supportedLocales;
        }

        $locales = $this->configRepository->get('laravellocalization.supportedLocales');

        if (empty($locales) || !\is_array($locales)) {
            throw new SupportedLocalesNotDefined();
        }

        $this->supportedLocales = $locales;

        return $locales;
    }

public function getSupportedLocales ( ) { if ( ! empty ( $ this -> supportedLocales ) ) { return $ this -> supportedLocales ; } $ locales = $ this -> configRepository -> get ( 'laravellocalization.supportedLocales' ) ; if ( empty ( $ locales ) || ! \ is_array ( $ locales ) ) { throw new SupportedLocalesNotDefined ( ) ; } $ this -> supportedLocales = $ locales ; return $ locales ; }

Return an array of all supported Locales.

@throws SupportedLocalesNotDefined

@return array

Return an array of all supported Locales .


public function getLocalesOrder()
    {
        $locales = $this->getSupportedLocales();

        $order = $this->configRepository->get('laravellocalization.localesOrder');

        uksort($locales, function ($a, $b) use ($order) {
            $pos_a = array_search($a, $order);
            $pos_b = array_search($b, $order);
            return $pos_a - $pos_b;
        });

        return $locales;
    }

public function getLocalesOrder ( ) { $ locales = $ this -> getSupportedLocales ( ) ; $ order = $ this -> configRepository -> get ( 'laravellocalization.localesOrder' ) ; uksort ( $ locales , function ( $ a , $ b ) use ( $ order ) { $ pos_a = array_search ( $ a , $ order ) ; $ pos_b = array_search ( $ b , $ order ) ; return $ pos_a - $ pos_b ; } ) ; return $ locales ; }

Return an array of all supported Locales but in the order the user
has specified in the config file. Useful for the language selector.

@return array

Return an array of all supported Locales but in the order the user has specified in the config file . Useful for the language selector .


public function getCurrentLocaleDirection()
    {
        if (!empty($this->supportedLocales[$this->getCurrentLocale()]['dir'])) {
            return $this->supportedLocales[$this->getCurrentLocale()]['dir'];
        }

        switch ($this->getCurrentLocaleScript()) {
            // Other (historic) RTL scripts exist, but this list contains the only ones in current use.
            case 'Arab':
            case 'Hebr':
            case 'Mong':
            case 'Tfng':
            case 'Thaa':
            return 'rtl';
            default:
            return 'ltr';
        }
    }

Returns current locale direction.

@return string current locale direction


public function getCurrentLocale()
    {
        if ($this->currentLocale) {
            return $this->currentLocale;
        }

        if ($this->useAcceptLanguageHeader() && !$this->app->runningInConsole()) {
            $negotiator = new LanguageNegotiator($this->defaultLocale, $this->getSupportedLocales(), $this->request);

            return $negotiator->negotiateLanguage();
        }

        // or get application default language
        return $this->configRepository->get('app.locale');
    }

public
function
getCurrentLocale
(
)
{
if
(
$
this
->
currentLocale
)
{
return
$
this
->
currentLocale
;
}
if
(
$
this
->
useAcceptLanguageHeader
(
)
&&
!
$
this
->
app
->
runningInConsole
(
)
)
{
$
negotiator
=
new
LanguageNegotiator
(
$
this
->
defaultLocale
,
$
this
->
getSupportedLocales
(
)
,
$
this
->
request
)
;
return
$
negotiator
->
negotiateLanguage
(
)
;
}
// or get application default language
return
$
this
->
configRepository
->
get
(
'app.locale'
)
;
}

Returns current language.

@return string current language


public function getCurrentLocaleRegional()
    {
        // need to check if it exists, since 'regional' has been added
        // after version 1.0.11 and existing users will not have it
        if (isset($this->supportedLocales[$this->getCurrentLocale()]['regional'])) {
            return $this->supportedLocales[$this->getCurrentLocale()]['regional'];
        } else {
            return;
        }
    }

Returns current regional.

@return string current regional


public function checkLocaleInSupportedLocales($locale)
    {
        $locales = $this->getSupportedLocales();
        if ($locale !== false && empty($locales[$locale])) {
            return false;
        }

        return true;
    }

public function checkLocaleInSupportedLocales ( $ locale ) { $ locales = $ this -> getSupportedLocales ( ) ; if ( $ locale !== false && empty ( $ locales [ $ locale ] ) ) { return false ; } return true ; }

Check if Locale exists on the supported locales array.

@param string|bool $locale string|bool Locale to be checked

@throws SupportedLocalesNotDefined

@return bool is the locale supported?

Check if Locale exists on the supported locales array .


public function getRouteNameFromAPath($path)
    {
        $attributes = $this->extractAttributes($path);

        $path = parse_url($path)['path'];
        $path = trim(str_replace('/'.$this->currentLocale.'/', '', $path), "/");

        foreach ($this->translatedRoutes as $route) {
            if (trim($this->substituteAttributesInRoute($attributes, $this->translator->trans($route)), '/') === $path) {
                return $route;
            }
        }

        return false;
    }

public function getRouteNameFromAPath ( $ path ) { $ attributes = $ this -> extractAttributes ( $ path ) ; $ path = parse_url ( $ path ) [ 'path' ] ; $ path = trim ( str_replace ( '/' . $ this -> currentLocale . '/' , '' , $ path ) , "/" ) ; foreach ( $ this -> translatedRoutes as $ route ) { if ( trim ( $ this -> substituteAttributesInRoute ( $ attributes , $ this -> translator -> trans ( $ route ) ) , '/' ) === $ path ) { return $ route ; } } return false ; }

Returns the translation key for a given path.

@param string $path Path to get the key translated

@return string|false Key for translation, false if not exist

Returns the translation key for a given path .


protected function findTranslatedRouteByPath($path, $url_locale)
    {
        // check if this url is a translated url
        foreach ($this->translatedRoutes as $translatedRoute) {
            if ($this->translator->trans($translatedRoute, [], $url_locale) == rawurldecode($path)) {
                return $translatedRoute;
            }
        }

        return false;
    }

Returns the translated route for the path and the url given.

@param string $path       Path to check if it is a translated route
@param string $url_locale Language to check if the path exists

@return string|false Key for translation, false if not exist

Returns the translated route for the path and the url given .


protected function findTranslatedRouteByUrl($url, $attributes, $locale)
    {
        if (empty($url)) {
            return false;
        }

        if (isset($this->cachedTranslatedRoutesByUrl[$locale][$url])) {
            return $this->cachedTranslatedRoutesByUrl[$locale][$url];
        }

        // check if this url is a translated url
        foreach ($this->translatedRoutes as $translatedRoute) {
            $routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes);

            // We can ignore extra url parts and compare only their url_path (ignore arguments that are not attributes)
            if (parse_url($this->getNonLocalizedURL($routeName), PHP_URL_PATH) == parse_url($this->getNonLocalizedURL($url), PHP_URL_PATH)) {
                $this->cachedTranslatedRoutesByUrl[$locale][$url] = $translatedRoute;

                return $translatedRoute;
            }
        }

        return false;
    }

Returns the translated route for an url and the attributes given and a locale.

@param string|false|null $url        Url to check if it is a translated route
@param array             $attributes Attributes to check if the url exists in the translated routes array
@param string            $locale     Language to check if the url exists

@throws SupportedLocalesNotDefined
@throws UnsupportedLocaleException

@return string|false Key for translation, false if not exist

Returns the translated route for an url and the attributes given and a locale .


public function createUrlFromUri($uri)
    {
        $uri = ltrim($uri, '/');

        if (empty($this->baseUrl)) {
            return app('url')->to($uri);
        }

        return $this->baseUrl.$uri;
    }

public function createUrlFromUri ( $ uri ) { $ uri = ltrim ( $ uri , '/' ) ; if ( empty ( $ this -> baseUrl ) ) { return app ( 'url' ) -> to ( $ uri ) ; } return $ this -> baseUrl . $ uri ; }

Create an url from the uri.

@param string $uri Uri

@return string Url for the given uri


protected function normalizeAttributes($attributes)
     {
         if (array_key_exists('data', $attributes) && \is_array($attributes['data']) && ! \count($attributes['data'])) {
             $attributes['data'] = null;
             return $attributes;
         }
         return $attributes;
     }

protected function normalizeAttributes ( $ attributes ) { if ( array_key_exists ( 'data' , $ attributes ) && \ is_array ( $ attributes [ 'data' ] ) && ! \ count ( $ attributes [ 'data' ] ) ) { $ attributes [ 'data' ] = null ; return $ attributes ; } return $ attributes ; }

Normalize attributes gotten from request parameters.

@param      array  $attributes  The attributes
@return     array  The normalized attributes

Normalize attributes gotten from request parameters .


protected function loadCachedRoutes()
    {
        $localization = $this->getLaravelLocalization();

        $localization->setLocale();

        $locale = $localization->getCurrentLocale();

        $localeKeys = $localization->getSupportedLanguagesKeys();

        // First, try to load the routes specifically cached for this locale
        // if they do not exist, write a warning to the log and load the default
        // routes instead. Note that this is guaranteed to exist, because the
        // 'cached routes' check in the Application checks its existence.

        $path = $this->makeLocaleRoutesPath($locale, $localeKeys);

        if ( ! file_exists($path)) {

            Log::warning("Routes cached, but no cached routes found for locale '{$locale}'!");

            $path = $this->getDefaultCachedRoutePath();
        }

        $this->app->booted(function () use ($path) {
            require $path;
        });
    }

Load the cached routes for the application.

@return void

Load the cached routes for the application .


protected function makeLocaleRoutesPath($locale, $localeKeys)
    {
        $path = $this->getDefaultCachedRoutePath();

        $localeSegment = request()->segment(1);
        if ( ! $localeSegment || ! in_array($localeSegment, $localeKeys)) {
            return $path;
        }

        return substr($path, 0, -4) . '_' . $locale . '.php';
    }

protected function makeLocaleRoutesPath ( $ locale , $ localeKeys ) { $ path = $ this -> getDefaultCachedRoutePath ( ) ; $ localeSegment = request ( ) -> segment ( 1 ) ; if ( ! $ localeSegment || ! in_array ( $ localeSegment , $ localeKeys ) ) { return $ path ; } return substr ( $ path , 0 , - 4 ) . '_' . $ locale . '.php' ; }

Returns the path to the cached routes file for a given locale.

@param string   $locale
@param string[] $localeKeys
@return string

Returns the path to the cached routes file for a given locale .


protected function encodeMessageSet(array $messages, int $compression = self::COMPRESSION_NONE): string
    {
        $data = '';
        $next = 0;

        foreach ($messages as $message) {
            $encodedMessage = $this->encodeMessage($message);

            $data .= self::pack(self::BIT_B64, (string) $next)
                   . self::encodeString($encodedMessage, self::PACK_INT32);

            ++$next;
        }

        if ($compression === self::COMPRESSION_NONE) {
            return $data;
        }

        return self::pack(self::BIT_B64, '0')
             . self::encodeString($this->encodeMessage($data, $compression), self::PACK_INT32);
    }

protected function encodeMessageSet ( array $ messages , int $ compression = self :: COMPRESSION_NONE ) : string { $ data = '' ; $ next = 0 ; foreach ( $ messages as $ message ) { $ encodedMessage = $ this -> encodeMessage ( $ message ) ; $ data .= self :: pack ( self :: BIT_B64 , ( string ) $ next ) . self :: encodeString ( $ encodedMessage , self :: PACK_INT32 ) ; ++ $ next ; } if ( $ compression === self :: COMPRESSION_NONE ) { return $ data ; } return self :: pack ( self :: BIT_B64 , '0' ) . self :: encodeString ( $ this -> encodeMessage ( $ data , $ compression ) , self :: PACK_INT32 ) ; }

encode message set
N.B., MessageSets are not preceded by an int32 like other array elements
in the protocol.

@param string[]|string[][] $messages

@throws NotSupported

encode message set N . B . MessageSets are not preceded by an int32 like other array elements in the protocol .


protected function encodeProducePartition(array $values, int $compression): string
    {
        if (! isset($values['partition_id'])) {
            throw new ProtocolException('given produce data invalid. `partition_id` is undefined.');
        }

        if (! isset($values['messages']) || empty($values['messages'])) {
            throw new ProtocolException('given produce data invalid. `messages` is undefined.');
        }

        $data  = self::pack(self::BIT_B32, (string) $values['partition_id']);
        $data .= self::encodeString(
            $this->encodeMessageSet((array) $values['messages'], $compression),
            self::PACK_INT32
        );

        return $data;
    }

encode signal part

@param mixed[] $values

@throws NotSupported
@throws ProtocolException


protected function encodeProduceTopic(array $values, int $compression): string
    {
        if (! isset($values['topic_name'])) {
            throw new ProtocolException('given produce data invalid. `topic_name` is undefined.');
        }

        if (! isset($values['partitions']) || empty($values['partitions'])) {
            throw new ProtocolException('given produce data invalid. `partitions` is undefined.');
        }

        $topic      = self::encodeString($values['topic_name'], self::PACK_INT16);
        $partitions = self::encodeArray($values['partitions'], [$this, 'encodeProducePartition'], $compression);

        return $topic . $partitions;
    }

encode signal topic

@param mixed[] $values

@throws NotSupported
@throws ProtocolException


protected function produceTopicPair(string $data, int $version): array
    {
        $offset    = 0;
        $topicInfo = $this->decodeString($data, self::BIT_B16);
        $offset   += $topicInfo['length'];
        $ret       = $this->decodeArray(substr($data, $offset), [$this, 'producePartitionPair'], $version);
        $offset   += $ret['length'];

        return [
            'length' => $offset,
            'data'   => [
                'topicName'  => $topicInfo['data'],
                'partitions' => $ret['data'],
            ],
        ];
    }

protected function produceTopicPair ( string $ data , int $ version ) : array { $ offset = 0 ; $ topicInfo = $ this -> decodeString ( $ data , self :: BIT_B16 ) ; $ offset += $ topicInfo [ 'length' ] ; $ ret = $ this -> decodeArray ( substr ( $ data , $ offset ) , [ $ this , 'producePartitionPair' ] , $ version ) ; $ offset += $ ret [ 'length' ] ; return [ 'length' => $ offset , 'data' => [ 'topicName' => $ topicInfo [ 'data' ] , 'partitions' => $ ret [ 'data' ] , ] , ] ; }

decode produce topic pair response

@return mixed[]

@throws ProtocolException


protected function producePartitionPair(string $data, int $version): array
    {
        $offset          = 0;
        $partitionId     = self::unpack(self::BIT_B32, substr($data, $offset, 4));
        $offset         += 4;
        $errorCode       = self::unpack(self::BIT_B16_SIGNED, substr($data, $offset, 2));
        $offset         += 2;
        $partitionOffset = self::unpack(self::BIT_B64, substr($data, $offset, 8));
        $offset         += 8;
        $timestamp       = 0;

        if ($version === self::API_VERSION2) {
            $timestamp = self::unpack(self::BIT_B64, substr($data, $offset, 8));
            $offset   += 8;
        }

        return [
            'length' => $offset,
            'data'   => [
                'partition' => $partitionId,
                'errorCode' => $errorCode,
                'offset'    => $offset,
                'timestamp' => $timestamp,
            ],
        ];
    }

protected function producePartitionPair ( string $ data , int $ version ) : array { $ offset = 0 ; $ partitionId = self :: unpack ( self :: BIT_B32 , substr ( $ data , $ offset , 4 ) ) ; $ offset += 4 ; $ errorCode = self :: unpack ( self :: BIT_B16_SIGNED , substr ( $ data , $ offset , 2 ) ) ; $ offset += 2 ; $ partitionOffset = self :: unpack ( self :: BIT_B64 , substr ( $ data , $ offset , 8 ) ) ; $ offset += 8 ; $ timestamp = 0 ; if ( $ version === self :: API_VERSION2 ) { $ timestamp = self :: unpack ( self :: BIT_B64 , substr ( $ data , $ offset , 8 ) ) ; $ offset += 8 ; } return [ 'length' => $ offset , 'data' => [ 'partition' => $ partitionId , 'errorCode' => $ errorCode , 'offset' => $ offset , 'timestamp' => $ timestamp , ] , ] ; }

decode produce partition pair response

@return mixed[]

@throws ProtocolException


protected function decodeMessageSet(string $data): ?array
    {
        if (strlen($data) <= 12) {
            return null;
        }

        $offset      = 0;
        $roffset     = self::unpack(self::BIT_B64, substr($data, $offset, 8));
        $offset     += 8;
        $messageSize = self::unpack(self::BIT_B32, substr($data, $offset, 4));
        $offset     += 4;
        $ret         = $this->decodeMessage(substr($data, $offset), $messageSize);

        if ($ret === null) {
            return null;
        }

        $offset += $ret['length'];

        return [
            'length' => $offset,
            'data'   => [
                'offset'  => $roffset,
                'size'    => $messageSize,
                'message' => $ret['data'],
            ],
        ];
    }

protected function decodeMessageSet ( string $ data ) : ? array { if ( strlen ( $ data ) <= 12 ) { return null ; } $ offset = 0 ; $ roffset = self :: unpack ( self :: BIT_B64 , substr ( $ data , $ offset , 8 ) ) ; $ offset += 8 ; $ messageSize = self :: unpack ( self :: BIT_B32 , substr ( $ data , $ offset , 4 ) ) ; $ offset += 4 ; $ ret = $ this -> decodeMessage ( substr ( $ data , $ offset ) , $ messageSize ) ; if ( $ ret === null ) { return null ; } $ offset += $ ret [ 'length' ] ; return [ 'length' => $ offset , 'data' => [ 'offset' => $ roffset , 'size' => $ messageSize , 'message' => $ ret [ 'data' ] , ] , ] ; }

decode message set
N.B., MessageSets are not preceded by an int32 like other array elements
in the protocol.

@return mixed[]|null

decode message set N . B . MessageSets are not preceded by an int32 like other array elements in the protocol .


protected function decodeMessage(string $data, int $messageSize): ?array
    {
        if ($messageSize === 0 || strlen($data) < $messageSize) {
            return null;
        }

        $offset  = 0;
        $crc     = self::unpack(self::BIT_B32, substr($data, $offset, 4));
        $offset += 4;

        $magic = self::unpack(self::BIT_B8, substr($data, $offset, 1));
        ++$offset;

        $attr = self::unpack(self::BIT_B8, substr($data, $offset, 1));
        ++$offset;

        $timestamp  = 0;
        $backOffset = $offset;

        try { // try unpack message format v1, falling back to v0 if it fails
            if ($magic >= self::MESSAGE_MAGIC_VERSION1) {
                $timestamp = self::unpack(self::BIT_B64, substr($data, $offset, 8));
                $offset   += 8;
            }

            $keyRet  = $this->decodeString(substr($data, $offset), self::BIT_B32);
            $offset += $keyRet['length'];

            $valueRet = $this->decodeString((string) substr($data, $offset), self::BIT_B32, $attr & Produce::COMPRESSION_CODEC_MASK);
            $offset  += $valueRet['length'];

            if ($offset !== $messageSize) {
                throw new Exception(
                    'pack message fail, message len:' . $messageSize . ' , data unpack offset :' . $offset
                );
            }
        } catch (Exception $e) { // try unpack message format v0
            $offset    = $backOffset;
            $timestamp = 0;
            $keyRet    = $this->decodeString(substr($data, $offset), self::BIT_B32);
            $offset   += $keyRet['length'];

            $valueRet = $this->decodeString(substr($data, $offset), self::BIT_B32);
            $offset  += $valueRet['length'];
        }

        return [
            'length' => $offset,
            'data'   => [
                'crc'       => $crc,
                'magic'     => $magic,
                'attr'      => $attr,
                'timestamp' => $timestamp,
                'key'       => $keyRet['data'],
                'value'     => $valueRet['data'],
            ],
        ];
    }

decode message
N.B., MessageSets are not preceded by an int32 like other array elements
in the protocol.

@return mixed[]|null

decode message N . B . MessageSets are not preceded by an int32 like other array elements in the protocol .


protected function createSocket(string $remoteSocket, $context, ?int &$errno, ?string &$errstr)
    {
        return stream_socket_client(
            $remoteSocket,
            $errno,
            $errstr,
            $this->sendTimeoutSec + ($this->sendTimeoutUsec / 1000000),
            STREAM_CLIENT_CONNECT,
            $context
        );
    }

protected function createSocket ( string $ remoteSocket , $ context , ? int & $ errno , ? string & $ errstr ) { return stream_socket_client ( $ remoteSocket , $ errno , $ errstr , $ this -> sendTimeoutSec + ( $ this -> sendTimeoutUsec / 1000000 ) , STREAM_CLIENT_CONNECT , $ context ) ; }

Encapsulation of stream_socket_client

Because `stream_socket_client` in stream wrapper mock no effect, if don't create this function will never be testable

@codeCoverageIgnore

@param resource $context

@return resource


protected function select(array $sockets, int $timeoutSec, int $timeoutUsec, bool $isRead = true)
    {
        $null = null;

        if ($isRead) {
            return @stream_select($sockets, $null, $null, $timeoutSec, $timeoutUsec);
        }

        return @stream_select($null, $sockets, $null, $timeoutSec, $timeoutUsec);
    }

protected function select ( array $ sockets , int $ timeoutSec , int $ timeoutUsec , bool $ isRead = true ) { $ null = null ; if ( $ isRead ) { return @ stream_select ( $ sockets , $ null , $ null , $ timeoutSec , $ timeoutUsec ) ; } return @ stream_select ( $ null , $ sockets , $ null , $ timeoutSec , $ timeoutUsec ) ; }

Encapsulation of stream_select

Because `stream_select` in stream wrapper mock no effect, if don't create this function will never be testable

@codeCoverageIgnore

@param resource[] $sockets

@return int|bool


public static function unpack(string $type, string $bytes)
    {
        self::checkLen($type, $bytes);

        if ($type === self::BIT_B64) {
            $set    = unpack($type, $bytes);
            $result = ($set[1] & 0xFFFFFFFF) << 32 | ($set[2] & 0xFFFFFFFF);
        } elseif ($type === self::BIT_B16_SIGNED) {
            // According to PHP docs: 's' = signed short (always 16 bit, machine byte order)
            // So lets unpack it..
            $set = unpack($type, $bytes);

            // But if our system is little endian
            if (self::isSystemLittleEndian()) {
                // We need to flip the endianess because coming from kafka it is big endian
                $set = self::convertSignedShortFromLittleEndianToBigEndian($set);
            }
            $result = $set;
        } else {
            $result = unpack($type, $bytes);
        }

        return is_array($result) ? array_shift($result) : $result;
    }

Unpack a bit integer as big endian long

@return mixed
@throws ProtocolException


protected static function checkLen(string $type, string $bytes): void
    {
        $expectedLength = 0;

        switch ($type) {
            case self::BIT_B64:
                $expectedLength = 8;
                break;
            case self::BIT_B32:
                $expectedLength = 4;
                break;
            case self::BIT_B16:
                $expectedLength = 2;
                break;
            case self::BIT_B16_SIGNED:
                $expectedLength = 2;
                break;
            case self::BIT_B8:
                $expectedLength = 1;
                break;
        }

        $length = strlen($bytes);

        if ($length !== $expectedLength) {
            throw new ProtocolException('unpack failed. string(raw) length is ' . $length . ' , TO ' . $type);
        }
    }

protected
static
function
checkLen
(
string
$
type
,
string
$
bytes
)
:
void
{
$
expectedLength
=
0
;
switch
(
$
type
)
{
case
self
::
BIT_B64
:
$
expectedLength
=
8
;
break
;
case
self
::
BIT_B32
:
$
expectedLength
=
4
;
break
;
case
self
::
BIT_B16
:
$
expectedLength
=
2
;
break
;
case
self
::
BIT_B16_SIGNED
:
$
expectedLength
=
2
;
break
;
case
self
::
BIT_B8
:
$
expectedLength
=
1
;
break
;
}
$
length
=
strlen
(
$
bytes
)
;
if
(
$
length
!==
$
expectedLength
)
{
throw
new
ProtocolException
(
'unpack failed. string(raw) length is '
.
$
length
.
' , TO '
.
$
type
)
;
}
}

check unpack bit is valid

@throws ProtocolException


public static function isSystemLittleEndian(): bool
    {
        // If we don't know if our system is big endian or not yet...
        if (self::$isLittleEndianSystem === null) {
            [$endianTest] = array_values(unpack('L1L', pack('V', 1)));

            self::$isLittleEndianSystem = (int) $endianTest === 1;
        }

        return self::$isLittleEndianSystem;
    }

Determines if the computer currently running this code is big endian or little endian.

Determines if the computer currently running this code is big endian or little endian .


public function getApiVersion(int $apikey): int
    {
        switch ($apikey) {
            case self::METADATA_REQUEST:
                return self::API_VERSION0;
            case self::PRODUCE_REQUEST:
                if (version_compare($this->version, '0.10.0') >= 0) {
                    return self::API_VERSION2;
                }

                if (version_compare($this->version, '0.9.0') >= 0) {
                    return self::API_VERSION1;
                }

                return self::API_VERSION0;
            case self::FETCH_REQUEST:
                if (version_compare($this->version, '0.10.0') >= 0) {
                    return self::API_VERSION2;
                }

                if (version_compare($this->version, '0.9.0') >= 0) {
                    return self::API_VERSION1;
                }

                return self::API_VERSION0;
            case self::OFFSET_REQUEST:
//                TODO: make it compatible with V1 of OFFSET_REQUEST
//                if (version_compare($this->version, '0.10.1.0') >= 0) {
//                    return self::API_VERSION1;
//                } else {
//                    return self::API_VERSION0;
//                }
                return self::API_VERSION0;
            case self::GROUP_COORDINATOR_REQUEST:
                return self::API_VERSION0;
            case self::OFFSET_COMMIT_REQUEST:
                if (version_compare($this->version, '0.9.0') >= 0) {
                    return self::API_VERSION2;
                }

                if (version_compare($this->version, '0.8.2') >= 0) {
                    return self::API_VERSION1;
                }

                return self::API_VERSION0; // supported in 0.8.1 or later
            case self::OFFSET_FETCH_REQUEST:
                if (version_compare($this->version, '0.8.2') >= 0) {
                    return self::API_VERSION1; // Offset Fetch Request v1 will fetch offset from Kafka
                }

                return self::API_VERSION0;//Offset Fetch Request v0 will fetch offset from zookeeper
            case self::JOIN_GROUP_REQUEST:
                if (version_compare($this->version, '0.10.1.0') >= 0) {
                    return self::API_VERSION1;
                }

                return self::API_VERSION0; // supported in 0.9.0.0 and greater
            case self::SYNC_GROUP_REQUEST:
                return self::API_VERSION0;
            case self::HEART_BEAT_REQUEST:
                return self::API_VERSION0;
            case self::LEAVE_GROUP_REQUEST:
                return self::API_VERSION0;
            case self::LIST_GROUPS_REQUEST:
                return self::API_VERSION0;
            case self::DESCRIBE_GROUPS_REQUEST:
                return self::API_VERSION0;
        }

        // default
        return self::API_VERSION0;
    }

//                    return self::API_VERSION1;

//                    return self::API_VERSION0;

Get kafka api version according to specify kafka broker version


public static function getApiText(int $apikey): string
    {
        $apis = [
            self::PRODUCE_REQUEST           => 'ProduceRequest',
            self::FETCH_REQUEST             => 'FetchRequest',
            self::OFFSET_REQUEST            => 'OffsetRequest',
            self::METADATA_REQUEST          => 'MetadataRequest',
            self::OFFSET_COMMIT_REQUEST     => 'OffsetCommitRequest',
            self::OFFSET_FETCH_REQUEST      => 'OffsetFetchRequest',
            self::GROUP_COORDINATOR_REQUEST => 'GroupCoordinatorRequest',
            self::JOIN_GROUP_REQUEST        => 'JoinGroupRequest',
            self::HEART_BEAT_REQUEST        => 'HeartbeatRequest',
            self::LEAVE_GROUP_REQUEST       => 'LeaveGroupRequest',
            self::SYNC_GROUP_REQUEST        => 'SyncGroupRequest',
            self::DESCRIBE_GROUPS_REQUEST   => 'DescribeGroupsRequest',
            self::LIST_GROUPS_REQUEST       => 'ListGroupsRequest',
            self::SASL_HAND_SHAKE_REQUEST   => 'SaslHandShakeRequest',
            self::API_VERSIONS_REQUEST      => 'ApiVersionsRequest',
        ];

        return $apis[$apikey] ?? 'Unknown message';
    }

public
static
function
getApiText
(
int
$
apikey
)
:
string
{
$
apis
=
[
self
::
PRODUCE_REQUEST
=>
'ProduceRequest'
,
self
::
FETCH_REQUEST
=>
'FetchRequest'
,
self
::
OFFSET_REQUEST
=>
'OffsetRequest'
,
self
::
METADATA_REQUEST
=>
'MetadataRequest'
,
self
::
OFFSET_COMMIT_REQUEST
=>
'OffsetCommitRequest'
,
self
::
OFFSET_FETCH_REQUEST
=>
'OffsetFetchRequest'
,
self
::
GROUP_COORDINATOR_REQUEST
=>
'GroupCoordinatorRequest'
,
self
::
JOIN_GROUP_REQUEST
=>
'JoinGroupRequest'
,
self
::
HEART_BEAT_REQUEST
=>
'HeartbeatRequest'
,
self
::
LEAVE_GROUP_REQUEST
=>
'LeaveGroupRequest'
,
self
::
SYNC_GROUP_REQUEST
=>
'SyncGroupRequest'
,
self
::
DESCRIBE_GROUPS_REQUEST
=>
'DescribeGroupsRequest'
,
self
::
LIST_GROUPS_REQUEST
=>
'ListGroupsRequest'
,
self
::
SASL_HAND_SHAKE_REQUEST
=>
'SaslHandShakeRequest'
,
self
::
API_VERSIONS_REQUEST
=>
'ApiVersionsRequest'
,
]
;
return
$
apis
[
$
apikey
]
??
'Unknown message'
;
}


public function before($methods, $pattern, $fn)
    {
        $pattern = $this->baseRoute . '/' . trim($pattern, '/');
        $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;

        foreach (explode('|', $methods) as $method) {
            $this->beforeRoutes[$method][] = [
                'pattern' => $pattern,
                'fn' => $fn,
            ];
        }
    }

public function before ( $ methods , $ pattern , $ fn ) { $ pattern = $ this -> baseRoute . '/' . trim ( $ pattern , '/' ) ; $ pattern = $ this -> baseRoute ? rtrim ( $ pattern , '/' ) : $ pattern ; foreach ( explode ( '|' , $ methods ) as $ method ) { $ this -> beforeRoutes [ $ method ] [ ] = [ 'pattern' => $ pattern , 'fn' => $ fn , ] ; } }

Store a before middleware route and a handling function to be executed when accessed using one of the specified methods.

@param string          $methods Allowed methods, | delimited
@param string          $pattern A route pattern such as /about/system
@param object|callable $fn      The handling function to be executed

Store a before middleware route and a handling function to be executed when accessed using one of the specified methods .


public function match($methods, $pattern, $fn)
    {
        $pattern = $this->baseRoute . '/' . trim($pattern, '/');
        $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;

        foreach (explode('|', $methods) as $method) {
            $this->afterRoutes[$method][] = [
                'pattern' => $pattern,
                'fn' => $fn,
            ];
        }
    }

public function match ( $ methods , $ pattern , $ fn ) { $ pattern = $ this -> baseRoute . '/' . trim ( $ pattern , '/' ) ; $ pattern = $ this -> baseRoute ? rtrim ( $ pattern , '/' ) : $ pattern ; foreach ( explode ( '|' , $ methods ) as $ method ) { $ this -> afterRoutes [ $ method ] [ ] = [ 'pattern' => $ pattern , 'fn' => $ fn , ] ; } }

Store a route and a handling function to be executed when accessed using one of the specified methods.

@param string          $methods Allowed methods, | delimited
@param string          $pattern A route pattern such as /about/system
@param object|callable $fn      The handling function to be executed

Store a route and a handling function to be executed when accessed using one of the specified methods .


public function mount($baseRoute, $fn)
    {
        // Track current base route
        $curBaseRoute = $this->baseRoute;

        // Build new base route string
        $this->baseRoute .= $baseRoute;

        // Call the callable
        call_user_func($fn);

        // Restore original base route
        $this->baseRoute = $curBaseRoute;
    }

public
function
mount
(
$
baseRoute
,
$
fn
)
{
// Track current base route
$
curBaseRoute
=
$
this
->
baseRoute
;
// Build new base route string
$
this
->
baseRoute
.=
$
baseRoute
;
// Call the callable
call_user_func
(
$
fn
)
;
// Restore original base route
$
this
->
baseRoute
=
$
curBaseRoute
;
}

Mounts a collection of callbacks onto a base route.

@param string   $baseRoute The route sub pattern to mount the callbacks on
@param callable $fn        The callback method

Mounts a collection of callbacks onto a base route .


public function getRequestMethod()
    {
        // Take the method as found in $_SERVER
        $method = $_SERVER['REQUEST_METHOD'];

        // If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification
        // @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
        if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
            ob_start();
            $method = 'GET';
        }

        // If it's a POST request, check for a method override header
        elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
            $headers = $this->getRequestHeaders();
            if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], ['PUT', 'DELETE', 'PATCH'])) {
                $method = $headers['X-HTTP-Method-Override'];
            }
        }

        return $method;
    }

Get the request method used, taking overrides into account.

@return string The Request method to handle

Get the request method used taking overrides into account .


public function getBasePath()
    {
        // Check if server base path is defined, if not define it.
        if ($this->serverBasePath === null) {
            $this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/';
        }

        return $this->serverBasePath;
    }

Return server base Path, and define it if isn't defined.

@return string

Return server base Path and define it if isn t defined .


public function map($pattern, $callback, $pass_route = false) {
        $url = $pattern;
        $methods = array('*');

        if (strpos($pattern, ' ') !== false) {
            list($method, $url) = explode(' ', trim($pattern), 2);

            $methods = explode('|', $method);
        }

        $this->routes[] = new Route($url, $callback, $methods, $pass_route);
    }

public
function
map
(
$
pattern
,
$
callback
,
$
pass_route
=
false
)
{
$
url
=
$
pattern
;
$
methods
=
array
(
'*'
)
;
if
(
strpos
(
$
pattern
,
' '
)
!==
false
)
{
list
(
$
method
,
$
url
)
=
explode
(
' '
,
trim
(
$
pattern
)
,
2
)
;
$
methods
=
explode
(
'|'
,
$
method
)
;
}
$
this
->
routes
[
]
=
new
Route
(
$
url
,
$
callback
,
$
methods
,
$
pass_route
)
;
}

Maps a URL pattern to a callback function.

@param string $pattern URL pattern to match
@param callback $callback Callback function
@param boolean $pass_route Pass the matching route object to the callback

Maps a URL pattern to a callback function .


public function route(Request $request) {
        $url_decoded = urldecode( $request->url );
        while ($route = $this->current()) {
            if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
                return $route;
            }
            $this->next();
        }

        return false;
    }

public function route ( Request $ request ) { $ url_decoded = urldecode ( $ request -> url ) ; while ( $ route = $ this -> current ( ) ) { if ( $ route !== false && $ route -> matchMethod ( $ request -> method ) && $ route -> matchUrl ( $ url_decoded , $ this -> case_sensitive ) ) { return $ route ; } $ this -> next ( ) ; } return false ; }

Routes the current request.

@param Request $request Request object
@return Route|bool Matching route or false if no match


public function current() {
        return isset($this->routes[$this->index]) ? $this->routes[$this->index] : false;
    }

public function current ( ) { return isset ( $ this -> routes [ $ this -> index ] ) ? $ this -> routes [ $ this -> index ] : false ; }


public function matchUrl($url, $case_sensitive = false) {
        // Wildcard or exact match
        if ($this->pattern === '*' || $this->pattern === $url) {
            return true;
        }

        $ids = array();
        $last_char = substr($this->pattern, -1);

        // Get splat
        if ($last_char === '*') {
            $n = 0;
            $len = strlen($url);
            $count = substr_count($this->pattern, '/');

            for ($i = 0; $i < $len; $i++) {
                if ($url[$i] == '/') $n++;
                if ($n == $count) break;
            }

            $this->splat = (string)substr($url, $i+1);
        }

        // Build the regex for matching
        $regex = str_replace(array(')','/*'), array(')?','(/?|/.*?)'), $this->pattern);

        $regex = preg_replace_callback(
            '#@([\w]+)(:([^/\(\)]*))?#',
            function($matches) use (&$ids) {
                $ids[$matches[1]] = null;
                if (isset($matches[3])) {
                    return '(?P<'.$matches[1].'>'.$matches[3].')';
                }
                return '(?P<'.$matches[1].'>[^/\?]+)';
            },
            $regex
        );

        // Fix trailing slash
        if ($last_char === '/') {
            $regex .= '?';
        }
        // Allow trailing slash
        else {
            $regex .= '/?';
        }

        // Attempt to match route and named parameters
        if (preg_match('#^'.$regex.'(?:\?.*)?$#'.(($case_sensitive) ? '' : 'i'), $url, $matches)) {
            foreach ($ids as $k => $v) {
                $this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
            }

            $this->regex = $regex;

            return true;
        }

        return false;
    }

Checks if a URL matches the route pattern. Also parses named parameters in the URL.

@param string $url Requested URL
@param boolean $case_sensitive Case sensitive matching
@return boolean Match status

Checks if a URL matches the route pattern . Also parses named parameters in the URL .


public function run($name, array $params = array()) {
        $output = '';

        // Run pre-filters
        if (!empty($this->filters[$name]['before'])) {
            $this->filter($this->filters[$name]['before'], $params, $output);
        }

        // Run requested method
        $output = $this->execute($this->get($name), $params);

        // Run post-filters
        if (!empty($this->filters[$name]['after'])) {
            $this->filter($this->filters[$name]['after'], $params, $output);
        }

        return $output;
    }

public
function
run
(
$
name
,
array
$
params
=
array
(
)
)
{
$
output
=
''
;
// Run pre-filters
if
(
!
empty
(
$
this
->
filters
[
$
name
]
[
'before'
]
)
)
{
$
this
->
filter
(
$
this
->
filters
[
$
name
]
[
'before'
]
,
$
params
,
$
output
)
;
}
// Run requested method
$
output
=
$
this
->
execute
(
$
this
->
get
(
$
name
)
,
$
params
)
;
// Run post-filters
if
(
!
empty
(
$
this
->
filters
[
$
name
]
[
'after'
]
)
)
{
$
this
->
filter
(
$
this
->
filters
[
$
name
]
[
'after'
]
,
$
params
,
$
output
)
;
}
return
$
output
;
}

Dispatches an event.

@param string $name Event name
@param array $params Callback parameters
@return string Output of callback
@throws \Exception


public function get($name) {
        return isset($this->events[$name]) ? $this->events[$name] : null;
    }

public function get ( $ name ) { return isset ( $ this -> events [ $ name ] ) ? $ this -> events [ $ name ] : null ; }

Gets an assigned callback.

@param string $name Event name
@return callback $callback Callback function


public function clear($name = null) {
        if ($name !== null) {
            unset($this->events[$name]);
            unset($this->filters[$name]);
        }
        else {
            $this->events = array();
            $this->filters = array();
        }
    }

public function clear ( $ name = null ) { if ( $ name !== null ) { unset ( $ this -> events [ $ name ] ) ; unset ( $ this -> filters [ $ name ] ) ; } else { $ this -> events = array ( ) ; $ this -> filters = array ( ) ; } }

Clears an event. If no name is given,
all events are removed.

@param string $name Event name

Clears an event . If no name is given all events are removed .


public function filter($filters, &$params, &$output) {
        $args = array(&$params, &$output);
        foreach ($filters as $callback) {
            $continue = $this->execute($callback, $args);
            if ($continue === false) break;
        }
    }

public function filter ( $ filters , & $ params , & $ output ) { $ args = array ( & $ params , & $ output ) ; foreach ( $ filters as $ callback ) { $ continue = $ this -> execute ( $ callback , $ args ) ; if ( $ continue === false ) break ; } }

Executes a chain of method filters.

@param array $filters Chain of filters
@param array $params Method parameters
@param mixed $output Method output
@throws \Exception


public static function execute($callback, array &$params = array()) {
        if (is_callable($callback)) {
            return is_array($callback) ?
                self::invokeMethod($callback, $params) :
                self::callFunction($callback, $params);
        }
        else {
            throw new \Exception('Invalid callback specified.');
        }
    }

public
static
function
execute
(
$
callback
,
array
&
$
params
=
array
(
)
)
{
if
(
is_callable
(
$
callback
)
)
{
return
is_array
(
$
callback
)
self
::
invokeMethod
(
$
callback
,
$
params
)
:
self
::
callFunction
(
$
callback
,
$
params
)
;
}
else
{
throw
new
\
Exception
(
'Invalid callback specified.'
)
;
}
}

Executes a callback function.

@param callback $callback Callback function
@param array $params Function parameters
@return mixed Function results
@throws \Exception


public static function callFunction($func, array &$params = array()) {
        // Call static method
        if (is_string($func) && strpos($func, '::') !== false) {
            return call_user_func_array($func, $params);
        }

        switch (count($params)) {
            case 0:
                return $func();
            case 1:
                return $func($params[0]);
            case 2:
                return $func($params[0], $params[1]);
            case 3:
                return $func($params[0], $params[1], $params[2]);
            case 4:
                return $func($params[0], $params[1], $params[2], $params[3]);
            case 5:
                return $func($params[0], $params[1], $params[2], $params[3], $params[4]);
            default:
                return call_user_func_array($func, $params);
        }
    }

public
static
function
callFunction
(
$
func
,
array
&
$
params
=
array
(
)
)
{
// Call static method
if
(
is_string
(
$
func
)
&&
strpos
(
$
func
,
'::'
)
!==
false
)
{
return
call_user_func_array
(
$
func
,
$
params
)
;
}
switch
(
count
(
$
params
)
)
{
case
0
:
return
$
func
(
)
;
case
1
:
return
$
func
(
$
params
[
0
]
)
;
case
2
:
return
$
func
(
$
params
[
0
]
,
$
params
[
1
]
)
;
case
3
:
return
$
func
(
$
params
[
0
]
,
$
params
[
1
]
,
$
params
[
2
]
)
;
case
4
:
return
$
func
(
$
params
[
0
]
,
$
params
[
1
]
,
$
params
[
2
]
,
$
params
[
3
]
)
;
case
5
:
return
$
func
(
$
params
[
0
]
,
$
params
[
1
]
,
$
params
[
2
]
,
$
params
[
3
]
,
$
params
[
4
]
)
;
default
:
return
call_user_func_array
(
$
func
,
$
params
)
;
}
}

Calls a function.

@param string $func Name of function to call
@param array $params Function parameters
@return mixed Function results


public static function invokeMethod($func, array &$params = array()) {
        list($class, $method) = $func;

        $instance = is_object($class);

        switch (count($params)) {
            case 0:
                return ($instance) ?
                    $class->$method() :
                    $class::$method();
            case 1:
                return ($instance) ?
                    $class->$method($params[0]) :
                    $class::$method($params[0]);
            case 2:
                return ($instance) ?
                    $class->$method($params[0], $params[1]) :
                    $class::$method($params[0], $params[1]);
            case 3:
                return ($instance) ?
                    $class->$method($params[0], $params[1], $params[2]) :
                    $class::$method($params[0], $params[1], $params[2]);
            case 4:
                return ($instance) ?
                    $class->$method($params[0], $params[1], $params[2], $params[3]) :
                    $class::$method($params[0], $params[1], $params[2], $params[3]);
            case 5:
                return ($instance) ?
                    $class->$method($params[0], $params[1], $params[2], $params[3], $params[4]) :
                    $class::$method($params[0], $params[1], $params[2], $params[3], $params[4]);
            default:
                return call_user_func_array($func, $params);
        }
    }

public static function invokeMethod ( $ func , array & $ params = array ( ) ) { list ( $ class , $ method ) = $ func ; $ instance = is_object ( $ class ) ; switch ( count ( $ params ) ) { case 0 : return ( $ instance ) ? $ class -> $ method ( ) : $ class :: $ method ( ) ; case 1 : return ( $ instance ) ? $ class -> $ method ( $ params [ 0 ] ) : $ class :: $ method ( $ params [ 0 ] ) ; case 2 : return ( $ instance ) ? $ class -> $ method ( $ params [ 0 ] , $ params [ 1 ] ) : $ class :: $ method ( $ params [ 0 ] , $ params [ 1 ] ) ; case 3 : return ( $ instance ) ? $ class -> $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] ) : $ class :: $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] ) ; case 4 : return ( $ instance ) ? $ class -> $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] , $ params [ 3 ] ) : $ class :: $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] , $ params [ 3 ] ) ; case 5 : return ( $ instance ) ? $ class -> $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] , $ params [ 3 ] , $ params [ 4 ] ) : $ class :: $ method ( $ params [ 0 ] , $ params [ 1 ] , $ params [ 2 ] , $ params [ 3 ] , $ params [ 4 ] ) ; default : return call_user_func_array ( $ func , $ params ) ; } }

Invokes a method.

@param mixed $func Class method
@param array $params Class method parameters
@return mixed Function results